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-2015 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 $application => $includedTemplates) {
432 foreach ($includedTemplates as $includedTemplate) {
433 $includedTemplateFilename = $this->getSourceFilename($includedTemplate, $application);
434 $includedMTime = @filemtime
($includedTemplateFilename);
436 if ($includedMTime >= $compileMTime) {
449 * Compiles a template.
451 * @param string $templateName
452 * @param string $sourceFilename
453 * @param string $compiledFilename
454 * @param array $metaData
456 protected function compileTemplate($templateName, $sourceFilename, $compiledFilename, array $metaData) {
458 $sourceContent = $this->getSourceContent($sourceFilename);
461 $this->getCompiler()->compile($templateName, $sourceContent, $compiledFilename, $metaData);
465 * Returns the template compiler.
467 * @return \wcf\system\template\TemplateCompiler
469 public function getCompiler() {
470 if ($this->compilerObj
=== null) {
471 $this->compilerObj
= new TemplateCompiler($this);
474 return $this->compilerObj
;
478 * Reads the content of a template file.
480 * @param string $sourceFilename
481 * @return string $sourceContent
483 public function getSourceContent($sourceFilename) {
485 if (!file_exists($sourceFilename) ||
(($sourceContent = @file_get_contents
($sourceFilename)) === false)) {
486 throw new SystemException("Could not open template '$sourceFilename' for reading");
489 return $sourceContent;
494 * Returns the class name of a plugin.
496 * @param string $type
500 public function getPluginClassName($type, $tag) {
501 return $this->pluginNamespace
.StringUtil
::firstCharToUpperCase($tag).StringUtil
::firstCharToUpperCase(mb_strtolower($type)).'TemplatePlugin';
505 * Enables execution in sandbox.
507 public function enableSandbox() {
508 $index = count($this->sandboxVars
);
509 $this->sandboxVars
[$index] = $this->v
;
513 * Disables execution in sandbox.
515 public function disableSandbox() {
516 if (empty($this->sandboxVars
)) {
517 throw new SystemException('TemplateEngine is currently not running in a sandbox.');
520 $this->v
= array_pop($this->sandboxVars
);
524 * Returns the output of a template.
526 * @param string $templateName
527 * @param string $application
528 * @param array $variables
529 * @param boolean $sandbox enables execution in sandbox
532 public function fetch($templateName, $application = 'wcf', array $variables = array(), $sandbox = false) {
535 $this->enableSandbox();
538 // add new template variables
539 if (!empty($variables)) {
540 $this->v
= array_merge($this->v
, $variables);
545 $this->display($templateName, $application, false);
546 $output = ob_get_contents();
551 $this->disableSandbox();
558 * Executes a compiled template scripting source and returns the result.
560 * @param string $compiledSource
561 * @param array $variables
562 * @param boolean $sandbox enables execution in sandbox
565 public function fetchString($compiledSource, array $variables = array(), $sandbox = true) {
568 $this->enableSandbox();
571 // add new template variables
572 if (!empty($variables)) {
573 $this->v
= array_merge($this->v
, $variables);
578 eval('?>'.$compiledSource);
579 $output = ob_get_contents();
584 $this->disableSandbox();
591 * Deletes all compiled templates.
593 * @param string $compileDir
595 public static function deleteCompiledTemplates($compileDir = '') {
596 if (empty($compileDir)) $compileDir = WCF_DIR
.'templates/compiled/';
598 // delete compiled templates
599 DirectoryUtil
::getInstance($compileDir)->removePattern(new Regex('.*_.*\.php$'));
603 * Returns an array with all prefilters.
605 * @return array<string>
607 public function getPrefilters() {
608 return $this->prefilters
;
612 * Returns the active template group id.
616 public function getTemplateGroupID() {
617 return $this->templateGroupID
;
621 * Sets the active template group id.
623 * @param integer $templateGroupID
625 public function setTemplateGroupID($templateGroupID) {
626 if ($templateGroupID && !isset($this->templateGroupCache
[$templateGroupID])) {
627 $templateGroupID = 0;
630 $this->templateGroupID
= $templateGroupID;
634 * Loads cached template group information.
636 protected function loadTemplateGroupCache() {
637 $this->templateGroupCache
= TemplateGroupCacheBuilder
::getInstance()->getData();
641 * Registers prefilters.
643 * @param array<string> $prefilters
645 public function registerPrefilter(array $prefilters) {
646 foreach ($prefilters as $name) {
647 $this->prefilters
[$name] = $name;
652 * Sets the dir for the compiled templates.
654 * @param string $compileDir
656 public function setCompileDir($compileDir) {
657 if (!is_dir($compileDir)) {
658 throw new SystemException("'".$compileDir."' is not a valid dir");
661 $this->compileDir
= $compileDir;
665 * Includes a template.
667 * @param string $templateName
668 * @param string $application
669 * @param array $variables
670 * @param boolean $sandbox enables execution in sandbox
672 protected function includeTemplate($templateName, $application, array $variables = array(), $sandbox = true) {
675 $this->enableSandbox();
678 // add new template variables
679 if (!empty($variables)) {
680 $this->v
= array_merge($this->v
, $variables);
684 $this->display($templateName, $application, false);
688 $this->disableSandbox();
693 * Returns the value of a template variable.
695 * @param string $varname
698 public function get($varname) {
699 if (isset($this->v
[$varname])) {
700 return $this->v
[$varname];
707 * Loads all available template listeners.
709 * @deprecated since 2.1
711 protected function loadTemplateListeners() {
720 public function hasTemplateListeners($templateName, $application = 'wcf') {
725 * Loads template listener code.
727 protected function loadTemplateListenerCode() {
728 if (!$this->templateListenersLoaded
) {
729 $this->templateListeners
= TemplateListenerCodeCacheBuilder
::getInstance()->getData(array('environment' => $this->environment
));
730 $this->templateListenersLoaded
= true;
735 * Returns template listener's code.
737 * @param string $templateName
738 * @param string $eventName
741 public function getTemplateListenerCode($templateName, $eventName) {
742 $this->loadTemplateListenerCode();
744 if (isset($this->templateListeners
[$templateName][$eventName])) {
745 return implode("\n", $this->templateListeners
[$templateName][$eventName]);
752 * Reads meta data from file.
754 * @param string $templateName
755 * @param string $filename
758 protected function getMetaData($templateName, $filename) {
759 if (!file_exists($filename) ||
!is_readable($filename)) {
764 $contents = file_get_contents($filename);
766 // find first newline
767 $position = strpos($contents, "\n");
768 if ($position === false) {
773 $contents = substr($contents, $position +
1);
775 // read serializes data
776 $data = @unserialize
($contents);
777 if ($data === false ||
!is_array($data)) {