From 7717a56a21b63e773c0e21f46909ef94c0c0d8e8 Mon Sep 17 00:00:00 2001 From: Thiago Date: Wed, 26 Apr 2017 02:36:43 +0200 Subject: [PATCH 01/12] Update Template.php This update enable the use of loop into template files. Allowing the user iterate through collections --- .../Magento/Framework/Filter/Template.php | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index eda75cace3f55..dff6d98d8de23 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -24,6 +24,11 @@ class Template implements \Zend_Filter_Interface const CONSTRUCTION_IF_PATTERN = '/{{if\s*(.*?)}}(.*?)({{else}}(.*?))?{{\\/if\s*}}/si'; const CONSTRUCTION_TEMPLATE_PATTERN = '/{{(template)(.*?)}}/si'; + + /** + * Looping regular expression + */ + const LOOP_PATTERN = '/{{loop(.*?)delimiter=(.*?)}}(.*?){{\/loop}}/si'; /**#@-*/ @@ -131,6 +136,8 @@ public function filter($value) } } + $value = $this->_filterLoop($value); + if (preg_match_all(self::CONSTRUCTION_PATTERN, $value, $constructions, PREG_SET_ORDER)) { foreach ($constructions as $construction) { $callback = [$this, $construction[1] . 'Directive']; @@ -371,4 +378,59 @@ protected function getStackArgs($stack) } return $stack; } + + /** + * Filter the string as template. + * + * @param string $value + * @return string + */ + protected function _filterLoop($value) + { + if (preg_match_all(self::LOOP_PATTERN, $value, $constructions, PREG_SET_ORDER)) { + foreach ($constructions as $index => $construction) { + + $full_text_to_replace = $construction[0]; + $objectArrayData = $this->_getVariable($construction[1], ''); + $delimiter = $construction[2]; + $loop_text_to_replace = $construction[3]; + + if (is_array($objectArrayData) || $objectArrayData instanceof Varien_Data_Collection) { + + $loopText = []; + foreach ($objectArrayData as $k => $objectData) { + + if (!$objectData instanceof Varien_Object) { // is array? + + if (!is_array($objectData)) { + continue; + } + + $_item = new Varien_Object(); + $_item->setData($k, $objectData); + $objectData = $_item; + } + + $this->_templateVars['item'] = $objectData; + + if (preg_match_all(self::CONSTRUCTION_PATTERN, $loop_text_to_replace, $attributes, PREG_SET_ORDER)) { + + $subText = $loop_text_to_replace; + foreach ($attributes as $j => $attribute) { + $text = $this->_getVariable($attribute[2], ''); + $subText = str_replace($attribute[0], $text, $subText); + } + $loopText[] = $subText; + } + unset($this->_templateVars['item']); + + } + $replaceText = implode($delimiter, $loopText); + $value = str_replace($full_text_to_replace, $replaceText, $value); + } + } + } + + return $value; + } } From 3026960f84809073cd6de5f88cd448a94f20c4df Mon Sep 17 00:00:00 2001 From: Thiago Date: Wed, 26 Apr 2017 02:49:08 +0200 Subject: [PATCH 02/12] Update Template.php removing unused variables --- lib/internal/Magento/Framework/Filter/Template.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index dff6d98d8de23..c5fb853722cf1 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -388,7 +388,7 @@ protected function getStackArgs($stack) protected function _filterLoop($value) { if (preg_match_all(self::LOOP_PATTERN, $value, $constructions, PREG_SET_ORDER)) { - foreach ($constructions as $index => $construction) { + foreach ($constructions as $construction) { $full_text_to_replace = $construction[0]; $objectArrayData = $this->_getVariable($construction[1], ''); @@ -416,7 +416,7 @@ protected function _filterLoop($value) if (preg_match_all(self::CONSTRUCTION_PATTERN, $loop_text_to_replace, $attributes, PREG_SET_ORDER)) { $subText = $loop_text_to_replace; - foreach ($attributes as $j => $attribute) { + foreach ($attributes as $attribute) { $text = $this->_getVariable($attribute[2], ''); $subText = str_replace($attribute[0], $text, $subText); } From 18ed2eb6ae3709ae299ceab4c1083d077b0e321b Mon Sep 17 00:00:00 2001 From: Thiago Date: Wed, 26 Apr 2017 13:41:40 +0200 Subject: [PATCH 03/12] Update Template.php removing m1 variables type and renaming m1 methods --- .../Magento/Framework/Filter/Template.php | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index c5fb853722cf1..dbb47f46e76fd 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -24,7 +24,7 @@ class Template implements \Zend_Filter_Interface const CONSTRUCTION_IF_PATTERN = '/{{if\s*(.*?)}}(.*?)({{else}}(.*?))?{{\\/if\s*}}/si'; const CONSTRUCTION_TEMPLATE_PATTERN = '/{{(template)(.*?)}}/si'; - + /** * Looping regular expression */ @@ -136,8 +136,8 @@ public function filter($value) } } - $value = $this->_filterLoop($value); - + $value = $this->filterLoop($value); + if (preg_match_all(self::CONSTRUCTION_PATTERN, $value, $constructions, PREG_SET_ORDER)) { foreach ($constructions as $construction) { $callback = [$this, $construction[1] . 'Directive']; @@ -326,7 +326,7 @@ protected function getVariable($value, $default = '{no_value_defined}') // Getting of template value $stackVars[$i]['variable'] = & $this->templateVars[$stackVars[$i]['name']]; } elseif (isset($stackVars[$i - 1]['variable']) - && $stackVars[$i - 1]['variable'] instanceof \Magento\Framework\DataObject + && $stackVars[$i - 1]['variable'] instanceof \Magento\Framework\DataObject ) { // If object calling methods or getting properties if ($stackVars[$i]['type'] == 'property') { @@ -340,7 +340,7 @@ protected function getVariable($value, $default = '{no_value_defined}') } elseif ($stackVars[$i]['type'] == 'method') { // Calling of object method if (method_exists($stackVars[$i - 1]['variable'], $stackVars[$i]['name']) - || substr($stackVars[$i]['name'], 0, 3) == 'get' + || substr($stackVars[$i]['name'], 0, 3) == 'get' ) { $stackVars[$i]['args'] = $this->getStackArgs($stackVars[$i]['args']); $stackVars[$i]['variable'] = call_user_func_array( @@ -378,51 +378,51 @@ protected function getStackArgs($stack) } return $stack; } - + /** * Filter the string as template. * * @param string $value * @return string */ - protected function _filterLoop($value) + protected function filterLoop($value) { if (preg_match_all(self::LOOP_PATTERN, $value, $constructions, PREG_SET_ORDER)) { foreach ($constructions as $construction) { $full_text_to_replace = $construction[0]; - $objectArrayData = $this->_getVariable($construction[1], ''); + $objectArrayData = $this->getVariable($construction[1], ''); $delimiter = $construction[2]; $loop_text_to_replace = $construction[3]; - - if (is_array($objectArrayData) || $objectArrayData instanceof Varien_Data_Collection) { + + if (is_array($objectArrayData) || $objectArrayData instanceof \Traversable) { $loopText = []; foreach ($objectArrayData as $k => $objectData) { - if (!$objectData instanceof Varien_Object) { // is array? + if (!$objectData instanceof \Magento\Framework\DataObject) { // is array? if (!is_array($objectData)) { continue; } - - $_item = new Varien_Object(); + + $_item = new \Magento\Framework\DataObject; $_item->setData($k, $objectData); $objectData = $_item; } - $this->_templateVars['item'] = $objectData; + $this->templateVars['item'] = $objectData; if (preg_match_all(self::CONSTRUCTION_PATTERN, $loop_text_to_replace, $attributes, PREG_SET_ORDER)) { $subText = $loop_text_to_replace; foreach ($attributes as $attribute) { - $text = $this->_getVariable($attribute[2], ''); + $text = $this->getVariable($attribute[2], ''); $subText = str_replace($attribute[0], $text, $subText); } $loopText[] = $subText; } - unset($this->_templateVars['item']); + unset($this->templateVars['item']); } $replaceText = implode($delimiter, $loopText); From e77cedf47669e6238d565d84734d4e214d322e51 Mon Sep 17 00:00:00 2001 From: Thiago Date: Wed, 26 Apr 2017 14:00:15 +0200 Subject: [PATCH 04/12] Update removing useless delimiter --- lib/internal/Magento/Framework/Filter/Template.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index dbb47f46e76fd..7e585650e36d8 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -28,7 +28,7 @@ class Template implements \Zend_Filter_Interface /** * Looping regular expression */ - const LOOP_PATTERN = '/{{loop(.*?)delimiter=(.*?)}}(.*?){{\/loop}}/si'; + const LOOP_PATTERN = '/{{loop(.*?)}}(.*?){{\/loop}}/si'; /**#@-*/ @@ -392,8 +392,7 @@ protected function filterLoop($value) $full_text_to_replace = $construction[0]; $objectArrayData = $this->getVariable($construction[1], ''); - $delimiter = $construction[2]; - $loop_text_to_replace = $construction[3]; + $loop_text_to_replace = $construction[2]; if (is_array($objectArrayData) || $objectArrayData instanceof \Traversable) { @@ -425,7 +424,7 @@ protected function filterLoop($value) unset($this->templateVars['item']); } - $replaceText = implode($delimiter, $loopText); + $replaceText = implode('', $loopText); $value = str_replace($full_text_to_replace, $replaceText, $value); } } From 6da53be77ddcb5fbaaa505bb6ca2e4292bf05c19 Mon Sep 17 00:00:00 2001 From: Thiago Date: Mon, 3 Jul 2017 22:39:39 +0200 Subject: [PATCH 05/12] Update Template.php Enabling use of arrays or collections. --- lib/internal/Magento/Framework/Filter/Template.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index 7e585650e36d8..40bf2e724d8b4 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -397,6 +397,7 @@ protected function filterLoop($value) if (is_array($objectArrayData) || $objectArrayData instanceof \Traversable) { $loopText = []; + $indexCount = 1; foreach ($objectArrayData as $k => $objectData) { if (!$objectData instanceof \Magento\Framework\DataObject) { // is array? @@ -406,10 +407,12 @@ protected function filterLoop($value) } $_item = new \Magento\Framework\DataObject; - $_item->setData($k, $objectData); + $_item->setData($objectData); $objectData = $_item; } + $objectData->setData('indexCount', $indexCount++); + $this->templateVars['item'] = $objectData; if (preg_match_all(self::CONSTRUCTION_PATTERN, $loop_text_to_replace, $attributes, PREG_SET_ORDER)) { From a69e9cdc7d9f1e92b020c71b72f5925f108b1b54 Mon Sep 17 00:00:00 2001 From: Thiago Date: Fri, 7 Jul 2017 18:22:05 +0200 Subject: [PATCH 06/12] Update Template.php adding Twig-like syntax --- .../Magento/Framework/Filter/Template.php | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index 40bf2e724d8b4..737d4c4a4dc7f 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -28,7 +28,7 @@ class Template implements \Zend_Filter_Interface /** * Looping regular expression */ - const LOOP_PATTERN = '/{{loop(.*?)}}(.*?){{\/loop}}/si'; + const LOOP_PATTERN = '/{{for(.*? )(in)(.*?)}}(.*?){{\/for}}/si'; /**#@-*/ @@ -136,7 +136,7 @@ public function filter($value) } } - $value = $this->filterLoop($value); + $value = $this->filterFor($value); if (preg_match_all(self::CONSTRUCTION_PATTERN, $value, $constructions, PREG_SET_ORDER)) { foreach ($constructions as $construction) { @@ -383,21 +383,30 @@ protected function getStackArgs($stack) * Filter the string as template. * * @param string $value + * @example syntax {{for item in order.all_visible_items}} sku: {{var item.sku}}
name: {{var item.name}}
{{/for}} order items collection. + * @example syntax {{for thing in things}} {{var thing.whatever}} {{/for}} e.g.:custom collection. * @return string */ - protected function filterLoop($value) + protected function filterFor($value) { if (preg_match_all(self::LOOP_PATTERN, $value, $constructions, PREG_SET_ORDER)) { foreach ($constructions as $construction) { + if (sizeof($construction) < 5) { + return $value; + } + $full_text_to_replace = $construction[0]; - $objectArrayData = $this->getVariable($construction[1], ''); - $loop_text_to_replace = $construction[2]; + $objectArrayData = $this->getVariable($construction[3], ''); + $loop_text_to_replace = $construction[4]; + $vName = preg_replace('/\s+/', '', $construction[1]); if (is_array($objectArrayData) || $objectArrayData instanceof \Traversable) { $loopText = []; $indexCount = 1; + $loop = new \Magento\Framework\DataObject; + foreach ($objectArrayData as $k => $objectData) { if (!$objectData instanceof \Magento\Framework\DataObject) { // is array? @@ -411,9 +420,11 @@ protected function filterLoop($value) $objectData = $_item; } - $objectData->setData('indexCount', $indexCount++); + $loop->setData('index', $indexCount++); + $loop->setData('loop'); + $this->templateVars['loop'] = $loop; - $this->templateVars['item'] = $objectData; + $this->templateVars[$vName] = $objectData; if (preg_match_all(self::CONSTRUCTION_PATTERN, $loop_text_to_replace, $attributes, PREG_SET_ORDER)) { @@ -424,7 +435,7 @@ protected function filterLoop($value) } $loopText[] = $subText; } - unset($this->templateVars['item']); + unset($this->templateVars[$vName]); } $replaceText = implode('', $loopText); From 104e664de1ab8487f208925ae203fc7cb745604c Mon Sep 17 00:00:00 2001 From: Thiago Date: Mon, 7 Aug 2017 14:33:01 +0200 Subject: [PATCH 07/12] Update Template.php adding named groups in order to avoid access by indexes, refactoring validations and variables --- .../Magento/Framework/Filter/Template.php | 413 +++++++++--------- 1 file changed, 212 insertions(+), 201 deletions(-) diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index 737d4c4a4dc7f..f3e9992195840 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -28,35 +28,31 @@ class Template implements \Zend_Filter_Interface /** * Looping regular expression */ - const LOOP_PATTERN = '/{{for(.*? )(in)(.*?)}}(.*?){{\/for}}/si'; + const LOOP_PATTERN = '/{{for(?P.*? )(in)(?P.*?)}}(?P.*?){{\/for}}/si'; /**#@-*/ - - /** - * Callbacks that will be applied after filtering - * - * @var array - */ - private $afterFilterCallbacks = []; - /** * Assigned template variables * * @var array */ protected $templateVars = []; - /** * Template processor * * @var callable|null */ protected $templateProcessor = null; - /** * @var \Magento\Framework\Stdlib\StringUtils */ protected $string; + /** + * Callbacks that will be applied after filtering + * + * @var array + */ + private $afterFilterCallbacks = []; /** * @param \Magento\Framework\Stdlib\StringUtils $string @@ -82,28 +78,6 @@ public function setVariables(array $variables) return $this; } - /** - * Sets the processor for template directive. - * - * @param callable $callback it must return string - * @return $this - */ - public function setTemplateProcessor(callable $callback) - { - $this->templateProcessor = $callback; - return $this; - } - - /** - * Sets the processor for template directive. - * - * @return callable|null - */ - public function getTemplateProcessor() - { - return is_callable($this->templateProcessor) ? $this->templateProcessor : null; - } - /** * Filter the string as template. * @@ -158,151 +132,86 @@ public function filter($value) } /** - * Runs callbacks that have been added to filter content after directive processing is finished. + * Filter the string as template. * * @param string $value + * @example syntax {{for item in order.all_visible_items}} sku: {{var item.sku}}
name: {{var item.name}}
{{/for}} order items collection. + * @example syntax {{for thing in things}} {{var thing.whatever}} {{/for}} e.g.:custom collection. * @return string */ - protected function afterFilter($value) + protected function filterFor($value) { - foreach ($this->afterFilterCallbacks as $callback) { - $value = call_user_func($callback, $value); - } - // Since a single instance of this class can be used to filter content multiple times, reset callbacks to - // prevent callbacks running for unrelated content (e.g., email subject and email body) - $this->resetAfterFilterCallbacks(); - return $value; - } + if (preg_match_all(self::LOOP_PATTERN, $value, $constructions, PREG_SET_ORDER)) { + foreach ($constructions as $construction) { - /** - * Adds a callback to run after main filtering has happened. Callback must accept a single argument and return - * a string of the processed value. - * - * @param callable $afterFilterCallback - * @return $this - */ - public function addAfterFilterCallback(callable $afterFilterCallback) - { - // Only add callback if it doesn't already exist - if (in_array($afterFilterCallback, $this->afterFilterCallbacks)) { - return $this; - } + if (!$this->isValidLoop($construction)) { + return $value; + } - $this->afterFilterCallbacks[] = $afterFilterCallback; - return $this; - } + $fullTextToReplace = $construction[0]; + $loopData = $this->getVariable($construction['loopData'], ''); - /** - * Resets the after filter callbacks - * - * @return $this - */ - protected function resetAfterFilterCallbacks() - { - $this->afterFilterCallbacks = []; - return $this; - } + $loopTextToReplace = $construction['loopBody']; + $loopItem = preg_replace('/\s+/', '', $construction['loopItem']); - /** - * @param string[] $construction - * @return string - */ - public function varDirective($construction) - { - if (count($this->templateVars) == 0) { - // If template prepossessing - return $construction[0]; - } + if (is_array($loopData) || $loopData instanceof \Traversable) { - $replacedValue = $this->getVariable($construction[2], ''); - return $replacedValue; - } + $loopText = []; + $indexCount = 0; + $loop = new \Magento\Framework\DataObject; - /** - * Allows templates to be included inside other templates - * - * Usage: - * - * {{template config_path=""}} - * - * equals the XPATH to the system configuration value that contains the value of the template. - * This directive is useful to include things like a global header/footer. - * - * @param string[] $construction - * @return mixed - */ - public function templateDirective($construction) - { - // Processing of {template config_path=... [...]} statement - $templateParameters = $this->getParameters($construction[2]); - if (!isset($templateParameters['config_path']) or !$this->getTemplateProcessor()) { - // Not specified template or not set include processor - $replacedValue = '{Error in template processing}'; - } else { - // Including of template - $configPath = $templateParameters['config_path']; - unset($templateParameters['config_path']); - $templateParameters = array_merge_recursive($templateParameters, $this->templateVars); - $replacedValue = call_user_func($this->getTemplateProcessor(), $configPath, $templateParameters); - } - return $replacedValue; - } + foreach ($loopData as $objectData) { - /** - * @param string[] $construction - * @return string - */ - public function dependDirective($construction) - { - if (count($this->templateVars) == 0) { - // If template processing - return $construction[0]; - } + if (!$objectData instanceof \Magento\Framework\DataObject) { // is array? - if ($this->getVariable($construction[1], '') == '') { - return ''; - } else { - return $construction[2]; - } - } + if (!is_array($objectData)) { + continue; + } - /** - * @param string[] $construction - * @return string - */ - public function ifDirective($construction) - { - if (count($this->templateVars) == 0) { - return $construction[0]; - } + $_item = new \Magento\Framework\DataObject; + $_item->setData($objectData); + $objectData = $_item; + } - if ($this->getVariable($construction[1], '') == '') { - if (isset($construction[3]) && isset($construction[4])) { - return $construction[4]; + $loop->setData('index', $indexCount++); + $this->templateVars['loop'] = $loop; + $this->templateVars[$loopItem] = $objectData; + + if (preg_match_all(self::CONSTRUCTION_PATTERN, $loopTextToReplace, $attributes, + PREG_SET_ORDER)) { + + $subText = $loopTextToReplace; + foreach ($attributes as $attribute) { + $text = $this->getVariable($attribute[2], ''); + $subText = str_replace($attribute[0], $text, $subText); + } + $loopText[] = $subText; + } + unset($this->templateVars[$loopItem]); + + } + $replaceText = implode('', $loopText); + $value = str_replace($fullTextToReplace, $replaceText, $value); + } } - return ''; - } else { - return $construction[2]; } + + return $value; } /** - * Return associative array of parameters. - * - * @param string $value raw parameters - * @return array + * @param $construction + * @return bool */ - protected function getParameters($value) + private function isValidLoop($construction) { - $tokenizer = new Template\Tokenizer\Parameter(); - $tokenizer->setString($value); - $params = $tokenizer->tokenize(); - foreach ($params as $key => $value) { - if (substr($value, 0, 1) === '$') { - $params[$key] = $this->getVariable(substr($value, 1), null); - } + if ((strlen(trim($construction['loopBody'])) == 0) && + (strlen(trim($construction['loopItem'])) == 0) && + (strlen(trim($construction['loopData'])) == 0) + ) { + return true; } - return $params; + return false; } /** @@ -324,7 +233,7 @@ protected function getVariable($value, $default = '{no_value_defined}') for ($i = 0; $i < count($stackVars); $i++) { if ($i == 0 && isset($this->templateVars[$stackVars[$i]['name']])) { // Getting of template value - $stackVars[$i]['variable'] = & $this->templateVars[$stackVars[$i]['name']]; + $stackVars[$i]['variable'] = &$this->templateVars[$stackVars[$i]['name']]; } elseif (isset($stackVars[$i - 1]['variable']) && $stackVars[$i - 1]['variable'] instanceof \Magento\Framework\DataObject ) { @@ -380,70 +289,172 @@ protected function getStackArgs($stack) } /** - * Filter the string as template. + * Runs callbacks that have been added to filter content after directive processing is finished. * * @param string $value - * @example syntax {{for item in order.all_visible_items}} sku: {{var item.sku}}
name: {{var item.name}}
{{/for}} order items collection. - * @example syntax {{for thing in things}} {{var thing.whatever}} {{/for}} e.g.:custom collection. * @return string */ - protected function filterFor($value) + protected function afterFilter($value) { - if (preg_match_all(self::LOOP_PATTERN, $value, $constructions, PREG_SET_ORDER)) { - foreach ($constructions as $construction) { + foreach ($this->afterFilterCallbacks as $callback) { + $value = call_user_func($callback, $value); + } + // Since a single instance of this class can be used to filter content multiple times, reset callbacks to + // prevent callbacks running for unrelated content (e.g., email subject and email body) + $this->resetAfterFilterCallbacks(); + return $value; + } - if (sizeof($construction) < 5) { - return $value; - } + /** + * Resets the after filter callbacks + * + * @return $this + */ + protected function resetAfterFilterCallbacks() + { + $this->afterFilterCallbacks = []; + return $this; + } - $full_text_to_replace = $construction[0]; - $objectArrayData = $this->getVariable($construction[3], ''); - $loop_text_to_replace = $construction[4]; - $vName = preg_replace('/\s+/', '', $construction[1]); + /** + * Adds a callback to run after main filtering has happened. Callback must accept a single argument and return + * a string of the processed value. + * + * @param callable $afterFilterCallback + * @return $this + */ + public function addAfterFilterCallback(callable $afterFilterCallback) + { + // Only add callback if it doesn't already exist + if (in_array($afterFilterCallback, $this->afterFilterCallbacks)) { + return $this; + } - if (is_array($objectArrayData) || $objectArrayData instanceof \Traversable) { + $this->afterFilterCallbacks[] = $afterFilterCallback; + return $this; + } - $loopText = []; - $indexCount = 1; - $loop = new \Magento\Framework\DataObject; + /** + * @param string[] $construction + * @return string + */ + public function varDirective($construction) + { + if (count($this->templateVars) == 0) { + // If template prepossessing + return $construction[0]; + } - foreach ($objectArrayData as $k => $objectData) { + $replacedValue = $this->getVariable($construction[2], ''); + return $replacedValue; + } - if (!$objectData instanceof \Magento\Framework\DataObject) { // is array? + /** + * Allows templates to be included inside other templates + * + * Usage: + * + * {{template config_path=""}} + * + * equals the XPATH to the system configuration value that contains the value of the template. + * This directive is useful to include things like a global header/footer. + * + * @param string[] $construction + * @return mixed + */ + public function templateDirective($construction) + { + // Processing of {template config_path=... [...]} statement + $templateParameters = $this->getParameters($construction[2]); + if (!isset($templateParameters['config_path']) or !$this->getTemplateProcessor()) { + // Not specified template or not set include processor + $replacedValue = '{Error in template processing}'; + } else { + // Including of template + $configPath = $templateParameters['config_path']; + unset($templateParameters['config_path']); + $templateParameters = array_merge_recursive($templateParameters, $this->templateVars); + $replacedValue = call_user_func($this->getTemplateProcessor(), $configPath, $templateParameters); + } + return $replacedValue; + } - if (!is_array($objectData)) { - continue; - } + /** + * Return associative array of parameters. + * + * @param string $value raw parameters + * @return array + */ + protected function getParameters($value) + { + $tokenizer = new Template\Tokenizer\Parameter(); + $tokenizer->setString($value); + $params = $tokenizer->tokenize(); + foreach ($params as $key => $value) { + if (substr($value, 0, 1) === '$') { + $params[$key] = $this->getVariable(substr($value, 1), null); + } + } + return $params; + } - $_item = new \Magento\Framework\DataObject; - $_item->setData($objectData); - $objectData = $_item; - } + /** + * Sets the processor for template directive. + * + * @return callable|null + */ + public function getTemplateProcessor() + { + return is_callable($this->templateProcessor) ? $this->templateProcessor : null; + } - $loop->setData('index', $indexCount++); - $loop->setData('loop'); - $this->templateVars['loop'] = $loop; + /** + * Sets the processor for template directive. + * + * @param callable $callback it must return string + * @return $this + */ + public function setTemplateProcessor(callable $callback) + { + $this->templateProcessor = $callback; + return $this; + } - $this->templateVars[$vName] = $objectData; + /** + * @param string[] $construction + * @return string + */ + public function dependDirective($construction) + { + if (count($this->templateVars) == 0) { + // If template processing + return $construction[0]; + } - if (preg_match_all(self::CONSTRUCTION_PATTERN, $loop_text_to_replace, $attributes, PREG_SET_ORDER)) { + if ($this->getVariable($construction[1], '') == '') { + return ''; + } else { + return $construction[2]; + } + } - $subText = $loop_text_to_replace; - foreach ($attributes as $attribute) { - $text = $this->getVariable($attribute[2], ''); - $subText = str_replace($attribute[0], $text, $subText); - } - $loopText[] = $subText; - } - unset($this->templateVars[$vName]); + /** + * @param string[] $construction + * @return string + */ + public function ifDirective($construction) + { + if (count($this->templateVars) == 0) { + return $construction[0]; + } - } - $replaceText = implode('', $loopText); - $value = str_replace($full_text_to_replace, $replaceText, $value); - } + if ($this->getVariable($construction[1], '') == '') { + if (isset($construction[3]) && isset($construction[4])) { + return $construction[4]; } + return ''; + } else { + return $construction[2]; } - - return $value; } } From 28e9d612b42d91e0d94d26c2a22065da66a20590 Mon Sep 17 00:00:00 2001 From: Thiago Date: Mon, 7 Aug 2017 14:46:26 +0200 Subject: [PATCH 08/12] Create TemplateTest.php --- .../Magento/Framework/Filter/TemplateTest.php | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Framework/Filter/TemplateTest.php diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filter/TemplateTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filter/TemplateTest.php new file mode 100644 index 0000000000000..ae11d9ce51fd4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Filter/TemplateTest.php @@ -0,0 +1,151 @@ +templateFilter = $objectManager->getObject('Magento\Framework\Filter\Template'); + } + + /** + * @param array $results + * @param array $values + * @dataProvider getFilterForDataProvider + */ + public function testFilterFor($results, $values) + { + + $this->templateFilter->setVariables(array('order' => $this->getOrder(), 'things' => $this->getThings())); + $this->assertEquals($results, $this->invokeMethod($this->templateFilter, 'filterFor', [$values])); + + } + + /** + * @return \Magento\Framework\DataObject + */ + private function getOrder() + { + $order = new \Magento\Framework\DataObject(); + + $visibleItems = [ + [ + 'sku' => 'ABC123', + 'name' => 'Product ABC', + 'price' => '123', + 'ordered_qty' => '2' + ] + ]; + + $order->setAllVisibleItems($visibleItems); + return $order; + + } + + public function getThings() + { + return [ + ['name' => 'Richard', 'age' => 24], + ['name' => 'Jane', 'age' => 12], + ['name' => 'Spot', 'age' => 7], + ]; + + } + + /** + * @return array + */ + public function getFilterForDataProvider() + { + $template = <<