Skip to content

Commit 48e6a55

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

File tree

5 files changed

+139
-25
lines changed

5 files changed

+139
-25
lines changed

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

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Magento\Framework\Filter\DirectiveProcessor\VarDirective;
2020
use Magento\Framework\Stdlib\StringUtils;
2121
use Magento\Framework\Filter\Template\SignatureProvider;
22+
use Magento\Framework\Filter\Template\FilteringDepthMeter;
2223

2324
/**
2425
* Template filter
@@ -106,28 +107,39 @@ class Template implements \Zend_Filter_Interface
106107
*/
107108
private $signatureProvider;
108109

110+
/**
111+
* @var FilteringDepthMeter|null
112+
*/
113+
private $filteringDepthMeter;
114+
109115
/**
110116
* @param StringUtils $string
111117
* @param array $variables
112118
* @param DirectiveProcessorInterface[] $directiveProcessors
113119
* @param VariableResolverInterface|null $variableResolver
114120
* @param SignatureProvider|null $signatureProvider
121+
* @param FilteringDepthMeter|null $filteringDepthMeter
115122
*/
116123
public function __construct(
117124
StringUtils $string,
118125
$variables = [],
119126
$directiveProcessors = [],
120127
VariableResolverInterface $variableResolver = null,
121-
SignatureProvider $signatureProvider = null
128+
SignatureProvider $signatureProvider = null,
129+
FilteringDepthMeter $filteringDepthMeter = null
122130
) {
123131
$this->string = $string;
124132
$this->setVariables($variables);
125133
$this->directiveProcessors = $directiveProcessors;
126134
$this->variableResolver = $variableResolver ?? ObjectManager::getInstance()
127135
->get(VariableResolverInterface::class);
136+
128137
$this->signatureProvider = $signatureProvider ?? ObjectManager::getInstance()
129138
->get(SignatureProvider::class);
130139

140+
$this->filteringDepthMeter = $filteringDepthMeter ?? ObjectManager::getInstance()
141+
->get(FilteringDepthMeter::class);
142+
131143
if (empty($directiveProcessors)) {
132144
$this->directiveProcessors = [
133145
'depend' => ObjectManager::getInstance()->get(DependDirective::class),
@@ -190,41 +202,47 @@ public function filter($value)
190202
)->render());
191203
}
192204

205+
$this->filteringDepthMeter->descend();
206+
193207
// Processing of template directives.
194208
$templateDirectivesResults = $this->processDirectives($value);
195209

196210
foreach ($templateDirectivesResults as $result) {
197211
$value = str_replace($result['directive'], $result['output'], $value);
198212
}
199213

200-
// Processing of deferred directives received from child templates.
214+
// Processing of deferred directives received from child templates
215+
// or nested directives.
201216
$deferredDirectivesResults = $this->processDirectives($value, true);
202217

203218
foreach ($deferredDirectivesResults as $result) {
204219
$value = str_replace($result['directive'], $result['output'], $value);
205220
}
206221

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-
);
222+
if ($this->filteringDepthMeter->showMark() > 1) {
223+
// Signing own deferred directives (if any).
224+
$signature = $this->signatureProvider->get();
225+
226+
foreach ($templateDirectivesResults as $result) {
227+
if ($result['directive'] === $result['output']) {
228+
$value = str_replace(
229+
$result['output'],
230+
$signature . $result['output'] . $signature,
231+
$value
232+
);
233+
}
217234
}
218235
}
219236

220237
$value = $this->afterFilter($value);
221238

239+
$this->filteringDepthMeter->ascend();
240+
222241
return $value;
223242
}
224243

225244
/**
226-
* Processes template directives and returns an array
227-
* that contains results produced by each directive.
245+
* Processes template directives and returns an array that contains results produced by each directive.
228246
*
229247
* @param string $value
230248
* @param bool $isSigned
@@ -234,7 +252,7 @@ public function filter($value)
234252
* @throws InvalidArgumentException
235253
* @throws \Magento\Framework\Exception\LocalizedException
236254
*/
237-
private function processDirectives($value, $isSigned = false)
255+
private function processDirectives($value, $isSigned = false): array
238256
{
239257
$results = [];
240258

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Filter\Template;
9+
10+
/**
11+
* Meter of template filtering depth.
12+
*
13+
* Records and provides template/directive filtering depth (filtering recursion).
14+
* Filtering depth 1 means that template or directive is root and has no parents.
15+
*/
16+
class FilteringDepthMeter
17+
{
18+
/**
19+
* @var int
20+
*/
21+
private $depth = 0;
22+
23+
/**
24+
* Increases filtering depth.
25+
*
26+
* @return void
27+
*/
28+
public function descend()
29+
{
30+
$this->depth++;
31+
}
32+
33+
/**
34+
* Decreases filtering depth.
35+
*
36+
* @return void
37+
*/
38+
public function ascend()
39+
{
40+
$this->depth--;
41+
}
42+
43+
/**
44+
* Shows current filtering depth.
45+
*
46+
* @return int
47+
*/
48+
public function showMark(): int
49+
{
50+
return $this->depth;
51+
}
52+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\Framework\Filter\Template;
79

810
/**
@@ -40,7 +42,7 @@ public function __construct(
4042
*
4143
* @throws \Magento\Framework\Exception\LocalizedException
4244
*/
43-
public function get()
45+
public function get(): string
4446
{
4547
if ($this->signature === null) {
4648
$this->signature = $this->random->getRandomString(32);

lib/internal/Magento/Framework/Filter/Test/Unit/Template/SignatureProviderTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\Framework\Filter\Test\Unit\Template;
79

810
class SignatureProviderTest extends \PHPUnit\Framework\TestCase

lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,43 @@ class TemplateTest extends TestCase
2828
*/
2929
private $store;
3030

31+
/**
32+
* @var \Magento\Framework\Filter\Template\SignatureProvider|\PHPUnit\Framework\MockObject\MockObject
33+
*/
34+
protected $signatureProvider;
35+
36+
/**
37+
* @var \Magento\Framework\Filter\Template\FilteringDepthMeter|\PHPUnit\Framework\MockObject\MockObject
38+
*/
39+
protected $filteringDepthMeter;
40+
3141
protected function setUp(): void
3242
{
3343
$objectManager = new ObjectManager($this);
34-
$this->templateFilter = $objectManager->getObject(Template::class);
44+
3545
$this->store = $objectManager->getObject(Store::class);
46+
47+
$this->signatureProvider = $this->createPartialMock(
48+
\Magento\Framework\Filter\Template\SignatureProvider::class,
49+
['get']
50+
);
51+
52+
$this->signatureProvider->expects($this->any())
53+
->method('get')
54+
->willReturn('Z0FFbeCU2R8bsVGJuTdkXyiiZBzsaceV');
55+
56+
$this->filteringDepthMeter = $this->createPartialMock(
57+
\Magento\Framework\Filter\Template\FilteringDepthMeter::class,
58+
['showMark']
59+
);
60+
61+
$this->templateFilter = $objectManager->getObject(
62+
\Magento\Framework\Filter\Template::class,
63+
[
64+
'signatureProvider' => $this->signatureProvider,
65+
'filteringDepthMeter' => $this->filteringDepthMeter
66+
]
67+
);
3668
}
3769

3870
/**
@@ -44,6 +76,10 @@ public function testAfterFilter()
4476
$value = 'test string';
4577
$expectedResult = 'TEST STRING';
4678

79+
$this->filteringDepthMeter->expects($this->any())
80+
->method('showMark')
81+
->willReturn(1);
82+
4783
// Build arbitrary object to pass into the addAfterFilterCallback method
4884
$callbackObject = $this->getMockBuilder('stdObject')
4985
->setMethods(['afterFilterCallbackMethod'])
@@ -72,6 +108,10 @@ public function testAfterFilterCallbackReset()
72108
$value = 'test string';
73109
$expectedResult = 'TEST STRING';
74110

111+
$this->filteringDepthMeter->expects($this->any())
112+
->method('showMark')
113+
->willReturn(1);
114+
75115
// Build arbitrary object to pass into the addAfterFilterCallback method
76116
$callbackObject = $this->getMockBuilder('stdObject')
77117
->setMethods(['afterFilterCallbackMethod'])
@@ -127,7 +167,7 @@ public function getTemplateAndExpectedResults($type)
127167
<ul>
128168
{{for in order.all_visible_items}}
129169
<li>
130-
name: , lastname: , age:
170+
name: , lastname: , age:
131171
</li>
132172
{{/for}}
133173
</ul>
@@ -137,14 +177,14 @@ public function getTemplateAndExpectedResults($type)
137177
$template = <<<TEMPLATE
138178
<ul>
139179
{{for in order.all_visible_items}}
140-
180+
141181
{{/for}}
142182
</ul>
143183
TEMPLATE;
144184
$expected = <<<TEMPLATE
145185
<ul>
146186
{{for in order.all_visible_items}}
147-
187+
148188
{{/for}}
149189
</ul>
150190
TEMPLATE;
@@ -153,14 +193,14 @@ public function getTemplateAndExpectedResults($type)
153193
$template = <<<TEMPLATE
154194
<ul>
155195
{{for in }}
156-
196+
157197
{{/for}}
158198
</ul>
159199
TEMPLATE;
160200
$expected = <<<TEMPLATE
161201
<ul>
162202
{{for in }}
163-
203+
164204
{{/for}}
165205
</ul>
166206
TEMPLATE;
@@ -178,17 +218,17 @@ public function getTemplateAndExpectedResults($type)
178218
TEMPLATE;
179219
$expected = <<<TEMPLATE
180220
<ul>
181-
221+
182222
<li>
183223
index: 0 sku: ABC123
184224
name: Product ABC price: 123 quantity: 2
185225
</li>
186-
226+
187227
<li>
188228
index: 1 sku: DOREMI
189229
name: Product DOREMI price: 456 quantity: 1
190230
</li>
191-
231+
192232
</ul>
193233
TEMPLATE;
194234
}

0 commit comments

Comments
 (0)