mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-04-28 06:42:14 +09:00
Separate common Template class from Template v1 parser
This commit is contained in:
parent
f15cdecbe1
commit
3505d49de6
5 changed files with 1239 additions and 5 deletions
297
common/framework/Template.php
Normal file
297
common/framework/Template.php
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework;
|
||||
|
||||
/**
|
||||
* The template class.
|
||||
*/
|
||||
class Template
|
||||
{
|
||||
/**
|
||||
* Properties for user
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Properties for internal use
|
||||
*/
|
||||
public $config;
|
||||
public $absolute_dirname;
|
||||
public $relative_dirname;
|
||||
public $filename;
|
||||
public $exists;
|
||||
public $absolute_path;
|
||||
public $relative_path;
|
||||
public $cache_path;
|
||||
public $ob_level;
|
||||
|
||||
/**
|
||||
* Static properties
|
||||
*/
|
||||
protected static $_mtime;
|
||||
protected static $_delay_compile;
|
||||
|
||||
/**
|
||||
* Provided for compatibility with old TemplateHandler.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getInstance(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* You can also call the constructor directly.
|
||||
*
|
||||
* @param ?string $dirname
|
||||
* @param ?string $filename
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(?string $dirname = null, ?string $filename = null)
|
||||
{
|
||||
// Set instance configuration to default values.
|
||||
$this->config = new \stdClass;
|
||||
$this->config->version = 1;
|
||||
$this->config->autoescape = false;
|
||||
|
||||
// Set user information.
|
||||
$this->user = Session::getMemberInfo() ?: new Helpers\SessionHelper();
|
||||
|
||||
// Cache commonly used configurations as static properties.
|
||||
if (self::$_mtime === null)
|
||||
{
|
||||
self::$_mtime = filemtime(__FILE__);
|
||||
}
|
||||
if (self::$_delay_compile === null)
|
||||
{
|
||||
self::$_delay_compile = config('view.delay_compile') ?? 0;
|
||||
}
|
||||
|
||||
// If paths were provided, initialize immediately.
|
||||
if ($dirname && $filename)
|
||||
{
|
||||
$this->_setSourcePath($dirname, $filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and normalize paths.
|
||||
*
|
||||
* @param string $dirname
|
||||
* @param string $filename
|
||||
* @return void
|
||||
*/
|
||||
protected function _setSourcePath(string $dirname, string $filename): void
|
||||
{
|
||||
// Normalize the template path. Result will look like 'modules/foo/views/'
|
||||
$dirname = trim(preg_replace('@^' . preg_quote(\RX_BASEDIR, '@') . '|\./@', '', strtr($dirname, ['\\' => '/', '//' => '/'])), '/') . '/';
|
||||
$dirname = preg_replace('/[\{\}\(\)\[\]<>\$\'"]/', '', $dirname);
|
||||
$this->absolute_dirname = \RX_BASEDIR . $dirname;
|
||||
$this->relative_dirname = $dirname;
|
||||
|
||||
// Normalize the filename. Result will look like 'bar/example.html'
|
||||
$filename = trim(strtr($filename, ['\\' => '/', '//' => '/']), '/');
|
||||
$filename = preg_replace('/[\{\}\(\)\[\]<>\$\'"]/', '', $filename);
|
||||
if (!preg_match('/\.(html?|php)$/', $filename))
|
||||
{
|
||||
$filename .= '.html';
|
||||
}
|
||||
$this->filename = $filename;
|
||||
$this->absolute_path = $this->absolute_dirname . $filename;
|
||||
$this->relative_path = $this->relative_dirname . $filename;
|
||||
$this->exists = Storage::exists($this->absolute_path);
|
||||
$this->_setCachePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path for the cache file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _setCachePath()
|
||||
{
|
||||
$this->cache_path = \RX_BASEDIR . 'files/cache/template/' . $this->relative_path . '.php';
|
||||
if ($this->exists)
|
||||
{
|
||||
Debug::addFilenameAlias($this->absolute_path, $this->cache_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and execute a template file.
|
||||
*
|
||||
* You don't need to pass any paths if you have already supplied them
|
||||
* through the constructor. They exist for backward compatibility.
|
||||
*
|
||||
* $override_filename should be considered deprecated, as it is only
|
||||
* used in faceOff (layout source editor).
|
||||
*
|
||||
* @param ?string $dirname
|
||||
* @param ?string $filename
|
||||
* @param ?string $override_filename
|
||||
* @return string
|
||||
*/
|
||||
public function compile(?string $dirname = null, ?string $filename = null, ?string $override_filename = null)
|
||||
{
|
||||
// If paths are given, initialize now.
|
||||
if ($dirname && $filename)
|
||||
{
|
||||
$this->_setSourcePath($dirname, $filename);
|
||||
}
|
||||
if ($override_filename)
|
||||
{
|
||||
$override_filename = trim(preg_replace('@^' . preg_quote(\RX_BASEDIR, '@') . '|\./@', '', strtr($override_filename, ['\\' => '/', '//' => '/'])), '/') . '/';
|
||||
$override_filename = preg_replace('/[\{\}\(\)\[\]<>\$\'"]/', '', $override_filename);
|
||||
$this->absolute_path = \RX_BASEDIR . $override_filename;
|
||||
$this->relative_path = $override_filename;
|
||||
$this->exists = Storage::exists($this->absolute_path);
|
||||
$this->_setCachePath();
|
||||
}
|
||||
|
||||
// Return error if the source file does not exist.
|
||||
if (!$this->exists)
|
||||
{
|
||||
$error_message = sprintf('Template not found: %s', $this->relative_path);
|
||||
trigger_error($error_message, \E_USER_WARNING);
|
||||
return escape($error_message);
|
||||
}
|
||||
|
||||
// Record the starting time.
|
||||
$start = microtime(true);
|
||||
|
||||
// Find the latest mtime of the source template and the template parser.
|
||||
$filemtime = filemtime($this->absolute_path);
|
||||
if ($filemtime > time() - self::$_delay_compile)
|
||||
{
|
||||
$latest_mtime = self::$_mtime;
|
||||
}
|
||||
else
|
||||
{
|
||||
$latest_mtime = max($filemtime, self::$_mtime);
|
||||
}
|
||||
|
||||
// If a cached result does not exist, or if it is stale, compile again.
|
||||
if (!Storage::exists($this->cache_path) || filemtime($this->cache_path) < $latest_mtime)
|
||||
{
|
||||
$content = $this->_convert();
|
||||
if (!Storage::write($this->cache_path, $content))
|
||||
{
|
||||
throw new Exception('Cannot write template cache file: ' . $this->cache_path);
|
||||
}
|
||||
}
|
||||
|
||||
$output = $this->_execute();
|
||||
|
||||
// Record the time elapsed.
|
||||
$elapsed_time = microtime(true) - $start;
|
||||
if (!isset($GLOBALS['__template_elapsed__']))
|
||||
{
|
||||
$GLOBALS['__template_elapsed__'] = 0;
|
||||
}
|
||||
$GLOBALS['__template_elapsed__'] += $elapsed_time;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a template and return the PHP code.
|
||||
*
|
||||
* @param string $dirname
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public function compileDirect(string $dirname, string $filename): string
|
||||
{
|
||||
// Initialize paths. Return error if file does not exist.
|
||||
$this->_setSourcePath($dirname, $filename);
|
||||
if (!$this->exists)
|
||||
{
|
||||
$error_message = sprintf('Template not found: %s', $this->relative_path);
|
||||
trigger_error($error_message, \E_USER_WARNING);
|
||||
return escape($error_message);
|
||||
}
|
||||
|
||||
// Convert, but don't actually execute it.
|
||||
return $this->_convert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert template code to PHP using a version-specific parser.
|
||||
*
|
||||
* Directly passing $content as a string is not available as an
|
||||
* official API. It only exists for unit testing.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _convert(?string $content = null): string
|
||||
{
|
||||
// Read the source, or use the provided content.
|
||||
if ($content === null && $this->exists)
|
||||
{
|
||||
$content = Storage::read($this->absolute_path);
|
||||
$content = trim($content) . PHP_EOL;
|
||||
}
|
||||
if ($content === null || $content === '')
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Remove UTF-8 BOM and convert CRLF to LF.
|
||||
$content = preg_replace(['/^\xEF\xBB\xBF/', '/\r\n/'], ['', "\n"], $content);
|
||||
|
||||
// Check the config tag: <config version="2" /> or <config autoescape="on" />
|
||||
$content = preg_replace_callback('!^<config\s+(\w+)="([^"]+)"\s*/?>!', function($match) {
|
||||
$this->config->{$match[1]} = ($match[1] === 'version' ? intval($match[2]) : toBool($match[2]));
|
||||
return sprintf('<?php $this->config->%s = %s; ?>', $match[1], var_export($this->config->{$match[1]}, true));
|
||||
}, $content);
|
||||
|
||||
// Turn autoescape on if the version is 2 or greater.
|
||||
if ($this->config->version >= 2)
|
||||
{
|
||||
$this->config->autoescape = true;
|
||||
}
|
||||
|
||||
// Call a version-specific parser to convert template code into PHP.
|
||||
$class_name = '\Rhymix\Framework\Parsers\Template\TemplateParser_v' . $this->config->version;
|
||||
$parser = new $class_name;
|
||||
$content = $parser->convert($content, $this);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the converted template and return the output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _execute(): string
|
||||
{
|
||||
// Import Context and lang as local variables.
|
||||
$__Context = \Context::getAll();
|
||||
global $lang;
|
||||
|
||||
// Start the output buffer.
|
||||
$this->ob_level = ob_get_level();
|
||||
ob_start();
|
||||
|
||||
// Include the compiled template.
|
||||
include $this->cache_path;
|
||||
|
||||
// Fetch the content of the output buffer until the buffer level is the same as before.
|
||||
$content = '';
|
||||
while (ob_get_level() > $this->ob_level)
|
||||
{
|
||||
$content .= ob_get_clean();
|
||||
}
|
||||
|
||||
// Insert comments for debugging.
|
||||
if(Debug::isEnabledForCurrentUser() && \Context::getResponseMethod() === 'HTML' && !preg_match('/^<(?:\!DOCTYPE|\?xml)/', $content))
|
||||
{
|
||||
$meta = '<!--#Template%s:' . $this->relative_path . '-->' . "\n";
|
||||
$content = sprintf($meta, 'Start') . $content . sprintf($meta, 'End');
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue