File name change
[squirrelmail.git] / class / template / Template.class.php
... / ...
CommitLineData
1<?php
2
3require(SM_PATH . 'functions/template.php');
4
5/**
6 * Template.class.php
7 *
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.
13 *
14 * @copyright &copy; 2003-2006 The SquirrelMail Project Team
15 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
16 * @version $Id$
17 * @package squirrelmail
18 * @subpackage Template
19 * @since 1.5.2
20 *
21 */
22
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()
34 * append()
35 * append_by_ref()
36 * apply_template()
37 *
38 * @author Paul Lesniewski
39 * @package squirrelmail
40 *
41 */
42class Template
43{
44
45 /**
46 * The template ID
47 *
48 * @var string
49 *
50 */
51 var $template_id = '';
52
53 /**
54 * The template directory to use
55 *
56 * @var string
57 *
58 */
59 var $template_dir = '';
60
61 /**
62 * The template engine (please use constants defined in constants.php)
63 *
64 * @var string
65 *
66 */
67 var $template_engine = '';
68
69 /**
70 * The default template ID
71 *
72 * @var string
73 *
74 */
75 var $default_template_id = '';
76
77 /**
78 * The default template directory
79 *
80 * @var string
81 *
82 */
83 var $default_template_dir = '';
84
85 /**
86 * The default template engine (please use constants defined in constants.php)
87 *
88 * @var string
89 *
90 */
91 var $default_template_engine = '';
92
93 /**
94 * Javascript files required by the template
95 *
96 * @var array
97 *
98 */
99 var $required_js_files = array();
100
101 /**
102 * Constructor
103 *
104 * Please do not call directly. Use Template::construct_template().
105 *
106 * @param string $template_id the template ID
107 *
108 */
109 function Template($template_id) {
110//FIXME: find a way to test that this is ONLY ever called
111// from the construct_template() method (I doubt it
112// is worth the trouble to parse the current stack trace)
113// if (???)
114// trigger_error('Please do not use default Template() constructor. Instead, use Template::construct_template().', E_USER_ERROR);
115
116 $this->set_up_template($template_id);
117
118 }
119
120 /**
121 * Construct Template
122 *
123 * This method should always be called instead of trying
124 * to get a Template object from the normal/default constructor,
125 * and is necessary in order to control the return value.
126 *
127 * @param string $template_id the template ID
128 *
129 * @return object The correct Template object for the given template set
130 *
131 */
132 function construct_template($template_id) {
133
134 $template = new Template($template_id);
135 return $template->get_template_engine_subclass();
136
137 }
138
139 /**
140 * Set up internal attributes
141 *
142 * This method does most of the work for setting up
143 * newly constructed objects.
144 *
145 * @param string $template_id the template ID
146 *
147 */
148 function set_up_template($template_id) {
149
150 // FIXME: do we want to place any restrictions on the ID like
151 // making sure no slashes included?
152 // get template ID
153 //
154 $this->template_id = $template_id;
155
156
157 // FIXME: do we want to place any restrictions on the ID like
158 // making sure no slashes included?
159 // get default template ID
160 //
161 global $templateset_default, $aTemplateSet;
162 $aTemplateSet = (!isset($aTemplateSet) || !is_array($aTemplateSet)
163 ? array() : $aTemplateSet);
164 $templateset_default = (!isset($templateset_default) ? 0 : $templateset_default);
165 $this->default_template_id = (!empty($aTemplateSet[$templateset_default]['ID'])
166 ? $aTemplateSet[$templateset_default]['ID']
167 : 'default');
168
169
170 // set up template directories
171 //
172 $this->template_dir
173 = Template::calculate_template_file_directory($this->template_id);
174 $this->default_template_dir
175 = Template::calculate_template_file_directory($this->default_template_id);
176
177
178 // pull in the template config file and load javascript and
179 // css files needed for this template set
180 //
181 $template_config_file = SM_PATH . $this->get_template_file_directory()
182 . 'config.php';
183 if (!file_exists($template_config_file)) {
184
185 trigger_error('No template configuration file was found where expected: ("'
186 . $template_config_file . '")', E_USER_ERROR);
187
188 } else {
189
190 require($template_config_file);
191 $this->required_js_files = is_array($required_js_files)
192 ? $required_js_files : array();
193
194 }
195
196
197 // determine template engine
198 //
199 if (empty($template_engine)) {
200 trigger_error('No template engine ($template_engine) was specified in template configuration file: ("'
201 . $template_config_file . '")', E_USER_ERROR);
202 } else {
203 $this->template_engine = $template_engine;
204 }
205
206 }
207
208 /**
209 * Instantiate and return correct subclass for this template
210 * set's templating engine.
211 *
212 * @return object The Template subclass object for the template engine.
213 *
214 */
215 function get_template_engine_subclass() {
216
217 $engine_class_file = SM_PATH . 'class/template/'
218 . $this->template_engine . 'Template.class.php';
219
220 if (!file_exists($engine_class_file)) {
221 trigger_error('Unknown template engine (' . $this->template_engine
222 . ') was specified in template configuration file',
223 E_USER_ERROR);
224 }
225
226 $engine_class = $this->template_engine . 'Template';
227 require($engine_class_file);
228 return new $engine_class($this->template_id);
229
230 }
231
232 /**
233 * Determine the relative template directory path for
234 * the given template ID.
235 *
236 * @param string $template_id The template ID from which to build
237 * the directory path
238 *
239 * @return string The relative template path (based off of SM_PATH)
240 *
241 */
242 function calculate_template_file_directory($template_id) {
243
244 return 'templates/' . $template_id . '/';
245
246 }
247
248 /**
249 * Determine the relative images directory path for
250 * the given template ID.
251 *
252 * @param string $template_id The template ID from which to build
253 * the directory path
254 *
255 * @return string The relative images path (based off of SM_PATH)
256 *
257 */
258 function calculate_template_images_directory($template_id) {
259
260 return 'templates/' . $template_id . '/images/';
261
262 }
263
264 /**
265 * Return the relative template directory path for this template set.
266 *
267 * @return string The relative path to the template directory based
268 * from the main SquirrelMail directory (SM_PATH).
269 *
270 */
271 function get_template_file_directory() {
272
273 return $this->template_dir;
274
275 }
276
277
278 /**
279 * Return the relative template directory path for the DEFAULT template set.
280 *
281 * @return string The relative path to the default template directory based
282 * from the main SquirrelMail directory (SM_PATH).
283 *
284 */
285 function get_default_template_file_directory() {
286
287 return $this->default_template_dir;
288
289 }
290
291
292 /**
293 * Find the right template file.
294 *
295 * Templates are expected to be found in the template set directory,
296 * for example:
297 * SM_PATH/templates/<template name>/
298 * or, in the case of plugin templates, in a plugin directory in the
299 * template set directory, for example:
300 * SM_PATH/templates/<template name>/plugins/<plugin name>/
301 * *OR* in a template directory in the plugin as a fallback, for example:
302 * SM_PATH/plugins/<plugin name>/templates/<template name>/
303 * If the correct file is not found for the current template set, a
304 * default template is loaded, which is expected to be found in the
305 * default template directory, for example:
306 * SM_PATH/templates/<default template>/
307 * or for plugins, in a plugin directory in the default template set,
308 * for example:
309 * SM_PATH/templates/<default template>/plugins/<plugin name>/
310 * *OR* in a default template directory in the plugin as a fallback,
311 * for example:
312 * SM_PATH/plugins/<plugin name>/templates/<default template>/
313 * *OR* if the plugin template still cannot be found, one last attempt
314 * will be made to load it from a hard-coded default template directory
315 * inside the plugin:
316 * SM_PATH/plugins/<plugin name>/templates/default/
317 *
318 * Plugin authors must note that the $filename MUST be prefaced
319 * with "plugins/<plugin name>/" in order to correctly resolve the
320 * template file.
321 *
322 * Note that it is perfectly acceptable to load template files from
323 * template subdirectories other than plugins; for example, JavaScript
324 * templates found in the js/ subdirectory would be loaded by passing
325 * "js/<javascript file name>" as the $filename.
326 *
327 * @param string $filename The name of the template file,
328 * possibly prefaced with
329 * "plugins/<plugin name>/"
330 * indicating that it is a plugin
331 * template.
332 *
333 * @return string The full path to the template file; if
334 * not found, an empty string. The caller
335 * is responsible for throwing erros or
336 * other actions if template file is not found.
337 *
338 */
339 function get_template_file_path($filename) {
340
341 // is the template found in the normal template directory?
342 //
343 $filepath = SM_PATH . $this->get_template_file_directory() . $filename;
344 if (!file_exists($filepath)) {
345
346 // no, so now we have to get the default template...
347 // however, in the case of a plugin template, let's
348 // give one more try to find the right template as
349 // provided by the plugin
350 //
351 if (strpos($filename, 'plugins/') === 0) {
352
353 $plugin_name = substr($filename, 8, strpos($filename, '/', 8) - 8);
354 $filepath = SM_PATH . 'plugins/' . $plugin_name . '/'
355 . $this->get_template_file_directory()
356 . substr($filename, strlen($plugin_name) + 9);
357
358 // no go, we have to get the default template,
359 // first try the default SM template
360 //
361 if (!file_exists($filepath)) {
362
363 $filepath = SM_PATH
364 . $this->get_default_template_file_directory()
365 . $filename;
366
367 // still no luck? get default template from the plugin
368 //
369 if (!file_exists($filepath)) {
370
371 $filepath = SM_PATH . 'plugins/' . $plugin_name . '/'
372 . $this->get_default_template_file_directory()
373 . substr($filename, strlen($plugin_name) + 9);
374
375 // we're almost out of luck, try hard-coded default...
376 //
377 if (!file_exists($filepath)) {
378
379 $filepath = SM_PATH . 'plugins/' . $plugin_name
380 . '/templates/default/'
381 . substr($filename, strlen($plugin_name) + 9);
382
383 // no dice whatsoever, return empty string
384 //
385 if (!file_exists($filepath)) {
386 $filepath = '';
387 }
388
389 }
390
391 }
392
393 }
394
395
396 // get default template for non-plugin templates
397 //
398 } else {
399
400 $filepath = SM_PATH . $this->get_default_template_file_directory()
401 . $filename;
402
403 // no dice whatsoever, return empty string
404 //
405 if (!file_exists($filepath)) {
406 $filepath = '';
407 }
408
409 }
410
411 }
412
413 return $filepath;
414
415 }
416
417 /**
418 * Return the list of javascript files required by this
419 * template set. Only files that actually exist are returned.
420 *
421 * @param boolean $full_path When FALSE, only the file names
422 * are included in the return array;
423 * otherwise, path information is
424 * included (relative to SM_PATH)
425 * (OPTIONAL; default only file names)
426 *
427 * @return array The required file names/paths.
428 *
429 */
430 function get_javascript_includes($full_path=FALSE) {
431
432//FIXME -- change this system so it just returns whatever is in js dir?
433// bah, maybe not, but we might want to enhance this to pull in
434// js files not found in this or the default template from SM_PATH/js???
435 $paths = array();
436 foreach ($this->required_js_files as $file) {
437 $file = $this->get_template_file_path('js/' . $file);
438 if (!empty($file)) {
439 if ($full_path) {
440 $paths[] = $file;
441 } else {
442 $paths[] = basename($file);
443 }
444 }
445 }
446
447 return $paths;
448
449 }
450
451 /**
452 * Return all standard stylsheets provided by the template.
453 *
454 * All files found in the template set's "css" directory with
455 * the extension ".css" except "rtl.css" (which is dealt with
456 * separately) are returned.
457 *
458 * @param boolean $full_path When FALSE, only the file names
459 * are included in the return array;
460 * otherwise, path information is
461 * included (relative to SM_PATH)
462 * (OPTIONAL; default only file names)
463 *
464 * @return array The required file names/paths.
465 *
466 */
467 function get_stylesheets($full_path=FALSE) {
468
469 $directory = SM_PATH . $this->get_template_file_directory() . 'css';
470 $files = list_files($directory, '.css', !$full_path);
471
472 // need to leave out "rtl.css"
473 //
474 $return_array = array();
475 foreach ($files as $file) {
476
477 if (strtolower(basename($file)) == 'rtl.css') {
478 continue;
479 }
480
481 $return_array[] = $file;
482
483 }
484
485 return $return_array;
486
487 }
488
489 /**
490 * Generate links to all this template set's standard stylesheets
491 *
492 * Subclasses can override this function if stylesheets are
493 * created differently for the template set's target output
494 * interface.
495 *
496 * @return string The stylesheet links as they should be sent
497 * to the browser.
498 *
499 */
500 function fetch_standard_stylesheet_links()
501 {
502
503 $sheets = $this->get_stylesheets(TRUE);
504 return $this->fetch_external_stylesheet_links($sheets);
505
506 }
507
508 /**
509 * Push out any other stylesheet links as provided (for
510 * stylesheets not included with the current template set)
511 *
512 * Subclasses can override this function if stylesheets are
513 * created differently for the template set's target output
514 * interface.
515 *
516 * @param mixed $sheets List of the desired stylesheets
517 * (file path to be used in stylesheet
518 * href attribute) to output (or single
519 * stylesheet file path).
520FIXME: We could make the incoming array more complex so it can
521 also contain the other parameters for create_css_link()
522 such as $name, $alt, $mtype, and $xhtml_end
523 But do we need to?
524 *
525 * @return string The stylesheet links as they should be sent
526 * to the browser.
527 *
528 */
529 function fetch_external_stylesheet_links($sheets)
530 {
531
532 if (!is_array($sheets)) $sheets = array($sheets);
533 $output = '';
534
535 foreach ($sheets as $sheet) {
536 $output .= create_css_link($sheet);
537 }
538
539 return $output;
540
541 }
542
543 /**
544 * Send HTTP header(s) to browser.
545 *
546 * Subclasses can override this function if headers are
547 * managed differently in the template set's target output
548 * interface.
549 *
550 * @param mixed $headers A list of (or a single) header
551 * text to be sent.
552 *
553 */
554 function header($headers)
555 {
556
557 if (!is_array($headers)) $headers = array($headers);
558
559 foreach ($headers as $header) {
560 header($header);
561 }
562
563 }
564
565 /**
566 * Generate a link to the right-to-left stylesheet for
567 * this template set, or use the one for the default
568 * template set if not found, or finally, fall back
569 * to SquirrelMail's own "rtl.css" if need be.
570 *
571 * Subclasses can override this function if stylesheets are
572 * created differently for the template set's target output
573 * interface.
574 *
575 * @return string The stylesheet link as it should be sent
576 * to the browser.
577 *
578 */
579 function fetch_right_to_left_stylesheet_link()
580 {
581
582 // get right template file
583 //
584 $sheet = $this->get_template_file_path('css/rtl.css');
585
586 // fall back to SquirrelMail's own default stylesheet
587 //
588 if (empty($sheet)) {
589 $sheet = SM_PATH . 'css/rtl.css';
590 }
591
592 return create_css_link($sheet);
593
594 }
595
596 /**
597 * Display the template
598 *
599 * @param string $file The template file to use
600 *
601 */
602 function display($file)
603 {
604
605 echo $this->fetch($file);
606
607 }
608
609 /**
610 * Applies the template and returns the resultant content string.
611 *
612 * @param string $file The template file to use
613 *
614 * @return string The template contents after applying the given template
615 *
616 */
617 function fetch($file) {
618
619 // get right template file
620 //
621 $template = $this->get_template_file_path($file);
622
623 // special case stylesheet.tpl falls back to SquirrelMail's
624 // own default stylesheet
625 //
626 if (empty($template) && $file == 'css/stylesheet.tpl') {
627 $template = SM_PATH . 'css/default.css';
628 }
629
630 if (empty($template)) {
631
632 trigger_error('The template "' . htmlspecialchars($file)
633 . '" could not be fetched!', E_USER_ERROR);
634
635 } else {
636
637 $aPluginOutput = array();
638 $aPluginOutput = concat_hook_function('template_construct_' . $file,
639 array($aPluginOutput, $this));
640 $this->assign('plugin_output', $aPluginOutput);
641
642 $output = $this->apply_template($template);
643
644 // CAUTION: USE OF THIS HOOK IS HIGHLY DISCOURAGED AND CAN
645 // RESULT IN NOTICABLE PERFORMANCE DEGREDATION. Plugins
646 // using this hook will probably be rejected by the
647 // SquirrelMail team.
648 //
649 $output = filter_hook_function('template_output', $output);
650
651 return $output;
652
653 }
654
655 }
656
657 /**
658 * Assigns values to template variables
659 *
660 * Note: this is an abstract method that must be implemented by subclass.
661 *
662 * @param array|string $tpl_var the template variable name(s)
663 * @param mixed $value the value to assign
664 *
665 */
666 function assign($tpl_var, $value = NULL) {
667
668 trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the assign() method.', E_USER_ERROR);
669
670 }
671
672 /**
673 * Assigns values to template variables by reference
674 *
675 * Note: this is an abstract method that must be implemented by subclass.
676 *
677 * @param string $tpl_var the template variable name
678 * @param mixed $value the referenced value to assign
679 *
680 */
681 function assign_by_ref($tpl_var, &$value) {
682
683 trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the assign_by_ref() method.', E_USER_ERROR);
684
685 }
686
687 /**
688 * Appends values to template variables
689 *
690 * Note: this is an abstract method that must be implemented by subclass.
691 *
692 * @param array|string $tpl_var the template variable name(s)
693 * @param mixed $value the value to append
694 * @param boolean $merge when $value is given as an array,
695 * this indicates whether or not that
696 * array itself should be appended as
697 * a new template variable value or if
698 * that array's values should be merged
699 * into the existing array of template
700 * variable values
701 *
702 */
703 function append($tpl_var, $value = NULL, $merge = FALSE) {
704
705 trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the append() method.', E_USER_ERROR);
706
707 }
708
709 /**
710 * Appends values to template variables by reference
711 *
712 * Note: this is an abstract method that must be implemented by subclass.
713 *
714 * @param string $tpl_var the template variable name
715 * @param mixed $value the referenced value to append
716 * @param boolean $merge when $value is given as an array,
717 * this indicates whether or not that
718 * array itself should be appended as
719 * a new template variable value or if
720 * that array's values should be merged
721 * into the existing array of template
722 * variable values
723 *
724 */
725 function append_by_ref($tpl_var, &$value, $merge = FALSE) {
726
727 trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the append_by_ref() method.', E_USER_ERROR);
728
729 }
730
731 /**
732 * Applys the template and generates final output destined
733 * for the user's browser
734 *
735 * Note: this is an abstract method that must be implemented by subclass.
736 *
737 * @param string $filepath The full file path to the template to be applied
738 *
739 * @return string The output for the given template
740 *
741 */
742 function apply_template($filepath) {
743
744 trigger_error('Template subclass (' . $this->template_engine . 'Template.class.php) needs to implement the apply_template() method.', E_USER_ERROR);
745
746 }
747
748}
749