Skip to content

Commit 065b9ba

Browse files
ENGCOM-3598: Use the new json serializer which throws an error when failing #16154
- Merge Pull Request #16154 from storefront-bvba/magento2:layout-config-serializer-fix - Merged commits: 1. da7f293 2. d7a11d7
2 parents 0502433 + d7a11d7 commit 065b9ba

File tree

7 files changed

+180
-13
lines changed

7 files changed

+180
-13
lines changed

app/code/Magento/Checkout/Block/Onepage.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Onepage extends \Magento\Framework\View\Element\Template
3838
protected $layoutProcessors;
3939

4040
/**
41-
* @var \Magento\Framework\Serialize\Serializer\Json
41+
* @var \Magento\Framework\Serialize\SerializerInterface
4242
*/
4343
private $serializer;
4444

@@ -48,25 +48,26 @@ class Onepage extends \Magento\Framework\View\Element\Template
4848
* @param \Magento\Checkout\Model\CompositeConfigProvider $configProvider
4949
* @param array $layoutProcessors
5050
* @param array $data
51-
* @param \Magento\Framework\Serialize\Serializer\Json|null $serializer
52-
* @throws \RuntimeException
51+
* @param \Magento\Framework\Serialize\Serializer\Json $serializer
52+
* @param \Magento\Framework\Serialize\SerializerInterface $serializerInterface
5353
*/
5454
public function __construct(
5555
\Magento\Framework\View\Element\Template\Context $context,
5656
\Magento\Framework\Data\Form\FormKey $formKey,
5757
\Magento\Checkout\Model\CompositeConfigProvider $configProvider,
5858
array $layoutProcessors = [],
5959
array $data = [],
60-
\Magento\Framework\Serialize\Serializer\Json $serializer = null
60+
\Magento\Framework\Serialize\Serializer\Json $serializer = null,
61+
\Magento\Framework\Serialize\SerializerInterface $serializerInterface = null
6162
) {
6263
parent::__construct($context, $data);
6364
$this->formKey = $formKey;
6465
$this->_isScopePrivate = true;
6566
$this->jsLayout = isset($data['jsLayout']) && is_array($data['jsLayout']) ? $data['jsLayout'] : [];
6667
$this->configProvider = $configProvider;
6768
$this->layoutProcessors = $layoutProcessors;
68-
$this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()
69-
->get(\Magento\Framework\Serialize\Serializer\Json::class);
69+
$this->serializer = $serializerInterface ?: \Magento\Framework\App\ObjectManager::getInstance()
70+
->get(\Magento\Framework\Serialize\Serializer\JsonHexTag::class);
7071
}
7172

7273
/**
@@ -78,7 +79,7 @@ public function getJsLayout()
7879
$this->jsLayout = $processor->process($this->jsLayout);
7980
}
8081

81-
return json_encode($this->jsLayout, JSON_HEX_TAG);
82+
return $this->serializer->serialize($this->jsLayout);
8283
}
8384

8485
/**
@@ -120,6 +121,6 @@ public function getBaseUrl()
120121
*/
121122
public function getSerializedCheckoutConfig()
122123
{
123-
return json_encode($this->getCheckoutConfig(), JSON_HEX_TAG);
124+
return $this->serializer->serialize($this->getCheckoutConfig());
124125
}
125126
}

app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class OnepageTest extends \PHPUnit\Framework\TestCase
3535
/**
3636
* @var \PHPUnit_Framework_MockObject_MockObject
3737
*/
38-
private $serializer;
38+
private $serializerMock;
3939

4040
protected function setUp()
4141
{
@@ -49,15 +49,16 @@ protected function setUp()
4949
\Magento\Checkout\Block\Checkout\LayoutProcessorInterface::class
5050
);
5151

52-
$this->serializer = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class);
52+
$this->serializerMock = $this->createMock(\Magento\Framework\Serialize\Serializer\JsonHexTag::class);
5353

5454
$this->model = new \Magento\Checkout\Block\Onepage(
5555
$contextMock,
5656
$this->formKeyMock,
5757
$this->configProviderMock,
5858
[$this->layoutProcessorMock],
5959
[],
60-
$this->serializer
60+
$this->serializerMock,
61+
$this->serializerMock
6162
);
6263
}
6364

@@ -93,6 +94,7 @@ public function testGetJsLayout()
9394
$processedLayout = ['layout' => ['processed' => true]];
9495
$jsonLayout = '{"layout":{"processed":true}}';
9596
$this->layoutProcessorMock->expects($this->once())->method('process')->with([])->willReturn($processedLayout);
97+
$this->serializerMock->expects($this->once())->method('serialize')->willReturn($jsonLayout);
9698

9799
$this->assertEquals($jsonLayout, $this->model->getJsLayout());
98100
}
@@ -101,6 +103,7 @@ public function testGetSerializedCheckoutConfig()
101103
{
102104
$checkoutConfig = ['checkout', 'config'];
103105
$this->configProviderMock->expects($this->once())->method('getConfig')->willReturn($checkoutConfig);
106+
$this->serializerMock->expects($this->once())->method('serialize')->willReturn(json_encode($checkoutConfig));
104107

105108
$this->assertEquals(json_encode($checkoutConfig), $this->model->getSerializedCheckoutConfig());
106109
}

app/code/Magento/Checkout/etc/frontend/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
<item name="totalsSortOrder" xsi:type="object">Magento\Checkout\Block\Checkout\TotalsProcessor</item>
6060
<item name="directoryData" xsi:type="object">Magento\Checkout\Block\Checkout\DirectoryDataProcessor</item>
6161
</argument>
62+
<argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\JsonHexTag</argument>
6263
</arguments>
6364
</type>
6465
<type name="Magento\Checkout\Block\Cart\Totals">

app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
namespace Magento\Ui\TemplateEngine\Xhtml;
77

8+
use Magento\Framework\App\ObjectManager;
9+
use Magento\Framework\Serialize\Serializer\JsonHexTag;
810
use Magento\Framework\View\Layout\Generator\Structure;
911
use Magento\Framework\View\Element\UiComponentInterface;
1012
use Magento\Framework\View\TemplateEngine\Xhtml\Template;
@@ -42,25 +44,33 @@ class Result implements ResultInterface
4244
*/
4345
protected $logger;
4446

47+
/**
48+
* @var JsonHexTag
49+
*/
50+
private $jsonSerializer;
51+
4552
/**
4653
* @param Template $template
4754
* @param CompilerInterface $compiler
4855
* @param UiComponentInterface $component
4956
* @param Structure $structure
5057
* @param LoggerInterface $logger
58+
* @param JsonHexTag $jsonSerializer
5159
*/
5260
public function __construct(
5361
Template $template,
5462
CompilerInterface $compiler,
5563
UiComponentInterface $component,
5664
Structure $structure,
57-
LoggerInterface $logger
65+
LoggerInterface $logger,
66+
JsonHexTag $jsonSerializer = null
5867
) {
5968
$this->template = $template;
6069
$this->compiler = $compiler;
6170
$this->component = $component;
6271
$this->structure = $structure;
6372
$this->logger = $logger;
73+
$this->jsonSerializer = $jsonSerializer ?? ObjectManager::getInstance()->get(JsonHexTag::class);
6474
}
6575

6676
/**
@@ -81,7 +91,7 @@ public function getDocumentElement()
8191
public function appendLayoutConfiguration()
8292
{
8393
$layoutConfiguration = $this->wrapContent(
84-
json_encode($this->structure->generate($this->component), JSON_HEX_TAG)
94+
$this->jsonSerializer->serialize($this->structure->generate($this->component))
8595
);
8696
$this->template->append($layoutConfiguration);
8797
}

lib/internal/Magento/Framework/Serialize/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
**Serialize** library provides interface *SerializerInterface* and multiple implementations:
44

55
* *Json* - default implementation. Uses PHP native json_encode/json_decode functions;
6+
* *JsonHexTag* - default implementation. Uses PHP native json_encode/json_decode functions with `JSON_HEX_TAG` option enabled;
67
* *Serialize* - less secure than *Json*, but gives higher performance on big arrays. Uses PHP native serialize/unserialize functions, does not unserialize objects on PHP 7.
78

89
Using *Serialize* implementation directly is discouraged, always use *SerializerInterface*, using *Serialize* implementation may lead to security vulnerabilities.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\Serialize\Serializer;
10+
11+
use Magento\Framework\Serialize\SerializerInterface;
12+
13+
/**
14+
* Serialize data to JSON with the JSON_HEX_TAG option enabled
15+
* (All < and > are converted to \u003C and \u003E),
16+
* unserialize JSON encoded data
17+
*
18+
* @api
19+
* @since 100.2.0
20+
*/
21+
class JsonHexTag extends Json implements SerializerInterface
22+
{
23+
/**
24+
* @inheritDoc
25+
* @since 100.2.0
26+
*/
27+
public function serialize($data): string
28+
{
29+
$result = json_encode($data, JSON_HEX_TAG);
30+
if (false === $result) {
31+
throw new \InvalidArgumentException('Unable to serialize value.');
32+
}
33+
return $result;
34+
}
35+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\Serialize\Test\Unit\Serializer;
10+
11+
use Magento\Framework\DataObject;
12+
use Magento\Framework\Serialize\Serializer\JsonHexTag;
13+
14+
class JsonHexTagTest extends \PHPUnit\Framework\TestCase
15+
{
16+
/**
17+
* @var \Magento\Framework\Serialize\Serializer\Json
18+
*/
19+
private $json;
20+
21+
protected function setUp()
22+
{
23+
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
24+
$this->json = $objectManager->getObject(JsonHexTag::class);
25+
}
26+
27+
/**
28+
* @param string|int|float|bool|array|null $value
29+
* @param string $expected
30+
* @dataProvider serializeDataProvider
31+
*/
32+
public function testSerialize($value, $expected)
33+
{
34+
$this->assertEquals(
35+
$expected,
36+
$this->json->serialize($value)
37+
);
38+
}
39+
40+
public function serializeDataProvider()
41+
{
42+
$dataObject = new DataObject(['something']);
43+
return [
44+
['', '""'],
45+
['string', '"string"'],
46+
[null, 'null'],
47+
[false, 'false'],
48+
[['a' => 'b', 'd' => 123], '{"a":"b","d":123}'],
49+
[123, '123'],
50+
[10.56, '10.56'],
51+
[$dataObject, '{}'],
52+
['< >', '"\u003C \u003E"'],
53+
];
54+
}
55+
56+
/**
57+
* @param string $value
58+
* @param string|int|float|bool|array|null $expected
59+
* @dataProvider unserializeDataProvider
60+
*/
61+
public function testUnserialize($value, $expected)
62+
{
63+
$this->assertEquals(
64+
$expected,
65+
$this->json->unserialize($value)
66+
);
67+
}
68+
69+
/**
70+
* @return array
71+
*/
72+
public function unserializeDataProvider(): array {
73+
return [
74+
['""', ''],
75+
['"string"', 'string'],
76+
['null', null],
77+
['false', false],
78+
['{"a":"b","d":123}', ['a' => 'b', 'd' => 123]],
79+
['123', 123],
80+
['10.56', 10.56],
81+
['{}', []],
82+
['"\u003C \u003E"', '< >'],
83+
];
84+
}
85+
86+
/**
87+
* @expectedException \InvalidArgumentException
88+
* @expectedExceptionMessage Unable to serialize value.
89+
*/
90+
public function testSerializeException()
91+
{
92+
$this->json->serialize(STDOUT);
93+
}
94+
95+
/**
96+
* @expectedException \InvalidArgumentException
97+
* @expectedExceptionMessage Unable to unserialize value.
98+
* @dataProvider unserializeExceptionDataProvider
99+
*/
100+
public function testUnserializeException($value)
101+
{
102+
$this->json->unserialize($value);
103+
}
104+
105+
/**
106+
* @return array
107+
*/
108+
public function unserializeExceptionDataProvider(): array {
109+
return [
110+
[''],
111+
[false],
112+
[null],
113+
['{']
114+
];
115+
}
116+
}

0 commit comments

Comments
 (0)