Skip to content

Commit 40bd6c7

Browse files
AC-3170: Incorrect processing order of Email template directives
1 parent 0107106 commit 40bd6c7

File tree

3 files changed

+174
-4
lines changed

3 files changed

+174
-4
lines changed

lib/internal/Magento/Framework/Filter/Template.php

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Magento\Framework\Filter\DirectiveProcessor\TemplateDirective;
1919
use Magento\Framework\Filter\DirectiveProcessor\VarDirective;
2020
use Magento\Framework\Stdlib\StringUtils;
21+
use Magento\Framework\Filter\Template\SignatureProvider;
2122

2223
/**
2324
* Template filter
@@ -100,23 +101,32 @@ class Template implements \Zend_Filter_Interface
100101
*/
101102
private $variableResolver;
102103

104+
/**
105+
* @var SignatureProvider|null
106+
*/
107+
private $signatureProvider;
108+
103109
/**
104110
* @param StringUtils $string
105111
* @param array $variables
106112
* @param DirectiveProcessorInterface[] $directiveProcessors
107113
* @param VariableResolverInterface|null $variableResolver
114+
* @param SignatureProvider|null $signatureProvider
108115
*/
109116
public function __construct(
110117
StringUtils $string,
111118
$variables = [],
112119
$directiveProcessors = [],
113-
VariableResolverInterface $variableResolver = null
120+
VariableResolverInterface $variableResolver = null,
121+
SignatureProvider $signatureProvider = null
114122
) {
115123
$this->string = $string;
116124
$this->setVariables($variables);
117125
$this->directiveProcessors = $directiveProcessors;
118126
$this->variableResolver = $variableResolver ?? ObjectManager::getInstance()
119127
->get(VariableResolverInterface::class);
128+
$this->signatureProvider = $signatureProvider ?? ObjectManager::getInstance()
129+
->get(SignatureProvider::class);
120130

121131
if (empty($directiveProcessors)) {
122132
$this->directiveProcessors = [
@@ -180,22 +190,83 @@ public function filter($value)
180190
)->render());
181191
}
182192

193+
// Processing of template directives.
194+
$templateDirectivesResults = $this->processDirectives($value);
195+
196+
foreach ($templateDirectivesResults as $result) {
197+
$value = str_replace($result['directive'], $result['output'], $value);
198+
}
199+
200+
// Processing of deferred directives received from child templates.
201+
$deferredDirectivesResults = $this->processDirectives($value, true);
202+
203+
foreach ($deferredDirectivesResults as $result) {
204+
$value = str_replace($result['directive'], $result['output'], $value);
205+
}
206+
207+
// Signing own deferred directives (if any).
208+
$signature = $this->signatureProvider->get();
209+
210+
foreach ($templateDirectivesResults as $result) {
211+
if ($result['directive'] === $result['output']) {
212+
$value = str_replace(
213+
$result['output'],
214+
$signature . $result['output'] . $signature,
215+
$value
216+
);
217+
}
218+
}
219+
220+
$value = $this->afterFilter($value);
221+
222+
return $value;
223+
}
224+
225+
/**
226+
* Processes template directives and returns an array
227+
* that contains results produced by each directive.
228+
*
229+
* @param string $value
230+
* @param bool $isSigned
231+
*
232+
* @return array
233+
*
234+
* @throws InvalidArgumentException
235+
* @throws \Magento\Framework\Exception\LocalizedException
236+
*/
237+
private function processDirectives($value, $isSigned = false)
238+
{
239+
$results = [];
240+
183241
foreach ($this->directiveProcessors as $directiveProcessor) {
184242
if (!$directiveProcessor instanceof DirectiveProcessorInterface) {
185243
throw new InvalidArgumentException(
186244
'Directive processors must implement ' . DirectiveProcessorInterface::class
187245
);
188246
}
189247

190-
if (preg_match_all($directiveProcessor->getRegularExpression(), $value, $constructions, PREG_SET_ORDER)) {
248+
$pattern = $directiveProcessor->getRegularExpression();
249+
250+
if ($isSigned) {
251+
$signature = $this->signatureProvider->get();
252+
253+
$pattern = substr_replace($pattern, $signature, strpos($pattern, '/') + 1, 0);
254+
$pattern = substr_replace($pattern, $signature, strrpos($pattern, '/'), 0);
255+
}
256+
257+
if (preg_match_all($pattern, $value, $constructions, PREG_SET_ORDER)) {
191258
foreach ($constructions as $construction) {
192259
$replacedValue = $directiveProcessor->process($construction, $this, $this->templateVars);
193-
$value = str_replace($construction[0], $replacedValue, $value);
260+
261+
$results[] = [
262+
'directive' => $construction[0],
263+
'output' => $replacedValue
264+
];
194265
}
195266
}
196267
}
197268

198-
return $this->afterFilter($value);
269+
return $results;
199270
}
200271

201272
/**
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\Filter\Template;
7+
8+
/**
9+
* Provider of a signature.
10+
*
11+
* Provides a signature which should be used to sign deferred directives
12+
* (directives that should be processed in scope of a parent template
13+
* instead of own scope, e.g. {{inlinecss}}).
14+
*/
15+
class SignatureProvider
16+
{
17+
/**
18+
* @var string|null
19+
*/
20+
private $signature;
21+
22+
/**
23+
* @var \Magento\Framework\Math\Random
24+
*/
25+
private $random;
26+
27+
/**
28+
* @param \Magento\Framework\Math\Random $random
29+
*/
30+
public function __construct(
31+
\Magento\Framework\Math\Random $random
32+
) {
33+
$this->random = $random;
34+
}
35+
36+
/**
37+
* Generates a random string which will be used as a signature during runtime.
38+
*
39+
* @return string
40+
*
41+
* @throws \Magento\Framework\Exception\LocalizedException
42+
*/
43+
public function get()
44+
{
45+
if ($this->signature === null) {
46+
$this->signature = $this->random->getRandomString(32);
47+
}
48+
49+
return $this->signature;
50+
}
51+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\Filter\Test\Unit\Template;
7+
8+
class SignatureProviderTest extends \PHPUnit\Framework\TestCase
9+
{
10+
/**
11+
* @var \Magento\Framework\Filter\Template\SignatureProvider
12+
*/
13+
protected $signatureProvider;
14+
15+
/**
16+
* @var \Magento\Framework\Math\Random|\PHPUnit\Framework\MockObject\MockObject
17+
*/
18+
protected $random;
19+
20+
protected function setUp(): void
21+
{
22+
$this->random = $this->createPartialMock(
23+
\Magento\Framework\Math\Random::class,
24+
['getRandomString']
25+
);
26+
27+
$this->signatureProvider = new \Magento\Framework\Filter\Template\SignatureProvider(
28+
$this->random
29+
);
30+
}
31+
32+
public function testGet()
33+
{
34+
$expectedResult = 'Z0FFbeCU2R8bsVGJuTdkXyiiZBzsaceV';
35+
36+
$this->random->expects($this->once())
37+
->method('getRandomString')
38+
->with(32)
39+
->willReturn($expectedResult);
40+
41+
$this->assertEquals($expectedResult, $this->signatureProvider->get());
42+
43+
$this->random->expects($this->never())
44+
->method('getRandomString');
45+
46+
$this->assertEquals($expectedResult, $this->signatureProvider->get());
47+
}
48+
}

0 commit comments

Comments
 (0)