5 * This file contains an abstract (PHP 4, so "abstract" is relative)
6 * class meant to define the basic template interface for the
7 * SquirrelMail core application. Subclasses should extend this
8 * class with any custom functionality needed to interface a target
9 * templating engine with SquirrelMail.
11 * @copyright © 2003-2006 The SquirrelMail Project Team
12 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
14 * @package squirrelmail
15 * @subpackage Template
20 /** load template functions */
21 require(SM_PATH
. 'functions/template/general_util.php');
24 * The SquirrelMail Template class.
26 * Basic template class for capturing values and pluging them into a template.
27 * This class uses a similar API to Smarty.
29 * Methods that must be implemented by subclasses are as follows (see method
30 * stubs below for further information about expected behavior):
40 * @author Paul Lesniewski <paul at squirrelmail.org>
41 * @package squirrelmail
53 var $template_set_id = '';
56 * The template set base directory (relative path from
57 * the main SquirrelMail directory (SM_PATH))
62 var $template_dir = '';
65 * The template engine (please use constants defined in constants.php)
70 var $template_engine = '';
73 * The fall-back template ID
78 var $fallback_template_set_id = '';
81 * The fall-back template directory (relative
82 * path from the main SquirrelMail directory (SM_PATH))
87 var $fallback_template_dir = '';
90 * The fall-back template engine (please use
91 * constants defined in constants.php)
96 var $fallback_template_engine = '';
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:
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
111 var $template_file_cache = array();
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.
122 var $other_template_engine_objects = array();
127 * Please do not call directly. Use Template::construct_template().
129 * @param string $template_set_id the template ID
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)
137 // trigger_error('Please do not use default Template() constructor. Instead, use Template::construct_template().', E_USER_ERROR);
139 $this->set_up_template($template_set_id);
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.
150 * @param string $template_set_id the template ID
152 * @return object The correct Template object for the given template set
157 function construct_template($template_set_id) {
159 $template = new Template($template_set_id);
160 $template->override_plugins();
161 return $template->get_template_engine_subclass();
166 * Set up internal attributes
168 * This method does most of the work for setting up
169 * newly constructed objects.
171 * @param string $template_set_id the template ID
174 function set_up_template($template_set_id) {
176 // FIXME: do we want to place any restrictions on the ID like
177 // making sure no slashes included?
180 $this->template_set_id
= $template_set_id;
183 $this->fallback_template_set_id
= Template
::get_fallback_template_set();
186 // set up template directories
189 = Template
::calculate_template_file_directory($this->template_set_id
);
190 $this->fallback_template_dir
191 = Template
::calculate_template_file_directory($this->fallback_template_set_id
);
194 // determine template engine
195 // FIXME: assuming PHP template engine may not necessarily be a good thing
197 $this->template_engine
= Template
::get_template_config($this->template_set_id
,
202 // get template file cache
204 $this->template_file_cache
= Template
::cache_template_file_hierarchy();
209 * Determine what the ultimate fallback template set is.
211 * NOTE that if the fallback setting cannot be found in the
212 * main SquirrelMail configuration settings that the value
213 * of $default is returned.
215 * @param string $default The template set ID to use if
216 * the fallback setting cannot be
217 * found in SM config (optional;
218 * defaults to "default").
220 * @return string The ID of the fallback template set.
225 function get_fallback_template_set($default='default') {
227 // FIXME: do we want to place any restrictions on the ID such as
228 // making sure no slashes included?
230 // values are in main SM config file
232 global $templateset_fallback, $aTemplateSet;
233 $aTemplateSet = (!isset($aTemplateSet) ||
!is_array($aTemplateSet)
234 ?
array() : $aTemplateSet);
235 $templateset_fallback = (!isset($templateset_fallback)
236 ?
$default : $templateset_fallback);
238 // iterate through all template sets, is this a valid skin ID?
241 foreach ($aTemplateSet as $aTemplate) {
242 if ($aTemplate['ID'] === $templateset_fallback) {
249 return $templateset_fallback;
251 // FIXME: note that it is possible for $default to
252 // point to an invalid (nonexistent) template set
253 // and that error will not be caught here
260 * Determine what the default template set is.
262 * NOTE that if the default setting cannot be found in the
263 * main SquirrelMail configuration settings that the value
264 * of $default is returned.
266 * @param string $default The template set ID to use if
267 * the default setting cannot be
268 * found in SM config (optional;
269 * defaults to "default").
271 * @return string The ID of the default template set.
276 function get_default_template_set($default='default') {
278 // FIXME: do we want to place any restrictions on the ID such as
279 // making sure no slashes included?
281 // values are in main SM config file
283 global $templateset_default, $aTemplateSet;
284 $aTemplateSet = (!isset($aTemplateSet) ||
!is_array($aTemplateSet)
285 ?
array() : $aTemplateSet);
286 $templateset_default = (!isset($templateset_default)
287 ?
$default : $templateset_default);
289 // iterate through all template sets, is this a valid skin ID?
292 foreach ($aTemplateSet as $aTemplate) {
293 if ($aTemplate['ID'] === $templateset_default) {
300 return $templateset_default;
302 // FIXME: note that it is possible for $default to
303 // point to an invalid (nonexistent) template set
304 // and that error will not be caught here
311 * Allow template set to override plugin configuration by either
312 * adding or removing plugins.
314 * NOTE: due to when this code executes, plugins activated here
315 * do not have access to the config_override and loading_prefs
316 * hooks; instead, such plugins can use the
317 * "template_plugins_override_after" hook defined below.
320 function override_plugins() {
322 global $disable_plugins, $plugins, $squirrelmail_plugin_hooks, $null;
323 if ($disable_plugins) return;
325 $add_plugins = Template
::get_template_config($this->template_set_id
,
326 'add_plugins', array());
327 $remove_plugins = Template
::get_template_config($this->template_set_id
,
328 'remove_plugins', array());
330 //FIXME (?) we assume $add_plugins and $remove_plugins are arrays -- we could
331 // error check here, or just assume that template authors or admins
332 // won't screw up their config files
335 // disable all plugins? (can still add some by using $add_plugins)
337 if (in_array('*', $remove_plugins)) {
339 $squirrelmail_plugin_hooks = array();
340 $remove_plugins = array();
344 foreach ($add_plugins as $plugin_name) {
345 // add plugin to global plugin array
347 $plugins[] = $plugin_name;
350 // enable plugin -- emulate code from use_plugin() function
351 // in SquirrelMail core, but also need to call the
352 // "squirrelmail_plugin_init_<plugin_name>" function, which
353 // in static configuration is not called (this inconsistency
354 // could be a source of anomalous-seeming bugs in poorly
357 if (file_exists(SM_PATH
. "plugins/$plugin_name/setup.php")) {
358 include_once(SM_PATH
. "plugins/$plugin_name/setup.php");
360 $function = "squirrelmail_plugin_init_$plugin_name";
361 if (function_exists($function))
366 foreach ($remove_plugins as $plugin_name) {
367 // remove plugin from both global plugin & plugin hook arrays
369 $plugin_key = array_search($plugin_name, $plugins);
370 if (!is_null($plugin_key) && $plugin_key !== FALSE) {
371 unset($plugins[$plugin_key]);
372 if (is_array($squirrelmail_plugin_hooks))
373 foreach (array_keys($squirrelmail_plugin_hooks) as $hookName) {
374 unset($squirrelmail_plugin_hooks[$hookName][$plugin_name]);
379 do_hook('template_plugins_override_after', $null);
384 * Instantiate and return correct subclass for this template
385 * set's templating engine.
387 * @param string $template_set_id The template set whose engine
388 * is to be used as an override
389 * (if not given, this template
390 * set's engine is used) (optional).
392 * @return object The Template subclass object for the template engine.
395 function get_template_engine_subclass($template_set_id='') {
397 if (empty($template_set_id)) $template_set_id = $this->template_set_id
;
398 // FIXME: assuming PHP template engine may not necessarily be a good thing
399 $engine = Template
::get_template_config($template_set_id,
400 'template_engine', SQ_PHP_TEMPLATE
);
403 $engine_class_file = SM_PATH
. 'class/template/'
404 . $engine . 'Template.class.php';
406 if (!file_exists($engine_class_file)) {
407 trigger_error('Unknown template engine (' . $engine
408 . ') was specified in template configuration file',
412 $engine_class = $engine . 'Template';
413 require_once($engine_class_file);
414 return new $engine_class($template_set_id);
419 * Determine the relative template directory path for
420 * the given template ID.
422 * @param string $template_set_id The template ID from which to build
425 * @return string The relative template path (based off of SM_PATH)
430 function calculate_template_file_directory($template_set_id) {
432 return 'templates/' . $template_set_id . '/';
437 * Determine the relative images directory path for
438 * the given template ID.
440 * @param string $template_set_id The template ID from which to build
443 * @return string The relative images path (based off of SM_PATH)
448 function calculate_template_images_directory($template_set_id) {
450 return 'templates/' . $template_set_id . '/images/';
455 * Return the relative template directory path for this template set.
457 * @return string The relative path to the template directory based
458 * from the main SquirrelMail directory (SM_PATH).
461 function get_template_file_directory() {
463 return $this->template_dir
;
468 * Return the template ID for the fallback template set.
470 * @return string The ID of the fallback template set.
473 function get_fallback_template_set_id() {
475 return $this->fallback_template_set_id
;
480 * Return the relative template directory path for the
481 * fallback template set.
483 * @return string The relative path to the fallback template
484 * directory based from the main SquirrelMail
485 * directory (SM_PATH).
488 function get_fallback_template_file_directory() {
490 return $this->fallback_template_dir
;
495 * Get template set config setting
497 * Given a template set ID and setting name, returns the
498 * setting's value. Note that settings are cached in
499 * session, so "live" changes to template configuration
500 * won't be reflected until the user logs out and back
503 * @param string $template_set_id The template set for which
504 * to look up the setting.
505 * @param string $setting The name of the setting to
507 * @param mixed $default When the requested setting
508 * is not found, the contents
509 * of this value are returned
510 * instead (optional; default
512 * NOTE that unlike sqGetGlobalVar(),
513 * this function will also return
514 * the default value if the
515 * requested setting is found
517 * @param boolean $live_config When TRUE, the target template
518 * set's configuration file is
519 * reloaded every time this
520 * method is called. Default
521 * behavior is to only load the
522 * configuration file if it had
523 * never been loaded before, but
524 * not again after that (optional;
525 * default FALSE). Use with care!
526 * Should mostly be used for
529 * @return mixed The desired setting's value or if not found,
530 * the contents of $default are returned.
535 function get_template_config($template_set_id, $setting,
536 $default=NULL, $live_config=FALSE) {
538 sqGetGlobalVar('template_configuration_settings',
539 $template_configuration_settings,
543 if ($live_config) unset($template_configuration_settings[$template_set_id]);
546 // NOTE: could use isset() instead of empty() below, but
547 // this function is designed to replace empty values
548 // as well as non-existing values with $default
550 if (!empty($template_configuration_settings[$template_set_id][$setting]))
551 return $template_configuration_settings[$template_set_id][$setting];
554 // if template set configuration has been loaded, but this
555 // setting is not known, return $default
557 if (!empty($template_configuration_settings[$template_set_id]))
561 // otherwise (template set configuration has not been loaded before),
562 // load it into session and return the desired setting after that
564 $template_config_file = SM_PATH
565 . Template
::calculate_template_file_directory($template_set_id)
568 if (!file_exists($template_config_file)) {
570 trigger_error('No template configuration file was found where expected: ("'
571 . $template_config_file . '")', E_USER_ERROR
);
575 // we require() the file to let PHP do the variable value
576 // parsing for us, and read the file in manually so we can
577 // know what variable names are used in the config file
578 // (settings can be different depending on specific requirements
579 // of different template engines)... the other way this may
580 // be accomplished is to somehow diff the symbol table
581 // before/after the require(), but anyway, this code should
582 // only run once for this template set...
584 require($template_config_file);
585 $file_contents = implode("\n", file($template_config_file));
588 // note that this assumes no template settings have
589 // a string in them that looks like a variable name like $x
590 // also note that this will attempt to grab things like
591 // $Id found in CVS headers, so we try to adjust for that
592 // by checking that the variable is actually set
594 preg_match_all('/\$(\w+)/', $file_contents, $variables, PREG_PATTERN_ORDER
);
595 foreach ($variables[1] as $variable) {
596 if (isset($
$variable))
597 $template_configuration_settings[$template_set_id][$variable]
601 sqsession_register($template_configuration_settings,
602 'template_configuration_settings');
604 // NOTE: could use isset() instead of empty() below, but
605 // this function is designed to replace empty values
606 // as well as non-existing values with $default
608 if (!empty($template_configuration_settings[$template_set_id][$setting]))
609 return $template_configuration_settings[$template_set_id][$setting];
618 * Obtain template file hierarchy from cache.
620 * If the file hierarchy does not exist in session, it is
621 * constructed and stored in session before being returned
624 * @param boolean $regenerate_cache When TRUE, the file hierarchy
625 * is reloaded and stored fresh
626 * (optional; default FALSE).
627 * @param array $additional_files Must be in same form as the
628 * files in the file hierarchy
629 * cache. These are then added
630 * to the cache (optional; default
631 * empty - no additional files).
633 * @return array Template file hierarchy array, whose keys
634 * are all the template file names (with path
635 * information relative to the template set's
636 * base directory, e.g., "css/style.css")
637 * found in all parent template sets including
638 * the ultimate fall-back template set.
639 * Array values are sub-arrays with the
640 * following key-value pairs:
642 * PATH -- file path, relative to SM_PATH
643 * SET_ID -- the ID of the template set that this file belongs to
644 * ENGINE -- the engine needed to render this template file
649 function cache_template_file_hierarchy($regenerate_cache=FALSE,
650 $additional_files=array()) {
652 sqGetGlobalVar('template_file_hierarchy', $template_file_hierarchy,
653 SQ_SESSION
, array());
656 if ($regenerate_cache) unset($template_file_hierarchy);
658 if (!empty($template_file_hierarchy)) {
660 // have to add additional files if given before returning
662 if (!empty($additional_files)) {
663 $template_file_hierarchy = array_merge($template_file_hierarchy,
665 sqsession_register($template_file_hierarchy,
666 'template_file_hierarchy');
669 return $template_file_hierarchy;
673 // nothing in cache apparently, so go build it now
675 // FIXME: not sure if there is any possibility that
676 // this could be called when $sTemplateID has
677 // yet to be defined... throw error for now,
678 // but if the error occurs, it's a coding error
679 // rather than a configuration error
682 if (empty($sTemplateID)) {
684 trigger_error('Template set ID unknown', E_USER_ERROR
);
688 $template_file_hierarchy = Template
::catalog_template_files($sTemplateID);
690 // additional files, if any
692 if (!empty($additional_files)) {
693 $template_file_hierarchy = array_merge($template_file_hierarchy,
697 sqsession_register($template_file_hierarchy,
698 'template_file_hierarchy');
700 return $template_file_hierarchy;
707 * Traverse template hierarchy and catalogue all template
708 * files (for storing in cache).
710 * Paths to all files in all parent, grand-parent, great grand
711 * parent, etc. template sets (including the fallback template)
712 * are catalogued; for identically named files, the file earlier
713 * in the hierarchy (closest to this template set) is used.
715 * @param string $template_set_id The template set in which to
717 * @param array $file_list The file list so far to be added
718 * to (allows recursive behavior)
719 * (optional; default empty array).
720 * @param string $directory The directory in which to search for
721 * files (must be given as full path).
722 * If empty, starts at top-level template
723 * set directory (optional; default empty).
724 * NOTE! Use with care, as behavior is
725 * unpredictable if directory given is not
726 * part of correct template set.
728 * @return mixed The top-level caller will have an array of template
729 * files returned to it; recursive calls to this function
730 * do not receive any return value at all. The format
731 * of the template file array is as described for the
732 * Template class attribute $template_file_cache
737 function catalog_template_files($template_set_id, $file_list=array(), $directory='') {
739 $template_base_dir = SM_PATH
740 . Template
::calculate_template_file_directory($template_set_id);
742 if (empty($directory)) {
743 $directory = $template_base_dir;
746 $files_and_dirs = list_files($directory, '', FALSE, TRUE, FALSE, TRUE);
748 // recurse for all the subdirectories in the template set
750 foreach ($files_and_dirs['DIRECTORIES'] as $dir) {
751 $file_list = Template
::catalog_template_files($template_set_id, $file_list, $dir);
754 // place all found files in the cache
755 // FIXME: assuming PHP template engine may not necessarily be a good thing
757 $engine = Template
::get_template_config($template_set_id,
758 'template_engine', SQ_PHP_TEMPLATE
);
759 foreach ($files_and_dirs['FILES'] as $file) {
761 // remove the part of the file path corresponding to the
762 // template set's base directory
764 $relative_file = substr($file, strlen($template_base_dir));
767 * only put file in cache if not already found in earlier template
768 * PATH should be relative to SquirrelMail top directory
770 if (!isset($file_list[$relative_file])) {
771 $file_list[$relative_file] = array(
772 'PATH' => substr($file,strlen(SM_PATH
)),
773 'SET_ID' => $template_set_id,
781 // now if we are currently at the top-level of the template
782 // set base directory, we need to move on to the parent
783 // template set, if any
785 if ($directory == $template_base_dir) {
787 // use fallback when we run out of parents
789 $fallback_id = Template
::get_fallback_template_set();
790 $parent_id = Template
::get_template_config($template_set_id,
791 'parent_template_set',
794 // were we already all the way to the last level? just exit
796 // note that this code allows the fallback set to have
797 // a parent, too, but can result in endless loops
798 // if ($parent_id == $template_set_id) {
800 if ($fallback_id == $template_set_id) {
804 $file_list = Template
::catalog_template_files($parent_id, $file_list);
813 * Look for a template file in a plugin; add to template
814 * file cache if found.
816 * The file is searched for in the following order:
818 * - A directory for the current template set within the plugin:
819 * SM_PATH/plugins/<plugin name>/templates/<template name>/
820 * - In a directory for one of the current template set's ancestor
821 * (inherited) template sets within the plugin:
822 * SM_PATH/plugins/<plugin name>/templates/<parent template name>/
823 * - In a directory for the fallback template set within the plugin:
824 * SM_PATH/plugins/<plugin name>/templates/<fallback template name>/
826 * @param string $plugin The name of the plugin
827 * @param string $file The name of the template file
828 * @param string $template_set_id The ID of the template for which
829 * to start looking for the file
830 * (optional; default is current
833 * @return boolean TRUE if the template file was found, FALSE otherwise.
836 function find_and_cache_plugin_template_file($plugin, $file, $template_set_id='') {
838 if (empty($template_set_id))
839 $template_set_id = $this->template_set_id
;
841 $file_path = SM_PATH
. 'plugins/' . $plugin . '/'
842 . $this->calculate_template_file_directory($template_set_id)
845 if (file_exists($file_path)) {
846 // FIXME: assuming PHP template engine may not necessarily be a good thing
847 $engine = $this->get_template_config($template_set_id,
848 'template_engine', SQ_PHP_TEMPLATE
);
849 $file_list = array('plugins/' . $plugin . '/' . $file => array(
850 'PATH' => substr($file_path, strlen(SM_PATH
)),
851 'SET_ID' => $template_set_id,
855 $this->template_file_cache
856 = $this->cache_template_file_hierarchy(FALSE, $file_list);
861 // not found yet, try parent template set
862 // (use fallback when we run out of parents)
864 $fallback_id = $this->get_fallback_template_set();
865 $parent_id = $this->get_template_config($template_set_id,
866 'parent_template_set',
869 // were we already all the way to the last level? just exit
871 // note that this code allows the fallback set to have
872 // a parent, too, but can result in endless loops
873 // if ($parent_id == $template_set_id) {
875 if ($fallback_id == $template_set_id) {
879 return $this->find_and_cache_plugin_template_file($plugin, $file, $parent_id);
884 * Find the right template file.
886 * The template file is taken from the template file cache, thus
887 * the file is taken from the current template, one of its
888 * ancestors or the fallback template.
890 * Note that it is perfectly acceptable to load template files from
891 * template subdirectories. For example, JavaScript templates found
892 * in the js/ subdirectory would be loaded by passing
893 * "js/<javascript file name>" as the $filename.
895 * Note that the caller can also ask for ALL files in a directory
896 * (and those in the same directory for all ancestor template sets)
897 * by giving a $filename that is a directory name (ending with a
900 * If not found and the file is a plugin template file (indicated
901 * by the presence of "plugins/" on the beginning of $filename),
902 * the target plugin is searched for a substitue template file
903 * before just returning nothing.
905 * Plugin authors must note that the $filename MUST be prefaced
906 * with "plugins/<plugin name>/" in order to correctly resolve the
909 * @param string $filename The name of the template file,
910 * possibly prefaced with
911 * "plugins/<plugin name>/"
912 * indicating that it is a plugin
913 * template, or ending with a
914 * slash, indicating that all files
915 * for that directory name should
917 * @param boolean $directories_ok When TRUE, directory names
918 * are acceptable search values,
919 * and when returning a list of
920 * directory contents, sub-directory
921 * names will also be included
922 * (optional; default FALSE).
923 * NOTE that empty directories
924 * are NOT included in the cache!
925 * @param boolean $directories_only When TRUE, only directory names
926 * are included in the returned
927 * results. (optional; default
928 * FALSE). Setting this argument
929 * to TRUE forces $directories_ok
931 * NOTE that empty directories
932 * are NOT included in the cache!
934 * @return mixed The full path to the template file or a list
935 * of all files in the given directory if $filename
936 * ends with a slash; if not found, an empty string
937 * is returned. The caller is responsible for
938 * throwing errors or other actions if template
942 function get_template_file_path($filename,
943 $directories_ok=FALSE,
944 $directories_only=FALSE) {
946 if ($directories_only) $directories_ok = TRUE;
949 // only looking for directory listing first...
951 // return list of all files in a directory (and that
954 if ($filename{strlen($filename) - 1} == '/') {
956 $return_array = array();
957 foreach ($this->template_file_cache
as $file => $file_info) {
959 // only want files in the requested directory
960 // (AND not in a subdirectory!)
962 if (!$directories_only && strpos($file, $filename) === 0
963 && strpos($file, '/', strlen($filename)) === FALSE)
964 $return_array[] = SM_PATH
. $file_info['PATH'];
966 // directories too? detect by finding any
967 // array key that matches a file in a sub-directory
968 // of the directory being processed
970 if ($directories_ok && strpos($file, $filename) === 0
971 && ($pos = strpos($file, '/', strlen($filename))) !== FALSE
972 && strpos($file, '/', $pos +
1) === FALSE) {
973 $directory_name = SM_PATH
974 . substr($file_info['PATH'],
976 strrpos($file_info['PATH'], '/'));
977 if (!in_array($directory_name, $return_array))
978 $return_array[] = $directory_name;
982 return $return_array;
987 // just looking for singular file or directory below...
989 // figure out what to do with files not found
991 if ($directories_only ||
empty($this->template_file_cache
[$filename]['PATH'])) {
993 // if looking for directories...
994 // have to iterate through cache and detect
995 // directory by matching any file inside of it
997 if ($directories_ok) {
998 foreach ($this->template_file_cache
as $file => $file_info) {
999 if (strpos($file, $filename) === 0
1000 && ($pos = strpos($file, '/', strlen($filename))) !== FALSE
1001 && strpos($file, '/', $pos +
1) === FALSE) {
1002 return SM_PATH
. substr($file_info['PATH'],
1004 strrpos($file_info['PATH'], '/'));
1008 if ($directories_only) return '';
1011 // plugins get one more chance
1013 if (strpos($filename, 'plugins/') === 0) {
1015 $plugin_name = substr($filename, 8, strpos($filename, '/', 8) - 8);
1016 $file = substr($filename, strlen($plugin_name) +
9);
1018 if (!$this->find_and_cache_plugin_template_file($plugin_name, $file))
1020 //FIXME: technically I guess we should check for directories
1021 // here too, but that's overkill (no need) presently
1022 // (plugin-provided alternate stylesheet dirs?!? bah.)
1026 // nothing... return empty string (yes, the else is intentional!)
1032 return SM_PATH
. $this->template_file_cache
[$filename]['PATH'];
1037 * Get template engine needed to render given template file.
1039 * If at all possible, just returns a reference to $this, but
1040 * some template files may require a different engine, thus
1041 * an object for that engine (which will subsequently be kept
1042 * in this object for future use) is returned.
1044 * @param string $filename The name of the template file,
1046 * @return object The needed template object to render the template.
1049 function get_rendering_template_engine_object($filename) {
1051 // for files that we cannot find engine info for,
1052 // just return $this
1054 if (empty($this->template_file_cache
[$filename]['ENGINE']))
1058 // otherwise, compare $this' engine to the file's engine
1060 $engine = $this->template_file_cache
[$filename]['ENGINE'];
1061 if ($this->template_engine
== $engine)
1065 // need to load another engine... if already instantiated,
1066 // and stored herein, return that
1067 // FIXME: this assumes same engine setup in all template
1068 // set config files that have same engine in common
1069 // (but keeping a separate class object for every
1070 // template set seems like overkill... for now we
1071 // won't do that unless it becomes a problem)
1073 if (!empty($this->other_template_engine_objects
[$engine])) {
1074 $rendering_engine = $this->other_template_engine_objects
[$engine];
1077 // otherwise, instantiate new engine object, add to cache
1081 $template_set_id = $this->template_file_cache
[$filename]['SET_ID'];
1082 $this->other_template_engine_objects
[$engine]
1083 = $this->get_template_engine_subclass($template_set_id);
1084 $rendering_engine = $this->other_template_engine_objects
[$engine];
1088 // now, need to copy over all the assigned variables
1089 // from $this to the rendering engine (YUCK! -- we need
1090 // to discourage template authors from creating
1091 // situations where engine changes occur)
1093 $rendering_engine->clear_all_assign();
1094 $rendering_engine->assign($this->get_template_vars());
1097 // finally ready to go
1099 return $rendering_engine;
1104 * Return all JavaScript files provided by the template.
1106 * All files found in the template set's "js" directory (and
1107 * that of its ancestors) with the extension ".js" are returned.
1109 * @param boolean $full_path When FALSE, only the file names
1110 * are included in the return array;
1111 * otherwise, path information is
1112 * included (relative to SM_PATH)
1113 * (OPTIONAL; default only file names)
1115 * @return array The required file names/paths.
1118 function get_javascript_includes($full_path=FALSE) {
1120 // since any page from a parent template set
1121 // could end up being loaded, we have to load
1122 // all js files from ancestor template sets,
1123 // not just this set
1125 //$directory = SM_PATH . $this->get_template_file_directory() . 'js';
1126 //$js_files = list_files($directory, '.js', !$full_path);
1128 $js_files = $this->get_template_file_path('js/');
1131 // parse out .js files only
1133 $return_array = array();
1134 foreach ($js_files as $file) {
1136 if (substr($file, strlen($file) - 3) != '.js') continue;
1139 $return_array[] = $file;
1141 $return_array[] = basename($file);
1146 return $return_array;
1151 * Return all alternate stylesheets provided by template.
1153 * All (non-empty) directories found in the template set's
1154 * "css/alternates" directory (and that of its ancestors)
1157 * Note that prettified names are constructed herein by
1158 * taking the directory name, changing underscores to spaces
1159 * and capitalizing each word in the resultant name.
1161 * @param boolean $full_path When FALSE, only the file names
1162 * are included in the return array;
1163 * otherwise, path information is
1164 * included (relative to SM_PATH)
1165 * (OPTIONAL; default only file names)
1167 * @return array A list of the available alternate stylesheets,
1168 * where the keys are the file names (formatted
1169 * according to $full_path) for the stylesheets,
1170 * and the values are the prettified version of
1171 * the file names for display to the user.
1174 function get_alternative_stylesheets($full_path=FALSE) {
1176 // since any page from a parent template set
1177 // could end up being loaded, we will load
1178 // all alternate css files from ancestor
1179 // template sets, not just this set
1181 $css_directories = $this->get_template_file_path('css/alternates/', TRUE, TRUE);
1186 $return_array = array();
1187 foreach ($css_directories as $directory) {
1189 // CVS directories are not wanted
1191 if (strpos($directory, '/CVS') === strlen($directory) - 4) continue;
1193 $pretty_name = ucwords(str_replace('_', ' ', basename($directory)));
1196 $return_array[$directory] = $pretty_name;
1198 $return_array[basename($directory)] = $pretty_name;
1203 return $return_array;
1208 * Return all standard stylsheets provided by the template.
1210 * All files found in the template set's "css" directory (and
1211 * that of its ancestors) with the extension ".css" except
1212 * "rtl.css" (which is dealt with separately) are returned.
1214 * @param boolean $full_path When FALSE, only the file names
1215 * are included in the return array;
1216 * otherwise, path information is
1217 * included (relative to SM_PATH)
1218 * (OPTIONAL; default only file names)
1220 * @return array The required file names/paths.
1223 function get_stylesheets($full_path=FALSE) {
1225 // since any page from a parent template set
1226 // could end up being loaded, we have to load
1227 // all css files from ancestor template sets,
1228 // not just this set
1230 //$directory = SM_PATH . $this->get_template_file_directory() . 'css';
1231 //$css_files = list_files($directory, '.css', !$full_path);
1233 $css_files = $this->get_template_file_path('css/');
1236 // need to leave out "rtl.css"
1238 $return_array = array();
1239 foreach ($css_files as $file) {
1241 if (substr($file, strlen($file) - 4) != '.css') continue;
1242 if (strtolower(basename($file)) == 'rtl.css') continue;
1245 $return_array[] = $file;
1247 $return_array[] = basename($file);
1253 // return sheets for the current template set
1254 // last so we can enable any custom overrides
1255 // of styles in ancestor sheets
1257 return array_reverse($return_array);
1262 * Generate links to all this template set's standard stylesheets
1264 * Subclasses can override this function if stylesheets are
1265 * created differently for the template set's target output
1268 * @return string The stylesheet links as they should be sent
1272 function fetch_standard_stylesheet_links()
1275 $sheets = $this->get_stylesheets(TRUE);
1276 return $this->fetch_external_stylesheet_links($sheets);
1281 * Push out any other stylesheet links as provided (for
1282 * stylesheets not included with the current template set)
1284 * Subclasses can override this function if stylesheets are
1285 * created differently for the template set's target output
1288 * @param mixed $sheets List of the desired stylesheets
1289 * (file path to be used in stylesheet
1290 * href attribute) to output (or single
1291 * stylesheet file path).
1292 FIXME: We could make the incoming array more complex so it can
1293 also contain the other parameters for create_css_link()
1294 such as $name, $alt, $mtype, and $xhtml_end
1297 * @return string The stylesheet links as they should be sent
1301 function fetch_external_stylesheet_links($sheets)
1304 if (!is_array($sheets)) $sheets = array($sheets);
1307 foreach ($sheets as $sheet) {
1308 $output .= create_css_link($sheet);
1316 * Send HTTP header(s) to browser.
1318 * Subclasses can override this function if headers are
1319 * managed differently in the template set's target output
1322 * @param mixed $headers A list of (or a single) header
1326 function header($headers)
1329 if (!is_array($headers)) $headers = array($headers);
1331 foreach ($headers as $header) {
1338 * Generate a link to the right-to-left stylesheet for
1339 * this template set by getting the "rtl.css" file from
1340 * this template set, its parent (or grandparent, etc.)
1341 * template set, the fall-back template set, or finally,
1342 * fall back to SquirrelMail's own "rtl.css" if need be.
1344 * Subclasses can override this function if stylesheets are
1345 * created differently for the template set's target output
1348 * @return string The stylesheet link as it should be sent
1352 function fetch_right_to_left_stylesheet_link()
1355 // get right template file
1357 $sheet = $this->get_template_file_path('css/rtl.css');
1359 // fall back to SquirrelMail's own default stylesheet
1361 if (empty($sheet)) {
1362 $sheet = SM_PATH
. 'css/rtl.css';
1365 return create_css_link($sheet);
1370 * Display the template
1372 * @param string $file The template file to use
1375 function display($file)
1378 echo $this->fetch($file);
1383 * Applies the template and returns the resultant content string.
1385 * @param string $file The template file to use
1387 * @return string The template contents after applying the given template
1390 function fetch($file) {
1392 // get right template file
1394 $template = $this->get_template_file_path($file);
1397 // special case stylesheet.tpl falls back to SquirrelMail's
1398 // own default stylesheet
1400 if (empty($template) && $file == 'css/stylesheet.tpl') {
1401 $template = SM_PATH
. 'css/default.css';
1405 if (empty($template)) {
1407 trigger_error('The template "' . htmlspecialchars($file)
1408 . '" could not be fetched!', E_USER_ERROR
);
1412 $aPluginOutput = array();
1413 // At this moment concat_hook_function can return string and arrays.
1414 // In php 4.3.x a notice will be raised when a string is passed as $aPluginOutput
1415 // TODO, only return an arrays by concat_hook_function.
1416 $mixedOutput = concat_hook_function('template_construct_' . $file,
1417 $temp=array(&$aPluginOutput, &$this));
1418 if (is_array($mixedOutput)) {
1419 $aPluginOutput = $mixedOutput;
1421 $this->assign('plugin_output', $aPluginOutput);
1423 //$output = $this->apply_template($template);
1424 $rendering_engine = $this->get_rendering_template_engine_object($file);
1425 $output = $rendering_engine->apply_template($template);
1427 // CAUTION: USE OF THIS HOOK IS HIGHLY DISCOURAGED AND CAN
1428 // RESULT IN NOTICABLE PERFORMANCE DEGREDATION. Plugins
1429 // using this hook will probably be rejected by the
1430 // SquirrelMail team.
1432 do_hook('template_output', $output);
1441 * Assigns values to template variables
1443 * Note: this is an abstract method that must be implemented by subclass.
1445 * @param array|string $tpl_var the template variable name(s)
1446 * @param mixed $value the value to assign
1449 function assign($tpl_var, $value = NULL) {
1451 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the assign() method.', E_USER_ERROR
);
1456 * Assigns values to template variables by reference
1458 * Note: this is an abstract method that must be implemented by subclass.
1460 * @param string $tpl_var the template variable name
1461 * @param mixed $value the referenced value to assign
1464 function assign_by_ref($tpl_var, &$value) {
1466 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the assign_by_ref() method.', E_USER_ERROR
);
1471 * Clears the values of all assigned varaiables.
1474 function clear_all_assign() {
1476 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the clear_all_assign() method.', E_USER_ERROR
);
1481 * Returns assigned variable value(s).
1483 * @param string $varname If given, the value of that variable
1484 * is returned, assuming it has been
1485 * previously assigned. If not specified
1486 * an array of all assigned variables is
1487 * returned. (optional)
1489 * @return mixed Desired single variable value or list of all
1490 * assigned variable values.
1493 function get_template_vars($varname=NULL) {
1495 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the get_template_vars() method.', E_USER_ERROR
);
1500 * Appends values to template variables
1502 * Note: this is an abstract method that must be implemented by subclass.
1504 * @param array|string $tpl_var the template variable name(s)
1505 * @param mixed $value the value to append
1506 * @param boolean $merge when $value is given as an array,
1507 * this indicates whether or not that
1508 * array itself should be appended as
1509 * a new template variable value or if
1510 * that array's values should be merged
1511 * into the existing array of template
1515 function append($tpl_var, $value = NULL, $merge = FALSE) {
1517 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the append() method.', E_USER_ERROR
);
1522 * Appends values to template variables by reference
1524 * Note: this is an abstract method that must be implemented by subclass.
1526 * @param string $tpl_var the template variable name
1527 * @param mixed $value the referenced value to append
1528 * @param boolean $merge when $value is given as an array,
1529 * this indicates whether or not that
1530 * array itself should be appended as
1531 * a new template variable value or if
1532 * that array's values should be merged
1533 * into the existing array of template
1537 function append_by_ref($tpl_var, &$value, $merge = FALSE) {
1539 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the append_by_ref() method.', E_USER_ERROR
);
1544 * Applys the template and generates final output destined
1545 * for the user's browser
1547 * Note: this is an abstract method that must be implemented by subclass.
1549 * @param string $filepath The full file path to the template to be applied
1551 * @return string The output for the given template
1554 function apply_template($filepath) {
1556 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the apply_template() method.', E_USER_ERROR
);