7
7
8
8
namespace Magento \PageBuilder \Model \Filter ;
9
9
10
+ use DOMDocument ;
11
+ use DOMElement ;
12
+ use DOMException ;
13
+ use DOMNode ;
14
+ use DOMXPath ;
15
+ use Exception ;
16
+ use Magento \Framework \Exception \LocalizedException ;
17
+ use Magento \Framework \Math \Random ;
18
+ use Magento \Framework \Serialize \Serializer \Json ;
19
+ use Magento \Framework \View \ConfigInterface ;
20
+ use Magento \PageBuilder \Plugin \Filter \TemplatePlugin ;
21
+ use Psr \Log \LoggerInterface ;
22
+
10
23
/**
11
24
* Specific template filters for Page Builder content
12
25
*/
13
26
class Template
14
27
{
15
28
/**
16
- * @var \Magento\Framework\View\ ConfigInterface
29
+ * @var ConfigInterface
17
30
*/
18
31
private $ viewConfig ;
19
32
20
33
/**
21
- * @var \Psr\Log\ LoggerInterface
34
+ * @var LoggerInterface
22
35
*/
23
36
private $ logger ;
24
37
25
38
/**
26
- * @var \ DOMDocument
39
+ * @var DOMDocument
27
40
*/
28
41
private $ domDocument ;
29
42
30
43
/**
31
- * @var \Magento\Framework\Math\ Random
44
+ * @var Random
32
45
*/
33
46
private $ mathRandom ;
34
47
35
48
/**
36
- * @var \Magento\Framework\Serialize\Serializer\ Json
49
+ * @var Json
37
50
*/
38
51
private $ json ;
39
52
40
53
/**
41
- * @param \Psr\Log\LoggerInterface $logger
42
- * @param \Magento\Framework\View\ConfigInterface $viewConfig
43
- * @param \Magento\Framework\Math\Random $mathRandom
44
- * @param \Magento\Framework\Serialize\Serializer\Json $json
54
+ * @var array
55
+ */
56
+ private $ scripts ;
57
+
58
+ /**
59
+ * @param LoggerInterface $logger
60
+ * @param ConfigInterface $viewConfig
61
+ * @param Random $mathRandom
62
+ * @param Json $json
45
63
*/
46
64
public function __construct (
47
- \ Psr \ Log \ LoggerInterface $ logger ,
48
- \ Magento \ Framework \ View \ ConfigInterface $ viewConfig ,
49
- \ Magento \ Framework \ Math \ Random $ mathRandom ,
50
- \ Magento \ Framework \ Serialize \ Serializer \ Json $ json
65
+ LoggerInterface $ logger ,
66
+ ConfigInterface $ viewConfig ,
67
+ Random $ mathRandom ,
68
+ Json $ json
51
69
) {
52
70
$ this ->logger = $ logger ;
53
71
$ this ->viewConfig = $ viewConfig ;
@@ -59,22 +77,22 @@ public function __construct(
59
77
* After filter of template data apply transformations
60
78
*
61
79
* @param string $result
62
- *
63
80
* @return string
64
81
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
65
82
*/
66
83
public function filter (string $ result ) : string
67
84
{
68
85
$ this ->domDocument = false ;
86
+ $ this ->scripts = [];
69
87
70
88
// Validate if the filtered result requires background image processing
71
- if (preg_match (\ Magento \ PageBuilder \ Plugin \ Filter \ TemplatePlugin::BACKGROUND_IMAGE_PATTERN , $ result )) {
89
+ if (preg_match (TemplatePlugin::BACKGROUND_IMAGE_PATTERN , $ result )) {
72
90
$ document = $ this ->getDomDocument ($ result );
73
91
$ this ->generateBackgroundImageStyles ($ document );
74
92
}
75
93
76
94
// Process any HTML content types, they need to be decoded on the front-end
77
- if (preg_match (\ Magento \ PageBuilder \ Plugin \ Filter \ TemplatePlugin::HTML_CONTENT_TYPE_PATTERN , $ result )) {
95
+ if (preg_match (TemplatePlugin::HTML_CONTENT_TYPE_PATTERN , $ result )) {
78
96
$ document = $ this ->getDomDocument ($ result );
79
97
$ uniqueNodeNameToDecodedOuterHtmlMap = $ this ->generateDecodedHtmlPlaceholderMappingInDocument ($ document );
80
98
}
@@ -112,6 +130,8 @@ function ($matches) {
112
130
113
131
$ result = $ docHtml ;
114
132
}
133
+
134
+ $ result = $ this ->unmaskScriptTags ($ result );
115
135
}
116
136
117
137
return $ result ;
@@ -122,9 +142,9 @@ function ($matches) {
122
142
*
123
143
* @param string $html
124
144
*
125
- * @return \ DOMDocument
145
+ * @return DOMDocument
126
146
*/
127
- private function getDomDocument (string $ html ) : \ DOMDocument
147
+ private function getDomDocument (string $ html ) : DOMDocument
128
148
{
129
149
if (!$ this ->domDocument ) {
130
150
$ this ->domDocument = $ this ->createDomDocument ($ html );
@@ -138,14 +158,16 @@ private function getDomDocument(string $html) : \DOMDocument
138
158
*
139
159
* @param string $html
140
160
*
141
- * @return \ DOMDocument
161
+ * @return DOMDocument
142
162
*/
143
- private function createDomDocument (string $ html ) : \ DOMDocument
163
+ private function createDomDocument (string $ html ) : DOMDocument
144
164
{
145
- $ domDocument = new \DOMDocument ('1.0 ' , 'UTF-8 ' );
165
+ $ html = $ this ->maskScriptTags ($ html );
166
+
167
+ $ domDocument = new DOMDocument ('1.0 ' , 'UTF-8 ' );
146
168
set_error_handler (
147
169
function ($ errorNumber , $ errorString ) {
148
- throw new \ DOMException ($ errorString , $ errorNumber );
170
+ throw new DOMException ($ errorString , $ errorNumber );
149
171
}
150
172
);
151
173
$ string = mb_convert_encoding ($ html , 'HTML-ENTITIES ' , 'UTF-8 ' );
@@ -155,7 +177,7 @@ function ($errorNumber, $errorString) {
155
177
'<html><body> ' . $ string . '</body></html> '
156
178
);
157
179
libxml_clear_errors ();
158
- } catch (\ Exception $ e ) {
180
+ } catch (Exception $ e ) {
159
181
restore_error_handler ();
160
182
$ this ->logger ->critical ($ e );
161
183
}
@@ -167,16 +189,16 @@ function ($errorNumber, $errorString) {
167
189
/**
168
190
* Convert encoded HTML content types to placeholders and generate decoded outer html map for future replacement
169
191
*
170
- * @param \ DOMDocument $document
192
+ * @param DOMDocument $document
171
193
* @return array
172
- * @throws \Magento\Framework\Exception\ LocalizedException
194
+ * @throws LocalizedException
173
195
*/
174
- private function generateDecodedHtmlPlaceholderMappingInDocument (\ DOMDocument $ document ): array
196
+ private function generateDecodedHtmlPlaceholderMappingInDocument (DOMDocument $ document ): array
175
197
{
176
- $ xpath = new \ DOMXPath ($ document );
198
+ $ xpath = new DOMXPath ($ document );
177
199
178
200
// construct xpath query to fetch top-level ancestor html content type nodes
179
- /** @var $htmlContentTypeNodes \ DOMNode[] */
201
+ /** @var $htmlContentTypeNodes DOMNode[] */
180
202
$ htmlContentTypeNodes = $ xpath ->query (
181
203
'//*[@data-content-type="html" and not(@data-decoded="true")] ' .
182
204
'[not(ancestor::*[@data-content-type="html"])] '
@@ -221,7 +243,7 @@ private function generateDecodedHtmlPlaceholderMappingInDocument(\DOMDocument $d
221
243
// by the dom library
222
244
$ uniqueNodeName = $ this ->mathRandom ->getRandomString (32 , $ this ->mathRandom ::CHARS_LOWERS );
223
245
224
- $ uniqueNode = new \ DOMElement ($ uniqueNodeName );
246
+ $ uniqueNode = new DOMElement ($ uniqueNodeName );
225
247
$ htmlContentTypeNode ->parentNode ->replaceChild ($ uniqueNode , $ htmlContentTypeNode );
226
248
227
249
$ uniqueNodeNameToDecodedOuterHtmlMap [$ uniqueNodeName ] = $ decodedOuterHtml ;
@@ -233,14 +255,14 @@ private function generateDecodedHtmlPlaceholderMappingInDocument(\DOMDocument $d
233
255
/**
234
256
* Generate the CSS for any background images on the page
235
257
*
236
- * @param \ DOMDocument $document
258
+ * @param DOMDocument $document
237
259
*/
238
- private function generateBackgroundImageStyles (\ DOMDocument $ document ) : void
260
+ private function generateBackgroundImageStyles (DOMDocument $ document ) : void
239
261
{
240
- $ xpath = new \ DOMXPath ($ document );
262
+ $ xpath = new DOMXPath ($ document );
241
263
$ nodes = $ xpath ->query ('//*[@data-background-images] ' );
242
264
foreach ($ nodes as $ node ) {
243
- /* @var \ DOMElement $node */
265
+ /* @var DOMElement $node */
244
266
$ backgroundImages = $ node ->attributes ->getNamedItem ('data-background-images ' );
245
267
if ($ backgroundImages ->nodeValue !== '' ) {
246
268
$ elementClass = uniqid ('background-image- ' );
@@ -337,4 +359,47 @@ private function getMediaQuery(string $view) : ?string
337
359
}
338
360
return null ;
339
361
}
362
+
363
+ /**
364
+ * Masks "x-magento-template" script tags in html content before loading it into DOM parser
365
+ *
366
+ * DOMDocument::loadHTML() will remove any closing tag inside script tag and will result in broken html template
367
+ *
368
+ * @param string $content
369
+ * @return string
370
+ * @see https://bugs.php.net/bug.php?id=52012
371
+ */
372
+ private function maskScriptTags (string $ content ): string
373
+ {
374
+ $ tag = 'script ' ;
375
+ $ content = preg_replace_callback (
376
+ sprintf ('#<%1$s[^>]*type="text/x-magento-template\"[^>]*>.*?</%1$s>#is ' , $ tag ),
377
+ function ($ matches ) {
378
+ $ key = $ this ->mathRandom ->getRandomString (32 , $ this ->mathRandom ::CHARS_LOWERS );
379
+ $ this ->scripts [$ key ] = $ matches [0 ];
380
+ return '< ' . $ key . '> ' . '</ ' . $ key . '> ' ;
381
+ },
382
+ $ content
383
+ );
384
+ return $ content ;
385
+ }
386
+
387
+ /**
388
+ * Replaces masked "x-magento-template" script tags with their corresponding content
389
+ *
390
+ * @param string $content
391
+ * @return string
392
+ * @see maskScriptTags()
393
+ */
394
+ private function unmaskScriptTags (string $ content ): string
395
+ {
396
+ foreach ($ this ->scripts as $ key => $ script ) {
397
+ $ content = str_replace (
398
+ '< ' . $ key . '> ' . '</ ' . $ key . '> ' ,
399
+ $ script ,
400
+ $ content
401
+ );
402
+ }
403
+ return $ content ;
404
+ }
340
405
}
0 commit comments