Merge pull request #22216 from demeritcowboy/th-br
[civicrm-core.git] / Civi / WorkflowMessage / Traits / AddressingTrait.php
CommitLineData
02a47bd1
TO
1<?php
2
3/*
4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
6 | |
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
10 +--------------------------------------------------------------------+
11 */
12
13namespace Civi\WorkflowMessage\Traits;
14
15const ADDRESS_STORAGE_FMT = 'rfc822';
16const ADDRESS_EXPORT_FMT = 'rfc822';
17
18/**
19 * Define the $to, $from, $replyTo, $cc, and $bcc fields to a WorkflowMessage class.
20 *
21 * Email addresses may be get or set in any of these formats:
22 *
23 * - rfc822 (string): RFC822-style, e.g. 'Full Name <user@example.com>'
24 * - record (array): Pair of name+email, e.g. ['name' => 'Full Name', 'email' => 'user@example.com']
25 * - records: (array) List of records, keyed sequentially.
26 */
27trait AddressingTrait {
28
29 /**
30 * The primary email recipient (single address).
31 *
32 * @var string|null
33 * Ex: '"Foo Bar" <foo.bar@example.com>'
34 * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"]
35 *
36 * The "To:" address is mapped to the "envelope" scope. The existing
37 * envelope format treats this as a pair of fields [toName,toEmail].
38 * Consequently, we only support one "To:" address, and it uses a
39 * special import/export method.
40 */
41 protected $to;
42
43 /**
44 * The email sender (single address).
45 *
46 * @var string|null
47 * Ex: '"Foo Bar" <foo.bar@example.com>'
48 * @scope envelope
49 */
50 protected $from;
51
52 /**
53 * The email sender's Reply-To (single address).
54 *
55 * @var string|null
56 * Ex: '"Foo Bar" <foo.bar@example.com>'
57 * @scope envelope
58 */
59 protected $replyTo;
60
61 /**
62 * Additional recipients (multiple addresses).
63 *
64 * @var string|null
65 * Ex: '"Foo Bar" <foo.bar@example.com>, "Whiz Bang" <whiz.bang@example.com>'
66 * Ex: [['name' => 'Foo Bar', 'email' => 'foo.bar@example.com'], ['name' => 'Whiz Bang', 'email' => 'whiz.bang@example.com']]
67 * @scope envelope
68 */
69 protected $cc;
70
71 /**
72 * Additional recipients (multiple addresses).
73 *
74 * @var string|null
75 * Ex: '"Foo Bar" <foo.bar@example.com>, "Whiz Bang" <whiz.bang@example.com>'
76 * Ex: [['name' => 'Foo Bar', 'email' => 'foo.bar@example.com'], ['name' => 'Whiz Bang', 'email' => 'whiz.bang@example.com']]
77 * @scope envelope
78 */
79 protected $bcc;
80
81 /**
82 * Get the list of "To:" addresses.
83 *
84 * Note: This returns only
85 *
86 * @param string $format
87 * Ex: 'rfc822', 'records', 'record'
88 * @return array|string
89 * Ex: '"Foo Bar" <foo.bar@example.com>'
90 * Ex: ['name' => 'Foo Bar', 'email' => 'foo.bar@example.com']
91 */
92 public function getTo($format = ADDRESS_EXPORT_FMT) {
93 return $this->formatAddress($format, $this->to);
94 }
95
96 /**
97 * Get the "From:" address.
98 *
99 * @param string $format
100 * Ex: 'rfc822', 'records', 'record'
101 * @return array|string
102 * The "From" address. If none set, this will be empty ([]).
103 * Ex: '"Foo Bar" <foo.bar@example.com>'
104 * Ex: ['name' => 'Foo Bar', 'email' => 'foo.bar@example.com']
105 */
106 public function getFrom($format = ADDRESS_EXPORT_FMT) {
107 return $this->formatAddress($format, $this->from);
108 }
109
110 /**
111 * Get the "Reply-To:" address.
112 *
113 * @param string $format
114 * Ex: 'rfc822', 'records', 'record'
115 * @return array|string
116 * The "From" address. If none set, this will be empty ([]).
117 * Ex: '"Foo Bar" <foo.bar@example.com>'
118 * Ex: ['name' => 'Foo Bar', 'email' => 'foo.bar@example.com']
119 */
120 public function getReplyTo($format = ADDRESS_EXPORT_FMT) {
121 return $this->formatAddress($format, $this->replyTo);
122 }
123
124 /**
125 * Get the list of "Cc:" addresses.
126 *
127 * @param string $format
128 * Ex: 'rfc822', 'records', 'record'
129 * @return array|string
130 * List of addresses.
131 * Ex: 'First <first@example.com>, second@example.com'
132 * Ex: [['name' => 'First', 'email' => 'first@example.com'], ['email' => 'second@example.com']]
133 */
134 public function getCc($format = ADDRESS_EXPORT_FMT) {
135 return $this->formatAddress($format, $this->cc);
136 }
137
138 /**
139 * Get the list of "Bcc:" addresses.
140 *
141 * @param string $format
142 * Ex: 'rfc822', 'records', 'record'
143 * @return array|string
144 * List of addresses.
145 * Ex: 'First <first@example.com>, second@example.com'
146 * Ex: [['name' => 'First', 'email' => 'first@example.com'], ['email' => 'second@example.com']]
147 */
148 public function getBcc($format = ADDRESS_EXPORT_FMT) {
149 return $this->formatAddress($format, $this->bcc);
150 }
151
152 /**
153 * @param string|array $address
154 * Ex: '"Foo Bar" <foo.bar@example.com>'
155 * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"]
156 * @return $this
157 */
158 public function setFrom($address) {
159 $this->from = $this->formatAddress(ADDRESS_STORAGE_FMT, $address);
160 return $this;
161 }
162
163 /**
164 * @param string|array $address
165 * Ex: '"Foo Bar" <foo.bar@example.com>'
166 * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"]
167 * @return $this
168 */
169 public function setTo($address) {
170 $this->to = $this->formatAddress(ADDRESS_STORAGE_FMT, $address);
171 return $this;
172 }
173
174 /**
175 * @param string|array $address
176 * Ex: '"Foo Bar" <foo.bar@example.com>'
177 * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"]
178 * @return $this
179 */
180 public function setReplyTo($address) {
181 $this->replyTo = $this->formatAddress(ADDRESS_STORAGE_FMT, $address);
182 return $this;
183 }
184
185 /**
186 * Set the "CC:" list.
187 *
188 * @param string|array $address
189 * Ex: '"Foo Bar" <foo.bar@example.com>'
190 * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"]
191 * Ex: [['email' => 'first@example.com'], ['email' => 'second@example.com']]
192 * @return $this
193 */
194 public function setCc($address) {
195 $this->cc = $this->formatAddress(ADDRESS_STORAGE_FMT, $address);
196 return $this;
197 }
198
199 /**
200 * Set the "BCC:" list.
201 *
202 * @param string|array $address
203 * Ex: '"Foo Bar" <foo.bar@example.com>'
204 * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"]
205 * Ex: [['email' => 'first@example.com'], ['email' => 'second@example.com']]
206 * @return $this
207 */
208 public function setBcc($address) {
209 $this->bcc = $this->formatAddress(ADDRESS_STORAGE_FMT, $address);
210 return $this;
211 }
212
213 /**
214 * Add another "CC:" address.
215 *
216 * @param string|array $address
217 * Ex: '"Foo Bar" <foo.bar@example.com>'
218 * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"]
219 * Ex: [['email' => 'first@example.com'], ['email' => 'second@example.com']]
220 * @return $this
221 */
222 public function addCc($address) {
223 return $this->setCc(array_merge(
224 $this->getCc('records'),
225 $this->formatAddress('records', $address)
226 ));
227 }
228
229 /**
230 * Add another "BCC:" address.
231 *
232 * @param string|array $address
233 * Ex: '"Foo Bar" <foo.bar@example.com>'
234 * Ex: ['name' => "Foo Bar", "email" => "foo.bar@example.com"]
235 * Ex: [['email' => 'first@example.com'], ['email' => 'second@example.com']]
236 * @return $this
237 */
238 public function addBcc($address) {
239 return $this->setBcc(array_merge(
240 $this->getBcc('records'),
241 $this->formatAddress('records', $address)
242 ));
243 }
244
245 /**
246 * Plugin to `WorkflowMessageInterface::import()` and handle toEmail/toName.
247 *
248 * @param array $values
249 * @see \Civi\WorkflowMessage\Traits\ReflectiveWorkflowTrait::import
250 */
251 protected function importExtraEnvelope_toAddress(array &$values): void {
252 if (array_key_exists('toEmail', $values) || array_key_exists('toName', $values)) {
253 $this->setTo(['name' => $values['toName'] ?? NULL, 'email' => $values['toEmail'] ?? NULL]);
254 unset($values['toName']);
255 unset($values['toEmail']);
256 }
257 }
258
259 /**
260 * Plugin to `WorkflowMessageInterface::export()` and handle toEmail/toName.
261 *
262 * @param array $values
263 * @see \Civi\WorkflowMessage\Traits\ReflectiveWorkflowTrait::export
264 */
265 protected function exportExtraEnvelope_toAddress(array &$values): void {
266 $addr = $this->getTo('record');
267 $values['toName'] = $addr['name'] ?? NULL;
268 $values['toEmail'] = $addr['email'] ?? NULL;
269 }
270
271 /**
272 * Convert an address to the desired format.
273 *
274 * @param string $newFormat
275 * Ex: 'rfc822', 'records', 'record'
276 * @param array|string $mixed
277 * @return array|string|null
278 */
279 private function formatAddress($newFormat, $mixed) {
280 if ($mixed === NULL) {
281 return NULL;
282 }
283
284 $oldFormat = is_string($mixed) ? 'rfc822' : (array_key_exists('email', $mixed) ? 'record' : 'records');
285 if ($oldFormat === $newFormat) {
286 return $mixed;
287 }
288
289 $recordToObj = function (?array $record) {
290 return new \ezcMailAddress($record['email'], $record['name'] ?? '');
291 };
292 $objToRecord = function (?\ezcMailAddress $addr) {
293 return is_null($addr) ? NULL : ['email' => $addr->email, 'name' => $addr->name];
294 };
295
296 // Convert $mixed to intermediate format (ezcMailAddress[] $objects) and then to final format.
297
298 /** @var \ezcMailAddress[] $objects */
299
300 switch ($oldFormat) {
301 case 'rfc822':
302 $objects = \ezcMailTools::parseEmailAddresses($mixed);
303 break;
304
305 case 'record':
306 $objects = [$recordToObj($mixed)];
307 break;
308
309 case 'records':
310 $objects = array_map($recordToObj, $mixed);
311 break;
312
313 default:
314 throw new \RuntimeException("Unrecognized source format: $oldFormat");
315 }
316
317 switch ($newFormat) {
318 case 'rfc822':
319 // We use `implode(map(composeEmailAddress))` instead of `composeEmailAddresses` because the latter has header-line-wrapping.
320 return implode(', ', array_map(['ezcMailTools', 'composeEmailAddress'], $objects));
321
322 case 'record':
323 if (count($objects) > 1) {
324 throw new \RuntimeException("Cannot convert email addresses to record format. Too many addresses.");
325 }
326 return $objToRecord($objects[0] ?? NULL);
327
328 case 'records':
329 return array_map($objToRecord, $objects);
330
331 default:
332 throw new \RuntimeException("Unrecognized output format: $newFormat");
333 }
334 }
335
336}