Source for file Template.class.php

Documentation is available at Template.class.php

  1. <?php
  2.  
  3. require(SM_PATH 'functions/template.php');
  4.  
  5. /**
  6.   * Template.class.php
  7.   *
  8.   * This file contains an abstract (PHP 4, so "abstract" is relative)
  9.   * class meant to define the basic template interface for the
  10.   * SquirrelMail core application.  Subclasses should extend this
  11.   * class with any custom functionality needed to interface a target
  12.   * templating engine with SquirrelMail.
  13.   * 
  14.   * @copyright &copy; 2003-2006 The SquirrelMail Project Team
  15.   * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  16.   * @version $Id: Template.class.php,v 1.6 2006/10/02 12:37:46 pdontthink Exp $
  17.   * @package squirrelmail
  18.   * @subpackage Template
  19.   * @since 1.5.2
  20.   *
  21.   */
  22.  
  23. /**
  24.   * The SquirrelMail Template class.
  25.   *
  26.   * Basic template class for capturing values and pluging them into a template.
  27.   * This class uses a similar API to Smarty.
  28.   *
  29.   * Methods that must be implemented by subclasses are as follows (see method
  30.   * stubs below for further information about expected behavior):
  31.   *
  32.   *     assign()
  33.   *     assign_by_ref()
  34.   *     clear_all_assign()
  35.   *     get_template_vars()
  36.   *     append()
  37.   *     append_by_ref()
  38.   *     apply_template()
  39.   *
  40.   * @author Paul Lesniewski
  41.   * @package squirrelmail
  42.   *
  43.   */
  44. class Template
  45. {
  46.  
  47.     /**
  48.       * The template ID
  49.       *
  50.       * @var string 
  51.       *
  52.       */
  53.     var $template_set_id = '';
  54.  
  55.     /**
  56.       * The template set base directory (relative path from
  57.       * the main SquirrelMail directory (SM_PATH))
  58.       *
  59.       * @var string 
  60.       *
  61.       */
  62.     var $template_dir = '';
  63.  
  64.     /**
  65.       * The template engine (please use constants defined in constants.php)
  66.       *
  67.       * @var string 
  68.       *
  69.       */
  70.     var $template_engine = '';
  71.  
  72.     /**
  73.       * The fall-back template ID
  74.       *
  75.       * @var string 
  76.       *
  77.       */
  78.     var $fallback_template_set_id = '';
  79.  
  80.     /**
  81.       * The fall-back template directory (relative
  82.       * path from the main SquirrelMail directory (SM_PATH))
  83.       *
  84.       * @var string 
  85.       *
  86.       */
  87.     var $fallback_template_dir = '';
  88.  
  89.     /**
  90.       * The fall-back template engine (please use
  91.       * constants defined in constants.php)
  92.       *
  93.       * @var string 
  94.       *
  95.       */
  96.     var $fallback_template_engine = '';
  97.  
  98.     /**
  99.       * Template file cache.  Structured as an array, whose keys
  100.       * are all the template file names (with path information relative
  101.       * to the template set's base directory, e.g., "css/style.css")
  102.       * found in all parent template sets including the ultimate fall-back
  103.       * template set.  Array values are sub-arrays with the
  104.       * following key-value pairs:
  105.       *
  106.       *   PATH    --  file path, relative to SM_PATH
  107.       *   SET_ID  --  the ID of the template set that this file belongs to
  108.       *   ENGINE  --  the engine needed to render this template file
  109.       *
  110.       */
  111.     var $template_file_cache = array();
  112.  
  113.     /**
  114.       * Extra template engine class objects for rendering templates
  115.       * that require a different engine than the one for the current
  116.       * template set.  Keys should be the name of the template engine,
  117.       * values are the corresponding class objects.
  118.       *
  119.       * @var array 
  120.       *
  121.       */
  122.     var $other_template_engine_objects = array();
  123.  
  124.     /**
  125.       * Constructor
  126.       *
  127.       * Please do not call directly.  Use Template::construct_template().
  128.       *
  129.       * @param string $template_set_id the template ID
  130.       *
  131.       */
  132.     function Template($template_set_id{
  133. //FIXME: find a way to test that this is ONLY ever called 
  134. //       from the construct_template() method (I doubt it
  135. //       is worth the trouble to parse the current stack trace)
  136. //        if (???)
  137. //            trigger_error('Please do not use default Template() constructor.  Instead, use Template::construct_template().', E_USER_ERROR);
  138.  
  139.         $this->set_up_template($template_set_id);
  140.  
  141.     }
  142.  
  143.     /**
  144.       * Construct Template
  145.       *
  146.       * This method should always be called instead of trying
  147.       * to get a Template object from the normal/default constructor,
  148.       * and is necessary in order to control the return value.
  149.       *
  150.       * @param string $template_set_id the template ID
  151.       *
  152.       * @return object The correct Template object for the given template set
  153.       *
  154.       * @static
  155.       *
  156.       */
  157.     function construct_template($template_set_id{
  158.  
  159.         $template new Template($template_set_id);
  160.         return $template->get_template_engine_subclass();
  161.  
  162.     }
  163.  
  164.     /**
  165.       * Set up internal attributes
  166.       *
  167.       * This method does most of the work for setting up
  168.       * newly constructed objects.
  169.       *
  170.       * @param string $template_set_id the template ID
  171.       *
  172.       */
  173.     function set_up_template($template_set_id{
  174.  
  175.         // FIXME: do we want to place any restrictions on the ID like
  176.         //        making sure no slashes included?
  177.         // get template ID
  178.         //
  179.         $this->template_set_id = $template_set_id;
  180.  
  181.  
  182.  
  183.  
  184.         // set up template directories
  185.         //
  186.         $this->template_dir 
  187.             = Template::calculate_template_file_directory($this->template_set_id);
  188.         $this->fallback_template_dir 
  189.  
  190.  
  191.         // determine template engine 
  192.         // FIXME: assuming PHP template engine may not necessarily be a good thing
  193.         //
  194.         $this->template_engine = Template::get_template_config($this->template_set_id
  195.                                                                'template_engine',
  196.                                                                SQ_PHP_TEMPLATE);
  197.  
  198.  
  199.         // get template file cache
  200.         //
  201.  
  202.     }
  203.  
  204.     /**
  205.       * Determine what the ultimate fallback template set is.
  206.       *
  207.       * NOTE that if the fallback setting cannot be found in the
  208.       * main SquirrelMail configuration settings that the value
  209.       * of $default is returned.
  210.       *
  211.       * @param string $default The template set ID to use if
  212.       *                         the fallback setting cannot be
  213.       *                         found in SM config (optional;
  214.       *                         defaults to "default").
  215.       *
  216.       * @return string The ID of the fallback template set.
  217.       *
  218.       * @static
  219.       *
  220.       */
  221.     function get_fallback_template_set($default='default'{
  222.  
  223. // FIXME: do we want to place any restrictions on the ID such as
  224. //        making sure no slashes included?
  225.  
  226.         // values are in main SM config file
  227.         //
  228.         global $templateset_fallback$aTemplateSet;
  229.         $aTemplateSet (!isset($aTemplateSet|| !is_array($aTemplateSet
  230.                          ? array($aTemplateSet);
  231.         $templateset_fallback (!isset($templateset_fallback
  232.                                  ? $templateset_fallback);
  233.  
  234.         return (!empty($aTemplateSet[$templateset_fallback]['ID'])
  235.                 ? $aTemplateSet[$templateset_fallback]['ID'$default);
  236.  
  237.     }
  238.  
  239.     /**
  240.       * Determine what the default template set is.
  241.       *
  242.       * NOTE that if the default setting cannot be found in the
  243.       * main SquirrelMail configuration settings that the value
  244.       * of $default is returned.
  245.       *
  246.       * @param string $default The template set ID to use if
  247.       *                         the default setting cannot be
  248.       *                         found in SM config (optional;
  249.       *                         defaults to "default").
  250.       *
  251.       * @return string The ID of the default template set.
  252.       *
  253.       * @static
  254.       *
  255.       */
  256.     function get_default_template_set($default='default'{
  257.  
  258. // FIXME: do we want to place any restrictions on the ID such as
  259. //        making sure no slashes included?
  260.  
  261.         // values are in main SM config file
  262.         //
  263.         global $templateset_default$aTemplateSet;
  264.         $aTemplateSet (!isset($aTemplateSet|| !is_array($aTemplateSet)
  265.                          ? array($aTemplateSet);
  266.         $templateset_default (!isset($templateset_default
  267.                                  ? $templateset_default);
  268.  
  269.         return (!empty($aTemplateSet[$templateset_default]['ID'])
  270.                 ? $aTemplateSet[$templateset_default]['ID'$default);
  271.  
  272.     }
  273.  
  274.     /**
  275.       * Instantiate and return correct subclass for this template
  276.       * set's templating engine.
  277.       *
  278.       * @param string $template_set_id The template set whose engine
  279.       *                                 is to be used as an override
  280.       *                                 (if not given, this template
  281.       *                                 set's engine is used) (optional).
  282.       *
  283.       * @return object The Template subclass object for the template engine.
  284.       *
  285.       */
  286.     function get_template_engine_subclass($template_set_id=''{
  287.  
  288.         if (empty($template_set_id)) $template_set_id $this->template_set_id;
  289.         // FIXME: assuming PHP template engine may not necessarily be a good thing
  290.         $engine Template::get_template_config($template_set_id
  291.                                                 'template_engine'SQ_PHP_TEMPLATE);
  292.         
  293.  
  294.         $engine_class_file SM_PATH 'class/template/' 
  295.                            . $engine 'Template.class.php';
  296.  
  297.         if (!file_exists($engine_class_file)) {
  298.             trigger_error('Unknown template engine (' $engine 
  299.                         . ') was specified in template configuration file',
  300.                          E_USER_ERROR);
  301.         }
  302.  
  303.         $engine_class $engine 'Template';
  304.         require_once($engine_class_file);
  305.         return new $engine_class($template_set_id);
  306.  
  307.     }
  308.  
  309.     /**
  310.       * Determine the relative template directory path for
  311.       * the given template ID.
  312.       *
  313.       * @param string $template_set_id The template ID from which to build
  314.       *                                 the directory path
  315.       *
  316.       * @return string The relative template path (based off of SM_PATH)
  317.       *
  318.       * @static
  319.       *
  320.       */
  321.     function calculate_template_file_directory($template_set_id{
  322.  
  323.         return 'templates/' $template_set_id '/';
  324.  
  325.     }
  326.  
  327.     /**
  328.       * Determine the relative images directory path for
  329.       * the given template ID.
  330.       *
  331.       * @param string $template_set_id The template ID from which to build
  332.       *                                 the directory path
  333.       *
  334.       * @return string The relative images path (based off of SM_PATH)
  335.       *
  336.       * @static
  337.       *
  338.       */
  339.     function calculate_template_images_directory($template_set_id{
  340.  
  341.         return 'templates/' $template_set_id '/images/';
  342.  
  343.     }
  344.  
  345.     /**
  346.       * Return the relative template directory path for this template set.
  347.       *
  348.       * @return string The relative path to the template directory based
  349.       *                 from the main SquirrelMail directory (SM_PATH).
  350.       *
  351.       */
  352.     function get_template_file_directory({
  353.  
  354.         return $this->template_dir;
  355.  
  356.     }
  357.  
  358.     /**
  359.       * Return the template ID for the fallback template set.
  360.       *
  361.       * @return string The ID of the fallback template set.
  362.       *
  363.       */
  364.     function get_fallback_template_set_id({
  365.  
  366.         return $this->fallback_template_set_id;
  367.  
  368.     }
  369.  
  370.     /**
  371.       * Return the relative template directory path for the
  372.       * fallback template set.
  373.       *
  374.       * @return string The relative path to the fallback template
  375.       *                 directory based from the main SquirrelMail
  376.       *                 directory (SM_PATH).
  377.       *
  378.       */
  379.  
  380.         return $this->fallback_template_dir;
  381.  
  382.     }
  383.  
  384.     /**
  385.       * Get template set config setting
  386.       *
  387.       * Given a template set ID and setting name, returns the
  388.       * setting's value.  Note that settings are cached in
  389.       * session, so "live" changes to template configuration
  390.       * won't be reflected until the user logs out and back
  391.       * in again.
  392.       *
  393.       * @param string  $template_set_id The template set for which
  394.       *                                  to look up the setting.
  395.       * @param string  $setting         The name of the setting to
  396.       *                                  retrieve.
  397.       * @param mixed   $default         When the requested setting
  398.       *                                  is not found, the contents
  399.       *                                  of this value are returned
  400.       *                                  instead (optional; default
  401.       *                                  is NULL).
  402.       *                                  NOTE that unlike sqGetGlobalVar(),
  403.       *                                  this function will also return
  404.       *                                  the default value if the
  405.       *                                  requested setting is found
  406.       *                                  but is empty.
  407.       * @param boolean $live_config     When TRUE, the target template
  408.       *                                  set's configuration file is
  409.       *                                  reloaded every time this
  410.       *                                  method is called.  Default
  411.       *                                  behavior is to only load the
  412.       *                                  configuration file if it had
  413.       *                                  never been loaded before, but
  414.       *                                  not again after that (optional;
  415.       *                                  default FALSE).  Use with care!
  416.       *                                  Should mostly be used for
  417.       *                                  debugging.
  418.       *
  419.       * @return mixed The desired setting's value or if not found,
  420.       *                the contents of $default are returned.
  421.       *
  422.       * @static
  423.       *
  424.       */
  425.     function get_template_config($template_set_id$setting
  426.                                  $default=NULL$live_config=FALSE{
  427.  
  428.         sqGetGlobalVar('template_configuration_settings'
  429.                        $template_configuration_settings
  430.                        SQ_SESSION
  431.                        array());
  432.  
  433.         if ($live_configunset($template_configuration_settings[$template_set_id]);
  434.  
  435.  
  436.         // NOTE: could use isset() instead of empty() below, but
  437.         //       this function is designed to replace empty values
  438.         //       as well as non-existing values with $default
  439.         //
  440.         if (!empty($template_configuration_settings[$template_set_id][$setting]))
  441.            return $template_configuration_settings[$template_set_id][$setting];
  442.  
  443.  
  444.         // if template set configuration has been loaded, but this 
  445.         // setting is not known, return $default
  446.         //
  447.         if (!empty($template_configuration_settings[$template_set_id]))
  448.            return $default;
  449.  
  450.  
  451.         // otherwise (template set configuration has not been loaded before), 
  452.         // load it into session and return the desired setting after that
  453.         //
  454.         $template_config_file SM_PATH 
  455.                      . Template::calculate_template_file_directory($template_set_id)
  456.                      . 'config.php';
  457.  
  458.         if (!file_exists($template_config_file)) {
  459.  
  460.             trigger_error('No template configuration file was found where expected: ("' 
  461.                         . $template_config_file '")'E_USER_ERROR);
  462.  
  463.         else {
  464.  
  465.             // we require() the file to let PHP do the variable value
  466.             // parsing for us, and read the file in manually so we can
  467.             // know what variable names are used in the config file 
  468.             // (settings can be different depending on specific requirements
  469.             // of different template engines)... the other way this may
  470.             // be accomplished is to somehow diff the symbol table 
  471.             // before/after the require(), but anyway, this code should
  472.             // only run once for this template set...
  473.             //
  474.             require($template_config_file);
  475.             $file_contents implode("\n"file($template_config_file));
  476.  
  477.  
  478.             // note that this assumes no template settings have
  479.             // a string in them that looks like a variable name like $x
  480.             // also note that this will attempt to grab things like
  481.             // $Id found in CVS headers, so we try to adjust for that
  482.             // by checking that the variable is actually set
  483.             //
  484.             preg_match_all('/\$(\w+)/'$file_contents$variablesPREG_PATTERN_ORDER);
  485.             foreach ($variables[1as $variable{
  486.                 if (isset($$variable))
  487.                     $template_configuration_settings[$template_set_id][$variable
  488.                         = $$variable;
  489.             }
  490.  
  491.             sqsession_register($template_configuration_settings
  492.                                'template_configuration_settings');
  493.  
  494.             // NOTE: could use isset() instead of empty() below, but
  495.             //       this function is designed to replace empty values
  496.             //       as well as non-existing values with $default
  497.             //
  498.             if (!empty($template_configuration_settings[$template_set_id][$setting]))
  499.                 return $template_configuration_settings[$template_set_id][$setting];
  500.             else
  501.                 return $default;
  502.  
  503.         }
  504.  
  505.     }
  506.  
  507.     /** 
  508.       * Obtain template file hierarchy from cache.
  509.       *
  510.       * If the file hierarchy does not exist in session, it is
  511.       * constructed and stored in session before being returned
  512.       * to the caller.
  513.       *
  514.       * @param boolean $regenerate_cache When TRUE, the file hierarchy
  515.       *                                   is reloaded and stored fresh
  516.       *                                   (optional; default FALSE).
  517.       * @param array   $additional_files Must be in same form as the
  518.       *                                   files in the file hierarchy
  519.       *                                   cache.  These are then added
  520.       *                                   to the cache (optional; default
  521.       *                                   empty - no additional files).
  522.       *
  523.       * @return array Template file hierarchy array, whose keys
  524.       *                are all the template file names (with path
  525.       *                information relative to the template set's
  526.       *                base directory, e.g., "css/style.css")
  527.       *                found in all parent template sets including
  528.       *                the ultimate fall-back template set.
  529.       *                Array values are sub-arrays with the
  530.       *                following key-value pairs:
  531.       *
  532.       *                  PATH    --  file path, relative to SM_PATH
  533.       *                  SET_ID  --  the ID of the template set that this file belongs to
  534.       *                  ENGINE  --  the engine needed to render this template file
  535.       *
  536.       * @static
  537.       *
  538.       */
  539.     function cache_template_file_hierarchy($regenerate_cache=FALSE,
  540.                                            $additional_files=array()) {
  541.  
  542.         sqGetGlobalVar('template_file_hierarchy'$template_file_hierarchy
  543.                        SQ_SESSIONarray());
  544.  
  545.  
  546.         if ($regenerate_cacheunset($template_file_hierarchy);
  547.  
  548.  
  549.         if (!empty($template_file_hierarchy)) {
  550.  
  551.             // have to add additional files if given before returning
  552.             //
  553.             if (!empty($additional_files)) {
  554.                 $template_file_hierarchy array_merge($template_file_hierarchy
  555.                                                        $additional_files);
  556.                 sqsession_register($template_file_hierarchy,
  557.                                    'template_file_hierarchy');
  558.             }
  559.  
  560.             return $template_file_hierarchy;
  561.         }
  562.  
  563.  
  564.         // nothing in cache apparently, so go build it now
  565.         //
  566.         // FIXME: not sure if there is any possibility that 
  567.         //        this could be called when $sTemplateID has
  568.         //        yet to be defined... throw error for now,
  569.         //        but if the error occurs, it's a coding error
  570.         //        rather than a configuration error
  571.         //
  572.         global $sTemplateID;
  573.         if (empty($sTemplateID)) {
  574.  
  575.             trigger_error('Template set ID unknown'E_USER_ERROR);
  576.  
  577.         else {
  578.  
  579.             $template_file_hierarchy Template::catalog_template_files($sTemplateID);
  580.  
  581.             // additional files, if any
  582.             //
  583.             if (!empty($additional_files)) {
  584.                 $template_file_hierarchy array_merge($template_file_hierarchy
  585.                                                        $additional_files);
  586.             }
  587.  
  588.             sqsession_register($template_file_hierarchy
  589.                                'template_file_hierarchy');
  590.  
  591.             return $template_file_hierarchy;
  592.  
  593.         }
  594.  
  595.     }
  596.  
  597.     /**
  598.       * Traverse template hierarchy and catalogue all template
  599.       * files (for storing in cache).
  600.       * 
  601.       * Paths to all files in all parent, grand-parent, great grand
  602.       * parent, etc. template sets (including the fallback template)
  603.       * are catalogued; for identically named files, the file earlier
  604.       * in the hierarchy (closest to this template set) is used.
  605.       * 
  606.       * @param string $template_set_id The template set in which to
  607.       *                                 search for files
  608.       * @param array  $file_list       The file list so far to be added
  609.       *                                 to (allows recursive behavior)
  610.       *                                 (optional; default empty array).
  611.       * @param string $directory       The directory in which to search for
  612.       *                                 files (must be given as full path).
  613.       *                                 If empty, starts at top-level template
  614.       *                                 set directory (optional; default empty).
  615.       *                                 NOTE!  Use with care, as behavior is
  616.       *                                 unpredictable if directory given is not
  617.       *                                 part of correct template set.
  618.       * 
  619.       * @return mixed The top-level caller will have an array of template
  620.       *                files returned to it; recursive calls to this function
  621.       *                do not receive any return value at all.  The format
  622.       *                of the template file array is as described for the
  623.       *                Template class attribute $template_file_cache
  624.       *
  625.       * @static
  626.       *
  627.       */
  628.     function catalog_template_files($template_set_id$file_list=array()$directory=''{
  629.  
  630.         $template_base_dir SM_PATH 
  631.                            . Template::calculate_template_file_directory($template_set_id);
  632.  
  633.         if (empty($directory)) {
  634.             $directory $template_base_dir;
  635.         }
  636.  
  637.         $files_and_dirs list_files($directory''FALSETRUEFALSETRUE);
  638.  
  639.         // recurse for all the subdirectories in the template set
  640.         //
  641.         foreach ($files_and_dirs['DIRECTORIES'as $dir{
  642.             $file_list Template::catalog_template_files($template_set_id$file_list$dir);
  643.         }
  644.  
  645.         // place all found files in the cache
  646.         // FIXME: assuming PHP template engine may not necessarily be a good thing
  647.         //
  648.         $engine Template::get_template_config($template_set_id
  649.                                                 'template_engine'SQ_PHP_TEMPLATE);
  650.         foreach ($files_and_dirs['FILES'as $file{
  651.  
  652.             // remove the part of the file path corresponding to the
  653.             // template set's base directory
  654.             //
  655.             $relative_file substr($filestrlen($template_base_dir));
  656.  
  657.             // only put file in cache if not already found in earlier template
  658.             //
  659.             if (!isset($file_list[$relative_file])) {
  660.                 $file_list[$relative_filearray(
  661.                                                      'PATH'   => $file,
  662.                                                      'SET_ID' => $template_set_id,
  663.                                                      'ENGINE' => $engine,
  664.                                                   );
  665.             }
  666.  
  667.         }
  668.  
  669.  
  670.         // now if we are currently at the top-level of the template
  671.         // set base directory, we need to move on to the parent 
  672.         // template set, if any
  673.         //
  674.         if ($directory == $template_base_dir{
  675.  
  676.             // use fallback when we run out of parents
  677.             //
  678.             $fallback_id Template::get_fallback_template_set();
  679.             $parent_id Template::get_template_config($template_set_id
  680.                                                        'parent_template_set'
  681.                                                        $fallback_id);
  682.  
  683.             // were we already all the way to the last level? just exit
  684.             //
  685.             // note that this code allows the fallback set to have
  686.             // a parent, too, but can result in endless loops
  687.             // if ($parent_id == $template_set_id) {
  688.             //
  689.             if ($fallback_id == $template_set_id{
  690.                return $file_list;
  691.             }
  692.  
  693.             $file_list Template::catalog_template_files($parent_id$file_list);
  694.  
  695.         }
  696.  
  697.         return $file_list;
  698.  
  699.     }
  700.  
  701.     /**
  702.       * Look for a template file in a plugin; add to template
  703.       * file cache if found.
  704.       *
  705.       * The file is searched for in the following order:
  706.       *
  707.       *  - A directory for the current template set within the plugin:
  708.       *       SM_PATH/plugins/<plugin name>/templates/<template name>/
  709.       *  - In a directory for one of the current template set's ancestor
  710.       *    (inherited) template sets within the plugin:
  711.       *       SM_PATH/plugins/<plugin name>/templates/<parent template name>/
  712.       *  - In a directory for the fallback template set within the plugin:
  713.       *       SM_PATH/plugins/<plugin name>/templates/<fallback template name>/
  714.       *
  715.       * @param string $plugin          The name of the plugin
  716.       * @param string $file            The name of the template file
  717.       * @param string $template_set_id The ID of the template for which
  718.       *                                 to start looking for the file
  719.       *                                 (optional; default is current
  720.       *                                 template set ID).
  721.       *
  722.       * @return boolean TRUE if the template file was found, FALSE otherwise.
  723.       *
  724.       */
  725.     function find_and_cache_plugin_template_file($plugin$file$template_set_id=''{
  726.  
  727.         if (empty($template_set_id))
  728.             $template_set_id $this->template_set_id;
  729.  
  730.         $file_path SM_PATH 'plugins/' $plugin '/'
  731.                    . $this->calculate_template_file_directory($template_set_id
  732.                    . $file;
  733.  
  734.         if (file_exists($file_path)) {
  735.             // FIXME: assuming PHP template engine may not necessarily be a good thing
  736.             $engine $this->get_template_config($template_set_id
  737.                                                  'template_engine'SQ_PHP_TEMPLATE);
  738.             $file_list array('plugins/' $plugin '/' $file => array(
  739.                                                       'PATH'   => $file_path,
  740.                                                       'SET_ID' => $template_set_id,
  741.                                                       'ENGINE' => $engine,
  742.                                                                           )
  743.                               );
  744.             $this->template_file_cache 
  745.                 = $this->cache_template_file_hierarchy(FALSE$file_list);
  746.             return TRUE;
  747.         }
  748.  
  749.  
  750.         // not found yet, try parent template set
  751.         // (use fallback when we run out of parents)
  752.         //
  753.         $fallback_id $this->get_fallback_template_set();
  754.         $parent_id $this->get_template_config($template_set_id
  755.                                                 'parent_template_set'
  756.                                                 $fallback_id);
  757.  
  758.         // were we already all the way to the last level? just exit
  759.         //
  760.         // note that this code allows the fallback set to have
  761.         // a parent, too, but can result in endless loops
  762.         // if ($parent_id == $template_set_id) {
  763.         //
  764.         if ($fallback_id == $template_set_id{
  765.             return FALSE;
  766.         }
  767.  
  768.         return $this->find_and_cache_plugin_template_file($plugin$file$parent_id);
  769.  
  770.     }
  771.  
  772.     /**
  773.       * Find the right template file.
  774.       *
  775.       * The template file is taken from the template file cache, thus
  776.       * the file is taken from the current template, one of its
  777.       * ancestors or the fallback template.
  778.       *
  779.       * Note that it is perfectly acceptable to load template files from
  780.       * template subdirectories.  For example, JavaScript templates found
  781.       * in the js/ subdirectory would be loaded by passing
  782.       * "js/<javascript file name>" as the $filename.
  783.       *
  784.       * Note that the caller can also ask for ALL files in a directory
  785.       * (and those in the same directory for all ancestor template sets)
  786.       * by giving a $filename that is a directory name (ending with a
  787.       * slash).
  788.       *
  789.       * If not found and the file is a plugin template file (indicated
  790.       * by the presence of "plugins/" on the beginning of $filename),
  791.       * the target plugin is searched for a substitue template file
  792.       * before just returning nothing.
  793.       *
  794.       * Plugin authors must note that the $filename MUST be prefaced
  795.       * with "plugins/<plugin name>/" in order to correctly resolve the
  796.       * template file.
  797.       *
  798.       * @param string $filename The name of the template file,
  799.       *                          possibly prefaced with
  800.       *                          "plugins/<plugin name>/"
  801.       *                          indicating that it is a plugin
  802.       *                          template, or ending with a
  803.       *                          slash, indicating that all files
  804.       *                          for that directory name should
  805.       *                          be returned.
  806.       *
  807.       * @return mixed The full path to the template file or a list
  808.       *                of all files in the given directory if $filename
  809.       *                ends with a slash; if not found, an empty string
  810.       *                is returned.  The caller is responsible for
  811.       *                throwing errors or other actions if template
  812.       *                file is not found.
  813.       *
  814.       */
  815.     function get_template_file_path($filename{
  816.  
  817.         // return list of all files in a directory (and that
  818.         // of any ancestors)
  819.         //
  820.         if ($filename{strlen($filename1== '/'{
  821.  
  822.             $return_array array();
  823.             foreach ($this->template_file_cache as $file => $file_info{
  824.  
  825.                 // only want files in the requested directory
  826.                 // (AND not in a subdirectory!)
  827.                 //
  828.                 if (strpos($file$filename=== 
  829.                  && strpos($file'/'strlen($filename)) === FALSE)
  830.                     $return_array[$file_info['PATH'];
  831.  
  832.             }
  833.             return $return_array;
  834.  
  835.         }
  836.  
  837.         // figure out what to do with files not found
  838.         //
  839.         if (empty($this->template_file_cache[$filename]['PATH'])) {
  840.  
  841.             // plugins get one more chance below; any other
  842.             // files we just give up now
  843.             //
  844.             if (strpos($filename'plugins/'!== 0
  845.                 return '';
  846.  
  847.             $plugin_name substr($filename8strpos($filename'/'88);
  848.             $file substr($filenamestrlen($plugin_name9);
  849.  
  850.             if (!$this->find_and_cache_plugin_template_file($plugin_name$file))
  851.                 return '';
  852.  
  853.         }
  854.  
  855.         return $this->template_file_cache[$filename]['PATH'];
  856.  
  857.     }
  858.  
  859.     /**
  860.       * Get template engine needed to render given template file.
  861.       *
  862.       * If at all possible, just returns a reference to $this, but
  863.       * some template files may require a different engine, thus
  864.       * an object for that engine (which will subsequently be kept
  865.       * in this object for future use) is returned.
  866.       *
  867.       * @param string $filename The name of the template file,
  868.       *
  869.       * @return object The needed template object to render the template.
  870.       * 
  871.       */
  872.     function get_rendering_template_engine_object($filename{
  873.         
  874.         // for files that we cannot find engine info for,
  875.         // just return $this
  876.         //  
  877.         if (empty($this->template_file_cache[$filename]['ENGINE']))
  878.             return $this;
  879.  
  880.             
  881.         // otherwise, compare $this' engine to the file's engine
  882.         //
  883.         $engine $this->template_file_cache[$filename]['ENGINE'];
  884.         if ($this->template_engine == $engine)
  885.             return $this;
  886.  
  887.  
  888.         // need to load another engine... if already instantiated,
  889.         // and stored herein, return that
  890.         // FIXME: this assumes same engine setup in all template 
  891.         //        set config files that have same engine in common
  892.         //        (but keeping a separate class object for every
  893.         //        template set seems like overkill... for now we 
  894.         //        won't do that unless it becomes a problem)
  895.         //
  896.         if (!empty($this->other_template_engine_objects[$engine])) {
  897.             $rendering_engine $this->other_template_engine_objects[$engine];
  898.  
  899.  
  900.         // otherwise, instantiate new engine object, add to cache
  901.         // and return it
  902.         //
  903.         else {
  904.             $template_set_id $this->template_file_cache[$filename]['SET_ID'];
  905.             $this->other_template_engine_objects[$engine]
  906.                 = $this->get_template_engine_subclass($template_set_id);
  907.             $rendering_engine $this->other_template_engine_objects[$engine];
  908.         }
  909.  
  910.  
  911.         // now, need to copy over all the assigned variables
  912.         // from $this to the rendering engine (YUCK! -- we need
  913.         // to discourage template authors from creating
  914.         // situations where engine changes occur)
  915.         //
  916.         $rendering_engine->clear_all_assign();
  917.         $rendering_engine->assign($this->get_template_vars());
  918.  
  919.  
  920.         // finally ready to go
  921.         //
  922.         return $rendering_engine;
  923.  
  924.     }
  925.  
  926.     /** 
  927.       * Return all JavaScript files provided by the template.
  928.       * 
  929.       * All files found in the template set's "js" directory (and
  930.       * that of its ancestors) with the extension ".js" are returned.
  931.       *
  932.       * @param boolean $full_path When FALSE, only the file names
  933.       *                            are included in the return array;
  934.       *                            otherwise, path information is
  935.       *                            included (relative to SM_PATH)
  936.       *                            (OPTIONAL; default only file names)
  937.       *     
  938.       * @return array The required file names/paths.
  939.       *     
  940.       */
  941.     function get_javascript_includes($full_path=FALSE{
  942.  
  943.         // since any page from a parent template set 
  944.         // could end up being loaded, we have to load
  945.         // all js files from ancestor template sets,
  946.         // not just this set
  947.         //
  948.         //$directory = SM_PATH . $this->get_template_file_directory() . 'js';
  949.         //$js_files = list_files($directory, '.js', !$full_path);
  950.         //
  951.         $js_files $this->get_template_file_path('js/');
  952.         
  953.         
  954.         // parse out .js files only
  955.         //
  956.         $return_array array();
  957.         foreach ($js_files as $file{
  958.     
  959.             if (substr($filestrlen($file3!= '.js'continue;
  960.  
  961.             if ($full_path{
  962.                 $return_array[$file;
  963.             else {
  964.                 $return_array[basename($file);
  965.             }
  966.  
  967.         }
  968.  
  969.         return $return_array;
  970.  
  971.     }
  972.  
  973.     /**
  974.       * Return all alternate stylesheets provided by template.
  975.       *
  976.       * All files found in the template set's "css/alternates"
  977.       * directory (and that of its ancestors) with the extension
  978.       * ".css" are returned.
  979.       *
  980.       * Note that prettified names are constructed herein by
  981.       * taking the file name, changing underscores to spaces,
  982.       * removing the ".css" from the end of the file, and
  983.       * capitalizing each word in the resultant name.
  984.       *
  985.       * @param boolean $full_path When FALSE, only the file names
  986.       *                            are included in the return array;
  987.       *                            otherwise, path information is
  988.       *                            included (relative to SM_PATH)
  989.       *                            (OPTIONAL; default only file names)
  990.       *
  991.       * @return array A list of the available alternate stylesheets,
  992.       *                where the keys are the file names (formatted
  993.       *                according to $full_path) for the stylesheets,
  994.       *                and the values are the prettified version of
  995.       *                the file names for display to the user.
  996.       *               
  997.       */
  998.     function get_alternative_stylesheets($full_path=FALSE{
  999.  
  1000.         // since any page from a parent template set
  1001.         // could end up being loaded, we will load
  1002.         // all alternate css files from ancestor 
  1003.         // template sets, not just this set
  1004.         //
  1005.         //$directory = SM_PATH . $this->get_template_file_directory() . 'css/alternates';
  1006.         //$css_files = list_files($directory, '.css', !$full_path);
  1007.         //
  1008.         $css_files $this->get_template_file_path('css/alternates/');
  1009.  
  1010.  
  1011.         // parse out .css files only
  1012.         //
  1013.         $return_array array();
  1014.         foreach ($css_files as $file{
  1015.  
  1016.             if (substr($filestrlen($file4!= '.css'continue;
  1017.  
  1018.             $pretty_name ucwords(str_replace('_'' 'substr(basename($file)0-4)));
  1019.  
  1020.             if ($full_path{
  1021.                 $return_array[$file$pretty_name;
  1022.             else {
  1023.                 $return_array[basename($file)$pretty_name;
  1024.             }
  1025.  
  1026.         }
  1027.  
  1028.         return $return_array;
  1029.  
  1030.     }
  1031.  
  1032.     /**
  1033.       * Return all standard stylsheets provided by the template.
  1034.       *
  1035.       * All files found in the template set's "css" directory (and
  1036.       * that of its ancestors) with the extension ".css" except
  1037.       * "rtl.css" (which is dealt with separately) are returned.
  1038.       *
  1039.       * @param boolean $full_path When FALSE, only the file names
  1040.       *                            are included in the return array;
  1041.       *                            otherwise, path information is
  1042.       *                            included (relative to SM_PATH)
  1043.       *                            (OPTIONAL; default only file names)
  1044.       *
  1045.       * @return array The required file names/paths.
  1046.       *
  1047.       */
  1048.     function get_stylesheets($full_path=FALSE{
  1049.  
  1050.         // since any page from a parent template set 
  1051.         // could end up being loaded, we have to load
  1052.         // all css files from ancestor template sets,
  1053.         // not just this set
  1054.         //
  1055.         //$directory = SM_PATH . $this->get_template_file_directory() . 'css';
  1056.         //$css_files = list_files($directory, '.css', !$full_path);
  1057.         //
  1058.         $css_files $this->get_template_file_path('css/');
  1059.  
  1060.  
  1061.         // need to leave out "rtl.css" 
  1062.         //
  1063.         $return_array array();
  1064.         foreach ($css_files as $file{
  1065.  
  1066.             if (substr($filestrlen($file4!= '.css'continue;
  1067.             if (strtolower(basename($file)) == 'rtl.css'continue;
  1068.  
  1069.             if ($full_path{
  1070.                 $return_array[$file;
  1071.             else {
  1072.                 $return_array[basename($file);
  1073.             }
  1074.  
  1075.         }
  1076.  
  1077.  
  1078.         // return sheets for the current template set
  1079.         // last so we can enable any custom overrides 
  1080.         // of styles in ancestor sheets 
  1081.         //
  1082.         return array_reverse($return_array);
  1083.  
  1084.     }
  1085.  
  1086.     /**
  1087.       * Generate links to all this template set's standard stylesheets
  1088.       *
  1089.       * Subclasses can override this function if stylesheets are
  1090.       * created differently for the template set's target output
  1091.       * interface.
  1092.       *
  1093.       * @return string The stylesheet links as they should be sent
  1094.       *                 to the browser.
  1095.       *
  1096.       */
  1097.     {
  1098.  
  1099.         $sheets $this->get_stylesheets(TRUE);
  1100.         return $this->fetch_external_stylesheet_links($sheets);
  1101.  
  1102.     }
  1103.  
  1104.     /**
  1105.       * Push out any other stylesheet links as provided (for
  1106.       * stylesheets not included with the current template set)
  1107.       *
  1108.       * Subclasses can override this function if stylesheets are
  1109.       * created differently for the template set's target output
  1110.       * interface.
  1111.       *
  1112.       * @param mixed $sheets List of the desired stylesheets
  1113.       *                       (file path to be used in stylesheet
  1114.       *                       href attribute) to output (or single
  1115.       *                       stylesheet file path).
  1116. FIXME: We could make the incoming array more complex so it can 
  1117.        also contain the other parameters for create_css_link()
  1118.        such as $name, $alt, $mtype, and $xhtml_end
  1119.        But do we need to?
  1120.       *
  1121.       * @return string The stylesheet links as they should be sent
  1122.       *                 to the browser.
  1123.       *
  1124.       */
  1125.     function fetch_external_stylesheet_links($sheets)
  1126.     {
  1127.  
  1128.         if (!is_array($sheets)) $sheets array($sheets);
  1129.         $output '';
  1130.  
  1131.         foreach ($sheets as $sheet{
  1132.             $output .= create_css_link($sheet);
  1133.         }
  1134.  
  1135.         return $output;
  1136.  
  1137.     }
  1138.  
  1139.     /**
  1140.       * Send HTTP header(s) to browser.
  1141.       *
  1142.       * Subclasses can override this function if headers are
  1143.       * managed differently in the template set's target output
  1144.       * interface.
  1145.       *
  1146.       * @param mixed $headers A list of (or a single) header
  1147.       *                        text to be sent.
  1148.       *
  1149.       */
  1150.     function header($headers)
  1151.     {
  1152.  
  1153.         if (!is_array($headers)) $headers array($headers);
  1154.  
  1155.         foreach ($headers as $header{
  1156.             header($header);
  1157.         }
  1158.  
  1159.     }
  1160.  
  1161.     /**
  1162.       * Generate a link to the right-to-left stylesheet for
  1163.       * this template set by getting the "rtl.css" file from
  1164.       * this template set, its parent (or grandparent, etc.)
  1165.       * template set, the fall-back template set, or finally,
  1166.       * fall back to SquirrelMail's own "rtl.css" if need be.
  1167.       *
  1168.       * Subclasses can override this function if stylesheets are
  1169.       * created differently for the template set's target output
  1170.       * interface.
  1171.       *
  1172.       * @return string The stylesheet link as it should be sent
  1173.       *                 to the browser.
  1174.       *
  1175.       */
  1176.     {
  1177.  
  1178.         // get right template file
  1179.         //
  1180.         $sheet $this->get_template_file_path('css/rtl.css');
  1181.  
  1182.         // fall back to SquirrelMail's own default stylesheet
  1183.         //
  1184.         if (empty($sheet)) {
  1185.             $sheet SM_PATH 'css/rtl.css';
  1186.         }
  1187.  
  1188.         return create_css_link($sheet);
  1189.  
  1190.     }
  1191.  
  1192.     /**
  1193.       * Display the template
  1194.       *
  1195.       * @param string $file The template file to use
  1196.       *
  1197.       */
  1198.     function display($file)
  1199.     {
  1200.  
  1201.         echo $this->fetch($file);
  1202.  
  1203.     }
  1204.  
  1205.     /**
  1206.       * Applies the template and returns the resultant content string.
  1207.       *
  1208.       * @param string $file The template file to use
  1209.       *
  1210.       * @return string The template contents after applying the given template
  1211.       *
  1212.       */
  1213.     function fetch($file{
  1214.  
  1215.         // get right template file
  1216.         //
  1217.         $template $this->get_template_file_path($file);
  1218.  
  1219.  
  1220.         // special case stylesheet.tpl falls back to SquirrelMail's 
  1221.         // own default stylesheet
  1222.         //
  1223.         if (empty($template&& $file == 'css/stylesheet.tpl'{
  1224.             $template SM_PATH 'css/default.css';
  1225.         }
  1226.  
  1227.  
  1228.         if (empty($template)) {
  1229.  
  1230.             trigger_error('The template "' htmlspecialchars($file)
  1231.                           . '" could not be fetched!'E_USER_ERROR);
  1232.  
  1233.         else {
  1234.  
  1235.             $aPluginOutput array();
  1236.             $aPluginOutput concat_hook_function('template_construct_' $file,
  1237.                                                   array($aPluginOutput$this));
  1238.             $this->assign('plugin_output'$aPluginOutput);
  1239.  
  1240.             //$output = $this->apply_template($template);
  1241.             $rendering_engine $this->get_rendering_template_engine_object($file);
  1242.             $output $rendering_engine->apply_template($template);
  1243.  
  1244.             // CAUTION: USE OF THIS HOOK IS HIGHLY DISCOURAGED AND CAN
  1245.             // RESULT IN NOTICABLE PERFORMANCE DEGREDATION.  Plugins
  1246.             // using this hook will probably be rejected by the
  1247.             // SquirrelMail team.
  1248.             //
  1249.             $output filter_hook_function('template_output'$output);
  1250.  
  1251.             return $output;
  1252.  
  1253.         }
  1254.  
  1255.     }
  1256.  
  1257.     /**
  1258.       * Assigns values to template variables
  1259.       *
  1260.       * Note: this is an abstract method that must be implemented by subclass.
  1261.       *
  1262.       * @param array|string$tpl_var the template variable name(s)
  1263.       * @param mixed $value the value to assign
  1264.       *
  1265.       */
  1266.     function assign($tpl_var$value NULL{
  1267.  
  1268.         trigger_error('Template subclass (' $this->template_engine . 'Template.class.php) needs to implement the assign() method.'E_USER_ERROR);
  1269.  
  1270.     }
  1271.  
  1272.     /**
  1273.       * Assigns values to template variables by reference
  1274.       *
  1275.       * Note: this is an abstract method that must be implemented by subclass.
  1276.       *
  1277.       * @param string $tpl_var the template variable name
  1278.       * @param mixed $value the referenced value to assign
  1279.       *
  1280.       */
  1281.     function assign_by_ref($tpl_var&$value{
  1282.  
  1283.         trigger_error('Template subclass (' $this->template_engine . 'Template.class.php) needs to implement the assign_by_ref() method.'E_USER_ERROR);
  1284.  
  1285.     }
  1286.  
  1287.     /**
  1288.       * Clears the values of all assigned varaiables.
  1289.       *
  1290.       */
  1291.     function clear_all_assign({
  1292.  
  1293.         trigger_error('Template subclass (' $this->template_engine . 'Template.class.php) needs to implement the clear_all_assign() method.'E_USER_ERROR);
  1294.  
  1295.     }
  1296.  
  1297.     /**
  1298.       * Returns assigned variable value(s).
  1299.       *
  1300.       * @param string $varname If given, the value of that variable
  1301.       *                         is returned, assuming it has been
  1302.       *                         previously assigned.  If not specified
  1303.       *                         an array of all assigned variables is
  1304.       *                         returned. (optional)
  1305.       *
  1306.       * @return mixed Desired single variable value or list of all
  1307.       *                assigned variable values.
  1308.       *
  1309.       */
  1310.     function get_template_vars($varname=NULL{
  1311.  
  1312.         trigger_error('Template subclass (' $this->template_engine . 'Template.class.php) needs to implement the get_template_vars() method.'E_USER_ERROR);
  1313.  
  1314.     }
  1315.  
  1316.     /**
  1317.       * Appends values to template variables
  1318.       *
  1319.       * Note: this is an abstract method that must be implemented by subclass.
  1320.       *
  1321.       * @param array|string$tpl_var the template variable name(s)
  1322.       * @param mixed $value the value to append
  1323.       * @param boolean $merge when $value is given as an array,
  1324.       *                        this indicates whether or not that
  1325.       *                        array itself should be appended as
  1326.       *                        a new template variable value or if
  1327.       *                        that array's values should be merged
  1328.       *                        into the existing array of template
  1329.       *                        variable values
  1330.       *
  1331.       */
  1332.     function append($tpl_var$value NULL$merge FALSE{
  1333.  
  1334.         trigger_error('Template subclass (' $this->template_engine . 'Template.class.php) needs to implement the append() method.'E_USER_ERROR);
  1335.  
  1336.     }
  1337.  
  1338.     /**
  1339.       * Appends values to template variables by reference
  1340.       *
  1341.       * Note: this is an abstract method that must be implemented by subclass.
  1342.       *
  1343.       * @param string $tpl_var the template variable name
  1344.       * @param mixed $value the referenced value to append
  1345.       * @param boolean $merge when $value is given as an array,
  1346.       *                        this indicates whether or not that
  1347.       *                        array itself should be appended as
  1348.       *                        a new template variable value or if
  1349.       *                        that array's values should be merged
  1350.       *                        into the existing array of template
  1351.       *                        variable values
  1352.       *
  1353.       */
  1354.     function append_by_ref($tpl_var&$value$merge FALSE{
  1355.  
  1356.         trigger_error('Template subclass (' $this->template_engine . 'Template.class.php) needs to implement the append_by_ref() method.'E_USER_ERROR);
  1357.  
  1358.     }
  1359.  
  1360.     /**
  1361.       * Applys the template and generates final output destined
  1362.       * for the user's browser
  1363.       *
  1364.       * Note: this is an abstract method that must be implemented by subclass.
  1365.       *
  1366.       * @param string $filepath The full file path to the template to be applied
  1367.       * 
  1368.       * @return string The output for the given template
  1369.       *
  1370.       */
  1371.     function apply_template($filepath{
  1372.  
  1373.         trigger_error('Template subclass (' $this->template_engine . 'Template.class.php) needs to implement the apply_template() method.'E_USER_ERROR);
  1374.  
  1375.     }
  1376.  
  1377. }

Documentation generated on Sat, 07 Oct 2006 16:13:56 +0300 by phpDocumentor 1.3.0RC6