X-Git-Url: https://git.stricted.de/?a=blobdiff_plain;f=wcfsetup%2Finstall%2Ffiles%2Flib%2Fsystem%2Ftemplate%2FTemplateEngine.class.php;h=cbf84b053771d61bd78c460a820b48b50227bc3e;hb=c0b28aa2c24deb4b2521e83e7ea14005e27f5fcf;hp=bf1d554835a541d43edad38331ae1a850390e2bf;hpb=1d56234d204a1c6a31390b2d705015ed8c0921e5;p=GitHub%2FWoltLab%2FWCF.git diff --git a/wcfsetup/install/files/lib/system/template/TemplateEngine.class.php b/wcfsetup/install/files/lib/system/template/TemplateEngine.class.php index bf1d554835..cbf84b0537 100755 --- a/wcfsetup/install/files/lib/system/template/TemplateEngine.class.php +++ b/wcfsetup/install/files/lib/system/template/TemplateEngine.class.php @@ -1,5 +1,7 @@ - * @package WoltLabSuite\Core\System\Template + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @package WoltLabSuite\Core\System\Template */ -class TemplateEngine extends SingletonFactory { - /** - * directory used to cache previously compiled templates - * @var string - */ - public $compileDir = ''; - - /** - * active language id used to identify specific language versions of compiled templates - * @var int - */ - public $languageID = 0; - - /** - * directories used as template source - * @var string[] - */ - public $templatePaths = []; - - /** - * namespace containing template modifiers and plugins - * @var string - */ - public $pluginNamespace = ''; - - /** - * active template compiler - * @var TemplateCompiler - */ - protected $compilerObj; - - /** - * forces the template engine to recompile all included templates - * @var bool - */ - protected $forceCompile = false; - - /** - * list of registered prefilters - * @var string[] - */ - protected $prefilters = []; - - /** - * cached list of known template groups - * @var array - */ - protected $templateGroupCache = []; - - /** - * active template group id - * @var int - */ - protected $templateGroupID = 0; - - /** - * all available template variables and those assigned during runtime - * @var mixed[][] - */ - protected $v = []; - - /** - * sandboxed values of currently active foreach loops' `item` and `key` variables - * - * for each currently active `foreach` loop, an array is added: - * $foreachHash => [ - * (optional) 'item' => sandboxed value of an existing variable with the same name, - * (optional) 'key' => (optional) sandboxed value of an existing variable with the same name - * ] - * - * @var mixed[][][] - */ - protected $foreachVars = []; - - /** - * all cached variables for usage after execution in sandbox - * @var mixed[][] - */ - protected $sandboxVars = []; - - /** - * contains all templates with assigned template listeners. - * @var string[][][] - */ - protected $templateListeners = []; - - /** - * true, if template listener code was already loaded - * @var bool - */ - protected $templateListenersLoaded = false; - - /** - * current environment - * @var string - */ - protected $environment = 'user'; - - /** - * @inheritDoc - */ - protected function init() { - $this->templatePaths = ['wcf' => WCF_DIR.'templates/']; - $this->pluginNamespace = 'wcf\system\template\plugin\\'; - $this->compileDir = WCF_DIR.'templates/compiled/'; - - $this->loadTemplateGroupCache(); - $this->assignSystemVariables(); - } - - /** - * Adds a new application. - * - * @param string $abbreviation - * @param string $templatePath - */ - public function addApplication($abbreviation, $templatePath) { - $this->templatePaths[$abbreviation] = $templatePath; - } - - /** - * Sets active language id. - * - * @param int $languageID - */ - public function setLanguageID($languageID) { - $this->languageID = $languageID; - } - - /** - * Assigns some system variables. - */ - protected function assignSystemVariables() { - $this->v['tpl'] = []; - - // assign super globals - $this->v['tpl']['get'] =& $_GET; - $this->v['tpl']['post'] =& $_POST; - $this->v['tpl']['cookie'] =& $_COOKIE; - $this->v['tpl']['server'] =& $_SERVER; - $this->v['tpl']['env'] =& $_ENV; - - // system info - $this->v['tpl']['now'] = TIME_NOW; - $this->v['tpl']['template'] = ''; - $this->v['tpl']['includedTemplates'] = []; - - // section / foreach / capture arrays - $this->v['tpl']['section'] = $this->v['tpl']['foreach'] = $this->v['tpl']['capture'] = []; - } - - /** - * Assigns a template variable. - * - * @param mixed $variable - * @param mixed $value - */ - public function assign($variable, $value = '') { - if (is_array($variable)) { - foreach ($variable as $key => $value) { - if (empty($key)) continue; - - $this->assign($key, $value); - } - } - else { - $this->v[$variable] = $value; - } - } - - /** - * Appends content to an existing template variable. - * - * @param mixed $variable - * @param mixed $value - */ - public function append($variable, $value = '') { - if (is_array($variable)) { - foreach ($variable as $key => $val) { - if ($key != '') { - $this->append($key, $val); - } - } - } - else { - if (!empty($variable)) { - if (isset($this->v[$variable])) { - if (is_array($this->v[$variable]) && is_array($value)) { - $keys = array_keys($value); - foreach ($keys as $key) { - if (isset($this->v[$variable][$key])) { - $this->v[$variable][$key] .= $value[$key]; - } - else { - $this->v[$variable][$key] = $value[$key]; - } - } - } - else { - $this->v[$variable] .= $value; - } - } - else { - $this->v[$variable] = $value; - } - } - } - } - - /** - * Prepends content to an existing template variable. - * - * @param mixed $variable - * @param mixed $value - */ - public function prepend($variable, $value = '') { - if (is_array($variable)) { - foreach ($variable as $key => $val) { - if ($key != '') { - $this->prepend($key, $val); - } - } - } - else { - if (!empty($variable)) { - if (isset($this->v[$variable])) { - if (is_array($this->v[$variable]) && is_array($value)) { - $keys = array_keys($value); - foreach ($keys as $key) { - if (isset($this->v[$variable][$key])) { - $this->v[$variable][$key] = $value[$key] . $this->v[$variable][$key]; - } - else { - $this->v[$variable][$key] = $value[$key]; - } - } - } - else { - $this->v[$variable] = $value . $this->v[$variable]; - } - } - else { - $this->v[$variable] = $value; - } - } - } - } - - /** - * Assigns a template variable by reference. - * - * @param string $variable - * @param mixed $value - */ - public function assignByRef($variable, &$value) { - if (!empty($variable)) { - $this->v[$variable] = &$value; - } - } - - /** - * Clears an assignment of template variables. - * - * @param mixed $variables - */ - public function clearAssign(array $variables) { - foreach ($variables as $key) { - unset($this->v[$key]); - } - } - - /** - * Clears assignment of all template variables. This should not be called - * during runtime as it could leed to an unexpected behaviour. - */ - public function clearAllAssign() { - $this->v = []; - } - - /** - * Outputs a template. - * - * @param string $templateName - * @param string $application - * @param bool $sendHeaders - */ - public function display($templateName, $application = 'wcf', $sendHeaders = true) { - if ($sendHeaders) { - HeaderUtil::sendHeaders(); - - // call beforeDisplay event - if (!defined('NO_IMPORTS')) EventHandler::getInstance()->fireAction($this, 'beforeDisplay'); - } - - $sourceFilename = $this->getSourceFilename($templateName, $application); - $compiledFilename = $this->getCompiledFilename($templateName, $application); - $metaDataFilename = $this->getMetaDataFilename($templateName); - $metaData = $this->getMetaData($templateName, $metaDataFilename); - - // check if compilation is necessary - if (($metaData === null) || !$this->isCompiled($templateName, $sourceFilename, $compiledFilename, $application, $metaData)) { - // compile - $this->compileTemplate($templateName, $sourceFilename, $compiledFilename, [ - 'application' => $application, - 'data' => $metaData, - 'filename' => $metaDataFilename - ]); - } - - // assign current package id - $this->assign('__APPLICATION', $application); - - include($compiledFilename); - - if ($sendHeaders) { - // call afterDisplay event - if (!defined('NO_IMPORTS')) EventHandler::getInstance()->fireAction($this, 'afterDisplay'); - } - } - - /** - * Returns the absolute filename of a template source. - * - * @param string $templateName - * @param string $application - * @return string $path - * @throws SystemException - */ - public function getSourceFilename($templateName, $application) { - $sourceFilename = $this->getPath($this->templatePaths[$application], $templateName); - if (!empty($sourceFilename)) { - return $sourceFilename; - } - - // try to find template within WCF if not already searching WCF - if ($application != 'wcf') { - $sourceFilename = $this->getSourceFilename($templateName, 'wcf'); - if (!empty($sourceFilename)) { - return $sourceFilename; - } - } - - throw new SystemException("Unable to find template '".$templateName."'"); - } - - /** - * Returns path if template was found. - * - * @param string $templatePath - * @param string $templateName - * @return string - */ - protected function getPath($templatePath, $templateName) { - if (!Template::isSystemCritical($templateName)) { - $templateGroupID = $this->getTemplateGroupID(); - - while ($templateGroupID != 0) { - $templateGroup = $this->templateGroupCache[$templateGroupID]; - - $path = $templatePath . $templateGroup->templateGroupFolderName . $templateName . '.tpl'; - if (file_exists($path)) { - return $path; - } - - $templateGroupID = $templateGroup->parentTemplateGroupID; - } - } - - // use default template - $path = $templatePath.$templateName.'.tpl'; - - if (file_exists($path)) { - return $path; - } - - return ''; - } - - /** - * Returns the absolute filename of a compiled template. - * - * @param string $templateName - * @param string $application - * @return string - */ - public function getCompiledFilename($templateName, $application) { - return $this->compileDir.$this->getTemplateGroupID().'_'.$application.'_'.$this->languageID.'_'.$templateName.'.php'; - } - - /** - * Returns the absolute filename for template's meta data. - * - * @param string $templateName - * @return string - */ - public function getMetaDataFilename($templateName) { - return $this->compileDir.$this->getTemplateGroupID().'_'.$templateName.'.meta.php'; - } - - /** - * Returns true if the template with the given data is already compiled. - * - * @param string $templateName - * @param string $sourceFilename - * @param string $compiledFilename - * @param string $application - * @param array $metaData - * @return bool - */ - protected function isCompiled($templateName, $sourceFilename, $compiledFilename, $application, array $metaData) { - if ($this->forceCompile || !file_exists($compiledFilename)) { - return false; - } - else { - $sourceMTime = @filemtime($sourceFilename); - $compileMTime = @filemtime($compiledFilename); - - if ($sourceMTime >= $compileMTime) { - return false; - } - else { - // check for meta data - if (!empty($metaData['include'])) { - foreach ($metaData['include'] as $application => $includedTemplates) { - foreach ($includedTemplates as $includedTemplate) { - $includedTemplateFilename = $this->getSourceFilename($includedTemplate, $application); - $includedMTime = @filemtime($includedTemplateFilename); - - if ($includedMTime >= $compileMTime) { - return false; - } - } - } - } - - return true; - } - } - } - - /** - * Compiles a template. - * - * @param string $templateName - * @param string $sourceFilename - * @param string $compiledFilename - * @param array $metaData - */ - protected function compileTemplate($templateName, $sourceFilename, $compiledFilename, array $metaData) { - // get source - $sourceContent = $this->getSourceContent($sourceFilename); - - // compile template - $this->getCompiler()->compile($templateName, $sourceContent, $compiledFilename, $metaData); - } - - /** - * Returns the template compiler. - * - * @return TemplateCompiler - */ - public function getCompiler() { - if ($this->compilerObj === null) { - $this->compilerObj = new TemplateCompiler($this); - } - - return $this->compilerObj; - } - - /** - * Reads the content of a template file. - * - * @param string $sourceFilename - * @return string - * @throws SystemException - */ - public function getSourceContent($sourceFilename) { - /** @noinspection PhpUnusedLocalVariableInspection */ - $sourceContent = ''; - if (!file_exists($sourceFilename) || (($sourceContent = @file_get_contents($sourceFilename)) === false)) { - throw new SystemException("Could not open template '$sourceFilename' for reading"); - } - else { - return $sourceContent; - } - } - - /** - * Returns the class name of a plugin. - * - * @param string $type - * @param string $tag - * @return string - */ - public function getPluginClassName($type, $tag) { - return $this->pluginNamespace.StringUtil::firstCharToUpperCase($tag).StringUtil::firstCharToUpperCase(mb_strtolower($type)).'TemplatePlugin'; - } - - /** - * Enables execution in sandbox. - */ - public function enableSandbox() { - $index = count($this->sandboxVars); - $this->sandboxVars[$index] = $this->v; - } - - /** - * Disables execution in sandbox. - */ - public function disableSandbox() { - if (empty($this->sandboxVars)) { - throw new SystemException('TemplateEngine is currently not running in a sandbox.'); - } - - $this->v = array_pop($this->sandboxVars); - } - - /** - * Returns the output of a template. - * - * @param string $templateName - * @param string $application - * @param array $variables - * @param bool $sandbox enables execution in sandbox - * @return string - */ - public function fetch($templateName, $application = 'wcf', array $variables = [], $sandbox = false) { - // enable sandbox - if ($sandbox) { - $this->enableSandbox(); - } - - // add new template variables - if (!empty($variables)) { - $this->v = array_merge($this->v, $variables); - } - - // get output - try { - ob_start(); - $this->display($templateName, $application, false); - $output = ob_get_contents(); - } - finally { - ob_end_clean(); - } - - // disable sandbox - if ($sandbox) { - $this->disableSandbox(); - } - - return $output; - } - - /** - * Executes a compiled template scripting source and returns the result. - * - * @param string $compiledSource - * @param array $variables - * @param bool $sandbox enables execution in sandbox - * @return string - */ - public function fetchString($compiledSource, array $variables = [], $sandbox = true) { - // enable sandbox - if ($sandbox) { - $this->enableSandbox(); - } - - // add new template variables - if (!empty($variables)) { - $this->v = array_merge($this->v, $variables); - } - - // get output - ob_start(); - eval('?>'.$compiledSource); - $output = ob_get_contents(); - ob_end_clean(); - - // disable sandbox - if ($sandbox) { - $this->disableSandbox(); - } - - return $output; - } - - /** - * Deletes all compiled templates. - * - * @param string $compileDir - */ - public static function deleteCompiledTemplates($compileDir = '') { - if (empty($compileDir)) $compileDir = WCF_DIR.'templates/compiled/'; - - // delete compiled templates - DirectoryUtil::getInstance($compileDir)->removePattern(new Regex('.*_.*\.php$')); - } - - /** - * Returns an array with all prefilters. - * - * @return string[] - */ - public function getPrefilters() { - return $this->prefilters; - } - - /** - * Returns the active template group id. - * - * @return int - */ - public function getTemplateGroupID() { - return $this->templateGroupID; - } - - /** - * Sets the active template group id. - * - * @param int $templateGroupID - */ - public function setTemplateGroupID($templateGroupID) { - if ($templateGroupID && !isset($this->templateGroupCache[$templateGroupID])) { - $templateGroupID = 0; - } - - $this->templateGroupID = $templateGroupID; - } - - /** - * Loads cached template group information. - */ - protected function loadTemplateGroupCache() { - $this->templateGroupCache = TemplateGroupCacheBuilder::getInstance()->getData(); - } - - /** - * Registers prefilters. - * - * @param string[] $prefilters - */ - public function registerPrefilter(array $prefilters) { - foreach ($prefilters as $name) { - $this->prefilters[$name] = $name; - } - } - - /** - * Removes a prefilter by its internal name. - * - * @param string $name internal prefilter identifier - */ - public function removePrefilter($name) { - unset($this->prefilters[$name]); - } - - /** - * Sets the dir for the compiled templates. - * - * @param string $compileDir - * @throws SystemException - */ - public function setCompileDir($compileDir) { - if (!is_dir($compileDir)) { - throw new SystemException("'".$compileDir."' is not a valid dir"); - } - - $this->compileDir = $compileDir; - } - - /** - * Includes a template. - * - * @param string $templateName - * @param string $application - * @param array $variables - * @param bool $sandbox enables execution in sandbox - */ - protected function includeTemplate($templateName, $application, array $variables = [], $sandbox = true) { - // enable sandbox - if ($sandbox) { - $this->enableSandbox(); - } - - // add new template variables - if (!empty($variables)) { - $this->v = array_merge($this->v, $variables); - } - - // display template - $this->display($templateName, $application, false); - - // disable sandbox - if ($sandbox) { - $this->disableSandbox(); - } - } - - /** - * Returns the value of a template variable. - * - * @param string $varname - * @return mixed - */ - public function get($varname) { - if (isset($this->v[$varname])) { - return $this->v[$varname]; - } - - return null; - } - - /** - * Loads template listener code. - */ - protected function loadTemplateListenerCode() { - if (!$this->templateListenersLoaded) { - $this->templateListeners = TemplateListenerCodeCacheBuilder::getInstance()->getData(['environment' => $this->environment]); - $this->templateListenersLoaded = true; - } - } - - /** - * Returns template listener's code. - * - * @param string $templateName - * @param string $eventName - * @return string - */ - public function getTemplateListenerCode($templateName, $eventName) { - $this->loadTemplateListenerCode(); - - if (isset($this->templateListeners[$templateName][$eventName])) { - return implode("\n", $this->templateListeners[$templateName][$eventName]); - } - - return ''; - } - - /** - * Reads meta data from file. - * - * @param string $templateName - * @param string $filename - * @return array - */ - protected function getMetaData($templateName, $filename) { - if (!file_exists($filename) || !is_readable($filename)) { - return null; - } - - // get file contents - $contents = file_get_contents($filename); - - // find first newline - $position = strpos($contents, "\n"); - if ($position === false) { - return null; - } - - // cut contents - $contents = substr($contents, $position + 1); - - // read serializes data - $data = @unserialize($contents); - if ($data === false || !is_array($data)) { - return null; - } - - return $data; - } +class TemplateEngine extends SingletonFactory +{ + /** + * directory used to cache previously compiled templates + * @var string + */ + public $compileDir = ''; + + /** + * active language id used to identify specific language versions of compiled templates + * @var int + */ + public $languageID = 0; + + /** + * directories used as template source + * @var string[] + */ + public $templatePaths = []; + + /** + * namespace containing template modifiers and plugins + * @var string + */ + public $pluginNamespace = ''; + + /** + * active template compiler + * @var TemplateCompiler + */ + protected $compilerObj; + + /** + * forces the template engine to recompile all included templates + * @var bool + */ + protected $forceCompile = false; + + /** + * list of registered prefilters + * @var string[] + */ + protected $prefilters = []; + + /** + * cached list of known template groups + * @var array + */ + protected $templateGroupCache = []; + + /** + * active template group id + * @var int + */ + protected $templateGroupID = 0; + + /** + * all available template variables and those assigned during runtime + * @var mixed[][] + */ + protected $v = []; + + /** + * sandboxed values of currently active foreach loops' `item` and `key` variables + * + * for each currently active `foreach` loop, an array is added: + * $foreachHash => [ + * (optional) 'item' => sandboxed value of an existing variable with the same name, + * (optional) 'key' => (optional) sandboxed value of an existing variable with the same name + * ] + * + * @var mixed[][][] + */ + protected $foreachVars = []; + + /** + * all cached variables for usage after execution in sandbox + * @var mixed[][] + */ + protected $sandboxVars = []; + + /** + * contains all templates with assigned template listeners. + * @var string[][][] + */ + protected $templateListeners = []; + + /** + * true, if template listener code was already loaded + * @var bool + */ + protected $templateListenersLoaded = false; + + /** + * current environment + * @var string + */ + protected $environment = 'user'; + + /** + * @inheritDoc + */ + protected function init() + { + $this->templatePaths = ['wcf' => WCF_DIR . 'templates/']; + $this->pluginNamespace = 'wcf\system\template\plugin\\'; + $this->compileDir = WCF_DIR . 'templates/compiled/'; + + $this->loadTemplateGroupCache(); + $this->assignSystemVariables(); + } + + /** + * Adds a new application. + * + * @param string $abbreviation + * @param string $templatePath + */ + public function addApplication($abbreviation, $templatePath) + { + $this->templatePaths[$abbreviation] = $templatePath; + } + + /** + * Sets active language id. + * + * @param int $languageID + */ + public function setLanguageID($languageID) + { + $this->languageID = $languageID; + } + + /** + * Assigns some system variables. + */ + protected function assignSystemVariables() + { + $this->v['tpl'] = []; + + // assign super globals + $this->v['tpl']['get'] = &$_GET; + $this->v['tpl']['post'] = &$_POST; + $this->v['tpl']['cookie'] = &$_COOKIE; + $this->v['tpl']['server'] = &$_SERVER; + $this->v['tpl']['env'] = &$_ENV; + + // system info + $this->v['tpl']['now'] = TIME_NOW; + $this->v['tpl']['template'] = ''; + $this->v['tpl']['includedTemplates'] = []; + + // section / foreach / capture arrays + $this->v['tpl']['section'] = $this->v['tpl']['foreach'] = $this->v['tpl']['capture'] = []; + } + + /** + * Assigns a template variable. + * + * @param mixed $variable + * @param mixed $value + */ + public function assign($variable, $value = '') + { + if (\is_array($variable)) { + foreach ($variable as $key => $value) { + if (empty($key)) { + continue; + } + + $this->assign($key, $value); + } + } else { + $this->v[$variable] = $value; + } + } + + /** + * Appends content to an existing template variable. + * + * @param mixed $variable + * @param mixed $value + */ + public function append($variable, $value = '') + { + if (\is_array($variable)) { + foreach ($variable as $key => $val) { + if ($key != '') { + $this->append($key, $val); + } + } + } else { + if (!empty($variable)) { + if (isset($this->v[$variable])) { + if (\is_array($this->v[$variable]) && \is_array($value)) { + $keys = \array_keys($value); + foreach ($keys as $key) { + if (isset($this->v[$variable][$key])) { + $this->v[$variable][$key] .= $value[$key]; + } else { + $this->v[$variable][$key] = $value[$key]; + } + } + } else { + $this->v[$variable] .= $value; + } + } else { + $this->v[$variable] = $value; + } + } + } + } + + /** + * Prepends content to an existing template variable. + * + * @param mixed $variable + * @param mixed $value + */ + public function prepend($variable, $value = '') + { + if (\is_array($variable)) { + foreach ($variable as $key => $val) { + if ($key != '') { + $this->prepend($key, $val); + } + } + } else { + if (!empty($variable)) { + if (isset($this->v[$variable])) { + if (\is_array($this->v[$variable]) && \is_array($value)) { + $keys = \array_keys($value); + foreach ($keys as $key) { + if (isset($this->v[$variable][$key])) { + $this->v[$variable][$key] = $value[$key] . $this->v[$variable][$key]; + } else { + $this->v[$variable][$key] = $value[$key]; + } + } + } else { + $this->v[$variable] = $value . $this->v[$variable]; + } + } else { + $this->v[$variable] = $value; + } + } + } + } + + /** + * Assigns a template variable by reference. + * + * @param string $variable + * @param mixed $value + */ + public function assignByRef($variable, &$value) + { + if (!empty($variable)) { + $this->v[$variable] = &$value; + } + } + + /** + * Clears an assignment of template variables. + * + * @param mixed $variables + */ + public function clearAssign(array $variables) + { + foreach ($variables as $key) { + unset($this->v[$key]); + } + } + + /** + * Clears assignment of all template variables. This should not be called + * during runtime as it could leed to an unexpected behaviour. + */ + public function clearAllAssign() + { + $this->v = []; + } + + /** + * Outputs a template. + * + * @param string $templateName + * @param string $application + * @param bool $sendHeaders + */ + public function display($templateName, $application = 'wcf', $sendHeaders = true) + { + if ($sendHeaders) { + HeaderUtil::sendHeaders(); + + // call beforeDisplay event + if (!\defined('NO_IMPORTS')) { + EventHandler::getInstance()->fireAction($this, 'beforeDisplay'); + } + } + + $sourceFilename = $this->getSourceFilename($templateName, $application); + $compiledFilename = $this->getCompiledFilename($templateName, $application); + $metaDataFilename = $this->getMetaDataFilename($templateName); + $metaData = $this->getMetaData($templateName, $metaDataFilename); + + // check if compilation is necessary + if ( + $metaData === null + || !$this->isCompiled($templateName, $sourceFilename, $compiledFilename, $application, $metaData) + ) { + // compile + $this->compileTemplate($templateName, $sourceFilename, $compiledFilename, [ + 'application' => $application, + 'data' => $metaData, + 'filename' => $metaDataFilename, + ]); + } + + // assign current package id + $this->assign('__APPLICATION', $application); + + include($compiledFilename); + + if ($sendHeaders) { + // call afterDisplay event + if (!\defined('NO_IMPORTS')) { + EventHandler::getInstance()->fireAction($this, 'afterDisplay'); + } + } + } + + /** + * Returns the absolute filename of a template source. + * + * @param string $templateName + * @param string $application + * @return string $path + * @throws SystemException + */ + public function getSourceFilename($templateName, $application) + { + $sourceFilename = $this->getPath($this->templatePaths[$application], $templateName); + if (!empty($sourceFilename)) { + return $sourceFilename; + } + + // try to find template within WCF if not already searching WCF + if ($application != 'wcf') { + $sourceFilename = $this->getSourceFilename($templateName, 'wcf'); + if (!empty($sourceFilename)) { + return $sourceFilename; + } + } + + throw new SystemException("Unable to find template '" . $templateName . "'"); + } + + /** + * Returns path if template was found. + * + * @param string $templatePath + * @param string $templateName + * @return string + */ + protected function getPath($templatePath, $templateName) + { + if (!Template::isSystemCritical($templateName)) { + $templateGroupID = $this->getTemplateGroupID(); + + while ($templateGroupID != 0) { + $templateGroup = $this->templateGroupCache[$templateGroupID]; + + $path = $templatePath . $templateGroup->templateGroupFolderName . $templateName . '.tpl'; + if (\file_exists($path)) { + return $path; + } + + $templateGroupID = $templateGroup->parentTemplateGroupID; + } + } + + // use default template + $path = $templatePath . $templateName . '.tpl'; + + if (\file_exists($path)) { + return $path; + } + + return ''; + } + + /** + * Returns the absolute filename of a compiled template. + * + * @param string $templateName + * @param string $application + * @return string + */ + public function getCompiledFilename($templateName, $application) + { + return $this->compileDir . $this->getTemplateGroupID() . '_' . $application . '_' . $this->languageID . '_' . $templateName . '.php'; + } + + /** + * Returns the absolute filename for template's meta data. + * + * @param string $templateName + * @return string + */ + public function getMetaDataFilename($templateName) + { + return $this->compileDir . $this->getTemplateGroupID() . '_' . $templateName . '.meta.php'; + } + + /** + * Returns true if the template with the given data is already compiled. + * + * @param string $templateName + * @param string $sourceFilename + * @param string $compiledFilename + * @param string $application + * @param array $metaData + * @return bool + */ + protected function isCompiled($templateName, $sourceFilename, $compiledFilename, $application, array $metaData) + { + if ($this->forceCompile || !\file_exists($compiledFilename)) { + return false; + } else { + $sourceMTime = @\filemtime($sourceFilename); + $compileMTime = @\filemtime($compiledFilename); + + if ($sourceMTime >= $compileMTime) { + return false; + } else { + // check for meta data + if (!empty($metaData['include'])) { + foreach ($metaData['include'] as $application => $includedTemplates) { + foreach ($includedTemplates as $includedTemplate) { + $includedTemplateFilename = $this->getSourceFilename($includedTemplate, $application); + $includedMTime = @\filemtime($includedTemplateFilename); + + if ($includedMTime >= $compileMTime) { + return false; + } + } + } + } + + return true; + } + } + } + + /** + * Compiles a template. + * + * @param string $templateName + * @param string $sourceFilename + * @param string $compiledFilename + * @param array $metaData + */ + protected function compileTemplate($templateName, $sourceFilename, $compiledFilename, array $metaData) + { + // get source + $sourceContent = $this->getSourceContent($sourceFilename); + + // compile template + $this->getCompiler()->compile($templateName, $sourceContent, $compiledFilename, $metaData); + } + + /** + * Returns the template compiler. + * + * @return TemplateCompiler + */ + public function getCompiler() + { + if ($this->compilerObj === null) { + $this->compilerObj = new TemplateCompiler($this); + } + + return $this->compilerObj; + } + + /** + * Reads the content of a template file. + * + * @param string $sourceFilename + * @return string + * @throws SystemException + */ + public function getSourceContent($sourceFilename) + { + /** @noinspection PhpUnusedLocalVariableInspection */ + $sourceContent = ''; + if (!\file_exists($sourceFilename) || (($sourceContent = @\file_get_contents($sourceFilename)) === false)) { + throw new SystemException("Could not open template '{$sourceFilename}' for reading"); + } else { + return $sourceContent; + } + } + + /** + * Returns the class name of a plugin. + * + * @param string $type + * @param string $tag + * @return string + */ + public function getPluginClassName($type, $tag) + { + return $this->pluginNamespace . StringUtil::firstCharToUpperCase($tag) . StringUtil::firstCharToUpperCase(\mb_strtolower($type)) . 'TemplatePlugin'; + } + + /** + * Enables execution in sandbox. + */ + public function enableSandbox() + { + $index = \count($this->sandboxVars); + $this->sandboxVars[$index] = $this->v; + } + + /** + * Disables execution in sandbox. + */ + public function disableSandbox() + { + if (empty($this->sandboxVars)) { + throw new SystemException('TemplateEngine is currently not running in a sandbox.'); + } + + $this->v = \array_pop($this->sandboxVars); + } + + /** + * Returns the output of a template. + * + * @param string $templateName + * @param string $application + * @param array $variables + * @param bool $sandbox enables execution in sandbox + * @return string + */ + public function fetch($templateName, $application = 'wcf', array $variables = [], $sandbox = false) + { + // enable sandbox + if ($sandbox) { + $this->enableSandbox(); + } + + // add new template variables + if (!empty($variables)) { + $this->v = \array_merge($this->v, $variables); + } + + // get output + try { + \ob_start(); + $this->display($templateName, $application, false); + $output = \ob_get_contents(); + } finally { + \ob_end_clean(); + } + + // disable sandbox + if ($sandbox) { + $this->disableSandbox(); + } + + return $output; + } + + /** + * Executes a compiled template scripting source and returns the result. + * + * @param string $compiledSource + * @param array $variables + * @param bool $sandbox enables execution in sandbox + * @return string + */ + public function fetchString($compiledSource, array $variables = [], $sandbox = true) + { + // enable sandbox + if ($sandbox) { + $this->enableSandbox(); + } + + // add new template variables + if (!empty($variables)) { + $this->v = \array_merge($this->v, $variables); + } + + // get output + \ob_start(); + eval('?>' . $compiledSource); + $output = \ob_get_contents(); + \ob_end_clean(); + + // disable sandbox + if ($sandbox) { + $this->disableSandbox(); + } + + return $output; + } + + /** + * Deletes all compiled templates. + * + * @param string $compileDir + */ + public static function deleteCompiledTemplates($compileDir = '') + { + if (empty($compileDir)) { + $compileDir = WCF_DIR . 'templates/compiled/'; + } + + // delete compiled templates + DirectoryUtil::getInstance($compileDir)->removePattern(new Regex('.*_.*\.php$')); + } + + /** + * Returns an array with all prefilters. + * + * @return string[] + */ + public function getPrefilters() + { + return $this->prefilters; + } + + /** + * Returns the active template group id. + * + * @return int + */ + public function getTemplateGroupID() + { + return $this->templateGroupID; + } + + /** + * Sets the active template group id. + * + * @param int $templateGroupID + */ + public function setTemplateGroupID($templateGroupID) + { + if ($templateGroupID && !isset($this->templateGroupCache[$templateGroupID])) { + $templateGroupID = 0; + } + + $this->templateGroupID = $templateGroupID; + } + + /** + * Loads cached template group information. + */ + protected function loadTemplateGroupCache() + { + $this->templateGroupCache = TemplateGroupCacheBuilder::getInstance()->getData(); + } + + /** + * Registers prefilters. + * + * @param string[] $prefilters + */ + public function registerPrefilter(array $prefilters) + { + foreach ($prefilters as $name) { + $this->prefilters[$name] = $name; + } + } + + /** + * Removes a prefilter by its internal name. + * + * @param string $name internal prefilter identifier + */ + public function removePrefilter($name) + { + unset($this->prefilters[$name]); + } + + /** + * Sets the dir for the compiled templates. + * + * @param string $compileDir + * @throws SystemException + */ + public function setCompileDir($compileDir) + { + if (!\is_dir($compileDir)) { + throw new SystemException("'" . $compileDir . "' is not a valid dir"); + } + + $this->compileDir = $compileDir; + } + + /** + * Includes a template. + * + * @param string $templateName + * @param string $application + * @param array $variables + * @param bool $sandbox enables execution in sandbox + */ + protected function includeTemplate($templateName, $application, array $variables = [], $sandbox = true) + { + // enable sandbox + if ($sandbox) { + $this->enableSandbox(); + } + + // add new template variables + if (!empty($variables)) { + $this->v = \array_merge($this->v, $variables); + } + + // display template + $this->display($templateName, $application, false); + + // disable sandbox + if ($sandbox) { + $this->disableSandbox(); + } + } + + /** + * Returns the value of a template variable. + * + * @param string $varname + * @return mixed + */ + public function get($varname) + { + if (isset($this->v[$varname])) { + return $this->v[$varname]; + } + } + + /** + * Loads template listener code. + */ + protected function loadTemplateListenerCode() + { + if (!$this->templateListenersLoaded) { + $this->templateListeners = TemplateListenerCodeCacheBuilder::getInstance() + ->getData(['environment' => $this->environment]); + $this->templateListenersLoaded = true; + } + } + + /** + * Returns template listener's code. + * + * @param string $templateName + * @param string $eventName + * @return string + */ + public function getTemplateListenerCode($templateName, $eventName) + { + $this->loadTemplateListenerCode(); + + if (isset($this->templateListeners[$templateName][$eventName])) { + return \implode("\n", $this->templateListeners[$templateName][$eventName]); + } + + return ''; + } + + /** + * Reads meta data from file. + * + * @param string $templateName + * @param string $filename + * @return array|null + */ + protected function getMetaData($templateName, $filename) + { + if (!\file_exists($filename) || !\is_readable($filename)) { + return null; + } + + // get file contents + $contents = \file_get_contents($filename); + + // find first newline + $position = \strpos($contents, "\n"); + if ($position === false) { + return null; + } + + // cut contents + $contents = \substr($contents, $position + 1); + + // read serializes data + $data = @\unserialize($contents); + if ($data === false || !\is_array($data)) { + return null; + } + + return $data; + } }