2 namespace wcf\system\template
;
3 use wcf\system\cache\builder\TemplateGroupCacheBuilder
;
4 use wcf\system\cache\builder\TemplateListenerCodeCacheBuilder
;
5 use wcf\system\event\EventHandler
;
6 use wcf\system\exception\SystemException
;
8 use wcf\system\SingletonFactory
;
9 use wcf\util\DirectoryUtil
;
10 use wcf\util\HeaderUtil
;
11 use wcf\util\StringUtil
;
14 * Loads and displays template.
16 * @author Alexander Ebert
17 * @copyright 2001-2014 WoltLab GmbH
18 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
19 * @package com.woltlab.wcf
20 * @subpackage system.template
21 * @category Community Framework
23 class TemplateEngine
extends SingletonFactory
{
25 * directory used to cache previously compiled templates
28 public $compileDir = '';
31 * active language id used to identify specific language versions of compiled templates
34 public $languageID = 0;
37 * directories used as template source
40 public $templatePaths = array();
43 * namespace containing template modifiers and plugins
46 public $pluginNamespace = '';
49 * active template compiler
50 * @var \wcf\system\template\TemplateCompiler
52 protected $compilerObj = null;
55 * forces the template engine to recompile all included templates
58 protected $forceCompile = false;
61 * list of registered prefilters
64 protected $prefilters = array();
67 * cached list of known template groups
70 protected $templateGroupCache = array();
73 * active template group id
76 protected $templateGroupID = 0;
79 * all available template variables and those assigned during runtime
82 protected $v = array();
85 * all cached variables for usage after execution in sandbox
88 protected $sandboxVars = array();
91 * contains all templates with assigned template listeners.
94 protected $templateListeners = array();
97 * true, if template listener code was already loaded
100 protected $templateListenersLoaded = false;
103 * current environment
106 protected $environment = 'user';
109 * @see \wcf\system\SingletonFactory::init()
111 protected function init() {
112 $this->templatePaths
= array('wcf' => WCF_DIR
.'templates/');
113 $this->pluginNamespace
= 'wcf\system\template\plugin\\';
114 $this->compileDir
= WCF_DIR
.'templates/compiled/';
116 $this->loadTemplateGroupCache();
117 $this->assignSystemVariables();
118 $this->loadTemplateListeners();
122 * Adds a new application.
124 * @param string $abbreviation
125 * @param string $templatePath
127 public function addApplication($abbreviation, $templatePath) {
128 $this->templatePaths
[$abbreviation] = $templatePath;
132 * Sets active language id.
134 * @param integer $languageID
136 public function setLanguageID($languageID) {
137 $this->languageID
= $languageID;
141 * Assigns some system variables.
143 protected function assignSystemVariables() {
144 $this->v
['tpl'] = array();
146 // assign super globals
147 $this->v
['tpl']['get'] =& $_GET;
148 $this->v
['tpl']['post'] =& $_POST;
149 $this->v
['tpl']['cookie'] =& $_COOKIE;
150 $this->v
['tpl']['server'] =& $_SERVER;
151 $this->v
['tpl']['env'] =& $_ENV;
154 $this->v
['tpl']['now'] = TIME_NOW
;
155 $this->v
['tpl']['template'] = '';
156 $this->v
['tpl']['includedTemplates'] = array();
158 // section / foreach / capture arrays
159 $this->v
['tpl']['section'] = $this->v
['tpl']['foreach'] = $this->v
['tpl']['capture'] = array();
163 * Assigns a template variable.
165 * @param mixed $variable
166 * @param mixed $value
168 public function assign($variable, $value = '') {
169 if (is_array($variable)) {
170 foreach ($variable as $key => $value) {
171 if (empty($key)) continue;
173 $this->assign($key, $value);
177 $this->v
[$variable] = $value;
182 * Appends content to an existing template variable.
184 * @param mixed $variable
185 * @param mixed $value
187 public function append($variable, $value = '') {
188 if (is_array($variable)) {
189 foreach ($variable as $key => $val) {
191 $this->append($key, $val);
196 if (!empty($variable)) {
197 if (isset($this->v
[$variable])) {
198 if (is_array($this->v
[$variable]) && is_array($value)) {
199 $keys = array_keys($value);
200 foreach ($keys as $key) {
201 if (isset($this->v
[$variable][$key])) {
202 $this->v
[$variable][$key] .= $value[$key];
205 $this->v
[$variable][$key] = $value[$key];
210 $this->v
[$variable] .= $value;
214 $this->v
[$variable] = $value;
221 * Prepends content to an existing template variable.
223 * @param mixed $variable
224 * @param mixed $value
226 public function prepend($variable, $value = '') {
227 if (is_array($variable)) {
228 foreach ($variable as $key => $val) {
230 $this->prepend($key, $val);
235 if (!empty($variable)) {
236 if (isset($this->v
[$variable])) {
237 if (is_array($this->v
[$variable]) && is_array($value)) {
238 $keys = array_keys($value);
239 foreach ($keys as $key) {
240 if (isset($this->v
[$variable][$key])) {
241 $this->v
[$variable][$key] = $value[$key] . $this->v
[$variable][$key];
244 $this->v
[$variable][$key] = $value[$key];
249 $this->v
[$variable] = $value . $this->v
[$variable];
253 $this->v
[$variable] = $value;
260 * Assigns a template variable by reference.
262 * @param string $variable
263 * @param mixed $value
265 public function assignByRef($variable, &$value) {
266 if (!empty($variable)) {
267 $this->v
[$variable] = &$value;
272 * Clears an assignment of template variables.
274 * @param mixed $variables
276 public function clearAssign(array $variables) {
277 foreach ($variables as $key) {
278 unset($this->v
[$key]);
283 * Clears assignment of all template variables. This should not be called
284 * during runtime as it could leed to an unexpected behaviour.
286 public function clearAllAssign() {
291 * Outputs a template.
293 * @param string $templateName
294 * @param string $application
295 * @param boolean $sendHeaders
297 public function display($templateName, $application = 'wcf', $sendHeaders = true) {
299 HeaderUtil
::sendHeaders();
301 // call beforeDisplay event
302 if (!defined('NO_IMPORTS')) EventHandler
::getInstance()->fireAction($this, 'beforeDisplay');
305 $sourceFilename = $this->getSourceFilename($templateName, $application);
306 $compiledFilename = $this->getCompiledFilename($templateName, $application);
307 $metaDataFilename = $this->getMetaDataFilename($templateName);
308 $metaData = $this->getMetaData($templateName, $metaDataFilename);
310 // check if compilation is necessary
311 if (($metaData === null) ||
!$this->isCompiled($templateName, $sourceFilename, $compiledFilename, $application, $metaData)) {
313 $this->compileTemplate($templateName, $sourceFilename, $compiledFilename, array(
314 'application' => $application,
316 'filename' => $metaDataFilename
320 // assign current package id
321 $this->assign('__APPLICATION', $application);
323 include($compiledFilename);
326 // call afterDisplay event
327 if (!defined('NO_IMPORTS')) EventHandler
::getInstance()->fireAction($this, 'afterDisplay');
332 * Returns the absolute filename of a template source.
334 * @param string $templateName
335 * @param string $application
336 * @return string $path
338 public function getSourceFilename($templateName, $application) {
339 $sourceFilename = $this->getPath($this->templatePaths
[$application], $templateName);
340 if (!empty($sourceFilename)) {
341 return $sourceFilename;
344 // try to find template within WCF if not already searching WCF
345 if ($application != 'wcf') {
346 $sourceFilename = $this->getSourceFilename($templateName, 'wcf');
347 if (!empty($sourceFilename)) {
348 return $sourceFilename;
352 throw new SystemException("Unable to find template '".$templateName."'");
356 * Returns path if template was found.
358 * @param string $templatePath
359 * @param string $templateName
362 protected function getPath($templatePath, $templateName) {
363 $templateGroupID = $this->templateGroupID
;
365 while ($templateGroupID != 0) {
366 $templateGroup = $this->templateGroupCache
[$templateGroupID];
368 $path = $templatePath.$templateGroup->templateGroupFolderName
.$templateName.'.tpl';
369 if (file_exists($path)) {
373 $templateGroupID = $templateGroup->parentTemplateGroupID
;
376 // use default template
377 $path = $templatePath.$templateName.'.tpl';
379 if (file_exists($path)) {
387 * Returns the absolute filename of a compiled template.
389 * @param string $templateName
390 * @param string $application
393 public function getCompiledFilename($templateName, $application) {
394 return $this->compileDir
.$this->templateGroupID
.'_'.$application.'_'.$this->languageID
.'_'.$templateName.'.php';
398 * Returns the absolute filename for template's meta data.
400 * @param string $templateName
403 public function getMetaDataFilename($templateName) {
404 return $this->compileDir
.$this->templateGroupID
.'_'.$templateName.'.meta.php';
408 * Returns true if the template with the given data is already compiled.
410 * @param string $templateName
411 * @param string $sourceFilename
412 * @param string $compiledFilename
413 * @param string $application
414 * @param array $metaData
417 protected function isCompiled($templateName, $sourceFilename, $compiledFilename, $application, array $metaData) {
418 if ($this->forceCompile ||
!file_exists($compiledFilename)) {
422 $sourceMTime = @filemtime
($sourceFilename);
423 $compileMTime = @filemtime
($compiledFilename);
425 if ($sourceMTime >= $compileMTime) {
429 // check for meta data
430 if (!empty($metaData['include'])) {
431 foreach ($metaData['include'] as $includedTemplate) {
432 $includedTemplateFilename = $this->getSourceFilename($includedTemplate, $application);
433 $includedMTime = @filemtime
($includedTemplateFilename);
435 if ($includedMTime >= $compileMTime) {
447 * Compiles a template.
449 * @param string $templateName
450 * @param string $sourceFilename
451 * @param string $compiledFilename
452 * @param array $metaData
454 protected function compileTemplate($templateName, $sourceFilename, $compiledFilename, array $metaData) {
456 $sourceContent = $this->getSourceContent($sourceFilename);
459 $this->getCompiler()->compile($templateName, $sourceContent, $compiledFilename, $metaData);
463 * Returns the template compiler.
465 * @return \wcf\system\template\TemplateCompiler
467 public function getCompiler() {
468 if ($this->compilerObj
=== null) {
469 $this->compilerObj
= new TemplateCompiler($this);
472 return $this->compilerObj
;
476 * Reads the content of a template file.
478 * @param string $sourceFilename
479 * @return string $sourceContent
481 public function getSourceContent($sourceFilename) {
483 if (!file_exists($sourceFilename) ||
(($sourceContent = @file_get_contents
($sourceFilename)) === false)) {
484 throw new SystemException("Could not open template '$sourceFilename' for reading");
487 return $sourceContent;
492 * Returns the class name of a plugin.
494 * @param string $type
498 public function getPluginClassName($type, $tag) {
499 return $this->pluginNamespace
.StringUtil
::firstCharToUpperCase($tag).StringUtil
::firstCharToUpperCase(mb_strtolower($type)).'TemplatePlugin';
503 * Enables execution in sandbox.
505 public function enableSandbox() {
506 $index = count($this->sandboxVars
);
507 $this->sandboxVars
[$index] = $this->v
;
511 * Disables execution in sandbox.
513 public function disableSandbox() {
514 if (empty($this->sandboxVars
)) {
515 throw new SystemException('TemplateEngine is currently not running in a sandbox.');
518 $this->v
= array_pop($this->sandboxVars
);
522 * Returns the output of a template.
524 * @param string $templateName
525 * @param string $application
526 * @param array $variables
527 * @param boolean $sandbox enables execution in sandbox
530 public function fetch($templateName, $application = 'wcf', array $variables = array(), $sandbox = false) {
533 $this->enableSandbox();
536 // add new template variables
537 if (!empty($variables)) {
538 $this->v
= array_merge($this->v
, $variables);
543 $this->display($templateName, $application, false);
544 $output = ob_get_contents();
549 $this->disableSandbox();
556 * Executes a compiled template scripting source and returns the result.
558 * @param string $compiledSource
559 * @param array $variables
560 * @param boolean $sandbox enables execution in sandbox
563 public function fetchString($compiledSource, array $variables = array(), $sandbox = true) {
566 $this->enableSandbox();
569 // add new template variables
570 if (!empty($variables)) {
571 $this->v
= array_merge($this->v
, $variables);
576 eval('?>'.$compiledSource);
577 $output = ob_get_contents();
582 $this->disableSandbox();
589 * Deletes all compiled templates.
591 * @param string $compileDir
593 public static function deleteCompiledTemplates($compileDir = '') {
594 if (empty($compileDir)) $compileDir = WCF_DIR
.'templates/compiled/';
596 // delete compiled templates
597 DirectoryUtil
::getInstance($compileDir)->removePattern(new Regex('.*_.*\.php$'));
601 * Returns an array with all prefilters.
603 * @return array<string>
605 public function getPrefilters() {
606 return $this->prefilters
;
610 * Returns the active template group id.
614 public function getTemplateGroupID() {
615 return $this->templateGroupID
;
619 * Sets the active template group id.
621 * @param integer $templateGroupID
623 public function setTemplateGroupID($templateGroupID) {
624 if ($templateGroupID && !isset($this->templateGroupCache
[$templateGroupID])) {
625 $templateGroupID = 0;
628 $this->templateGroupID
= $templateGroupID;
632 * Loads cached template group information.
634 protected function loadTemplateGroupCache() {
635 $this->templateGroupCache
= TemplateGroupCacheBuilder
::getInstance()->getData();
639 * Registers prefilters.
641 * @param array<string> $prefilters
643 public function registerPrefilter(array $prefilters) {
644 foreach ($prefilters as $name) {
645 $this->prefilters
[$name] = $name;
650 * Sets the dir for the compiled templates.
652 * @param string $compileDir
654 public function setCompileDir($compileDir) {
655 if (!is_dir($compileDir)) {
656 throw new SystemException("'".$compileDir."' is not a valid dir");
659 $this->compileDir
= $compileDir;
663 * Includes a template.
665 * @param string $templateName
666 * @param string $application
667 * @param array $variables
668 * @param boolean $sandbox enables execution in sandbox
670 protected function includeTemplate($templateName, $application, array $variables = array(), $sandbox = true) {
673 $this->enableSandbox();
676 // add new template variables
677 if (!empty($variables)) {
678 $this->v
= array_merge($this->v
, $variables);
682 $this->display($templateName, $application, false);
686 $this->disableSandbox();
691 * Returns the value of a template variable.
693 * @param string $varname
696 public function get($varname) {
697 if (isset($this->v
[$varname])) {
698 return $this->v
[$varname];
705 * Loads all available template listeners.
707 * @deprecated since 2.1
709 protected function loadTemplateListeners() {
714 * Returns true if requested template has assigned template listeners.
716 * @param string $templateName
717 * @param string $application
720 public function hasTemplateListeners($templateName, $application = 'wcf') {
721 if (isset($this->templateListeners
[$application]) && isset($this->templateListeners
[$application][$templateName])) {
729 * Loads template listener code.
731 protected function loadTemplateListenerCode() {
732 if (!$this->templateListenersLoaded
) {
733 $this->templateListeners
= TemplateListenerCodeCacheBuilder
::getInstance()->getData(array('environment' => $this->environment
));
734 $this->templateListenersLoaded
= true;
739 * Returns template listener's code.
741 * @param string $templateName
742 * @param string $eventName
745 public function getTemplateListenerCode($templateName, $eventName) {
746 $this->loadTemplateListenerCode();
748 if (isset($this->templateListeners
[$templateName][$eventName])) {
749 return implode("\n", $this->templateListeners
[$templateName][$eventName]);
756 * Reads meta data from file.
758 * @param string $templateName
759 * @param string $filename
762 protected function getMetaData($templateName, $filename) {
763 if (!file_exists($filename) ||
!is_readable($filename)) {
768 $contents = file_get_contents($filename);
770 // find first newline
771 $position = strpos($contents, "\n");
772 if ($position === false) {
777 $contents = substr($contents, $position +
1);
779 // read serializes data
780 $data = @unserialize
($contents);
781 if ($data === false ||
!is_array($data)) {