diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php index 999ca4d078d5..186f8b01b2bf 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php @@ -4,6 +4,13 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Serializer; class TemplatedEmailTest extends TestCase { @@ -33,4 +40,77 @@ public function testSerialize() $this->assertEquals('text.html.twig', $email->getHtmlTemplate()); $this->assertEquals($context, $email->getContext()); } + + public function testSymfonySerialize() + { + // we don't add from/sender to check that validation is not triggered to serialize an email + $e = new TemplatedEmail(); + $e->to('you@example.com'); + $e->textTemplate('email.txt.twig'); + $e->htmlTemplate('email.html.twig'); + $e->context(['foo' => 'bar']); + $e->attach('Some Text file', 'test.txt'); + $expected = clone $e; + + $expectedJson = <<serialize($e, 'json'); + $this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + $n = $serializer->deserialize($serialized, TemplatedEmail::class, 'json'); + $serialized = $serializer->serialize($e, 'json'); + $this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + $n->from('fabien@symfony.com'); + $expected->from('fabien@symfony.com'); + $this->assertEquals($expected->getHeaders(), $n->getHeaders()); + $this->assertEquals($expected->getBody(), $n->getBody()); + } } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index f90018f48b3d..4d19c35bf475 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -23,14 +23,16 @@ }, "require-dev": { "egulias/email-validator": "^2.1.10", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", "symfony/form": "^5.1", "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", + "symfony/mime": "^5.2", "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^4.4|^5.1", "symfony/routing": "^4.4|^5.0", "symfony/translation": "^5.0", "symfony/yaml": "^4.4|^5.0", @@ -38,6 +40,7 @@ "symfony/security-core": "^4.4|^5.0", "symfony/security-csrf": "^4.4|^5.0", "symfony/security-http": "^4.4|^5.0", + "symfony/serializer": "^5.2", "symfony/stopwatch": "^4.4|^5.0", "symfony/console": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index fbeb348b6e55..a0c5be34b993 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -39,9 +39,11 @@ use Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; +use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; @@ -76,6 +78,10 @@ ->args([[], service('serializer.name_converter.metadata_aware')]) ->tag('serializer.normalizer', ['priority' => -915]) + ->set('serializer.normalizer.mime_message', MimeMessageNormalizer::class) + ->args([service('serializer.normalizer.property')]) + ->tag('serializer.normalizer', ['priority' => -915]) + ->set('serializer.normalizer.datetimezone', DateTimeZoneNormalizer::class) ->tag('serializer.normalizer', ['priority' => -915]) @@ -114,6 +120,18 @@ ->alias(ObjectNormalizer::class, 'serializer.normalizer.object') + ->set('serializer.normalizer.property', PropertyNormalizer::class) + ->args([ + service('serializer.mapping.class_metadata_factory'), + service('serializer.name_converter.metadata_aware'), + service('property_info')->ignoreOnInvalid(), + service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), + null, + [], + ]) + + ->alias(PropertyNormalizer::class, 'serializer.normalizer.property') + ->set('serializer.denormalizer.array', ArrayDenormalizer::class) ->tag('serializer.normalizer', ['priority' => -990]) diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 0d84c344aa42..cd49e43511ea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -52,7 +52,7 @@ "symfony/security-bundle": "^5.1", "symfony/security-csrf": "^4.4|^5.0", "symfony/security-http": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0", + "symfony/serializer": "^5.2", "symfony/stopwatch": "^4.4|^5.0", "symfony/string": "^5.0", "symfony/translation": "^5.0", @@ -62,7 +62,7 @@ "symfony/yaml": "^4.4|^5.0", "symfony/property-info": "^4.4|^5.0", "symfony/web-link": "^4.4|^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "paragonie/sodium_compat": "^1.8", "twig/twig": "^2.10|^3.0" }, diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index e5f9f11b36fc..b21e99e8961d 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -378,7 +378,7 @@ public function attachPart(DataPart $part) } /** - * @return DataPart[] + * @return array|DataPart[] */ public function getAttachments(): array { diff --git a/src/Symfony/Component/Mime/Header/Headers.php b/src/Symfony/Component/Mime/Header/Headers.php index 3f1efcbbebe8..9493e2c2da23 100644 --- a/src/Symfony/Component/Mime/Header/Headers.php +++ b/src/Symfony/Component/Mime/Header/Headers.php @@ -39,6 +39,9 @@ final class Headers 'return-path' => PathHeader::class, ]; + /** + * @var HeaderInterface[][] + */ private $headers = []; private $lineLength = 76; diff --git a/src/Symfony/Component/Mime/Part/TextPart.php b/src/Symfony/Component/Mime/Part/TextPart.php index 72c7d4f69596..8772a3367c1b 100644 --- a/src/Symfony/Component/Mime/Part/TextPart.php +++ b/src/Symfony/Component/Mime/Part/TextPart.php @@ -28,6 +28,9 @@ class TextPart extends AbstractPart private $body; private $charset; private $subtype; + /** + * @var ?string + */ private $disposition; private $name; private $encoding; diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index 230df0791e15..117c19e4f838 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -19,6 +19,13 @@ use Symfony\Component\Mime\Part\Multipart\MixedPart; use Symfony\Component\Mime\Part\Multipart\RelatedPart; use Symfony\Component\Mime\Part\TextPart; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Serializer; class EmailTest extends TestCase { @@ -384,4 +391,71 @@ public function testSerialize() $this->assertEquals($expected->getHeaders(), $n->getHeaders()); $this->assertEquals($e->getBody(), $n->getBody()); } + + public function testSymfonySerialize() + { + // we don't add from/sender to check that validation is not triggered to serialize an email + $e = new Email(); + $e->to('you@example.com'); + $e->text('Text content'); + $e->html('HTML content'); + $e->attach('Some Text file', 'test.txt'); + $expected = clone $e; + + $expectedJson = <<content", + "htmlCharset": "utf-8", + "attachments": [ + { + "body": "Some Text file", + "name": "test.txt", + "content-type": null, + "inline": false + } + ], + "headers": { + "to": [ + { + "addresses": [ + { + "address": "you@example.com", + "name": "" + } + ], + "name": "To", + "lineLength": 76, + "lang": null, + "charset": "utf-8" + } + ] + }, + "body": null, + "message": null +} +EOF; + + $extractor = new PhpDocExtractor(); + $propertyNormalizer = new PropertyNormalizer(null, null, $extractor); + $serializer = new Serializer([ + new ArrayDenormalizer(), + new MimeMessageNormalizer($propertyNormalizer), + new ObjectNormalizer(null, null, null, $extractor), + $propertyNormalizer, + ], [new JsonEncoder()]); + + $serialized = $serializer->serialize($e, 'json'); + $this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + $n = $serializer->deserialize($serialized, Email::class, 'json'); + $serialized = $serializer->serialize($e, 'json'); + $this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + $n->from('fabien@symfony.com'); + $expected->from('fabien@symfony.com'); + $this->assertEquals($expected->getHeaders(), $n->getHeaders()); + $this->assertEquals($expected->getBody(), $n->getBody()); + } } diff --git a/src/Symfony/Component/Mime/Tests/MessageTest.php b/src/Symfony/Component/Mime/Tests/MessageTest.php index bd5d7ca8903d..ed9b8e614246 100644 --- a/src/Symfony/Component/Mime/Tests/MessageTest.php +++ b/src/Symfony/Component/Mime/Tests/MessageTest.php @@ -17,7 +17,17 @@ use Symfony\Component\Mime\Header\MailboxListHeader; use Symfony\Component\Mime\Header\UnstructuredHeader; use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\AlternativePart; +use Symfony\Component\Mime\Part\Multipart\MixedPart; use Symfony\Component\Mime\Part\TextPart; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Serializer; class MessageTest extends TestCase { @@ -147,4 +157,109 @@ public function testToString() $this->assertStringMatchesFormat($expected, str_replace("\r\n", "\n", $message->toString())); $this->assertStringMatchesFormat($expected, str_replace("\r\n", "\n", implode('', iterator_to_array($message->toIterable(), false)))); } + + public function testSymfonySerialize() + { + // we don't add from/sender to check that it's not needed to serialize an email + $body = new MixedPart( + new AlternativePart( + new TextPart('Text content'), + new TextPart('HTML content', 'utf-8', 'html') + ), + new DataPart('text data', 'text.txt') + ); + $e = new Message((new Headers())->addMailboxListHeader('To', ['you@example.com']), $body); + $expected = clone $e; + + $expectedJson = <<serialize($e, 'json'); + $this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + $n = $serializer->deserialize($serialized, Message::class, 'json'); + $this->assertEquals($expected->getHeaders(), $n->getHeaders()); + + $serialized = $serializer->serialize($e, 'json'); + $this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } } diff --git a/src/Symfony/Component/Mime/composer.json b/src/Symfony/Component/Mime/composer.json index 9e4b0e5803e1..62a3d49e44df 100644 --- a/src/Symfony/Component/Mime/composer.json +++ b/src/Symfony/Component/Mime/composer.json @@ -17,10 +17,14 @@ ], "require": { "php": ">=7.2.5", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.15", + "symfony/property-access": "^4.4|^5.1", + "symfony/property-info": "^4.4|^5.1", + "symfony/serializer": "^5.2" }, "require-dev": { "egulias/email-validator": "^2.1.10", diff --git a/src/Symfony/Component/Serializer/Normalizer/MimeMessageNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/MimeMessageNormalizer.php new file mode 100644 index 000000000000..a1c4f169bbf5 --- /dev/null +++ b/src/Symfony/Component/Serializer/Normalizer/MimeMessageNormalizer.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Normalizer; + +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Header\HeaderInterface; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Header\UnstructuredHeader; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Normalize Mime message classes. + * + * It forces the use of a PropertyNormalizer instance for normalization + * of all data objects composing a Message. + * + * Emails using resources for any parts are not serializable. + */ +final class MimeMessageNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +{ + private $serializer; + private $normalizer; + private $headerClassMap; + private $headersProperty; + + public function __construct(PropertyNormalizer $normalizer) + { + $this->normalizer = $normalizer; + $this->headerClassMap = (new \ReflectionClassConstant(Headers::class, 'HEADER_CLASS_MAP'))->getValue(); + $this->headersProperty = new \ReflectionProperty(Headers::class, 'headers'); + $this->headersProperty->setAccessible(true); + } + + public function setSerializer(SerializerInterface $serializer) + { + $this->serializer = $serializer; + $this->normalizer->setSerializer($serializer); + } + + /** + * {@inheritdoc} + */ + public function normalize($object, ?string $format = null, array $context = []) + { + if ($object instanceof Headers) { + $ret = []; + foreach ($this->headersProperty->getValue($object) as $name => $header) { + $ret[$name] = $this->serializer->normalize($header, $format, $context); + } + + return $ret; + } + + if ($object instanceof AbstractPart) { + $ret = $this->normalizer->normalize($object, $format, $context); + $ret['class'] = \get_class($object); + + return $ret; + } + + return $this->normalizer->normalize($object, $format, $context); + } + + /** + * {@inheritdoc} + */ + public function denormalize($data, string $type, ?string $format = null, array $context = []) + { + if (Headers::class === $type) { + $ret = []; + foreach ($data as $headers) { + foreach ($headers as $header) { + $ret[] = $this->serializer->denormalize($header, $this->headerClassMap[strtolower($header['name'])] ?? UnstructuredHeader::class, $format, $context); + } + } + + return new Headers(...$ret); + } + + if (AbstractPart::class === $type) { + $type = $data['class']; + unset($data['class']); + } + + return $this->normalizer->denormalize($data, $type, $format, $context); + } + + /** + * {@inheritdoc} + */ + public function supportsNormalization($data, string $format = null) + { + return $data instanceof Message || $data instanceof Headers || $data instanceof HeaderInterface || $data instanceof Address || $data instanceof AbstractPart; + } + + /** + * {@inheritdoc} + */ + public function supportsDenormalization($data, string $type, ?string $format = null) + { + return is_a($type, Message::class, true) || Headers::class === $type || AbstractPart::class === $type; + } + + /** + * {@inheritdoc} + */ + public function hasCacheableSupportsMethod(): bool + { + return __CLASS__ === static::class; + } +} diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index f2de3df93df7..9e6b2d60ab5e 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -23,7 +23,7 @@ "require-dev": { "doctrine/annotations": "~1.0", "doctrine/cache": "~1.0", - "phpdocumentor/reflection-docblock": "^3.2|^4.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/cache": "^4.4|^5.0", "symfony/config": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy