* @author Uwe Tews * @author Rodney Rehm * @author Simon Wisselink */ /** * This is the main Smarty class */ class Smarty extends \Smarty\TemplateBase { /** * smarty version */ const SMARTY_VERSION = '5.5.2'; /** * define caching modes */ const CACHING_OFF = 0; const CACHING_LIFETIME_CURRENT = 1; const CACHING_LIFETIME_SAVED = 2; /** * define constant for clearing cache files be saved expiration dates */ const CLEAR_EXPIRED = -1; /** * define compile check modes */ const COMPILECHECK_OFF = 0; const COMPILECHECK_ON = 1; /** * filter types */ const FILTER_POST = 'post'; const FILTER_PRE = 'pre'; const FILTER_OUTPUT = 'output'; const FILTER_VARIABLE = 'variable'; /** * plugin types */ const PLUGIN_FUNCTION = 'function'; const PLUGIN_BLOCK = 'block'; const PLUGIN_COMPILER = 'compiler'; const PLUGIN_MODIFIER = 'modifier'; const PLUGIN_MODIFIERCOMPILER = 'modifiercompiler'; /** * The character set to adhere to (defaults to "UTF-8") */ public static $_CHARSET = 'UTF-8'; /** * The date format to be used internally * (accepts date() and strftime()) */ public static $_DATE_FORMAT = '%b %e, %Y'; /** * Flag denoting if PCRE should run in UTF-8 mode */ public static $_UTF8_MODIFIER = 'u'; /** * Flag denoting if operating system is windows */ public static $_IS_WINDOWS = false; /** * auto literal on delimiters with whitespace * * @var boolean */ public $auto_literal = true; /** * display error on not assigned variables * * @var boolean */ public $error_unassigned = false; /** * flag if template_dir is normalized * * @var bool */ public $_templateDirNormalized = false; /** * joined template directory string used in cache keys * * @var string */ public $_joined_template_dir = null; /** * flag if config_dir is normalized * * @var bool */ public $_configDirNormalized = false; /** * joined config directory string used in cache keys * * @var string */ public $_joined_config_dir = null; /** * default template handler * * @var callable */ public $default_template_handler_func = null; /** * default config handler * * @var callable */ public $default_config_handler_func = null; /** * default plugin handler * * @var callable */ private $default_plugin_handler_func = null; /** * flag if template_dir is normalized * * @var bool */ public $_compileDirNormalized = false; /** * flag if template_dir is normalized * * @var bool */ public $_cacheDirNormalized = false; /** * force template compiling? * * @var boolean */ public $force_compile = false; /** * use sub dirs for compiled/cached files? * * @var boolean */ public $use_sub_dirs = false; /** * merge compiled includes * * @var boolean */ public $merge_compiled_includes = false; /** * force cache file creation * * @var boolean */ public $force_cache = false; /** * template left-delimiter * * @var string */ private $left_delimiter = "{"; /** * template right-delimiter * * @var string */ private $right_delimiter = "}"; /** * array of strings which shall be treated as literal by compiler * * @var array string */ public $literals = []; /** * class name * This should be instance of \Smarty\Security. * * @var string * @see \Smarty\Security */ public $security_class = \Smarty\Security::class; /** * implementation of security class * * @var \Smarty\Security */ public $security_policy = null; /** * debug mode * Setting this to true enables the debug-console. Setting it to 2 enables individual Debug Console window by * template name. * * @var boolean|int */ public $debugging = false; /** * This determines if debugging is enable-able from the browser. * * * @var string */ public $debugging_ctrl = 'NONE'; /** * Name of debugging URL-param. * Only used when $debugging_ctrl is set to 'URL'. * The name of the URL-parameter that activates debugging. * * @var string */ public $smarty_debug_id = 'SMARTY_DEBUG'; /** * Path of debug template. * * @var string */ public $debug_tpl = null; /** * When set, smarty uses this value as error_reporting-level. * * @var int */ public $error_reporting = null; /** * Controls whether variables with the same name overwrite each other. * * @var boolean */ public $config_overwrite = true; /** * Controls whether config values of on/true/yes and off/false/no get converted to boolean. * * @var boolean */ public $config_booleanize = true; /** * Controls whether hidden config sections/vars are read from the file. * * @var boolean */ public $config_read_hidden = false; /** * locking concurrent compiles * * @var boolean */ public $compile_locking = true; /** * Controls whether cache resources should use locking mechanism * * @var boolean */ public $cache_locking = false; /** * seconds to wait for acquiring a lock before ignoring the write lock * * @var float */ public $locking_timeout = 10; /** * resource type used if none given * Must be a valid key of $registered_resources. * * @var string */ public $default_resource_type = 'file'; /** * cache resource * Must be a subclass of \Smarty\Cacheresource\Base * * @var \Smarty\Cacheresource\Base */ private $cacheResource; /** * config type * * @var string */ public $default_config_type = 'file'; /** * check If-Modified-Since headers * * @var boolean */ public $cache_modified_check = false; /** * registered plugins * * @var array */ public $registered_plugins = []; /** * registered objects * * @var array */ public $registered_objects = []; /** * registered classes * * @var array */ public $registered_classes = []; /** * registered resources * * @var array */ public $registered_resources = []; /** * registered cache resources * * @var array * @deprecated since 5.0 */ private $registered_cache_resources = []; /** * default modifier * * @var array */ public $default_modifiers = []; /** * autoescape variable output * * @var boolean */ public $escape_html = false; /** * start time for execution time calculation * * @var int */ public $start_time = 0; /** * internal flag to enable parser debugging * * @var bool */ public $_parserdebug = false; /** * Debug object * * @var \Smarty\Debug */ public $_debug = null; /** * template directory * * @var array */ protected $template_dir = ['./templates/']; /** * flags for normalized template directory entries * * @var array */ protected $_processedTemplateDir = []; /** * config directory * * @var array */ protected $config_dir = ['./configs/']; /** * flags for normalized template directory entries * * @var array */ protected $_processedConfigDir = []; /** * compile directory * * @var string */ protected $compile_dir = './templates_c/'; /** * cache directory * * @var string */ protected $cache_dir = './cache/'; /** * PHP7 Compatibility mode * * @var bool */ private $isMutingUndefinedOrNullWarnings = false; /** * Cache of loaded resource handlers. * * @var array */ public $_resource_handlers = []; /** * Cache of loaded cacheresource handlers. * * @var array */ public $_cacheresource_handlers = []; /** * List of extensions * * @var ExtensionInterface[] */ private $extensions = []; /** * @var BCPluginsAdapter */ private $BCPluginsAdapter; /** * Initialize new Smarty object */ public function __construct() { $this->start_time = microtime(true); // Check if we're running on Windows \Smarty\Smarty::$_IS_WINDOWS = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; // let PCRE (preg_*) treat strings as ISO-8859-1 if we're not dealing with UTF-8 if (\Smarty\Smarty::$_CHARSET !== 'UTF-8') { \Smarty\Smarty::$_UTF8_MODIFIER = ''; } $this->BCPluginsAdapter = new BCPluginsAdapter($this); $this->extensions[] = new CoreExtension(); $this->extensions[] = new DefaultExtension(); $this->extensions[] = $this->BCPluginsAdapter; $this->cacheResource = new File(); } /** * Load an additional extension. * * @return void */ public function addExtension(ExtensionInterface $extension) { $this->extensions[] = $extension; } /** * Returns all loaded extensions * * @return array|ExtensionInterface[] */ public function getExtensions(): array { return $this->extensions; } /** * Replace the entire list extensions, allowing you to determine the exact order of the extensions. * * @param ExtensionInterface[] $extensions * * @return void */ public function setExtensions(array $extensions): void { $this->extensions = $extensions; } /** * Check if a template resource exists * * @param string $resource_name template name * * @return bool status * @throws \Smarty\Exception */ public function templateExists($resource_name) { // create source object $source = Template\Source::load(null, $this, $resource_name); return $source->exists; } /** * Loads security class and enables security * * @param string|\Smarty\Security $security_class if a string is used, it must be class-name * * @return static current Smarty instance for chaining * @throws \Smarty\Exception */ public function enableSecurity($security_class = null) { \Smarty\Security::enableSecurity($this, $security_class); return $this; } /** * Disable security * * @return static current Smarty instance for chaining */ public function disableSecurity() { $this->security_policy = null; return $this; } /** * Add template directory(s) * * @param string|array $template_dir directory(s) of template sources * @param string $key of the array element to assign the template dir to * @param bool $isConfig true for config_dir * * @return static current Smarty instance for chaining */ public function addTemplateDir($template_dir, $key = null, $isConfig = false) { if ($isConfig) { $processed = &$this->_processedConfigDir; $dir = &$this->config_dir; $this->_configDirNormalized = false; } else { $processed = &$this->_processedTemplateDir; $dir = &$this->template_dir; $this->_templateDirNormalized = false; } if (is_array($template_dir)) { foreach ($template_dir as $k => $v) { if (is_int($k)) { // indexes are not merged but appended $dir[] = $v; } else { // string indexes are overridden $dir[$k] = $v; unset($processed[$key]); } } } else { if ($key !== null) { // override directory at specified index $dir[$key] = $template_dir; unset($processed[$key]); } else { // append new directory $dir[] = $template_dir; } } return $this; } /** * Get template directories * * @param mixed $index index of directory to get, null to get all * @param bool $isConfig true for config_dir * * @return array|string list of template directories, or directory of $index */ public function getTemplateDir($index = null, $isConfig = false) { if ($isConfig) { $dir = &$this->config_dir; } else { $dir = &$this->template_dir; } if ($isConfig ? !$this->_configDirNormalized : !$this->_templateDirNormalized) { $this->_normalizeTemplateConfig($isConfig); } if ($index !== null) { return isset($dir[$index]) ? $dir[$index] : null; } return $dir; } /** * Set template directory * * @param string|array $template_dir directory(s) of template sources * @param bool $isConfig true for config_dir * * @return static current Smarty instance for chaining */ public function setTemplateDir($template_dir, $isConfig = false) { if ($isConfig) { $this->config_dir = []; $this->_processedConfigDir = []; } else { $this->template_dir = []; $this->_processedTemplateDir = []; } $this->addTemplateDir($template_dir, null, $isConfig); return $this; } /** * Adds a template directory before any existing directoires * * @param string $new_template_dir directory of template sources * @param bool $is_config true for config_dir * * @return static current Smarty instance for chaining */ public function prependTemplateDir($new_template_dir, $is_config = false) { $current_template_dirs = $is_config ? $this->config_dir : $this->template_dir; array_unshift($current_template_dirs, $new_template_dir); $this->setTemplateDir($current_template_dirs, $is_config); return $this; } /** * Add config directory(s) * * @param string|array $config_dir directory(s) of config sources * @param mixed $key key of the array element to assign the config dir to * * @return static current Smarty instance for chaining */ public function addConfigDir($config_dir, $key = null) { return $this->addTemplateDir($config_dir, $key, true); } /** * Get config directory * * @param mixed $index index of directory to get, null to get all * * @return array configuration directory */ public function getConfigDir($index = null) { return $this->getTemplateDir($index, true); } /** * Set config directory * * @param $config_dir * * @return static current Smarty instance for chaining */ public function setConfigDir($config_dir) { return $this->setTemplateDir($config_dir, true); } /** * Registers plugin to be used in templates * * @param string $type plugin type * @param string $name name of template tag * @param callable $callback PHP callback to register * @param bool $cacheable if true (default) this function is cache able * * @return $this * @throws \Smarty\Exception * * @api Smarty::registerPlugin() */ public function registerPlugin($type, $name, $callback, $cacheable = true) { if (isset($this->registered_plugins[$type][$name])) { throw new Exception("Plugin tag '{$name}' already registered"); } elseif (!is_callable($callback) && !class_exists($callback)) { throw new Exception("Plugin '{$name}' not callable"); } else { $this->registered_plugins[$type][$name] = [$callback, (bool)$cacheable]; } return $this; } /** * Returns plugin previously registered using ::registerPlugin as a numerical array as follows or null if not found: * [ * 0 => the callback * 1 => (bool) $cacheable * 2 => (array) $cache_attr * ] * * @param string $type plugin type * @param string $name name of template tag * * @return array|null * * @api Smarty::unregisterPlugin() */ public function getRegisteredPlugin($type, $name): ?array { if (isset($this->registered_plugins[$type][$name])) { return $this->registered_plugins[$type][$name]; } return null; } /** * Unregisters plugin previously registered using ::registerPlugin * * @param string $type plugin type * @param string $name name of template tag * * @return $this * * @api Smarty::unregisterPlugin() */ public function unregisterPlugin($type, $name) { if (isset($this->registered_plugins[$type][$name])) { unset($this->registered_plugins[$type][$name]); } return $this; } /** * Adds directory of plugin files * * @param null|array|string $plugins_dir * * @return static current Smarty instance for chaining * @deprecated since 5.0 */ public function addPluginsDir($plugins_dir) { trigger_error('Using Smarty::addPluginsDir() to load plugins is deprecated and will be ' . 'removed in a future release. Use Smarty::addExtension() to add an extension or Smarty::registerPlugin to ' . 'quickly register a plugin using a callback function.', E_USER_DEPRECATED); foreach ((array)$plugins_dir as $v) { $path = $this->_realpath(rtrim($v ?? '', '/\\') . DIRECTORY_SEPARATOR, true); $this->BCPluginsAdapter->loadPluginsFromDir($path); } return $this; } /** * Get plugin directories * * @return array list of plugin directories * @deprecated since 5.0 */ public function getPluginsDir() { trigger_error('Using Smarty::getPluginsDir() is deprecated and will be ' . 'removed in a future release. It will always return an empty array.', E_USER_DEPRECATED); return []; } /** * Set plugins directory * * @param string|array $plugins_dir directory(s) of plugins * * @return static current Smarty instance for chaining * @deprecated since 5.0 */ public function setPluginsDir($plugins_dir) { trigger_error('Using Smarty::getPluginsDir() is deprecated and will be ' . 'removed in a future release. For now, it will remove the DefaultExtension from the extensions list and ' . 'proceed to call Smartyy::addPluginsDir..', E_USER_DEPRECATED); $this->extensions = array_filter( $this->extensions, function ($extension) { return !($extension instanceof DefaultExtension); } ); return $this->addPluginsDir($plugins_dir); } /** * Registers a default plugin handler * * @param callable $callback class/method name * * @return $this * @throws Exception if $callback is not callable * * @api Smarty::registerDefaultPluginHandler() * * @deprecated since 5.0 */ public function registerDefaultPluginHandler($callback) { trigger_error('Using Smarty::registerDefaultPluginHandler() is deprecated and will be ' . 'removed in a future release. Please rewrite your plugin handler as an extension.', E_USER_DEPRECATED); if (is_callable($callback)) { $this->default_plugin_handler_func = $callback; } else { throw new Exception("Default plugin handler '$callback' not callable"); } return $this; } /** * Get compiled directory * * @return string path to compiled templates */ public function getCompileDir() { if (!$this->_compileDirNormalized) { $this->_normalizeDir('compile_dir', $this->compile_dir); $this->_compileDirNormalized = true; } return $this->compile_dir; } /** * * @param string $compile_dir directory to store compiled templates in * * @return static current Smarty instance for chaining */ public function setCompileDir($compile_dir) { $this->_normalizeDir('compile_dir', $compile_dir); $this->_compileDirNormalized = true; return $this; } /** * Get cache directory * * @return string path of cache directory */ public function getCacheDir() { if (!$this->_cacheDirNormalized) { $this->_normalizeDir('cache_dir', $this->cache_dir); $this->_cacheDirNormalized = true; } return $this->cache_dir; } /** * Set cache directory * * @param string $cache_dir directory to store cached templates in * * @return static current Smarty instance for chaining */ public function setCacheDir($cache_dir) { $this->_normalizeDir('cache_dir', $cache_dir); $this->_cacheDirNormalized = true; return $this; } private $templates = []; /** * Creates a template object * * @param string $template_name * @param mixed $cache_id cache id to be used with this template * @param mixed $compile_id compile id to be used with this template * @param null $parent next higher level of Smarty variables * * @return Template template object * @throws Exception */ public function createTemplate($template_name, $cache_id = null, $compile_id = null, $parent = null): Template { $data = []; // Shuffle params for backward compatibility: if 2nd param is an object, it's the parent if (is_object($cache_id)) { $parent = $cache_id; $cache_id = null; } // Shuffle params for backward compatibility: if 2nd param is an array, it's data if (is_array($cache_id)) { $data = $cache_id; $cache_id = null; } return $this->doCreateTemplate($template_name, $cache_id, $compile_id, $parent, null, null, false, $data); } /** * Get unique template id * * @param string $resource_name * @param null|mixed $cache_id * @param null|mixed $compile_id * @param null $caching * * @return string */ private function generateUniqueTemplateId( $resource_name, $cache_id = null, $compile_id = null, $caching = null ): string { // defaults for optional params $cache_id = $cache_id ?? $this->cache_id; $compile_id = $compile_id ?? $this->compile_id; $caching = (int)$caching ?? $this->caching; // Add default resource type to resource name if it is missing if (strpos($resource_name, ':') === false) { $resource_name = "{$this->default_resource_type}:{$resource_name}"; } $_templateId = $resource_name . '#' . $cache_id . '#' . $compile_id . '#' . $caching; // hash very long IDs to prevent problems with filename length // do not hash shorter IDs, so they remain recognizable if (strlen($_templateId) > 150) { $_templateId = sha1($_templateId); } return $_templateId; } /** * Normalize path * - remove /./ and /../ * - make it absolute if required * * @param string $path file path * @param bool $realpath if true - convert to absolute * false - convert to relative * null - keep as it is but * remove /./ /../ * * @return string */ public function _realpath($path, $realpath = null) { $nds = ['/' => '\\', '\\' => '/']; preg_match( '%^(?(?:[[:alpha:]]:[\\\\/]|/|[\\\\]{2}[[:alpha:]]+|[[:print:]]{2,}:[/]{2}|[\\\\])?)(?(.*))$%u', $path, $parts ); $path = $parts['path']; if ($parts['root'] === '\\') { $parts['root'] = substr(getcwd(), 0, 2) . $parts['root']; } else { if ($realpath !== null && !$parts['root']) { $path = getcwd() . DIRECTORY_SEPARATOR . $path; } } // normalize DIRECTORY_SEPARATOR $path = str_replace($nds[DIRECTORY_SEPARATOR], DIRECTORY_SEPARATOR, $path); $parts['root'] = str_replace($nds[DIRECTORY_SEPARATOR], DIRECTORY_SEPARATOR, $parts['root']); do { $path = preg_replace( ['#[\\\\/]{2}#', '#[\\\\/][.][\\\\/]#', '#[\\\\/]([^\\\\/.]+)[\\\\/][.][.][\\\\/]#'], DIRECTORY_SEPARATOR, $path, -1, $count ); } while ($count > 0); return $realpath !== false ? $parts['root'] . $path : str_ireplace(getcwd(), '.', $parts['root'] . $path); } /** * @param boolean $use_sub_dirs */ public function setUseSubDirs($use_sub_dirs) { $this->use_sub_dirs = $use_sub_dirs; } /** * @param int $error_reporting */ public function setErrorReporting($error_reporting) { $this->error_reporting = $error_reporting; } /** * @param boolean $escape_html */ public function setEscapeHtml($escape_html) { $this->escape_html = $escape_html; } /** * Return auto_literal flag * * @return boolean */ public function getAutoLiteral() { return $this->auto_literal; } /** * Set auto_literal flag * * @param boolean $auto_literal */ public function setAutoLiteral($auto_literal = true) { $this->auto_literal = $auto_literal; } /** * @param boolean $force_compile */ public function setForceCompile($force_compile) { $this->force_compile = $force_compile; } /** * @param boolean $merge_compiled_includes */ public function setMergeCompiledIncludes($merge_compiled_includes) { $this->merge_compiled_includes = $merge_compiled_includes; } /** * Get left delimiter * * @return string */ public function getLeftDelimiter() { return $this->left_delimiter; } /** * Set left delimiter * * @param string $left_delimiter */ public function setLeftDelimiter($left_delimiter) { $this->left_delimiter = $left_delimiter; } /** * Get right delimiter * * @return string $right_delimiter */ public function getRightDelimiter() { return $this->right_delimiter; } /** * Set right delimiter * * @param string */ public function setRightDelimiter($right_delimiter) { $this->right_delimiter = $right_delimiter; } /** * @param boolean $debugging */ public function setDebugging($debugging) { $this->debugging = $debugging; } /** * @param boolean $config_overwrite */ public function setConfigOverwrite($config_overwrite) { $this->config_overwrite = $config_overwrite; } /** * @param boolean $config_booleanize */ public function setConfigBooleanize($config_booleanize) { $this->config_booleanize = $config_booleanize; } /** * @param boolean $config_read_hidden */ public function setConfigReadHidden($config_read_hidden) { $this->config_read_hidden = $config_read_hidden; } /** * @param boolean $compile_locking */ public function setCompileLocking($compile_locking) { $this->compile_locking = $compile_locking; } /** * @param string $default_resource_type */ public function setDefaultResourceType($default_resource_type) { $this->default_resource_type = $default_resource_type; } /** * Test install * * @param null $errors */ public function testInstall(&$errors = null) { \Smarty\TestInstall::testInstall($this, $errors); } /** * Get Smarty object * * @return static */ public function getSmarty() { return $this; } /** * Normalize and set directory string * * @param string $dirName cache_dir or compile_dir * @param string $dir filepath of folder */ private function _normalizeDir($dirName, $dir) { $this->{$dirName} = $this->_realpath(rtrim($dir ?? '', "/\\") . DIRECTORY_SEPARATOR, true); } /** * Normalize template_dir or config_dir * * @param bool $isConfig true for config_dir */ private function _normalizeTemplateConfig($isConfig) { if ($isConfig) { $processed = &$this->_processedConfigDir; $dir = &$this->config_dir; } else { $processed = &$this->_processedTemplateDir; $dir = &$this->template_dir; } if (!is_array($dir)) { $dir = (array)$dir; } foreach ($dir as $k => $v) { if (!isset($processed[$k])) { $dir[$k] = $this->_realpath(rtrim($v ?? '', "/\\") . DIRECTORY_SEPARATOR, true); $processed[$k] = true; } } if ($isConfig) { $this->_configDirNormalized = true; $this->_joined_config_dir = join('#', $this->config_dir); } else { $this->_templateDirNormalized = true; $this->_joined_template_dir = join('#', $this->template_dir); } } /** * Mutes errors for "undefined index", "undefined array key" and "trying to read property of null". * * @void */ public function muteUndefinedOrNullWarnings(): void { $this->isMutingUndefinedOrNullWarnings = true; } /** * Indicates if Smarty will mute errors for "undefined index", "undefined array key" and "trying to read property of null". * * @return bool */ public function isMutingUndefinedOrNullWarnings(): bool { return $this->isMutingUndefinedOrNullWarnings; } /** * Empty cache for a specific template * * @param string $template_name template name * @param string $cache_id cache id * @param string $compile_id compile id * @param integer $exp_time expiration time * @param string $type resource type * * @return int number of cache files deleted * @throws \Smarty\Exception * * @api Smarty::clearCache() */ public function clearCache( $template_name, $cache_id = null, $compile_id = null, $exp_time = null ) { return $this->getCacheResource()->clear($this, $template_name, $cache_id, $compile_id, $exp_time); } /** * Empty cache folder * * @param integer $exp_time expiration time * @param string $type resource type * * @return int number of cache files deleted * * @api Smarty::clearAllCache() */ public function clearAllCache($exp_time = null) { return $this->getCacheResource()->clearAll($this, $exp_time); } /** * Delete compiled template file * * @param string $resource_name template name * @param string $compile_id compile id * @param integer $exp_time expiration time * * @return int number of template files deleted * @throws \Smarty\Exception * * @api Smarty::clearCompiledTemplate() */ public function clearCompiledTemplate($resource_name = null, $compile_id = null, $exp_time = null) { $_compile_dir = $this->getCompileDir(); if ($_compile_dir === '/') { //We should never want to delete this! return 0; } $_compile_id = isset($compile_id) ? preg_replace('![^\w]+!', '_', $compile_id) : null; $_dir_sep = $this->use_sub_dirs ? DIRECTORY_SEPARATOR : '^'; if (isset($resource_name)) { $_save_stat = $this->caching; $this->caching = \Smarty\Smarty::CACHING_OFF; /* @var Template $tpl */ $tpl = $this->doCreateTemplate($resource_name); $this->caching = $_save_stat; if (!$tpl->getSource()->handler->recompiled && $tpl->getSource()->exists) { $_resource_part_1 = basename(str_replace('^', DIRECTORY_SEPARATOR, $tpl->getCompiled()->filepath)); $_resource_part_1_length = strlen($_resource_part_1); } else { return 0; } $_resource_part_2 = str_replace('.php', '.cache.php', $_resource_part_1); $_resource_part_2_length = strlen($_resource_part_2); } $_dir = $_compile_dir; if ($this->use_sub_dirs && isset($_compile_id)) { $_dir .= $_compile_id . $_dir_sep; } if (isset($_compile_id)) { $_compile_id_part = $_compile_dir . $_compile_id . $_dir_sep; $_compile_id_part_length = strlen($_compile_id_part); } $_count = 0; try { $_compileDirs = new RecursiveDirectoryIterator($_dir); } catch (\UnexpectedValueException $e) { // path not found / not a dir return 0; } $_compile = new RecursiveIteratorIterator($_compileDirs, RecursiveIteratorIterator::CHILD_FIRST); foreach ($_compile as $_file) { if (substr(basename($_file->getPathname()), 0, 1) === '.') { continue; } $_filepath = (string)$_file; if ($_file->isDir()) { if (!$_compile->isDot()) { // delete folder if empty @rmdir($_file->getPathname()); } } else { // delete only php files if (substr($_filepath, -4) !== '.php') { continue; } $unlink = false; if ((!isset($_compile_id) || (isset($_filepath[$_compile_id_part_length]) && $a = !strncmp($_filepath, $_compile_id_part, $_compile_id_part_length))) && (!isset($resource_name) || (isset($_filepath[$_resource_part_1_length]) && substr_compare( $_filepath, $_resource_part_1, -$_resource_part_1_length, $_resource_part_1_length ) === 0) || (isset($_filepath[$_resource_part_2_length]) && substr_compare( $_filepath, $_resource_part_2, -$_resource_part_2_length, $_resource_part_2_length ) === 0)) ) { if (isset($exp_time)) { if (is_file($_filepath) && time() - filemtime($_filepath) >= $exp_time) { $unlink = true; } } else { $unlink = true; } } if ($unlink && is_file($_filepath) && @unlink($_filepath)) { $_count++; if (function_exists('opcache_invalidate') && (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api')) < 1) ) { opcache_invalidate($_filepath, true); } elseif (function_exists('apc_delete_file')) { apc_delete_file($_filepath); } } } } return $_count; } /** * Compile all template files * * @param string $extension file extension * @param bool $force_compile force all to recompile * @param int $time_limit * @param int $max_errors * * @return integer number of template files recompiled * @api Smarty::compileAllTemplates() * */ public function compileAllTemplates( $extension = '.tpl', $force_compile = false, $time_limit = 0, $max_errors = null ) { return $this->compileAll($extension, $force_compile, $time_limit, $max_errors); } /** * Compile all config files * * @param string $extension file extension * @param bool $force_compile force all to recompile * @param int $time_limit * @param int $max_errors * * @return int number of template files recompiled * @api Smarty::compileAllConfig() * */ public function compileAllConfig( $extension = '.conf', $force_compile = false, $time_limit = 0, $max_errors = null ) { return $this->compileAll($extension, $force_compile, $time_limit, $max_errors, true); } /** * Compile all template or config files * * @param string $extension template file name extension * @param bool $force_compile force all to recompile * @param int $time_limit set maximum execution time * @param int $max_errors set maximum allowed errors * @param bool $isConfig flag true if called for config files * * @return int number of template files compiled */ protected function compileAll( $extension, $force_compile, $time_limit, $max_errors, $isConfig = false ) { // switch off time limit if (function_exists('set_time_limit')) { @set_time_limit($time_limit); } $_count = 0; $_error_count = 0; $sourceDir = $isConfig ? $this->getConfigDir() : $this->getTemplateDir(); // loop over array of source directories foreach ($sourceDir as $_dir) { $_dir_1 = new RecursiveDirectoryIterator( $_dir, defined('FilesystemIterator::FOLLOW_SYMLINKS') ? FilesystemIterator::FOLLOW_SYMLINKS : 0 ); $_dir_2 = new RecursiveIteratorIterator($_dir_1); foreach ($_dir_2 as $_fileinfo) { $_file = $_fileinfo->getFilename(); if (substr(basename($_fileinfo->getPathname()), 0, 1) === '.' || strpos($_file, '.svn') !== false) { continue; } if (substr_compare($_file, $extension, -strlen($extension)) !== 0) { continue; } if ($_fileinfo->getPath() !== substr($_dir, 0, -1)) { $_file = substr($_fileinfo->getPath(), strlen($_dir)) . DIRECTORY_SEPARATOR . $_file; } echo "\n", $_dir, '---', $_file; flush(); $_start_time = microtime(true); $_smarty = clone $this; // $_smarty->force_compile = $force_compile; try { $_tpl = $this->doCreateTemplate($_file); $_tpl->caching = self::CACHING_OFF; $_tpl->setSource( $isConfig ? \Smarty\Template\Config::load($_tpl) : \Smarty\Template\Source::load($_tpl) ); if ($_tpl->mustCompile()) { $_tpl->compileTemplateSource(); $_count++; echo ' compiled in ', microtime(true) - $_start_time, ' seconds'; flush(); } else { echo ' is up to date'; flush(); } } catch (\Exception $e) { echo "\n ------>Error: ", $e->getMessage(), "\n"; $_error_count++; } // free memory unset($_tpl); if ($max_errors !== null && $_error_count === $max_errors) { echo "\ntoo many errors\n"; exit(1); } } } echo "\n"; return $_count; } /** * check client side cache * * @param \Smarty\Template\Cached $cached * @param Template $_template * @param string $content * * @throws \Exception * @throws \Smarty\Exception */ public function cacheModifiedCheck(Template\Cached $cached, Template $_template, $content) { $_isCached = $_template->isCached() && !$_template->getCompiled()->getNocacheCode(); $_last_modified_date = @substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], 'GMT') + 3); if ($_isCached && $cached->timestamp <= strtotime($_last_modified_date)) { switch (PHP_SAPI) { case 'cgi': // php-cgi < 5.3 case 'cgi-fcgi': // php-cgi >= 5.3 case 'fpm-fcgi': // php-fpm >= 5.3.3 header('Status: 304 Not Modified'); break; case 'cli': if (/* ^phpunit */ !empty($_SERVER['SMARTY_PHPUNIT_DISABLE_HEADERS']) /* phpunit$ */ ) { $_SERVER['SMARTY_PHPUNIT_HEADERS'][] = '304 Not Modified'; } break; default: if (/* ^phpunit */ !empty($_SERVER['SMARTY_PHPUNIT_DISABLE_HEADERS']) /* phpunit$ */ ) { $_SERVER['SMARTY_PHPUNIT_HEADERS'][] = '304 Not Modified'; } else { header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified'); } break; } } else { switch (PHP_SAPI) { case 'cli': if (/* ^phpunit */ !empty($_SERVER['SMARTY_PHPUNIT_DISABLE_HEADERS']) /* phpunit$ */ ) { $_SERVER['SMARTY_PHPUNIT_HEADERS'][] = 'Last-Modified: ' . gmdate('D, d M Y H:i:s', $cached->timestamp) . ' GMT'; } break; default: header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $cached->timestamp) . ' GMT'); break; } echo $content; } } public function getModifierCallback(string $modifierName) { foreach ($this->getExtensions() as $extension) { if ($callback = $extension->getModifierCallback($modifierName)) { return [new CallbackWrapper($modifierName, $callback), 'handle']; } } return null; } public function getFunctionHandler(string $functionName): ?\Smarty\FunctionHandler\FunctionHandlerInterface { foreach ($this->getExtensions() as $extension) { if ($handler = $extension->getFunctionHandler($functionName)) { return $handler; } } return null; } public function getBlockHandler(string $blockTagName): ?\Smarty\BlockHandler\BlockHandlerInterface { foreach ($this->getExtensions() as $extension) { if ($handler = $extension->getBlockHandler($blockTagName)) { return $handler; } } return null; } public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier\ModifierCompilerInterface { foreach ($this->getExtensions() as $extension) { if ($handler = $extension->getModifierCompiler($modifier)) { return $handler; } } return null; } /** * Run pre-filters over template source * * @param string $source the content which shall be processed by the filters * @param Template $template template object * * @return string the filtered source */ public function runPreFilters($source, Template $template) { foreach ($this->getExtensions() as $extension) { /** @var \Smarty\Filter\FilterInterface $filter */ foreach ($extension->getPreFilters() as $filter) { $source = $filter->filter($source, $template); } } // return filtered output return $source; } /** * Run post-filters over template's compiled code * * @param string $code the content which shall be processed by the filters * @param Template $template template object * * @return string the filtered code */ public function runPostFilters($code, Template $template) { foreach ($this->getExtensions() as $extension) { /** @var \Smarty\Filter\FilterInterface $filter */ foreach ($extension->getPostFilters() as $filter) { $code = $filter->filter($code, $template); } } // return filtered output return $code; } /** * Run filters over template output * * @param string $content the content which shall be processed by the filters * @param Template $template template object * * @return string the filtered (modified) output */ public function runOutputFilters($content, Template $template) { foreach ($this->getExtensions() as $extension) { /** @var \Smarty\Filter\FilterInterface $filter */ foreach ($extension->getOutputFilters() as $filter) { $content = $filter->filter($content, $template); } } // return filtered output return $content; } /** * Writes file in a safe way to disk * * @param string $_filepath complete filepath * @param string $_contents file content * * @return boolean true * @throws Exception */ public function writeFile($_filepath, $_contents) { $_error_reporting = error_reporting(); error_reporting($_error_reporting & ~E_NOTICE & ~E_WARNING); $_dirpath = dirname($_filepath); // if subdirs, create dir structure if ($_dirpath !== '.') { $i = 0; // loop if concurrency problem occurs // see https://bugs.php.net/bug.php?id=35326 while (!is_dir($_dirpath)) { if (@mkdir($_dirpath, 0777, true)) { break; } clearstatcache(); if (++$i === 3) { error_reporting($_error_reporting); throw new Exception("unable to create directory {$_dirpath}"); } sleep(1); } } // write to tmp file, then move to overt file lock race condition $_tmp_file = $_dirpath . DIRECTORY_SEPARATOR . str_replace(['.', ','], '_', uniqid('wrt', true)); if (!file_put_contents($_tmp_file, $_contents)) { error_reporting($_error_reporting); throw new Exception("unable to write file {$_tmp_file}"); } /* * Windows' rename() fails if the destination exists, * Linux' rename() properly handles the overwrite. * Simply unlink()ing a file might cause other processes * currently reading that file to fail, but linux' rename() * seems to be smart enough to handle that for us. */ if (\Smarty\Smarty::$_IS_WINDOWS) { // remove original file if (is_file($_filepath)) { @unlink($_filepath); } // rename tmp file $success = @rename($_tmp_file, $_filepath); } else { // rename tmp file $success = @rename($_tmp_file, $_filepath); if (!$success) { // remove original file if (is_file($_filepath)) { @unlink($_filepath); } // rename tmp file $success = @rename($_tmp_file, $_filepath); } } if (!$success) { error_reporting($_error_reporting); throw new Exception("unable to write file {$_filepath}"); } // set file permissions @chmod($_filepath, 0666 & ~umask()); error_reporting($_error_reporting); return true; } private $runtimes = []; /** * Loads and returns a runtime extension or null if not found * * @param string $type * * @return object|null */ public function getRuntime(string $type) { if (isset($this->runtimes[$type])) { return $this->runtimes[$type]; } // Lazy load runtimes when/if needed switch ($type) { case 'Capture': return $this->runtimes[$type] = new CaptureRuntime(); case 'Foreach': return $this->runtimes[$type] = new ForeachRuntime(); case 'Inheritance': return $this->runtimes[$type] = new InheritanceRuntime(); case 'TplFunction': return $this->runtimes[$type] = new TplFunctionRuntime(); case 'DefaultPluginHandler': return $this->runtimes[$type] = new DefaultPluginHandlerRuntime( $this->getDefaultPluginHandlerFunc() ); } throw new \Smarty\Exception('Trying to load invalid runtime ' . $type); } /** * Indicates if a runtime is available. * * @param string $type * * @return bool */ public function hasRuntime(string $type): bool { try { $this->getRuntime($type); return true; } catch (\Smarty\Exception $e) { return false; } } /** * @return callable|null */ public function getDefaultPluginHandlerFunc(): ?callable { return $this->default_plugin_handler_func; } /** * load a filter of specified type and name * * @param string $type filter type * @param string $name filter name * * @return bool * @throws \Smarty\Exception * @api Smarty::loadFilter() * * @deprecated since 5.0 */ public function loadFilter($type, $name) { if ($type == \Smarty\Smarty::FILTER_VARIABLE) { foreach ($this->getExtensions() as $extension) { if ($extension->getModifierCallback($name)) { trigger_error('Using Smarty::loadFilter() to load variable filters is deprecated and will ' . 'be removed in a future release. Use Smarty::addDefaultModifiers() to add a modifier.', E_USER_DEPRECATED); $this->addDefaultModifiers([$name]); return true; } } } trigger_error('Using Smarty::loadFilter() to load filters is deprecated and will be ' . 'removed in a future release. Use Smarty::addExtension() to add an extension or Smarty::registerFilter to ' . 'quickly register a filter using a callback function.', E_USER_DEPRECATED); if ($type == \Smarty\Smarty::FILTER_OUTPUT && $name == 'trimwhitespace') { $this->BCPluginsAdapter->addOutputFilter(new TrimWhitespace()); return true; } $_plugin = "smarty_{$type}filter_{$name}"; if (!is_callable($_plugin) && class_exists($_plugin, false)) { $_plugin = [$_plugin, 'execute']; } if (is_callable($_plugin)) { $this->registerFilter($type, $_plugin, $name); return true; } throw new Exception("{$type}filter '{$name}' not found or callable"); } /** * load a filter of specified type and name * * @param string $type filter type * @param string $name filter name * * @return static * @throws \Smarty\Exception * @api Smarty::unloadFilter() * * * @deprecated since 5.0 */ public function unloadFilter($type, $name) { trigger_error('Using Smarty::unloadFilter() to unload filters is deprecated and will be ' . 'removed in a future release. Use Smarty::addExtension() to add an extension or Smarty::(un)registerFilter to ' . 'quickly (un)register a filter using a callback function.', E_USER_DEPRECATED); return $this->unregisterFilter($type, $name); } private $_caching_type = 'file'; /** * @param $type * * @return void * @deprecated since 5.0 */ public function setCachingType($type) { trigger_error('Using Smarty::setCachingType() is deprecated and will be ' . 'removed in a future release. Use Smarty::setCacheResource() instead.', E_USER_DEPRECATED); $this->_caching_type = $type; $this->activateBCCacheResource(); } /** * @return string * @deprecated since 5.0 */ public function getCachingType(): string { trigger_error('Using Smarty::getCachingType() is deprecated and will be ' . 'removed in a future release.', E_USER_DEPRECATED); return $this->_caching_type; } /** * Registers a resource to fetch a template * * @param string $name name of resource type * @param Base $resource_handler * * @return static * * @api Smarty::registerCacheResource() * * @deprecated since 5.0 */ public function registerCacheResource($name, \Smarty\Cacheresource\Base $resource_handler) { trigger_error('Using Smarty::registerCacheResource() is deprecated and will be ' . 'removed in a future release. Use Smarty::setCacheResource() instead.', E_USER_DEPRECATED); $this->registered_cache_resources[$name] = $resource_handler; $this->activateBCCacheResource(); return $this; } /** * Unregisters a resource to fetch a template * * @param $name * * @return static * @api Smarty::unregisterCacheResource() * * @deprecated since 5.0 * */ public function unregisterCacheResource($name) { trigger_error('Using Smarty::unregisterCacheResource() is deprecated and will be ' . 'removed in a future release.', E_USER_DEPRECATED); if (isset($this->registered_cache_resources[$name])) { unset($this->registered_cache_resources[$name]); } return $this; } private function activateBCCacheResource() { if ($this->_caching_type == 'file') { $this->setCacheResource(new File()); } if (isset($this->registered_cache_resources[$this->_caching_type])) { $this->setCacheResource($this->registered_cache_resources[$this->_caching_type]); } } /** * Registers a filter function * * @param string $type filter type * @param callable $callback * @param string|null $name optional filter name * * @return static * @throws \Smarty\Exception * * @api Smarty::registerFilter() */ public function registerFilter($type, $callback, $name = null) { $name = $name ?? $this->_getFilterName($callback); if (!is_callable($callback)) { throw new Exception("{$type}filter '{$name}' not callable"); } switch ($type) { case 'variable': $this->registerPlugin(self::PLUGIN_MODIFIER, $name, $callback); trigger_error('Using Smarty::registerFilter() to register variable filters is deprecated and ' . 'will be removed in a future release. Use Smarty::addDefaultModifiers() to add a modifier.', E_USER_DEPRECATED); $this->addDefaultModifiers([$name]); break; case 'output': $this->BCPluginsAdapter->addCallableAsOutputFilter($callback, $name); break; case 'pre': $this->BCPluginsAdapter->addCallableAsPreFilter($callback, $name); break; case 'post': $this->BCPluginsAdapter->addCallableAsPostFilter($callback, $name); break; default: throw new Exception("Illegal filter type '{$type}'"); } return $this; } /** * Return internal filter name * * @param callback $callable * * @return string|null internal filter name or null if callable cannot be serialized */ private function _getFilterName($callable) { if (is_array($callable)) { $_class_name = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; return $_class_name . '_' . $callable[1]; } elseif (is_string($callable)) { return $callable; } return null; } /** * Unregisters a filter function. Smarty cannot unregister closures/anonymous functions if * no name was given in ::registerFilter. * * @param string $type filter type * @param callback|string $name the name previously used in ::registerFilter * * @return static * @throws \Smarty\Exception * @api Smarty::unregisterFilter() * * */ public function unregisterFilter($type, $name) { if (!is_string($name)) { $name = $this->_getFilterName($name); } if ($name) { switch ($type) { case 'output': $this->BCPluginsAdapter->removeOutputFilter($name); break; case 'pre': $this->BCPluginsAdapter->removePreFilter($name); break; case 'post': $this->BCPluginsAdapter->removePostFilter($name); break; default: throw new Exception("Illegal filter type '{$type}'"); } } return $this; } /** * Add default modifiers * * @param array|string $modifiers modifier or list of modifiers * to add * * @return static * @api Smarty::addDefaultModifiers() * */ public function addDefaultModifiers($modifiers) { if (is_array($modifiers)) { $this->default_modifiers = array_merge($this->default_modifiers, $modifiers); } else { $this->default_modifiers[] = $modifiers; } return $this; } /** * Get default modifiers * * @return array list of default modifiers * @api Smarty::getDefaultModifiers() * */ public function getDefaultModifiers() { return $this->default_modifiers; } /** * Set default modifiers * * @param array|string $modifiers modifier or list of modifiers * to set * * @return static * @api Smarty::setDefaultModifiers() * */ public function setDefaultModifiers($modifiers) { $this->default_modifiers = (array)$modifiers; return $this; } /** * @return Cacheresource\Base */ public function getCacheResource(): Cacheresource\Base { return $this->cacheResource; } /** * @param Cacheresource\Base $cacheResource */ public function setCacheResource(Cacheresource\Base $cacheResource): void { $this->cacheResource = $cacheResource; } /** * fetches a rendered Smarty template * * @param string $template the resource handle of the template file or template object * @param mixed $cache_id cache id to be used with this template * @param mixed $compile_id compile id to be used with this template * * @return string rendered template output * @throws Exception * @throws Exception */ public function fetch($template = null, $cache_id = null, $compile_id = null) { return $this->returnOrCreateTemplate($template, $cache_id, $compile_id)->fetch(); } /** * displays a Smarty template * * @param string $template the resource handle of the template file or template object * @param mixed $cache_id cache id to be used with this template * @param mixed $compile_id compile id to be used with this template * * @throws \Exception * @throws \Smarty\Exception */ public function display($template = null, $cache_id = null, $compile_id = null) { $this->returnOrCreateTemplate($template, $cache_id, $compile_id)->display(); } /** * @param $resource_name * @param $cache_id * @param $compile_id * @param $parent * @param $caching * @param $cache_lifetime * @param bool $isConfig * @param array $data * * @return Template * @throws Exception */ public function doCreateTemplate( $resource_name, $cache_id = null, $compile_id = null, $parent = null, $caching = null, $cache_lifetime = null, bool $isConfig = false, array $data = []): Template { if (!$this->_templateDirNormalized) { $this->_normalizeTemplateConfig(false); } $_templateId = $this->generateUniqueTemplateId($resource_name, $cache_id, $compile_id, $caching); if (!isset($this->templates[$_templateId])) { $newTemplate = new Template($resource_name, $this, $parent ?: $this, $cache_id, $compile_id, $caching, $isConfig); $newTemplate->templateId = $_templateId; // @TODO this could go in constructor ^? $this->templates[$_templateId] = $newTemplate; } $tpl = clone $this->templates[$_templateId]; $tpl->setParent($parent ?: $this); if ($cache_lifetime) { $tpl->setCacheLifetime($cache_lifetime); } // fill data if present foreach ($data as $_key => $_val) { $tpl->assign($_key, $_val); } $tpl->tplFunctions = array_merge($parent->tplFunctions ?? [], $tpl->tplFunctions ?? []); if (!$this->debugging && $this->debugging_ctrl === 'URL') { $tpl->getSmarty()->getDebug()->debugUrl($tpl->getSmarty()); } return $tpl; } /** * test if cache is valid * * @param null|string|Template $template the resource handle of the template file or template * object * @param mixed $cache_id cache id to be used with this template * @param mixed $compile_id compile id to be used with this template * * @return bool cache status * @throws \Exception * @throws \Smarty\Exception * * @api Smarty::isCached() */ public function isCached($template = null, $cache_id = null, $compile_id = null) { return $this->returnOrCreateTemplate($template, $cache_id, $compile_id)->isCached(); } /** * @param $template * @param $cache_id * @param $compile_id * @param $parent * * @return Template * @throws Exception */ private function returnOrCreateTemplate($template, $cache_id = null, $compile_id = null) { if (!($template instanceof Template)) { $template = $this->createTemplate($template, $cache_id, $compile_id, $this); $template->caching = $this->caching; } return $template; } /** * Sets if Smarty should check If-Modified-Since headers to determine cache validity. * @param bool $cache_modified_check * @return void */ public function setCacheModifiedCheck($cache_modified_check): void { $this->cache_modified_check = (bool) $cache_modified_check; } }