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