10000 [FrameworkBundle][Mailer] Add a way to configure some email headers f… · symfony/symfony@805e9e6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 805e9e6

Browse files
committed
[FrameworkBundle][Mailer] Add a way to configure some email headers from semantic configuration
1 parent 5a74790 commit 805e9e6

File tree

15 files changed

+301
-36
lines changed

15 files changed

+301
-36
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode)
14921492
->thenInvalid('"dsn" and "transports" cannot be used together.')
14931493
->end()
14941494
->fixXmlConfig('transport')
1495+
->fixXmlConfig('header')
14951496
->children()
14961497
->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end()
14971498
->scalarNode('dsn')->defaultNull()->end()
@@ -1515,6 +1516,20 @@ private function addMailerSection(ArrayNodeDefinition $rootNode)
15151516
->end()
15161517
->end()
15171518
->end()
1519+
->arrayNode('headers')
1520+
->normalizeKeys(false)
1521+
->useAttributeAsKey('name')
1522+
->prototype('array')
1523+
->normalizeKeys(false)
1524+
->beforeNormalization()
1525+
->ifTrue(function ($v) { return !\is_array($v) || array_keys($v) !== ['value']; })
1526+
->then(function ($v) { return ['value' => $v]; })
1527+
->end()
1528+
->children()
1529+
->variableNode('value')->end()
1530+
->end()
1531+
->end()
1532+
->end()
15181533
->end()
15191534
->end()
15201535
->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
use Symfony\Component\Messenger\MessageBusInterface;
9090
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
9191
use Symfony\Component\Messenger\Transport\TransportInterface;
92+
use Symfony\Component\Mime\Header\Headers;
9293
use Symfony\Component\Mime\MimeTypeGuesserInterface;
9394
use Symfony\Component\Mime\MimeTypes;
9495
use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory;
@@ -1986,12 +1987,24 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
19861987
}
19871988
}
19881989

1989-
$recipients = $config['envelope']['recipients'] ?? null;
1990-
$sender = $config['envelope']['sender'] ?? null;
1991-
19921990
$envelopeListener = $container->getDefinition('mailer.envelope_listener');
1993-
$envelopeListener->setArgument(0, $sender);
1994-
$envelopeListener->setArgument(1, $recipients);
1991+
$envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null);
1992+
$envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null);
1993+
1994+
if ($config['headers']) {
1995+
$headers = new Definition(Headers::class);
1996+
foreach ($config['headers'] as $name => $data) {
1997+
$value = $data['value'];
1998+
if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) {
1999+
$value = (array) $value;
2000+
}
2001+
$headers->addMethodCall('addHeader', [$name, $value]);
2002+
}
2003+
$messageListener = $container->getDefinition('mailer.message_listener');
2004+
$messageListener->setArgument(0, $headers);
2005+
} else {
2006+
$container->removeDefinition('mailer.message_listener');
2007+
}
19952008
}
19962009

19972010
private function registerNotifierConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)

src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
<tag name="kernel.event_subscriber"/>
4040
</service>
4141

42+
<service id="mailer.message_listener" class="Symfony\Component\Mailer\EventListener\MessageListener">
43+
<argument /> <!-- headers -->
44+
<tag name="kernel.event_subscriber"/>
45+
</service>
46+
4247
<service id="mailer.logger_message_listener" class="Symfony\Component\Mailer\EventListener\MessageLoggerListener">
4348
<tag name="kernel.event_subscriber"/>
4449
</service>

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,14 +560,19 @@
560560
<xsd:complexType name="mailer">
561561
<xsd:sequence>
562562
<xsd:element name="envelope" type="mailer_envelope" minOccurs="0" maxOccurs="1" />
563+
<xsd:element name="header" type="header" minOccurs="0" maxOccurs="unbounded" />
563564
</xsd:sequence>
564565
<xsd:attribute name="dsn" type="xsd:string" />
565566
<xsd:attribute name="message-bus" type="xsd:string" />
566567
</xsd:complexType>
567568

569+
<xsd:complexType name="header" mixed="true">
570+
<xsd:attribute name="name" type="xsd:string" use="required" />
571+
</xsd:complexType>
572+
568573
<xsd:complexType name="mailer_envelope">
569574
<xsd:sequence>
570-
<xsd:element name="sender" type="xsd:string" />
575+
<xsd:element name="sender" type="xsd:string" minOccurs="0" maxOccurs="1" />
571576
<xsd:element name="recipients" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
572577
</xsd:sequence>
573578
</xsd:complexType>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
493493
'transports' => [],
494494
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
495495
'message_bus' => null,
496+
'headers' => [],
496497
],
497498
'notifier' => [
498499
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,10 @@
77
'sender' => 'sender@example.org',
88
'recipients' => ['redirected@example.org', 'redirected1@example.org'],
99
],
10+
'headers' => [
11+
'from' => 'from@example.org',
12+
'bcc' => ['bcc1@example.org', 'bcc2@example.org'],
13+
'foo' => 'bar',
14+
],
1015
],
1116
]);

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
<framework:recipie F438 nts>redirected@example.org</framework:recipients>
1414
<framework:recipients>redirected1@example.org</framework:recipients>
1515
</framework:envelope>
16+
<framework:header name="from">from@example.org</framework:header>
17+
<framework:header name="bcc">bcc1@example.org</framework:header>
18+
<framework:header name="foo">bar</framework:header>
1619
</framework:mailer>
1720
</framework:config>
1821
</container>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ framework:
66
recipients:
77
- redirected@example.org
88
- redirected1@example.org
9+
headers:
10+
from: from@example.org
11+
bcc: [bcc1@example.org, bcc2@example.org]
12+
foo: bar

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,11 @@ public function testMailer(): void
14351435
$this->assertSame('sender@example.org', $l->getArgument(0));
14361436
$this->assertSame(['redirected@example.org', 'redirected1@example.org'], $l->getArgument(1));
14371437
$this->assertEquals(new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE), $container->getDefinition('mailer.mailer')->getArgument(1));
1438+
1439+
$this->assertTrue($container->hasDefinition('mailer.message_listener'));
1440+
$l = $container->getDefinition('mailer.message_listener');
1441+
$h = $l->getArgument(0);
1442+
$this->assertCount(3, $h->getMethodCalls());
14381443
}
14391444

14401445
public function testMailerWithDisabledMessageBus(): void

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"symfony/expression-language": "^4.4|^5.0",
4646
"symfony/http-client": "^4.4|^5.0",
4747
"symfony/lock": "^4.4|^5.0",
48-
"symfony/mailer": "^4.4|^5.0",
48+
"symfony/mailer": "^5.2",
4949
"symfony/messenger": "^4.4|^5.0",
5050
"symfony/mime": "^4.4|^5.0",
5151
"symfony/process": "^4.4|^5.0",
@@ -79,7 +79,7 @@
7979
"symfony/http-client": "<4.4",
8080
"symfony/form": "<4.4",
8181
"symfony/lock": "<4.4",
82-
"symfony/mailer": "<4.4",
82+
"symfony/mailer": "<5.2",
8383
"symfony/messenger": "<4.4",
8484
"symfony/mime": "<4.4",
8585
"symfony/property-info": "<4.4",

src/Symfony/Component/Mailer/EventListener/MessageListener.php

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313

1414
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1515
use Symfony\Component\Mailer\Event\MessageEvent;
16+
use Symfony\Component\Mailer\Exception\InvalidArgumentException;
17+
use Symfony\Component\Mailer\Exception\RuntimeException;
1618
use Symfony\Component\Mime\BodyRendererInterface;
1719
use Symfony\Component\Mime\Header\Headers;
20+
use Symfony\Component\Mime\Header\MailboxListHeader;
1821
use Symfony\Component\Mime\Message;
1922

2023
/**
@@ -24,13 +27,38 @@
2427
*/
2528
class MessageListener implements EventSubscriberInterface
2629
{
30+
public const HEADER_SET_IF_EMPTY = 1;
31+
public const HEADER_ADD = 2;
32+
public const HEADER_REPLACE = 3;
33+
public const DEFAULT_RULES = [
34+
'from' => self::HEADER_SET_IF_EMPTY,
35+
'return-path' => self::HEADER_SET_IF_EMPTY,
36+
'reply-to' => self::HEADER_ADD,
37+
'to' => self::HEADER_SET_IF_EMPTY,
38+
'cc' => self::HEADER_ADD,
39+
'bcc' => self::HEADER_ADD,
40+
];
41+
2742
private $headers;
43+
private $headerRules = [];
2844
private $renderer;
2945

30-
public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null)
46+
public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES)
3147
{
3248
$this->headers = $headers;
3349
$this->renderer = $renderer;
50+
foreach ($headerRules as $headerName => $rule) {
51+
$this->addHeaderRule($headerName, $rule);
52+
}
53+
}
54+
55+
public function addHeaderRule(string $headerName, int $rule): void
56+
{
57+
if ($rule < 1 || $rule > 3) {
58+
throw new InvalidArgumentException(sprintf('The "%d" rule is not supported.', $rule));
59+
}
60+
61+
$this->headerRules[$headerName] = $rule;
3462
}
3563

3664
public function onMessage(MessageEvent $event): void
@@ -54,14 +82,38 @@ private function setHeaders(Message $message): void
5482
foreach ($this->headers->all() as $name => $header) {
5583
if (!$headers->has($name)) {
5684
$headers->add($header);
57-
} else {
58-
if (Headers::isUniqueHeader($name)) {
59-
continue;
60-
}
61-
$headers->add($header);
85+
86+
continue;
87+
}
88+
89+
switch ($this->headerRules[$name] ?? self::HEADER_SET_IF_EMPTY) {
90+
case self::HEADER_SET_IF_EMPTY:
91+
break;
92+
93+
case self::HEADER_REPLACE:
94+
$headers->remove($name);
95+
$headers->add($header);
96+
97+
break;
98+
99+
case self::HEADER_ADD:
100+
if (!Headers::isUniqueHeader($name)) {
101+
$headers->add($header);
102+
103+
break;
104+
}
105+
106+
$h = $headers->get($name);
107+
if (!$h instanceof MailboxListHeader) {
108+
throw new RuntimeException(sprintf('Unable to set header "%s".', $name));
109+
}
110+
111+
Headers::checkHeaderClass($header);
112+
foreach ($header->getAddresses() as $address) {
113+
$h->addAddress($address);
114+
}
62115
}
63116
}
64-
$message->setHeaders($headers);
65117
}
66118

67119
private function renderMessage(Message $message): void
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Mailer\Envelope;
16+
use Symfony\Component\Mailer\Event\MessageEvent;
17+
use Symfony\Component\Mailer\EventListener\MessageListener;
18+
use Symfony\Component\Mime\Address;
19+
use Symfony\Component\Mime\Header\Headers;
20+
use Symfony\Component\Mime\Header\MailboxListHeader;
21+
use Symfony\Component\Mime\Header\UnstructuredHeader;
22+
use Symfony\Component\Mime\Message;
23+
24+
class MessageListenerTest extends TestCase
25+
{
26+
/**
27+
* @dataProvider provideHeaders
28+
*/
29+
public function testHeaders(Headers $initialHeaders, Headers $defaultHeaders, Headers $expectedHeaders, array $rules = MessageListener::DEFAULT_RULES)
30+
{
31+
$message = new Message($initialHeaders);
32+
$listener = new MessageListener($defaultHeaders, null, $rules);
33+
$event = new MessageEvent($message, new Envelope(new Address('sender@example.com'), [new Address('recipient@example.com')]), 'smtp');
34+
$listener->onMessage($event);
35+
36+
$this->assertEquals($expectedHeaders, $event->getMessage()->getHeaders());
37+
}
38+
39+
public function provideHeaders(): iterable
40+
{
41+
$initialHeaders = new Headers();
42+
$defaultHeaders = (new Headers())
43+
->add(new MailboxListHeader('from', [new Address('from-default@example.com')]))
44+
;
45+
yield 'No defaults, all headers copied over' => [$initialHeaders, $defaultHeaders, $defaultHeaders];
46+
47+
$initialHeaders = new Headers();
48+
$defaultHeaders = (new Headers())
49+
->add(new UnstructuredHeader('foo', 'bar'))
50+
->add(new UnstructuredHeader('bar', 'foo'))
51+
;
52+
yield 'No defaults, default is to set if empty' D7AE ; => [$initialHeaders, $defaultHeaders, $defaultHeaders];
53+
54+
$initialHeaders = (new Headers())
55+
->add(new UnstructuredHeader('foo', 'initial'))
56+
;
57+
$defaultHeaders = (new Headers())
58+
->add(new UnstructuredHeader('foo', 'bar'))
59+
->add(new UnstructuredHeader('bar', 'foo'))
60+
;
61+
$expectedHeaders = (new Headers())
62+
->add(new UnstructuredHeader('foo', 'initial'))
63+
->add(new UnstructuredHeader('bar', 'foo'))
64+
;
65+
yield 'Some defaults, default is to set if empty' => [$initialHeaders, $defaultHeaders, $expectedHeaders];
66+
67+
$initialHeaders = (new Headers())
68+
->add(new UnstructuredHeader('foo', 'initial'))
69+
;
70+
$defaultHeaders = (new Headers())
71+
->add(new UnstructuredHeader('foo', 'bar'))
72+
->add(new UnstructuredHeader('bar', 'foo'))
73+
;
74+
$rules = [
75+
'foo' => MessageListener::HEADER_REPLACE,
76+
];
77+
yield 'Some defaults, replace if set' => [$initialHeaders, $defaultHeaders, $defaultHeaders, $rules];
78+
79+
$initialHeaders = (new Headers())
80+
->add(new UnstructuredHeader('foo', 'bar'))
81+
;
82+
$defaultHeaders = (new Headers())
83+
->add(new UnstructuredHeader('foo', 'foo'))
84+
;
85+
$expectedHeaders = (new Headers())
86+
->add(new UnstructuredHeader('foo', 'bar'))
87+
->add(new UnstructuredHeader('foo', 'foo'))
88+
;
89+
$rules = [
90+
'foo' => MessageListener::HEADER_ADD,
91+
];
92+
yield 'Some defaults, add if set (not unique header)' => [$initialHeaders, $defaultHeaders, $expectedHeaders, $rules];
93+
94+
$initialHeaders = (new Headers())
95+
->add(new MailboxListHeader('bcc', [new Address('bcc-initial@example.com')]))
96+
;
97+
$defaultHeaders = (new Headers())
98+
->add(new MailboxListHeader('bcc', [new Address('bcc-default@example.com'), new Address('bcc-default-1@example.com')]))
99+
;
100+
$expectedHeaders = (new Headers())
101+
->add(new MailboxListHeader('bcc', [new Address('bcc-initial@example.com'), new Address('bcc-default@example.com'), new Address('bcc-default-1@example.com')]))
102+
;
103+
yield 'bcc, add another bcc (unique header)' => [$initialHeaders, $defaultHeaders, $expectedHeaders];
104+
}
105+
}

src/Symfony/Component/Mailer/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"egulias/email-validator": "^2.1.10",
2121
"psr/log": "~1.0",
2222
"symfony/event-dispatcher": "^4.4|^5.0",
23-
"symfony/mime": "^4.4|^5.0",
23+
"symfony/mime": "^5.2",
2424
"symfony/polyfill-php80": "^1.15",
2525
"symfony/service-contracts": "^1.1|^2"
2626
},

0 commit comments

Comments
 (0)
0