X-Git-Url: https://vcs.fsf.org/?p=squirrelmail.git;a=blobdiff_plain;f=class%2Ftemplate%2FTemplate.class.php;h=b9b992f402d6b870459faf77f5a5f528ab38aade;hp=1e91362102f6a933420b67e6713cd0c8208f3c53;hb=c404d448239826f2935fe804ed8b39b0a8918280;hpb=dfbd28954a0a041b31682bbfbd8b6ffc065efd36;ds=sidebyside diff --git a/class/template/Template.class.php b/class/template/Template.class.php index 1e913621..b9b992f4 100644 --- a/class/template/Template.class.php +++ b/class/template/Template.class.php @@ -31,6 +31,8 @@ require(SM_PATH . 'functions/template.php'); * * assign() * assign_by_ref() + * clear_all_assign() + * get_template_vars() * append() * append_by_ref() * apply_template() @@ -48,10 +50,11 @@ class Template * @var string * */ - var $template_id = ''; + var $template_set_id = ''; /** - * The template directory to use + * The template set base directory (relative path from + * the main SquirrelMail directory (SM_PATH)) * * @var string * @@ -67,62 +70,73 @@ class Template var $template_engine = ''; /** - * The default template ID + * The fall-back template ID * * @var string * */ - var $default_template_id = ''; + var $fallback_template_set_id = ''; /** - * The default template directory + * The fall-back template directory (relative + * path from the main SquirrelMail directory (SM_PATH)) * * @var string * */ - var $default_template_dir = ''; + var $fallback_template_dir = ''; /** - * The default template engine (please use constants defined in constants.php) + * The fall-back template engine (please use + * constants defined in constants.php) * * @var string * */ - var $default_template_engine = ''; + var $fallback_template_engine = ''; /** - * Javascript files required by the template + * Template file cache. Structured as an array, whose keys + * are all the template file names (with path information relative + * to the template set's base directory, e.g., "css/style.css") + * found in all parent template sets including the ultimate fall-back + * template set. Array values are sub-arrays with the + * following key-value pairs: * - * @var array + * PATH -- file path, relative to SM_PATH + * SET_ID -- the ID of the template set that this file belongs to + * ENGINE -- the engine needed to render this template file * */ - var $required_js_files = array(); + var $template_file_cache = array(); /** - * Alternate stylesheets provided by the template. This is defined in the - * template config file so that we can provide pretty names in the display - * preferences - * - * @var array - **/ - var $alternate_stylesehets = array(); - + * Extra template engine class objects for rendering templates + * that require a different engine than the one for the current + * template set. Keys should be the name of the template engine, + * values are the corresponding class objects. + * + * @var array + * + */ + var $other_template_engine_objects = array(); + /** * Constructor * * Please do not call directly. Use Template::construct_template(). * - * @param string $template_id the template ID + * @param string $template_set_id the template ID * */ - function Template($template_id) { + function Template($template_set_id) { //FIXME: find a way to test that this is ONLY ever called // from the construct_template() method (I doubt it // is worth the trouble to parse the current stack trace) // if (???) // trigger_error('Please do not use default Template() constructor. Instead, use Template::construct_template().', E_USER_ERROR); - $this->set_up_template($template_id); + $this->set_up_template($template_set_id); } @@ -133,14 +147,16 @@ class Template * to get a Template object from the normal/default constructor, * and is necessary in order to control the return value. * - * @param string $template_id the template ID + * @param string $template_set_id the template ID * * @return object The correct Template object for the given template set * + * @static + * */ - function construct_template($template_id) { + function construct_template($template_set_id) { - $template = new Template($template_id); + $template = new Template($template_set_id); return $template->get_template_engine_subclass(); } @@ -151,69 +167,110 @@ class Template * This method does most of the work for setting up * newly constructed objects. * - * @param string $template_id the template ID + * @param string $template_set_id the template ID * */ - function set_up_template($template_id) { + function set_up_template($template_set_id) { // FIXME: do we want to place any restrictions on the ID like // making sure no slashes included? // get template ID // - $this->template_id = $template_id; + $this->template_set_id = $template_set_id; - // FIXME: do we want to place any restrictions on the ID like - // making sure no slashes included? - // get default template ID - // - global $templateset_default, $aTemplateSet; - $aTemplateSet = (!isset($aTemplateSet) || !is_array($aTemplateSet) - ? array() : $aTemplateSet); - $templateset_default = (!isset($templateset_default) ? 0 : $templateset_default); - $this->default_template_id = (!empty($aTemplateSet[$templateset_default]['ID']) - ? $aTemplateSet[$templateset_default]['ID'] - : 'default'); + $this->fallback_template_set_id = Template::get_fallback_template_set(); // set up template directories // $this->template_dir - = Template::calculate_template_file_directory($this->template_id); - $this->default_template_dir - = Template::calculate_template_file_directory($this->default_template_id); + = Template::calculate_template_file_directory($this->template_set_id); + $this->fallback_template_dir + = Template::calculate_template_file_directory($this->fallback_template_set_id); - // pull in the template config file and load javascript and - // css files needed for this template set + // determine template engine + // FIXME: assuming PHP template engine may not necessarily be a good thing // - $template_config_file = SM_PATH . $this->get_template_file_directory() - . 'config.php'; - if (!file_exists($template_config_file)) { + $this->template_engine = Template::get_template_config($this->template_set_id, + 'template_engine', + SQ_PHP_TEMPLATE); - trigger_error('No template configuration file was found where expected: ("' - . $template_config_file . '")', E_USER_ERROR); - } else { + // get template file cache + // + $this->template_file_cache = Template::cache_template_file_hierarchy(); - require($template_config_file); - $this->required_js_files = is_array($required_js_files) - ? $required_js_files : array(); - $this->alternate_stylesheets = is_array($alternate_stylesheets) ? - $alternate_stylesheets : - array(); + } - } + /** + * Determine what the ultimate fallback template set is. + * + * NOTE that if the fallback setting cannot be found in the + * main SquirrelMail configuration settings that the value + * of $default is returned. + * + * @param string $default The template set ID to use if + * the fallback setting cannot be + * found in SM config (optional; + * defaults to "default"). + * + * @return string The ID of the fallback template set. + * + * @static + * + */ + function get_fallback_template_set($default='default') { +// FIXME: do we want to place any restrictions on the ID such as +// making sure no slashes included? - // determine template engine + // values are in main SM config file // - if (empty($template_engine)) { - trigger_error('No template engine ($template_engine) was specified in template configuration file: ("' - . $template_config_file . '")', E_USER_ERROR); - } else { - $this->template_engine = $template_engine; - } + global $templateset_fallback, $aTemplateSet; + $aTemplateSet = (!isset($aTemplateSet) || !is_array($aTemplateSet) + ? array() : $aTemplateSet); + $templateset_fallback = (!isset($templateset_fallback) + ? 0 : $templateset_fallback); + + return (!empty($aTemplateSet[$templateset_fallback]['ID']) + ? $aTemplateSet[$templateset_fallback]['ID'] : $default); + + } + + /** + * Determine what the default template set is. + * + * NOTE that if the default setting cannot be found in the + * main SquirrelMail configuration settings that the value + * of $default is returned. + * + * @param string $default The template set ID to use if + * the default setting cannot be + * found in SM config (optional; + * defaults to "default"). + * + * @return string The ID of the default template set. + * + * @static + * + */ + function get_default_template_set($default='default') { + +// FIXME: do we want to place any restrictions on the ID such as +// making sure no slashes included? + + // values are in main SM config file + // + global $templateset_default, $aTemplateSet; + $aTemplateSet = (!isset($aTemplateSet) || !is_array($aTemplateSet) + ? array() : $aTemplateSet); + $templateset_default = (!isset($templateset_default) + ? 0 : $templateset_default); + + return (!empty($aTemplateSet[$templateset_default]['ID']) + ? $aTemplateSet[$templateset_default]['ID'] : $default); } @@ -221,23 +278,34 @@ class Template * Instantiate and return correct subclass for this template * set's templating engine. * + * @param string $template_set_id The template set whose engine + * is to be used as an override + * (if not given, this template + * set's engine is used) (optional). + * * @return object The Template subclass object for the template engine. * */ - function get_template_engine_subclass() { + function get_template_engine_subclass($template_set_id='') { + + if (empty($template_set_id)) $template_set_id = $this->template_set_id; + // FIXME: assuming PHP template engine may not necessarily be a good thing + $engine = Template::get_template_config($template_set_id, + 'template_engine', SQ_PHP_TEMPLATE); + $engine_class_file = SM_PATH . 'class/template/' - . $this->template_engine . 'Template.class.php'; + . $engine . 'Template.class.php'; if (!file_exists($engine_class_file)) { - trigger_error('Unknown template engine (' . $this->template_engine + trigger_error('Unknown template engine (' . $engine . ') was specified in template configuration file', E_USER_ERROR); } - $engine_class = $this->template_engine . 'Template'; - require($engine_class_file); - return new $engine_class($this->template_id); + $engine_class = $engine . 'Template'; + require_once($engine_class_file); + return new $engine_class($template_set_id); } @@ -245,15 +313,17 @@ class Template * Determine the relative template directory path for * the given template ID. * - * @param string $template_id The template ID from which to build - * the directory path + * @param string $template_set_id The template ID from which to build + * the directory path * * @return string The relative template path (based off of SM_PATH) * + * @static + * */ - function calculate_template_file_directory($template_id) { + function calculate_template_file_directory($template_set_id) { - return 'templates/' . $template_id . '/'; + return 'templates/' . $template_set_id . '/'; } @@ -261,15 +331,17 @@ class Template * Determine the relative images directory path for * the given template ID. * - * @param string $template_id The template ID from which to build - * the directory path + * @param string $template_set_id The template ID from which to build + * the directory path * * @return string The relative images path (based off of SM_PATH) * + * @static + * */ - function calculate_template_images_directory($template_id) { + function calculate_template_images_directory($template_set_id) { - return 'templates/' . $template_id . '/images/'; + return 'templates/' . $template_set_id . '/images/'; } @@ -286,149 +358,633 @@ class Template } + /** + * Return the template ID for the fallback template set. + * + * @return string The ID of the fallback template set. + * + */ + function get_fallback_template_set_id() { + + return $this->fallback_template_set_id; + + } /** - * Return the relative template directory path for the DEFAULT template set. + * Return the relative template directory path for the + * fallback template set. * - * @return string The relative path to the default template directory based - * from the main SquirrelMail directory (SM_PATH). + * @return string The relative path to the fallback template + * directory based from the main SquirrelMail + * directory (SM_PATH). * */ - function get_default_template_file_directory() { + function get_fallback_template_file_directory() { - return $this->default_template_dir; + return $this->fallback_template_dir; } + /** + * Get template set config setting + * + * Given a template set ID and setting name, returns the + * setting's value. Note that settings are cached in + * session, so "live" changes to template configuration + * won't be reflected until the user logs out and back + * in again. + * + * @param string $template_set_id The template set for which + * to look up the setting. + * @param string $setting The name of the setting to + * retrieve. + * @param mixed $default When the requested setting + * is not found, the contents + * of this value are returned + * instead (optional; default + * is NULL). + * NOTE that unlike sqGetGlobalVar(), + * this function will also return + * the default value if the + * requested setting is found + * but is empty. + * @param boolean $live_config When TRUE, the target template + * set's configuration file is + * reloaded every time this + * method is called. Default + * behavior is to only load the + * configuration file if it had + * never been loaded before, but + * not again after that (optional; + * default FALSE). Use with care! + * Should mostly be used for + * debugging. + * + * @return mixed The desired setting's value or if not found, + * the contents of $default are returned. + * + * @static + * + */ + function get_template_config($template_set_id, $setting, + $default=NULL, $live_config=FALSE) { + + sqGetGlobalVar('template_configuration_settings', + $template_configuration_settings, + SQ_SESSION, + array()); + + if ($live_config) unset($template_configuration_settings[$template_set_id]); + + + // NOTE: could use isset() instead of empty() below, but + // this function is designed to replace empty values + // as well as non-existing values with $default + // + if (!empty($template_configuration_settings[$template_set_id][$setting])) + return $template_configuration_settings[$template_set_id][$setting]; + + + // if template set configuration has been loaded, but this + // setting is not known, return $default + // + if (!empty($template_configuration_settings[$template_set_id])) + return $default; + + + // otherwise (template set configuration has not been loaded before), + // load it into session and return the desired setting after that + // + $template_config_file = SM_PATH + . Template::calculate_template_file_directory($template_set_id) + . 'config.php'; + + if (!file_exists($template_config_file)) { + + trigger_error('No template configuration file was found where expected: ("' + . $template_config_file . '")', E_USER_ERROR); + + } else { + + // we require() the file to let PHP do the variable value + // parsing for us, and read the file in manually so we can + // know what variable names are used in the config file + // (settings can be different depending on specific requirements + // of different template engines)... the other way this may + // be accomplished is to somehow diff the symbol table + // before/after the require(), but anyway, this code should + // only run once for this template set... + // + require($template_config_file); + $file_contents = implode("\n", file($template_config_file)); + + + // note that this assumes no template settings have + // a string in them that looks like a variable name like $x + // also note that this will attempt to grab things like + // $Id found in CVS headers, so we try to adjust for that + // by checking that the variable is actually set + // + preg_match_all('/\$(\w+)/', $file_contents, $variables, PREG_PATTERN_ORDER); + foreach ($variables[1] as $variable) { + if (isset($$variable)) + $template_configuration_settings[$template_set_id][$variable] + = $$variable; + } + + sqsession_register($template_configuration_settings, + 'template_configuration_settings'); + + // NOTE: could use isset() instead of empty() below, but + // this function is designed to replace empty values + // as well as non-existing values with $default + // + if (!empty($template_configuration_settings[$template_set_id][$setting])) + return $template_configuration_settings[$template_set_id][$setting]; + else + return $default; + + } + + } + + /** + * Obtain template file hierarchy from cache. + * + * If the file hierarchy does not exist in session, it is + * constructed and stored in session before being returned + * to the caller. + * + * @param boolean $regenerate_cache When TRUE, the file hierarchy + * is reloaded and stored fresh + * (optional; default FALSE). + * @param array $additional_files Must be in same form as the + * files in the file hierarchy + * cache. These are then added + * to the cache (optional; default + * empty - no additional files). + * + * @return array Template file hierarchy array, whose keys + * are all the template file names (with path + * information relative to the template set's + * base directory, e.g., "css/style.css") + * found in all parent template sets including + * the ultimate fall-back template set. + * Array values are sub-arrays with the + * following key-value pairs: + * + * PATH -- file path, relative to SM_PATH + * SET_ID -- the ID of the template set that this file belongs to + * ENGINE -- the engine needed to render this template file + * + * @static + * + */ + function cache_template_file_hierarchy($regenerate_cache=FALSE, + $additional_files=array()) { + + sqGetGlobalVar('template_file_hierarchy', $template_file_hierarchy, + SQ_SESSION, array()); + + + if ($regenerate_cache) unset($template_file_hierarchy); + + + if (!empty($template_file_hierarchy)) { + + // have to add additional files if given before returning + // + if (!empty($additional_files)) { + $template_file_hierarchy = array_merge($template_file_hierarchy, + $additional_files); + sqsession_register($template_file_hierarchy, + 'template_file_hierarchy'); + } + + return $template_file_hierarchy; + } + + + // nothing in cache apparently, so go build it now + // + // FIXME: not sure if there is any possibility that + // this could be called when $sTemplateID has + // yet to be defined... throw error for now, + // but if the error occurs, it's a coding error + // rather than a configuration error + // + global $sTemplateID; + if (empty($sTemplateID)) { + + trigger_error('Template set ID unknown', E_USER_ERROR); + + } else { + + $template_file_hierarchy = Template::catalog_template_files($sTemplateID); + + // additional files, if any + // + if (!empty($additional_files)) { + $template_file_hierarchy = array_merge($template_file_hierarchy, + $additional_files); + } + + sqsession_register($template_file_hierarchy, + 'template_file_hierarchy'); + + return $template_file_hierarchy; + + } + + } + + /** + * Traverse template hierarchy and catalogue all template + * files (for storing in cache). + * + * Paths to all files in all parent, grand-parent, great grand + * parent, etc. template sets (including the fallback template) + * are catalogued; for identically named files, the file earlier + * in the hierarchy (closest to this template set) is used. + * + * @param string $template_set_id The template set in which to + * search for files + * @param array $file_list The file list so far to be added + * to (allows recursive behavior) + * (optional; default empty array). + * @param string $directory The directory in which to search for + * files (must be given as full path). + * If empty, starts at top-level template + * set directory (optional; default empty). + * NOTE! Use with care, as behavior is + * unpredictable if directory given is not + * part of correct template set. + * + * @return mixed The top-level caller will have an array of template + * files returned to it; recursive calls to this function + * do not receive any return value at all. The format + * of the template file array is as described for the + * Template class attribute $template_file_cache + * + * @static + * + */ + function catalog_template_files($template_set_id, $file_list=array(), $directory='') { + + $template_base_dir = SM_PATH + . Template::calculate_template_file_directory($template_set_id); + + if (empty($directory)) { + $directory = $template_base_dir; + } + + $files_and_dirs = list_files($directory, '', FALSE, TRUE, FALSE, TRUE); + + // recurse for all the subdirectories in the template set + // + foreach ($files_and_dirs['DIRECTORIES'] as $dir) { + $file_list = Template::catalog_template_files($template_set_id, $file_list, $dir); + } + + // place all found files in the cache + // FIXME: assuming PHP template engine may not necessarily be a good thing + // + $engine = Template::get_template_config($template_set_id, + 'template_engine', SQ_PHP_TEMPLATE); + foreach ($files_and_dirs['FILES'] as $file) { + + // remove the part of the file path corresponding to the + // template set's base directory + // + $relative_file = substr($file, strlen($template_base_dir)); + + // only put file in cache if not already found in earlier template + // + if (!isset($file_list[$relative_file])) { + $file_list[$relative_file] = array( + 'PATH' => $file, + 'SET_ID' => $template_set_id, + 'ENGINE' => $engine, + ); + } + + } + + + // now if we are currently at the top-level of the template + // set base directory, we need to move on to the parent + // template set, if any + // + if ($directory == $template_base_dir) { + + // use fallback when we run out of parents + // + $fallback_id = Template::get_fallback_template_set(); + $parent_id = Template::get_template_config($template_set_id, + 'parent_template_set', + $fallback_id); + + // were we already all the way to the last level? just exit + // + // note that this code allows the fallback set to have + // a parent, too, but can result in endless loops + // if ($parent_id == $template_set_id) { + // + if ($fallback_id == $template_set_id) { + return $file_list; + } + + $file_list = Template::catalog_template_files($parent_id, $file_list); + + } + + return $file_list; + + } + + /** + * Look for a template file in a plugin; add to template + * file cache if found. + * + * The file is searched for in the following order: + * + * - A directory for the current template set within the plugin: + * SM_PATH/plugins//templates/