diff --git a/src/Symfony/Component/Mime/DraftEmail.php b/src/Symfony/Component/Mime/DraftEmail.php new file mode 100644 index 0000000000000..a60fea17360a8 --- /dev/null +++ b/src/Symfony/Component/Mime/DraftEmail.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime; + +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Part\AbstractPart; + +/** + * @author Kevin Bond + */ +class DraftEmail extends Email +{ + public function __construct(Headers $headers = null, AbstractPart $body = null) + { + parent::__construct($headers, $body); + + $this->getHeaders()->addTextHeader('X-Unsent', '1'); + } + + /** + * Override default behavior as draft emails do not require From/Sender/Date/Message-ID headers. + * These are added by the client that actually sends the email. + */ + public function getPreparedHeaders(): Headers + { + $headers = clone $this->getHeaders(); + + if (!$headers->has('MIME-Version')) { + $headers->addTextHeader('MIME-Version', '1.0'); + } + + $headers->remove('Bcc'); + + return $headers; + } +} diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index 788e4f351263b..952c7b5c151ca 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -386,13 +386,22 @@ public function getBody(): AbstractPart public function ensureValidity() { - if (null === $this->text && null === $this->html && !$this->attachments) { - throw new LogicException('A message must have a text or an HTML part or attachments.'); + $this->ensureBodyValid(); + + if ('1' === $this->getHeaders()->getHeaderBody('X-Unsent')) { + throw new LogicException('Cannot send messages marked as "draft".'); } parent::ensureValidity(); } + private function ensureBodyValid(): void + { + if (null === $this->text && null === $this->html && !$this->attachments) { + throw new LogicException('A message must have a text or an HTML part or attachments.'); + } + } + /** * Generates an AbstractPart based on the raw body of a message. * @@ -415,7 +424,7 @@ public function ensureValidity() */ private function generateBody(): AbstractPart { - $this->ensureValidity(); + $this->ensureBodyValid(); [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); diff --git a/src/Symfony/Component/Mime/Tests/DraftEmailTest.php b/src/Symfony/Component/Mime/Tests/DraftEmailTest.php new file mode 100644 index 0000000000000..713048bfd9e59 --- /dev/null +++ b/src/Symfony/Component/Mime/Tests/DraftEmailTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mime\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mime\DraftEmail; +use Symfony\Component\Mime\Exception\LogicException; + +/** + * @author Kevin Bond + */ +final class DraftEmailTest extends TestCase +{ + public function testCanHaveJustBody() + { + $email = (new DraftEmail())->text('some text')->toString(); + + $this->assertStringContainsString('some text', $email); + $this->assertStringContainsString('MIME-Version: 1.0', $email); + $this->assertStringContainsString('X-Unsent: 1', $email); + } + + public function testBccIsRemoved() + { + $email = (new DraftEmail())->text('some text')->bcc('sam@example.com')->toString(); + + $this->assertStringNotContainsString('sam@example.com', $email); + } + + public function testMustHaveBody() + { + $this->expectException(LogicException::class); + + (new DraftEmail())->toString(); + } + + public function testEnsureValidityAlwaysFails() + { + $email = (new DraftEmail()) + ->to('alice@example.com') + ->from('webmaster@example.com') + ->text('some text') + ; + + $this->expectException(LogicException::class); + + $email->ensureValidity(); + } +}