Automad
 All Classes Functions Variables Pages
template.php
1 <?php
2 /*
3  * ....
4  * .: '':.
5  * :::: ':..
6  * ::. ''..
7  * .:'.. ..':.:::' . :. '':.
8  * :. '' '' '. ::::.. ..:
9  * ::::. ..':.. .'''::::: .
10  * :::::::.. '..:::: :. :::: :
11  * ::'':::::::. ':::.'':.:::: :
12  * :.. ''::::::....': '':: :
13  * :::::. '::::: : .. '' .
14  * .''::::::::... ':::.'' ..'' :.''''.
15  * :..:::''::::: :::::...:'' :..:
16  * ::::::. ':::: :::::::: ..:: .
17  * ::::::::.:::: :::::::: :'':.:: .''
18  * ::: '::::::::.' ''::::: :.' '': :
19  * ::: :::::::::..' :::: ::...' .
20  * ::: .:::::::::: :::: :::: .:'
21  * '::' ''::::::: :::: : :: :
22  * ':::: :::: :'' .:
23  * :::: :::: ..''
24  * :::: ..:::: .:''
25  * '''' '''''
26  *
27  *
28  * AUTOMAD
29  *
30  * Copyright (c) 2015 by Marc Anton Dahmen
31  * http://marcdahmen.de
32  *
33  * Licensed under the MIT license.
34  * http://automad.org/license
35  */
36 
37 
38 namespace Automad\Core;
39 
40 
41 defined('AUTOMAD') or die('Direct access not permitted!');
42 
43 
64 class Template {
65 
66 
71  private $Automad;
72 
73 
78  private $extensionAssets = array();
79 
80 
85  private $outerStatementMarker = '#';
86 
87 
92  private $Toolbox;
93 
94 
99  private $template;
100 
101 
106  public function __construct($Automad) {
107 
108  $this->Automad = $Automad;
109  $this->Toolbox = new Toolbox($Automad);
110  $Page = $Automad->Context->get();
111 
112  // Redirect page, if the defined URL variable differs from AM_REQUEST.
113  if (!empty($Page->url)) {
114  if ($Page->url != AM_REQUEST) {
115  header('Location: ' . Resolve::url($Page, $Page->url));
116  die;
117  }
118  }
119 
120  $this->template = $Page->getTemplate();
121 
122  Debug::log($Page, 'New instance created for the current page');
123 
124  }
125 
126 
134  private function addMetaTags($str) {
135 
136  $meta = "\n\t" . '<meta name="Generator" content="Automad ' . AM_VERSION . '">';
137 
138  return str_replace('<head>', '<head>' . $meta, $str);
139 
140  }
141 
142 
151  private function callExtension($name, $options) {
152 
153  // Adding the extension namespace to the called class here, to make sure,
154  // that only classes from the /extensions directory and within the \Extension namespace get used.
155  $class = AM_NAMESPACE_EXTENSIONS . '\\' . $name;
156 
157  // Building the extension's file path.
158  $file = AM_BASE_DIR . strtolower(str_replace('\\', '/', $class) . '/' . $name) . '.php';
159 
160  if (file_exists($file)) {
161 
162  // Load class.
163  Debug::log($file, 'Loading class');
164  require_once $file;
165 
166  if (class_exists($class, false)) {
167 
168  // Create instance of class dynamically.
169  $object = new $class();
170  Debug::log($class, 'New instance created of');
171 
172  if (method_exists($object, $name)) {
173 
174  // Collect assets.
175  $this->collectExtensionAssets($name);
176 
177  // Call method dynamically and pass $options & Automad.
178  Debug::log($options, 'Calling method "' . $name . '" and passing the following options');
179  return $object->$name($options, $this->Automad);
180 
181  } else {
182 
183  Debug::log($name, 'Method not existing!');
184 
185  }
186 
187  } else {
188 
189  Debug::log($class, 'Class not existing!');
190 
191  }
192 
193  } else {
194 
195  Debug::log($file, 'File not found!');
196 
197  }
198 
199  }
200 
201 
208  private function collectExtensionAssets($extension) {
209 
210  $path = AM_BASE_DIR . strtolower(str_replace('\\', '/', AM_NAMESPACE_EXTENSIONS) . '/' . $extension);
211 
212  Debug::log($path, 'Getting assets for "' . $extension . '" in');
213 
214  foreach (glob($path . '/*.css') as $file) {
215 
216  // Only add the minified version, if existing.
217  if (!file_exists(str_replace('.css', '.min.css', $file))) {
218 
219  // Use $file also as key to keep elemtens unique.
220  $this->extensionAssets['css'][$file] = $file;
221 
222  }
223 
224  }
225 
226  foreach (glob($path . '/*.js') as $file) {
227 
228  // Only add the minified version, if existing.
229  if (!file_exists(str_replace('.js', '.min.js', $file))) {
230 
231  // Use $file also as key to keep elemtens unique.
232  $this->extensionAssets['js'][$file] = $file;
233 
234  }
235 
236  }
237 
238  }
239 
240 
248  private function createExtensionAssetTags($str) {
249 
250  Debug::log($this->extensionAssets, 'Assets');
251 
252  $html = '';
253 
254  if (isset($this->extensionAssets['css'])) {
255  foreach ($this->extensionAssets['css'] as $file) {
256  $html .= "\t" . '<link type="text/css" rel="stylesheet" href="' . str_replace(AM_BASE_DIR, '', $file) . '" />' . "\n";
257  Debug::log($file, 'Created tag for');
258  }
259  }
260 
261  if (isset($this->extensionAssets['js'])) {
262  foreach ($this->extensionAssets['js'] as $file) {
263  $html .= "\t" . '<script type="text/javascript" src="' . str_replace(AM_BASE_DIR, '', $file) . '"></script>' . "\n";
264  Debug::log($file, 'Created tag for');
265  }
266  }
267 
268  // Prepend all items ($html) to the closing </head> tag.
269  return str_replace('</head>', $html . '</head>', $str);
270 
271  }
272 
273 
281  private function convertLegacy($str) {
282 
283  $str = preg_replace('/@i\(\s*([\w\/\.\-]+\.php)\s*\)/s', AM_DEL_STATEMENT_OPEN . '$1' . AM_DEL_STATEMENT_CLOSE, $str);
284  $str = preg_replace('/@(s|p)\(\s*(' . AM_CHARCLASS_VAR_ALL . '+)\s*\)/s', AM_DEL_VAR_OPEN . '$2' . AM_DEL_VAR_CLOSE, $str);
285  $str = preg_replace('/@(t|x)\(\s*(' . AM_CHARCLASS_VAR_ALL . '+)\s*(\{.*?\})?\s*\)/s', AM_DEL_STATEMENT_OPEN . '$2$3' . AM_DEL_STATEMENT_CLOSE, $str);
286 
287  return $str;
288 
289  }
290 
291 
305  private function loadTemplate($file) {
306 
307  // Provide an interface to the Automad object for the templates to be used with plain PHP.
308  $Automad = $this->Automad;
309 
310  ob_start();
311  include $file;
312  $output = ob_get_contents();
313  ob_end_clean();
314 
315  // Backwards compatibility.
316  $output = $this->convertLegacy($output);
317 
318  // Strip comments.
319  $output = preg_replace('/(' . preg_quote(AM_DEL_COMMENT_OPEN) . '.*?' . preg_quote(AM_DEL_COMMENT_CLOSE) . ')/s', '', $output);
320 
321  return $output;
322 
323  }
324 
325 
333  private function preProcessWrappingStatements($str) {
334 
335  $depth = 0;
336  $regex = '/(' .
337  '(?P<begin>' . preg_quote(AM_DEL_STATEMENT_OPEN) . '\s*(?:if|foreach|with).*?' . preg_quote(AM_DEL_STATEMENT_CLOSE) . ')|' .
338  '(?P<else>' . preg_quote(AM_DEL_STATEMENT_OPEN) . '\s*else\s*' . preg_quote(AM_DEL_STATEMENT_CLOSE) . ')|' .
339  '(?P<end>' . preg_quote(AM_DEL_STATEMENT_OPEN) . '\s*end\s*' . preg_quote(AM_DEL_STATEMENT_CLOSE) . ')' .
340  ')/s';
341 
342  return preg_replace_callback($regex, function($match) use (&$depth) {
343 
344  // Convert $match to the actually needed string.
345  $return = array_unique($match);
346  $return = array_filter($return);
347  $return = implode($return);
348 
349  // Decrease depth in case the match is else or end.
350  if (!empty($match['end']) || !empty($match['else'])) {
351  $depth--;
352  }
353 
354  // Append a marker to the opening delimiter in case depth === 0.
355  if ($depth === 0) {
356  $return = str_replace(AM_DEL_STATEMENT_OPEN, AM_DEL_STATEMENT_OPEN . $this->outerStatementMarker, $return);
357  }
358 
359  // Increase depth after (!) return was possible modified (in case depth === 0) in case the match is begin or else.
360  if (!empty($match['begin']) || !empty($match['else'])) {
361  $depth++;
362  }
363 
364  return $return;
365 
366  }, $str);
367 
368  }
369 
370 
391  private function processContent($str, $isJsonString = false) {
392 
393  $subpatternVar = preg_quote(AM_DEL_VAR_OPEN) . '\s*(' . AM_CHARCLASS_VAR_ALL . '+)\s*' . preg_quote(AM_DEL_VAR_CLOSE);
394  $regexContent = '/(?:' .
395  // Simple variable {[var]}.
396  $subpatternVar . '|' .
397  // A variable as method parameter while being not wrapped in quotes ": {[var]} ,|}"
398  '(?<=\:)\s*' . $subpatternVar . '\s*(?=,|\})' .
399  ')/s';
400 
401  return preg_replace_callback($regexContent, function($matches) use ($isJsonString) {
402 
403  /*
404 
405  Possible items in $matches:
406 
407  0: Full match
408  1: Normal variable in any other context
409  2: Variable is a method paramter without beeing wrapped in double quotes.
410  The regex will match {[ var ]} only if there is a ":" before and a "," or a "}" after the variable (whitespace is allowed),
411  like: {@ img { file: {[ file ]} } @}
412 
413  */
414 
415  // Use the last item in the array to get the requested value. If $matches[2] only exists, if $matches[1] is empty. Either [1] or [2] will return the matched key.
416  // The distinction between $matches[1] and $matches[2] is only made to check, if $value must be wrapped in quotes (see below).
417  $value = $this->Automad->getValue(end($matches));
418 
419  // In case $value will be used as option, some chars have to be escaped to work within a JSON formatted string.
420  if ($isJsonString) {
421 
422  $value = Parse::jsonEscape($value);
423 
424  // In case the variable is an "stand-alone" value in a JSON formatted string ($matches[2] will be defined then - regex ": {[ var ]} ,|}" ),
425  // it has to be wrapped in double quotes.
426  if (!empty($matches[2])) {
427  $value = '"' . $value . '"';
428  }
429 
430  }
431 
432  return $value;
433 
434  }, $str);
435 
436  }
437 
438 
453  private function processMarkup($str, $directory) {
454 
455  // Identify the outer statements.
456  $str = $this->preProcessWrappingStatements($str);
457 
458  $var = preg_quote(AM_DEL_VAR_OPEN) . '\s*' . AM_CHARCLASS_VAR_ALL . '+\s*' . preg_quote(AM_DEL_VAR_CLOSE);
459  $statementOpen = preg_quote(AM_DEL_STATEMENT_OPEN);
460  $statementClose = preg_quote(AM_DEL_STATEMENT_CLOSE);
461 
462  // The subpatterns don't include the wrapping delimiter: "{@ subpattern @}".
463  $statementSubpatterns['include'] = '(?P<file>[\w\/\-\.]+\.php)';
464 
465  $statementSubpatterns['method'] = '(?P<method>[\w\-]+)\s*(?P<options>\{.*?\})?';
466 
467  $statementSubpatterns['with'] = $this->outerStatementMarker . '\s*' . // Note the additional preparsed marker!
468  'with\s+(?P<with>' .
469  '"([^"]*)"|' . "'([^']*)'|(" . $var . ')' .
470  ')' .
471  '\s*' . $statementClose .
472  '(?P<withSnippet>.*?)' .
473  $statementOpen . $this->outerStatementMarker . '\s*end'; // Note the additional preparsed marker!
474 
475  $statementSubpatterns['loop'] = $this->outerStatementMarker . '\s*' . // Note the additional preparsed marker!
476  'foreach\s+in\s+(?P<foreach>' .
477  'pagelist|' .
478  'filters|' .
479  'tags|' .
480  'filelist|' .
481  '"(?P<foreachInDoubleQuotes>[^"]*)"|' .
482  "'(?P<foreachInSingleQuotes>[^']*)'" .
483  '|(?P<foreachInVar>' . $var . ')' .
484  ')' .
485  '\s*' . $statementClose .
486  '(?P<foreachSnippet>.*?)' .
487  $statementOpen . $this->outerStatementMarker . '\s*end'; // Note the additional preparsed marker!
488 
489  $statementSubpatterns['condition'] = $this->outerStatementMarker . '\s*' . // Note the additional preparsed marker!
490  'if\s+(?P<condition>' .
491 
492  // Boolean
493  '(?P<ifNot>!)?(?<ifVar>' . $var . ')|' .
494 
495  // Comparison
496  // Left
497  '(?:"(?P<ifLeftDoubleQuotes>[^"]*)"|' . "'(?P<ifLeftSingleQuotes>[^']*)'" . '|(?P<ifLeftVar>' . $var . ')|(?P<ifLeftNumber>[\d\.]+))' .
498  // !=
499  '\s*(?P<ifOperator>!?=|>=?|<=?)\s*' .
500  // Right
501  '(?:"(?P<ifRightDoubleQuotes>[^"]*)"|' . "'(?P<ifRightSingleQuotes>[^']*)'" . '|(?P<ifRightVar>' . $var . ')|(?P<ifRightNumber>[\d\.]+))' .
502 
503  ')' .
504  '\s*' . $statementClose .
505  '(?P<ifSnippet>.*?)' .
506  '(?:' . $statementOpen . $this->outerStatementMarker . '\s*else\s*' . $statementClose . '(?P<elseSnippet>.*?)' . ')?' . // Note the additional preparsed marker!
507  $statementOpen . $this->outerStatementMarker . '\s*end'; // Note the additional preparsed marker!
508 
509  // Variable or statement.
510  $regexMarkup = '/((?P<var>' . $var . ')|' . $statementOpen . '\s*(?:' . implode('|', $statementSubpatterns) . ')\s*' . $statementClose . ')/s';
511 
512  return preg_replace_callback($regexMarkup, function($matches) use ($directory) {
513 
514  // Variable - if the variable syntax gets matched, simply process that string as content to get the value.
515  if (!empty($matches['var'])) {
516  return $this->processContent($matches['var']);
517  }
518 
519  // Include
520  if (!empty($matches['file'])) {
521 
522  Debug::log($matches['file'], 'Matched include');
523  $file = $directory . '/' . $matches['file'];
524 
525  if (file_exists($file)) {
526  Debug::log($file, 'Including');
527  return $this->processMarkup($this->loadTemplate($file), dirname($file));
528  } else {
529  Debug::log($file, 'File not found');
530  }
531 
532  }
533 
534  // Method (Toolbox or extension)
535  if (!empty($matches['method'])) {
536 
537  $method = $matches['method'];
538  Debug::log($method, 'Matched method');
539 
540  // Check if options exist.
541  if (isset($matches['options'])) {
542  // Parse the options JSON and also find and replace included variables within the JSON string.
543  $options = Parse::jsonOptions($this->processContent($matches['options'], true));
544  } else {
545  $options = array();
546  }
547 
548  // Call method.
549  if (method_exists($this->Toolbox, $method)) {
550  // Try calling a matching toolbox method.
551  Debug::log($options, 'Calling method ' . $method . ' and passing the following options');
552  $html = $this->Toolbox->$method($options);
553  } else {
554  // Try extension, if no toolbox method was found.
555  Debug::log($method . ' is not a core method. Will look for a matching extension ...');
556  $html = $this->callExtension($method, $options);
557  }
558 
559  // Process all variables created by methods or extensions in a second pass.
560  return $this->processContent($html);
561 
562  }
563 
564  // With
565  if (!empty($matches['with'])) {
566 
567  $url = $this->processContent(trim($matches['with'], '\'"'));
568  Debug::log($url, 'With page');
569  $Context = $this->Automad->Context;
570  // Save original context.
571  $contextBeforeWith = $Context->get();
572  // Set context to $url.
573  $Context->set($this->Automad->getPageByUrl($url));
574  // Parse snippet.
575  $html = $this->processMarkup($matches['withSnippet'], $directory);
576  // Restore original context.
577  $Context->set($contextBeforeWith);
578  return $html;
579 
580  }
581 
582  // Foreach loop
583  if (!empty($matches['foreach'])) {
584 
585  $Context = $this->Automad->Context;
586  $snippet = $matches['foreachSnippet'];
587  $html = '';
588  $i = 1;
589 
590  // Save the index before any loop - the index will be overwritten when iterating over filter, tags and files and must be restored after the loop.
591  $iBeforeLoop = $this->Automad->getSystemVar(AM_KEY_INDEX);
592 
593  if ($matches['foreach'] == 'pagelist') {
594 
595  // Pagelist
596 
597  // Get pages.
598  $pages = $this->Automad->getPagelist()->getPages();
599  // Save context page.
600  $contextBeforeLoop = $Context->get();
601 
602  Debug::log($pages, 'Foreach in pagelist loop');
603 
604  foreach ($pages as $Page) {
605  // Cache the current pagelist configuration to be restored after processing the snippet.
606  $pagelistConfigCache = $this->Automad->getPagelist()->config();
607  // Set context to the current page in the loop.
608  $Context->set($Page);
609  // Set index for current page. The index can be used as {[ :i ]}.
610  $this->Automad->setSystemVar(AM_KEY_INDEX, $i++);
611  // Parse snippet.
612  Debug::log($Page, 'Processing snippet in loop for page: "' . $Page->url . '"');
613  $html .= $this->processMarkup($snippet, $directory);
614  // Restore pagelist configuration.
615  $this->Automad->getPagelist()->config($pagelistConfigCache);
616  }
617 
618  // Restore context.
619  $Context->set($contextBeforeLoop);
620 
621  } else if ($matches['foreach'] == 'filters') {
622 
623  // Filters (tags of the pages in the pagelist)
624  // Each filter can be used as {[ :filter ]} within a snippet.
625 
626  foreach ($this->Automad->getPagelist()->getTags() as $filter) {
627  Debug::log($filter, 'Processing snippet in loop for filter');
628  // Store current filter in the system variable buffer.
629  $this->Automad->setSystemVar(AM_KEY_FILTER, $filter);
630  // Set index. The index can be used as {[ :i ]}.
631  $this->Automad->setSystemVar(AM_KEY_INDEX, $i++);
632  $html .= $this->processMarkup($snippet, $directory);
633  }
634 
635  $this->Automad->setSystemVar(AM_KEY_FILTER, NULL);
636 
637  } else if ($matches['foreach'] == 'tags') {
638 
639  // Tags (of the current page)
640  // Each tag can be used as {[ :tag ]} within a snippet.
641 
642  foreach ($Context->get()->tags as $tag) {
643  Debug::log($tag, 'Processing snippet in loop for tag');
644  // Store current tag in the system variable buffer.
645  $this->Automad->setSystemVar(AM_KEY_TAG, $tag);
646  // Set index. The index can be used as {[ :i ]}.
647  $this->Automad->setSystemVar(AM_KEY_INDEX, $i++);
648  $html .= $this->processMarkup($snippet, $directory);
649  }
650 
651  $this->Automad->setSystemVar(AM_KEY_TAG, NULL);
652 
653  } else {
654 
655  // Files
656  // The file path and the basename can be used like {[ :file ]} and {[ :basename ]} within a snippet.
657 
658  if ($matches['foreach'] == 'filelist') {
659  // Use files from filelist.
660  $files = $this->Automad->getFilelist()->getFiles();
661  } else {
662  // Merge and parse given glob pattern within any kind of quotes or from a variable value.
663  // Only one of the following matches is not an empty string, but all three are always defined. Therefore they can simply get parsed as one concatenated string.
664  $files = Parse::fileDeclaration($this->processContent($matches['foreachInDoubleQuotes'] . $matches['foreachInSingleQuotes'] . $matches['foreachInVar']), $Context->get(), true);
665  }
666 
667  foreach ($files as $file) {
668  Debug::log($file, 'Processing snippet in loop for file');
669  // Store current filename and its basename in the system variable buffer.
670  $this->Automad->setSystemVar(AM_KEY_FILE, $file);
671  $this->Automad->setSystemVar(AM_KEY_BASENAME, basename($file));
672  // Set index. The index can be used as {[ :i ]}.
673  $this->Automad->setSystemVar(AM_KEY_INDEX, $i++);
674  $html .= $this->processMarkup($snippet, $directory);
675  }
676 
677  $this->Automad->setSystemVar(AM_KEY_FILE, NULL);
678  $this->Automad->setSystemVar(AM_KEY_BASENAME, NULL);
679 
680  }
681 
682  // Restore index.
683  $this->Automad->setSystemVar(AM_KEY_INDEX, $iBeforeLoop);
684 
685  return $html;
686 
687  }
688 
689  // Condition
690  if (!empty($matches['condition'])) {
691 
692  $ifSnippet = $matches['ifSnippet'];
693  $elseSnippet = '';
694 
695  if (!empty($matches['elseSnippet'])) {
696  $elseSnippet = $matches['elseSnippet'];
697  }
698 
699  if (!empty($matches['ifVar'])) {
700 
701  // Boolean condition.
702 
703  // Get the value of the given variable.
704  $ifVar = $this->processContent($matches['ifVar']);
705 
706  // If EMPTY NOT == NOT EMPTY Value.
707  if (empty($matches['ifNot']) == !empty($ifVar)) {
708  Debug::log('TRUE', 'Evaluating boolean condition: "' . $matches['ifNot'] . $matches['ifVar'] . '"');
709  return $this->processMarkup($ifSnippet, $directory);
710  } else {
711  Debug::log('FALSE', 'Evaluating boolean condition: "' . $matches['ifNot'] . $matches['ifVar'] . '"');
712  return $this->processMarkup($elseSnippet, $directory);
713  }
714 
715  } else {
716 
717  // Comparison.
718 
719  // Parse both sides of the condition. Again, all possible matches for each side can get merged in to one string, since there will be only one item for left/right not empty.
720  $left = $this->processContent($matches['ifLeftDoubleQuotes'] . $matches['ifLeftSingleQuotes'] . $matches['ifLeftVar'] . $matches['ifLeftNumber']);
721  $right = $this->processContent($matches['ifRightDoubleQuotes'] . $matches['ifRightSingleQuotes'] . $matches['ifRightVar'] . $matches['ifRightNumber']);
722 
723  // Build the expression.
724  switch ($matches['ifOperator']) {
725 
726  case '=':
727  $expression = ($left == $right);
728  break;
729 
730  case '!=':
731  $expression = ($left != $right);
732  break;
733 
734  case '>':
735  $expression = ($left > $right);
736  break;
737 
738  case '>=':
739  $expression = ($left >= $right);
740  break;
741 
742  case '<':
743  $expression = ($left < $right);
744  break;
745 
746  case '<=':
747  $expression = ($left <= $right);
748  break;
749 
750  }
751 
752  // Evaluate the expression.
753  if ($expression) {
754  Debug::log('TRUE', 'Evaluating condition: "' . $left . $matches['ifOperator'] . $right . '"');
755  return $this->processMarkup($ifSnippet, $directory);
756  } else {
757  Debug::log('FALSE', 'Evaluating condition: "' . $left . $matches['ifOperator'] . $right . '"');
758  return $this->processMarkup($elseSnippet, $directory);
759  }
760 
761  }
762 
763  }
764 
765  }, $str);
766 
767  }
768 
769 
777  private function resolveUrls($str) {
778 
779  $Page = $this->Automad->Context->get();
780 
781  // action, href and src
782  $str = preg_replace_callback('/(action|href|src)="(.+?)"/', function($match) use ($Page) {
783  return $match[1] . '="' . Resolve::url($Page, $match[2]) . '"';
784  }, $str);
785 
786  // Inline styles (like background-image)
787  $str = preg_replace_callback('/url\(\'(.+?)\'\)/', function($match) use ($Page) {
788  return 'url(\'' . Resolve::url($Page, $match[1]) . '\')';
789  }, $str);
790 
791  return $str;
792 
793  }
794 
795 
803  private function obfuscateEmails($str) {
804 
805  return preg_replace_callback('/(?<!mailto:)\b([\w\d\._\+\-]+@([a-zA-Z_\-\.]+)\.[a-zA-Z]{2,6})/', function($matches) {
806 
807  Debug::log($matches[1], 'Obfuscating email');
808 
809  $html = '<a href="#" onclick="this.href=\'mailto:\'+ this.innerHTML.split(\'\').reverse().join(\'\')" style="unicode-bidi:bidi-override;direction:rtl">';
810  $html .= strrev($matches[1]);
811  $html .= "</a>&#x200E;";
812 
813  return $html;
814 
815  }, $str);
816 
817  }
818 
819 
826  public function render() {
827 
828  Debug::log($this->template, 'Render template');
829 
830  $output = $this->loadTemplate($this->template);
831  $output = $this->processMarkup($output, dirname($this->template));
832  $output = $this->createExtensionAssetTags($output);
833  $output = $this->addMetaTags($output);
834  $output = $this->resolveUrls($output);
835  $output = $this->obfuscateEmails($output);
836 
837  return $output;
838 
839  }
840 
841 
842 }
843 
844 
845 ?>
static log($element, $description= '')
Definition: debug.php:113
callExtension($name, $options)
Definition: template.php:151
processContent($str, $isJsonString=false)
Definition: template.php:391
createExtensionAssetTags($str)
Definition: template.php:248
static jsonOptions($str)
Definition: parse.php:198
static jsonEscape($str)
Definition: parse.php:181
static url($Page, $url)
Definition: resolve.php:101
collectExtensionAssets($extension)
Definition: template.php:208
processMarkup($str, $directory)
Definition: template.php:453
preProcessWrappingStatements($str)
Definition: template.php:333
__construct($Automad)
Definition: template.php:106