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