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