Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Template.php
Go to the documentation of this file.
1 <?php
10 namespace Magento\Framework\Filter;
11 
17 {
21  const CONSTRUCTION_PATTERN = '/{{([a-z]{0,10})(.*?)}}/si';
22 
26  const CONSTRUCTION_DEPEND_PATTERN = '/{{depend\s*(.*?)}}(.*?){{\\/depend\s*}}/si';
27 
31  const CONSTRUCTION_IF_PATTERN = '/{{if\s*(.*?)}}(.*?)({{else}}(.*?))?{{\\/if\s*}}/si';
32 
36  const CONSTRUCTION_TEMPLATE_PATTERN = '/{{(template)(.*?)}}/si';
37 
41  const LOOP_PATTERN = '/{{for(?P<loopItem>.*? )(in)(?P<loopData>.*?)}}(?P<loopBody>.*?){{\/for}}/si';
42 
44  private $afterFilterCallbacks = [];
45 
51  protected $templateVars = [];
52 
58  protected $templateProcessor = null;
59 
63  protected $string;
64 
69  public function __construct(\Magento\Framework\Stdlib\StringUtils $string, $variables = [])
70  {
71  $this->string = $string;
72  $this->setVariables($variables);
73  }
74 
81  public function setVariables(array $variables)
82  {
83  foreach ($variables as $name => $value) {
84  $this->templateVars[$name] = $value;
85  }
86  return $this;
87  }
88 
95  public function setTemplateProcessor(callable $callback)
96  {
97  $this->templateProcessor = $callback;
98  return $this;
99  }
100 
106  public function getTemplateProcessor()
107  {
108  return is_callable($this->templateProcessor) ? $this->templateProcessor : null;
109  }
110 
119  public function filter($value)
120  {
121  // "depend", "if", and "template" directives should be first
122  foreach ([
123  self::CONSTRUCTION_DEPEND_PATTERN => 'dependDirective',
124  self::CONSTRUCTION_IF_PATTERN => 'ifDirective',
125  self::CONSTRUCTION_TEMPLATE_PATTERN => 'templateDirective',
126  ] as $pattern => $directive) {
127  if (preg_match_all($pattern, $value, $constructions, PREG_SET_ORDER)) {
128  foreach ($constructions as $construction) {
129  $callback = [$this, $directive];
130  if (!is_callable($callback)) {
131  continue;
132  }
133  try {
134  $replacedValue = call_user_func($callback, $construction);
135  } catch (\Exception $e) {
136  throw $e;
137  }
138  $value = str_replace($construction[0], $replacedValue, $value);
139  }
140  }
141  }
142 
143  $value = $this->filterFor($value);
144 
145  if (preg_match_all(self::CONSTRUCTION_PATTERN, $value, $constructions, PREG_SET_ORDER)) {
146  foreach ($constructions as $construction) {
147  $callback = [$this, $construction[1] . 'Directive'];
148  if (!is_callable($callback)) {
149  continue;
150  }
151  try {
152  $replacedValue = call_user_func($callback, $construction);
153  } catch (\Exception $e) {
154  throw $e;
155  }
156  $value = str_replace($construction[0], $replacedValue, $value);
157  }
158  }
159 
160  $value = $this->afterFilter($value);
161  return $value;
162  }
163 
172  private function filterFor($value)
173  {
174  if (preg_match_all(self::LOOP_PATTERN, $value, $constructions, PREG_SET_ORDER)) {
175  foreach ($constructions as $construction) {
176  if (!$this->isValidLoop($construction)) {
177  return $value;
178  }
179 
180  $fullTextToReplace = $construction[0];
181  $loopData = $this->getVariable($construction['loopData'], '');
182 
183  $loopTextToReplace = $construction['loopBody'];
184  $loopItemVariableName = preg_replace('/\s+/', '', $construction['loopItem']);
185 
186  if (is_array($loopData) || $loopData instanceof \Traversable) {
187  $replaceText = $this->getLoopReplacementText($loopData, $loopItemVariableName, $loopTextToReplace);
188  $value = str_replace($fullTextToReplace, $replaceText, $value);
189  }
190  }
191  }
192 
193  return $value;
194  }
195 
202  private function isValidLoop(array $construction)
203  {
204  $requiredFields = ['loopBody', 'loopItem', 'loopData'];
205  $validFields = array_filter(
206  $requiredFields,
207  function ($field) use ($construction) {
208  return isset($construction[$field]) && strlen(trim($construction[$field]));
209  }
210  );
211  return count($requiredFields) == count($validFields);
212  }
213 
220  protected function afterFilter($value)
221  {
222  foreach ($this->afterFilterCallbacks as $callback) {
223  $value = call_user_func($callback, $value);
224  }
225  // Since a single instance of this class can be used to filter content multiple times, reset callbacks to
226  // prevent callbacks running for unrelated content (e.g., email subject and email body)
227  $this->resetAfterFilterCallbacks();
228  return $value;
229  }
230 
238  public function addAfterFilterCallback(callable $afterFilterCallback)
239  {
240  // Only add callback if it doesn't already exist
241  if (in_array($afterFilterCallback, $this->afterFilterCallbacks)) {
242  return $this;
243  }
244 
245  $this->afterFilterCallbacks[] = $afterFilterCallback;
246  return $this;
247  }
248 
254  protected function resetAfterFilterCallbacks()
255  {
256  $this->afterFilterCallbacks = [];
257  return $this;
258  }
259 
264  public function varDirective($construction)
265  {
266  if (count($this->templateVars) == 0) {
267  // If template prepossessing
268  return $construction[0];
269  }
270 
271  $replacedValue = $this->getVariable($construction[2], '');
272  return $replacedValue;
273  }
274 
288  public function templateDirective($construction)
289  {
290  // Processing of {template config_path=... [...]} statement
291  $templateParameters = $this->getParameters($construction[2]);
292  if (!isset($templateParameters['config_path']) or !$this->getTemplateProcessor()) {
293  // Not specified template or not set include processor
294  $replacedValue = '{Error in template processing}';
295  } else {
296  // Including of template
297  $configPath = $templateParameters['config_path'];
298  unset($templateParameters['config_path']);
299  $templateParameters = array_merge_recursive($templateParameters, $this->templateVars);
300  $replacedValue = call_user_func($this->getTemplateProcessor(), $configPath, $templateParameters);
301  }
302  return $replacedValue;
303  }
304 
309  public function dependDirective($construction)
310  {
311  if (count($this->templateVars) == 0) {
312  // If template processing
313  return $construction[0];
314  }
315 
316  if ($this->getVariable($construction[1], '') == '') {
317  return '';
318  } else {
319  return $construction[2];
320  }
321  }
322 
327  public function ifDirective($construction)
328  {
329  if (count($this->templateVars) == 0) {
330  return $construction[0];
331  }
332 
333  if ($this->getVariable($construction[1], '') == '') {
334  if (isset($construction[3]) && isset($construction[4])) {
335  return $construction[4];
336  }
337  return '';
338  } else {
339  return $construction[2];
340  }
341  }
342 
349  protected function getParameters($value)
350  {
351  $tokenizer = new Template\Tokenizer\Parameter();
352  $tokenizer->setString($value);
353  $params = $tokenizer->tokenize();
354  foreach ($params as $key => $value) {
355  if (substr($value, 0, 1) === '$') {
356  $params[$key] = $this->getVariable(substr($value, 1), null);
357  }
358  }
359  return $params;
360  }
361 
370  protected function getVariable($value, $default = '{no_value_defined}')
371  {
372  \Magento\Framework\Profiler::start('email_template_processing_variables');
373  $tokenizer = new Template\Tokenizer\Variable();
374  $tokenizer->setString($value);
375  $stackVars = $tokenizer->tokenize();
376  $result = $default;
377  $last = 0;
378  for ($i = 0; $i < count($stackVars); $i++) {
379  if ($i == 0 && isset($this->templateVars[$stackVars[$i]['name']])) {
380  // Getting of template value
381  $stackVars[$i]['variable'] = & $this->templateVars[$stackVars[$i]['name']];
382  } elseif (isset($stackVars[$i - 1]['variable'])
383  && $stackVars[$i - 1]['variable'] instanceof \Magento\Framework\DataObject
384  ) {
385  // If data object calling methods or getting properties
386  if ($stackVars[$i]['type'] == 'property') {
387  $caller = 'get' . $this->string->upperCaseWords($stackVars[$i]['name'], '_', '');
388  $stackVars[$i]['variable'] = method_exists(
389  $stackVars[$i - 1]['variable'],
390  $caller
391  ) ? $stackVars[$i - 1]['variable']->{$caller}() : $stackVars[$i - 1]['variable']->getData(
392  $stackVars[$i]['name']
393  );
394  } elseif ($stackVars[$i]['type'] == 'method') {
395  // Calling of data object method
396  if (method_exists($stackVars[$i - 1]['variable'], $stackVars[$i]['name'])
397  || substr($stackVars[$i]['name'], 0, 3) == 'get'
398  ) {
399  $stackVars[$i]['args'] = $this->getStackArgs($stackVars[$i]['args']);
400  $stackVars[$i]['variable'] = call_user_func_array(
401  [$stackVars[$i - 1]['variable'], $stackVars[$i]['name']],
402  $stackVars[$i]['args']
403  );
404  }
405  }
406  $last = $i;
407  } elseif (isset($stackVars[$i - 1]['variable']) && $stackVars[$i]['type'] == 'method') {
408  // Calling object methods
409  if (method_exists($stackVars[$i - 1]['variable'], $stackVars[$i]['name'])) {
410  $stackVars[$i]['args'] = $this->getStackArgs($stackVars[$i]['args']);
411  $stackVars[$i]['variable'] = call_user_func_array(
412  [$stackVars[$i - 1]['variable'], $stackVars[$i]['name']],
413  $stackVars[$i]['args']
414  );
415  }
416  $last = $i;
417  }
418  }
419 
420  if (isset($stackVars[$last]['variable'])) {
421  // If value for construction exists set it
422  $result = $stackVars[$last]['variable'];
423  }
424  \Magento\Framework\Profiler::stop('email_template_processing_variables');
425  return $result;
426  }
427 
434  protected function getStackArgs($stack)
435  {
436  foreach ($stack as $i => $value) {
437  if (is_array($value)) {
438  $stack[$i] = $this->getStackArgs($value);
439  } elseif (substr($value, 0, 1) === '$') {
440  $stack[$i] = $this->getVariable(substr($value, 1), null);
441  }
442  }
443  return $stack;
444  }
445 
454  private function getLoopReplacementText(array $loopData, $loopItemVariableName, $loopTextToReplace)
455  {
456  $loopText = [];
457  $loopIndex = 0;
458  $loopDataObject = new \Magento\Framework\DataObject();
459 
460  foreach ($loopData as $loopItemDataObject) {
461  // Loop item can be an array or DataObject.
462  // If loop item is an array, convert it to DataObject
463  // to have unified interface if the collection
464  if (!$loopItemDataObject instanceof \Magento\Framework\DataObject) {
465  if (!is_array($loopItemDataObject)) {
466  continue;
467  }
468  $loopItemDataObject = new \Magento\Framework\DataObject($loopItemDataObject);
469  }
470 
471  $loopDataObject->setData('index', $loopIndex++);
472  $this->templateVars['loop'] = $loopDataObject;
473  $this->templateVars[$loopItemVariableName] = $loopItemDataObject;
474 
475  if (preg_match_all(
476  self::CONSTRUCTION_PATTERN,
477  $loopTextToReplace,
478  $attributes,
479  PREG_SET_ORDER
480  )
481  ) {
482  $subText = $loopTextToReplace;
483  foreach ($attributes as $attribute) {
484  $text = $this->getVariable($attribute[2], '');
485  $subText = str_replace($attribute[0], $text, $subText);
486  }
487  $loopText[] = $subText;
488  }
489  unset($this->templateVars[$loopItemVariableName]);
490  }
491  $replaceText = implode('', $loopText);
492  return $replaceText;
493  }
494 }
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
$pattern
Definition: website.php:22
setTemplateProcessor(callable $callback)
Definition: Template.php:95
endifif( $block->getLastPageNum()>1)( 'Page') ?></strong >< ul class $text
Definition: pager.phtml:43
__construct(\Magento\Framework\Stdlib\StringUtils $string, $variables=[])
Definition: Template.php:69
setVariables(array $variables)
Definition: Template.php:81
$value
Definition: gender.phtml:16
addAfterFilterCallback(callable $afterFilterCallback)
Definition: Template.php:238
getVariable($value, $default='{no_value_defined}')
Definition: Template.php:370
$attributes
Definition: matrix.phtml:13
$params[\Magento\Store\Model\StoreManager::PARAM_RUN_CODE]
Definition: website.php:18
$i
Definition: gallery.phtml:31
if(!isset($_GET['name'])) $name
Definition: log.php:14