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