Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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