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
12namespace Symfony\Component\Mime;
13
14use Symfony\Component\Mime\Exception\LogicException;
15use Symfony\Component\Mime\Header\Headers;
16use Symfony\Component\Mime\Part\AbstractPart;
17use Symfony\Component\Mime\Part\TextPart;
18
19/**
20 * @author Fabien Potencier <fabien@symfony.com>
21 */
22class Message extends RawMessage
23{
24    private $headers;
25    private $body;
26
27    public function __construct(Headers $headers = null, AbstractPart $body = null)
28    {
29        $this->headers = $headers ? clone $headers : new Headers();
30        $this->body = $body;
31    }
32
33    public function __clone()
34    {
35        $this->headers = clone $this->headers;
36
37        if (null !== $this->body) {
38            $this->body = clone $this->body;
39        }
40    }
41
42    /**
43     * @return $this
44     */
45    public function setBody(AbstractPart $body = null)
46    {
47        $this->body = $body;
48
49        return $this;
50    }
51
52    public function getBody(): ?AbstractPart
53    {
54        return $this->body;
55    }
56
57    /**
58     * @return $this
59     */
60    public function setHeaders(Headers $headers)
61    {
62        $this->headers = $headers;
63
64        return $this;
65    }
66
67    public function getHeaders(): Headers
68    {
69        return $this->headers;
70    }
71
72    public function getPreparedHeaders(): Headers
73    {
74        $headers = clone $this->headers;
75
76        if (!$headers->has('From')) {
77            throw new LogicException('An email must have a "From" header.');
78        }
79
80        $headers->addTextHeader('MIME-Version', '1.0');
81
82        if (!$headers->has('Date')) {
83            $headers->addDateHeader('Date', new \DateTimeImmutable());
84        }
85
86        // determine the "real" sender
87        if (!$headers->has('Sender') && \count($froms = $headers->get('From')->getAddresses()) > 1) {
88            $headers->addMailboxHeader('Sender', $froms[0]);
89        }
90
91        if (!$headers->has('Message-ID')) {
92            $headers->addIdHeader('Message-ID', $this->generateMessageId());
93        }
94
95        // remove the Bcc field which should NOT be part of the sent message
96        $headers->remove('Bcc');
97
98        return $headers;
99    }
100
101    public function toString(): string
102    {
103        if (null === $body = $this->getBody()) {
104            $body = new TextPart('');
105        }
106
107        return $this->getPreparedHeaders()->toString().$body->toString();
108    }
109
110    public function toIterable(): iterable
111    {
112        if (null === $body = $this->getBody()) {
113            $body = new TextPart('');
114        }
115
116        yield $this->getPreparedHeaders()->toString();
117        yield from $body->toIterable();
118    }
119
120    public function ensureValidity()
121    {
122        if (!$this->headers->has('From')) {
123            throw new LogicException('An email must have a "From" header.');
124        }
125
126        parent::ensureValidity();
127    }
128
129    public function generateMessageId(): string
130    {
131        if ($this->headers->has('Sender')) {
132            $sender = $this->headers->get('Sender')->getAddress();
133        } elseif ($this->headers->has('From')) {
134            $sender = $this->headers->get('From')->getAddresses()[0];
135        } else {
136            throw new LogicException('An email must have a "From" or a "Sender" header to compute a Messsage ID.');
137        }
138
139        return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@');
140    }
141
142    public function __serialize(): array
143    {
144        return [$this->headers, $this->body];
145    }
146
147    public function __unserialize(array $data): void
148    {
149        [$this->headers, $this->body] = $data;
150    }
151}
152