Skip to content

Commit 2c064cf

Browse files
committed
Merge branch 'AC-3170-2' of github.com:magento-cia/magento2ce into cia-2.4.6-develop-bugfixes-08012022
2 parents 0979ad2 + 146ab42 commit 2c064cf

File tree

5 files changed

+326
-13
lines changed

5 files changed

+326
-13
lines changed

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

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
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;
22+
use Magento\Framework\Filter\Template\FilteringDepthMeter;
2123

2224
/**
2325
* Template filter
@@ -100,24 +102,44 @@ class Template implements \Zend_Filter_Interface
100102
*/
101103
private $variableResolver;
102104

105+
/**
106+
* @var SignatureProvider|null
107+
*/
108+
private $signatureProvider;
109+
110+
/**
111+
* @var FilteringDepthMeter|null
112+
*/
113+
private $filteringDepthMeter;
114+
103115
/**
104116
* @param StringUtils $string
105117
* @param array $variables
106118
* @param DirectiveProcessorInterface[] $directiveProcessors
107119
* @param VariableResolverInterface|null $variableResolver
120+
* @param SignatureProvider|null $signatureProvider
121+
* @param FilteringDepthMeter|null $filteringDepthMeter
108122
*/
109123
public function __construct(
110124
StringUtils $string,
111125
$variables = [],
112126
$directiveProcessors = [],
113-
VariableResolverInterface $variableResolver = null
127+
VariableResolverInterface $variableResolver = null,
128+
SignatureProvider $signatureProvider = null,
129+
FilteringDepthMeter $filteringDepthMeter = null
114130
) {
115131
$this->string = $string;
116132
$this->setVariables($variables);
117133
$this->directiveProcessors = $directiveProcessors;
118134
$this->variableResolver = $variableResolver ?? ObjectManager::getInstance()
119135
->get(VariableResolverInterface::class);
120136

137+
$this->signatureProvider = $signatureProvider ?? ObjectManager::getInstance()
138+
->get(SignatureProvider::class);
139+
140+
$this->filteringDepthMeter = $filteringDepthMeter ?? ObjectManager::getInstance()
141+
->get(FilteringDepthMeter::class);
142+
121143
if (empty($directiveProcessors)) {
122144
$this->directiveProcessors = [
123145
'depend' => ObjectManager::getInstance()->get(DependDirective::class),
@@ -180,22 +202,118 @@ public function filter($value)
180202
)->render());
181203
}
182204

205+
$this->filteringDepthMeter->descend();
206+
207+
// Processing of template directives.
208+
$templateDirectivesResults = $this->processDirectives($value);
209+
210+
foreach ($templateDirectivesResults as $result) {
211+
$value = str_replace($result['directive'], $result['output'], $value);
212+
}
213+
214+
// Processing of deferred directives received from child templates
215+
// or nested directives.
216+
$deferredDirectivesResults = $this->processDirectives($value, true);
217+
218+
foreach ($deferredDirectivesResults as $result) {
219+
$value = str_replace($result['directive'], $result['output'], $value);
220+
}
221+
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+
}
234+
}
235+
}
236+
237+
$value = $this->afterFilter($value);
238+
239+
$this->filteringDepthMeter->ascend();
240+
241+
return $value;
242+
}
243+
244+
/**
245+
* Processes template directives and returns an array that contains results produced by each directive.
246+
*
247+
* @param string $value
248+
* @param bool $isSigned
249+
*
250+
* @return array
251+
*
252+
* @throws InvalidArgumentException
253+
* @throws \Magento\Framework\Exception\LocalizedException
254+
*/
255+
private function processDirectives($value, $isSigned = false): array
256+
{
257+
$results = [];
258+
183259
foreach ($this->directiveProcessors as $directiveProcessor) {
184260
if (!$directiveProcessor instanceof DirectiveProcessorInterface) {
185261
throw new InvalidArgumentException(
186262
'Directive processors must implement ' . DirectiveProcessorInterface::class
187263
);
188264
}
189265

190-
if (preg_match_all($directiveProcessor->getRegularExpression(), $value, $constructions, PREG_SET_ORDER)) {
266+
$pattern = $directiveProcessor->getRegularExpression();
267+
268+
if ($isSigned) {
269+
$pattern = $this->embedSignatureIntoPattern($pattern);
270+
}
271+
272+
if (preg_match_all($pattern, $value, $constructions, PREG_SET_ORDER)) {
191273
foreach ($constructions as $construction) {
192274
$replacedValue = $directiveProcessor->process($construction, $this, $this->templateVars);
193-
$value = str_replace($construction[0], $replacedValue, $value);
275+
276+
$results[] = [
277+
'directive' => $construction[0],
278+
'output' => $replacedValue
279+
];
194280
}
195281
}
196282
}
197283

198-
return $this->afterFilter($value);
284+
return $results;
285+
}
286+
287+
/**
288+
* Modifies given regular expression pattern to be able to recognize signed directives.
289+
*
290+
* @param string $pattern
291+
*
292+
* @return string
293+
*
294+
* @throws \Magento\Framework\Exception\LocalizedException
295+
*/
296+
private function embedSignatureIntoPattern(string $pattern): string
297+
{
298+
$signature = $this->signatureProvider->get();
299+
300+
$closingDelimiters = [
301+
'(' => ')',
302+
'{' => '}',
303+
'[' => ']',
304+
'<' => '>'
305+
];
306+
307+
$closingDelimiter = $openingDelimiter = substr(trim($pattern), 0, 1);
308+
309+
if (array_key_exists($openingDelimiter, $closingDelimiters)) {
310+
$closingDelimiter = $closingDelimiters[$openingDelimiter];
311+
}
312+
313+
$pattern = substr_replace($pattern, $signature, strpos($pattern, $openingDelimiter) + 1, 0);
314+
$pattern = substr_replace($pattern, $signature, strrpos($pattern, $closingDelimiter), 0);
315+
316+
return $pattern;
199317
}
200318

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

0 commit comments

Comments
 (0)