1<?php
2/**
3 * Smarty Internal Plugin Template
4 * This file contains the Smarty template engine
5 *
6 * @package    Smarty
7 * @subpackage Template
8 * @author     Uwe Tews
9 */
10
11/**
12 * Main class with template data structures and methods
13 *
14 * @package    Smarty
15 * @subpackage Template
16 *
17 * @property Smarty_Template_Compiled             $compiled
18 * @property Smarty_Template_Cached               $cached
19 * @property Smarty_Internal_TemplateCompilerBase $compiler
20 * @property mixed|\Smarty_Template_Cached        registered_plugins
21 *
22 * The following methods will be dynamically loaded by the extension handler when they are called.
23 * They are located in a corresponding Smarty_Internal_Method_xxxx class
24 *
25 * @method bool mustCompile()
26 */
27class Smarty_Internal_Template extends Smarty_Internal_TemplateBase
28{
29    /**
30     * Template object cache
31     *
32     * @var Smarty_Internal_Template[]
33     */
34    public static $tplObjCache = array();
35
36    /**
37     * Template object cache for Smarty::isCached() === true
38     *
39     * @var Smarty_Internal_Template[]
40     */
41    public static $isCacheTplObj = array();
42
43    /**
44     * Sub template Info Cache
45     * - index name
46     * - value use count
47     *
48     * @var int[]
49     */
50    public static $subTplInfo = array();
51
52    /**
53     * This object type (Smarty = 1, template = 2, data = 4)
54     *
55     * @var int
56     */
57    public $_objType = 2;
58
59    /**
60     * Global smarty instance
61     *
62     * @var Smarty
63     */
64    public $smarty = null;
65
66    /**
67     * Source instance
68     *
69     * @var Smarty_Template_Source|Smarty_Template_Config
70     */
71    public $source = null;
72
73    /**
74     * Inheritance runtime extension
75     *
76     * @var Smarty_Internal_Runtime_Inheritance
77     */
78    public $inheritance = null;
79
80    /**
81     * Template resource
82     *
83     * @var string
84     */
85    public $template_resource = null;
86
87    /**
88     * flag if compiled template is invalid and must be (re)compiled
89     *
90     * @var bool
91     */
92    public $mustCompile = null;
93
94    /**
95     * Template Id
96     *
97     * @var null|string
98     */
99    public $templateId = null;
100
101    /**
102     * Scope in which variables shall be assigned
103     *
104     * @var int
105     */
106    public $scope = 0;
107
108    /**
109     * Flag which is set while rending a cache file
110     *
111     * @var bool
112     */
113    public $isRenderingCache = false;
114
115    /**
116     * Callbacks called before rendering template
117     *
118     * @var callback[]
119     */
120    public $startRenderCallbacks = array();
121
122    /**
123     * Callbacks called after rendering template
124     *
125     * @var callback[]
126     */
127    public $endRenderCallbacks = array();
128
129    /**
130     * Create template data object
131     * Some of the global Smarty settings copied to template scope
132     * It load the required template resources and caching plugins
133     *
134     * @param string                                                       $template_resource template resource string
135     * @param Smarty                                                       $smarty            Smarty instance
136     * @param null|\Smarty_Internal_Template|\Smarty|\Smarty_Internal_Data $_parent           back pointer to parent
137     *                                                                                        object with variables or
138     *                                                                                        null
139     * @param mixed                                                        $_cache_id         cache   id or null
140     * @param mixed                                                        $_compile_id       compile id or null
141     * @param bool|int|null                                                $_caching          use caching?
142     * @param int|null                                                     $_cache_lifetime   cache life-time in
143     *                                                                                        seconds
144     * @param bool                                                         $_isConfig
145     *
146     * @throws \SmartyException
147     */
148    public function __construct(
149        $template_resource,
150        Smarty $smarty,
151        Smarty_Internal_Data $_parent = null,
152        $_cache_id = null,
153        $_compile_id = null,
154        $_caching = null,
155        $_cache_lifetime = null,
156        $_isConfig = false
157    ) {
158        $this->smarty = $smarty;
159        // Smarty parameter
160        $this->cache_id = $_cache_id === null ? $this->smarty->cache_id : $_cache_id;
161        $this->compile_id = $_compile_id === null ? $this->smarty->compile_id : $_compile_id;
162        $this->caching = (int)($_caching === null ? $this->smarty->caching : $_caching);
163        $this->cache_lifetime = $_cache_lifetime === null ? $this->smarty->cache_lifetime : $_cache_lifetime;
164        $this->compile_check = (int)$smarty->compile_check;
165        $this->parent = $_parent;
166        // Template resource
167        $this->template_resource = $template_resource;
168        $this->source = $_isConfig ? Smarty_Template_Config::load($this) : Smarty_Template_Source::load($this);
169        parent::__construct();
170        if ($smarty->security_policy && method_exists($smarty->security_policy, 'registerCallBacks')) {
171            $smarty->security_policy->registerCallBacks($this);
172        }
173    }
174
175    /**
176     * render template
177     *
178     * @param bool      $no_output_filter if true do not run output filter
179     * @param null|bool $display          true: display, false: fetch null: sub-template
180     *
181     * @return string
182     * @throws \Exception
183     * @throws \SmartyException
184     */
185    public function render($no_output_filter = true, $display = null)
186    {
187        if ($this->smarty->debugging) {
188            if (!isset($this->smarty->_debug)) {
189                $this->smarty->_debug = new Smarty_Internal_Debug();
190            }
191            $this->smarty->_debug->start_template($this, $display);
192        }
193        // checks if template exists
194        if (!$this->source->exists) {
195            throw new SmartyException(
196                "Unable to load template '{$this->source->type}:{$this->source->name}'" .
197                ($this->_isSubTpl() ? " in '{$this->parent->template_resource}'" : '')
198            );
199        }
200        // disable caching for evaluated code
201        if ($this->source->handler->recompiled) {
202            $this->caching = Smarty::CACHING_OFF;
203        }
204        // read from cache or render
205        if ($this->caching === Smarty::CACHING_LIFETIME_CURRENT || $this->caching === Smarty::CACHING_LIFETIME_SAVED) {
206            if (!isset($this->cached) || $this->cached->cache_id !== $this->cache_id
207                || $this->cached->compile_id !== $this->compile_id
208            ) {
209                $this->loadCached(true);
210            }
211            $this->cached->render($this, $no_output_filter);
212        } else {
213            if (!isset($this->compiled) || $this->compiled->compile_id !== $this->compile_id) {
214                $this->loadCompiled(true);
215            }
216            $this->compiled->render($this);
217        }
218        // display or fetch
219        if ($display) {
220            if ($this->caching && $this->smarty->cache_modified_check) {
221                $this->smarty->ext->_cacheModify->cacheModifiedCheck(
222                    $this->cached,
223                    $this,
224                    isset($content) ? $content : ob_get_clean()
225                );
226            } else {
227                if ((!$this->caching || $this->cached->has_nocache_code || $this->source->handler->recompiled)
228                    && !$no_output_filter && (isset($this->smarty->autoload_filters[ 'output' ])
229                                              || isset($this->smarty->registered_filters[ 'output' ]))
230                ) {
231                    echo $this->smarty->ext->_filterHandler->runFilter('output', ob_get_clean(), $this);
232                } else {
233                    echo ob_get_clean();
234                }
235            }
236            if ($this->smarty->debugging) {
237                $this->smarty->_debug->end_template($this);
238                // debug output
239                $this->smarty->_debug->display_debug($this, true);
240            }
241            return '';
242        } else {
243            if ($this->smarty->debugging) {
244                $this->smarty->_debug->end_template($this);
245                if ($this->smarty->debugging === 2 && $display === false) {
246                    $this->smarty->_debug->display_debug($this, true);
247                }
248            }
249            if (!$no_output_filter
250                && (!$this->caching || $this->cached->has_nocache_code || $this->source->handler->recompiled)
251                && (isset($this->smarty->autoload_filters[ 'output' ])
252                    || isset($this->smarty->registered_filters[ 'output' ]))
253            ) {
254                return $this->smarty->ext->_filterHandler->runFilter('output', ob_get_clean(), $this);
255            }
256            // return cache content
257            return null;
258        }
259    }
260
261    /**
262     * Runtime function to render sub-template
263     *
264     * @param string  $template       template name
265     * @param mixed   $cache_id       cache id
266     * @param mixed   $compile_id     compile id
267     * @param integer $caching        cache mode
268     * @param integer $cache_lifetime life time of cache data
269     * @param array   $data           passed parameter template variables
270     * @param int     $scope          scope in which {include} should execute
271     * @param bool    $forceTplCache  cache template object
272     * @param string  $uid            file dependency uid
273     * @param string  $content_func   function name
274     *
275     * @throws \Exception
276     * @throws \SmartyException
277     */
278    public function _subTemplateRender(
279        $template,
280        $cache_id,
281        $compile_id,
282        $caching,
283        $cache_lifetime,
284        $data,
285        $scope,
286        $forceTplCache,
287        $uid = null,
288        $content_func = null
289    ) {
290        $tpl = clone $this;
291        $tpl->parent = $this;
292        $smarty = &$this->smarty;
293        $_templateId = $smarty->_getTemplateId($template, $cache_id, $compile_id, $caching, $tpl);
294        // recursive call ?
295        if (isset($tpl->templateId) ? $tpl->templateId : $tpl->_getTemplateId() !== $_templateId) {
296            // already in template cache?
297            if (isset(self::$tplObjCache[ $_templateId ])) {
298                // copy data from cached object
299                $cachedTpl = &self::$tplObjCache[ $_templateId ];
300                $tpl->templateId = $cachedTpl->templateId;
301                $tpl->template_resource = $cachedTpl->template_resource;
302                $tpl->cache_id = $cachedTpl->cache_id;
303                $tpl->compile_id = $cachedTpl->compile_id;
304                $tpl->source = $cachedTpl->source;
305                if (isset($cachedTpl->compiled)) {
306                    $tpl->compiled = $cachedTpl->compiled;
307                } else {
308                    unset($tpl->compiled);
309                }
310                if ($caching !== 9999 && isset($cachedTpl->cached)) {
311                    $tpl->cached = $cachedTpl->cached;
312                } else {
313                    unset($tpl->cached);
314                }
315            } else {
316                $tpl->templateId = $_templateId;
317                $tpl->template_resource = $template;
318                $tpl->cache_id = $cache_id;
319                $tpl->compile_id = $compile_id;
320                if (isset($uid)) {
321                    // for inline templates we can get all resource information from file dependency
322                    list($filepath, $timestamp, $type) = $tpl->compiled->file_dependency[ $uid ];
323                    $tpl->source = new Smarty_Template_Source($smarty, $filepath, $type, $filepath);
324                    $tpl->source->filepath = $filepath;
325                    $tpl->source->timestamp = $timestamp;
326                    $tpl->source->exists = true;
327                    $tpl->source->uid = $uid;
328                } else {
329                    $tpl->source = Smarty_Template_Source::load($tpl);
330                    unset($tpl->compiled);
331                }
332                if ($caching !== 9999) {
333                    unset($tpl->cached);
334                }
335            }
336        } else {
337            // on recursive calls force caching
338            $forceTplCache = true;
339        }
340        $tpl->caching = $caching;
341        $tpl->cache_lifetime = $cache_lifetime;
342        // set template scope
343        $tpl->scope = $scope;
344        if (!isset(self::$tplObjCache[ $tpl->templateId ]) && !$tpl->source->handler->recompiled) {
345            // check if template object should be cached
346            if ($forceTplCache || (isset(self::$subTplInfo[ $tpl->template_resource ])
347                                   && self::$subTplInfo[ $tpl->template_resource ] > 1)
348                || ($tpl->_isSubTpl() && isset(self::$tplObjCache[ $tpl->parent->templateId ]))
349            ) {
350                self::$tplObjCache[ $tpl->templateId ] = $tpl;
351            }
352        }
353        if (!empty($data)) {
354            // set up variable values
355            foreach ($data as $_key => $_val) {
356                $tpl->tpl_vars[ $_key ] = new Smarty_Variable($_val, $this->isRenderingCache);
357            }
358        }
359        if ($tpl->caching === 9999) {
360            if (!isset($tpl->compiled)) {
361                $this->loadCompiled(true);
362            }
363            if ($tpl->compiled->has_nocache_code) {
364                $this->cached->hashes[ $tpl->compiled->nocache_hash ] = true;
365            }
366        }
367        $tpl->_cache = array();
368        if (isset($uid)) {
369            if ($smarty->debugging) {
370                if (!isset($smarty->_debug)) {
371                    $smarty->_debug = new Smarty_Internal_Debug();
372                }
373                $smarty->_debug->start_template($tpl);
374                $smarty->_debug->start_render($tpl);
375            }
376            $tpl->compiled->getRenderedTemplateCode($tpl, $content_func);
377            if ($smarty->debugging) {
378                $smarty->_debug->end_template($tpl);
379                $smarty->_debug->end_render($tpl);
380            }
381        } else {
382            if (isset($tpl->compiled)) {
383                $tpl->compiled->render($tpl);
384            } else {
385                $tpl->render();
386            }
387        }
388    }
389
390    /**
391     * Get called sub-templates and save call count
392     */
393    public function _subTemplateRegister()
394    {
395        foreach ($this->compiled->includes as $name => $count) {
396            if (isset(self::$subTplInfo[ $name ])) {
397                self::$subTplInfo[ $name ] += $count;
398            } else {
399                self::$subTplInfo[ $name ] = $count;
400            }
401        }
402    }
403
404    /**
405     * Check if this is a sub template
406     *
407     * @return bool true is sub template
408     */
409    public function _isSubTpl()
410    {
411        return isset($this->parent) && $this->parent->_isTplObj();
412    }
413
414    /**
415     * Assign variable in scope
416     *
417     * @param string $varName variable name
418     * @param mixed  $value   value
419     * @param bool   $nocache nocache flag
420     * @param int    $scope   scope into which variable shall be assigned
421     */
422    public function _assignInScope($varName, $value, $nocache = false, $scope = 0)
423    {
424        if (isset($this->tpl_vars[ $varName ])) {
425            $this->tpl_vars[ $varName ] = clone $this->tpl_vars[ $varName ];
426            $this->tpl_vars[ $varName ]->value = $value;
427            if ($nocache || $this->isRenderingCache) {
428                $this->tpl_vars[ $varName ]->nocache = true;
429            }
430        } else {
431            $this->tpl_vars[ $varName ] = new Smarty_Variable($value, $nocache || $this->isRenderingCache);
432        }
433        if ($scope >= 0) {
434            if ($scope > 0 || $this->scope > 0) {
435                $this->smarty->ext->_updateScope->_updateScope($this, $varName, $scope);
436            }
437        }
438    }
439
440    /**
441     * Check if plugins are callable require file otherwise
442     *
443     * @param array $plugins required plugins
444     *
445     * @throws \SmartyException
446     */
447    public function _checkPlugins($plugins)
448    {
449        static $checked = array();
450        foreach ($plugins as $plugin) {
451            $name = join('::', (array)$plugin[ 'function' ]);
452            if (!isset($checked[ $name ])) {
453                if (!is_callable($plugin[ 'function' ])) {
454                    if (is_file($plugin[ 'file' ])) {
455                        include_once $plugin[ 'file' ];
456                        if (is_callable($plugin[ 'function' ])) {
457                            $checked[ $name ] = true;
458                        }
459                    }
460                } else {
461                    $checked[ $name ] = true;
462                }
463            }
464            if (!isset($checked[ $name ])) {
465                if (false !== $this->smarty->loadPlugin($name)) {
466                    $checked[ $name ] = true;
467                } else {
468                    throw new SmartyException("Plugin '{$name}' not callable");
469                }
470            }
471        }
472    }
473
474    /**
475     * This function is executed automatically when a compiled or cached template file is included
476     * - Decode saved properties from compiled template and cache files
477     * - Check if compiled or cache file is valid
478     *
479     * @param \Smarty_Internal_Template $tpl
480     * @param array                     $properties special template properties
481     * @param bool                      $cache      flag if called from cache file
482     *
483     * @return bool flag if compiled or cache file is valid
484     * @throws \SmartyException
485     */
486    public function _decodeProperties(Smarty_Internal_Template $tpl, $properties, $cache = false)
487    {
488        // on cache resources other than file check version stored in cache code
489        if (!isset($properties[ 'version' ]) || Smarty::SMARTY_VERSION !== $properties[ 'version' ]) {
490            if ($cache) {
491                $tpl->smarty->clearAllCache();
492            } else {
493                $tpl->smarty->clearCompiledTemplate();
494            }
495            return false;
496        }
497        $is_valid = true;
498        if (!empty($properties[ 'file_dependency' ])
499            && ((!$cache && $tpl->compile_check) || $tpl->compile_check === Smarty::COMPILECHECK_ON)
500        ) {
501            // check file dependencies at compiled code
502            foreach ($properties[ 'file_dependency' ] as $_file_to_check) {
503                if ($_file_to_check[ 2 ] === 'file' || $_file_to_check[ 2 ] === 'php') {
504                    if ($tpl->source->filepath === $_file_to_check[ 0 ]) {
505                        // do not recheck current template
506                        continue;
507                        //$mtime = $tpl->source->getTimeStamp();
508                    } else {
509                        // file and php types can be checked without loading the respective resource handlers
510                        $mtime = is_file($_file_to_check[ 0 ]) ? filemtime($_file_to_check[ 0 ]) : false;
511                    }
512                } else {
513                    $handler = Smarty_Resource::load($tpl->smarty, $_file_to_check[ 2 ]);
514                    if ($handler->checkTimestamps()) {
515                        $source = Smarty_Template_Source::load($tpl, $tpl->smarty, $_file_to_check[ 0 ]);
516                        $mtime = $source->getTimeStamp();
517                    } else {
518                        continue;
519                    }
520                }
521                if ($mtime === false || $mtime > $_file_to_check[ 1 ]) {
522                    $is_valid = false;
523                    break;
524                }
525            }
526        }
527        if ($cache) {
528            // CACHING_LIFETIME_SAVED cache expiry has to be validated here since otherwise we'd define the unifunc
529            if ($tpl->caching === Smarty::CACHING_LIFETIME_SAVED && $properties[ 'cache_lifetime' ] >= 0
530                && (time() > ($tpl->cached->timestamp + $properties[ 'cache_lifetime' ]))
531            ) {
532                $is_valid = false;
533            }
534            $tpl->cached->cache_lifetime = $properties[ 'cache_lifetime' ];
535            $tpl->cached->valid = $is_valid;
536            $resource = $tpl->cached;
537        } else {
538            $tpl->mustCompile = !$is_valid;
539            $resource = $tpl->compiled;
540            $resource->includes = isset($properties[ 'includes' ]) ? $properties[ 'includes' ] : array();
541        }
542        if ($is_valid) {
543            $resource->unifunc = $properties[ 'unifunc' ];
544            $resource->has_nocache_code = $properties[ 'has_nocache_code' ];
545            //            $tpl->compiled->nocache_hash = $properties['nocache_hash'];
546            $resource->file_dependency = $properties[ 'file_dependency' ];
547        }
548        return $is_valid && !function_exists($properties[ 'unifunc' ]);
549    }
550
551    /**
552     * Compiles the template
553     * If the template is not evaluated the compiled template is saved on disk
554     *
555     * @throws \Exception
556     */
557    public function compileTemplateSource()
558    {
559        return $this->compiled->compileTemplateSource($this);
560    }
561
562    /**
563     * Writes the content to cache resource
564     *
565     * @param string $content
566     *
567     * @return bool
568     */
569    public function writeCachedContent($content)
570    {
571        return $this->smarty->ext->_updateCache->writeCachedContent($this, $content);
572    }
573
574    /**
575     * Get unique template id
576     *
577     * @return string
578     * @throws \SmartyException
579     */
580    public function _getTemplateId()
581    {
582        return isset($this->templateId) ? $this->templateId : $this->templateId =
583            $this->smarty->_getTemplateId($this->template_resource, $this->cache_id, $this->compile_id);
584    }
585
586    /**
587     * runtime error not matching capture tags
588     *
589     * @throws \SmartyException
590     */
591    public function capture_error()
592    {
593        throw new SmartyException("Not matching {capture} open/close in '{$this->template_resource}'");
594    }
595
596    /**
597     * Load compiled object
598     *
599     * @param bool $force force new compiled object
600     */
601    public function loadCompiled($force = false)
602    {
603        if ($force || !isset($this->compiled)) {
604            $this->compiled = Smarty_Template_Compiled::load($this);
605        }
606    }
607
608    /**
609     * Load cached object
610     *
611     * @param bool $force force new cached object
612     */
613    public function loadCached($force = false)
614    {
615        if ($force || !isset($this->cached)) {
616            $this->cached = Smarty_Template_Cached::load($this);
617        }
618    }
619
620    /**
621     * Load inheritance object
622     */
623    public function _loadInheritance()
624    {
625        if (!isset($this->inheritance)) {
626            $this->inheritance = new Smarty_Internal_Runtime_Inheritance();
627        }
628    }
629
630    /**
631     * Unload inheritance object
632     */
633    public function _cleanUp()
634    {
635        $this->startRenderCallbacks = array();
636        $this->endRenderCallbacks = array();
637        $this->inheritance = null;
638    }
639
640    /**
641     * Load compiler object
642     *
643     * @throws \SmartyException
644     */
645    public function loadCompiler()
646    {
647        if (!class_exists($this->source->compiler_class)) {
648            $this->smarty->loadPlugin($this->source->compiler_class);
649        }
650        $this->compiler =
651            new $this->source->compiler_class(
652                $this->source->template_lexer_class,
653                $this->source->template_parser_class,
654                $this->smarty
655            );
656    }
657
658    /**
659     * Handle unknown class methods
660     *
661     * @param string $name unknown method-name
662     * @param array  $args argument array
663     *
664     * @return mixed
665     */
666    public function __call($name, $args)
667    {
668        // method of Smarty object?
669        if (method_exists($this->smarty, $name)) {
670            return call_user_func_array(array($this->smarty, $name), $args);
671        }
672        // parent
673        return parent::__call($name, $args);
674    }
675
676    /**
677     * get Smarty property in template context
678     *
679     * @param string $property_name property name
680     *
681     * @return mixed|Smarty_Template_Cached
682     * @throws SmartyException
683     */
684    public function __get($property_name)
685    {
686        switch ($property_name) {
687            case 'compiled':
688                $this->loadCompiled();
689                return $this->compiled;
690            case 'cached':
691                $this->loadCached();
692                return $this->cached;
693            case 'compiler':
694                $this->loadCompiler();
695                return $this->compiler;
696            default:
697                // Smarty property ?
698                if (property_exists($this->smarty, $property_name)) {
699                    return $this->smarty->$property_name;
700                }
701        }
702        throw new SmartyException("template property '$property_name' does not exist.");
703    }
704
705    /**
706     * set Smarty property in template context
707     *
708     * @param string $property_name property name
709     * @param mixed  $value         value
710     *
711     * @throws SmartyException
712     */
713    public function __set($property_name, $value)
714    {
715        switch ($property_name) {
716            case 'compiled':
717            case 'cached':
718            case 'compiler':
719                $this->$property_name = $value;
720                return;
721            default:
722                // Smarty property ?
723                if (property_exists($this->smarty, $property_name)) {
724                    $this->smarty->$property_name = $value;
725                    return;
726                }
727        }
728        throw new SmartyException("invalid template property '$property_name'.");
729    }
730
731    /**
732     * Template data object destructor
733     */
734    public function __destruct()
735    {
736        if ($this->smarty->cache_locking && isset($this->cached) && $this->cached->is_locked) {
737            $this->cached->handler->releaseLock($this->smarty, $this->cached);
738        }
739    }
740}
741