]> git.mxchange.org Git - friendica.git/blob - vendor/smarty/smarty/libs/sysplugins/smarty_internal_templatecompilerbase.php
Add Smarty to Composer
[friendica.git] / vendor / smarty / smarty / libs / sysplugins / smarty_internal_templatecompilerbase.php
1 <?php
2
3 /**
4  * Smarty Internal Plugin Smarty Template Compiler Base
5  * This file contains the basic classes and methods for compiling Smarty templates with lexer/parser
6  *
7  * @package    Smarty
8  * @subpackage Compiler
9  * @author     Uwe Tews
10  */
11
12 /**
13  * Main abstract compiler class
14  *
15  * @package    Smarty
16  * @subpackage Compiler
17  *
18  * @property Smarty_Internal_SmartyTemplateCompiler $prefixCompiledCode  = ''
19  * @property Smarty_Internal_SmartyTemplateCompiler $postfixCompiledCode = ''
20  * @method registerPostCompileCallback($callback, $parameter = array(), $key = null, $replace = false)
21  * @method unregisterPostCompileCallback($key)
22  */
23 abstract class Smarty_Internal_TemplateCompilerBase
24 {
25
26     /**
27      * Smarty object
28      *
29      * @var Smarty
30      */
31     public $smarty = null;
32
33     /**
34      * Parser object
35      *
36      * @var Smarty_Internal_Templateparser
37      */
38     public $parser = null;
39
40     /**
41      * hash for nocache sections
42      *
43      * @var mixed
44      */
45     public $nocache_hash = null;
46
47     /**
48      * suppress generation of nocache code
49      *
50      * @var bool
51      */
52     public $suppressNocacheProcessing = false;
53
54     /**
55      * compile tag objects cache
56      *
57      * @var array
58      */
59     static $_tag_objects = array();
60
61     /**
62      * tag stack
63      *
64      * @var array
65      */
66     public $_tag_stack = array();
67
68     /**
69      * current template
70      *
71      * @var Smarty_Internal_Template
72      */
73     public $template = null;
74
75     /**
76      * merged included sub template data
77      *
78      * @var array
79      */
80     public $mergedSubTemplatesData = array();
81
82     /**
83      * merged sub template code
84      *
85      * @var array
86      */
87     public $mergedSubTemplatesCode = array();
88
89     /**
90      * collected template properties during compilation
91      *
92      * @var array
93      */
94     public $templateProperties = array();
95
96     /**
97      * source line offset for error messages
98      *
99      * @var int
100      */
101     public $trace_line_offset = 0;
102
103     /**
104      * trace uid
105      *
106      * @var string
107      */
108     public $trace_uid = '';
109
110     /**
111      * trace file path
112      *
113      * @var string
114      */
115     public $trace_filepath = '';
116
117     /**
118      * stack for tracing file and line of nested {block} tags
119      *
120      * @var array
121      */
122     public $trace_stack = array();
123
124     /**
125      * plugins loaded by default plugin handler
126      *
127      * @var array
128      */
129     public $default_handler_plugins = array();
130
131     /**
132      * saved preprocessed modifier list
133      *
134      * @var mixed
135      */
136     public $default_modifier_list = null;
137
138     /**
139      * force compilation of complete template as nocache
140      *
141      * @var boolean
142      */
143     public $forceNocache = false;
144
145     /**
146      * flag if compiled template file shall we written
147      *
148      * @var bool
149      */
150     public $write_compiled_code = true;
151
152     /**
153      * Template functions
154      *
155      * @var array
156      */
157     public $tpl_function = array();
158
159     /**
160      * called sub functions from template function
161      *
162      * @var array
163      */
164     public $called_functions = array();
165
166     /**
167      * compiled template or block function code
168      *
169      * @var string
170      */
171     public $blockOrFunctionCode = '';
172
173     /**
174      * php_handling setting either from Smarty or security
175      *
176      * @var int
177      */
178     public $php_handling = 0;
179
180     /**
181      * flags for used modifier plugins
182      *
183      * @var array
184      */
185     public $modifier_plugins = array();
186
187     /**
188      * type of already compiled modifier
189      *
190      * @var array
191      */
192     public $known_modifier_type = array();
193
194     /**
195      * parent compiler object for merged subtemplates and template functions
196      *
197      * @var Smarty_Internal_TemplateCompilerBase
198      */
199     public $parent_compiler = null;
200
201     /**
202      * Flag true when compiling nocache section
203      *
204      * @var bool
205      */
206     public $nocache = false;
207
208     /**
209      * Flag true when tag is compiled as nocache
210      *
211      * @var bool
212      */
213     public $tag_nocache = false;
214
215     /**
216      * Compiled tag prefix code
217      *
218      * @var array
219      */
220     public $prefix_code = array();
221
222     /**
223      * Prefix code  stack
224      *
225      * @var array
226      */
227     public $prefixCodeStack = array();
228
229     /**
230      * Tag has compiled code
231      *
232      * @var bool
233      */
234     public $has_code = false;
235
236     /**
237      * A variable string was compiled
238      *
239      * @var bool
240      */
241     public $has_variable_string = false;
242
243     /**
244      * Tag creates output
245      *
246      * @var bool
247      */
248     public $has_output = false;
249
250     /**
251      * Stack for {setfilter} {/setfilter}
252      *
253      * @var array
254      */
255     public $variable_filter_stack = array();
256
257     /**
258      * variable filters for {setfilter} {/setfilter}
259      *
260      * @var array
261      */
262     public $variable_filters = array();
263
264     /**
265      * Nesting count of looping tags like {foreach}, {for}, {section}, {while}
266      *
267      * @var int
268      */
269     public $loopNesting = 0;
270
271     /**
272      * Strip preg pattern
273      *
274      * @var string
275      */
276     public $stripRegEx = '![\t ]*[\r\n]+[\t ]*!';
277
278     /**
279      * plugin search order
280      *
281      * @var array
282      */
283     public $plugin_search_order = array('function', 'block', 'compiler', 'class');
284
285     /**
286      * General storage area for tag compiler plugins
287      *
288      * @var array
289      */
290     public $_cache = array();
291
292     /**
293      * counter for prefix variable number
294      *
295      * @var int
296      */
297     public static $prefixVariableNumber = 0;
298
299     /**
300      * method to compile a Smarty template
301      *
302      * @param mixed $_content template source
303      * @param bool  $isTemplateSource
304      *
305      * @return bool true if compiling succeeded, false if it failed
306      */
307     abstract protected function doCompile($_content, $isTemplateSource = false);
308
309     /**
310      * Initialize compiler
311      *
312      * @param Smarty $smarty global instance
313      */
314     public function __construct(Smarty $smarty)
315     {
316         $this->smarty = $smarty;
317         $this->nocache_hash = str_replace(array('.', ','), '_', uniqid(rand(), true));
318     }
319
320     /**
321      * Method to compile a Smarty template
322      *
323      * @param  Smarty_Internal_Template                 $template template object to compile
324      * @param  bool                                     $nocache  true is shall be compiled in nocache mode
325      * @param null|Smarty_Internal_TemplateCompilerBase $parent_compiler
326      *
327      * @return bool true if compiling succeeded, false if it failed
328      * @throws \Exception
329      */
330     public function compileTemplate(Smarty_Internal_Template $template, $nocache = null,
331                                     Smarty_Internal_TemplateCompilerBase $parent_compiler = null)
332     {
333         // get code frame of compiled template
334         $_compiled_code = $template->smarty->ext->_codeFrame->create($template,
335                                                                      $this->compileTemplateSource($template, $nocache,
336                                                                                                   $parent_compiler),
337                                                                      $this->postFilter($this->blockOrFunctionCode) .
338                                                                      join('', $this->mergedSubTemplatesCode), false,
339                                                                      $this);
340         return $_compiled_code;
341     }
342
343     /**
344      * Compile template source and run optional post filter
345      *
346      * @param \Smarty_Internal_Template             $template
347      * @param null|bool                             $nocache flag if template must be compiled in nocache mode
348      * @param \Smarty_Internal_TemplateCompilerBase $parent_compiler
349      *
350      * @return string
351      * @throws \Exception
352      */
353     public function compileTemplateSource(Smarty_Internal_Template $template, $nocache = null,
354                                           Smarty_Internal_TemplateCompilerBase $parent_compiler = null)
355     {
356         try {
357             // save template object in compiler class
358             $this->template = $template;
359             if (property_exists($this->template->smarty, 'plugin_search_order')) {
360                 $this->plugin_search_order = $this->template->smarty->plugin_search_order;
361             }
362             if ($this->smarty->debugging) {
363                 if (!isset($this->smarty->_debug)) {
364                     $this->smarty->_debug = new Smarty_Internal_Debug();
365                 }
366                 $this->smarty->_debug->start_compile($this->template);
367             }
368             if (isset($this->template->smarty->security_policy)) {
369                 $this->php_handling = $this->template->smarty->security_policy->php_handling;
370             } else {
371                 $this->php_handling = $this->template->smarty->php_handling;
372             }
373             $this->parent_compiler = $parent_compiler ? $parent_compiler : $this;
374             $nocache = isset($nocache) ? $nocache : false;
375             if (empty($template->compiled->nocache_hash)) {
376                 $template->compiled->nocache_hash = $this->nocache_hash;
377             } else {
378                 $this->nocache_hash = $template->compiled->nocache_hash;
379             }
380             // flag for nocache sections
381             $this->nocache = $nocache;
382             $this->tag_nocache = false;
383             // reset has nocache code flag
384             $this->template->compiled->has_nocache_code = false;
385             $this->has_variable_string = false;
386             $this->prefix_code = array();
387             // add file dependency
388             if ($this->smarty->merge_compiled_includes || $this->template->source->handler->checkTimestamps()) {
389                 $this->parent_compiler->template->compiled->file_dependency[ $this->template->source->uid ] =
390                     array($this->template->source->filepath, $this->template->source->getTimeStamp(),
391                           $this->template->source->type,);
392             }
393             $this->smarty->_current_file = $this->template->source->filepath;
394             // get template source
395             if (!empty($this->template->source->components)) {
396                 // we have array of inheritance templates by extends: resource
397                 // generate corresponding source code sequence
398                 $_content =
399                     Smarty_Internal_Compile_Extends::extendsSourceArrayCode($this->template->source->components);
400             } else {
401                 // get template source
402                 $_content = $this->template->source->getContent();
403             }
404             $_compiled_code = $this->postFilter($this->doCompile($this->preFilter($_content), true));
405         }
406         catch (Exception $e) {
407             if ($this->smarty->debugging) {
408                 $this->smarty->_debug->end_compile($this->template);
409             }
410             $this->_tag_stack = array();
411             // free memory
412             $this->parent_compiler = null;
413             $this->template = null;
414             $this->parser = null;
415             throw $e;
416         }
417         if ($this->smarty->debugging) {
418             $this->smarty->_debug->end_compile($this->template);
419         }
420         $this->parent_compiler = null;
421         $this->parser = null;
422         return $_compiled_code;
423     }
424
425     /**
426      * Optionally process compiled code by post filter
427      *
428      * @param string $code compiled code
429      *
430      * @return string
431      * @throws \SmartyException
432      */
433     public function postFilter($code)
434     {
435         // run post filter if on code
436         if (!empty($code) &&
437             (isset($this->smarty->autoload_filters[ 'post' ]) || isset($this->smarty->registered_filters[ 'post' ]))
438         ) {
439             return $this->smarty->ext->_filterHandler->runFilter('post', $code, $this->template);
440         } else {
441             return $code;
442         }
443     }
444
445     /**
446      * Run optional prefilter
447      *
448      * @param string $_content template source
449      *
450      * @return string
451      * @throws \SmartyException
452      */
453     public function preFilter($_content)
454     {
455         // run pre filter if required
456         if ($_content != '' &&
457             ((isset($this->smarty->autoload_filters[ 'pre' ]) || isset($this->smarty->registered_filters[ 'pre' ])))
458         ) {
459             return $this->smarty->ext->_filterHandler->runFilter('pre', $_content, $this->template);
460         } else {
461             return $_content;
462         }
463     }
464
465     /**
466      * Compile Tag
467      * This is a call back from the lexer/parser
468      *
469      * Save current prefix code
470      * Compile tag
471      * Merge tag prefix code with saved one
472      * (required nested tags in attributes)
473      *
474      * @param  string $tag       tag name
475      * @param  array  $args      array with tag attributes
476      * @param  array  $parameter array with compilation parameter
477      *
478      * @throws SmartyCompilerException
479      * @throws SmartyException
480      * @return string compiled code
481      */
482     public function compileTag($tag, $args, $parameter = array())
483     {
484         $this->prefixCodeStack[] = $this->prefix_code;
485         $this->prefix_code = array();
486         $result = $this->compileTag2($tag, $args, $parameter);
487         $this->prefix_code = array_merge($this->prefix_code, array_pop($this->prefixCodeStack));
488         return $result;
489     }
490
491     /**
492      * Compile Tag
493      *
494      * @param  string $tag       tag name
495      * @param  array  $args      array with tag attributes
496      * @param  array  $parameter array with compilation parameter
497      *
498      * @throws SmartyCompilerException
499      * @throws SmartyException
500      * @return string compiled code
501      */
502     private function compileTag2($tag, $args, $parameter)
503     {
504         $plugin_type = '';
505         // $args contains the attributes parsed and compiled by the lexer/parser
506         // assume that tag does compile into code, but creates no HTML output
507         $this->has_code = true;
508         $this->has_output = false;
509         // log tag/attributes
510         if (isset($this->smarty->_cache[ 'get_used_tags' ])) {
511             $this->template->_cache[ 'used_tags' ][] = array($tag, $args);
512         }
513         // check nocache option flag
514         foreach ($args as $arg) {
515             if (!is_array($arg)) {
516                 if ($arg === "'nocache'" || $arg === 'nocache') {
517                     $this->tag_nocache = true;
518                 }
519             } else {
520                 foreach ($arg as $k => $v) {
521                     if (($k === "'nocache'" || $k === 'nocache') && (trim($v, "'\" ") == 'true')) {
522                         $this->tag_nocache = true;
523                     }
524                 }
525             }
526         }
527         // compile the smarty tag (required compile classes to compile the tag are auto loaded)
528         if (($_output = $this->callTagCompiler($tag, $args, $parameter)) === false) {
529             if (isset($this->parent_compiler->tpl_function[ $tag ]) ||
530                 (isset ($this->template->smarty->ext->_tplFunction) && $this->template->smarty->ext->_tplFunction->getTplFunction($this->template, $tag) !== false)) {
531                 // template defined by {template} tag
532                 $args[ '_attr' ][ 'name' ] = "'" . $tag . "'";
533                 $_output = $this->callTagCompiler('call', $args, $parameter);
534             }
535         }
536         if ($_output !== false) {
537             if ($_output !== true) {
538                 // did we get compiled code
539                 if ($this->has_code) {
540                     // Does it create output?
541                     if ($this->has_output) {
542                         $_output .= "\n";
543                     }
544                     // return compiled code
545                     return $_output;
546                 }
547             }
548             // tag did not produce compiled code
549             return null;
550         } else {
551             // map_named attributes
552             if (isset($args[ '_attr' ])) {
553                 foreach ($args[ '_attr' ] as $key => $attribute) {
554                     if (is_array($attribute)) {
555                         $args = array_merge($args, $attribute);
556                     }
557                 }
558             }
559             // not an internal compiler tag
560             if (strlen($tag) < 6 || substr($tag, - 5) != 'close') {
561                 // check if tag is a registered object
562                 if (isset($this->smarty->registered_objects[ $tag ]) && isset($parameter[ 'object_method' ])) {
563                     $method = $parameter[ 'object_method' ];
564                     if (!in_array($method, $this->smarty->registered_objects[ $tag ][ 3 ]) &&
565                         (empty($this->smarty->registered_objects[ $tag ][ 1 ]) ||
566                          in_array($method, $this->smarty->registered_objects[ $tag ][ 1 ]))
567                     ) {
568                         return $this->callTagCompiler('private_object_function', $args, $parameter, $tag, $method);
569                     } elseif (in_array($method, $this->smarty->registered_objects[ $tag ][ 3 ])) {
570                         return $this->callTagCompiler('private_object_block_function', $args, $parameter, $tag,
571                                                       $method);
572                     } else {
573                         // throw exception
574                         $this->trigger_template_error('not allowed method "' . $method . '" in registered object "' .
575                                                       $tag . '"', null, true);
576                     }
577                 }
578                 // check if tag is registered
579                 foreach (array(Smarty::PLUGIN_COMPILER, Smarty::PLUGIN_FUNCTION, Smarty::PLUGIN_BLOCK,) as $plugin_type)
580                 {
581                     if (isset($this->smarty->registered_plugins[ $plugin_type ][ $tag ])) {
582                         // if compiler function plugin call it now
583                         if ($plugin_type == Smarty::PLUGIN_COMPILER) {
584                             $new_args = array();
585                             foreach ($args as $key => $mixed) {
586                                 if (is_array($mixed)) {
587                                     $new_args = array_merge($new_args, $mixed);
588                                 } else {
589                                     $new_args[ $key ] = $mixed;
590                                 }
591                             }
592                             if (!$this->smarty->registered_plugins[ $plugin_type ][ $tag ][ 1 ]) {
593                                 $this->tag_nocache = true;
594                             }
595                             return call_user_func_array($this->smarty->registered_plugins[ $plugin_type ][ $tag ][ 0 ],
596                                                         array($new_args, $this));
597                         }
598                         // compile registered function or block function
599                         if ($plugin_type == Smarty::PLUGIN_FUNCTION || $plugin_type == Smarty::PLUGIN_BLOCK) {
600                             return $this->callTagCompiler('private_registered_' . $plugin_type, $args, $parameter,
601                                                           $tag);
602                         }
603                     }
604                 }
605                 // check plugins from plugins folder
606                 foreach ($this->plugin_search_order as $plugin_type) {
607                     if ($plugin_type == Smarty::PLUGIN_COMPILER &&
608                         $this->smarty->loadPlugin('smarty_compiler_' . $tag) &&
609                         (!isset($this->smarty->security_policy) ||
610                          $this->smarty->security_policy->isTrustedTag($tag, $this))
611                     ) {
612                         $plugin = 'smarty_compiler_' . $tag;
613                         if (is_callable($plugin)) {
614                             // convert arguments format for old compiler plugins
615                             $new_args = array();
616                             foreach ($args as $key => $mixed) {
617                                 if (is_array($mixed)) {
618                                     $new_args = array_merge($new_args, $mixed);
619                                 } else {
620                                     $new_args[ $key ] = $mixed;
621                                 }
622                             }
623
624                             return $plugin($new_args, $this->smarty);
625                         }
626                         if (class_exists($plugin, false)) {
627                             $plugin_object = new $plugin;
628                             if (method_exists($plugin_object, 'compile')) {
629                                 return $plugin_object->compile($args, $this);
630                             }
631                         }
632                         throw new SmartyException("Plugin \"{$tag}\" not callable");
633                     } else {
634                         if ($function = $this->getPlugin($tag, $plugin_type)) {
635                             if (!isset($this->smarty->security_policy) ||
636                                 $this->smarty->security_policy->isTrustedTag($tag, $this)
637                             ) {
638                                 return $this->callTagCompiler('private_' . $plugin_type . '_plugin', $args, $parameter,
639                                                               $tag, $function);
640                             }
641                         }
642                     }
643                 }
644                 if (is_callable($this->smarty->default_plugin_handler_func)) {
645                     $found = false;
646                     // look for already resolved tags
647                     foreach ($this->plugin_search_order as $plugin_type) {
648                         if (isset($this->default_handler_plugins[ $plugin_type ][ $tag ])) {
649                             $found = true;
650                             break;
651                         }
652                     }
653                     if (!$found) {
654                         // call default handler
655                         foreach ($this->plugin_search_order as $plugin_type) {
656                             if ($this->getPluginFromDefaultHandler($tag, $plugin_type)) {
657                                 $found = true;
658                                 break;
659                             }
660                         }
661                     }
662                     if ($found) {
663                         // if compiler function plugin call it now
664                         if ($plugin_type == Smarty::PLUGIN_COMPILER) {
665                             $new_args = array();
666                             foreach ($args as $mixed) {
667                                 $new_args = array_merge($new_args, $mixed);
668                             }
669                             return call_user_func_array($this->default_handler_plugins[ $plugin_type ][ $tag ][ 0 ],
670                                                         array($new_args, $this));
671                         } else {
672                             return $this->callTagCompiler('private_registered_' . $plugin_type, $args, $parameter,
673                                                           $tag);
674                         }
675                     }
676                 }
677             } else {
678                 // compile closing tag of block function
679                 $base_tag = substr($tag, 0, - 5);
680                 // check if closing tag is a registered object
681                 if (isset($this->smarty->registered_objects[ $base_tag ]) && isset($parameter[ 'object_method' ])) {
682                     $method = $parameter[ 'object_method' ];
683                     if (in_array($method, $this->smarty->registered_objects[ $base_tag ][ 3 ])) {
684                         return $this->callTagCompiler('private_object_block_function', $args, $parameter, $tag,
685                                                       $method);
686                     } else {
687                         // throw exception
688                         $this->trigger_template_error('not allowed closing tag method "' . $method .
689                                                       '" in registered object "' . $base_tag . '"', null, true);
690                     }
691                 }
692                 // registered block tag ?
693                 if (isset($this->smarty->registered_plugins[ Smarty::PLUGIN_BLOCK ][ $base_tag ]) ||
694                     isset($this->default_handler_plugins[ Smarty::PLUGIN_BLOCK ][ $base_tag ])
695                 ) {
696                     return $this->callTagCompiler('private_registered_block', $args, $parameter, $tag);
697                 }
698                 // registered function tag ?
699                 if (isset($this->smarty->registered_plugins[ Smarty::PLUGIN_FUNCTION ][ $tag ])) {
700                     return $this->callTagCompiler('private_registered_function', $args, $parameter, $tag);
701                 }
702                 // block plugin?
703                 if ($function = $this->getPlugin($base_tag, Smarty::PLUGIN_BLOCK)) {
704                     return $this->callTagCompiler('private_block_plugin', $args, $parameter, $tag, $function);
705                 }
706                 // function plugin?
707                 if ($function = $this->getPlugin($tag, Smarty::PLUGIN_FUNCTION)) {
708                     if (!isset($this->smarty->security_policy) ||
709                         $this->smarty->security_policy->isTrustedTag($tag, $this)
710                     ) {
711                         return $this->callTagCompiler('private_function_plugin', $args, $parameter, $tag, $function);
712                     }
713                 }
714                 // registered compiler plugin ?
715                 if (isset($this->smarty->registered_plugins[ Smarty::PLUGIN_COMPILER ][ $tag ])) {
716                     // if compiler function plugin call it now
717                     $args = array();
718                     if (!$this->smarty->registered_plugins[ Smarty::PLUGIN_COMPILER ][ $tag ][ 1 ]) {
719                         $this->tag_nocache = true;
720                     }
721                     return call_user_func_array($this->smarty->registered_plugins[ Smarty::PLUGIN_COMPILER ][ $tag ][ 0 ],
722                                                 array($args, $this));
723                 }
724                 if ($this->smarty->loadPlugin('smarty_compiler_' . $tag)) {
725                     $plugin = 'smarty_compiler_' . $tag;
726                     if (is_callable($plugin)) {
727                         return $plugin($args, $this->smarty);
728                     }
729                     if (class_exists($plugin, false)) {
730                         $plugin_object = new $plugin;
731                         if (method_exists($plugin_object, 'compile')) {
732                             return $plugin_object->compile($args, $this);
733                         }
734                     }
735                     throw new SmartyException("Plugin \"{$tag}\" not callable");
736                 }
737             }
738             $this->trigger_template_error("unknown tag \"" . $tag . "\"", null, true);
739         }
740     }
741
742     /**
743      * compile variable
744      *
745      * @param string $variable
746      *
747      * @return string
748      */
749     public function compileVariable($variable)
750     {
751         if (strpos($variable, '(') == 0) {
752             // not a variable variable
753             $var = trim($variable, '\'');
754             $this->tag_nocache = $this->tag_nocache |
755                                  $this->template->ext->getTemplateVars->_getVariable($this->template, $var, null, true,
756                                                                                      false)->nocache;
757             // todo $this->template->compiled->properties['variables'][$var] = $this->tag_nocache | $this->nocache;
758         }
759         return '$_smarty_tpl->tpl_vars[' . $variable . ']->value';
760     }
761
762     /**
763      * compile config variable
764      *
765      * @param string $variable
766      *
767      * @return string
768      */
769     public function compileConfigVariable($variable)
770     {
771         // return '$_smarty_tpl->config_vars[' . $variable . ']';
772         return '$_smarty_tpl->smarty->ext->configLoad->_getConfigVariable($_smarty_tpl, ' . $variable . ')';
773     }
774
775     /**
776      * compile PHP function call
777      *
778      * @param string       $name
779      * @param        array $parameter
780      *
781      * @return string
782      */
783     public function compilePHPFunctionCall($name, $parameter)
784     {
785         if (!$this->smarty->security_policy || $this->smarty->security_policy->isTrustedPhpFunction($name, $this)) {
786             if (strcasecmp($name, 'isset') === 0 || strcasecmp($name, 'empty') === 0 ||
787                 strcasecmp($name, 'array') === 0 || is_callable($name)
788             ) {
789                 $func_name = strtolower($name);
790                 $par = implode(',', $parameter);
791                 $parHasFuction = strpos($par, '(') !== false;
792                 if ($func_name == 'isset') {
793                     if (count($parameter) == 0) {
794                         $this->trigger_template_error('Illegal number of paramer in "isset()"');
795                     }
796                     if ($parHasFuction) {
797                         $prefixVar = $this->getNewPrefixVariable();
798                         $this->appendPrefixCode("<?php $prefixVar" . '=' . $par . ';?>');
799                         $isset_par = $prefixVar;
800                     } else {
801                         $isset_par = str_replace("')->value", "',null,true,false)->value", $par);
802                     }
803                     return $name . "(" . $isset_par . ")";
804                 } elseif (in_array($func_name, array('empty', 'reset', 'current', 'end', 'prev', 'next'))) {
805                     if (count($parameter) != 1) {
806                         $this->trigger_template_error('Illegal number of paramer in "empty()"');
807                     }
808                     if ($func_name == 'empty') {
809                         if ($parHasFuction) {
810                             $prefixVar = $this->getNewPrefixVariable();
811                             $this->appendPrefixCode("<?php $prefixVar" . '=' . $par . ';?>');
812
813                             return $func_name . '(' . $prefixVar . ')';
814                         } else {
815                             return $func_name . '(' .
816                                    str_replace("')->value", "',null,true,false)->value", $parameter[ 0 ]) . ')';
817                         }
818                     } else {
819                         return $func_name . '(' . $parameter[ 0 ] . ')';
820                     }
821                 } else {
822                     return $name . "(" . implode(',', $parameter) . ")";
823                 }
824             } else {
825                 $this->trigger_template_error("unknown function \"" . $name . "\"");
826             }
827         }
828     }
829
830     /**
831      * This method is called from parser to process a text content section
832      * - remove text from inheritance child templates as they may generate output
833      * - strip text if strip is enabled
834      *
835      * @param string $text
836      *
837      * @return null|\Smarty_Internal_ParseTree_Text
838      */
839     public function processText($text)
840     {
841         if ((string) $text != '') {
842             $store = array();
843             $_store = 0;
844             if ($this->parser->strip) {
845                 if (strpos($text, '<') !== false) {
846                     // capture html elements not to be messed with
847                     $_offset = 0;
848                     if (preg_match_all('#(<script[^>]*>.*?</script[^>]*>)|(<textarea[^>]*>.*?</textarea[^>]*>)|(<pre[^>]*>.*?</pre[^>]*>)#is',
849                                        $text, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
850                         foreach ($matches as $match) {
851                             $store[] = $match[ 0 ][ 0 ];
852                             $_length = strlen($match[ 0 ][ 0 ]);
853                             $replace = '@!@SMARTY:' . $_store . ':SMARTY@!@';
854                             $text = substr_replace($text, $replace, $match[ 0 ][ 1 ] - $_offset, $_length);
855
856                             $_offset += $_length - strlen($replace);
857                             $_store ++;
858                         }
859                     }
860                     $expressions = array(// replace multiple spaces between tags by a single space
861                                          '#(:SMARTY@!@|>)[\040\011]+(?=@!@SMARTY:|<)#s' => '\1 \2',
862                                          // remove newline between tags
863                                          '#(:SMARTY@!@|>)[\040\011]*[\n]\s*(?=@!@SMARTY:|<)#s' => '\1\2',
864                                          // remove multiple spaces between attributes (but not in attribute values!)
865                                          '#(([a-z0-9]\s*=\s*("[^"]*?")|(\'[^\']*?\'))|<[a-z0-9_]+)\s+([a-z/>])#is' => '\1 \5',
866                                          '#>[\040\011]+$#Ss' => '> ', '#>[\040\011]*[\n]\s*$#Ss' => '>',
867                                          $this->stripRegEx => '',);
868
869                     $text = preg_replace(array_keys($expressions), array_values($expressions), $text);
870                     $_offset = 0;
871                     if (preg_match_all('#@!@SMARTY:([0-9]+):SMARTY@!@#is', $text, $matches,
872                                        PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
873                         foreach ($matches as $match) {
874                             $_length = strlen($match[ 0 ][ 0 ]);
875                             $replace = $store[ $match[ 1 ][ 0 ] ];
876                             $text = substr_replace($text, $replace, $match[ 0 ][ 1 ] + $_offset, $_length);
877
878                             $_offset += strlen($replace) - $_length;
879                             $_store ++;
880                         }
881                     }
882                 } else {
883                     $text = preg_replace($this->stripRegEx, '', $text);
884                 }
885             }
886             return new Smarty_Internal_ParseTree_Text($text);
887         }
888         return null;
889     }
890
891     /**
892      * lazy loads internal compile plugin for tag and calls the compile method
893      * compile objects cached for reuse.
894      * class name format:  Smarty_Internal_Compile_TagName
895      * plugin filename format: Smarty_Internal_TagName.php
896      *
897      * @param  string $tag    tag name
898      * @param  array  $args   list of tag attributes
899      * @param  mixed  $param1 optional parameter
900      * @param  mixed  $param2 optional parameter
901      * @param  mixed  $param3 optional parameter
902      *
903      * @return string|bool compiled code or false
904      */
905     public function callTagCompiler($tag, $args, $param1 = null, $param2 = null, $param3 = null)
906     {
907         $tagCompiler = $this->getTagCompiler($tag);
908         // compile this tag
909         return $tagCompiler === false ? false : $tagCompiler->compile($args, $this, $param1, $param2, $param3);
910     }
911
912     /**
913      * lazy loads internal compile plugin for tag compile objects cached for reuse.
914      *
915      * class name format:  Smarty_Internal_Compile_TagName
916      * plugin filename format: Smarty_Internal_TagName.php
917      *
918      * @param  string $tag tag name
919      *
920      * @return Smarty_Internal_CompileBase|bool tag compiler object or false if not found
921      */
922     public function getTagCompiler($tag)
923     {
924         // re-use object if already exists
925         if (!isset(self::$_tag_objects[ $tag ])) {
926             // lazy load internal compiler plugin
927             $_tag = explode('_', $tag);
928             $_tag = array_map('ucfirst', $_tag);
929             $class_name = 'Smarty_Internal_Compile_' . implode('_', $_tag);
930             if (class_exists($class_name) &&
931                 (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($tag, $this))
932             ) {
933                 self::$_tag_objects[ $tag ] = new $class_name;
934             } else {
935                 self::$_tag_objects[ $tag ] = false;
936             }
937         }
938         return self::$_tag_objects[ $tag ];
939     }
940
941     /**
942      * Check for plugins and return function name
943      *
944      * @param         $plugin_name
945      * @param  string $plugin_type type of plugin
946      *
947      * @return string call name of function
948      */
949     public function getPlugin($plugin_name, $plugin_type)
950     {
951         $function = null;
952         if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {
953             if (isset($this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ])) {
954                 $function =
955                     $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ][ 'function' ];
956             } elseif (isset($this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ])) {
957                 $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ] =
958                     $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ];
959                 $function =
960                     $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ][ 'function' ];
961             }
962         } else {
963             if (isset($this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ])) {
964                 $function =
965                     $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ][ 'function' ];
966             } elseif (isset($this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ])) {
967                 $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ] =
968                     $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ];
969                 $function =
970                     $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ][ 'function' ];
971             }
972         }
973         if (isset($function)) {
974             if ($plugin_type == 'modifier') {
975                 $this->modifier_plugins[ $plugin_name ] = true;
976             }
977
978             return $function;
979         }
980         // loop through plugin dirs and find the plugin
981         $function = 'smarty_' . $plugin_type . '_' . $plugin_name;
982         $file = $this->smarty->loadPlugin($function, false);
983
984         if (is_string($file)) {
985             if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {
986                 $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ][ 'file' ] =
987                     $file;
988                 $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ][ 'function' ] =
989                     $function;
990             } else {
991                 $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ][ 'file' ] =
992                     $file;
993                 $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ][ 'function' ] =
994                     $function;
995             }
996             if ($plugin_type == 'modifier') {
997                 $this->modifier_plugins[ $plugin_name ] = true;
998             }
999
1000             return $function;
1001         }
1002         if (is_callable($function)) {
1003             // plugin function is defined in the script
1004             return $function;
1005         }
1006
1007         return false;
1008     }
1009
1010     /**
1011      * Check for plugins by default plugin handler
1012      *
1013      * @param  string $tag         name of tag
1014      * @param  string $plugin_type type of plugin
1015      *
1016      * @return boolean true if found
1017      */
1018     public function getPluginFromDefaultHandler($tag, $plugin_type)
1019     {
1020         $callback = null;
1021         $script = null;
1022         $cacheable = true;
1023         $result = call_user_func_array($this->smarty->default_plugin_handler_func,
1024                                        array($tag, $plugin_type, $this->template, &$callback, &$script, &$cacheable,));
1025         if ($result) {
1026             $this->tag_nocache = $this->tag_nocache || !$cacheable;
1027             if ($script !== null) {
1028                 if (is_file($script)) {
1029                     if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {
1030                         $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $tag ][ $plugin_type ][ 'file' ] =
1031                             $script;
1032                         $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $tag ][ $plugin_type ][ 'function' ] =
1033                             $callback;
1034                     } else {
1035                         $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $tag ][ $plugin_type ][ 'file' ] =
1036                             $script;
1037                         $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $tag ][ $plugin_type ][ 'function' ] =
1038                             $callback;
1039                     }
1040                     require_once $script;
1041                 } else {
1042                     $this->trigger_template_error("Default plugin handler: Returned script file \"{$script}\" for \"{$tag}\" not found");
1043                 }
1044             }
1045             if (is_callable($callback)) {
1046                 $this->default_handler_plugins[ $plugin_type ][ $tag ] = array($callback, true, array());
1047
1048                 return true;
1049             } else {
1050                 $this->trigger_template_error("Default plugin handler: Returned callback for \"{$tag}\" not callable");
1051             }
1052         }
1053
1054         return false;
1055     }
1056
1057     /**
1058      * Append code segments and remove unneeded ?> <?php transitions
1059      *
1060      * @param string $left
1061      * @param string $right
1062      *
1063      * @return string
1064      */
1065     public function appendCode($left, $right)
1066     {
1067         if (preg_match('/\s*\?>\s*$/', $left) && preg_match('/^\s*<\?php\s+/', $right)) {
1068             $left = preg_replace('/\s*\?>\s*$/', "\n", $left);
1069             $left .= preg_replace('/^\s*<\?php\s+/', '', $right);
1070         } else {
1071             $left .= $right;
1072         }
1073         return $left;
1074     }
1075
1076     /**
1077      * Inject inline code for nocache template sections
1078      * This method gets the content of each template element from the parser.
1079      * If the content is compiled code and it should be not cached the code is injected
1080      * into the rendered output.
1081      *
1082      * @param  string  $content content of template element
1083      * @param  boolean $is_code true if content is compiled code
1084      *
1085      * @return string  content
1086      */
1087     public function processNocacheCode($content, $is_code)
1088     {
1089         // If the template is not evaluated and we have a nocache section and or a nocache tag
1090         if ($is_code && !empty($content)) {
1091             // generate replacement code
1092             if ((!($this->template->source->handler->recompiled) || $this->forceNocache) && $this->template->caching &&
1093                 !$this->suppressNocacheProcessing && ($this->nocache || $this->tag_nocache)
1094             ) {
1095                 $this->template->compiled->has_nocache_code = true;
1096                 $_output = addcslashes($content, '\'\\');
1097                 $_output = str_replace("^#^", "'", $_output);
1098                 $_output = "<?php echo '/*%%SmartyNocache:{$this->nocache_hash}%%*/" . $_output .
1099                            "/*/%%SmartyNocache:{$this->nocache_hash}%%*/';?>\n";
1100                 // make sure we include modifier plugins for nocache code
1101                 foreach ($this->modifier_plugins as $plugin_name => $dummy) {
1102                     if (isset($this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ 'modifier' ])) {
1103                         $this->parent_compiler->template->compiled->required_plugins[ 'nocache' ][ $plugin_name ][ 'modifier' ] =
1104                             $this->parent_compiler->template->compiled->required_plugins[ 'compiled' ][ $plugin_name ][ 'modifier' ];
1105                     }
1106                 }
1107             } else {
1108                 $_output = $content;
1109             }
1110         } else {
1111             $_output = $content;
1112         }
1113         $this->modifier_plugins = array();
1114         $this->suppressNocacheProcessing = false;
1115         $this->tag_nocache = false;
1116
1117         return $_output;
1118     }
1119
1120     /**
1121      * Get Id
1122      *
1123      * @param string $input
1124      *
1125      * @return bool|string
1126      */
1127     public function getId($input)
1128     {
1129         if (preg_match('~^([\'"]*)([0-9]*[a-zA-Z_]\w*)\1$~', $input, $match)) {
1130             return $match[ 2 ];
1131         }
1132         return false;
1133     }
1134
1135     /**
1136      * Get variable name from string
1137      *
1138      * @param string $input
1139      *
1140      * @return bool|string
1141      */
1142     public function getVariableName($input)
1143     {
1144         if (preg_match('~^[$]_smarty_tpl->tpl_vars\[[\'"]*([0-9]*[a-zA-Z_]\w*)[\'"]*\]->value$~', $input, $match)) {
1145             return $match[ 1 ];
1146         }
1147         return false;
1148     }
1149
1150     /**
1151      * Set nocache flag in variable or create new variable
1152      *
1153      * @param string $varName
1154      */
1155     public function setNocacheInVariable($varName)
1156     {
1157         // create nocache var to make it know for further compiling
1158         if ($_var = $this->getId($varName)) {
1159             if (isset($this->template->tpl_vars[ $_var ])) {
1160                 $this->template->tpl_vars[ $_var ] = clone $this->template->tpl_vars[ $_var ];
1161                 $this->template->tpl_vars[ $_var ]->nocache = true;
1162             } else {
1163                 $this->template->tpl_vars[ $_var ] = new Smarty_Variable(null, true);
1164             }
1165         }
1166     }
1167
1168     /**
1169      * @param array $_attr tag attributes
1170      * @param array $validScopes
1171      *
1172      * @return int|string
1173      * @throws \SmartyCompilerException
1174      */
1175     public function convertScope($_attr, $validScopes)
1176     {
1177         $_scope = 0;
1178         if (isset($_attr[ 'scope' ])) {
1179             $_scopeName = trim($_attr[ 'scope' ], "'\"");
1180             if (is_numeric($_scopeName) && in_array($_scopeName, $validScopes)) {
1181                 $_scope = $_scopeName;
1182             } elseif (is_string($_scopeName)) {
1183                 $_scopeName = trim($_scopeName, "'\"");
1184                 $_scope = isset($validScopes[ $_scopeName ]) ? $validScopes[ $_scopeName ] : false;
1185             } else {
1186                 $_scope = false;
1187             }
1188             if ($_scope === false) {
1189                 $err = var_export($_scopeName, true);
1190                 $this->trigger_template_error("illegal value '{$err}' for \"scope\" attribute", null, true);
1191             }
1192         }
1193         return $_scope;
1194     }
1195
1196     /**
1197      * Generate nocache code string
1198      *
1199      * @param string $code PHP code
1200      *
1201      * @return string
1202      */
1203     public function makeNocacheCode($code)
1204     {
1205         return "echo '/*%%SmartyNocache:{$this->nocache_hash}%%*/<?php " .
1206                str_replace("^#^", "'", addcslashes($code, '\'\\')) .
1207                "?>/*/%%SmartyNocache:{$this->nocache_hash}%%*/';\n";
1208     }
1209
1210     /**
1211      * display compiler error messages without dying
1212      * If parameter $args is empty it is a parser detected syntax error.
1213      * In this case the parser is called to obtain information about expected tokens.
1214      * If parameter $args contains a string this is used as error message
1215      *
1216      * @param  string   $args    individual error message or null
1217      * @param  string   $line    line-number
1218      * @param null|bool $tagline if true the line number of last tag
1219      *
1220      * @throws \SmartyCompilerException when an unexpected token is found
1221      */
1222     public function trigger_template_error($args = null, $line = null, $tagline = null)
1223     {
1224         $lex = $this->parser->lex;
1225         if ($tagline === true) {
1226             // get line number of Tag
1227             $line = $lex->taglineno;
1228         } elseif (!isset($line)) {
1229             // get template source line which has error
1230             $line = $lex->line;
1231         } else {
1232             $line = (int) $line;
1233         }
1234
1235         if (in_array($this->template->source->type, array('eval', 'string'))) {
1236             $templateName = $this->template->source->type . ':' . trim(preg_replace('![\t\r\n]+!', ' ',
1237                                                                                     strlen($lex->data) > 40 ?
1238                                                                                         substr($lex->data, 0, 40) .
1239                                                                                         '...' : $lex->data));
1240         } else {
1241             $templateName = $this->template->source->type . ':' . $this->template->source->filepath;
1242         }
1243
1244         //        $line += $this->trace_line_offset;
1245         $match = preg_split("/\n/", $lex->data);
1246         $error_text =
1247             'Syntax error in template "' . (empty($this->trace_filepath) ? $templateName : $this->trace_filepath) .
1248             '"  on line ' . ($line + $this->trace_line_offset) . ' "' .
1249             trim(preg_replace('![\t\r\n]+!', ' ', $match[ $line - 1 ])) . '" ';
1250         if (isset($args)) {
1251             // individual error message
1252             $error_text .= $args;
1253         } else {
1254             $expect = array();
1255             // expected token from parser
1256             $error_text .= ' - Unexpected "' . $lex->value . '"';
1257             if (count($this->parser->yy_get_expected_tokens($this->parser->yymajor)) <= 4) {
1258                 foreach ($this->parser->yy_get_expected_tokens($this->parser->yymajor) as $token) {
1259                     $exp_token = $this->parser->yyTokenName[ $token ];
1260                     if (isset($lex->smarty_token_names[ $exp_token ])) {
1261                         // token type from lexer
1262                         $expect[] = '"' . $lex->smarty_token_names[ $exp_token ] . '"';
1263                     } else {
1264                         // otherwise internal token name
1265                         $expect[] = $this->parser->yyTokenName[ $token ];
1266                     }
1267                 }
1268                 $error_text .= ', expected one of: ' . implode(' , ', $expect);
1269             }
1270         }
1271         $e = new SmartyCompilerException($error_text);
1272         $e->line = $line;
1273         $e->source = trim(preg_replace('![\t\r\n]+!', ' ', $match[ $line - 1 ]));
1274         $e->desc = $args;
1275         $e->template = $this->template->source->filepath;
1276         throw $e;
1277     }
1278
1279     /**
1280      * Return var_export() value with all white spaces removed
1281      *
1282      * @param  mixed $value
1283      *
1284      * @return string
1285      */
1286     public function getVarExport($value)
1287     {
1288         return preg_replace('/\s/', '', var_export($value, true));
1289     }
1290
1291     /**
1292      * Check if $value contains variable elements
1293      *
1294      * @param mixed $value
1295      *
1296      * @return bool|int
1297      */
1298     public function isVariable($value)
1299     {
1300         if (is_string($value)) {
1301             return preg_match('/[$(]/', $value);
1302         }
1303         if (is_bool($value) || is_numeric($value)) {
1304             return false;
1305         }
1306         if (is_array($value)) {
1307             foreach ($value as $k => $v) {
1308                 if ($this->isVariable($k) || $this->isVariable($v)) {
1309                     return true;
1310                 }
1311             }
1312             return false;
1313         }
1314         return false;
1315     }
1316
1317     /**
1318      * Get new prefix variable name
1319      *
1320      * @return string
1321      */
1322     public function getNewPrefixVariable()
1323     {
1324         self::$prefixVariableNumber ++;
1325         return $this->getPrefixVariable();
1326     }
1327
1328     /**
1329      * Get current prefix variable name
1330      *
1331      * @return string
1332      */
1333     public function getPrefixVariable()
1334     {
1335         return '$_prefixVariable' . self::$prefixVariableNumber;
1336     }
1337
1338     /**
1339      * append  code to prefix buffer
1340      *
1341      * @param string $code
1342      */
1343     public function appendPrefixCode($code)
1344     {
1345         $this->prefix_code[] = $code;
1346     }
1347
1348     /**
1349      * get prefix code string
1350      *
1351      * @return string
1352      */
1353     public function getPrefixCode()
1354     {
1355         $code = '';
1356         $prefixArray = array_merge($this->prefix_code, array_pop($this->prefixCodeStack));
1357         $this->prefixCodeStack[] = array();
1358         foreach ($prefixArray as $c) {
1359             $code = $this->appendCode($code, $c);
1360         }
1361         $this->prefix_code = array();
1362         return $code;
1363     }
1364
1365 }