PluginProbe
WPIDE – File Manager & Code Editor / 3.5.9
WPIDE – File Manager & Code Editor v3.5.9
3.5.9 3.5.8 3.5.7 2.0.14 2.0.15 2.0.16 2.0.2 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1 2.2 2.3 2.3.1 2.3.2 2.4.0 2.5 2.6 3.0 3.1 3.2 3.3 All 55 releases
wpide / vendor / symfony / mime / Email.php

Email.php in WPIDE – File Manager & Code Editor 3.5.9, at vendor/symfony/mime/Email.php

635 lines 16.6 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 <fabien@symfony.com>
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 <fabien@symfony.com>
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 if (!$addresses) {
125 throw new LogicException('"from()" must be called with at least one address.');
126 }
127
128 return $this->setListAddressHeaderBody('From', $addresses);
129 }
130
131 /**
132 * @return Address[]
133 */
134 public function getFrom(): array
135 {
136 return $this->getHeaders()->getHeaderBody('From') ?: [];
137 }
138
139 /**
140 * @param Address|string ...$addresses
141 *
142 * @return $this
143 */
144 public function addReplyTo(...$addresses)
145 {
146 return $this->addListAddressHeaderBody('Reply-To', $addresses);
147 }
148
149 /**
150 * @param Address|string ...$addresses
151 *
152 * @return $this
153 */
154 public function replyTo(...$addresses)
155 {
156 return $this->setListAddressHeaderBody('Reply-To', $addresses);
157 }
158
159 /**
160 * @return Address[]
161 */
162 public function getReplyTo(): array
163 {
164 return $this->getHeaders()->getHeaderBody('Reply-To') ?: [];
165 }
166
167 /**
168 * @param Address|string ...$addresses
169 *
170 * @return $this
171 */
172 public function addTo(...$addresses)
173 {
174 return $this->addListAddressHeaderBody('To', $addresses);
175 }
176
177 /**
178 * @param Address|string ...$addresses
179 *
180 * @return $this
181 */
182 public function to(...$addresses)
183 {
184 return $this->setListAddressHeaderBody('To', $addresses);
185 }
186
187 /**
188 * @return Address[]
189 */
190 public function getTo(): array
191 {
192 return $this->getHeaders()->getHeaderBody('To') ?: [];
193 }
194
195 /**
196 * @param Address|string ...$addresses
197 *
198 * @return $this
199 */
200 public function addCc(...$addresses)
201 {
202 return $this->addListAddressHeaderBody('Cc', $addresses);
203 }
204
205 /**
206 * @param Address|string ...$addresses
207 *
208 * @return $this
209 */
210 public function cc(...$addresses)
211 {
212 return $this->setListAddressHeaderBody('Cc', $addresses);
213 }
214
215 /**
216 * @return Address[]
217 */
218 public function getCc(): array
219 {
220 return $this->getHeaders()->getHeaderBody('Cc') ?: [];
221 }
222
223 /**
224 * @param Address|string ...$addresses
225 *
226 * @return $this
227 */
228 public function addBcc(...$addresses)
229 {
230 return $this->addListAddressHeaderBody('Bcc', $addresses);
231 }
232
233 /**
234 * @param Address|string ...$addresses
235 *
236 * @return $this
237 */
238 public function bcc(...$addresses)
239 {
240 return $this->setListAddressHeaderBody('Bcc', $addresses);
241 }
242
243 /**
244 * @return Address[]
245 */
246 public function getBcc(): array
247 {
248 return $this->getHeaders()->getHeaderBody('Bcc') ?: [];
249 }
250
251 /**
252 * Sets the priority of this message.
253 *
254 * The value is an integer where 1 is the highest priority and 5 is the lowest.
255 *
256 * @return $this
257 */
258 public function priority(int $priority)
259 {
260 if ($priority > 5) {
261 $priority = 5;
262 } elseif ($priority < 1) {
263 $priority = 1;
264 }
265
266 return $this->setHeaderBody('Text', 'X-Priority', sprintf('%d (%s)', $priority, self::PRIORITY_MAP[$priority]));
267 }
268
269 /**
270 * Get the priority of this message.
271 *
272 * The returned value is an integer where 1 is the highest priority and 5
273 * is the lowest.
274 */
275 public function getPriority(): int
276 {
277 [$priority] = sscanf($this->getHeaders()->getHeaderBody('X-Priority') ?? '', '%[1-5]');
278
279 return $priority ?? 3;
280 }
281
282 /**
283 * @param resource|string|null $body
284 *
285 * @return $this
286 */
287 public function text($body, string $charset = 'utf-8')
288 {
289 if (null !== $body && !\is_string($body) && !\is_resource($body)) {
290 throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
291 }
292
293 $this->cachedBody = null;
294 $this->text = $body;
295 $this->textCharset = $charset;
296
297 return $this;
298 }
299
300 /**
301 * @return resource|string|null
302 */
303 public function getTextBody()
304 {
305 return $this->text;
306 }
307
308 public function getTextCharset(): ?string
309 {
310 return $this->textCharset;
311 }
312
313 /**
314 * @param resource|string|null $body
315 *
316 * @return $this
317 */
318 public function html($body, string $charset = 'utf-8')
319 {
320 if (null !== $body && !\is_string($body) && !\is_resource($body)) {
321 throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
322 }
323
324 $this->cachedBody = null;
325 $this->html = $body;
326 $this->htmlCharset = $charset;
327
328 return $this;
329 }
330
331 /**
332 * @return resource|string|null
333 */
334 public function getHtmlBody()
335 {
336 return $this->html;
337 }
338
339 public function getHtmlCharset(): ?string
340 {
341 return $this->htmlCharset;
342 }
343
344 /**
345 * @param resource|string $body
346 *
347 * @return $this
348 */
349 public function attach($body, ?string $name = null, ?string $contentType = null)
350 {
351 if (!\is_string($body) && !\is_resource($body)) {
352 throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
353 }
354
355 $this->cachedBody = null;
356 $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
357
358 return $this;
359 }
360
361 /**
362 * @return $this
363 */
364 public function attachFromPath(string $path, ?string $name = null, ?string $contentType = null)
365 {
366 $this->cachedBody = null;
367 $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
368
369 return $this;
370 }
371
372 /**
373 * @param resource|string $body
374 *
375 * @return $this
376 */
377 public function embed($body, ?string $name = null, ?string $contentType = null)
378 {
379 if (!\is_string($body) && !\is_resource($body)) {
380 throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
381 }
382
383 $this->cachedBody = null;
384 $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
385
386 return $this;
387 }
388
389 /**
390 * @return $this
391 */
392 public function embedFromPath(string $path, ?string $name = null, ?string $contentType = null)
393 {
394 $this->cachedBody = null;
395 $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
396
397 return $this;
398 }
399
400 /**
401 * @return $this
402 */
403 public function attachPart(DataPart $part)
404 {
405 $this->cachedBody = null;
406 $this->attachments[] = ['part' => $part];
407
408 return $this;
409 }
410
411 /**
412 * @return array|DataPart[]
413 */
414 public function getAttachments(): array
415 {
416 $parts = [];
417 foreach ($this->attachments as $attachment) {
418 $parts[] = $this->createDataPart($attachment);
419 }
420
421 return $parts;
422 }
423
424 public function getBody(): AbstractPart
425 {
426 if (null !== $body = parent::getBody()) {
427 return $body;
428 }
429
430 return $this->generateBody();
431 }
432
433 public function ensureValidity()
434 {
435 if (null === $this->text && null === $this->html && !$this->attachments) {
436 throw new LogicException('A message must have a text or an HTML part or attachments.');
437 }
438
439 parent::ensureValidity();
440 }
441
442 /**
443 * Generates an AbstractPart based on the raw body of a message.
444 *
445 * The most "complex" part generated by this method is when there is text and HTML bodies
446 * with related images for the HTML part and some attachments:
447 *
448 * multipart/mixed
449 * |
450 * |------------> multipart/related
451 * | |
452 * | |------------> multipart/alternative
453 * | | |
454 * | | ------------> text/plain (with content)
455 * | | |
456 * | | ------------> text/html (with content)
457 * | |
458 * | ------------> image/png (with content)
459 * |
460 * ------------> application/pdf (with content)
461 */
462 private function generateBody(): AbstractPart
463 {
464 if (null !== $this->cachedBody) {
465 return $this->cachedBody;
466 }
467
468 $this->ensureValidity();
469
470 [$htmlPart, $otherParts, $relatedParts] = $this->prepareParts();
471
472 $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset);
473 if (null !== $htmlPart) {
474 if (null !== $part) {
475 $part = new AlternativePart($part, $htmlPart);
476 } else {
477 $part = $htmlPart;
478 }
479 }
480
481 if ($relatedParts) {
482 $part = new RelatedPart($part, ...$relatedParts);
483 }
484
485 if ($otherParts) {
486 if ($part) {
487 $part = new MixedPart($part, ...$otherParts);
488 } else {
489 $part = new MixedPart(...$otherParts);
490 }
491 }
492
493 return $this->cachedBody = $part;
494 }
495
496 private function prepareParts(): ?array
497 {
498 $names = [];
499 $htmlPart = null;
500 $html = $this->html;
501 if (null !== $html) {
502 $htmlPart = new TextPart($html, $this->htmlCharset, 'html');
503 $html = $htmlPart->getBody();
504 preg_match_all('(<img\s+[^>]*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+)))i', $html, $names);
505 $names = array_filter(array_unique(array_merge($names[2], $names[3])));
506 }
507
508 // usage of reflection is a temporary workaround for missing getters that will be added in 6.2
509 $nameRef = new \ReflectionProperty(TextPart::class, 'name');
510 $nameRef->setAccessible(true);
511 $otherParts = $relatedParts = [];
512 foreach ($this->attachments as $attachment) {
513 $part = $this->createDataPart($attachment);
514 if (isset($attachment['part'])) {
515 $attachment['name'] = $nameRef->getValue($part);
516 }
517
518 $related = false;
519 foreach ($names as $name) {
520 if ($name !== $attachment['name']) {
521 continue;
522 }
523 if (isset($relatedParts[$name])) {
524 continue 2;
525 }
526 $part->setDisposition('inline');
527 $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count);
528 if ($count) {
529 $related = true;
530 }
531 $part->setName($part->getContentId());
532
533 break;
534 }
535
536 if ($related) {
537 $relatedParts[$attachment['name']] = $part;
538 } else {
539 $otherParts[] = $part;
540 }
541 }
542 if (null !== $htmlPart) {
543 $htmlPart = new TextPart($html, $this->htmlCharset, 'html');
544 }
545
546 return [$htmlPart, $otherParts, array_values($relatedParts)];
547 }
548
549 private function createDataPart(array $attachment): DataPart
550 {
551 if (isset($attachment['part'])) {
552 return $attachment['part'];
553 }
554
555 if (isset($attachment['body'])) {
556 $part = new DataPart($attachment['body'], $attachment['name'] ?? null, $attachment['content-type'] ?? null);
557 } else {
558 $part = DataPart::fromPath($attachment['path'] ?? '', $attachment['name'] ?? null, $attachment['content-type'] ?? null);
559 }
560 if ($attachment['inline']) {
561 $part->asInline();
562 }
563
564 return $part;
565 }
566
567 /**
568 * @return $this
569 */
570 private function setHeaderBody(string $type, string $name, $body): object
571 {
572 $this->getHeaders()->setHeaderBody($type, $name, $body);
573
574 return $this;
575 }
576
577 private function addListAddressHeaderBody(string $name, array $addresses)
578 {
579 if (!$header = $this->getHeaders()->get($name)) {
580 return $this->setListAddressHeaderBody($name, $addresses);
581 }
582 $header->addAddresses(Address::createArray($addresses));
583
584 return $this;
585 }
586
587 /**
588 * @return $this
589 */
590 private function setListAddressHeaderBody(string $name, array $addresses)
591 {
592 $addresses = Address::createArray($addresses);
593 $headers = $this->getHeaders();
594 if ($header = $headers->get($name)) {
595 $header->setAddresses($addresses);
596 } else {
597 $headers->addMailboxListHeader($name, $addresses);
598 }
599
600 return $this;
601 }
602
603 /**
604 * @internal
605 */
606 public function __serialize(): array
607 {
608 if (\is_resource($this->text)) {
609 $this->text = (new TextPart($this->text))->getBody();
610 }
611
612 if (\is_resource($this->html)) {
613 $this->html = (new TextPart($this->html))->getBody();
614 }
615
616 foreach ($this->attachments as $i => $attachment) {
617 if (isset($attachment['body']) && \is_resource($attachment['body'])) {
618 $this->attachments[$i]['body'] = (new TextPart($attachment['body']))->getBody();
619 }
620 }
621
622 return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
623 }
624
625 /**
626 * @internal
627 */
628 public function __unserialize(array $data): void
629 {
630 [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data;
631
632 parent::__unserialize($parentData);
633 }
634 }
635