Template.class.php 23 KB

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