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 if (!$headers->has('Sender')) { 78 throw new LogicException('An email must have a "From" or a "Sender" header.'); 79 } 80 $headers->addMailboxListHeader('From', [$headers->get('Sender')->getAddress()]); 81 } 82 83 if (!$headers->has('MIME-Version')) { 84 $headers->addTextHeader('MIME-Version', '1.0'); 85 } 86 87 if (!$headers->has('Date')) { 88 $headers->addDateHeader('Date', new \DateTimeImmutable()); 89 } 90 91 // determine the "real" sender 92 if (!$headers->has('Sender') && \count($froms = $headers->get('From')->getAddresses()) > 1) { 93 $headers->addMailboxHeader('Sender', $froms[0]); 94 } 95 96 if (!$headers->has('Message-ID')) { 97 $headers->addIdHeader('Message-ID', $this->generateMessageId()); 98 } 99 100 // remove the Bcc field which should NOT be part of the sent message 101 $headers->remove('Bcc'); 102 103 return $headers; 104 } 105 106 public function toString(): string 107 { 108 if (null === $body = $this->getBody()) { 109 $body = new TextPart(''); 110 } 111 112 return $this->getPreparedHeaders()->toString().$body->toString(); 113 } 114 115 public function toIterable(): iterable 116 { 117 if (null === $body = $this->getBody()) { 118 $body = new TextPart(''); 119 } 120 121 yield $this->getPreparedHeaders()->toString(); 122 yield from $body->toIterable(); 123 } 124 125 public function ensureValidity() 126 { 127 if (!$this->headers->has('To') && !$this->headers->has('Cc') && !$this->headers->has('Bcc')) { 128 throw new LogicException('An email must have a "To", "Cc", or "Bcc" header.'); 129 } 130 131 if (!$this->headers->has('From') && !$this->headers->has('Sender')) { 132 throw new LogicException('An email must have a "From" or a "Sender" header.'); 133 } 134 135 parent::ensureValidity(); 136 } 137 138 public function generateMessageId(): string 139 { 140 if ($this->headers->has('Sender')) { 141 $sender = $this->headers->get('Sender')->getAddress(); 142 } elseif ($this->headers->has('From')) { 143 $sender = $this->headers->get('From')->getAddresses()[0]; 144 } else { 145 throw new LogicException('An email must have a "From" or a "Sender" header.'); 146 } 147 148 return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@'); 149 } 150 151 public function __serialize(): array 152 { 153 return [$this->headers, $this->body]; 154 } 155 156 public function __unserialize(array $data): void 157 { 158 [$this->headers, $this->body] = $data; 159 } 160} 161