123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632 |
- <?php
- /**
- * Template.class.php
- *
- * This file contains an abstract (PHP 4, so "abstract" is relative)
- * class meant to define the basic template interface for the
- * SquirrelMail core application. Subclasses should extend this
- * class with any custom functionality needed to interface a target
- * templating engine with SquirrelMail.
- *
- * @copyright © 2003-2007 The SquirrelMail Project Team
- * @license http://opensource.org/licenses/gpl-license.php GNU Public License
- * @version $Id$
- * @package squirrelmail
- * @subpackage Template
- * @since 1.5.2
- *
- */
- /** load template functions */
- require(SM_PATH . 'functions/template/general_util.php');
- /**
- * The SquirrelMail Template class.
- *
- * Basic template class for capturing values and pluging them into a template.
- * This class uses a similar API to Smarty.
- *
- * Methods that must be implemented by subclasses are as follows (see method
- * stubs below for further information about expected behavior):
- *
- * assign()
- * assign_by_ref()
- * clear_all_assign()
- * get_template_vars()
- * append()
- * append_by_ref()
- * apply_template()
- *
- * @author Paul Lesniewski <paul at squirrelmail.org>
- * @package squirrelmail
- *
- */
- class Template
- {
- /**
- * The template ID
- *
- * @var string
- *
- */
- var $template_set_id = '';
- /**
- * The template set base directory (relative path from
- * the main SquirrelMail directory (SM_PATH))
- *
- * @var string
- *
- */
- var $template_dir = '';
- /**
- * The template engine (please use constants defined in constants.php)
- *
- * @var string
- *
- */
- var $template_engine = '';
- /**
- * The content type for this template set
- */
- var $content_type = '';
- /**
- * The fall-back template ID
- *
- * @var string
- *
- */
- var $fallback_template_set_id = '';
- /**
- * The fall-back template directory (relative
- * path from the main SquirrelMail directory (SM_PATH))
- *
- * @var string
- *
- */
- var $fallback_template_dir = '';
- /**
- * The fall-back template engine (please use
- * constants defined in constants.php)
- *
- * @var string
- *
- */
- var $fallback_template_engine = '';
- /**
- * 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:
- *
- * 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 $template_file_cache = 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_set_id the 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_set_id);
- }
- /**
- * Construct Template
- *
- * This method should always be called instead of trying
- * to get a Template object from the normal/default constructor,
- * and is necessary in order to control the return value.
- *
- * @param string $template_set_id the template ID
- *
- * @return object The correct Template object for the given template set
- *
- * @static
- *
- */
- function construct_template($template_set_id) {
- $template = new Template($template_set_id);
- $template->override_plugins();
- return $template->get_template_engine_subclass();
- }
- /**
- * Set up internal attributes
- *
- * This method does most of the work for setting up
- * newly constructed objects.
- *
- * @param string $template_set_id the 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_set_id = $template_set_id;
- $this->fallback_template_set_id = Template::get_fallback_template_set();
- // set up template directories
- //
- $this->template_dir
- = Template::calculate_template_file_directory($this->template_set_id);
- $this->fallback_template_dir
- = Template::calculate_template_file_directory($this->fallback_template_set_id);
- // determine content type, defaulting to text/html
- //
- $this->content_type = Template::get_template_config($this->template_set_id,
- 'content_type',
- 'text/html');
- // determine template engine
- // FIXME: assuming PHP template engine may not necessarily be a good thing
- //
- $this->template_engine = Template::get_template_config($this->template_set_id,
- 'template_engine',
- SQ_PHP_TEMPLATE);
- // get template file cache
- //
- $this->template_file_cache = Template::cache_template_file_hierarchy($template_set_id);
- }
- /**
- * 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?
- // values are in main SM config file
- //
- global $templateset_fallback, $aTemplateSet;
- $aTemplateSet = (!isset($aTemplateSet) || !is_array($aTemplateSet)
- ? array() : $aTemplateSet);
- $templateset_fallback = (!isset($templateset_fallback)
- ? $default : $templateset_fallback);
- // iterate through all template sets, is this a valid skin ID?
- //
- $found_it = FALSE;
- foreach ($aTemplateSet as $aTemplate) {
- if ($aTemplate['ID'] === $templateset_fallback) {
- $found_it = TRUE;
- break;
- }
- }
- if ($found_it)
- return $templateset_fallback;
- // FIXME: note that it is possible for $default to
- // point to an invalid (nonexistent) template set
- // and that error will not be caught here
- //
- return $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)
- ? $default : $templateset_default);
- // iterate through all template sets, is this a valid skin ID?
- //
- $found_it = FALSE;
- foreach ($aTemplateSet as $aTemplate) {
- if ($aTemplate['ID'] === $templateset_default) {
- $found_it = TRUE;
- break;
- }
- }
- if ($found_it)
- return $templateset_default;
- // FIXME: note that it is possible for $default to
- // point to an invalid (nonexistent) template set
- // and that error will not be caught here
- //
- return $default;
- }
- /**
- * Determine what the RPC 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_rpc").
- *
- * @return string The ID of the RPC template set.
- *
- * @static
- *
- */
- function get_rpc_template_set($default='default_rpc') {
- // 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 $rpc_templateset;
- $rpc_templateset = (!isset($rpc_templateset)
- ? $default : $rpc_templateset);
- // FIXME: note that it is possible for this to
- // point to an invalid (nonexistent) template set
- // and that error will not be caught here
- //
- return $rpc_templateset;
- }
- /**
- * Allow template set to override plugin configuration by either
- * adding or removing plugins.
- *
- * NOTE: due to when this code executes, plugins activated here
- * do not have access to the config_override and loading_prefs
- * hooks; instead, such plugins can use the
- * "template_plugins_override_after" hook defined below.
- *
- */
- function override_plugins() {
- global $disable_plugins, $plugins, $squirrelmail_plugin_hooks, $null;
- if ($disable_plugins) return;
- $add_plugins = Template::get_template_config($this->template_set_id,
- 'add_plugins', array());
- $remove_plugins = Template::get_template_config($this->template_set_id,
- 'remove_plugins', array());
- //FIXME (?) we assume $add_plugins and $remove_plugins are arrays -- we could
- // error check here, or just assume that template authors or admins
- // won't screw up their config files
- // disable all plugins? (can still add some by using $add_plugins)
- //
- if (in_array('*', $remove_plugins)) {
- $plugins = array();
- $squirrelmail_plugin_hooks = array();
- $remove_plugins = array();
- }
- foreach ($add_plugins as $plugin_name) {
- // add plugin to global plugin array
- //
- $plugins[] = $plugin_name;
- // enable plugin -- emulate code from use_plugin() function
- // in SquirrelMail core, but also need to call the
- // "squirrelmail_plugin_init_<plugin_name>" function, which
- // in static configuration is not called (this inconsistency
- // could be a source of anomalous-seeming bugs in poorly
- // coded plugins)
- //
- if (file_exists(SM_PATH . "plugins/$plugin_name/setup.php")) {
- include_once(SM_PATH . "plugins/$plugin_name/setup.php");
- $function = "squirrelmail_plugin_init_$plugin_name";
- if (function_exists($function))
- $function();
- }
- }
- foreach ($remove_plugins as $plugin_name) {
- // remove plugin from both global plugin & plugin hook arrays
- //
- $plugin_key = array_search($plugin_name, $plugins);
- if (!is_null($plugin_key) && $plugin_key !== FALSE) {
- unset($plugins[$plugin_key]);
- if (is_array($squirrelmail_plugin_hooks))
- foreach (array_keys($squirrelmail_plugin_hooks) as $hookName) {
- unset($squirrelmail_plugin_hooks[$hookName][$plugin_name]);
- }
- }
- }
- do_hook('template_plugins_override_after', $null);
- }
- /**
- * 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($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/'
- . $engine . 'Template.class.php';
- if (!file_exists($engine_class_file)) {
- trigger_error('Unknown template engine (' . $engine
- . ') was specified in template configuration file',
- E_USER_ERROR);
- }
- $engine_class = $engine . 'Template';
- require_once($engine_class_file);
- return new $engine_class($template_set_id);
- }
- /**
- * Determine the relative template directory path for
- * the given template ID.
- *
- * @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_set_id) {
- return 'templates/' . $template_set_id . '/';
- }
- /**
- * Determine the relative images directory path for
- * the given template ID.
- *
- * @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_set_id) {
- return 'templates/' . $template_set_id . '/images/';
- }
- /**
- * Return the relative template directory path for this template set.
- *
- * @return string The relative path to the template directory based
- * from the main SquirrelMail directory (SM_PATH).
- *
- */
- function get_template_file_directory() {
- return $this->template_dir;
- }
- /**
- * 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
- * fallback template set.
- *
- * @return string The relative path to the fallback template
- * directory based from the main SquirrelMail
- * directory (SM_PATH).
- *
- */
- function get_fallback_template_file_directory() {
- return $this->fallback_template_dir;
- }
- /**
- * Return the content-type for this template set.
- *
- * @return string The content-type.
- *
- */
- function get_content_type() {
- return $this->content_type;
- }
- /**
- * 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 string $template_set_id The template set for which
- * the cache should be built.
- * This function will save more
- * than one set's files, so it
- * may be called multiple times
- * with different values for this
- * argument. When regenerating,
- * all set caches are dumped.
- * @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 for the given
- * template set ID (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($template_set_id,
- $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[$template_set_id])) {
- // have to add additional files if given before returning
- //
- if (!empty($additional_files)) {
- $template_file_hierarchy[$template_set_id]
- = array_merge($template_file_hierarchy[$template_set_id],
- $additional_files);
- sqsession_register($template_file_hierarchy,
- 'template_file_hierarchy');
- }
- return $template_file_hierarchy[$template_set_id];
- }
- // nothing in cache apparently, so go build it now
- //
- $template_file_hierarchy[$template_set_id] = Template::catalog_template_files($template_set_id);
- // additional files, if any
- //
- if (!empty($additional_files)) {
- $template_file_hierarchy[$template_set_id]
- = array_merge($template_file_hierarchy[$template_set_id],
- $additional_files);
- }
- sqsession_register($template_file_hierarchy,
- 'template_file_hierarchy');
- return $template_file_hierarchy[$template_set_id];
- }
- /**
- * 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.
- *
- * Refuses to traverse directories called ".svn"
- *
- * @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;
- }
- // bail if we have been asked to traverse a Subversion directory
- //
- if (strpos($directory, '/.svn') === strlen($directory) - 5) return $file_list;
- $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
- * PATH should be relative to SquirrelMail top directory
- */
- if (!isset($file_list[$relative_file])) {
- $file_list[$relative_file] = array(
- 'PATH' => substr($file,strlen(SM_PATH)),
- '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' => substr($file_path, strlen(SM_PATH)),
- 'SET_ID' => $template_set_id,
- 'ENGINE' => $engine,
- )
- );
- $this->template_file_cache
- = $this->cache_template_file_hierarchy($this->template_set_id,
- 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);
- }
- /**
- * Find the right template file.
- *
- * The template file is taken from the template file cache, thus
- * the file is taken from the current template, one of its
- * ancestors or the fallback template.
- *
- * Note that it is perfectly acceptable to load template files from
- * template subdirectories. For example, JavaScript templates found
- * in the js/ subdirectory would be loaded by passing
- * "js/<javascript file name>" as the $filename.
- *
- * Note that the caller can also ask for ALL files in a directory
- * (and those in the same directory for all ancestor template sets)
- * by giving a $filename that is a directory name (ending with a
- * slash).
- *
- * If not found and the file is a plugin template file (indicated
- * by the presence of "plugins/" on the beginning of $filename),
- * the target plugin is searched for a substitue template file
- * before just returning nothing.
- *
- * Plugin authors must note that the $filename MUST be prefaced
- * with "plugins/<plugin name>/" in order to correctly resolve the
- * template file.
- *
- * @param string $filename The name of the template file,
- * possibly prefaced with
- * "plugins/<plugin name>/"
- * indicating that it is a plugin
- * template, or ending with a
- * slash, indicating that all files
- * for that directory name should
- * be returned.
- * @param boolean $directories_ok When TRUE, directory names
- * are acceptable search values,
- * and when returning a list of
- * directory contents, sub-directory
- * names will also be included
- * (optional; default FALSE).
- * NOTE that empty directories
- * are NOT included in the cache!
- * @param boolean $directories_only When TRUE, only directory names
- * are included in the returned
- * results. (optional; default
- * FALSE). Setting this argument
- * to TRUE forces $directories_ok
- * to TRUE as well.
- * NOTE that empty directories
- * are NOT included in the cache!
- *
- * @return mixed The full path to the template file or a list
- * of all files in the given directory if $filename
- * ends with a slash; if not found, an empty string
- * is returned. The caller is responsible for
- * throwing errors or other actions if template
- * file is not found.
- *
- */
- function get_template_file_path($filename,
- $directories_ok=FALSE,
- $directories_only=FALSE) {
- if ($directories_only) $directories_ok = TRUE;
- // only looking for directory listing first...
- //
- // return list of all files in a directory (and that
- // of any ancestors)
- //
- if ($filename{strlen($filename) - 1} == '/') {
- $return_array = array();
- foreach ($this->template_file_cache as $file => $file_info) {
- // only want files in the requested directory
- // (AND not in a subdirectory!)
- //
- if (!$directories_only && strpos($file, $filename) === 0
- && strpos($file, '/', strlen($filename)) === FALSE)
- $return_array[] = SM_PATH . $file_info['PATH'];
- // directories too? detect by finding any
- // array key that matches a file in a sub-directory
- // of the directory being processed
- //
- if ($directories_ok && strpos($file, $filename) === 0
- && ($pos = strpos($file, '/', strlen($filename))) !== FALSE
- && strpos($file, '/', $pos + 1) === FALSE) {
- $directory_name = SM_PATH
- . substr($file_info['PATH'],
- 0,
- strrpos($file_info['PATH'], '/'));
- if (!in_array($directory_name, $return_array))
- $return_array[] = $directory_name;
- }
- }
- return $return_array;
- }
- // just looking for singular file or directory below...
- //
- // figure out what to do with files not found
- //
- if ($directories_only || empty($this->template_file_cache[$filename]['PATH'])) {
- // if looking for directories...
- // have to iterate through cache and detect
- // directory by matching any file inside of it
- //
- if ($directories_ok) {
- foreach ($this->template_file_cache as $file => $file_info) {
- if (strpos($file, $filename) === 0
- && ($pos = strpos($file, '/', strlen($filename))) !== FALSE
- && strpos($file, '/', $pos + 1) === FALSE) {
- return SM_PATH . substr($file_info['PATH'],
- 0,
- strrpos($file_info['PATH'], '/'));
- }
- }
- if ($directories_only) return '';
- }
- // plugins get one more chance
- //
- if (strpos($filename, 'plugins/') === 0) {
- $plugin_name = substr($filename, 8, strpos($filename, '/', 8) - 8);
- $file = substr($filename, strlen($plugin_name) + 9);
- if (!$this->find_and_cache_plugin_template_file($plugin_name, $file))
- return '';
- //FIXME: technically I guess we should check for directories
- // here too, but that's overkill (no need) presently
- // (plugin-provided alternate stylesheet dirs?!? bah.)
- }
- // nothing... return empty string (yes, the else is intentional!)
- //
- else return '';
- }
- return SM_PATH . $this->template_file_cache[$filename]['PATH'];
- }
- /**
- * Get template engine needed to render given template file.
- *
- * If at all possible, just returns a reference to $this, but
- * some template files may require a different engine, thus
- * an object for that engine (which will subsequently be kept
- * in this object for future use) is returned.
- *
- * @param string $filename The name of the template file,
- *
- * @return object The needed template object to render the template.
- *
- */
- function get_rendering_template_engine_object($filename) {
- // for files that we cannot find engine info for,
- // just return $this
- //
- if (empty($this->template_file_cache[$filename]['ENGINE']))
- return $this;
- // otherwise, compare $this' engine to the file's engine
- //
- $engine = $this->template_file_cache[$filename]['ENGINE'];
- if ($this->template_engine == $engine)
- return $this;
- // need to load another engine... if already instantiated,
- // and stored herein, return that
- // FIXME: this assumes same engine setup in all template
- // set config files that have same engine in common
- // (but keeping a separate class object for every
- // template set seems like overkill... for now we
- // won't do that unless it becomes a problem)
- //
- if (!empty($this->other_template_engine_objects[$engine])) {
- $rendering_engine = $this->other_template_engine_objects[$engine];
- // otherwise, instantiate new engine object, add to cache
- // and return it
- //
- } else {
- $template_set_id = $this->template_file_cache[$filename]['SET_ID'];
- $this->other_template_engine_objects[$engine]
- = $this->get_template_engine_subclass($template_set_id);
- $rendering_engine = $this->other_template_engine_objects[$engine];
- }
- // now, need to copy over all the assigned variables
- // from $this to the rendering engine (YUCK! -- we need
- // to discourage template authors from creating
- // situations where engine changes occur)
- //
- $rendering_engine->clear_all_assign();
- $rendering_engine->assign($this->get_template_vars());
- // finally ready to go
- //
- return $rendering_engine;
- }
- /**
- * Return all JavaScript files provided by the template.
- *
- * All files found in the template set's "js" directory (and
- * that of its ancestors) with the extension ".js" are returned.
- *
- * @param boolean $full_path When FALSE, only the file names
- * are included in the return array;
- * otherwise, path information is
- * included (relative to SM_PATH)
- * (OPTIONAL; default only file names)
- *
- * @return array The required file names/paths.
- *
- */
- function get_javascript_includes($full_path=FALSE) {
- // since any page from a parent template set
- // could end up being loaded, we have to load
- // all js files from ancestor template sets,
- // not just this set
- //
- //$directory = SM_PATH . $this->get_template_file_directory() . 'js';
- //$js_files = list_files($directory, '.js', !$full_path);
- //
- $js_files = $this->get_template_file_path('js/');
- // parse out .js files only
- //
- $return_array = array();
- foreach ($js_files as $file) {
- if (substr($file, strlen($file) - 3) != '.js') continue;
- if ($full_path) {
- $return_array[] = $file;
- } else {
- $return_array[] = basename($file);
- }
- }
- return $return_array;
- }
- /**
- * Return all alternate stylesheets provided by template.
- *
- * All (non-empty) directories found in the template set's
- * "css/alternates" directory (and that of its ancestors)
- * are returned.
- *
- * Note that prettified names are constructed herein by
- * taking the directory name, changing underscores to spaces
- * and capitalizing each word in the resultant name.
- *
- * @param boolean $full_path When FALSE, only the file names
- * are included in the return array;
- * otherwise, path information is
- * included (relative to SM_PATH)
- * (OPTIONAL; default only file names)
- *
- * @return array A list of the available alternate stylesheets,
- * where the keys are the file names (formatted
- * according to $full_path) for the stylesheets,
- * and the values are the prettified version of
- * the file names for display to the user.
- *
- */
- function get_alternative_stylesheets($full_path=FALSE) {
- // since any page from a parent template set
- // could end up being loaded, we will load
- // all alternate css files from ancestor
- // template sets, not just this set
- //
- $css_directories = $this->get_template_file_path('css/alternates/', TRUE, TRUE);
- // prettify names
- //
- $return_array = array();
- foreach ($css_directories as $directory) {
- // CVS and SVN directories are not wanted
- //
- if ((strpos($directory, '/CVS') === strlen($directory) - 4)
- || (strpos($directory, '/.svn') === strlen($directory) - 5)) continue;
- $pretty_name = ucwords(str_replace('_', ' ', basename($directory)));
- if ($full_path) {
- $return_array[$directory] = $pretty_name;
- } else {
- $return_array[basename($directory)] = $pretty_name;
- }
- }
- return $return_array;
- }
- /**
- * Return all standard stylsheets provided by the template.
- *
- * All files found in the template set's "css" directory (and
- * that of its ancestors) with the extension ".css" except
- * "rtl.css" (which is dealt with separately) are returned.
- *
- * @param boolean $full_path When FALSE, only the file names
- * are included in the return array;
- * otherwise, path information is
- * included (relative to SM_PATH)
- * (OPTIONAL; default only file names)
- *
- * @return array The required file names/paths.
- *
- */
- function get_stylesheets($full_path=FALSE) {
- // since any page from a parent template set
- // could end up being loaded, we have to load
- // all css files from ancestor template sets,
- // not just this set
- //
- //$directory = SM_PATH . $this->get_template_file_directory() . 'css';
- //$css_files = list_files($directory, '.css', !$full_path);
- //
- $css_files = $this->get_template_file_path('css/');
- // need to leave out "rtl.css"
- //
- $return_array = array();
- foreach ($css_files as $file) {
- if (substr($file, strlen($file) - 4) != '.css') continue;
- if (strtolower(basename($file)) == 'rtl.css') continue;
- if ($full_path) {
- $return_array[] = $file;
- } else {
- $return_array[] = basename($file);
- }
- }
- // return sheets for the current template set
- // last so we can enable any custom overrides
- // of styles in ancestor sheets
- //
- return array_reverse($return_array);
- }
- /**
- * Generate links to all this template set's standard stylesheets
- *
- * Subclasses can override this function if stylesheets are
- * created differently for the template set's target output
- * interface.
- *
- * @return string The stylesheet links as they should be sent
- * to the browser.
- *
- */
- function fetch_standard_stylesheet_links()
- {
- $sheets = $this->get_stylesheets(TRUE);
- return $this->fetch_external_stylesheet_links($sheets);
- }
- /**
- * Push out any other stylesheet links as provided (for
- * stylesheets not included with the current template set)
- *
- * Subclasses can override this function if stylesheets are
- * created differently for the template set's target output
- * interface.
- *
- * @param mixed $sheets List of the desired stylesheets
- * (file path to be used in stylesheet
- * href attribute) to output (or single
- * stylesheet file path).
- FIXME: We could make the incoming array more complex so it can
- also contain the other parameters for create_css_link()
- such as $name, $alt, $mtype, and $xhtml_end
- But do we need to?
- *
- * @return string The stylesheet links as they should be sent
- * to the browser.
- *
- */
- function fetch_external_stylesheet_links($sheets)
- {
- if (!is_array($sheets)) $sheets = array($sheets);
- $output = '';
- foreach ($sheets as $sheet) {
- $output .= create_css_link($sheet);
- }
- return $output;
- }
- /**
- * Send HTTP header(s) to browser.
- *
- * Subclasses can override this function if headers are
- * managed differently in the engine's target output
- * interface.
- *
- * @param mixed $headers A list of (or a single) header
- * text to be sent.
- * @param boolean $replace Whether or not to replace header(s)
- * previously sent header(s) of the
- * same type (this parameter may be
- * ignored in some implementations
- * of this class if the target interface
- * does not support this functionality)
- * (OPTIONAL; default = TRUE, always replace).
- *
- */
- function header($headers, $replace=TRUE)
- {
- if (!is_array($headers)) $headers = array($headers);
- foreach ($headers as $header) {
- $this->assign('header', $header);
- header($this->fetch('header.tpl'), $replace);
- }
- }
- /**
- * Generate a link to the right-to-left stylesheet for
- * this template set by getting the "rtl.css" file from
- * this template set, its parent (or grandparent, etc.)
- * template set, the fall-back template set, or finally,
- * fall back to SquirrelMail's own "rtl.css" if need be.
- *
- * Subclasses can override this function if stylesheets are
- * created differently for the template set's target output
- * interface.
- *
- * @return string The stylesheet link as it should be sent
- * to the browser.
- *
- */
- function fetch_right_to_left_stylesheet_link()
- {
- // get right template file
- //
- $sheet = $this->get_template_file_path('css/rtl.css');
- // fall back to SquirrelMail's own default stylesheet
- //
- if (empty($sheet)) {
- $sheet = SM_PATH . 'css/rtl.css';
- }
- return create_css_link($sheet);
- }
- /**
- * Display the template
- *
- * @param string $file The template file to use
- *
- */
- function display($file)
- {
- echo $this->fetch($file);
- }
- /**
- * Applies the template and returns the resultant content string.
- *
- * @param string $file The template file to use
- *
- * @return string The template contents after applying the given template
- *
- */
- function fetch($file) {
- // get right template file
- //
- $template = $this->get_template_file_path($file);
- // special case stylesheet.tpl falls back to SquirrelMail's
- // own default stylesheet
- //
- if (empty($template) && $file == 'css/stylesheet.tpl') {
- $template = SM_PATH . 'css/default.css';
- }
- if (empty($template)) {
- trigger_error('The template "' . htmlspecialchars($file)
- . '" could not be fetched!', E_USER_ERROR);
- } else {
- $aPluginOutput = array();
- $temp = array(&$aPluginOutput, &$this);
- $aPluginOutput = concat_hook_function('template_construct_' . $file,
- $temp, TRUE);
- $this->assign('plugin_output', $aPluginOutput);
- //$output = $this->apply_template($template);
- $rendering_engine = $this->get_rendering_template_engine_object($file);
- $output = $rendering_engine->apply_template($template);
- // CAUTION: USE OF THIS HOOK IS HIGHLY DISCOURAGED AND CAN
- // RESULT IN NOTICABLE PERFORMANCE DEGREDATION. Plugins
- // using this hook will probably be rejected by the
- // SquirrelMail team.
- //
- do_hook('template_output', $output);
- return $output;
- }
- }
- /**
- * Assigns values to template variables
- *
- * Note: this is an abstract method that must be implemented by subclass.
- *
- * @param array|string $tpl_var the template variable name(s)
- * @param mixed $value the value to assign
- *
- */
- function assign($tpl_var, $value = NULL) {
- trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the assign() method.', E_USER_ERROR);
- }
- /**
- * Assigns values to template variables by reference
- *
- * Note: this is an abstract method that must be implemented by subclass.
- *
- * @param string $tpl_var the template variable name
- * @param mixed $value the referenced value to assign
- *
- */
- function assign_by_ref($tpl_var, &$value) {
- trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the assign_by_ref() method.', E_USER_ERROR);
- }
- /**
- * Clears the values of all assigned varaiables.
- *
- */
- function clear_all_assign() {
- trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the clear_all_assign() method.', E_USER_ERROR);
- }
- /**
- * Returns assigned variable value(s).
- *
- * @param string $varname If given, the value of that variable
- * is returned, assuming it has been
- * previously assigned. If not specified
- * an array of all assigned variables is
- * returned. (optional)
- *
- * @return mixed Desired single variable value or list of all
- * assigned variable values.
- *
- */
- function get_template_vars($varname=NULL) {
- trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the get_template_vars() method.', E_USER_ERROR);
- }
- /**
- * Appends values to template variables
- *
- * Note: this is an abstract method that must be implemented by subclass.
- *
- * @param array|string $tpl_var the template variable name(s)
- * @param mixed $value the value to append
- * @param boolean $merge when $value is given as an array,
- * this indicates whether or not that
- * array itself should be appended as
- * a new template variable value or if
- * that array's values should be merged
- * into the existing array of template
- * variable values
- *
- */
- function append($tpl_var, $value = NULL, $merge = FALSE) {
- trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the append() method.', E_USER_ERROR);
- }
- /**
- * Appends values to template variables by reference
- *
- * Note: this is an abstract method that must be implemented by subclass.
- *
- * @param string $tpl_var the template variable name
- * @param mixed $value the referenced value to append
- * @param boolean $merge when $value is given as an array,
- * this indicates whether or not that
- * array itself should be appended as
- * a new template variable value or if
- * that array's values should be merged
- * into the existing array of template
- * variable values
- *
- */
- function append_by_ref($tpl_var, &$value, $merge = FALSE) {
- trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the append_by_ref() method.', E_USER_ERROR);
- }
- /**
- * Applys the template and generates final output destined
- * for the user's browser
- *
- * Note: this is an abstract method that must be implemented by subclass.
- *
- * @param string $filepath The full file path to the template to be applied
- *
- * @return string The output for the given template
- *
- */
- function apply_template($filepath) {
- trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the apply_template() method.', E_USER_ERROR);
- }
- }
|