*/ /** * @class TemplateHandler * @author NAVER (developers@xpressengine.com) * template compiler * @version 0.1 * @remarks It compiles template file by using regular expression into php * code, and XE caches compiled code for further uses */ class TemplateHandler { private $path = NULL; ///< target directory private $filename = NULL; ///< target filename private $file = NULL; ///< target file (fullpath) private $web_path = NULL; ///< tpl file web path private $compiled_file = NULL; ///< tpl file web path private $config = NULL; private $skipTags = NULL; private $handler_mtime = 0; private static $rootTpl = NULL; /** * Context variables accessible as $this in template files */ public $user = FALSE; /** * constructor * @return void */ public function __construct() { ini_set('pcre.jit', false); $this->config = new stdClass; $this->handler_mtime = filemtime(__FILE__); $this->user = Rhymix\Framework\Session::getMemberInfo() ?: new Rhymix\Framework\Helpers\SessionHelper; } /** * returns TemplateHandler's singleton object * @return TemplateHandler instance */ public static function getInstance() { static $oTemplate = NULL; if(!isset($GLOBALS['__TemplateHandlerCalled__'])) { $GLOBALS['__TemplateHandlerCalled__'] = 1; } else { $GLOBALS['__TemplateHandlerCalled__']++; } if(!$oTemplate) { $oTemplate = new TemplateHandler(); } return $oTemplate; } /** * set variables for template compile * @param string $tpl_path * @param string $tpl_filename * @param string $tpl_file * @return void */ protected function init($tpl_path, $tpl_filename, $tpl_file = '') { // verify arguments $tpl_path = trim(preg_replace('@^' . preg_quote(\RX_BASEDIR, '@') . '|\./@', '', str_replace('\\', '/', $tpl_path)), '/') . '/'; if($tpl_path === '/' || !is_dir($tpl_path)) { return; } if(!file_exists(\RX_BASEDIR . $tpl_path . $tpl_filename) && file_exists(\RX_BASEDIR . $tpl_path . $tpl_filename . '.html')) { $tpl_filename .= '.html'; } // create tpl_file variable if($tpl_file) { $tpl_file = trim(preg_replace('@^' . preg_quote(\RX_BASEDIR, '@') . '|\./@', '', str_replace('\\', '/', $tpl_file)), '/'); } else { $tpl_file = $tpl_path . $tpl_filename; } // set template file infos. $this->path = \RX_BASEDIR . $tpl_path; $this->web_path = \RX_BASEURL . $tpl_path; $this->filename = $tpl_filename; $this->file = \RX_BASEDIR . $tpl_file; // set compiled file name $converted_path = ltrim(str_replace(array('\\', '..'), array('/', 'dotdot'), $tpl_file), '/'); $this->compiled_file = \RX_BASEDIR . 'files/cache/template/' . $converted_path . '.php'; } /** * compiles specified tpl file and execution result in Context into resultant content * @param string $tpl_path path of the directory containing target template file * @param string $tpl_filename target template file's name * @param string $tpl_file if specified use it as template file's full path * @return string Returns compiled result in case of success, NULL otherwise */ public function compile($tpl_path, $tpl_filename, $tpl_file = '') { // store the starting time for debug information $start = microtime(true); // initiation $this->init($tpl_path, $tpl_filename, $tpl_file); // if target file does not exist exit if(!$this->file || !file_exists($this->file)) { $tpl_path = str_replace('\\', '/', $tpl_path); $error_message = "Template not found: ${tpl_path}${tpl_filename}" . ($tpl_file ? " (${tpl_file})" : ''); trigger_error($error_message, \E_USER_WARNING); return escape($error_message); } // for backward compatibility if(is_null(self::$rootTpl)) { self::$rootTpl = $this->file; } $latest_mtime = max(filemtime($this->file), $this->handler_mtime); // make compiled file if(!file_exists($this->compiled_file) || filemtime($this->compiled_file) < $latest_mtime) { $buff = $this->parse(); if(Rhymix\Framework\Storage::write($this->compiled_file, $buff) === false) { $tmpfilename = tempnam(sys_get_temp_dir(), 'rx-compiled'); if($tmpfilename === false || Rhymix\Framework\Storage::write($tmpfilename, $buff) === false) { return 'Fatal Error : Cannot create temporary file. Please check permissions.'; } $this->compiled_file = $tmpfilename; } } Rhymix\Framework\Debug::addFilenameAlias($this->file, $this->compiled_file); $output = $this->_fetch($this->compiled_file); // delete tmpfile if(isset($tmpfilename)) { Rhymix\Framework\Storage::delete($tmpfilename); } if($__templatehandler_root_tpl == $this->file) { $__templatehandler_root_tpl = null; } // store the ending time for debug information $GLOBALS['__template_elapsed__'] += microtime(true) - $start; return $output; } /** * compile specified file and immediately return * @param string $tpl_path path of the directory containing target template file * @param string $tpl_filename target template file's name * @return string Returns compiled content in case of success or NULL in case of failure */ public function compileDirect($tpl_path, $tpl_filename) { $this->init($tpl_path, $tpl_filename, null); // if target file does not exist exit if(!$this->file || !file_exists($this->file)) { $tpl_path = str_replace('\\', '/', $tpl_path); $error_message = "Template not found: ${tpl_path}${tpl_filename}"; trigger_error($error_message, \E_USER_WARNING); return escape($error_message); } return $this->parse(); } /** * parse syntax. * @param string $buff template file * @return string compiled result in case of success or NULL in case of error */ protected function parse($buff = null) { if(is_null($buff)) { if(!is_readable($this->file)) { return; } // read tpl file $buff = FileHandler::readFile($this->file); } // HTML tags to skip if(is_null($this->skipTags)) { $this->skipTags = array('marquee'); } // reset config for this buffer (this step is necessary because we use a singleton for every template) $previous_config = clone $this->config; $this->config = new stdClass(); // detect existence of autoescape config $this->config->autoescape = (strpos($buff, ' autoescape="') === FALSE) ? NULL : 'off'; // replace comments $buff = preg_replace('@@s', '', $buff); // replace value of src in img/input/script tag $buff = preg_replace_callback('/<(?:img|input|script)(?:[^<>]*?)(?(?=cond=")(?:cond="[^"]+"[^<>]*)+|)[^<>]* src="(?!(?:https?|file):\/\/|[\/\{])([^"]+)"/is', array($this, '_replacePath'), $buff); // replace loop and cond template syntax $buff = $this->_parseInline($buff); // include, unload/load, import $buff = preg_replace_callback('/{(@[\s\S]+?|(?=[\$\\\\]\w+|_{1,2}[A-Z]+|[!\(+-]|\w+(?:\(|::)|\d+|[\'"].*?[\'"]).+?)}|<(!--[#%])?(include|import|(un)?load(?(4)|(?:_js_plugin)?)|config)(?(2)\(["\']([^"\']+)["\'])(.*?)(?(2)\)--|\/)>|(\s*)/', array($this, '_parseResource'), $buff); // remove block which is a virtual tag $buff = preg_replace('@?block\s*>@is', '', $buff); // form auto generation $temp = preg_replace_callback('/(