3 require(SM_PATH
. 'functions/template.php');
8 * This file contains an abstract (PHP 4, so "abstract" is relative)
9 * class meant to define the basic template interface for the
10 * SquirrelMail core application. Subclasses should extend this
11 * class with any custom functionality needed to interface a target
12 * templating engine with SquirrelMail.
14 * @copyright © 2003-2006 The SquirrelMail Project Team
15 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
17 * @package squirrelmail
18 * @subpackage Template
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):
38 * @author Paul Lesniewski
39 * @package squirrelmail
51 var $template_id = '';
54 * The template directory to use
59 var $template_dir = '';
62 * The template engine (please use constants defined in constants.php)
67 var $template_engine = '';
70 * The default template ID
75 var $default_template_id = '';
78 * The default template directory
83 var $default_template_dir = '';
86 * The default template engine (please use constants defined in constants.php)
91 var $default_template_engine = '';
94 * Javascript files required by the template
99 var $required_js_files = array();
102 * Alternate stylesheets provided by the template. This is defined in the
103 * template config file so that we can provide pretty names in the display
108 var $alternate_stylesehets = array();
113 * Please do not call directly. Use Template::construct_template().
115 * @param string $template_id the template ID
118 function Template($template_id) {
119 //FIXME: find a way to test that this is ONLY ever called
120 // from the construct_template() method (I doubt it
121 // is worth the trouble to parse the current stack trace)
123 // trigger_error('Please do not use default Template() constructor. Instead, use Template::construct_template().', E_USER_ERROR);
125 $this->set_up_template($template_id);
132 * This method should always be called instead of trying
133 * to get a Template object from the normal/default constructor,
134 * and is necessary in order to control the return value.
136 * @param string $template_id the template ID
138 * @return object The correct Template object for the given template set
141 function construct_template($template_id) {
143 $template = new Template($template_id);
144 return $template->get_template_engine_subclass();
149 * Set up internal attributes
151 * This method does most of the work for setting up
152 * newly constructed objects.
154 * @param string $template_id the template ID
157 function set_up_template($template_id) {
159 // FIXME: do we want to place any restrictions on the ID like
160 // making sure no slashes included?
163 $this->template_id
= $template_id;
166 // FIXME: do we want to place any restrictions on the ID like
167 // making sure no slashes included?
168 // get default template ID
170 global $templateset_default, $aTemplateSet;
171 $aTemplateSet = (!isset($aTemplateSet) ||
!is_array($aTemplateSet)
172 ?
array() : $aTemplateSet);
173 $templateset_default = (!isset($templateset_default) ?
0 : $templateset_default);
174 $this->default_template_id
= (!empty($aTemplateSet[$templateset_default]['ID'])
175 ?
$aTemplateSet[$templateset_default]['ID']
179 // set up template directories
182 = Template
::calculate_template_file_directory($this->template_id
);
183 $this->default_template_dir
184 = Template
::calculate_template_file_directory($this->default_template_id
);
187 // pull in the template config file and load javascript and
188 // css files needed for this template set
190 $template_config_file = SM_PATH
. $this->get_template_file_directory()
192 if (!file_exists($template_config_file)) {
194 trigger_error('No template configuration file was found where expected: ("'
195 . $template_config_file . '")', E_USER_ERROR
);
199 require($template_config_file);
200 $this->required_js_files
= is_array($required_js_files)
201 ?
$required_js_files : array();
202 $this->alternate_stylesheets
= is_array($alternate_stylesheets) ?
203 $alternate_stylesheets :
209 // determine template engine
211 if (empty($template_engine)) {
212 trigger_error('No template engine ($template_engine) was specified in template configuration file: ("'
213 . $template_config_file . '")', E_USER_ERROR
);
215 $this->template_engine
= $template_engine;
221 * Instantiate and return correct subclass for this template
222 * set's templating engine.
224 * @return object The Template subclass object for the template engine.
227 function get_template_engine_subclass() {
229 $engine_class_file = SM_PATH
. 'class/template/'
230 . $this->template_engine
. 'Template.class.php';
232 if (!file_exists($engine_class_file)) {
233 trigger_error('Unknown template engine (' . $this->template_engine
234 . ') was specified in template configuration file',
238 $engine_class = $this->template_engine
. 'Template';
239 require($engine_class_file);
240 return new $engine_class($this->template_id
);
245 * Determine the relative template directory path for
246 * the given template ID.
248 * @param string $template_id The template ID from which to build
251 * @return string The relative template path (based off of SM_PATH)
254 function calculate_template_file_directory($template_id) {
256 return 'templates/' . $template_id . '/';
261 * Determine the relative images directory path for
262 * the given template ID.
264 * @param string $template_id The template ID from which to build
267 * @return string The relative images path (based off of SM_PATH)
270 function calculate_template_images_directory($template_id) {
272 return 'templates/' . $template_id . '/images/';
277 * Return the relative template directory path for this template set.
279 * @return string The relative path to the template directory based
280 * from the main SquirrelMail directory (SM_PATH).
283 function get_template_file_directory() {
285 return $this->template_dir
;
291 * Return the relative template directory path for the DEFAULT template set.
293 * @return string The relative path to the default template directory based
294 * from the main SquirrelMail directory (SM_PATH).
297 function get_default_template_file_directory() {
299 return $this->default_template_dir
;
305 * Find the right template file.
307 * Templates are expected to be found in the template set directory,
309 * SM_PATH/templates/<template name>/
310 * or, in the case of plugin templates, in a plugin directory in the
311 * template set directory, for example:
312 * SM_PATH/templates/<template name>/plugins/<plugin name>/
313 * *OR* in a template directory in the plugin as a fallback, for example:
314 * SM_PATH/plugins/<plugin name>/templates/<template name>/
315 * If the correct file is not found for the current template set, a
316 * default template is loaded, which is expected to be found in the
317 * default template directory, for example:
318 * SM_PATH/templates/<default template>/
319 * or for plugins, in a plugin directory in the default template set,
321 * SM_PATH/templates/<default template>/plugins/<plugin name>/
322 * *OR* in a default template directory in the plugin as a fallback,
324 * SM_PATH/plugins/<plugin name>/templates/<default template>/
325 * *OR* if the plugin template still cannot be found, one last attempt
326 * will be made to load it from a hard-coded default template directory
328 * SM_PATH/plugins/<plugin name>/templates/default/
330 * Plugin authors must note that the $filename MUST be prefaced
331 * with "plugins/<plugin name>/" in order to correctly resolve the
334 * Note that it is perfectly acceptable to load template files from
335 * template subdirectories other than plugins; for example, JavaScript
336 * templates found in the js/ subdirectory would be loaded by passing
337 * "js/<javascript file name>" as the $filename.
339 * @param string $filename The name of the template file,
340 * possibly prefaced with
341 * "plugins/<plugin name>/"
342 * indicating that it is a plugin
345 * @return string The full path to the template file; if
346 * not found, an empty string. The caller
347 * is responsible for throwing erros or
348 * other actions if template file is not found.
351 function get_template_file_path($filename) {
353 // is the template found in the normal template directory?
355 $filepath = SM_PATH
. $this->get_template_file_directory() . $filename;
356 if (!file_exists($filepath)) {
358 // no, so now we have to get the default template...
359 // however, in the case of a plugin template, let's
360 // give one more try to find the right template as
361 // provided by the plugin
363 if (strpos($filename, 'plugins/') === 0) {
365 $plugin_name = substr($filename, 8, strpos($filename, '/', 8) - 8);
366 $filepath = SM_PATH
. 'plugins/' . $plugin_name . '/'
367 . $this->get_template_file_directory()
368 . substr($filename, strlen($plugin_name) +
9);
370 // no go, we have to get the default template,
371 // first try the default SM template
373 if (!file_exists($filepath)) {
376 . $this->get_default_template_file_directory()
379 // still no luck? get default template from the plugin
381 if (!file_exists($filepath)) {
383 $filepath = SM_PATH
. 'plugins/' . $plugin_name . '/'
384 . $this->get_default_template_file_directory()
385 . substr($filename, strlen($plugin_name) +
9);
387 // we're almost out of luck, try hard-coded default...
389 if (!file_exists($filepath)) {
391 $filepath = SM_PATH
. 'plugins/' . $plugin_name
392 . '/templates/default/'
393 . substr($filename, strlen($plugin_name) +
9);
395 // no dice whatsoever, return empty string
397 if (!file_exists($filepath)) {
408 // get default template for non-plugin templates
412 $filepath = SM_PATH
. $this->get_default_template_file_directory()
415 // no dice whatsoever, return empty string
417 if (!file_exists($filepath)) {
430 * Return the list of javascript files required by this
431 * template set. Only files that actually exist are returned.
433 * @param boolean $full_path When FALSE, only the file names
434 * are included in the return array;
435 * otherwise, path information is
436 * included (relative to SM_PATH)
437 * (OPTIONAL; default only file names)
439 * @return array The required file names/paths.
442 function get_javascript_includes($full_path=FALSE) {
444 //FIXME -- change this system so it just returns whatever is in js dir?
445 // bah, maybe not, but we might want to enhance this to pull in
446 // js files not found in this or the default template from SM_PATH/js???
448 foreach ($this->required_js_files
as $file) {
449 $file = $this->get_template_file_path('js/' . $file);
454 $paths[] = basename($file);
464 * Return all standard stylsheets provided by the template.
466 * All files found in the template set's "css" directory with
467 * the extension ".css" except "rtl.css" (which is dealt with
468 * separately) are returned.
470 * @param boolean $full_path When FALSE, only the file names
471 * are included in the return array;
472 * otherwise, path information is
473 * included (relative to SM_PATH)
474 * (OPTIONAL; default only file names)
476 * @return array The required file names/paths.
479 function get_stylesheets($full_path=FALSE) {
481 $directory = SM_PATH
. $this->get_template_file_directory() . 'css';
482 $files = list_files($directory, '.css', !$full_path);
484 // need to leave out "rtl.css"
485 $return_array = array();
486 foreach ($files as $file) {
488 if (strtolower(basename($file)) == 'rtl.css') {
492 $return_array[] = $file;
496 return $return_array;
501 * Return all alternate stylesheets provided by template. These
502 * sheets are defined in the template config file so that we cna display
503 * pretty names in the Display Preferences. The CSS files are located in
504 * $this->template_dir/css/alternatives/
506 * @return array alternate style sheets
508 function get_alternative_stylesheets () {
510 foreach ($this->alternate_stylesheets
as $path=>$name) {
511 $a[strtolower(basename($path))] = $name;
517 * Generate links to all this template set's standard stylesheets
519 * Subclasses can override this function if stylesheets are
520 * created differently for the template set's target output
523 * @return string The stylesheet links as they should be sent
527 function fetch_standard_stylesheet_links()
530 $sheets = $this->get_stylesheets(TRUE);
531 return $this->fetch_external_stylesheet_links($sheets);
536 * Push out any other stylesheet links as provided (for
537 * stylesheets not included with the current template set)
539 * Subclasses can override this function if stylesheets are
540 * created differently for the template set's target output
543 * @param mixed $sheets List of the desired stylesheets
544 * (file path to be used in stylesheet
545 * href attribute) to output (or single
546 * stylesheet file path).
547 FIXME: We could make the incoming array more complex so it can
548 also contain the other parameters for create_css_link()
549 such as $name, $alt, $mtype, and $xhtml_end
552 * @return string The stylesheet links as they should be sent
556 function fetch_external_stylesheet_links($sheets)
559 if (!is_array($sheets)) $sheets = array($sheets);
562 foreach ($sheets as $sheet) {
563 $output .= create_css_link($sheet);
571 * Send HTTP header(s) to browser.
573 * Subclasses can override this function if headers are
574 * managed differently in the template set's target output
577 * @param mixed $headers A list of (or a single) header
581 function header($headers)
584 if (!is_array($headers)) $headers = array($headers);
586 foreach ($headers as $header) {
593 * Generate a link to the right-to-left stylesheet for
594 * this template set, or use the one for the default
595 * template set if not found, or finally, fall back
596 * to SquirrelMail's own "rtl.css" if need be.
598 * Subclasses can override this function if stylesheets are
599 * created differently for the template set's target output
602 * @return string The stylesheet link as it should be sent
606 function fetch_right_to_left_stylesheet_link()
609 // get right template file
611 $sheet = $this->get_template_file_path('css/rtl.css');
613 // fall back to SquirrelMail's own default stylesheet
616 $sheet = SM_PATH
. 'css/rtl.css';
619 return create_css_link($sheet);
624 * Display the template
626 * @param string $file The template file to use
629 function display($file)
632 echo $this->fetch($file);
637 * Applies the template and returns the resultant content string.
639 * @param string $file The template file to use
641 * @return string The template contents after applying the given template
644 function fetch($file) {
646 // get right template file
648 $template = $this->get_template_file_path($file);
650 // special case stylesheet.tpl falls back to SquirrelMail's
651 // own default stylesheet
653 if (empty($template) && $file == 'css/stylesheet.tpl') {
654 $template = SM_PATH
. 'css/default.css';
657 if (empty($template)) {
659 trigger_error('The template "' . htmlspecialchars($file)
660 . '" could not be fetched!', E_USER_ERROR
);
664 $aPluginOutput = array();
665 $aPluginOutput = concat_hook_function('template_construct_' . $file,
666 array($aPluginOutput, $this));
667 $this->assign('plugin_output', $aPluginOutput);
669 $output = $this->apply_template($template);
671 // CAUTION: USE OF THIS HOOK IS HIGHLY DISCOURAGED AND CAN
672 // RESULT IN NOTICABLE PERFORMANCE DEGREDATION. Plugins
673 // using this hook will probably be rejected by the
674 // SquirrelMail team.
676 $output = filter_hook_function('template_output', $output);
685 * Assigns values to template variables
687 * Note: this is an abstract method that must be implemented by subclass.
689 * @param array|string $tpl_var the template variable name(s)
690 * @param mixed $value the value to assign
693 function assign($tpl_var, $value = NULL) {
695 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the assign() method.', E_USER_ERROR
);
700 * Assigns values to template variables by reference
702 * Note: this is an abstract method that must be implemented by subclass.
704 * @param string $tpl_var the template variable name
705 * @param mixed $value the referenced value to assign
708 function assign_by_ref($tpl_var, &$value) {
710 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the assign_by_ref() method.', E_USER_ERROR
);
715 * Appends values to template variables
717 * Note: this is an abstract method that must be implemented by subclass.
719 * @param array|string $tpl_var the template variable name(s)
720 * @param mixed $value the value to append
721 * @param boolean $merge when $value is given as an array,
722 * this indicates whether or not that
723 * array itself should be appended as
724 * a new template variable value or if
725 * that array's values should be merged
726 * into the existing array of template
730 function append($tpl_var, $value = NULL, $merge = FALSE) {
732 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the append() method.', E_USER_ERROR
);
737 * Appends values to template variables by reference
739 * Note: this is an abstract method that must be implemented by subclass.
741 * @param string $tpl_var the template variable name
742 * @param mixed $value the referenced value to append
743 * @param boolean $merge when $value is given as an array,
744 * this indicates whether or not that
745 * array itself should be appended as
746 * a new template variable value or if
747 * that array's values should be merged
748 * into the existing array of template
752 function append_by_ref($tpl_var, &$value, $merge = FALSE) {
754 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the append_by_ref() method.', E_USER_ERROR
);
759 * Applys the template and generates final output destined
760 * for the user's browser
762 * Note: this is an abstract method that must be implemented by subclass.
764 * @param string $filepath The full file path to the template to be applied
766 * @return string The output for the given template
769 function apply_template($filepath) {
771 trigger_error('Template subclass (' . $this->template_engine
. 'Template.class.php) needs to implement the apply_template() method.', E_USER_ERROR
);