@@ -19,16 +19,25 @@ class Template implements \Zend_Filter_Interface
19
19
*/
20
20
const CONSTRUCTION_PATTERN = '/{{([a-z]{0,10})(.*?)}}/si ' ;
21
21
22
- /**#@+
23
- * Construction logic regular expression
22
+ /**
23
+ * Construction `depend` regular expression
24
24
*/
25
25
const CONSTRUCTION_DEPEND_PATTERN = '/{{depend\s*(.*?)}}(.*?){{ \\/depend\s*}}/si ' ;
26
26
27
+ /**
28
+ * Construction `if` regular expression
29
+ */
27
30
const CONSTRUCTION_IF_PATTERN = '/{{if\s*(.*?)}}(.*?)({{else}}(.*?))?{{ \\/if\s*}}/si ' ;
28
31
32
+ /**
33
+ * Construction `template` regular expression
34
+ */
29
35
const CONSTRUCTION_TEMPLATE_PATTERN = '/{{(template)(.*?)}}/si ' ;
30
36
31
- /**#@-*/
37
+ /**
38
+ * Construction `for` regular expression
39
+ */
40
+ const LOOP_PATTERN = '/{{for(?P<loopItem>.*? )(in)(?P<loopData>.*?)}}(?P<loopBody>.*?){{\/for}}/si ' ;
32
41
33
42
/**#@-*/
34
43
private $ afterFilterCallbacks = [];
@@ -130,6 +139,8 @@ public function filter($value)
130
139
}
131
140
}
132
141
142
+ $ value = $ this ->filterFor ($ value );
143
+
133
144
if (preg_match_all (self ::CONSTRUCTION_PATTERN , $ value , $ constructions , PREG_SET_ORDER )) {
134
145
foreach ($ constructions as $ construction ) {
135
146
$ callback = [$ this , $ construction [1 ] . 'Directive ' ];
@@ -149,6 +160,56 @@ public function filter($value)
149
160
return $ value ;
150
161
}
151
162
163
+ /**
164
+ * Filter the string as template.
165
+ *
166
+ * @param string $value
167
+ * @example syntax {{for item in order.items}} name: {{var item.name}} {{/for}} order items collection.
168
+ * @example syntax {{for thing in things}} {{var thing.whatever}} {{/for}} e.g.:custom collection.
169
+ * @return string
170
+ */
171
+ private function filterFor ($ value )
172
+ {
173
+ if (preg_match_all (self ::LOOP_PATTERN , $ value , $ constructions , PREG_SET_ORDER )) {
174
+ foreach ($ constructions as $ construction ) {
175
+ if (!$ this ->isValidLoop ($ construction )) {
176
+ return $ value ;
177
+ }
178
+
179
+ $ fullTextToReplace = $ construction [0 ];
180
+ $ loopData = $ this ->getVariable ($ construction ['loopData ' ], '' );
181
+
182
+ $ loopTextToReplace = $ construction ['loopBody ' ];
183
+ $ loopItemVariableName = preg_replace ('/\s+/ ' , '' , $ construction ['loopItem ' ]);
184
+
185
+ if (is_array ($ loopData ) || $ loopData instanceof \Traversable) {
186
+ $ replaceText = $ this ->getLoopReplacementText ($ loopData , $ loopItemVariableName , $ loopTextToReplace );
187
+ $ value = str_replace ($ fullTextToReplace , $ replaceText , $ value );
188
+ }
189
+ }
190
+ }
191
+
192
+ return $ value ;
193
+ }
194
+
195
+ /**
196
+ * Check if the matched construction is valid.
197
+ *
198
+ * @param array $construction
199
+ * @return bool
200
+ */
201
+ private function isValidLoop (array $ construction )
202
+ {
203
+ $ requiredFields = ['loopBody ' , 'loopItem ' , 'loopData ' ];
204
+ $ validFields = array_filter (
205
+ $ requiredFields ,
206
+ function ($ field ) use ($ construction ) {
207
+ return isset ($ construction [$ field ]) && strlen (trim ($ construction [$ field ]));
208
+ }
209
+ );
210
+ return count ($ requiredFields ) == count ($ validFields );
211
+ }
212
+
152
213
/**
153
214
* Runs callbacks that have been added to filter content after directive processing is finished.
154
215
*
@@ -370,4 +431,53 @@ protected function getStackArgs($stack)
370
431
}
371
432
return $ stack ;
372
433
}
434
+
435
+ /**
436
+ * Process loop text to replace.
437
+ *
438
+ * @param array $loopData
439
+ * @param string $loopItemVariableName
440
+ * @param string $loopTextToReplace
441
+ * @return string
442
+ */
443
+ private function getLoopReplacementText (array $ loopData , $ loopItemVariableName , $ loopTextToReplace )
444
+ {
445
+ $ loopText = [];
446
+ $ loopIndex = 0 ;
447
+ $ loopDataObject = new \Magento \Framework \DataObject ();
448
+
449
+ foreach ($ loopData as $ loopItemDataObject ) {
450
+ // Loop item can be an array or DataObject.
451
+ // If loop item is an array, convert it to DataObject
452
+ // to have unified interface if the collection
453
+ if (!$ loopItemDataObject instanceof \Magento \Framework \DataObject) {
454
+ if (!is_array ($ loopItemDataObject )) {
455
+ continue ;
456
+ }
457
+ $ loopItemDataObject = new \Magento \Framework \DataObject ($ loopItemDataObject );
458
+ }
459
+
460
+ $ loopDataObject ->setData ('index ' , $ loopIndex ++);
461
+ $ this ->templateVars ['loop ' ] = $ loopDataObject ;
462
+ $ this ->templateVars [$ loopItemVariableName ] = $ loopItemDataObject ;
463
+
464
+ if (preg_match_all (
465
+ self ::CONSTRUCTION_PATTERN ,
466
+ $ loopTextToReplace ,
467
+ $ attributes ,
468
+ PREG_SET_ORDER
469
+ )
470
+ ) {
471
+ $ subText = $ loopTextToReplace ;
472
+ foreach ($ attributes as $ attribute ) {
473
+ $ text = $ this ->getVariable ($ attribute [2 ], '' );
474
+ $ subText = str_replace ($ attribute [0 ], $ text , $ subText );
475
+ }
476
+ $ loopText [] = $ subText ;
477
+ }
478
+ unset($ this ->templateVars [$ loopItemVariableName ]);
479
+ }
480
+ $ replaceText = implode ('' , $ loopText );
481
+ return $ replaceText ;
482
+ }
373
483
}
0 commit comments