diff --git a/libs/Smarty.class.php b/libs/Smarty.class.php index 820a1ebde..df0c237c8 100644 --- a/libs/Smarty.class.php +++ b/libs/Smarty.class.php @@ -611,6 +611,13 @@ class Smarty extends Smarty_Internal_TemplateBase */ protected $plugins_dir = array(); + /** + * plugins namespace + * + * @var array + */ + protected $plugins_namespaces = array(); + /** * cache directory * @@ -889,6 +896,42 @@ public function setPluginsDir($plugins_dir) return $this; } + /** + * Adds namespace for plugin files + * + * @param null|array|string $plugins_namespaces + * + * @return Smarty current Smarty instance for chaining + */ + public function addPluginsNamespace($plugins_namespaces) + { + $this->plugins_namespaces = array_merge($this->plugins_namespaces, (array)$plugins_namespaces); + return $this; + } + + /** + * Get plugin namespace(s) + * + * @return array list of plugin namespace(s) + */ + public function getPluginsNamespaces() : Array + { + return $this->plugins_namespaces; + } + + /** + * Set plugin namepaces + * + * @param string|array $plugins_namespaces namespace(s) of plugins + * + * @return Smarty current Smarty instance for chaining + */ + public function setPluginsNamespaces($plugins_namespaces) + { + $this->plugins_namespaces = (array)$plugins_namespaces; + return $this; + } + /** * Get compiled directory * @@ -1021,7 +1064,10 @@ public function createTemplate($template, $cache_id = null, $compile_id = null, */ public function loadPlugin($plugin_name, $check = true) { - return $this->ext->loadPlugin->loadPlugin($this, $plugin_name, $check); + $result = $this->ext->autoLoadPlugin->autoLoadPlugin($this, $plugin_name, $check); + if ($result === false) $result = $this->ext->loadPlugin->loadPlugin($this, $plugin_name, $check); + + return $result; } /** diff --git a/libs/sysplugins/smarty_internal_extension_handler.php b/libs/sysplugins/smarty_internal_extension_handler.php index b07615526..b40f76e46 100644 --- a/libs/sysplugins/smarty_internal_extension_handler.php +++ b/libs/sysplugins/smarty_internal_extension_handler.php @@ -31,6 +31,7 @@ * @property Smarty_Internal_Method_AssignByRef $assignByRef * @property Smarty_Internal_Method_LoadFilter $loadFilter * @property Smarty_Internal_Method_LoadPlugin $loadPlugin + * @property Smarty_Internal_Method_AutoLoadPlugin $autoLoadPlugin * @property Smarty_Internal_Method_RegisterFilter $registerFilter * @property Smarty_Internal_Method_RegisterObject $registerObject * @property Smarty_Internal_Method_RegisterPlugin $registerPlugin diff --git a/libs/sysplugins/smarty_internal_method_autoloadplugin.php b/libs/sysplugins/smarty_internal_method_autoloadplugin.php new file mode 100644 index 000000000..2055c2a24 --- /dev/null +++ b/libs/sysplugins/smarty_internal_method_autoloadplugin.php @@ -0,0 +1,56 @@ +autoLoadPlugin() method + * + * @package Smarty + * @subpackage PluginsInternal + * @author Stefan Froehlich + */ +class Smarty_Internal_Method_AutoLoadPlugin +{ + /** + * Takes unknown classes and tries to load the + * appropriate plugin file via autoloader. + * The namespace must be registered with Smarty, + * the class name must match the plugin name and + * there needs to be a method named like the plugin + * type. + * + * @param \Smarty $smarty + * @param string $plugin_name class plugin name to load + * @param bool $check check if already loaded + * + * @return bool|string|array + * @throws \SmartyException + */ + public function autoLoadPlugin(Smarty $smarty, String $plugin_name, Bool $check) + { + // Naming convention of plugins is "Smarty_type_name" (corresponds + // to the file name of procedural plugins). We need to extract type + // and name + if (!preg_match('#^smarty_((internal)|([^_]+))_(.+)$#i', $plugin_name, $matches)) { + throw new SmartyException("plugin {$plugin_name} is not a valid name format"); + } + + // if function or class exists, exit silently (procedural plugin already loaded) + if ($check && (is_callable($plugin_name) || class_exists($plugin_name, false))) { + return true; + } + + // check for auto-loader plugin + foreach ($smarty->getPluginsNamespaces() as $namespace) { + $class = $namespace . '\\' . $matches[4]; + if (class_exists($class, true) && is_callable(array($class, $matches[1]))) { + // the requested class exists and the required method exists + if ($check) return true; + else return array($class, $matches[1]); + } + } + + // no plugin loaded + return false; + } +} diff --git a/libs/sysplugins/smarty_internal_runtime_filterhandler.php b/libs/sysplugins/smarty_internal_runtime_filterhandler.php index 9f868e1a4..2824ba33a 100644 --- a/libs/sysplugins/smarty_internal_runtime_filterhandler.php +++ b/libs/sysplugins/smarty_internal_runtime_filterhandler.php @@ -41,7 +41,10 @@ public function runFilter($type, $content, Smarty_Internal_Template $template) } elseif (class_exists($plugin_name, false) && is_callable(array($plugin_name, 'execute'))) { $callback = array($plugin_name, 'execute'); } elseif ($template->smarty->loadPlugin($plugin_name, false)) { - if (function_exists($plugin_name)) { + if (class_exists($name, true) && is_callable(array($name, "{$type}filter"))) { + // use autoloader style plugin + $callback = array($name, "{$type}filter"); + } elseif (function_exists($plugin_name)) { // use loaded Smarty2 style plugin $callback = $plugin_name; } elseif (class_exists($plugin_name, false) && is_callable(array($plugin_name, 'execute'))) { diff --git a/libs/sysplugins/smarty_internal_template.php b/libs/sysplugins/smarty_internal_template.php index bae22a7d5..1ee0a0df4 100644 --- a/libs/sysplugins/smarty_internal_template.php +++ b/libs/sysplugins/smarty_internal_template.php @@ -448,19 +448,38 @@ public function _checkPlugins($plugins) { static $checked = array(); foreach ($plugins as $plugin) { - $name = join('::', (array)$plugin[ 'function' ]); - if (!isset($checked[ $name ])) { - if (!is_callable($plugin[ 'function' ])) { - if (is_file($plugin[ 'file' ])) { - include_once $plugin[ 'file' ]; - if (is_callable($plugin[ 'function' ])) { - $checked[ $name ] = true; + if (array_key_exists('method', $plugin)) { + // autoloader plugins + $name = join('::', $plugin[ 'method' ]); + if (is_callable($name)) { + $checked[ $name ] = true; + } + else { + throw new SmartyException("Plugin '{$name}' not callable"); + } + } + elseif (array_key_exists('function', $plugin)) { + // legacy plugins + $name = join('::', (array)$plugin[ 'function' ]); + if (!isset($checked[ $name ])) { + if (!is_callable($plugin[ 'function' ])) { + if (is_file($plugin[ 'file' ])) { + include_once $plugin[ 'file' ]; + if (is_callable($plugin[ 'function' ])) { + $checked[ $name ] = true; + } } + } else { + $checked[ $name ] = true; } - } else { - $checked[ $name ] = true; } } + else { + // plugin must either set "method" (auto-load plugins) + // or "function" (procedural plugins) + throw new SmartyException("Plugin '{$name}' not defined"); + } + if (!isset($checked[ $name ])) { if (false !== $this->smarty->loadPlugin($name)) { $checked[ $name ] = true; diff --git a/libs/sysplugins/smarty_internal_templatecompilerbase.php b/libs/sysplugins/smarty_internal_templatecompilerbase.php index d6f86ac0a..ce365eb8d 100644 --- a/libs/sysplugins/smarty_internal_templatecompilerbase.php +++ b/libs/sysplugins/smarty_internal_templatecompilerbase.php @@ -820,7 +820,20 @@ public function getPlugin($plugin_name, $plugin_type) // loop through plugin dirs and find the plugin $function = 'smarty_' . $plugin_type . '_' . $plugin_name; $file = $this->smarty->loadPlugin($function, false); - if (is_string($file)) { + if (is_array($file)) { + // plugin is implemented by a static method of an auto-loaded class + if ($this->caching && ($this->nocache || $this->tag_nocache)) { + $this->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ][ 'method' ] = + $file; + } else { + $this->required_plugins[ 'compiled' ][ $plugin_name ][ $plugin_type ][ 'method' ] = + $file; + } + if ($plugin_type === 'modifier') { + $this->modifier_plugins[ $plugin_name ] = true; + } + return join('::', $file); + } elseif (is_string($file)) { if ($this->caching && ($this->nocache || $this->tag_nocache)) { $this->required_plugins[ 'nocache' ][ $plugin_name ][ $plugin_type ][ 'file' ] = $file;