3 * Smarty Resource Plugin
6 * @subpackage TemplateResources
11 * Smarty Resource Plugin
13 * Base implementation for resource plugins
16 * @subpackage TemplateResources
18 abstract class Smarty_Resource {
20 * cache for Smarty_Template_Source instances
23 public static $sources = array();
25 * cache for Smarty_Template_Compiled instances
28 public static $compileds = array();
30 * cache for Smarty_Resource instances
33 public static $resources = array();
35 * resource types provided by the core
38 protected static $sysplugins = array(
48 * Name of the Class to compile this resource's contents with
51 public $compiler_class = 'Smarty_Internal_SmartyTemplateCompiler';
54 * Name of the Class to tokenize this resource's contents with
57 public $template_lexer_class = 'Smarty_Internal_Templatelexer';
60 * Name of the Class to parse this resource's contents with
63 public $template_parser_class = 'Smarty_Internal_Templateparser';
66 * Load template's source into current template object
68 * {@internal The loaded source is assigned to $_template->source->content directly.}}
70 * @param Smarty_Template_Source $source source object
71 * @return string template source
72 * @throws SmartyException if source cannot be loaded
74 public abstract function getContent(Smarty_Template_Source $source);
77 * populate Source Object with meta data from Resource
79 * @param Smarty_Template_Source $source source object
80 * @param Smarty_Internal_Template $_template template object
82 public abstract function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template=null);
85 * populate Source Object with timestamp and exists from Resource
87 * @param Smarty_Template_Source $source source object
89 public function populateTimestamp(Smarty_Template_Source $source)
91 // intentionally left blank
96 * modify resource_name according to resource handlers specifications
98 * @param Smarty $smarty Smarty instance
99 * @param string $resource_name resource_name to make unique
100 * @return string unique resource name
102 protected function buildUniqueResourceName(Smarty $smarty, $resource_name)
104 return get_class($this) . '#' . $smarty->joined_template_dir . '#' . $resource_name;
108 * populate Compiled Object with compiled filepath
110 * @param Smarty_Template_Compiled $compiled compiled object
111 * @param Smarty_Internal_Template $_template template object
113 public function populateCompiledFilepath(Smarty_Template_Compiled $compiled, Smarty_Internal_Template $_template)
115 $_compile_id = isset($_template->compile_id) ? preg_replace('![^\w\|]+!', '_', $_template->compile_id) : null;
116 $_filepath = $compiled->source->uid;
117 // if use_sub_dirs, break file into directories
118 if ($_template->smarty->use_sub_dirs) {
119 $_filepath = substr($_filepath, 0, 2) . DS
120 . substr($_filepath, 2, 2) . DS
121 . substr($_filepath, 4, 2) . DS
124 $_compile_dir_sep = $_template->smarty->use_sub_dirs ? DS : '^';
125 if (isset($_compile_id)) {
126 $_filepath = $_compile_id . $_compile_dir_sep . $_filepath;
129 if ($_template->caching) {
134 $_compile_dir = $_template->smarty->getCompileDir();
135 // set basename if not specified
136 $_basename = $this->getBasename($compiled->source);
137 if ($_basename === null) {
138 $_basename = basename( preg_replace('![^\w\/]+!', '_', $compiled->source->name) );
140 // separate (optional) basename by dot
142 $_basename = '.' . $_basename;
145 $compiled->filepath = $_compile_dir . $_filepath . '.' . $compiled->source->type . $_basename . $_cache . '.php';
149 * Normalize Paths "foo/../bar" to "bar"
151 * @param string $_path path to normalize
152 * @param boolean $ds respect windows directory separator
153 * @return string normalized path
155 protected function normalizePath($_path, $ds=true)
158 // don't we all just love windows?
159 $_path = str_replace('\\', '/', $_path);
163 $_path = preg_replace('#(/\./(\./)*)|/{2,}#', '/', $_path);
166 $_parent = strpos($_path, '/../');
167 if ($_parent === false) {
169 } else if ($_parent === 0) {
170 $_path = substr($_path, 3);
174 $_pos = strrpos($_path, '/', $_parent - strlen($_path) - 1);
175 if ($_pos === false) {
176 // don't we all just love windows?
180 $_path = substr_replace($_path, '', $_pos, $_parent + 3 - $_pos);
183 if ($ds && DS != '/') {
184 // don't we all just love windows?
185 $_path = str_replace('/', '\\', $_path);
192 * build template filepath by traversing the template_dir array
194 * @param Smarty_Template_Source $source source object
195 * @param Smarty_Internal_Template $_template template object
196 * @return string fully qualified filepath
197 * @throws SmartyException if default template handler is registered but not callable
199 protected function buildFilepath(Smarty_Template_Source $source, Smarty_Internal_Template $_template=null)
201 $file = $source->name;
202 if ($source instanceof Smarty_Config_Source) {
203 $_directories = $source->smarty->getConfigDir();
204 $_default_handler = $source->smarty->default_config_handler_func;
206 $_directories = $source->smarty->getTemplateDir();
207 $_default_handler = $source->smarty->default_template_handler_func;
210 // go relative to a given template?
211 $_file_is_dotted = $file[0] == '.' && ($file[1] == '.' || $file[1] == '/' || $file[1] == "\\");
212 if ($_template && $_template->parent instanceof Smarty_Internal_Template && $_file_is_dotted) {
213 if ($_template->parent->source->type != 'file' && $_template->parent->source->type != 'extends' && !$_template->parent->allow_relative_path) {
214 throw new SmartyException("Template '{$file}' cannot be relative to template of resource type '{$_template->parent->source->type}'");
216 $file = dirname($_template->parent->source->filepath) . DS . $file;
217 $_file_exact_match = true;
218 if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $file)) {
219 // the path gained from the parent template is relative to the current working directory
220 // as expansions (like include_path) have already been done
221 $file = getcwd() . DS . $file;
225 // resolve relative path
226 if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $file)) {
227 // don't we all just love windows?
228 $_path = str_replace('\\', '/', $file);
229 $_was_relative_prefix = $file[0] == '.' ? substr($file, 0, strpos($_path, '/')) : null;
230 $_path = DS . trim($file, '/');
231 $_was_relative = true;
233 // don't we all just love windows?
234 $_path = str_replace('\\', '/', $file);
236 $_path = $this->normalizePath($_path, false);
238 // don't we all just love windows?
239 $_path = str_replace('/', '\\', $_path);
241 // revert to relative
242 if (isset($_was_relative)) {
243 if (isset($_was_relative_prefix)){
244 $_path = $_was_relative_prefix . $_path;
246 $_path = substr($_path, 1);
250 // this is only required for directories
251 $file = rtrim($_path, '/\\');
253 // files relative to a template only get one shot
254 if (isset($_file_exact_match)) {
255 return $this->fileExists($source, $file) ? $file : false;
258 // template_dir index?
259 if (preg_match('#^\[(?P<key>[^\]]+)\](?P<file>.+)$#', $file, $match)) {
261 // try string indexes
262 if (isset($_directories[$match['key']])) {
263 $_directory = $_directories[$match['key']];
264 } else if (is_numeric($match['key'])) {
266 $match['key'] = (int) $match['key'];
267 if (isset($_directories[$match['key']])) {
268 $_directory = $_directories[$match['key']];
270 // try at location index
271 $keys = array_keys($_directories);
272 $_directory = $_directories[$keys[$match['key']]];
277 $_file = substr($file, strpos($file, ']') + 1);
278 $_filepath = $_directory . $_file;
279 if ($this->fileExists($source, $_filepath)) {
285 $_stream_resolve_include_path = function_exists('stream_resolve_include_path');
287 // relative file name?
288 if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $file)) {
289 foreach ($_directories as $_directory) {
290 $_filepath = $_directory . $file;
291 if ($this->fileExists($source, $_filepath)) {
292 return $this->normalizePath($_filepath);
294 if ($source->smarty->use_include_path && !preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $_directory)) {
295 // try PHP include_path
296 if ($_stream_resolve_include_path) {
297 $_filepath = stream_resolve_include_path($_filepath);
299 $_filepath = Smarty_Internal_Get_Include_Path::getIncludePath($_filepath);
302 if ($_filepath !== false) {
303 if ($this->fileExists($source, $_filepath)) {
304 return $this->normalizePath($_filepath);
311 // try absolute filepath
312 if ($this->fileExists($source, $file)) {
317 if ($_default_handler) {
318 if (!is_callable($_default_handler)) {
319 if ($source instanceof Smarty_Config_Source) {
320 throw new SmartyException("Default config handler not callable");
322 throw new SmartyException("Default template handler not callable");
325 $_return = call_user_func_array($_default_handler,
326 array($source->type, $source->name, &$_content, &$_timestamp, $source->smarty));
327 if (is_string($_return)) {
328 $source->timestamp = @filemtime($_return);
329 $source->exists = !!$source->timestamp;
331 } elseif ($_return === true) {
332 $source->content = $_content;
333 $source->timestamp = $_timestamp;
334 $source->exists = true;
344 * test is file exists and save timestamp
346 * @param Smarty_Template_Source $source source object
347 * @param string $file file name
348 * @return bool true if file exists
350 protected function fileExists(Smarty_Template_Source $source, $file)
352 $source->timestamp = @filemtime($file);
353 return $source->exists = !!$source->timestamp;
358 * Determine basename for compiled filename
360 * @param Smarty_Template_Source $source source object
361 * @return string resource's basename
363 protected function getBasename(Smarty_Template_Source $source)
369 * Load Resource Handler
371 * @param Smarty $smarty smarty object
372 * @param string $type name of the resource
373 * @return Smarty_Resource Resource Handler
375 public static function load(Smarty $smarty, $type)
377 // try smarty's cache
378 if (isset($smarty->_resource_handlers[$type])) {
379 return $smarty->_resource_handlers[$type];
382 // try registered resource
383 if (isset($smarty->registered_resources[$type])) {
384 if ($smarty->registered_resources[$type] instanceof Smarty_Resource) {
385 $smarty->_resource_handlers[$type] = $smarty->registered_resources[$type];
386 // note registered to smarty is not kept unique!
387 return $smarty->_resource_handlers[$type];
390 if (!isset(self::$resources['registered'])) {
391 self::$resources['registered'] = new Smarty_Internal_Resource_Registered();
393 if (!isset($smarty->_resource_handlers[$type])) {
394 $smarty->_resource_handlers[$type] = self::$resources['registered'];
397 return $smarty->_resource_handlers[$type];
400 // try sysplugins dir
401 if (isset(self::$sysplugins[$type])) {
402 if (!isset(self::$resources[$type])) {
403 $_resource_class = 'Smarty_Internal_Resource_' . ucfirst($type);
404 self::$resources[$type] = new $_resource_class();
406 return $smarty->_resource_handlers[$type] = self::$resources[$type];
410 $_resource_class = 'Smarty_Resource_' . ucfirst($type);
411 if ($smarty->loadPlugin($_resource_class)) {
412 if (isset(self::$resources[$type])) {
413 return $smarty->_resource_handlers[$type] = self::$resources[$type];
416 if (class_exists($_resource_class, false)) {
417 self::$resources[$type] = new $_resource_class();
418 return $smarty->_resource_handlers[$type] = self::$resources[$type];
420 $smarty->registerResource($type, array(
421 "smarty_resource_{$type}_source",
422 "smarty_resource_{$type}_timestamp",
423 "smarty_resource_{$type}_secure",
424 "smarty_resource_{$type}_trusted"
427 // give it another try, now that the resource is registered properly
428 return self::load($smarty, $type);
433 $_known_stream = stream_get_wrappers();
434 if (in_array($type, $_known_stream)) {
436 if (is_object($smarty->security_policy)) {
437 $smarty->security_policy->isTrustedStream($type);
439 if (!isset(self::$resources['stream'])) {
440 self::$resources['stream'] = new Smarty_Internal_Resource_Stream();
442 return $smarty->_resource_handlers[$type] = self::$resources['stream'];
445 // TODO: try default_(template|config)_handler
448 throw new SmartyException("Unkown resource type '{$type}'");
452 * extract resource_type and resource_name from template_resource and config_resource
454 * @note "C:/foo.tpl" was forced to file resource up till Smarty 3.1.3 (including).
455 * @param string $resource_name template_resource or config_resource to parse
456 * @param string $default_resource the default resource_type defined in $smarty
457 * @param string &$name the parsed resource name
458 * @param string &$type the parsed resource type
461 protected static function parseResourceName($resource_name, $default_resource, &$name, &$type)
463 $parts = explode(':', $resource_name, 2);
464 if (!isset($parts[1]) || !isset($parts[0][1])) {
465 // no resource given, use default
466 // or single character before the colon is not a resource type, but part of the filepath
467 $type = $default_resource;
468 $name = $resource_name;
477 * modify resource_name according to resource handlers specifications
479 * @param Smarty $smarty Smarty instance
480 * @param string $resource_name resource_name to make unique
481 * @return string unique resource name
485 * modify template_resource according to resource handlers specifications
487 * @param string $smarty Smarty instance
488 * @param string $template_resource template_resource to extracate resource handler and name of
489 * @return string unique resource name
491 public static function getUniqueTemplateName($smarty, $template_resource)
493 self::parseResourceName($template_resource, $smarty->default_resource_type, $name, $type);
494 // TODO: optimize for Smarty's internal resource types
495 $resource = Smarty_Resource::load($smarty, $type);
496 return $resource->buildUniqueResourceName($smarty, $name);
500 * initialize Source Object for given resource
502 * Either [$_template] or [$smarty, $template_resource] must be specified
504 * @param Smarty_Internal_Template $_template template object
505 * @param Smarty $smarty smarty object
506 * @param string $template_resource resource identifier
507 * @return Smarty_Template_Source Source Object
509 public static function source(Smarty_Internal_Template $_template=null, Smarty $smarty=null, $template_resource=null)
512 $smarty = $_template->smarty;
513 $template_resource = $_template->template_resource;
516 // parse resource_name, load resource handler, identify unique resource name
517 self::parseResourceName($template_resource, $smarty->default_resource_type, $name, $type);
518 $resource = Smarty_Resource::load($smarty, $type);
519 $unique_resource_name = $resource->buildUniqueResourceName($smarty, $name);
521 // check runtime cache
522 $_cache_key = 'template|' . $unique_resource_name;
523 if ($smarty->compile_id) {
524 $_cache_key .= '|'.$smarty->compile_id;
526 if (isset(self::$sources[$_cache_key])) {
527 return self::$sources[$_cache_key];
531 $source = new Smarty_Template_Source($resource, $smarty, $template_resource, $type, $name, $unique_resource_name);
532 $resource->populate($source, $_template);
535 self::$sources[$_cache_key] = $source;
540 * initialize Config Source Object for given resource
542 * @param Smarty_Internal_Config $_config config object
543 * @return Smarty_Config_Source Source Object
545 public static function config(Smarty_Internal_Config $_config)
547 static $_incompatible_resources = array('eval' => true, 'string' => true, 'extends' => true, 'php' => true);
548 $config_resource = $_config->config_resource;
549 $smarty = $_config->smarty;
551 // parse resource_name
552 self::parseResourceName($config_resource, $smarty->default_config_type, $name, $type);
554 // make sure configs are not loaded via anything smarty can't handle
555 if (isset($_incompatible_resources[$type])) {
556 throw new SmartyException ("Unable to use resource '{$type}' for config");
559 // load resource handler, identify unique resource name
560 $resource = Smarty_Resource::load($smarty, $type);
561 $unique_resource_name = $resource->buildUniqueResourceName($smarty, $name);
563 // check runtime cache
564 $_cache_key = 'config|' . $unique_resource_name;
565 if (isset(self::$sources[$_cache_key])) {
566 return self::$sources[$_cache_key];
570 $source = new Smarty_Config_Source($resource, $smarty, $config_resource, $type, $name, $unique_resource_name);
571 $resource->populate($source, null);
574 self::$sources[$_cache_key] = $source;
581 * Smarty Resource Data Object
583 * Meta Data Container for Template Files
586 * @subpackage TemplateResources
587 * @author Rodney Rehm
589 * @property integer $timestamp Source Timestamp
590 * @property boolean $exists Source Existance
591 * @property boolean $template Extended Template reference
592 * @property string $content Source Content
594 class Smarty_Template_Source {
597 * Name of the Class to compile this resource's contents with
600 public $compiler_class = null;
603 * Name of the Class to tokenize this resource's contents with
606 public $template_lexer_class = null;
609 * Name of the Class to parse this resource's contents with
612 public $template_parser_class = null;
621 * Template Resource (Smarty_Internal_Template::$template_resource)
624 public $resource = null;
639 * Unique Resource Name
642 public $unique_resource = null;
648 public $filepath = null;
651 * Source is bypassing compiler
654 public $uncompiled = null;
657 * Source must be recompiled on every occasion
660 public $recompiled = null;
663 * The Components an extended template is made of
666 public $components = null;
670 * @var Smarty_Resource
672 public $handler = null;
678 public $smarty = null;
681 * create Source Object container
683 * @param Smarty_Resource $handler Resource Handler this source object communicates with
684 * @param Smarty $smarty Smarty instance this source object belongs to
685 * @param string $resource full template_resource
686 * @param string $type type of resource
687 * @param string $name resource name
688 * @param string $unique_resource unqiue resource name
690 public function __construct(Smarty_Resource $handler, Smarty $smarty, $resource, $type, $name, $unique_resource)
692 $this->handler = $handler; // Note: prone to circular references
694 $this->compiler_class = $handler->compiler_class;
695 $this->template_lexer_class = $handler->template_lexer_class;
696 $this->template_parser_class = $handler->template_parser_class;
697 $this->uncompiled = $this->handler instanceof Smarty_Resource_Uncompiled;
698 $this->recompiled = $this->handler instanceof Smarty_Resource_Recompiled;
700 $this->smarty = $smarty;
701 $this->resource = $resource;
704 $this->unique_resource = $unique_resource;
708 * get a Compiled Object of this source
710 * @param Smarty_Internal_Template $_template template objet
711 * @return Smarty_Template_Compiled compiled object
713 public function getCompiled(Smarty_Internal_Template $_template)
715 // check runtime cache
716 $_cache_key = $this->unique_resource . '#' . $_template->compile_id;
717 if (isset(Smarty_Resource::$compileds[$_cache_key])) {
718 return Smarty_Resource::$compileds[$_cache_key];
721 $compiled = new Smarty_Template_Compiled($this);
722 $this->handler->populateCompiledFilepath($compiled, $_template);
723 $compiled->timestamp = @filemtime($compiled->filepath);
724 $compiled->exists = !!$compiled->timestamp;
727 Smarty_Resource::$compileds[$_cache_key] = $compiled;
733 * render the uncompiled source
735 * @param Smarty_Internal_Template $_template template object
737 public function renderUncompiled(Smarty_Internal_Template $_template)
739 return $this->handler->renderUncompiled($this, $_template);
743 * <<magic>> Generic Setter.
745 * @param string $property_name valid: timestamp, exists, content, template
746 * @param mixed $value new value (is not checked)
747 * @throws SmartyException if $property_name is not valid
749 public function __set($property_name, $value)
751 switch ($property_name) {
752 // regular attributes
756 // required for extends: only
758 $this->$property_name = $value;
762 throw new SmartyException("invalid source property '$property_name'.");
767 * <<magic>> Generic getter.
769 * @param string $property_name valid: timestamp, exists, content
771 * @throws SmartyException if $property_name is not valid
773 public function __get($property_name)
775 switch ($property_name) {
778 $this->handler->populateTimestamp($this);
779 return $this->$property_name;
782 return $this->content = $this->handler->getContent($this);
785 throw new SmartyException("source property '$property_name' does not exist.");
792 * Smarty Resource Data Object
794 * Meta Data Container for Template Files
797 * @subpackage TemplateResources
798 * @author Rodney Rehm
800 * @property string $content compiled content
802 class Smarty_Template_Compiled {
808 public $filepath = null;
814 public $timestamp = null;
820 public $exists = false;
823 * Compiled Content Loaded
826 public $loaded = false;
829 * Template was compiled
832 public $isCompiled = false;
836 * @var Smarty_Template_Source
838 public $source = null;
841 * Metadata properties
843 * populated by Smarty_Internal_Template::decodeProperties()
846 public $_properties = null;
849 * create Compiled Object container
851 * @param Smarty_Template_Source $source source object this compiled object belongs to
853 public function __construct(Smarty_Template_Source $source)
855 $this->source = $source;