PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 2.8.16
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v2.8.16
3.13.4 3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 All 262 releases
wpfunnels / vendor / symfony / mime / Email.php

Email.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 2.8.16, at vendor/symfony/mime/Email.php

631 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Mime;
13
14 use Symfony\Component\Mime\Exception\LogicException;
15 use Symfony\Component\Mime\Part\AbstractPart;
16 use Symfony\Component\Mime\Part\DataPart;
17 use Symfony\Component\Mime\Part\Multipart\AlternativePart;
18 use Symfony\Component\Mime\Part\Multipart\MixedPart;
19 use Symfony\Component\Mime\Part\Multipart\RelatedPart;
20 use Symfony\Component\Mime\Part\TextPart;
21
22 /**
23 * @author Fabien Potencier <[email protected]>
24 */
25 class Email extends Message
26 {
27 public const PRIORITY_HIGHEST = 1;
28 public const PRIORITY_HIGH = 2;
29 public const PRIORITY_NORMAL = 3;
30 public const PRIORITY_LOW = 4;
31 public const PRIORITY_LOWEST = 5;
32
33 private const PRIORITY_MAP = [
34 self::PRIORITY_HIGHEST => 'Highest',
35 self::PRIORITY_HIGH => 'High',
36 self::PRIORITY_NORMAL => 'Normal',
37 self::PRIORITY_LOW => 'Low',
38 self::PRIORITY_LOWEST => 'Lowest',
39 ];
40
41 private $text;
42 private $textCharset;
43 private $html;
44 private $htmlCharset;
45 private $attachments = [];
46 /**
47 * @var AbstractPart|null
48 */
49 private $cachedBody; // Used to avoid wrong body hash in DKIM signatures with multiple parts (e.g. HTML + TEXT) due to multiple boundaries.
50
51 /**
52 * @return $this
53 */
54 public function subject(string $subject)
55 {
56 return $this->setHeaderBody('Text', 'Subject', $subject);
57 }
58
59 public function getSubject(): ?string
60 {
61 return $this->getHeaders()->getHeaderBody('Subject');
62 }
63
64 /**
65 * @return $this
66 */
67 public function date(\DateTimeInterface $dateTime)
68 {
69 return $this->setHeaderBody('Date', 'Date', $dateTime);
70 }
71
72 public function getDate(): ?\DateTimeImmutable
73 {
74 return $this->getHeaders()->getHeaderBody('Date');
75 }
76
77 /**
78 * @param Address|string $address
79 *
80 * @return $this
81 */
82 public function returnPath($address)
83 {
84 return $this->setHeaderBody('Path', 'Return-Path', Address::create($address));
85 }
86
87 public function getReturnPath(): ?Address
88 {
89 return $this->getHeaders()->getHeaderBody('Return-Path');
90 }
91
92 /**
93 * @param Address|string $address
94 *
95 * @return $this
96 */
97 public function sender($address)
98 {
99 return $this->setHeaderBody('Mailbox', 'Sender', Address::create($address));
100 }
101
102 public function getSender(): ?Address
103 {
104 return $this->getHeaders()->getHeaderBody('Sender');
105 }
106
107 /**
108 * @param Address|string ...$addresses
109 *
110 * @return $this
111 */
112 public function addFrom(...$addresses)
113 {
114 return $this->addListAddressHeaderBody('From', $addresses);
115 }
116
117 /**
118 * @param Address|string ...$addresses
119 *
120 * @return $this
121 */
122 public function from(...$addresses)
123 {
124 return $this->setListAddressHeaderBody('From', $addresses);
125 }
126
127 /**
128 * @return Address[]
129 */
130 public function getFrom(): array
131 {
132 return $this->getHeaders()->getHeaderBody('From') ?: [];
133 }
134
135 /**
136 * @param Address|string ...$addresses
137 *
138 * @return $this
139 */
140 public function addReplyTo(...$addresses)
141 {
142 return $this->addListAddressHeaderBody('Reply-To', $addresses);
143 }
144
145 /**
146 * @param Address|string ...$addresses
147 *
148 * @return $this
149 */
150 public function replyTo(...$addresses)
151 {
152 return $this->setListAddressHeaderBody('Reply-To', $addresses);
153 }
154
155 /**
156 * @return Address[]
157 */
158 public function getReplyTo(): array
159 {
160 return $this->getHeaders()->getHeaderBody('Reply-To') ?: [];
161 }
162
163 /**
164 * @param Address|string ...$addresses
165 *
166 * @return $this
167 */
168 public function addTo(...$addresses)
169 {
170 return $this->addListAddressHeaderBody('To', $addresses);
171 }
172
173 /**
174 * @param Address|string ...$addresses
175 *
176 * @return $this
177 */
178 public function to(...$addresses)
179 {
180 return $this->setListAddressHeaderBody('To', $addresses);
181 }
182
183 /**
184 * @return Address[]
185 */
186 public function getTo(): array
187 {
188 return $this->getHeaders()->getHeaderBody('To') ?: [];
189 }
190
191 /**
192 * @param Address|string ...$addresses
193 *
194 * @return $this
195 */
196 public function addCc(...$addresses)
197 {
198 return $this->addListAddressHeaderBody('Cc', $addresses);
199 }
200
201 /**
202 * @param Address|string ...$addresses
203 *
204 * @return $this
205 */
206 public function cc(...$addresses)
207 {
208 return $this->setListAddressHeaderBody('Cc', $addresses);
209 }
210
211 /**
212 * @return Address[]
213 */
214 public function getCc(): array
215 {
216 return $this->getHeaders()->getHeaderBody('Cc') ?: [];
217 }
218
219 /**
220 * @param Address|string ...$addresses
221 *
222 * @return $this
223 */
224 public function addBcc(...$addresses)
225 {
226 return $this->addListAddressHeaderBody('Bcc', $addresses);
227 }
228
229 /**
230 * @param Address|string ...$addresses
231 *
232 * @return $this
233 */
234 public function bcc(...$addresses)
235 {
236 return $this->setListAddressHeaderBody('Bcc', $addresses);
237 }
238
239 /**
240 * @return Address[]
241 */
242 public function getBcc(): array
243 {
244 return $this->getHeaders()->getHeaderBody('Bcc') ?: [];
245 }
246
247 /**
248 * Sets the priority of this message.
249 *
250 * The value is an integer where 1 is the highest priority and 5 is the lowest.
251 *
252 * @return $this
253 */
254 public function priority(int $priority)
255 {
256 if ($priority > 5) {
257 $priority = 5;
258 } elseif ($priority < 1) {
259 $priority = 1;
260 }
261
262 return $this->setHeaderBody('Text', 'X-Priority', sprintf('%d (%s)', $priority, self::PRIORITY_MAP[$priority]));
263 }
264
265 /**
266 * Get the priority of this message.
267 *
268 * The returned value is an integer where 1 is the highest priority and 5
269 * is the lowest.
270 */
271 public function getPriority(): int
272 {
273 [$priority] = sscanf($this->getHeaders()->getHeaderBody('X-Priority') ?? '', '%[1-5]');
274
275 return $priority ?? 3;
276 }
277
278 /**
279 * @param resource|string|null $body
280 *
281 * @return $this
282 */
283 public function text($body, string $charset = 'utf-8')
284 {
285 if (null !== $body && !\is_string($body) && !\is_resource($body)) {
286 throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
287 }
288
289 $this->cachedBody = null;
290 $this->text = $body;
291 $this->textCharset = $charset;
292
293 return $this;
294 }
295
296 /**
297 * @return resource|string|null
298 */
299 public function getTextBody()
300 {
301 return $this->text;
302 }
303
304 public function getTextCharset(): ?string
305 {
306 return $this->textCharset;
307 }
308
309 /**
310 * @param resource|string|null $body
311 *
312 * @return $this
313 */
314 public function html($body, string $charset = 'utf-8')
315 {
316 if (null !== $body && !\is_string($body) && !\is_resource($body)) {
317 throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
318 }
319
320 $this->cachedBody = null;
321 $this->html = $body;
322 $this->htmlCharset = $charset;
323
324 return $this;
325 }
326
327 /**
328 * @return resource|string|null
329 */
330 public function getHtmlBody()
331 {
332 return $this->html;
333 }
334
335 public function getHtmlCharset(): ?string
336 {
337 return $this->htmlCharset;
338 }
339
340 /**
341 * @param resource|string $body
342 *
343 * @return $this
344 */
345 public function attach($body, string $name = null, string $contentType = null)
346 {
347 if (!\is_string($body) && !\is_resource($body)) {
348 throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
349 }
350
351 $this->cachedBody = null;
352 $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
353
354 return $this;
355 }
356
357 /**
358 * @return $this
359 */
360 public function attachFromPath(string $path, string $name = null, string $contentType = null)
361 {
362 $this->cachedBody = null;
363 $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
364
365 return $this;
366 }
367
368 /**
369 * @param resource|string $body
370 *
371 * @return $this
372 */
373 public function embed($body, string $name = null, string $contentType = null)
374 {
375 if (!\is_string($body) && !\is_resource($body)) {
376 throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
377 }
378
379 $this->cachedBody = null;
380 $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
381
382 return $this;
383 }
384
385 /**
386 * @return $this
387 */
388 public function embedFromPath(string $path, string $name = null, string $contentType = null)
389 {
390 $this->cachedBody = null;
391 $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
392
393 return $this;
394 }
395
396 /**
397 * @return $this
398 */
399 public function attachPart(DataPart $part)
400 {
401 $this->cachedBody = null;
402 $this->attachments[] = ['part' => $part];
403
404 return $this;
405 }
406
407 /**
408 * @return array|DataPart[]
409 */
410 public function getAttachments(): array
411 {
412 $parts = [];
413 foreach ($this->attachments as $attachment) {
414 $parts[] = $this->createDataPart($attachment);
415 }
416
417 return $parts;
418 }
419
420 public function getBody(): AbstractPart
421 {
422 if (null !== $body = parent::getBody()) {
423 return $body;
424 }
425
426 return $this->generateBody();
427 }
428
429 public function ensureValidity()
430 {
431 if (null === $this->text && null === $this->html && !$this->attachments) {
432 throw new LogicException('A message must have a text or an HTML part or attachments.');
433 }
434
435 parent::ensureValidity();
436 }
437
438 /**
439 * Generates an AbstractPart based on the raw body of a message.
440 *
441 * The most "complex" part generated by this method is when there is text and HTML bodies
442 * with related images for the HTML part and some attachments:
443 *
444 * multipart/mixed
445 * |
446 * |------------> multipart/related
447 * | |
448 * | |------------> multipart/alternative
449 * | | |
450 * | | ------------> text/plain (with content)
451 * | | |
452 * | | ------------> text/html (with content)
453 * | |
454 * | ------------> image/png (with content)
455 * |
456 * ------------> application/pdf (with content)
457 */
458 private function generateBody(): AbstractPart
459 {
460 if (null !== $this->cachedBody) {
461 return $this->cachedBody;
462 }
463
464 $this->ensureValidity();
465
466 [$htmlPart, $otherParts, $relatedParts] = $this->prepareParts();
467
468 $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset);
469 if (null !== $htmlPart) {
470 if (null !== $part) {
471 $part = new AlternativePart($part, $htmlPart);
472 } else {
473 $part = $htmlPart;
474 }
475 }
476
477 if ($relatedParts) {
478 $part = new RelatedPart($part, ...$relatedParts);
479 }
480
481 if ($otherParts) {
482 if ($part) {
483 $part = new MixedPart($part, ...$otherParts);
484 } else {
485 $part = new MixedPart(...$otherParts);
486 }
487 }
488
489 return $this->cachedBody = $part;
490 }
491
492 private function prepareParts(): ?array
493 {
494 $names = [];
495 $htmlPart = null;
496 $html = $this->html;
497 if (null !== $html) {
498 $htmlPart = new TextPart($html, $this->htmlCharset, 'html');
499 $html = $htmlPart->getBody();
500 preg_match_all('(<img\s+[^>]*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+)))i', $html, $names);
501 $names = array_filter(array_unique(array_merge($names[2], $names[3])));
502 }
503
504 // usage of reflection is a temporary workaround for missing getters that will be added in 6.2
505 $nameRef = new \ReflectionProperty(TextPart::class, 'name');
506 $nameRef->setAccessible(true);
507 $otherParts = $relatedParts = [];
508 foreach ($this->attachments as $attachment) {
509 $part = $this->createDataPart($attachment);
510 if (isset($attachment['part'])) {
511 $attachment['name'] = $nameRef->getValue($part);
512 }
513
514 $related = false;
515 foreach ($names as $name) {
516 if ($name !== $attachment['name']) {
517 continue;
518 }
519 if (isset($relatedParts[$name])) {
520 continue 2;
521 }
522 $part->setDisposition('inline');
523 $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count);
524 if ($count) {
525 $related = true;
526 }
527 $part->setName($part->getContentId());
528
529 break;
530 }
531
532 if ($related) {
533 $relatedParts[$attachment['name']] = $part;
534 } else {
535 $otherParts[] = $part;
536 }
537 }
538 if (null !== $htmlPart) {
539 $htmlPart = new TextPart($html, $this->htmlCharset, 'html');
540 }
541
542 return [$htmlPart, $otherParts, array_values($relatedParts)];
543 }
544
545 private function createDataPart(array $attachment): DataPart
546 {
547 if (isset($attachment['part'])) {
548 return $attachment['part'];
549 }
550
551 if (isset($attachment['body'])) {
552 $part = new DataPart($attachment['body'], $attachment['name'] ?? null, $attachment['content-type'] ?? null);
553 } else {
554 $part = DataPart::fromPath($attachment['path'] ?? '', $attachment['name'] ?? null, $attachment['content-type'] ?? null);
555 }
556 if ($attachment['inline']) {
557 $part->asInline();
558 }
559
560 return $part;
561 }
562
563 /**
564 * @return $this
565 */
566 private function setHeaderBody(string $type, string $name, $body): object
567 {
568 $this->getHeaders()->setHeaderBody($type, $name, $body);
569
570 return $this;
571 }
572
573 private function addListAddressHeaderBody(string $name, array $addresses)
574 {
575 if (!$header = $this->getHeaders()->get($name)) {
576 return $this->setListAddressHeaderBody($name, $addresses);
577 }
578 $header->addAddresses(Address::createArray($addresses));
579
580 return $this;
581 }
582
583 /**
584 * @return $this
585 */
586 private function setListAddressHeaderBody(string $name, array $addresses)
587 {
588 $addresses = Address::createArray($addresses);
589 $headers = $this->getHeaders();
590 if ($header = $headers->get($name)) {
591 $header->setAddresses($addresses);
592 } else {
593 $headers->addMailboxListHeader($name, $addresses);
594 }
595
596 return $this;
597 }
598
599 /**
600 * @internal
601 */
602 public function __serialize(): array
603 {
604 if (\is_resource($this->text)) {
605 $this->text = (new TextPart($this->text))->getBody();
606 }
607
608 if (\is_resource($this->html)) {
609 $this->html = (new TextPart($this->html))->getBody();
610 }
611
612 foreach ($this->attachments as $i => $attachment) {
613 if (isset($attachment['body']) && \is_resource($attachment['body'])) {
614 $this->attachments[$i]['body'] = (new TextPart($attachment['body']))->getBody();
615 }
616 }
617
618 return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
619 }
620
621 /**
622 * @internal
623 */
624 public function __unserialize(array $data): void
625 {
626 [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data;
627
628 parent::__unserialize($parentData);
629 }
630 }
631