From 093979be90b9f360f69a4cc8fa2db9e7811b15a9 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Fri, 29 Sep 2023 18:36:29 -0300 Subject: [PATCH 1/5] Check compatibility PHP `8.2`, `8.3`. --- .github/workflows/build.yml | 70 ++++++++++++++----------------------- phpunit.xml.dist | 43 +++++++++++++---------- 2 files changed, 52 insertions(+), 61 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1cd306b..d504ca6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,47 +1,31 @@ -name: build +on: + pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' -on: [push, pull_request] + push: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' -env: - DEFAULT_COMPOSER_FLAGS: "--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi" +name: build jobs: - phpunit: - name: PHP ${{ matrix.php }} on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - php: ['7.4', '8.0', '8.1'] - - steps: - - name: Install sendmail - run: sudo apt-get install -y sendmail - - name: Checkout - uses: actions/checkout@v2 - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - coverage: pcov - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache composer dependencies - uses: actions/cache@v1 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer-${{ matrix.php }}- - - name: Install dependencies - run: composer update $DEFAULT_COMPOSER_FLAGS - - name: Install logging proxy - if: ${{ matrix.php != '7.4' }} - run: composer require "yiisoft/yii2-psr-log-source" - - name: Run unit tests with coverage - run: vendor/bin/phpunit --coverage-text - - name: Static analysis (Psalm) - run: vendor/bin/psalm - - name: Test code style - run: vendor/bin/ecs + phpunit: + uses: yiisoft/actions/.github/workflows/phpunit.yml@master + with: + os: >- + ['ubuntu-latest'] + php: >- + ['7.4', '8.0', '8.1', '8.2', '8.3'] diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 65e04be..c5ad8e5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,20 +1,27 @@ - - - - ./tests - - - - - src - - + + + + + + + + + ./tests + + + + + + ./src + + From 3d70d17f99bffc225de14a0f21428e421cfb38d0 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Fri, 29 Sep 2023 18:42:49 -0300 Subject: [PATCH 2/5] Fix test php `8.3`. --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d504ca6..4bc4d36 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,7 @@ jobs: phpunit: uses: yiisoft/actions/.github/workflows/phpunit.yml@master with: + composer-command: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi --ignore-platform-req=php+ os: >- ['ubuntu-latest'] php: >- From 21b16ca150325f4fbcf58b36addd0155b88c703f Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Fri, 29 Sep 2023 18:47:04 -0300 Subject: [PATCH 3/5] Update `README.md`. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cdb9891..80cbc53 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ For license information check the [LICENSE](LICENSE.md)-file. [![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-symfonymailer/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-symfonymailer) [![Total Downloads](https://poser.pugx.org/yiisoft/yii2-symfonymailer/downloads.png)](https://packagist.org/packages/yiisoft/yii2-symfonymailer) [![Build Status](https://github.com/yiisoft/yii2-symfonymailer/workflows/build/badge.svg)](https://github.com/yiisoft/yii2-symfonymailer/actions) +[![codecov](https://codecov.io/gh/yiisoft/yii2-symfonymailer/graph/badge.svg?token=XCj60xP699)](https://codecov.io/gh/yiisoft/yii2-symfonymailer) + +Requirements +------------ + +- PHP 7.4 or higher. Installation ------------ From 4d258149cd772c4e50cb266629cf9e3b7828f602 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Wed, 24 Jan 2024 08:35:10 -0300 Subject: [PATCH 4/5] Fix workflow. --- .github/workflows/ecs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ecs.yml b/.github/workflows/ecs.yml index fe3f8e3..6ca0eb6 100644 --- a/.github/workflows/ecs.yml +++ b/.github/workflows/ecs.yml @@ -10,7 +10,6 @@ on: - 'phpunit.xml.dist' push: - branches: ['main'] paths-ignore: - 'docs/**' - 'README.md' From 127a41a9b854fd3849b7078be659ae927e01a163 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 24 Jan 2024 12:08:45 +0000 Subject: [PATCH 5/5] Apply fixes from StyleCI --- src/DkimMessageSigner.php | 59 +- src/EventDispatcherProxy.php | 47 +- src/Mailer.php | 262 ++++----- src/Message.php | 804 +++++++++++++-------------- src/MessageEncrypterInterface.php | 32 +- src/MessageSignerInterface.php | 38 +- src/MessageWrapperInterface.php | 32 +- src/PsrEvent.php | 58 +- src/SMimeMessageEncrypter.php | 50 +- src/SMimeMessageSigner.php | 50 +- tests/MailerTest.php | 335 ++++++------ tests/MessageTest.php | 879 +++++++++++++++--------------- tests/TestCase.php | 104 ++-- tests/bootstrap.php | 38 +- tests/compatibility.php | 71 +-- 15 files changed, 1430 insertions(+), 1429 deletions(-) diff --git a/src/DkimMessageSigner.php b/src/DkimMessageSigner.php index 30718df..3fc7df7 100644 --- a/src/DkimMessageSigner.php +++ b/src/DkimMessageSigner.php @@ -1,29 +1,30 @@ -dkimSigner = $dkimSigner; - } - - public function sign(Message $message, array $options = []): Message - { - return $this->dkimSigner->sign($message, $options); - } -} +dkimSigner = $dkimSigner; + } + + public function sign(Message $message, array $options = []): Message + { + return $this->dkimSigner->sign($message, $options); + } +} diff --git a/src/EventDispatcherProxy.php b/src/EventDispatcherProxy.php index 15fb200..0fda9e5 100644 --- a/src/EventDispatcherProxy.php +++ b/src/EventDispatcherProxy.php @@ -1,23 +1,24 @@ -component = $component; - } - - public function dispatch(object $event): object - { - $this->component->trigger(get_class($event), new PsrEvent($event)); - return $event; - } -} +component = $component; + } + + public function dispatch(object $event): object + { + $this->component->trigger(get_class($event), new PsrEvent($event)); + return $event; + } +} diff --git a/src/Mailer.php b/src/Mailer.php index 0bf84f5..4043812 100644 --- a/src/Mailer.php +++ b/src/Mailer.php @@ -1,131 +1,131 @@ -, dsn?:string|Dsn } - * @phpstan-type TransportConfigArray array{scheme?:string, host?:string, username?:string, password?:string, port?:int, options?: array, dsn?:string|Dsn } - * @extendable - */ -class Mailer extends BaseMailer -{ - /** - * @var string message default class name. - */ - public $messageClass = Message::class; - - /** - * @see https://symfony.com/doc/current/mailer.html#encrypting-messages - */ - public ?MessageEncrypterInterface $encrypter = null; - /** - * @see https://symfony.com/doc/current/mailer.html#signing-messages - */ - public ?MessageSignerInterface $signer = null; - /** - * @var array - */ - public array $signerOptions = []; - public ?Transport $transportFactory = null; - /** - * @var null|TransportInterface Symfony transport instance or its array configuration. - */ - private ?TransportInterface $_transport = null; - - /** - * @param TransportConfigArray|TransportInterface $transport - * @throws InvalidConfigException on invalid argument. - */ - public function setTransport(array|TransportInterface $transport): void - { - $this->_transport = $transport instanceof TransportInterface ? $transport : $this->createTransport($transport); - } - - protected function sendMessage($message): bool - { - if (!($message instanceof MessageWrapperInterface)) { - throw new InvalidArgumentException(sprintf( - 'The message must be an instance of "%s". The "%s" instance is received.', - MessageWrapperInterface::class, - get_class($message), - )); - } - - $message = $message->getSymfonyEmail(); - if ($this->encrypter !== null) { - $message = $this->encrypter->encrypt($message); - } - - if ($this->signer !== null) { - $message = $this->signer->sign($message, $this->signerOptions); - } - $this->getTransport()->send($message); - return true; - } - - private function getTransport(): TransportInterface - { - /** @psalm-suppress RedundantPropertyInitializationCheck Yii2 configuration flow does not guarantee full initialisation */ - if (!isset($this->_transport)) { - throw new InvalidConfigException('No transport was configured.'); - } - return $this->_transport; - } - - private function getTransportFactory(): Transport - { - if (isset($this->transportFactory)) { - return $this->transportFactory; - } - /** @var LoggerInterface|null $logger */ - $logger = class_exists(DynamicLogger::class) ? new DynamicLogger() : null; - $defaultFactories = Transport::getDefaultFactories(new EventDispatcherProxy($this), null, $logger); - /** @psalm-suppress InvalidArgument Symfony's type annotation is wrong */ - return new Transport($defaultFactories); - } - - /** - * @param TransportConfigArray $config - * @throws InvalidConfigException - */ - private function createTransport(array $config = []): TransportInterface - { - $transportFactory = $this->getTransportFactory(); - if (array_key_exists('dsn', $config)) { - if (is_string($config['dsn'])) { - $transport = $transportFactory->fromString($config['dsn']); - } else { - $transport = $transportFactory->fromDsnObject($config['dsn']); - } - } elseif (array_key_exists('scheme', $config) && array_key_exists('host', $config)) { - $dsn = new Dsn( - $config['scheme'], - $config['host'], - $config['username'] ?? '', - $config['password'] ?? '', - $config['port'] ?? null, - $config['options'] ?? [], - ); - $transport = $transportFactory->fromDsnObject($dsn); - } else { - throw new InvalidConfigException('Transport configuration array must contain either "dsn", or "scheme" and "host" keys.'); - } - return $transport; - } -} +, dsn?:string|Dsn } + * @phpstan-type TransportConfigArray array{scheme?:string, host?:string, username?:string, password?:string, port?:int, options?: array, dsn?:string|Dsn } + * @extendable + */ +class Mailer extends BaseMailer +{ + /** + * @var string message default class name. + */ + public $messageClass = Message::class; + + /** + * @see https://symfony.com/doc/current/mailer.html#encrypting-messages + */ + public ?MessageEncrypterInterface $encrypter = null; + /** + * @see https://symfony.com/doc/current/mailer.html#signing-messages + */ + public ?MessageSignerInterface $signer = null; + /** + * @var array + */ + public array $signerOptions = []; + public ?Transport $transportFactory = null; + /** + * @var TransportInterface|null Symfony transport instance or its array configuration. + */ + private ?TransportInterface $_transport = null; + + /** + * @param TransportConfigArray|TransportInterface $transport + * @throws InvalidConfigException on invalid argument. + */ + public function setTransport(array|TransportInterface $transport): void + { + $this->_transport = $transport instanceof TransportInterface ? $transport : $this->createTransport($transport); + } + + protected function sendMessage($message): bool + { + if (!($message instanceof MessageWrapperInterface)) { + throw new InvalidArgumentException(sprintf( + 'The message must be an instance of "%s". The "%s" instance is received.', + MessageWrapperInterface::class, + get_class($message), + )); + } + + $message = $message->getSymfonyEmail(); + if ($this->encrypter !== null) { + $message = $this->encrypter->encrypt($message); + } + + if ($this->signer !== null) { + $message = $this->signer->sign($message, $this->signerOptions); + } + $this->getTransport()->send($message); + return true; + } + + private function getTransport(): TransportInterface + { + /** @psalm-suppress RedundantPropertyInitializationCheck Yii2 configuration flow does not guarantee full initialisation */ + if (!isset($this->_transport)) { + throw new InvalidConfigException('No transport was configured.'); + } + return $this->_transport; + } + + private function getTransportFactory(): Transport + { + if (isset($this->transportFactory)) { + return $this->transportFactory; + } + /** @var LoggerInterface|null $logger */ + $logger = class_exists(DynamicLogger::class) ? new DynamicLogger() : null; + $defaultFactories = Transport::getDefaultFactories(new EventDispatcherProxy($this), null, $logger); + /** @psalm-suppress InvalidArgument Symfony's type annotation is wrong */ + return new Transport($defaultFactories); + } + + /** + * @param TransportConfigArray $config + * @throws InvalidConfigException + */ + private function createTransport(array $config = []): TransportInterface + { + $transportFactory = $this->getTransportFactory(); + if (array_key_exists('dsn', $config)) { + if (is_string($config['dsn'])) { + $transport = $transportFactory->fromString($config['dsn']); + } else { + $transport = $transportFactory->fromDsnObject($config['dsn']); + } + } elseif (array_key_exists('scheme', $config) && array_key_exists('host', $config)) { + $dsn = new Dsn( + $config['scheme'], + $config['host'], + $config['username'] ?? '', + $config['password'] ?? '', + $config['port'] ?? null, + $config['options'] ?? [], + ); + $transport = $transportFactory->fromDsnObject($dsn); + } else { + throw new InvalidConfigException('Transport configuration array must contain either "dsn", or "scheme" and "host" keys.'); + } + return $transport; + } +} diff --git a/src/Message.php b/src/Message.php index 7dd24cb..20ae328 100644 --- a/src/Message.php +++ b/src/Message.php @@ -1,402 +1,402 @@ -|string - * - * @property PsalmAddressList $bcc The type defined by the message interface is not strict enough. - * @property-read Email $symfonyEmail Symfony email instance. - * - * @extendable - */ -class Message extends BaseMessage implements MessageWrapperInterface -{ - private Email $email; - private string $charset = 'utf-8'; - - /** - * @param array $config - */ - public function __construct(array $config = []) - { - $this->email = new Email(); - parent::__construct($config); - } - - public function __clone() - { - $this->email = clone $this->email; - } - - public function __sleep(): array - { - return ['email', 'charset']; - } - - public function getCharset(): string - { - return $this->charset; - } - - public function setCharset($charset): self - { - $this->charset = $charset; - return $this; - } - - /** - * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose - * @return array|string - */ - public function getFrom(): array|string - { - return $this->convertAddressesToStrings($this->email->getFrom()); - } - - /** - * @param array|string $from - * @psalm-suppress MoreSpecificImplementedParamType Yii typehint is too loose - * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose - * @return $this - */ - public function setFrom($from): static - { - $this->email->from(...$this->convertStringsToAddresses($from)); - return $this; - } - - /** - * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose - * @return array|string - */ - public function getTo(): array|string - { - return $this->convertAddressesToStrings($this->email->getTo()); - } - - /** - * @psalm-suppress MoreSpecificImplementedParamType - * @param PsalmAddressList $to - * @return $this - */ - public function setTo($to): self - { - $this->email->to(...$this->convertStringsToAddresses($to)); - return $this; - } - - /** - * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose - * @return array|string - */ - public function getReplyTo() - { - return $this->convertAddressesToStrings($this->email->getReplyTo()); - } - - /** - * @psalm-suppress MoreSpecificImplementedParamType - * @param PsalmAddressList $replyTo - * @return $this - */ - public function setReplyTo($replyTo): self - { - $this->email->replyTo(...$this->convertStringsToAddresses($replyTo)); - return $this; - } - - /** - * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose - * @return array|string - */ - public function getCc(): array|string - { - return $this->convertAddressesToStrings($this->email->getCc()); - } - - /** - * @psalm-suppress MoreSpecificImplementedParamType - * @param PsalmAddressList $cc - * @return $this - */ - public function setCc($cc): self - { - $this->email->cc(...$this->convertStringsToAddresses($cc)); - return $this; - } - - /** - * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose - * @return array|string - */ - public function getBcc(): array|string - { - return $this->convertAddressesToStrings($this->email->getBcc()); - } - - /** - * @psalm-suppress MoreSpecificImplementedParamType - * @param PsalmAddressList $bcc The type defined by the message interface is not strict enough - * @return $this - */ - public function setBcc($bcc): self - { - $this->email->bcc(...$this->convertStringsToAddresses($bcc)); - return $this; - } - - public function getSubject(): string - { - return (string) $this->email->getSubject(); - } - - public function setSubject($subject): self - { - $this->email->subject($subject); - return $this; - } - - public function getDate(): ?DateTimeImmutable - { - return $this->email->getDate(); - } - - public function setDate(DateTimeInterface $date): self - { - $this->email->date($date); - return $this; - } - - public function getPriority(): int - { - return $this->email->getPriority(); - } - - public function setPriority(int $priority): self - { - $this->email->priority($priority); - return $this; - } - - public function getReturnPath(): string - { - $returnPath = $this->email->getReturnPath(); - return $returnPath === null ? '' : $returnPath->getAddress(); - } - - public function setReturnPath(string $address): self - { - $this->email->returnPath($address); - return $this; - } - - public function getSender(): string - { - $sender = $this->email->getSender(); - return $sender === null ? '' : $sender->getAddress(); - } - - public function setSender(string $address): self - { - $this->email->sender($address); - return $this; - } - - public function setTextBody($text): self - { - $this->email->text($text, $this->charset); - return $this; - } - - public function setHtmlBody($html): self - { - $this->email->html($html, $this->charset); - return $this; - } - - /** - * @param string $fileName - * @param PsalmFileOptions $options - * @psalm-suppress MoreSpecificImplementedParamType The real expected type is defined in human readable text only - * @return $this - */ - public function attach($fileName, array $options = []): self - { - $this->email->attachFromPath( - $fileName, - $options['fileName'] ?? $fileName, - $options['contentType'] ?? FileHelper::getMimeType($fileName) - ); - return $this; - } - - /** - * @param resource|string $content - * @param PsalmFileOptions $options - * @psalm-suppress MoreSpecificImplementedParamType The real expected type is defined in human readable text only - * @return $this - */ - public function attachContent($content, array $options = []): self - { - $this->email->attach($content, $options['fileName'] ?? null, $options['contentType'] ?? null); - return $this; - } - - /** - * @param string $fileName - * @param PsalmFileOptions $options - * @psalm-suppress MoreSpecificImplementedParamType The real expected type is defined in human readable text only - */ - public function embed($fileName, array $options = []): string - { - $name = $options['fileName'] ?? $fileName; - $this->email->embedFromPath( - $fileName, - $name, - $options['contentType'] ?? FileHelper::getMimeType($fileName) - ); - return 'cid:' . $name; - } - - /** - * @param resource|string $content - * @param PsalmFileOptions $options - * @psalm-suppress MoreSpecificImplementedParamType The real expected type is defined in human readable text only - */ - public function embedContent($content, array $options = []): string - { - if (!isset($options['fileName'])) { - throw new InvalidConfigException('A valid file name must be passed when embedding content'); - } - $this->email->embed($content, $options['fileName'], $options['contentType'] ?? null); - return 'cid:' . $options['fileName']; - } - - /** - * @return list - */ - public function getHeader(string $name): array - { - $headers = $this->email->getHeaders(); - - $values = []; - - /** @var HeaderInterface $header */ - foreach ($headers->all($name) as $header) { - $values[] = $header->getBodyAsString(); - } - - return $values; - } - - public function addHeader(string $name, string $value): self - { - $this->email->getHeaders()->addTextHeader($name, $value); - return $this; - } - - /** - * @param string|list $value - */ - public function setHeader(string $name, array|string $value): self - { - $headers = $this->email->getHeaders(); - - $headers->remove($name); - - foreach ((array) $value as $v) { - $headers->addTextHeader($name, $v); - } - - return $this; - } - - /** - * @param array> $headers - * @return $this - */ - public function setHeaders(array $headers): self - { - foreach ($headers as $name => $value) { - $this->setHeader($name, $value); - } - - return $this; - } - - public function toString(): string - { - return $this->email->toString(); - } - - /** - * Returns a Symfony email instance. - * - * @return Email Symfony email instance. - */ - public function getSymfonyEmail(): Email - { - return $this->email; - } - - /** - * Converts address instances to their string representations. - * - * @param list
$addresses - * - * @return array|string - */ - private function convertAddressesToStrings(array $addresses): string|array - { - $strings = []; - - foreach ($addresses as $address) { - $strings[$address->getAddress()] = $address->getName(); - } - - return empty($strings) ? '' : $strings; - } - - /** - * Converts string representations of address to their instances. - * - * @param array|string $strings - * - * @return list
- */ - private function convertStringsToAddresses(array|string $strings): array - { - $addresses = []; - - foreach ((array) $strings as $address => $name) { - if (!is_string($address)) { - // email address without name - $addresses[] = new Address($name); - continue; - } - - $addresses[] = new Address($address, $name); - } - - return $addresses; - } -} +|string + * + * @property PsalmAddressList $bcc The type defined by the message interface is not strict enough. + * @property Email $symfonyEmail Symfony email instance. + * + * @extendable + */ +class Message extends BaseMessage implements MessageWrapperInterface +{ + private Email $email; + private string $charset = 'utf-8'; + + /** + * @param array $config + */ + public function __construct(array $config = []) + { + $this->email = new Email(); + parent::__construct($config); + } + + public function __clone() + { + $this->email = clone $this->email; + } + + public function __sleep(): array + { + return ['email', 'charset']; + } + + public function getCharset(): string + { + return $this->charset; + } + + public function setCharset($charset): self + { + $this->charset = $charset; + return $this; + } + + /** + * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose + * @return array|string + */ + public function getFrom(): array|string + { + return $this->convertAddressesToStrings($this->email->getFrom()); + } + + /** + * @param array|string $from + * @psalm-suppress MoreSpecificImplementedParamType Yii typehint is too loose + * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose + * @return $this + */ + public function setFrom($from): static + { + $this->email->from(...$this->convertStringsToAddresses($from)); + return $this; + } + + /** + * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose + * @return array|string + */ + public function getTo(): array|string + { + return $this->convertAddressesToStrings($this->email->getTo()); + } + + /** + * @psalm-suppress MoreSpecificImplementedParamType + * @param PsalmAddressList $to + * @return $this + */ + public function setTo($to): self + { + $this->email->to(...$this->convertStringsToAddresses($to)); + return $this; + } + + /** + * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose + * @return array|string + */ + public function getReplyTo() + { + return $this->convertAddressesToStrings($this->email->getReplyTo()); + } + + /** + * @psalm-suppress MoreSpecificImplementedParamType + * @param PsalmAddressList $replyTo + * @return $this + */ + public function setReplyTo($replyTo): self + { + $this->email->replyTo(...$this->convertStringsToAddresses($replyTo)); + return $this; + } + + /** + * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose + * @return array|string + */ + public function getCc(): array|string + { + return $this->convertAddressesToStrings($this->email->getCc()); + } + + /** + * @psalm-suppress MoreSpecificImplementedParamType + * @param PsalmAddressList $cc + * @return $this + */ + public function setCc($cc): self + { + $this->email->cc(...$this->convertStringsToAddresses($cc)); + return $this; + } + + /** + * @psalm-suppress ArgumentTypeCoercion Symfony typehint is too loose + * @return array|string + */ + public function getBcc(): array|string + { + return $this->convertAddressesToStrings($this->email->getBcc()); + } + + /** + * @psalm-suppress MoreSpecificImplementedParamType + * @param PsalmAddressList $bcc The type defined by the message interface is not strict enough + * @return $this + */ + public function setBcc($bcc): self + { + $this->email->bcc(...$this->convertStringsToAddresses($bcc)); + return $this; + } + + public function getSubject(): string + { + return (string) $this->email->getSubject(); + } + + public function setSubject($subject): self + { + $this->email->subject($subject); + return $this; + } + + public function getDate(): ?DateTimeImmutable + { + return $this->email->getDate(); + } + + public function setDate(DateTimeInterface $date): self + { + $this->email->date($date); + return $this; + } + + public function getPriority(): int + { + return $this->email->getPriority(); + } + + public function setPriority(int $priority): self + { + $this->email->priority($priority); + return $this; + } + + public function getReturnPath(): string + { + $returnPath = $this->email->getReturnPath(); + return $returnPath === null ? '' : $returnPath->getAddress(); + } + + public function setReturnPath(string $address): self + { + $this->email->returnPath($address); + return $this; + } + + public function getSender(): string + { + $sender = $this->email->getSender(); + return $sender === null ? '' : $sender->getAddress(); + } + + public function setSender(string $address): self + { + $this->email->sender($address); + return $this; + } + + public function setTextBody($text): self + { + $this->email->text($text, $this->charset); + return $this; + } + + public function setHtmlBody($html): self + { + $this->email->html($html, $this->charset); + return $this; + } + + /** + * @param string $fileName + * @param PsalmFileOptions $options + * @psalm-suppress MoreSpecificImplementedParamType The real expected type is defined in human readable text only + * @return $this + */ + public function attach($fileName, array $options = []): self + { + $this->email->attachFromPath( + $fileName, + $options['fileName'] ?? $fileName, + $options['contentType'] ?? FileHelper::getMimeType($fileName) + ); + return $this; + } + + /** + * @param resource|string $content + * @param PsalmFileOptions $options + * @psalm-suppress MoreSpecificImplementedParamType The real expected type is defined in human readable text only + * @return $this + */ + public function attachContent($content, array $options = []): self + { + $this->email->attach($content, $options['fileName'] ?? null, $options['contentType'] ?? null); + return $this; + } + + /** + * @param string $fileName + * @param PsalmFileOptions $options + * @psalm-suppress MoreSpecificImplementedParamType The real expected type is defined in human readable text only + */ + public function embed($fileName, array $options = []): string + { + $name = $options['fileName'] ?? $fileName; + $this->email->embedFromPath( + $fileName, + $name, + $options['contentType'] ?? FileHelper::getMimeType($fileName) + ); + return 'cid:' . $name; + } + + /** + * @param resource|string $content + * @param PsalmFileOptions $options + * @psalm-suppress MoreSpecificImplementedParamType The real expected type is defined in human readable text only + */ + public function embedContent($content, array $options = []): string + { + if (!isset($options['fileName'])) { + throw new InvalidConfigException('A valid file name must be passed when embedding content'); + } + $this->email->embed($content, $options['fileName'], $options['contentType'] ?? null); + return 'cid:' . $options['fileName']; + } + + /** + * @return list + */ + public function getHeader(string $name): array + { + $headers = $this->email->getHeaders(); + + $values = []; + + /** @var HeaderInterface $header */ + foreach ($headers->all($name) as $header) { + $values[] = $header->getBodyAsString(); + } + + return $values; + } + + public function addHeader(string $name, string $value): self + { + $this->email->getHeaders()->addTextHeader($name, $value); + return $this; + } + + /** + * @param list|string $value + */ + public function setHeader(string $name, array|string $value): self + { + $headers = $this->email->getHeaders(); + + $headers->remove($name); + + foreach ((array) $value as $v) { + $headers->addTextHeader($name, $v); + } + + return $this; + } + + /** + * @param array|string> $headers + * @return $this + */ + public function setHeaders(array $headers): self + { + foreach ($headers as $name => $value) { + $this->setHeader($name, $value); + } + + return $this; + } + + public function toString(): string + { + return $this->email->toString(); + } + + /** + * Returns a Symfony email instance. + * + * @return Email Symfony email instance. + */ + public function getSymfonyEmail(): Email + { + return $this->email; + } + + /** + * Converts address instances to their string representations. + * + * @param list
$addresses + * + * @return array|string + */ + private function convertAddressesToStrings(array $addresses): string|array + { + $strings = []; + + foreach ($addresses as $address) { + $strings[$address->getAddress()] = $address->getName(); + } + + return empty($strings) ? '' : $strings; + } + + /** + * Converts string representations of address to their instances. + * + * @param array|string $strings + * + * @return list
+ */ + private function convertStringsToAddresses(array|string $strings): array + { + $addresses = []; + + foreach ((array) $strings as $address => $name) { + if (!is_string($address)) { + // email address without name + $addresses[] = new Address($name); + continue; + } + + $addresses[] = new Address($address, $name); + } + + return $addresses; + } +} diff --git a/src/MessageEncrypterInterface.php b/src/MessageEncrypterInterface.php index 07090b0..aed824e 100644 --- a/src/MessageEncrypterInterface.php +++ b/src/MessageEncrypterInterface.php @@ -1,16 +1,16 @@ - $options - */ - public function sign(Message $message, array $options = []): Message; -} + $options + */ + public function sign(Message $message, array $options = []): Message; +} diff --git a/src/MessageWrapperInterface.php b/src/MessageWrapperInterface.php index a7f0ef5..2e2de75 100644 --- a/src/MessageWrapperInterface.php +++ b/src/MessageWrapperInterface.php @@ -1,16 +1,16 @@ -name = get_class($originalEvent); - $this->originalEvent = $originalEvent; - } - - public function getOriginalEvent(): object - { - return $this->originalEvent; - } -} +name = get_class($originalEvent); + $this->originalEvent = $originalEvent; + } + + public function getOriginalEvent(): object + { + return $this->originalEvent; + } +} diff --git a/src/SMimeMessageEncrypter.php b/src/SMimeMessageEncrypter.php index ce54393..ac9cfa1 100644 --- a/src/SMimeMessageEncrypter.php +++ b/src/SMimeMessageEncrypter.php @@ -1,25 +1,25 @@ -encrypter->encrypt($message); - } -} +encrypter->encrypt($message); + } +} diff --git a/src/SMimeMessageSigner.php b/src/SMimeMessageSigner.php index 3ff9131..0c3990d 100644 --- a/src/SMimeMessageSigner.php +++ b/src/SMimeMessageSigner.php @@ -1,25 +1,25 @@ -signer->sign($message); - } -} +signer->sign($message); + } +} diff --git a/tests/MailerTest.php b/tests/MailerTest.php index 779eea2..5778f1f 100644 --- a/tests/MailerTest.php +++ b/tests/MailerTest.php @@ -1,168 +1,167 @@ -getMockBuilder(TransportInterface::class)->getMock(); - $transport->expects($this->once())->method('send'); - - $mailer = new Mailer(); - $mailer->transport = $transport; - - $message = $this->getMockBuilder(Message::class)->getMock(); - // We test if the correct transport is used - $mailer->send($message); - } - - public function testSendWithEncryptor(): void - { - $mailer = new Mailer(); - $mailer->transport = new Transport\NullTransport(); - - $message = new Message(); - $message - ->setHtmlBody('htmlbody') - ->setFrom('test@test.com') - ->setTo('test@test.com') - ; - $encrypter = $this->getMockBuilder(MessageEncrypterInterface::class)->getMock(); - $encrypter->expects($this->once())->method('encrypt')->willReturnCallback(function ($message) { - return $message; - }); - $mailer->encrypter = $encrypter; - $mailer->send($message); - } - - public function testSendWithSigner(): void - { - $mailer = new Mailer(); - $mailer->transport = new Transport\NullTransport(); - - $message = new Message(); - $message - ->setHtmlBody('htmlbody') - ->setFrom('test@test.com') - ->setTo('test@test.com') - ; - - $signer = $this->getMockBuilder(MessageSignerInterface::class)->getMock(); - $signer->expects($this->once())->method('sign')->willReturnCallback(function ($message) { - return $message; - }); - $mailer->signer = $signer; - $mailer->send($message); - } - - /** - * @depends testSetupTransport - */ - public function testConfigureTransportFromArray(): void - { - $transportConfig = [ - 'scheme' => 'smtp', - 'host' => 'localhost', - 'username' => 'username', - 'password' => 'password', - 'port' => 465, - 'options' => [ - 'ssl' => true, - ], - ]; - $mailer = new Mailer(); - - $factory = $this->getMockBuilder(Transport\TransportFactoryInterface::class)->getMock(); - $factory->expects($this->atLeastOnce())->method('supports')->willReturn(true); - $factory->expects($this->once())->method('create'); - - $mailer->transportFactory = new Transport([$factory]); - $mailer->setTransport($transportConfig); - } - - public function testConfigureTransportFromArrayWithYii(): void - { - $transportConfig = [ - 'scheme' => 'smtp', - 'host' => 'localhost', - 'username' => 'username', - 'password' => 'password', - 'port' => 465, - 'options' => [ - 'ssl' => true, - ], - ]; - $mailer = Yii::createObject([ - 'class' => Mailer::class, - 'transport' => $transportConfig, - ]); - $this->assertInstanceOf(Mailer::class, $mailer); - } - - public function testConfigureTransportInvalidArray(): void - { - $transportConfig = [ - - ]; - $mailer = new Mailer(); - $this->expectException(InvalidConfigException::class); - $mailer->setTransport($transportConfig); - } - - public function testConfigureTransportFromString(): void - { - $mailer = new Mailer(); - - $factory = $this->getMockBuilder(Transport\TransportFactoryInterface::class)->getMock(); - $factory->expects($this->atLeastOnce())->method('supports')->willReturn(true); - $factory->expects($this->once())->method('create'); - - $mailer->transportFactory = new Transport([$factory]); - $mailer->setTransport([ - 'dsn' => 'null://null', - ]); - } - - public function testConfigureTransportFromDsnObject(): void - { - $mailer = new Mailer(); - - $factory = $this->getMockBuilder(Transport\TransportFactoryInterface::class)->getMock(); - $factory->expects($this->atLeastOnce())->method('supports')->willReturn(true); - $factory->expects($this->once())->method('create'); - - $mailer->transportFactory = new Transport([$factory]); - $mailer->setTransport([ - 'dsn' => new \Symfony\Component\Mailer\Transport\Dsn('null', 'null'), - ]); - } - - public function testSendMessageThrowsOnBadMessageType(): void - { - $mailer = new Mailer(); - $this->expectException(InvalidArgumentException::class); - $message = $this->getMockBuilder(MessageInterface::class)->getMock(); - - $mailer->send($message); - } -} +getMockBuilder(TransportInterface::class)->getMock(); + $transport->expects($this->once())->method('send'); + + $mailer = new Mailer(); + $mailer->transport = $transport; + + $message = $this->getMockBuilder(Message::class)->getMock(); + // We test if the correct transport is used + $mailer->send($message); + } + + public function testSendWithEncryptor(): void + { + $mailer = new Mailer(); + $mailer->transport = new Transport\NullTransport(); + + $message = new Message(); + $message + ->setHtmlBody('htmlbody') + ->setFrom('test@test.com') + ->setTo('test@test.com') + ; + $encrypter = $this->getMockBuilder(MessageEncrypterInterface::class)->getMock(); + $encrypter->expects($this->once())->method('encrypt')->willReturnCallback(function ($message) { + return $message; + }); + $mailer->encrypter = $encrypter; + $mailer->send($message); + } + + public function testSendWithSigner(): void + { + $mailer = new Mailer(); + $mailer->transport = new Transport\NullTransport(); + + $message = new Message(); + $message + ->setHtmlBody('htmlbody') + ->setFrom('test@test.com') + ->setTo('test@test.com') + ; + + $signer = $this->getMockBuilder(MessageSignerInterface::class)->getMock(); + $signer->expects($this->once())->method('sign')->willReturnCallback(function ($message) { + return $message; + }); + $mailer->signer = $signer; + $mailer->send($message); + } + + /** + * @depends testSetupTransport + */ + public function testConfigureTransportFromArray(): void + { + $transportConfig = [ + 'scheme' => 'smtp', + 'host' => 'localhost', + 'username' => 'username', + 'password' => 'password', + 'port' => 465, + 'options' => [ + 'ssl' => true, + ], + ]; + $mailer = new Mailer(); + + $factory = $this->getMockBuilder(Transport\TransportFactoryInterface::class)->getMock(); + $factory->expects($this->atLeastOnce())->method('supports')->willReturn(true); + $factory->expects($this->once())->method('create'); + + $mailer->transportFactory = new Transport([$factory]); + $mailer->setTransport($transportConfig); + } + + public function testConfigureTransportFromArrayWithYii(): void + { + $transportConfig = [ + 'scheme' => 'smtp', + 'host' => 'localhost', + 'username' => 'username', + 'password' => 'password', + 'port' => 465, + 'options' => [ + 'ssl' => true, + ], + ]; + $mailer = Yii::createObject([ + 'class' => Mailer::class, + 'transport' => $transportConfig, + ]); + $this->assertInstanceOf(Mailer::class, $mailer); + } + + public function testConfigureTransportInvalidArray(): void + { + $transportConfig = [ + ]; + $mailer = new Mailer(); + $this->expectException(InvalidConfigException::class); + $mailer->setTransport($transportConfig); + } + + public function testConfigureTransportFromString(): void + { + $mailer = new Mailer(); + + $factory = $this->getMockBuilder(Transport\TransportFactoryInterface::class)->getMock(); + $factory->expects($this->atLeastOnce())->method('supports')->willReturn(true); + $factory->expects($this->once())->method('create'); + + $mailer->transportFactory = new Transport([$factory]); + $mailer->setTransport([ + 'dsn' => 'null://null', + ]); + } + + public function testConfigureTransportFromDsnObject(): void + { + $mailer = new Mailer(); + + $factory = $this->getMockBuilder(Transport\TransportFactoryInterface::class)->getMock(); + $factory->expects($this->atLeastOnce())->method('supports')->willReturn(true); + $factory->expects($this->once())->method('create'); + + $mailer->transportFactory = new Transport([$factory]); + $mailer->setTransport([ + 'dsn' => new \Symfony\Component\Mailer\Transport\Dsn('null', 'null'), + ]); + } + + public function testSendMessageThrowsOnBadMessageType(): void + { + $mailer = new Mailer(); + $this->expectException(InvalidArgumentException::class); + $message = $this->getMockBuilder(MessageInterface::class)->getMock(); + + $mailer->send($message); + } +} diff --git a/tests/MessageTest.php b/tests/MessageTest.php index 6bf9831..b43929f 100644 --- a/tests/MessageTest.php +++ b/tests/MessageTest.php @@ -1,440 +1,439 @@ -mockApplication([ - 'components' => [ - 'mailer' => $this->createTestEmailComponent(), - ], - ]); - $filePath = $this->getTestFilePath(); - if (!file_exists($filePath)) { - FileHelper::createDirectory($filePath); - } - } - - public function tearDown(): void - { - $filePath = $this->getTestFilePath(); - if (file_exists($filePath)) { - FileHelper::removeDirectory($filePath); - } - } - - - // Tests : - - public function testSetGet(): void - { - $message = new Message(); - - $charset = 'utf-16'; - $message->setCharset($charset); - $this->assertEquals($charset, $message->getCharset(), 'Unable to set charset!'); - - $subject = 'Test Subject'; - $message->setSubject($subject); - $this->assertEquals($subject, $message->getSubject(), 'Unable to set subject!'); - - $from = 'from@somedomain.com'; - $message->setFrom($from); - $this->assertContains($from, array_keys($message->getFrom()), 'Unable to set from!'); - - $replyTo = 'reply-to@somedomain.com'; - $message->setReplyTo($replyTo); - $this->assertContains($replyTo, array_keys($message->getReplyTo()), 'Unable to set replyTo!'); - - $to = 'someuser@somedomain.com'; - $message->setTo($to); - $this->assertContains($to, array_keys($message->getTo()), 'Unable to set to!'); - - $cc = 'ccuser@somedomain.com'; - $message->setCc($cc); - $this->assertContains($cc, array_keys($message->getCc()), 'Unable to set cc!'); - - $bcc = 'bccuser@somedomain.com'; - $message->setBcc($bcc); - $this->assertContains($bcc, array_keys($message->getBcc()), 'Unable to set bcc!'); - } - - /** - * @depends testSetGet - */ - public function testClone(): void - { - $m1 = new Message(); - $m1->setFrom('user@example.com'); - $m2 = clone $m1; - $m1->setTo([ - 'user1@example.com' => 'user1', - ]); - $m2->setTo([ - 'user2@example.com' => 'user2', - ]); - - $this->assertEquals([ - 'user1@example.com' => 'user1', - ], $m1->getTo()); - $this->assertEquals([ - 'user2@example.com' => 'user2', - ], $m2->getTo()); - - $messageWithoutSymfonyInitialized = new Message(); - $m2 = clone $messageWithoutSymfonyInitialized; // should be no error during cloning - $this->assertTrue($m2 instanceof Message); - } - - public function testSetupHeaderShortcuts(): void - { - $charset = 'utf-16'; - $subject = 'Test Subject'; - $from = 'from@somedomain.com'; - $replyTo = 'reply-to@somedomain.com'; - $to = 'someuser@somedomain.com'; - $cc = 'ccuser@somedomain.com'; - $bcc = 'bccuser@somedomain.com'; - $returnPath = 'bounce@somedomain.com'; - $textBody = 'textBody'; - - $messageString = $this->createTestMessage() - ->setCharset($charset) - ->setSubject($subject) - ->setFrom($from) - ->setReplyTo($replyTo) - ->setTo($to) - ->setCc($cc) - ->setReturnPath($returnPath) - ->setPriority(2) - ->setTextBody($textBody) - ->toString(); - - $this->assertStringContainsString("charset=$charset", $messageString, 'Incorrect charset!'); - $this->assertStringContainsString("Subject: $subject", $messageString, 'Incorrect "Subject" header!'); - $this->assertStringContainsString("From: $from", $messageString, 'Incorrect "From" header!'); - $this->assertStringContainsString("Reply-To: $replyTo", $messageString, 'Incorrect "Reply-To" header!'); - $this->assertStringContainsString("To: $to", $messageString, 'Incorrect "To" header!'); - $this->assertStringContainsString("Cc: $cc", $messageString, 'Incorrect "Cc" header!'); - $this->assertStringContainsString("Return-Path: <{$returnPath}>", $messageString, 'Incorrect "Return-Path" header!'); - $this->assertStringContainsString("X-Priority: 2 (High)", $messageString, 'Incorrect "Priority" header!'); - } - - public function testSend(): void - { - $message = $this->createTestMessage(); - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Symfony Test'); - $message->setTextBody('Yii Symfony Test body'); - $this->assertTrue($message->send()); - } - - /** - * @depends testSend - */ - public function testAttachFile(): void - { - $message = $this->createTestMessage(); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Symfony Attach File Test'); - $message->setTextBody('Yii Symfony Attach File Test body'); - $fileName = __FILE__; - $message->attach($fileName); - - $this->assertTrue($message->send()); - - $attachment = $this->getAttachment($message); - $this->assertTrue(is_object($attachment), 'No attachment found!'); - $this->assertStringContainsString("attachment filename: $fileName", $attachment->asDebugString(), 'Invalid file name!'); - } - - /** - * @depends testSend - */ - public function testAttachContent(): void - { - $message = $this->createTestMessage(); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Symfony Create Attachment Test'); - $message->setTextBody('Yii Symfony Create Attachment Test body'); - $fileName = 'test.txt'; - $fileContent = 'Test attachment content'; - $message->attachContent($fileContent, [ - 'fileName' => $fileName, - 'contentType' => 'image/png', - ]); - - $this->assertTrue($message->send()); - $attachment = $this->getAttachment($message); - $this->assertTrue(is_object($attachment), 'No attachment found!'); - $this->assertStringContainsString("attachment filename: $fileName", $attachment->asDebugString(), 'Invalid file name!'); - $this->assertStringContainsString('image/png', $attachment->asDebugString(), 'Invalid content type!'); - } - - /** - * @depends testSend - */ - public function testEmbedFile(): void - { - $fileName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); - - $message = $this->createTestMessage(); - - $cid = $message->embed($fileName); - $this->assertIsString($cid); - $this->assertStringStartsWith('cid:', $cid); - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Symfony Embed File Test'); - $message->setHtmlBody('Embed image: pic'); - - $this->assertTrue($message->send()); - - $attachment = $this->getAttachment($message); - $this->assertTrue(is_object($attachment), 'No attachment found!'); - $this->assertStringContainsString(" filename: $fileName", $attachment->asDebugString(), 'Invalid file name!'); - $this->assertStringContainsString(" disposition: inline", $attachment->asDebugString(), 'Invalid disposition!'); - } - - /** - * @depends testSend - */ - public function testEmbedContent(): void - { - $fileFullName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); - $message = $this->createTestMessage(); - - $fileName = basename($fileFullName); - $contentType = 'image/jpeg'; - $fileContent = file_get_contents($fileFullName); - - $cid = $message->embedContent($fileContent, [ - 'fileName' => $fileName, - 'contentType' => $contentType, - ]); - $this->assertIsString($cid); - $this->assertStringStartsWith('cid:', $cid); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Symfony Embed File Test'); - $message->setHtmlBody('Embed image: pic'); - - $this->assertTrue($message->send()); - - $attachment = $this->getAttachment($message); - $this->assertTrue(is_object($attachment), 'No attachment found!'); - $this->assertStringContainsString(" filename: $fileName", $attachment->asDebugString(), 'Invalid file name!'); - $this->assertStringContainsString($contentType, $attachment->asDebugString(), 'Invalid content type!'); - $this->assertStringContainsString(" disposition: inline", $attachment->asDebugString(), 'Invalid disposition!'); - } - - /** - * @depends testSend - */ - public function testSendAlternativeBody(): void - { - $message = $this->createTestMessage(); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Alternative Body Test'); - $message->setHtmlBody('Yii Swift test HTML body'); - $message->setTextBody('Yii Swift test plain text body'); - - $this->assertTrue($message->send(), 'Don`t send!'); - - $body = $message->getSymfonyEmail()->getBody(); - - $this->assertStringContainsString('text/plain', $body->asDebugString(), 'No text!'); - $this->assertStringContainsString('text/html', $body->asDebugString(), 'No HTML!'); - } - - public function testSerialize(): void - { - $message = $this->createTestMessage(); - - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Symfony Alternative Body Test'); - $message->setTextBody('Yii Symfony test plain text body'); - - $serializedMessage = serialize($message); - $this->assertNotEmpty($serializedMessage, 'Unable to serialize message!'); - - $unserializedMessage = unserialize($serializedMessage); - $this->assertEquals($message->getSymfonyEmail(), $unserializedMessage->getSymfonyEmail(), 'Unable to unserialize message!'); - } - - public function testThatSerializeDoesNotSerializeMailer(): void - { - $message = $this->createTestMessage(); - $message->mailer = 'testvalue'; - - $unserializedMessage = unserialize(serialize($message)); - $this->assertNull($unserializedMessage->mailer); - } - /** - * @depends testSendAlternativeBody - */ - public function testAlternativeBodyCharset(): void - { - $message = $this->createTestMessage(); - $charset = 'windows-1251'; - $message->setCharset($charset); - - $message->setTextBody('some text'); - $message->setHtmlBody('some html'); - $message->setTo('to@to.to'); - $message->setFrom('from@to.to'); - $content = $message->toString(); - $this->assertEquals(2, substr_count($content, $charset), 'Wrong charset for alternative body.'); - - $message->setTextBody('some text override'); - $content = $message->toString(); - $this->assertEquals(2, substr_count($content, $charset), 'Wrong charset for alternative body override.'); - } - - public function testSetupHeaders(): void - { - $messageString = $this->createTestMessage() - ->setTo('to@to.to') - ->setFrom('from@to.to') - ->addHeader('Some', 'foo') - ->addHeader('Multiple', 'value1') - ->addHeader('Multiple', 'value2') - ->setTextBody('Body') - ->toString(); - - $this->assertStringContainsString('Some: foo', $messageString, 'Unable to add header!'); - $this->assertStringContainsString('Multiple: value1', $messageString, 'First value of multiple header lost!'); - $this->assertStringContainsString('Multiple: value2', $messageString, 'Second value of multiple header lost!'); - - $messageString = $this->createTestMessage() - ->setTo('to@to.to') - ->setFrom('from@to.to') - ->setHeader('Some', 'foo') - ->setHeader('Some', 'override') - ->setHeader('Multiple', ['value1', 'value2']) - ->setTextBody('Body') - ->toString(); - - $this->assertStringContainsString('Some: override', $messageString, 'Unable to set header!'); - $this->assertStringNotContainsString('Some: foo', $messageString, 'Unable to override header!'); - $this->assertStringContainsString('Multiple: value1', $messageString, 'First value of multiple header lost!'); - $this->assertStringContainsString('Multiple: value2', $messageString, 'Second value of multiple header lost!'); - - $message = $this->createTestMessage(); - $message->setTextBody('Body'); - $message->setTo('to@to.to'); - $message->setFrom('from@to.to'); - $message->setHeader('Some', 'foo'); - $this->assertEquals(['foo'], $message->getHeader('Some')); - $message->setHeader('Multiple', ['value1', 'value2']); - $this->assertEquals(['value1', 'value2'], $message->getHeader('Multiple')); - - $message = $this->createTestMessage() - ->setHeaders([ - 'Some' => 'foo', - 'Multiple' => ['value1', 'value2'], - ]); - $this->assertEquals(['foo'], $message->getHeader('Some')); - $this->assertEquals(['value1', 'value2'], $message->getHeader('Multiple')); - } - - /** - * Finds the attachment object in the message. - * @param Message $message message instance - * @return null|DataPart attachment instance. - */ - protected function getAttachment(Message $message): ?DataPart - { - $messageParts = $message->getSymfonyEmail()->getAttachments(); - $attachment = null; - foreach ($messageParts as $part) { - if ($part instanceof DataPart) { - $attachment = $part; - break; - } - } - - return $attachment; - } - - /** - * @return string test file path. - */ - private function getTestFilePath(): string - { - return Yii::getAlias('@yiiunit/extensions/symfonymailer/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); - } - - /** - * @return Mailer test email component instance. - */ - private function createTestEmailComponent(): Mailer - { - $component = new Mailer([ - 'useFileTransport' => true, - ]); - - return $component; - } - - /** - * @return Message test message instance. - */ - private function createTestMessage(): Message - { - return Yii::$app->get('mailer')->compose(); - } - - /** - * Creates image file with given text. - * @param string $fileName file name. - * @param string $text text to be applied on image. - * @return string image file full name. - */ - private function createImageFile($fileName = 'test.jpg', $text = 'Test Image'): string - { - if (!function_exists('imagecreatetruecolor')) { - $this->markTestSkipped('GD lib required.'); - } - $fileFullName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $fileName; - $image = imagecreatetruecolor(120, 20); - $textColor = imagecolorallocate($image, 233, 14, 91); - imagestring($image, 1, 5, 5, $text, $textColor); - imagejpeg($image, $fileFullName); - imagedestroy($image); - - return $fileFullName; - } -} +mockApplication([ + 'components' => [ + 'mailer' => $this->createTestEmailComponent(), + ], + ]); + $filePath = $this->getTestFilePath(); + if (!file_exists($filePath)) { + FileHelper::createDirectory($filePath); + } + } + + public function tearDown(): void + { + $filePath = $this->getTestFilePath(); + if (file_exists($filePath)) { + FileHelper::removeDirectory($filePath); + } + } + + + // Tests : + + public function testSetGet(): void + { + $message = new Message(); + + $charset = 'utf-16'; + $message->setCharset($charset); + $this->assertEquals($charset, $message->getCharset(), 'Unable to set charset!'); + + $subject = 'Test Subject'; + $message->setSubject($subject); + $this->assertEquals($subject, $message->getSubject(), 'Unable to set subject!'); + + $from = 'from@somedomain.com'; + $message->setFrom($from); + $this->assertContains($from, array_keys($message->getFrom()), 'Unable to set from!'); + + $replyTo = 'reply-to@somedomain.com'; + $message->setReplyTo($replyTo); + $this->assertContains($replyTo, array_keys($message->getReplyTo()), 'Unable to set replyTo!'); + + $to = 'someuser@somedomain.com'; + $message->setTo($to); + $this->assertContains($to, array_keys($message->getTo()), 'Unable to set to!'); + + $cc = 'ccuser@somedomain.com'; + $message->setCc($cc); + $this->assertContains($cc, array_keys($message->getCc()), 'Unable to set cc!'); + + $bcc = 'bccuser@somedomain.com'; + $message->setBcc($bcc); + $this->assertContains($bcc, array_keys($message->getBcc()), 'Unable to set bcc!'); + } + + /** + * @depends testSetGet + */ + public function testClone(): void + { + $m1 = new Message(); + $m1->setFrom('user@example.com'); + $m2 = clone $m1; + $m1->setTo([ + 'user1@example.com' => 'user1', + ]); + $m2->setTo([ + 'user2@example.com' => 'user2', + ]); + + $this->assertEquals([ + 'user1@example.com' => 'user1', + ], $m1->getTo()); + $this->assertEquals([ + 'user2@example.com' => 'user2', + ], $m2->getTo()); + + $messageWithoutSymfonyInitialized = new Message(); + $m2 = clone $messageWithoutSymfonyInitialized; // should be no error during cloning + $this->assertInstanceOf(Message::class, $m2); + } + + public function testSetupHeaderShortcuts(): void + { + $charset = 'utf-16'; + $subject = 'Test Subject'; + $from = 'from@somedomain.com'; + $replyTo = 'reply-to@somedomain.com'; + $to = 'someuser@somedomain.com'; + $cc = 'ccuser@somedomain.com'; + $bcc = 'bccuser@somedomain.com'; + $returnPath = 'bounce@somedomain.com'; + $textBody = 'textBody'; + + $messageString = $this->createTestMessage() + ->setCharset($charset) + ->setSubject($subject) + ->setFrom($from) + ->setReplyTo($replyTo) + ->setTo($to) + ->setCc($cc) + ->setReturnPath($returnPath) + ->setPriority(2) + ->setTextBody($textBody) + ->toString(); + + $this->assertStringContainsString("charset=$charset", $messageString, 'Incorrect charset!'); + $this->assertStringContainsString("Subject: $subject", $messageString, 'Incorrect "Subject" header!'); + $this->assertStringContainsString("From: $from", $messageString, 'Incorrect "From" header!'); + $this->assertStringContainsString("Reply-To: $replyTo", $messageString, 'Incorrect "Reply-To" header!'); + $this->assertStringContainsString("To: $to", $messageString, 'Incorrect "To" header!'); + $this->assertStringContainsString("Cc: $cc", $messageString, 'Incorrect "Cc" header!'); + $this->assertStringContainsString("Return-Path: <{$returnPath}>", $messageString, 'Incorrect "Return-Path" header!'); + $this->assertStringContainsString('X-Priority: 2 (High)', $messageString, 'Incorrect "Priority" header!'); + } + + public function testSend(): void + { + $message = $this->createTestMessage(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Symfony Test'); + $message->setTextBody('Yii Symfony Test body'); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testAttachFile(): void + { + $message = $this->createTestMessage(); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Symfony Attach File Test'); + $message->setTextBody('Yii Symfony Attach File Test body'); + $fileName = __FILE__; + $message->attach($fileName); + + $this->assertTrue($message->send()); + + $attachment = $this->getAttachment($message); + $this->assertIsObject($attachment, 'No attachment found!'); + $this->assertStringContainsString("attachment filename: $fileName", $attachment->asDebugString(), 'Invalid file name!'); + } + + /** + * @depends testSend + */ + public function testAttachContent(): void + { + $message = $this->createTestMessage(); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Symfony Create Attachment Test'); + $message->setTextBody('Yii Symfony Create Attachment Test body'); + $fileName = 'test.txt'; + $fileContent = 'Test attachment content'; + $message->attachContent($fileContent, [ + 'fileName' => $fileName, + 'contentType' => 'image/png', + ]); + + $this->assertTrue($message->send()); + $attachment = $this->getAttachment($message); + $this->assertIsObject($attachment, 'No attachment found!'); + $this->assertStringContainsString("attachment filename: $fileName", $attachment->asDebugString(), 'Invalid file name!'); + $this->assertStringContainsString('image/png', $attachment->asDebugString(), 'Invalid content type!'); + } + + /** + * @depends testSend + */ + public function testEmbedFile(): void + { + $fileName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); + + $message = $this->createTestMessage(); + + $cid = $message->embed($fileName); + $this->assertIsString($cid); + $this->assertStringStartsWith('cid:', $cid); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Symfony Embed File Test'); + $message->setHtmlBody('Embed image: pic'); + + $this->assertTrue($message->send()); + + $attachment = $this->getAttachment($message); + $this->assertIsObject($attachment, 'No attachment found!'); + $this->assertStringContainsString(" filename: $fileName", $attachment->asDebugString(), 'Invalid file name!'); + $this->assertStringContainsString(' disposition: inline', $attachment->asDebugString(), 'Invalid disposition!'); + } + + /** + * @depends testSend + */ + public function testEmbedContent(): void + { + $fileFullName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); + $message = $this->createTestMessage(); + + $fileName = basename($fileFullName); + $contentType = 'image/jpeg'; + $fileContent = file_get_contents($fileFullName); + + $cid = $message->embedContent($fileContent, [ + 'fileName' => $fileName, + 'contentType' => $contentType, + ]); + $this->assertIsString($cid); + $this->assertStringStartsWith('cid:', $cid); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Symfony Embed File Test'); + $message->setHtmlBody('Embed image: pic'); + + $this->assertTrue($message->send()); + + $attachment = $this->getAttachment($message); + $this->assertIsObject($attachment, 'No attachment found!'); + $this->assertStringContainsString(" filename: $fileName", $attachment->asDebugString(), 'Invalid file name!'); + $this->assertStringContainsString($contentType, $attachment->asDebugString(), 'Invalid content type!'); + $this->assertStringContainsString(' disposition: inline', $attachment->asDebugString(), 'Invalid disposition!'); + } + + /** + * @depends testSend + */ + public function testSendAlternativeBody(): void + { + $message = $this->createTestMessage(); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Alternative Body Test'); + $message->setHtmlBody('Yii Swift test HTML body'); + $message->setTextBody('Yii Swift test plain text body'); + + $this->assertTrue($message->send(), 'Don`t send!'); + + $body = $message->getSymfonyEmail()->getBody(); + + $this->assertStringContainsString('text/plain', $body->asDebugString(), 'No text!'); + $this->assertStringContainsString('text/html', $body->asDebugString(), 'No HTML!'); + } + + public function testSerialize(): void + { + $message = $this->createTestMessage(); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Symfony Alternative Body Test'); + $message->setTextBody('Yii Symfony test plain text body'); + + $serializedMessage = serialize($message); + $this->assertNotEmpty($serializedMessage, 'Unable to serialize message!'); + + $unserializedMessage = unserialize($serializedMessage); + $this->assertEquals($message->getSymfonyEmail(), $unserializedMessage->getSymfonyEmail(), 'Unable to unserialize message!'); + } + + public function testThatSerializeDoesNotSerializeMailer(): void + { + $message = $this->createTestMessage(); + $message->mailer = 'testvalue'; + + $unserializedMessage = unserialize(serialize($message)); + $this->assertNull($unserializedMessage->mailer); + } + + /** + * @depends testSendAlternativeBody + */ + public function testAlternativeBodyCharset(): void + { + $message = $this->createTestMessage(); + $charset = 'windows-1251'; + $message->setCharset($charset); + + $message->setTextBody('some text'); + $message->setHtmlBody('some html'); + $message->setTo('to@to.to'); + $message->setFrom('from@to.to'); + $content = $message->toString(); + $this->assertEquals(2, substr_count($content, $charset), 'Wrong charset for alternative body.'); + + $message->setTextBody('some text override'); + $content = $message->toString(); + $this->assertEquals(2, substr_count($content, $charset), 'Wrong charset for alternative body override.'); + } + + public function testSetupHeaders(): void + { + $messageString = $this->createTestMessage() + ->setTo('to@to.to') + ->setFrom('from@to.to') + ->addHeader('Some', 'foo') + ->addHeader('Multiple', 'value1') + ->addHeader('Multiple', 'value2') + ->setTextBody('Body') + ->toString(); + + $this->assertStringContainsString('Some: foo', $messageString, 'Unable to add header!'); + $this->assertStringContainsString('Multiple: value1', $messageString, 'First value of multiple header lost!'); + $this->assertStringContainsString('Multiple: value2', $messageString, 'Second value of multiple header lost!'); + + $messageString = $this->createTestMessage() + ->setTo('to@to.to') + ->setFrom('from@to.to') + ->setHeader('Some', 'foo') + ->setHeader('Some', 'override') + ->setHeader('Multiple', ['value1', 'value2']) + ->setTextBody('Body') + ->toString(); + + $this->assertStringContainsString('Some: override', $messageString, 'Unable to set header!'); + $this->assertStringNotContainsString('Some: foo', $messageString, 'Unable to override header!'); + $this->assertStringContainsString('Multiple: value1', $messageString, 'First value of multiple header lost!'); + $this->assertStringContainsString('Multiple: value2', $messageString, 'Second value of multiple header lost!'); + + $message = $this->createTestMessage(); + $message->setTextBody('Body'); + $message->setTo('to@to.to'); + $message->setFrom('from@to.to'); + $message->setHeader('Some', 'foo'); + $this->assertEquals(['foo'], $message->getHeader('Some')); + $message->setHeader('Multiple', ['value1', 'value2']); + $this->assertEquals(['value1', 'value2'], $message->getHeader('Multiple')); + + $message = $this->createTestMessage() + ->setHeaders([ + 'Some' => 'foo', + 'Multiple' => ['value1', 'value2'], + ]); + $this->assertEquals(['foo'], $message->getHeader('Some')); + $this->assertEquals(['value1', 'value2'], $message->getHeader('Multiple')); + } + + /** + * Finds the attachment object in the message. + * @param Message $message message instance + * @return DataPart|null attachment instance. + */ + protected function getAttachment(Message $message): ?DataPart + { + $messageParts = $message->getSymfonyEmail()->getAttachments(); + $attachment = null; + foreach ($messageParts as $part) { + if ($part instanceof DataPart) { + $attachment = $part; + break; + } + } + + return $attachment; + } + + /** + * @return string test file path. + */ + private function getTestFilePath(): string + { + return Yii::getAlias('@yiiunit/extensions/symfonymailer/runtime') . DIRECTORY_SEPARATOR . basename(self::class) . '_' . getmypid(); + } + + /** + * @return Mailer test email component instance. + */ + private function createTestEmailComponent(): Mailer + { + return new Mailer([ + 'useFileTransport' => true, + ]); + } + + /** + * @return Message test message instance. + */ + private function createTestMessage(): Message + { + return Yii::$app->get('mailer')->compose(); + } + + /** + * Creates image file with given text. + * @param string $fileName file name. + * @param string $text text to be applied on image. + * @return string image file full name. + */ + private function createImageFile($fileName = 'test.jpg', $text = 'Test Image'): string + { + if (!function_exists('imagecreatetruecolor')) { + $this->markTestSkipped('GD lib required.'); + } + $fileFullName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $fileName; + $image = imagecreatetruecolor(120, 20); + $textColor = imagecolorallocate($image, 233, 14, 91); + imagestring($image, 1, 5, 5, $text, $textColor); + imagejpeg($image, $fileFullName); + imagedestroy($image); + + return $fileFullName; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 2ca552b..eb11124 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,52 +1,52 @@ -destroyApplication(); - } - - /** - * Populates Yii::$app with a new application - * The application will be destroyed on tearDown() automatically. - * @param array $config The application configuration, if needed - * @param string $appClass name of the application class to create - */ - protected function mockApplication($config = [], $appClass = '\yii\console\Application') - { - new $appClass(ArrayHelper::merge([ - 'id' => 'testapp', - 'basePath' => __DIR__, - 'vendorPath' => $this->getVendorPath(), - ], $config)); - } - - protected function getVendorPath() - { - $vendor = dirname(dirname(__DIR__)) . '/vendor'; - if (!is_dir($vendor)) { - $vendor = dirname(dirname(dirname(dirname(__DIR__)))); - } - return $vendor; - } - - /** - * Destroys application in Yii::$app by setting it to null. - */ - protected function destroyApplication() - { - \Yii::$app = null; - } -} +destroyApplication(); + } + + /** + * Populates Yii::$app with a new application + * The application will be destroyed on tearDown() automatically. + * @param array $config The application configuration, if needed + * @param string $appClass name of the application class to create + */ + protected function mockApplication($config = [], $appClass = '\yii\console\Application') + { + new $appClass(ArrayHelper::merge([ + 'id' => 'testapp', + 'basePath' => __DIR__, + 'vendorPath' => $this->getVendorPath(), + ], $config)); + } + + protected function getVendorPath() + { + $vendor = dirname(__DIR__, 2) . '/vendor'; + if (!is_dir($vendor)) { + $vendor = dirname(__DIR__, 4); + } + return $vendor; + } + + /** + * Destroys application in Yii::$app by setting it to null. + */ + protected function destroyApplication() + { + \Yii::$app = null; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3fb84e1..93fa97b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,19 +1,19 @@ -setExpectedException($exception); - } - - /** - * @param string $message - */ - public function expectExceptionMessage($message) - { - $this->setExpectedException($this->getExpectedException(), $message); - } - } - } -} +expectException($exception); + } + + /** + * @param string $message + */ + public function expectExceptionMessage($message) + { + $this->expectException($this->getExpectedException()); + $this->expectExceptionMessage($message); + } + } + } +}