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