From b4fc8e0bb91162fe5e338774bc1be283cc21ebc0 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Fri, 11 Apr 2025 23:43:29 +0200 Subject: [PATCH 1/6] FEATURE: Replace swiftmailer with symfonymailer --- .github/workflows/build.yml | 9 +--- Classes/Domain/Form.php | 1 + Classes/Runtime/Action/EmailAction.php | 52 ++++++++++--------- .../FusionObjects/ValidatorImplementation.php | 24 +++++---- Classes/Runtime/Helper/SchemaDefinition.php | 3 ++ Documentation/RuntimeActionReference.rst | 4 +- composer.json | 14 ++--- phpstan.neon | 2 + 8 files changed, 59 insertions(+), 50 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc7f4ca..83011b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,13 +13,8 @@ jobs: strategy: fail-fast: false matrix: - php-versions: [ '7.3', '7.4' ] - flow-versions: [ '6.3', '7.0' ] - include: - - php-versions: '7.2' - flow-versions: '6.0' - - php-versions: '8.0' - flow-versions: '7.0' + php-versions: [ '8.3', '8.4' ] + flow-versions: [ '8.3', '9.0' ] runs-on: ubuntu-latest diff --git a/Classes/Domain/Form.php b/Classes/Domain/Form.php index a254ebc..852feca 100644 --- a/Classes/Domain/Form.php +++ b/Classes/Domain/Form.php @@ -261,6 +261,7 @@ public function calculateHiddenFields(?string $content = null): array // if ($request && ($this->enableReferrer === true)) { $childRequestArgumentNamespace = null; + /** @phpstan-ignore-next-line */ while ($request instanceof ActionRequest) { $requestArgumentNamespace = $request->getArgumentNamespace(); $hiddenFields[$this->prefixFieldName('__referrer[@package]', $requestArgumentNamespace)] = $request->getControllerPackageKey(); diff --git a/Classes/Runtime/Action/EmailAction.php b/Classes/Runtime/Action/EmailAction.php index 4fd6797..3b8d4c0 100644 --- a/Classes/Runtime/Action/EmailAction.php +++ b/Classes/Runtime/Action/EmailAction.php @@ -16,9 +16,13 @@ use Neos\Flow\Mvc\ActionResponse; use Neos\Flow\ResourceManagement\PersistentResource; use Neos\Fusion\Form\Runtime\Domain\Exception\ActionException; -use Neos\SwiftMailer\Message as SwiftMailerMessage; +use Neos\SymfonyMailer\Service\MailerService; use Neos\Utility\MediaTypes; use Psr\Http\Message\UploadedFileInterface; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\File; class EmailAction extends AbstractAction { @@ -28,8 +32,8 @@ class EmailAction extends AbstractAction */ public function perform(): ?ActionResponse { - if (!class_exists(SwiftMailerMessage::class)) { - throw new ActionException('The "neos/swiftmailer" doesn\'t seem to be installed, but is required for the EmailAction to work!', 1503392532); + if (!class_exists(MailerService::class)) { + throw new ActionException('The "neos/symfonymailer" doesn\'t seem to be installed, but is required for the EmailAction to work!', 1503392532); } $subject = $this->options['subject'] ?? null; @@ -59,37 +63,36 @@ public function perform(): ?ActionResponse throw new ActionException('The option "senderAddress" must be set for the EmailAction.', 1327060210); } - $mail = new SwiftMailerMessage(); - + $mail = new Email(); $mail - ->setFrom($senderName ? [$senderAddress => $senderName] : $senderAddress) - ->setSubject($subject); + ->addFrom(new Address($senderAddress, $senderName)) + ->subject($subject); if (is_array($recipientAddress)) { - $mail->setTo($recipientAddress); + $mail->addTo(...array_map(fn ($entry) => new Address($entry), $recipientAddress)); } else { - $mail->setTo($recipientName ? [$recipientAddress => $recipientName] : $recipientAddress); + $mail->addTo(new Address($recipientAddress, $recipientName)); } if ($replyToAddress !== null) { - $mail->setReplyTo($replyToAddress); + $mail->addReplyTo(new Address($replyToAddress)); } if ($carbonCopyAddress !== null) { - $mail->setCc($carbonCopyAddress); + $mail->addCc(new Address($carbonCopyAddress)); } if ($blindCarbonCopyAddress !== null) { - $mail->setBcc($blindCarbonCopyAddress); + $mail->addBcc(new Address($blindCarbonCopyAddress)); } if ($text !== null && $html !== null) { - $mail->setBody($html, 'text/html'); - $mail->addPart($text, 'text/plain'); + $mail->html($html); + $mail->text($text); } elseif ($text !== null) { - $mail->setBody($text, 'text/plain'); + $mail->text($text); } elseif ($html !== null) { - $mail->setBody($html, 'text/html'); + $mail->html($html); } $this->addAttachments($mail); @@ -116,37 +119,38 @@ public function perform(): ?ActionResponse ); return $response; } else { - $mail->send(); + $mailer = new MailerService(); + $mailer->getMailer()->send($mail); } return null; } /** - * @param SwiftMailerMessage $mail + * @param Email $mail */ - protected function addAttachments(SwiftMailerMessage $mail): void + protected function addAttachments(Email $mail): void { $attachments = $this->options['attachments'] ?? null; if (is_array($attachments)) { foreach ($attachments as $attachment) { if (is_string($attachment)) { - $mail->attach(\Swift_Attachment::fromPath($attachment)); + $mail->addPart(new DataPart(new File($attachment))); } elseif (is_object($attachment) && ($attachment instanceof UploadedFileInterface)) { - $mail->attach(new \Swift_Attachment($attachment->getStream()->getContents(), $attachment->getClientFilename(), $attachment->getClientMediaType())); + $mail->addPart(new DataPart($attachment->getStream()->getContents(), $attachment->getClientFilename(), $attachment->getClientMediaType())); } elseif (is_object($attachment) && ($attachment instanceof PersistentResource)) { $stream = $attachment->getStream(); if (!is_bool($stream)) { $content = stream_get_contents($stream); if (!is_bool($content)) { - $mail->attach(new \Swift_Attachment($content, $attachment->getFilename(), $attachment->getMediaType())); + $mail->addPart(new DataPart($content, $attachment->getFilename(), $attachment->getMediaType())); } } } elseif (is_array($attachment) && isset($attachment['content']) && isset($attachment['name'])) { $content = $attachment['content']; $name = $attachment['name']; - $type = $attachment['type'] ?? MediaTypes::getMediaTypeFromFilename($name); - $mail->attach(new \Swift_Attachment($content, $name, $type)); + $type = $attachment['type'] ?? MediaTypes::getMediaTypeFromFilename($name); + $mail->addPart(new DataPart($content, $name, $type)); } } } diff --git a/Classes/Runtime/FusionObjects/ValidatorImplementation.php b/Classes/Runtime/FusionObjects/ValidatorImplementation.php index bb7423e..18b7d0b 100644 --- a/Classes/Runtime/FusionObjects/ValidatorImplementation.php +++ b/Classes/Runtime/FusionObjects/ValidatorImplementation.php @@ -17,6 +17,7 @@ use Neos\Error\Messages\Result; use Neos\Flow\Validation\Validator\ValidatorInterface; use Neos\Flow\Validation\ValidatorResolver; +use Neos\Fusion\Exception\RuntimeException; use Neos\Fusion\FusionObjects\AbstractFusionObject; class ValidatorImplementation extends AbstractFusionObject implements ValidatorInterface @@ -27,15 +28,6 @@ class ValidatorImplementation extends AbstractFusionObject implements ValidatorI */ protected $validatorResolver; - /** - * Return reference to self during fusion evaluation - * @return $this - */ - public function evaluate() - { - return $this; - } - /** * @param mixed $value * @return Result @@ -46,11 +38,23 @@ public function validate($value): Result $this->fusionValue('type'), $this->fusionValue('options') ); + if ($validator === null) { + throw new \RuntimeException('Validator could not get created.', 1744410020); + } return $validator->validate($value); } /** - * @return array[] + * Return reference to self during fusion evaluation + * @return $this + */ + public function evaluate() + { + return $this; + } + + /** + * @return mixed[] */ public function getOptions(): array { diff --git a/Classes/Runtime/Helper/SchemaDefinition.php b/Classes/Runtime/Helper/SchemaDefinition.php index fc7760d..c331fca 100644 --- a/Classes/Runtime/Helper/SchemaDefinition.php +++ b/Classes/Runtime/Helper/SchemaDefinition.php @@ -132,6 +132,9 @@ public function validate($data): Result $validationConfiguration['type'], $validationConfiguration['options'] ?? [] ); + if ($validator === null) { + throw new \RuntimeException('Validator could not get created.', 1744410020); + } $propertyValidationResult->merge($validator->validate($data)); } diff --git a/Documentation/RuntimeActionReference.rst b/Documentation/RuntimeActionReference.rst index ad59677..3e382c8 100644 --- a/Documentation/RuntimeActionReference.rst +++ b/Documentation/RuntimeActionReference.rst @@ -47,11 +47,11 @@ Example:: Neos.Fusion.Form.Runtime:Email ------------------------------ -The email action uses swiftmailer to create and send an email. It supports +The email action uses symfonymailer to create and send an email. It supports multipart emails and file attachments that can even be created on the fly from form data. -.. note:: The neos/swiftmailer package must be installed separately. +.. note:: The neos/symfonymailer package must be installed separately. Options: diff --git a/composer.json b/composer.json index d5bcf20..602e8a4 100644 --- a/composer.json +++ b/composer.json @@ -6,21 +6,21 @@ "GPL-3.0-or-later" ], "require": { - "php" : ">7.2", - "neos/flow": "^6.0 || ^7.0 || ^8.0 || ^9.0 || dev-master", - "neos/fusion": "^5.0 || ^7.0 || ^8.0 || ^9.0 || dev-master", - "neos/fusion-afx": "^1.2 || ^7.0 || ^8.0 || ^9.0 || dev-master", + "php" : ">=8.2", + "neos/flow": "^8.0 || ^9.0", + "neos/fusion": "^8.0 || ^9.0", + "neos/fusion-afx": "^1.2 || ^7.0 || ^8.0 || ^9.0", "neos/utility-arrays": "*", "neos/utility-objecthandling": "*", "psr/http-factory": "*" }, "require-dev": { - "neos/swiftmailer": "*", + "neos/symfonymailer": "*", "phpunit/phpunit": "^7.1 || ^8.0 || ^9.0", - "phpstan/phpstan": "^0.12.78" + "phpstan/phpstan": "^2.1" }, "suggest": { - "neos/swiftmailer": "Required for the Neos.Fusion.Form.Runtime:Email action to work" + "neos/symfonymailer": "Required for the Neos.Fusion.Form.Runtime:Email action to work" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon index 2cdf332..fb81767 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,5 +2,7 @@ parameters: level: 1 paths: - Classes + treatPhpDocTypesAsCertain: false + reportUnmatchedIgnoredErrors: false ignoreErrors: - '#Unsafe usage of new static\(\).#' From fa65b595aace289246e30a28e2d44a1b2057ed8b Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sat, 12 Apr 2025 20:25:49 +0200 Subject: [PATCH 2/6] TASK: Run tests also for PHP8.2 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83011b6..b515d4d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: [ '8.3', '8.4' ] + php-versions: [ '8.2', '8.3', '8.4' ] flow-versions: [ '8.3', '9.0' ] runs-on: ubuntu-latest From 35628fcd7d577d5606fe9e63ab0bc4baa685fc1a Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sat, 12 Apr 2025 20:26:09 +0200 Subject: [PATCH 3/6] BUGFIX: Inject MailerService --- Classes/Runtime/Action/EmailAction.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Classes/Runtime/Action/EmailAction.php b/Classes/Runtime/Action/EmailAction.php index 3b8d4c0..b549e2b 100644 --- a/Classes/Runtime/Action/EmailAction.php +++ b/Classes/Runtime/Action/EmailAction.php @@ -13,6 +13,7 @@ * source code. */ +use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\ActionResponse; use Neos\Flow\ResourceManagement\PersistentResource; use Neos\Fusion\Form\Runtime\Domain\Exception\ActionException; @@ -26,6 +27,9 @@ class EmailAction extends AbstractAction { + #[Flow\Inject] + protected MailerService $mailerService; + /** * @return ActionResponse|null * @throws ActionException @@ -69,7 +73,7 @@ public function perform(): ?ActionResponse ->subject($subject); if (is_array($recipientAddress)) { - $mail->addTo(...array_map(fn ($entry) => new Address($entry), $recipientAddress)); + $mail->addTo(...array_map(fn($entry) => new Address($entry), $recipientAddress)); } else { $mail->addTo(new Address($recipientAddress, $recipientName)); } @@ -100,9 +104,9 @@ public function perform(): ?ActionResponse if ($testMode === true) { $response = new ActionResponse(); $response->setContent( - /** - * @phpstan-ignore-next-line - */ + /** + * @phpstan-ignore-next-line + */ \Neos\Flow\var_dump( [ 'sender' => [$senderAddress => $senderName], @@ -119,8 +123,7 @@ public function perform(): ?ActionResponse ); return $response; } else { - $mailer = new MailerService(); - $mailer->getMailer()->send($mail); + $this->mailer->getMailer()->send($mail); } return null; From 5aaffb3693ce5489baf8449b805dc21e23b5d92d Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sat, 12 Apr 2025 20:31:33 +0200 Subject: [PATCH 4/6] BUGFIX: Inject MailerService --- Classes/Runtime/Action/EmailAction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Runtime/Action/EmailAction.php b/Classes/Runtime/Action/EmailAction.php index b549e2b..a02bd5b 100644 --- a/Classes/Runtime/Action/EmailAction.php +++ b/Classes/Runtime/Action/EmailAction.php @@ -123,7 +123,7 @@ public function perform(): ?ActionResponse ); return $response; } else { - $this->mailer->getMailer()->send($mail); + $this->mailerService->getMailer()->send($mail); } return null; From e1b80dd367e133632de3fb3fe12df7d810b03923 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sat, 12 Apr 2025 21:41:56 +0200 Subject: [PATCH 5/6] TASK: StyleCI --- Classes/Runtime/Action/EmailAction.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Classes/Runtime/Action/EmailAction.php b/Classes/Runtime/Action/EmailAction.php index a02bd5b..b6fb163 100644 --- a/Classes/Runtime/Action/EmailAction.php +++ b/Classes/Runtime/Action/EmailAction.php @@ -73,7 +73,7 @@ public function perform(): ?ActionResponse ->subject($subject); if (is_array($recipientAddress)) { - $mail->addTo(...array_map(fn($entry) => new Address($entry), $recipientAddress)); + $mail->addTo(...array_map(fn ($entry) => new Address($entry), $recipientAddress)); } else { $mail->addTo(new Address($recipientAddress, $recipientName)); } @@ -104,9 +104,9 @@ public function perform(): ?ActionResponse if ($testMode === true) { $response = new ActionResponse(); $response->setContent( - /** - * @phpstan-ignore-next-line - */ + /** + * @phpstan-ignore-next-line + */ \Neos\Flow\var_dump( [ 'sender' => [$senderAddress => $senderName], From e4d6f37b4461be2967f351c55903c49ba7118adb Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sat, 12 Apr 2025 22:48:02 +0200 Subject: [PATCH 6/6] FEATURE: Replace swiftmailer with symfonymailer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 602e8a4..7901a2d 100644 --- a/composer.json +++ b/composer.json @@ -10,12 +10,12 @@ "neos/flow": "^8.0 || ^9.0", "neos/fusion": "^8.0 || ^9.0", "neos/fusion-afx": "^1.2 || ^7.0 || ^8.0 || ^9.0", + "neos/symfonymailer": "^0.1.0", "neos/utility-arrays": "*", "neos/utility-objecthandling": "*", "psr/http-factory": "*" }, "require-dev": { - "neos/symfonymailer": "*", "phpunit/phpunit": "^7.1 || ^8.0 || ^9.0", "phpstan/phpstan": "^2.1" },