+ /**
+ * 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/<plugin name>/templates/<template name>/
+ * - In a directory for one of the current template set's ancestor
+ * (inherited) template sets within the plugin:
+ * SM_PATH/plugins/<plugin name>/templates/<parent template name>/
+ * - In a directory for the fallback template set within the plugin:
+ * SM_PATH/plugins/<plugin name>/templates/<fallback template name>/
+ *
+ * @param string $plugin The name of the plugin
+ * @param string $file The name of the template file
+ * @param string $template_set_id The ID of the template for which
+ * to start looking for the file
+ * (optional; default is current
+ * template set ID).
+ *
+ * @return boolean TRUE if the template file was found, FALSE otherwise.
+ *
+ */
+ function find_and_cache_plugin_template_file($plugin, $file, $template_set_id='') {
+
+ if (empty($template_set_id))
+ $template_set_id = $this->template_set_id;
+
+ $file_path = SM_PATH . 'plugins/' . $plugin . '/'
+ . $this->calculate_template_file_directory($template_set_id)
+ . $file;
+
+ if (file_exists($file_path)) {
+ // FIXME: assuming PHP template engine may not necessarily be a good thing
+ $engine = $this->get_template_config($template_set_id,
+ 'template_engine', SQ_PHP_TEMPLATE);
+ $file_list = array('plugins/' . $plugin . '/' . $file => array(
+ 'PATH' => $file_path,
+ 'SET_ID' => $template_set_id,
+ 'ENGINE' => $engine,
+ )
+ );
+ $this->template_file_cache
+ = $this->cache_template_file_hierarchy(FALSE, $file_list);
+ return TRUE;
+ }
+
+
+ // not found yet, try parent template set
+ // (use fallback when we run out of parents)
+ //
+ $fallback_id = $this->get_fallback_template_set();
+ $parent_id = $this->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 FALSE;
+ }
+
+ return $this->find_and_cache_plugin_template_file($plugin, $file, $parent_id);
+
+ }