3 * Smarty Resource Plugin
6 * @subpackage TemplateResources
11 * Smarty Resource Plugin
12 * Base implementation for resource plugins
15 * @subpackage TemplateResources
17 abstract class Smarty_Resource
20 * cache for Smarty_Template_Source instances
24 public static $sources = array();
26 * cache for Smarty_Template_Compiled instances
30 public static $compileds = array();
32 * cache for Smarty_Resource instances
36 public static $resources = array();
38 * resource types provided by the core
42 protected static $sysplugins = array(
52 * Name of the Class to compile this resource's contents with
56 public $compiler_class = 'Smarty_Internal_SmartyTemplateCompiler';
59 * Name of the Class to tokenize this resource's contents with
63 public $template_lexer_class = 'Smarty_Internal_Templatelexer';
66 * Name of the Class to parse this resource's contents with
70 public $template_parser_class = 'Smarty_Internal_Templateparser';
73 * Load template's source into current template object
74 * {@internal The loaded source is assigned to $_template->source->content directly.}}
76 * @param Smarty_Template_Source $source source object
78 * @return string template source
79 * @throws SmartyException if source cannot be loaded
81 abstract public function getContent(Smarty_Template_Source $source);
84 * populate Source Object with meta data from Resource
86 * @param Smarty_Template_Source $source source object
87 * @param Smarty_Internal_Template $_template template object
89 abstract public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null);
92 * populate Source Object with timestamp and exists from Resource
94 * @param Smarty_Template_Source $source source object
96 public function populateTimestamp(Smarty_Template_Source $source)
98 // intentionally left blank
102 * modify resource_name according to resource handlers specifications
104 * @param Smarty $smarty Smarty instance
105 * @param string $resource_name resource_name to make unique
106 * @param boolean $is_config flag for config resource
108 * @return string unique resource name
110 protected function buildUniqueResourceName(Smarty $smarty, $resource_name, $is_config = false)
113 return get_class($this) . '#' . $smarty->joined_config_dir . '#' . $resource_name;
115 return get_class($this) . '#' . $smarty->joined_template_dir . '#' . $resource_name;
120 * populate Compiled Object with compiled filepath
122 * @param Smarty_Template_Compiled $compiled compiled object
123 * @param Smarty_Internal_Template $_template template object
125 public function populateCompiledFilepath(Smarty_Template_Compiled $compiled, Smarty_Internal_Template $_template)
127 $_compile_id = isset($_template->compile_id) ? preg_replace('![^\w\|]+!', '_', $_template->compile_id) : null;
128 $_filepath = $compiled->source->uid;
129 // if use_sub_dirs, break file into directories
130 if ($_template->smarty->use_sub_dirs) {
131 $_filepath = substr($_filepath, 0, 2) . DS
132 . substr($_filepath, 2, 2) . DS
133 . substr($_filepath, 4, 2) . DS
136 $_compile_dir_sep = $_template->smarty->use_sub_dirs ? DS : '^';
137 if (isset($_compile_id)) {
138 $_filepath = $_compile_id . $_compile_dir_sep . $_filepath;
141 if ($_template->caching) {
146 $_compile_dir = $_template->smarty->getCompileDir();
147 // set basename if not specified
148 $_basename = $this->getBasename($compiled->source);
149 if ($_basename === null) {
150 $_basename = basename(preg_replace('![^\w\/]+!', '_', $compiled->source->name));
152 // separate (optional) basename by dot
154 $_basename = '.' . $_basename;
157 $compiled->filepath = $_compile_dir . $_filepath . '.' . $compiled->source->type . $_basename . $_cache . '.php';
161 * Normalize Paths "foo/../bar" to "bar"
163 * @param string $_path path to normalize
164 * @param boolean $ds respect windows directory separator
166 * @return string normalized path
168 protected function normalizePath($_path, $ds = true)
171 // don't we all just love windows?
172 $_path = str_replace('\\', '/', $_path);
178 $_path = preg_replace('#/\./(\./)*#', '/', $_path);
181 $_parent = strpos($_path, '/../', $offset);
184 } elseif ($_path[$_parent - 1] === '.') {
185 $offset = $_parent + 3;
189 $_pos = strrpos($_path, '/', $_parent - strlen($_path) - 1);
190 if ($_pos === false) {
191 // don't we all just love windows?
195 $_path = substr_replace($_path, '', $_pos, $_parent + 3 - $_pos);
198 if ($ds && DS != '/') {
199 // don't we all just love windows?
200 $_path = str_replace('/', '\\', $_path);
207 * build template filepath by traversing the template_dir array
209 * @param Smarty_Template_Source $source source object
210 * @param Smarty_Internal_Template $_template template object
212 * @return string fully qualified filepath
213 * @throws SmartyException if default template handler is registered but not callable
215 protected function buildFilepath(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
217 $file = $source->name;
218 if ($source instanceof Smarty_Config_Source) {
219 $_directories = $source->smarty->getConfigDir();
220 $_default_handler = $source->smarty->default_config_handler_func;
222 $_directories = $source->smarty->getTemplateDir();
223 $_default_handler = $source->smarty->default_template_handler_func;
226 // go relative to a given template?
227 $_file_is_dotted = $file[0] == '.' && ($file[1] == '.' || $file[1] == '/' || $file[1] == "\\");
228 if ($_template && $_template->parent instanceof Smarty_Internal_Template && $_file_is_dotted) {
229 if ($_template->parent->source->type != 'file' && $_template->parent->source->type != 'extends' && !$_template->parent->allow_relative_path) {
230 throw new SmartyException("Template '{$file}' cannot be relative to template of resource type '{$_template->parent->source->type}'");
232 $file = dirname($_template->parent->source->filepath) . DS . $file;
233 $_file_exact_match = true;
234 if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $file)) {
235 // the path gained from the parent template is relative to the current working directory
236 // as expansions (like include_path) have already been done
237 $file = getcwd() . DS . $file;
241 // resolve relative path
242 if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $file)) {
243 // don't we all just love windows?
244 $_path = DS . trim($file, '/');
245 $_was_relative = true;
247 // don't we all just love windows?
248 $_path = str_replace('\\', '/', $file);
250 $_path = $this->normalizePath($_path, false);
252 // don't we all just love windows?
253 $_path = str_replace('/', '\\', $_path);
255 // revert to relative
256 if (isset($_was_relative)) {
257 $_path = substr($_path, 1);
260 // this is only required for directories
261 $file = rtrim($_path, '/\\');
263 // files relative to a template only get one shot
264 if (isset($_file_exact_match)) {
265 return $this->fileExists($source, $file) ? $file : false;
268 // template_dir index?
269 if (preg_match('#^\[(?P<key>[^\]]+)\](?P<file>.+)$#', $file, $match)) {
271 // try string indexes
272 if (isset($_directories[$match['key']])) {
273 $_directory = $_directories[$match['key']];
274 } elseif (is_numeric($match['key'])) {
276 $match['key'] = (int) $match['key'];
277 if (isset($_directories[$match['key']])) {
278 $_directory = $_directories[$match['key']];
280 // try at location index
281 $keys = array_keys($_directories);
282 $_directory = $_directories[$keys[$match['key']]];
287 $_file = substr($file, strpos($file, ']') + 1);
288 $_filepath = $_directory . $_file;
289 if ($this->fileExists($source, $_filepath)) {
295 $_stream_resolve_include_path = function_exists('stream_resolve_include_path');
297 // relative file name?
298 if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $file)) {
299 foreach ($_directories as $_directory) {
300 $_filepath = $_directory . $file;
301 if ($this->fileExists($source, $_filepath)) {
302 return $this->normalizePath($_filepath);
304 if ($source->smarty->use_include_path && !preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $_directory)) {
305 // try PHP include_path
306 if ($_stream_resolve_include_path) {
307 $_filepath = stream_resolve_include_path($_filepath);
309 $_filepath = Smarty_Internal_Get_Include_Path::getIncludePath($_filepath);
312 if ($_filepath !== false) {
313 if ($this->fileExists($source, $_filepath)) {
314 return $this->normalizePath($_filepath);
321 // try absolute filepath
322 if ($this->fileExists($source, $file)) {
327 if ($_default_handler) {
328 if (!is_callable($_default_handler)) {
329 if ($source instanceof Smarty_Config_Source) {
330 throw new SmartyException("Default config handler not callable");
332 throw new SmartyException("Default template handler not callable");
335 $_return = call_user_func_array($_default_handler,
336 array($source->type, $source->name, &$_content, &$_timestamp, $source->smarty));
337 if (is_string($_return)) {
338 $source->timestamp = @filemtime($_return);
339 $source->exists = !!$source->timestamp;
342 } elseif ($_return === true) {
343 $source->content = $_content;
344 $source->timestamp = $_timestamp;
345 $source->exists = true;
356 * test is file exists and save timestamp
358 * @param Smarty_Template_Source $source source object
359 * @param string $file file name
361 * @return bool true if file exists
363 protected function fileExists(Smarty_Template_Source $source, $file)
365 $source->timestamp = is_file($file) ? @filemtime($file) : false;
367 return $source->exists = !!$source->timestamp;
371 * Determine basename for compiled filename
373 * @param Smarty_Template_Source $source source object
375 * @return string resource's basename
377 protected function getBasename(Smarty_Template_Source $source)
383 * Load Resource Handler
385 * @param Smarty $smarty smarty object
386 * @param string $type name of the resource
388 * @throws SmartyException
389 * @return Smarty_Resource Resource Handler
391 public static function load(Smarty $smarty, $type)
393 // try smarty's cache
394 if (isset($smarty->_resource_handlers[$type])) {
395 return $smarty->_resource_handlers[$type];
398 // try registered resource
399 if (isset($smarty->registered_resources[$type])) {
400 if ($smarty->registered_resources[$type] instanceof Smarty_Resource) {
401 $smarty->_resource_handlers[$type] = $smarty->registered_resources[$type];
402 // note registered to smarty is not kept unique!
403 return $smarty->_resource_handlers[$type];
406 if (!isset(self::$resources['registered'])) {
407 self::$resources['registered'] = new Smarty_Internal_Resource_Registered();
409 if (!isset($smarty->_resource_handlers[$type])) {
410 $smarty->_resource_handlers[$type] = self::$resources['registered'];
413 return $smarty->_resource_handlers[$type];
416 // try sysplugins dir
417 if (isset(self::$sysplugins[$type])) {
418 if (!isset(self::$resources[$type])) {
419 $_resource_class = 'Smarty_Internal_Resource_' . ucfirst($type);
420 self::$resources[$type] = new $_resource_class();
423 return $smarty->_resource_handlers[$type] = self::$resources[$type];
427 $_resource_class = 'Smarty_Resource_' . ucfirst($type);
428 if ($smarty->loadPlugin($_resource_class)) {
429 if (isset(self::$resources[$type])) {
430 return $smarty->_resource_handlers[$type] = self::$resources[$type];
433 if (class_exists($_resource_class, false)) {
434 self::$resources[$type] = new $_resource_class();
436 return $smarty->_resource_handlers[$type] = self::$resources[$type];
438 $smarty->registerResource($type, array(
439 "smarty_resource_{$type}_source",
440 "smarty_resource_{$type}_timestamp",
441 "smarty_resource_{$type}_secure",
442 "smarty_resource_{$type}_trusted"
445 // give it another try, now that the resource is registered properly
446 return self::load($smarty, $type);
451 $_known_stream = stream_get_wrappers();
452 if (in_array($type, $_known_stream)) {
454 if (is_object($smarty->security_policy)) {
455 $smarty->security_policy->isTrustedStream($type);
457 if (!isset(self::$resources['stream'])) {
458 self::$resources['stream'] = new Smarty_Internal_Resource_Stream();
461 return $smarty->_resource_handlers[$type] = self::$resources['stream'];
464 // TODO: try default_(template|config)_handler
467 throw new SmartyException("Unknown resource type '{$type}'");
471 * extract resource_type and resource_name from template_resource and config_resource
472 * @note "C:/foo.tpl" was forced to file resource up till Smarty 3.1.3 (including).
474 * @param string $resource_name template_resource or config_resource to parse
475 * @param string $default_resource the default resource_type defined in $smarty
476 * @param string &$name the parsed resource name
477 * @param string &$type the parsed resource type
481 protected static function parseResourceName($resource_name, $default_resource, &$name, &$type)
483 $parts = explode(':', $resource_name, 2);
484 if (!isset($parts[1]) || !isset($parts[0][1])) {
485 // no resource given, use default
486 // or single character before the colon is not a resource type, but part of the filepath
487 $type = $default_resource;
488 $name = $resource_name;
496 * modify resource_name according to resource handlers specifications
498 * @param Smarty $smarty Smarty instance
499 * @param string $resource_name resource_name to make unique
501 * @return string unique resource name
505 * modify template_resource according to resource handlers specifications
507 * @param Smarty_Internal_template $template Smarty instance
508 * @param string $template_resource template_resource to extract resource handler and name of
510 * @return string unique resource name
512 public static function getUniqueTemplateName($template, $template_resource)
514 self::parseResourceName($template_resource, $template->smarty->default_resource_type, $name, $type);
515 // TODO: optimize for Smarty's internal resource types
516 $resource = Smarty_Resource::load($template->smarty, $type);
517 // go relative to a given template?
518 $_file_is_dotted = $name[0] == '.' && ($name[1] == '.' || $name[1] == '/' || $name[1] == "\\");
519 if ($template instanceof Smarty_Internal_Template && $_file_is_dotted && ($template->source->type == 'file' || $template->parent->source->type == 'extends')) {
520 $name = dirname($template->source->filepath) . DS . $name;
522 return $resource->buildUniqueResourceName($template->smarty, $name);
526 * initialize Source Object for given resource
527 * Either [$_template] or [$smarty, $template_resource] must be specified
529 * @param Smarty_Internal_Template $_template template object
530 * @param Smarty $smarty smarty object
531 * @param string $template_resource resource identifier
533 * @return Smarty_Template_Source Source Object
535 public static function source(Smarty_Internal_Template $_template = null, Smarty $smarty = null, $template_resource = null)
538 $smarty = $_template->smarty;
539 $template_resource = $_template->template_resource;
542 // parse resource_name, load resource handler, identify unique resource name
543 self::parseResourceName($template_resource, $smarty->default_resource_type, $name, $type);
544 $resource = Smarty_Resource::load($smarty, $type);
545 // go relative to a given template?
546 $_file_is_dotted = isset($name[0]) && $name[0] == '.' && ($name[1] == '.' || $name[1] == '/' || $name[1] == "\\");
547 if ($_file_is_dotted && isset($_template) && $_template->parent instanceof Smarty_Internal_Template && ($_template->parent->source->type == 'file' || $_template->parent->source->type == 'extends')) {
548 $name2 = dirname($_template->parent->source->filepath) . DS . $name;
552 $unique_resource_name = $resource->buildUniqueResourceName($smarty, $name2);
554 // check runtime cache
555 $_cache_key = 'template|' . $unique_resource_name;
556 if ($smarty->compile_id) {
557 $_cache_key .= '|' . $smarty->compile_id;
559 if (isset(self::$sources[$_cache_key])) {
560 return self::$sources[$_cache_key];
564 $source = new Smarty_Template_Source($resource, $smarty, $template_resource, $type, $name, $unique_resource_name);
565 $resource->populate($source, $_template);
568 self::$sources[$_cache_key] = $source;
574 * initialize Config Source Object for given resource
576 * @param Smarty_Internal_Config $_config config object
578 * @throws SmartyException
579 * @return Smarty_Config_Source Source Object
581 public static function config(Smarty_Internal_Config $_config)
583 static $_incompatible_resources = array('eval' => true, 'string' => true, 'extends' => true, 'php' => true);
584 $config_resource = $_config->config_resource;
585 $smarty = $_config->smarty;
587 // parse resource_name
588 self::parseResourceName($config_resource, $smarty->default_config_type, $name, $type);
590 // make sure configs are not loaded via anything smarty can't handle
591 if (isset($_incompatible_resources[$type])) {
592 throw new SmartyException ("Unable to use resource '{$type}' for config");
595 // load resource handler, identify unique resource name
596 $resource = Smarty_Resource::load($smarty, $type);
597 $unique_resource_name = $resource->buildUniqueResourceName($smarty, $name, true);
599 // check runtime cache
600 $_cache_key = 'config|' . $unique_resource_name;
601 if (isset(self::$sources[$_cache_key])) {
602 return self::$sources[$_cache_key];
606 $source = new Smarty_Config_Source($resource, $smarty, $config_resource, $type, $name, $unique_resource_name);
607 $resource->populate($source, null);
610 self::$sources[$_cache_key] = $source;
617 * Smarty Resource Data Object
618 * Meta Data Container for Template Files
621 * @subpackage TemplateResources
622 * @author Rodney Rehm
623 * @property integer $timestamp Source Timestamp
624 * @property boolean $exists Source Existence
625 * @property boolean $template Extended Template reference
626 * @property string $content Source Content
628 class Smarty_Template_Source
631 * Name of the Class to compile this resource's contents with
635 public $compiler_class = null;
638 * Name of the Class to tokenize this resource's contents with
642 public $template_lexer_class = null;
645 * Name of the Class to parse this resource's contents with
649 public $template_parser_class = null;
659 * Template Resource (Smarty_Internal_Template::$template_resource)
663 public $resource = null;
680 * Unique Resource Name
684 public $unique_resource = null;
691 public $filepath = null;
694 * Source is bypassing compiler
698 public $uncompiled = null;
701 * Source must be recompiled on every occasion
705 public $recompiled = null;
708 * The Components an extended template is made of
712 public $components = null;
717 * @var Smarty_Resource
719 public $handler = null;
726 public $smarty = null;
729 * create Source Object container
731 * @param Smarty_Resource $handler Resource Handler this source object communicates with
732 * @param Smarty $smarty Smarty instance this source object belongs to
733 * @param string $resource full template_resource
734 * @param string $type type of resource
735 * @param string $name resource name
736 * @param string $unique_resource unique resource name
738 public function __construct(Smarty_Resource $handler, Smarty $smarty, $resource, $type, $name, $unique_resource)
740 $this->handler = $handler; // Note: prone to circular references
742 $this->compiler_class = $handler->compiler_class;
743 $this->template_lexer_class = $handler->template_lexer_class;
744 $this->template_parser_class = $handler->template_parser_class;
745 $this->uncompiled = $this->handler instanceof Smarty_Resource_Uncompiled;
746 $this->recompiled = $this->handler instanceof Smarty_Resource_Recompiled;
748 $this->smarty = $smarty;
749 $this->resource = $resource;
752 $this->unique_resource = $unique_resource;
756 * get a Compiled Object of this source
758 * @param Smarty_Internal_Template|Smarty_Internal_Config $_template template object
760 * @return Smarty_Template_Compiled compiled object
762 public function getCompiled($_template)
764 // check runtime cache
765 $_cache_key = $this->unique_resource . '#' . $_template->compile_id;
766 if (isset(Smarty_Resource::$compileds[$_cache_key])) {
767 return Smarty_Resource::$compileds[$_cache_key];
770 $compiled = new Smarty_Template_Compiled($this);
771 $this->handler->populateCompiledFilepath($compiled, $_template);
772 $compiled->timestamp = @filemtime($compiled->filepath);
773 $compiled->exists = !!$compiled->timestamp;
776 Smarty_Resource::$compileds[$_cache_key] = $compiled;
782 * render the uncompiled source
784 * @param Smarty_Internal_Template $_template template object
786 public function renderUncompiled(Smarty_Internal_Template $_template)
788 return $this->handler->renderUncompiled($this, $_template);
792 * <<magic>> Generic Setter.
794 * @param string $property_name valid: timestamp, exists, content, template
795 * @param mixed $value new value (is not checked)
797 * @throws SmartyException if $property_name is not valid
799 public function __set($property_name, $value)
801 switch ($property_name) {
802 // regular attributes
806 // required for extends: only
808 $this->$property_name = $value;
812 throw new SmartyException("invalid source property '$property_name'.");
817 * <<magic>> Generic getter.
819 * @param string $property_name valid: timestamp, exists, content
822 * @throws SmartyException if $property_name is not valid
824 public function __get($property_name)
826 switch ($property_name) {
829 $this->handler->populateTimestamp($this);
831 return $this->$property_name;
834 return $this->content = $this->handler->getContent($this);
837 throw new SmartyException("source property '$property_name' does not exist.");
843 * Smarty Resource Data Object
844 * Meta Data Container for Template Files
847 * @subpackage TemplateResources
848 * @author Rodney Rehm
849 * @property string $content compiled content
851 class Smarty_Template_Compiled
858 public $filepath = null;
865 public $timestamp = null;
872 public $exists = false;
875 * Compiled Content Loaded
879 public $loaded = false;
882 * Template was compiled
886 public $isCompiled = false;
891 * @var Smarty_Template_Source
893 public $source = null;
896 * Metadata properties
897 * populated by Smarty_Internal_Template::decodeProperties()
901 public $_properties = null;
904 * create Compiled Object container
906 * @param Smarty_Template_Source $source source object this compiled object belongs to
908 public function __construct(Smarty_Template_Source $source)
910 $this->source = $source;