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: */
27: class 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: