PageRenderTime 67ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/library/XenForo/Template/Abstract.php

https://github.com/manhhoangxuan/DTUI_201105
PHP | 635 lines | 416 code | 60 blank | 159 comment | 19 complexity | 07b782fa8f4ee91c2a8da8a35e0a768f MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * Base template rendering class.
  4. *
  5. * Note that due to a lack of late static binding support, all static properties
  6. * and any method that deals with those properties (via "self", regardless of whether
  7. * it's static or not) must be (re)defined in child classes!
  8. *
  9. * @package XenForo_Core
  10. */
  11. abstract class XenForo_Template_Abstract
  12. {
  13. /**
  14. * Cached template data. Key is the template name; value is the compiled template.
  15. * All child classes must redefine this property!
  16. *
  17. * @var array
  18. */
  19. protected static $_templateCache = array();
  20. /**
  21. * A list of templates that still need to be loaded. Key is the template name.
  22. * All child classes must redefine this property!
  23. *
  24. * @var array
  25. */
  26. protected static $_toLoad = array();
  27. /**
  28. * Base path to compiled templates that are stored on disk.
  29. * All child classes must redefine this property!
  30. *
  31. * @var string
  32. */
  33. protected static $_filePath = '';
  34. /**
  35. * Array of required external resources for this type of template.
  36. * All child classes must redefine this property!
  37. *
  38. * @var array
  39. */
  40. protected static $_required = array();
  41. /**
  42. * Name of the template to load.
  43. *
  44. * @var string
  45. */
  46. protected $_templateName;
  47. /**
  48. * Key-value params to make available in the template.
  49. *
  50. * @var array
  51. */
  52. protected $_params = array();
  53. /**
  54. * PHP errors generated during template evaluation.
  55. *
  56. * @var array
  57. */
  58. protected $_templateErrors = array();
  59. /**
  60. * The ID of the language that templates will be retrieved from.
  61. *
  62. * @var integer
  63. */
  64. protected static $_languageId = 0;
  65. /**
  66. * Constructor
  67. *
  68. * @param string Template name
  69. * @param array Key-value parameters
  70. */
  71. public function __construct($templateName, array $params = array())
  72. {
  73. XenForo_CodeEvent::fire('template_create', array(&$templateName, &$params, $this));
  74. $this->_templateName = $templateName;
  75. $this->preloadTemplate($templateName);
  76. if ($params)
  77. {
  78. $this->setParams($params);
  79. }
  80. }
  81. /**
  82. * Creates a new template object of the current type. Mainly helpful
  83. * if an event only has the current template object in scope.
  84. *
  85. * @param string $templateName
  86. * @param array $params
  87. *
  88. * @return XenForo_Template_Abstract
  89. */
  90. public function create($templateName, array $params = array())
  91. {
  92. $class = get_class($this);
  93. return new $class($templateName, $params);
  94. }
  95. /**
  96. * Sets the language ID that templates will be retrieved from.
  97. *
  98. * @param integer $languageId
  99. */
  100. public static function setLanguageId($languageId)
  101. {
  102. self::$_languageId = intval($languageId);
  103. }
  104. /**
  105. * Add an array of params to the template. Overwrites parameters with the same name.
  106. *
  107. * @param array
  108. */
  109. public function setParams(array $params)
  110. {
  111. $this->_params = ($this->_params ? XenForo_Application::mapMerge($this->_params, $params) : $params);
  112. }
  113. /**
  114. * Add a single param to the template. Overwrites parameters with the same name.
  115. *
  116. * @param string
  117. */
  118. public function setParam($key, $value)
  119. {
  120. $this->_params[$key] = $value;
  121. }
  122. /**
  123. * Get all template parameters.
  124. *
  125. * @return array
  126. */
  127. public function getParams()
  128. {
  129. return $this->_params;
  130. }
  131. /**
  132. * Get a single template parameter.
  133. *
  134. * @param string
  135. *
  136. * @return mixed Null if not found.
  137. */
  138. public function getParam($key)
  139. {
  140. if (array_key_exists($key, $this->_params))
  141. {
  142. return $this->_params[$key];
  143. }
  144. return null;
  145. }
  146. /**
  147. * @return string
  148. */
  149. public function getTemplateName()
  150. {
  151. return $this->_templateName;
  152. }
  153. /**
  154. * Renders the specified template and returns the output.
  155. *
  156. * @return string
  157. */
  158. public function render()
  159. {
  160. $__template = $this->_loadTemplate($this->_templateName);
  161. if ($__template === '')
  162. {
  163. return '';
  164. }
  165. XenForo_Phrase::loadPhrases();
  166. set_error_handler(array($this, 'handleTemplateError'));
  167. $this->_templateErrors = array();
  168. $__output = $this->_renderInternal($__template, $__extraData);
  169. restore_error_handler();
  170. XenForo_CodeEvent::fire('template_post_render', array($this->_templateName, &$__output, &$__extraData, $this));
  171. if (is_array($__extraData) && !empty($__extraData))
  172. {
  173. $this->_mergeExtraContainerData($__extraData);
  174. }
  175. if ($this->_templateErrors && XenForo_Application::debugMode())
  176. {
  177. if ($this->_usingTemplateFiles())
  178. {
  179. $templateCode = file_get_contents($__template);
  180. }
  181. else
  182. {
  183. $templateCode = $__template;
  184. }
  185. $lines = preg_split('/\r?\n/', $__template);
  186. echo "<div class=\"baseHtml\"><h4>Template Errors: " . htmlspecialchars($this->_templateName) . "</h4><ol>\n";
  187. foreach ($this->_templateErrors AS $error)
  188. {
  189. $contextLine = ($error['line'] > 1 ? $error['line'] - 2 : 0);
  190. $context = array_slice($lines, $contextLine, 3, true);
  191. echo "\t<li><i>" . htmlspecialchars($error['error']) . "</i> in " . htmlspecialchars($error['file']) . ", line $error[line]";
  192. if ($context)
  193. {
  194. echo ": <pre>";
  195. foreach ($context AS $lineNum => $contextLine)
  196. {
  197. echo ($lineNum + 1) . ": " . htmlspecialchars($contextLine) . "\n";
  198. }
  199. echo "</pre>";
  200. }
  201. echo "</li>\n";
  202. }
  203. echo "</ol></div>\n\n";
  204. }
  205. return $__output;
  206. }
  207. /**
  208. * Internal template rendering.
  209. *
  210. * @param string $__template Template text or name of template file
  211. * @param array $__extraData Returned extra data from the render
  212. *
  213. * @return string Rendered template
  214. */
  215. protected function _renderInternal($__template, &$__extraData)
  216. {
  217. $__params = $this->_params; // special variable for dumping purposes
  218. extract($this->_params);
  219. $__output = '';
  220. $__extraData = array();
  221. if ($this->_usingTemplateFiles())
  222. {
  223. include($__template);
  224. }
  225. else
  226. {
  227. eval($__template);
  228. }
  229. return $__output;
  230. }
  231. /**
  232. * Calls the specified template hook event.
  233. *
  234. * Params passed by template explicitly will respect mappings and greater context.
  235. * Raw params are still available via the template object.
  236. *
  237. * @param string $name Name of the hook
  238. * @param string $contents Contents of the hook; may be empty
  239. * @param array $params List of params to pass specifically; these will respect mappings.
  240. *
  241. * @return string New version of the contents (could be modified)
  242. */
  243. public function callTemplateHook($name, $contents, array $params)
  244. {
  245. XenForo_CodeEvent::fire('template_hook', array($name, &$contents, $params, $this));
  246. return $contents;
  247. }
  248. /**
  249. * Error handler that traps errors in templates.
  250. *
  251. * @param integer $errorType Type of error (one of the E_* constants)
  252. * @param string $errorString
  253. * @param string $file
  254. * @param integer $line
  255. */
  256. public function handleTemplateError($errorType, $errorString, $file, $line)
  257. {
  258. if ($errorType == E_NOTICE)
  259. {
  260. return;
  261. }
  262. if ($errorType & error_reporting())
  263. {
  264. $this->_templateErrors[] = array(
  265. 'type' => $errorType,
  266. 'error' => $errorString,
  267. 'file' => $file,
  268. 'line' => $line
  269. );
  270. }
  271. }
  272. /**
  273. * Gets required external resources as HTML for use in a template directly.
  274. *
  275. * @param string Type of requirement to fetch
  276. *
  277. * @return string Requirements as HTML
  278. */
  279. public function getRequiredExternalsAsHtml($type)
  280. {
  281. $required = $this->_getRequiredExternals();
  282. if (empty($required[$type]))
  283. {
  284. return '';
  285. }
  286. $typeRequired = array_unique($required[$type]);
  287. switch ($type)
  288. {
  289. case 'js':
  290. return $this->getRequiredJavaScriptAsHtml($typeRequired);
  291. case 'css':
  292. return $this->getRequiredCssAsHtml($this->getRequiredCssUrl($typeRequired));
  293. default:
  294. return false;
  295. }
  296. }
  297. public function getRequiredExternalsAsJson()
  298. {
  299. $required = $this->_getRequiredExternals();
  300. $output = array();
  301. foreach ($required AS $type => $externals)
  302. {
  303. if ($type == 'js')
  304. {
  305. $externals = $this->addJsVersionToJsUrls($externals);
  306. }
  307. foreach ($externals AS $external)
  308. {
  309. $output[$external] = true;
  310. }
  311. }
  312. return json_encode($output);
  313. }
  314. /**
  315. * Gets required externals in a structured way. Values will be returned as a list of URLs.
  316. *
  317. * @param string $type
  318. *
  319. * @return array List of URLs
  320. */
  321. public function getRequiredExternals($type)
  322. {
  323. $required = $this->_getRequiredExternals();
  324. if (empty($required[$type]))
  325. {
  326. return '';
  327. }
  328. $typeRequired = array_reverse(array_unique($required[$type]));
  329. switch ($type)
  330. {
  331. case 'js':
  332. return $this->addJsVersionToJsUrls($typeRequired);
  333. case 'css':
  334. return array(
  335. 'stylesheets' => $typeRequired,
  336. 'urlTemplate' => $this->getRequiredCssUrl(array('__sentinel__'))
  337. );
  338. default:
  339. return false;
  340. }
  341. }
  342. /**
  343. * Gets the list of required JavaScript files as HTML script tags.
  344. *
  345. * @param array $requirements Array of paths to JS files.
  346. *
  347. * @return string
  348. */
  349. public function getRequiredJavaScriptAsHtml(array $requirements)
  350. {
  351. $javaScriptSource = XenForo_Application::get('options')->javaScriptSource;
  352. $output = '';
  353. foreach ($this->addJsVersionToJsUrls($requirements) AS $requirement)
  354. {
  355. $requirement = preg_replace('#^js/#', $javaScriptSource . '/', $requirement);
  356. $output .= "\t" . '<script type="text/javascript" src="' . $requirement . '"></script>' . "\n";
  357. }
  358. return $output;
  359. }
  360. protected function addJsVersionToJsUrls(array $jsFiles)
  361. {
  362. $key = '_v=' . XenForo_Application::$jsVersion;
  363. foreach ($jsFiles AS &$file)
  364. {
  365. $file = $file . (strpos($file, '?') ? '&' : '?') . $key;
  366. }
  367. return $jsFiles;
  368. }
  369. /**
  370. * Gets the required CSS as an HTML tag. Expected arg is simple a URL.
  371. *
  372. * @param string $requirement
  373. *
  374. * @return string
  375. */
  376. public function getRequiredCssAsHtml($requirement)
  377. {
  378. return '<link rel="stylesheet" type="text/css" href="' . htmlspecialchars($requirement) . "\" />\n";
  379. }
  380. /**
  381. * Gets the URL to fetch the list of required CSS templates. Requirements
  382. * should be a list of CSS templates, not including the trailing ".css".
  383. *
  384. * @param array $requirements
  385. *
  386. * @return string
  387. */
  388. abstract public function getRequiredCssUrl(array $requirements);
  389. /**
  390. * Implicit string cast renders the template.
  391. *
  392. * @return string
  393. */
  394. public function __toString()
  395. {
  396. return $this->render();
  397. }
  398. /**
  399. * Load the named template.
  400. *
  401. * @param string Template name
  402. *
  403. * @return string Compiled version of the template
  404. */
  405. protected function _loadTemplate($templateName)
  406. {
  407. if ($template = $this->_loadTemplateFilePath($templateName))
  408. {
  409. return $template;
  410. }
  411. else if ($template = $this->_loadTemplateFromCache($templateName))
  412. {
  413. return $template;
  414. }
  415. else
  416. {
  417. $this->_loadTemplates();
  418. return $this->_loadTemplateFromCache($templateName);
  419. }
  420. }
  421. /**
  422. * Bulk load all templates that are required.
  423. */
  424. protected function _loadTemplates()
  425. {
  426. $toLoad = $this->getToLoadList();
  427. if (!$toLoad)
  428. {
  429. return;
  430. }
  431. $templates = $this->_getTemplatesFromDataSource(array_keys($toLoad));
  432. if ($templates)
  433. {
  434. $this->_mergeIntoTemplateCache($templates);
  435. }
  436. $this->_resetToLoadList();
  437. }
  438. /**
  439. * Adds required external for this type of template to be output later.
  440. *
  441. * @param string Type of requirement
  442. * @param string Value for requirement
  443. */
  444. public function addRequiredExternal($type, $requirement)
  445. {
  446. $existing = $this->_getRequiredExternals();
  447. $existing[$type][] = $requirement;
  448. $this->_setRequiredExternals($existing);
  449. }
  450. /**
  451. * Goes to the data source to load the list of templates.
  452. *
  453. * @param array Template list
  454. *
  455. * @return array Key-value pairs of template titles/compiled templates
  456. */
  457. abstract protected function _getTemplatesFromDataSource(array $templateList);
  458. /**
  459. * Helper function get the list of templates that are waiting to be loaded.
  460. *
  461. * @return array
  462. */
  463. abstract public function getToLoadList();
  464. /**
  465. * Resets the to load list to empty.
  466. */
  467. abstract protected function _resetToLoadList();
  468. /**
  469. * Merges key-value pairs of template names/compiled templates into the local template
  470. * cache.
  471. *
  472. * @param array Templates (key: name, value: compiled output)
  473. */
  474. abstract protected function _mergeIntoTemplateCache(array $templates);
  475. /**
  476. * Non-static method for pre-loading a template.
  477. *
  478. * @param string Template name
  479. */
  480. abstract protected function _preloadTemplate($templateName);
  481. /**
  482. * Loads a template out of the local template cache. If the template does not
  483. * exist, it will be set to an empty string. This will be overwritten if
  484. * the template is loaded from the data source.
  485. *
  486. * @param string Template name
  487. *
  488. * @return string Compiled template
  489. */
  490. abstract protected function _loadTemplateFromCache($templateName);
  491. /**
  492. * Loads the file path where a template is located in the file system, if
  493. * templates are being stored in the file system.
  494. *
  495. * @param string Template name
  496. *
  497. * @param string Empty string (not using file system) or file path
  498. */
  499. abstract protected function _loadTemplateFilePath($templateName);
  500. /**
  501. * Gets the list of required external resources.
  502. *
  503. * @return array
  504. */
  505. abstract protected function _getRequiredExternals();
  506. /**
  507. * Sets the list of required external resources.
  508. *
  509. * @param array
  510. */
  511. abstract protected function _setRequiredExternals(array $required);
  512. /**
  513. * Merges in extra container data from the template render.
  514. *
  515. * @param array
  516. */
  517. abstract protected function _mergeExtraContainerData(array $extraData);
  518. /**
  519. * Determines whether we are using templates in the file system.
  520. *
  521. * @return boolean
  522. */
  523. abstract protected function _usingTemplateFiles();
  524. /**
  525. * Specify a template that needs to be preloaded for use later. This is useful
  526. * if you think a render is going to be called before the template you require
  527. * is to be used.
  528. *
  529. * @param string Template to preload
  530. */
  531. public static function preloadTemplate($templateName)
  532. {
  533. throw new XenForo_Exception('This function must be overridden in a child class.');
  534. }
  535. /**
  536. * Manually sets a template. This is primarily useful for testing.
  537. *
  538. * @param string Name of the template
  539. * @param string Value for the template
  540. */
  541. public static function setTemplate($templateName, $templateValue)
  542. {
  543. throw new XenForo_Exception('This function must be overridden in a child class.');
  544. }
  545. /**
  546. * Resets the template system state.
  547. */
  548. public static function reset()
  549. {
  550. throw new XenForo_Exception('This function must be overridden in a child class.');
  551. }
  552. }