mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-04 17:21:39 +09:00
commit
1d079e3194
40 changed files with 4859 additions and 1272 deletions
|
|
@ -2,7 +2,7 @@ RewriteEngine On
|
|||
|
||||
# block direct access to templates, XML schema files, config files, dotfiles, environment, etc.
|
||||
RewriteCond %{REQUEST_URI} !/modules/editor/(skins|styles)/
|
||||
RewriteRule ^(addons|common/tpl|files/ruleset|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml)$ - [L,F]
|
||||
RewriteRule ^(addons|common/tpl|files/ruleset|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml|blade\.php)$ - [L,F]
|
||||
RewriteRule ^files/(attach|config|cache)/.+\.(ph(p|t|ar)?[0-9]?|p?html?|cgi|pl|exe|[aj]spx?|inc|bak)$ - [L,F]
|
||||
RewriteRule ^files/(env|member_extra_info/(new_message_flags|point))/ - [L,F]
|
||||
RewriteRule ^(\.git|\.ht|\.travis|codeception\.|composer\.|Gruntfile\.js|package\.json|CONTRIBUTING|COPYRIGHT|LICENSE|README) - [L,F]
|
||||
|
|
|
|||
|
|
@ -49,13 +49,12 @@ class HTMLDisplayHandler
|
|||
*/
|
||||
public function toDoc(&$oModule)
|
||||
{
|
||||
$oTemplate = TemplateHandler::getInstance();
|
||||
|
||||
// SECISSUE https://github.com/xpressengine/xe-core/issues/1583
|
||||
$oSecurity = new Security();
|
||||
$oSecurity->encodeHTML('is_keyword', 'search_keyword', 'search_target', 'order_target', 'order_type');
|
||||
|
||||
$template_path = $oModule->getTemplatePath();
|
||||
$template_file = $oModule->getTemplateFile();
|
||||
|
||||
if(!is_dir($template_path))
|
||||
{
|
||||
|
|
@ -95,8 +94,8 @@ class HTMLDisplayHandler
|
|||
}
|
||||
}
|
||||
|
||||
$tpl_file = $oModule->getTemplateFile();
|
||||
$output = $oTemplate->compile($template_path, $tpl_file);
|
||||
$oTemplate = new Rhymix\Framework\Template($template_path, $template_file);
|
||||
$output = $oTemplate->compile();
|
||||
|
||||
// add .x div for adminitration pages
|
||||
if(Context::getResponseMethod() == 'HTML')
|
||||
|
|
@ -161,6 +160,8 @@ class HTMLDisplayHandler
|
|||
{
|
||||
$layout_file = 'default_layout';
|
||||
}
|
||||
|
||||
$oTemplate = new Rhymix\Framework\Template;
|
||||
$output = $oTemplate->compile($layout_path, $layout_file, $edited_layout_file);
|
||||
|
||||
// if popup_layout, remove admin bar.
|
||||
|
|
@ -291,7 +292,6 @@ class HTMLDisplayHandler
|
|||
|
||||
// convert the final layout
|
||||
Context::set('content', $output);
|
||||
$oTemplate = TemplateHandler::getInstance();
|
||||
if(Mobile::isFromMobilePhone())
|
||||
{
|
||||
$this->_loadMobileJSCSS();
|
||||
|
|
@ -300,7 +300,8 @@ class HTMLDisplayHandler
|
|||
{
|
||||
$this->_loadDesktopJSCSS();
|
||||
}
|
||||
$output = $oTemplate->compile('./common/tpl', 'common_layout');
|
||||
$oTemplate = new Rhymix\Framework\Template('./common/tpl', 'common_layout');
|
||||
$output = $oTemplate->compile();
|
||||
|
||||
// replace the user-defined-language
|
||||
$output = Context::replaceUserLang($output);
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ class RawDisplayHandler
|
|||
$tpl_file = $oModule->getTemplateFile();
|
||||
if ($tpl_path && $tpl_file)
|
||||
{
|
||||
$oTemplate = TemplateHandler::getInstance();
|
||||
$output = $oTemplate->compile($tpl_path, $tpl_file);
|
||||
$oTemplate = new Rhymix\Framework\Template($tpl_path, $tpl_file);
|
||||
$output = $oTemplate->compile();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -218,8 +218,8 @@ class ModuleObject extends BaseObject
|
|||
}
|
||||
else
|
||||
{
|
||||
$oTemplate = TemplateHandler::getInstance();
|
||||
$oTemplate->compile('modules/admin/tpl', '_admin_common.html');
|
||||
$oTemplate = new Rhymix\Framework\Template('modules/admin/tpl', '_admin_common.html');
|
||||
$oTemplate->compile();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -465,10 +465,12 @@ class ModuleObject extends BaseObject
|
|||
*/
|
||||
public function setTemplateFile($filename)
|
||||
{
|
||||
/*
|
||||
if(isset($filename) && substr_compare($filename, '.html', -5) !== 0)
|
||||
{
|
||||
$filename .= '.html';
|
||||
}
|
||||
*/
|
||||
$this->template_file = $filename;
|
||||
return $this;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
919
common/framework/Template.php
Normal file
919
common/framework/Template.php
Normal file
|
|
@ -0,0 +1,919 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework;
|
||||
|
||||
/**
|
||||
* The template class.
|
||||
*/
|
||||
class Template
|
||||
{
|
||||
/**
|
||||
* Properties for user
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Properties for configuration
|
||||
*/
|
||||
public $config;
|
||||
public $source_type;
|
||||
public $parent;
|
||||
public $vars;
|
||||
|
||||
/**
|
||||
* Properties for path manipulation
|
||||
*/
|
||||
public $absolute_dirname;
|
||||
public $relative_dirname;
|
||||
public $filename;
|
||||
public $extension;
|
||||
public $exists;
|
||||
public $absolute_path;
|
||||
public $relative_path;
|
||||
|
||||
/**
|
||||
* Properties for caching
|
||||
*/
|
||||
public $cache_enabled = true;
|
||||
public $cache_path;
|
||||
|
||||
/**
|
||||
* Properties for state management during compilation/execution
|
||||
*/
|
||||
protected $_ob_level;
|
||||
protected $_fragments = [];
|
||||
protected static $_loopvars = [];
|
||||
protected static $_stacks = [];
|
||||
|
||||
/**
|
||||
* Properties for optimization
|
||||
*/
|
||||
protected static $_mtime;
|
||||
protected static $_delay_compile;
|
||||
protected static $_json_options;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param ?string $extension
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(?string $dirname = null, ?string $filename = null, ?string $extension = null)
|
||||
{
|
||||
// Initialize configuration.
|
||||
$this->_initConfig();
|
||||
|
||||
// Set user information.
|
||||
$this->user = Session::getMemberInfo() ?: new Helpers\SessionHelper();
|
||||
|
||||
// Populate static properties for optimization.
|
||||
if (self::$_mtime === null)
|
||||
{
|
||||
self::$_mtime = filemtime(__FILE__);
|
||||
}
|
||||
if (self::$_delay_compile === null)
|
||||
{
|
||||
self::$_delay_compile = config('view.delay_compile') ?? 0;
|
||||
}
|
||||
if (self::$_json_options === null)
|
||||
{
|
||||
self::$_json_options = \JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_APOS | \JSON_HEX_QUOT | \JSON_UNESCAPED_UNICODE;
|
||||
}
|
||||
|
||||
// If paths were provided, initialize immediately.
|
||||
if ($dirname && $filename)
|
||||
{
|
||||
$this->_setSourcePath($dirname, $filename, $extension ?? 'auto');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the configuration object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _initConfig(): void
|
||||
{
|
||||
$this->config = new \stdClass;
|
||||
$this->config->version = 1;
|
||||
$this->config->autoescape = false;
|
||||
$this->config->context = 'HTML';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and normalize paths.
|
||||
*
|
||||
* @param string $dirname
|
||||
* @param string $filename
|
||||
* @param string $extension
|
||||
* @return void
|
||||
*/
|
||||
protected function _setSourcePath(string $dirname, string $filename, string $extension = 'auto'): 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 the filename doesn't have a typical extension and doesn't exist, try adding common extensions.
|
||||
if (!preg_match('/\.(?:html?|php)$/', $filename) && !Storage::exists($this->absolute_dirname . $filename))
|
||||
{
|
||||
if ($extension !== 'auto')
|
||||
{
|
||||
$filename .= '.' . $extension;
|
||||
$this->extension = $extension;
|
||||
}
|
||||
elseif (Storage::exists($this->absolute_dirname . $filename . '.html'))
|
||||
{
|
||||
$filename .= '.html';
|
||||
$this->extension = 'html';
|
||||
$this->exists = true;
|
||||
}
|
||||
elseif (Storage::exists($this->absolute_dirname . $filename . '.blade.php'))
|
||||
{
|
||||
$filename .= '.blade.php';
|
||||
$this->extension = 'blade.php';
|
||||
$this->exists = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$filename .= '.html';
|
||||
$this->extension = 'html';
|
||||
}
|
||||
}
|
||||
|
||||
// Set the remainder of properties.
|
||||
$this->filename = $filename;
|
||||
$this->absolute_path = $this->absolute_dirname . $filename;
|
||||
$this->relative_path = $this->relative_dirname . $filename;
|
||||
if ($this->extension === null)
|
||||
{
|
||||
$this->extension = preg_match('/\.(blade\.php|[a-z]+)$/i', $filename, $m) ? $m[1] : '';
|
||||
}
|
||||
if ($this->exists === null)
|
||||
{
|
||||
$this->exists = Storage::exists($this->absolute_path);
|
||||
}
|
||||
if ($this->exists && $this->extension === 'blade.php')
|
||||
{
|
||||
$this->config->version = 2;
|
||||
$this->config->autoescape = true;
|
||||
}
|
||||
$this->source_type = preg_match('!^((?:m\.)?[a-z]+)/!', $this->relative_dirname, $match) ? $match[1] : null;
|
||||
$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 . '.compiled.php';
|
||||
if ($this->exists)
|
||||
{
|
||||
Debug::addFilenameAlias($this->absolute_path, $this->cache_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable caching.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disableCache(): void
|
||||
{
|
||||
$this->cache_enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the template file exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return $this->exists ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent template.
|
||||
*
|
||||
* @return ?self
|
||||
*/
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent template.
|
||||
*
|
||||
* @param ?self $parent
|
||||
* @return void
|
||||
*/
|
||||
public function setParent(self $parent): void
|
||||
{
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get vars.
|
||||
*
|
||||
* @return ?object
|
||||
*/
|
||||
public function getVars(): ?object
|
||||
{
|
||||
return $this->vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set vars.
|
||||
*
|
||||
* @param array|object $vars
|
||||
* @return void
|
||||
*/
|
||||
public function setVars($vars): void
|
||||
{
|
||||
if (is_array($vars))
|
||||
{
|
||||
$this->vars = (object)$vars;
|
||||
}
|
||||
elseif (is_object($vars))
|
||||
{
|
||||
$this->vars = clone $vars;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception('Template vars must be an array or object');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add vars.
|
||||
*
|
||||
* @param array|object $vars
|
||||
* @return void
|
||||
*/
|
||||
public function addVars($vars): void
|
||||
{
|
||||
if (!isset($this->vars))
|
||||
{
|
||||
$this->vars = new \stdClass;
|
||||
}
|
||||
|
||||
foreach (is_object($vars) ? get_object_vars($vars) : $vars as $key => $val)
|
||||
{
|
||||
$this->vars->$key = $val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->_initConfig();
|
||||
$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 || !$this->cache_enabled)
|
||||
{
|
||||
$content = $this->parse();
|
||||
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->_initConfig();
|
||||
$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);
|
||||
}
|
||||
|
||||
// Parse the template, but don't actually execute it.
|
||||
return $this->parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function parse(?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 === '' || $content === PHP_EOL)
|
||||
{
|
||||
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('!(?<=^|\n)<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);
|
||||
|
||||
// Check the alternative version directive: @version(2)
|
||||
$content = preg_replace_callback('!(?<=^|\n)@version\s?\(([0-9]+)\)!', function($match) {
|
||||
$this->config->version = intval($match[1]);
|
||||
return sprintf('<?php $this->config->version = %s; ?>', var_export($this->config->version, true));
|
||||
}, $content);
|
||||
|
||||
// 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
|
||||
*/
|
||||
public function execute(): string
|
||||
{
|
||||
// Import Context and lang as local variables.
|
||||
$__Context = $this->vars ?: \Context::getAll();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fragment of the executed output.
|
||||
*
|
||||
* @param string $name
|
||||
* @return ?string
|
||||
*/
|
||||
public function getFragment(string $name): ?string
|
||||
{
|
||||
if (isset($this->_fragments[$name]))
|
||||
{
|
||||
return $this->_fragments[$name];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a stack.
|
||||
*
|
||||
* @param string $name
|
||||
* @return ?array
|
||||
*/
|
||||
public function getStack(string $name): ?array
|
||||
{
|
||||
if (isset(self::$_stacks[$name]))
|
||||
{
|
||||
return self::$_stacks[$name];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path should be treated as relative to the path of the current template.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function isRelativePath(string $path): bool
|
||||
{
|
||||
return !preg_match('#^((?:https?|file|data):|[\/\{<])#i', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a relative path using the given basepath.
|
||||
*
|
||||
* @param string $path
|
||||
* @param ?string $basepath
|
||||
* @return string
|
||||
*/
|
||||
public function convertPath(string $path, ?string $basepath = null): string
|
||||
{
|
||||
// If basepath is not provided, use the relative dir of the current instance.
|
||||
if ($basepath === null)
|
||||
{
|
||||
$basepath = $this->relative_dirname;
|
||||
}
|
||||
|
||||
// Path relative to the Rhymix installation directory?
|
||||
if (preg_match('#^\^/?(\w.+)$#s', $path, $match))
|
||||
{
|
||||
$path = \RX_BASEURL . $match[1];
|
||||
}
|
||||
|
||||
// Other paths will be relative to the given basepath.
|
||||
else
|
||||
{
|
||||
$path = preg_replace('#/\./#', '/', $basepath . $path);
|
||||
}
|
||||
|
||||
// Normalize and return the path.
|
||||
return $this->normalizePath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a path by removing extra slashes and parent directory references.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function normalizePath(string $path): string
|
||||
{
|
||||
$path = preg_replace('#[\\\\/]+#', '/', $path);
|
||||
while (($tmp = preg_replace('#/[^/]+/\.\.(/|$)#', '$1', $path)) !== $path)
|
||||
{
|
||||
$path = $tmp;
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* =================== HELPER FUNCTIONS FOR TEMPLATE v2 ===================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Include another template from v2 @include directive.
|
||||
*
|
||||
* Blade has several variations of the @include directive, and we need
|
||||
* access to the actual PHP args in order to process them accurately.
|
||||
* So we do this in the Template class, not in the converter.
|
||||
*
|
||||
* @param ...$args
|
||||
* @return string
|
||||
*/
|
||||
protected function _v2_include(...$args): string
|
||||
{
|
||||
// Set some basic information.
|
||||
$directive = $args[0];
|
||||
$extension = $this->extension === 'blade.php' ? 'blade.php' : null;
|
||||
$isConditional = in_array($directive, ['includeWhen', 'includeUnless']);
|
||||
$basedir = $this->relative_dirname;
|
||||
$cond = $isConditional ? $args[1] : null;
|
||||
$path = $isConditional ? $args[2] : $args[1];
|
||||
$vars = $isConditional ? ($args[3] ?? null) : ($args[2] ?? null);
|
||||
|
||||
// If the conditions are not met, return.
|
||||
if ($isConditional && $directive === 'includeWhen' && !$cond)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
if ($isConditional && $directive === 'includeUnless' && $cond)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Handle paths relative to the Rhymix installation directory.
|
||||
if (preg_match('#^\^/?(\w.+)$#s', $path, $match))
|
||||
{
|
||||
$basedir = str_contains($match[1], '/') ? dirname($match[1]) : \RX_BASEDIR;
|
||||
$path = basename($match[1]);
|
||||
}
|
||||
|
||||
// Convert relative paths embedded in the filename.
|
||||
if (preg_match('#^(.+)/([^/]+)$#', $path, $match))
|
||||
{
|
||||
$basedir = $this->normalizePath($basedir . $match[1] . '/');
|
||||
$path = $match[2];
|
||||
}
|
||||
|
||||
// Create a new instance of TemplateHandler.
|
||||
$template = new self($basedir, $path, $extension);
|
||||
|
||||
// If the directive is @includeIf and the template file does not exist, return.
|
||||
if ($directive === 'includeIf' && !$template->exists())
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Set variables.
|
||||
$template->setParent($this);
|
||||
if ($this->vars)
|
||||
{
|
||||
$template->setVars($this->vars);
|
||||
}
|
||||
if ($vars !== null)
|
||||
{
|
||||
$template->addVars($vars);
|
||||
}
|
||||
|
||||
// Compile and return.
|
||||
return $template->compile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a resource from v2 @load directive.
|
||||
*
|
||||
* The Blade-style syntax does not have named arguments, so we must rely
|
||||
* on the position and format of each argument to guess what it is for.
|
||||
* Fortunately, there are only a handful of valid options for the type,
|
||||
* media, and index attributes.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $media_type
|
||||
* @param int $index
|
||||
* @param array|object $vars
|
||||
* @return void
|
||||
*/
|
||||
protected function _v2_loadResource(string $path, $media_type = null, $index = null, $vars = null): void
|
||||
{
|
||||
// Assign the path.
|
||||
if (empty($path))
|
||||
{
|
||||
trigger_error('Resource loading directive used with no path', \E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether the path is an internal or external link.
|
||||
$external = false;
|
||||
if (preg_match('#^\^#', $path))
|
||||
{
|
||||
$path = './' . ltrim($path, '^/');
|
||||
}
|
||||
elseif ($this->isRelativePath($path))
|
||||
{
|
||||
$path = $this->convertPath($path, './' . $this->relative_dirname);
|
||||
}
|
||||
else
|
||||
{
|
||||
$external = true;
|
||||
}
|
||||
|
||||
// If any of the variables seems to be an array or object, it's $vars.
|
||||
if (!is_scalar($media_type))
|
||||
{
|
||||
$vars = $media_type;
|
||||
$media_type = null;
|
||||
}
|
||||
if (!is_scalar($index))
|
||||
{
|
||||
$vars = $index;
|
||||
$index = null;
|
||||
}
|
||||
if (ctype_digit($media_type ?? ''))
|
||||
{
|
||||
$index = $media_type;
|
||||
$media_type = null;
|
||||
}
|
||||
|
||||
// Split the media type if it has a colon in it.
|
||||
if (preg_match('#^(css|js):(.+)$#s', $media_type ?? '', $match))
|
||||
{
|
||||
$media_type = trim($match[2]);
|
||||
$type = $match[1];
|
||||
}
|
||||
|
||||
// Determine the type of resource.
|
||||
elseif (!$external && str_starts_with($path, './common/js/plugins/'))
|
||||
{
|
||||
$type = 'jsplugin';
|
||||
}
|
||||
elseif (!$external && preg_match('#/lang(\.xml)?$#', $path))
|
||||
{
|
||||
$type = 'lang';
|
||||
}
|
||||
elseif (preg_match('#\.(css|js|scss|less)($|\?|/)#', $path, $match))
|
||||
{
|
||||
$type = $match[1];
|
||||
}
|
||||
elseif (preg_match('#/css\d?\?.+#', $path))
|
||||
{
|
||||
$type = 'css';
|
||||
}
|
||||
else
|
||||
{
|
||||
$type = 'unknown';
|
||||
}
|
||||
|
||||
// Load the resource.
|
||||
if ($type === 'jsplugin')
|
||||
{
|
||||
if (preg_match('#/common/js/plugins/([^/]+)#', $path, $match))
|
||||
{
|
||||
$plugin_name = $match[1];
|
||||
\Context::loadJavascriptPlugin($plugin_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error("Unable to find JS plugin at $path", \E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
elseif ($type === 'lang')
|
||||
{
|
||||
$lang_dir = preg_replace('#/lang\.xml$#', '', $path);
|
||||
\Context::loadLang($lang_dir);
|
||||
}
|
||||
elseif ($type === 'js')
|
||||
{
|
||||
\Context::loadFile([
|
||||
$path,
|
||||
$media_type ?? '',
|
||||
$external ? $this->source_type : '',
|
||||
$index ? intval($index) : '',
|
||||
]);
|
||||
}
|
||||
elseif ($type === 'css' || $type === 'scss' || $type === 'less')
|
||||
{
|
||||
\Context::loadFile([
|
||||
$path,
|
||||
$media_type ?? '',
|
||||
$external ? $this->source_type : '',
|
||||
$index ? intval($index) : '',
|
||||
$vars ?? [],
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error("Unable to determine type of resource at $path", \E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize v2 loop variable.
|
||||
*
|
||||
* @param string $stack_id
|
||||
* @param array|Traversable &$array
|
||||
* @return object
|
||||
*/
|
||||
protected function _v2_initLoopVar(string $stack_id, &$array): object
|
||||
{
|
||||
// Create the data structure.
|
||||
$loop = new \stdClass;
|
||||
$loop->index = 0;
|
||||
$loop->iteration = 1;
|
||||
$loop->count = is_countable($array) ? count($array) : countobj($array);
|
||||
$loop->remaining = $loop->count - 1;
|
||||
$loop->first = true;
|
||||
$loop->last = ($loop->count === 1);
|
||||
$loop->even = false;
|
||||
$loop->odd = true;
|
||||
$loop->depth = count(self::$_loopvars) + 1;
|
||||
$loop->parent = count(self::$_loopvars) ? end(self::$_loopvars) : null;
|
||||
|
||||
// Append to stack and return.
|
||||
return self::$_loopvars[$stack_id] = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment v2 loop variable.
|
||||
*
|
||||
* @param object $loopvar
|
||||
* @return void
|
||||
*/
|
||||
protected function _v2_incrLoopVar(object $loop): void
|
||||
{
|
||||
// Update properties.
|
||||
$loop->index++;
|
||||
$loop->iteration++;
|
||||
$loop->remaining--;
|
||||
$loop->first = ($loop->count === 1);
|
||||
$loop->last = ($loop->iteration === $loop->count);
|
||||
$loop->even = ($loop->iteration % 2 === 0);
|
||||
$loop->odd = !$loop->even;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove v2 loop variable.
|
||||
*
|
||||
* @param object $loopvar
|
||||
* @return void
|
||||
*/
|
||||
protected function _v2_removeLoopVar(object $loop): void
|
||||
{
|
||||
// Remove from stack.
|
||||
if ($loop === end(self::$_loopvars))
|
||||
{
|
||||
array_pop(self::$_loopvars);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute builder for v2.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param array $definition
|
||||
* @return string
|
||||
*/
|
||||
protected function _v2_buildAttribute(string $attribute, array $definition = []): string
|
||||
{
|
||||
$delimiters = [
|
||||
'class' => ' ',
|
||||
'style' => '; ',
|
||||
];
|
||||
|
||||
$values = [];
|
||||
foreach ($definition as $key => $val)
|
||||
{
|
||||
if (is_int($key) && !empty($val))
|
||||
{
|
||||
$values[] = $val;
|
||||
}
|
||||
elseif ($val)
|
||||
{
|
||||
$values[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf(' %s="%s"', $attribute, escape(implode($delimiters[$attribute], $values), false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth checker for v2.
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
protected function _v2_checkAuth(string $type = 'member'): bool
|
||||
{
|
||||
$grant = \Context::get('grant');
|
||||
switch ($type)
|
||||
{
|
||||
case 'admin': return $this->user->isAdmin();
|
||||
case 'manager': return $grant->manager ?? false;
|
||||
case 'member': return $this->user->isMember();
|
||||
default: false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Capability checker for v2.
|
||||
*
|
||||
* @param int $check_type
|
||||
* @param string|array $capability
|
||||
* @return bool
|
||||
*/
|
||||
protected function _v2_checkCapability(int $check_type, $capability): bool
|
||||
{
|
||||
$grant = \Context::get('grant');
|
||||
if ($check_type === 1)
|
||||
{
|
||||
return isset($grant->$capability) ? boolval($grant->$capability) : false;
|
||||
}
|
||||
elseif ($check_type === 2)
|
||||
{
|
||||
return isset($grant->$capability) ? !boolval($grant->$capability) : true;
|
||||
}
|
||||
elseif (is_array($capability))
|
||||
{
|
||||
foreach ($capability as $cap)
|
||||
{
|
||||
if (isset($grant->$cap) && $grant->$cap)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a validation error exists for v2.
|
||||
*
|
||||
* @param ...$args
|
||||
* @return bool
|
||||
*/
|
||||
protected function _v2_errorExists(...$args): bool
|
||||
{
|
||||
$validator_id = \Context::get('XE_VALIDATOR_ID');
|
||||
$validator_message = \Context::get('XE_VALIDATOR_MESSAGE');
|
||||
if (empty($validator_id) || empty($validator_message))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return count($args) ? in_array((string)$validator_id, $args, true) : true;
|
||||
}
|
||||
}
|
||||
911
common/framework/parsers/template/TemplateParser_v1.php
Normal file
911
common/framework/parsers/template/TemplateParser_v1.php
Normal file
|
|
@ -0,0 +1,911 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Parsers\Template;
|
||||
|
||||
use HTMLDisplayHandler;
|
||||
use Rhymix\Framework\Template;
|
||||
|
||||
/**
|
||||
* Template parser v1 for XE compatibility.
|
||||
*
|
||||
* Originally part of TemplateHandler, this parser is preserved here
|
||||
* for bug-for-bug compatibility with XE and older versions of Rhymix.
|
||||
* A significant part of this code dates back to the early days of XE,
|
||||
* though Rhymix has managed to squeeze in a few new features.
|
||||
*
|
||||
* Except in the case of a serious security issue, there will be no change
|
||||
* to the parsing and conversion logic, and no new features.
|
||||
* It is strongly recommended that new templates be written in v2.
|
||||
*/
|
||||
class TemplateParser_v1
|
||||
{
|
||||
/**
|
||||
* Instance properties.
|
||||
*/
|
||||
public $autoescape_config_exists;
|
||||
public $source_type;
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* Convert template code into PHP.
|
||||
*
|
||||
* @param string $content
|
||||
* @param Template $template
|
||||
* @return string
|
||||
*/
|
||||
public function convert(string $content, Template $template): string
|
||||
{
|
||||
// Prepare default settings.
|
||||
ini_set('pcre.jit', false);
|
||||
$this->autoescape_config_exists = str_contains($content, '$this->config->autoescape = ');
|
||||
$this->source_type = preg_match('!^((?:m\.)?[a-z]+)/!', $template->relative_dirname, $matches) ? $matches[1] : null;
|
||||
$this->template = $template;
|
||||
|
||||
// replace comments
|
||||
$content = preg_replace('@<!--//.*?-->@s', '', $content);
|
||||
|
||||
// replace value of src in img/input/script tag
|
||||
$content = preg_replace_callback('/<(?:img|input|script)(?:[^<>]*?)(?(?=cond=")(?:cond="[^"]+"[^<>]*)+|)[^<>]* src="(?!(?:https?|file|data):|[\/\{])([^"]+)"/is', array($this, '_replacePath'), $content);
|
||||
|
||||
// replace value of srcset in img/source/link tag
|
||||
$content = preg_replace_callback('/<(?:img|source|link)(?:[^<>]*?)(?(?=cond=")(?:cond="[^"]+"[^<>]*)+|)[^<>]* srcset="([^"]+)"/is', array($this, '_replaceSrcsetPath'), $content);
|
||||
|
||||
// replace loop and cond template syntax
|
||||
$content = $this->_parseInline($content);
|
||||
|
||||
// include, unload/load, import
|
||||
$content = preg_replace_callback('/{(@[\s\S]+?|(?=[\$\\\\]\w+|_{1,2}[A-Z]+|[!\(+-]|\w+(?:\(|::)|\d+|[\'"].*?[\'"]).+?)}|<(!--[#%])?(include|import|(un)?load(?(4)|(?:_js_plugin)?)|config)(?(2)\(["\']([^"\']+)["\'])(.*?)(?(2)\)--|\/)>|<!--(@[a-z@]*)([\s\S]*?)-->(\s*)/', array($this, '_parseResource'), $content);
|
||||
|
||||
// remove block which is a virtual tag
|
||||
$content = preg_replace('@</?block\s*>@is', '', $content);
|
||||
|
||||
// form auto generation
|
||||
$temp = preg_replace_callback('/(<form(?:<\?php.+?\?>|[^<>]+)*?>)(.*?)(<\/form>)/is', array($this, '_compileFormAuthGeneration'), $content);
|
||||
if($temp)
|
||||
{
|
||||
$content = $temp;
|
||||
}
|
||||
|
||||
// prevent from calling directly before writing into file
|
||||
$content = '<?php if (!defined("RX_VERSION")) exit();?>' . $content;
|
||||
|
||||
// restore curly braces from temporary entities
|
||||
$content = self::_replaceTempEntities($content);
|
||||
|
||||
// remove php script reopening
|
||||
$content = preg_replace_callback('/([;{])?( )*\?\>\<\?php\s/', function($match) {
|
||||
return $match[1] === '{' ? '{ ' : '; ';
|
||||
}, $content);
|
||||
|
||||
// remove empty lines
|
||||
$content = preg_replace([
|
||||
'/>\<\?php } \?\>\n[\t\x20]*?(?=\n<!--)/',
|
||||
'/\n[\t\x20]*?(?=\n<!--)/',
|
||||
'/\n[\t\x20]+?\<\?php/',
|
||||
], [
|
||||
"><?php } ?>\n<?php echo \"\\n\"; ?>",
|
||||
"\n<?php ?>",
|
||||
"\n\t<?php",
|
||||
], $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* preg_replace_callback handler
|
||||
* 1. remove ruleset from form tag
|
||||
* 2. add hidden tag with ruleset value
|
||||
* 3. if empty default hidden tag, generate hidden tag (ex:mid, act...)
|
||||
* 4. generate return url, return url use in server side validator
|
||||
* @param array $matches
|
||||
* @return string
|
||||
*/
|
||||
private function _compileFormAuthGeneration($matches)
|
||||
{
|
||||
// check rx-autoform attribute
|
||||
if (preg_match('/\srx-autoform="([^">]*?)"/', $matches[1], $m1))
|
||||
{
|
||||
$autoform = toBool($m1[1]);
|
||||
$matches[1] = preg_replace('/\srx-autoform="([^">]*?)"/', '', $matches[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$autoform = true;
|
||||
}
|
||||
|
||||
// form ruleset attribute move to hidden tag
|
||||
if ($autoform && $matches[1])
|
||||
{
|
||||
preg_match('/ruleset="([^"]*?)"/is', $matches[1], $m);
|
||||
if(isset($m[0]) && $m[0])
|
||||
{
|
||||
$matches[1] = preg_replace('/' . addcslashes($m[0], '?$') . '/i', '', $matches[1]);
|
||||
|
||||
if(strpos($m[1], '@') !== FALSE)
|
||||
{
|
||||
$path = str_replace('@', '', $m[1]);
|
||||
$path = './files/ruleset/' . $path . '.xml';
|
||||
$autoPath = '';
|
||||
}
|
||||
else if(strpos($m[1], '#') !== FALSE)
|
||||
{
|
||||
$fileName = str_replace('#', '', $m[1]);
|
||||
$fileName = str_replace('<?php echo ', '', $fileName);
|
||||
$fileName = str_replace(' ?>', '', $fileName);
|
||||
$path = '#./files/ruleset/' . $fileName . '.xml';
|
||||
|
||||
preg_match('@(?:^|\.?/)(modules/[\w-]+)@', $this->template->relative_path, $mm);
|
||||
$module_path = $mm[1];
|
||||
list($rulsetFile) = explode('.', $fileName);
|
||||
$autoPath = $module_path . '/ruleset/' . $rulsetFile . '.xml';
|
||||
$m[1] = $rulsetFile;
|
||||
}
|
||||
else if(preg_match('@(?:^|\.?/)(modules/[\w-]+)@', $this->template->relative_path, $mm))
|
||||
{
|
||||
$module_path = $mm[1];
|
||||
$path = $module_path . '/ruleset/' . $m[1] . '.xml';
|
||||
$autoPath = '';
|
||||
}
|
||||
|
||||
$matches[2] = '<input type="hidden" name="ruleset" value="' . $m[1] . '" />' . $matches[2];
|
||||
//assign to addJsFile method for js dynamic recache
|
||||
$matches[1] = '<?php Context::addJsFile("' . $path . '", FALSE, "", 0, "body", TRUE, "' . $autoPath . '") ?' . '>' . $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
// if not exists default hidden tag, generate hidden tag
|
||||
if ($autoform)
|
||||
{
|
||||
preg_match_all('/<input[^>]* name="(act|mid)"/is', $matches[2], $m2);
|
||||
$missing_inputs = array_diff(['act', 'mid'], $m2[1]);
|
||||
if(is_array($missing_inputs))
|
||||
{
|
||||
$generatedHidden = '';
|
||||
foreach($missing_inputs as $key)
|
||||
{
|
||||
$generatedHidden .= '<input type="hidden" name="' . $key . '" value="<?php echo $__Context->' . $key . ' ?? \'\'; ?>" />';
|
||||
}
|
||||
$matches[2] = $generatedHidden . $matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
// return url generate
|
||||
if ($autoform)
|
||||
{
|
||||
if (!preg_match('/no-(?:error-)?return-url="true"/i', $matches[1]))
|
||||
{
|
||||
preg_match('/<input[^>]*name="error_return_url"[^>]*>/is', $matches[2], $m3);
|
||||
if(!isset($m3[0]) || !$m3[0])
|
||||
{
|
||||
$matches[2] = '<input type="hidden" name="error_return_url" value="<?php echo escape(getRequestUriByServerEnviroment(), false); ?>" />' . $matches[2];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$matches[1] = preg_replace('/no-(?:error-)?return-url="true"/i', '', $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
array_shift($matches);
|
||||
return implode('', $matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* preg_replace_callback handler
|
||||
*
|
||||
* replace image path
|
||||
* @param array $match
|
||||
*
|
||||
* @return string changed result
|
||||
*/
|
||||
private function _replacePath($match)
|
||||
{
|
||||
$src = $this->_replaceRelativePath($match);
|
||||
return substr($match[0], 0, -strlen($match[1]) - 6) . "src=\"{$src}\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* replace relative path
|
||||
* @param array $match
|
||||
*
|
||||
* @return string changed result
|
||||
*/
|
||||
private function _replaceRelativePath($match)
|
||||
{
|
||||
//return origin code when src value started '${'.
|
||||
if(preg_match('@^\${@', $match[1]))
|
||||
{
|
||||
return $match[1];
|
||||
}
|
||||
|
||||
//return origin code when src value include variable.
|
||||
if(preg_match('@^[\'|"]\s*\.\s*\$@', $match[1]))
|
||||
{
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
$src = preg_replace('@^(\./)+@', '', trim($match[1]));
|
||||
|
||||
$src = \RX_BASEURL . $this->template->relative_dirname . $src;
|
||||
$src = str_replace('/./', '/', $src);
|
||||
|
||||
// for backward compatibility
|
||||
$src = preg_replace('@/((?:[\w-]+/)+)\1@', '/\1', $src);
|
||||
|
||||
while(($tmp = preg_replace('@[^/]+/\.\./@', '', $src, 1)) !== $src)
|
||||
{
|
||||
$src = $tmp;
|
||||
}
|
||||
|
||||
return $src;
|
||||
}
|
||||
|
||||
/**
|
||||
* preg_replace_callback handler
|
||||
*
|
||||
* replace srcset string with multiple paths
|
||||
* @param array $match
|
||||
*
|
||||
* @return string changed result
|
||||
*/
|
||||
private function _replaceSrcsetPath($match)
|
||||
{
|
||||
// explode urls by comma
|
||||
$url_list = explode(",", $match[1]);
|
||||
|
||||
foreach ($url_list as &$url) {
|
||||
// replace if url is not starting with the pattern
|
||||
$url = preg_replace_callback(
|
||||
'/^(?!(?:https?|file|data):|[\/\{])(\S+)/i',
|
||||
array($this, '_replaceRelativePath'),
|
||||
trim($url)
|
||||
);
|
||||
}
|
||||
$srcset = implode(", ", $url_list);
|
||||
|
||||
return substr($match[0], 0, -strlen($match[1]) - 9) . "srcset=\"{$srcset}\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* replace loop and cond template syntax
|
||||
* @param string $content
|
||||
* @return string changed result
|
||||
*/
|
||||
private function _parseInline($content)
|
||||
{
|
||||
// list of self closing tags
|
||||
$self_closing = array('area' => 1, 'base' => 1, 'basefont' => 1, 'br' => 1, 'hr' => 1, 'input' => 1, 'img' => 1, 'link' => 1, 'meta' => 1, 'param' => 1, 'frame' => 1, 'col' => 1);
|
||||
|
||||
$skip = sprintf('(?!%s)', implode('|', ['marquee']));
|
||||
$split_regex = "@(</?{$skip}[a-zA-Z](?>[^<>{}\"]+|<!--.*?-->.*?<!--.*?end-->|{[^}]*}|\"(?>'.*?'|.)*?\"|.)*?>)@s";
|
||||
$nodes = preg_split($split_regex, $content, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
for($idx = 1, $node_len = count($nodes); $idx < $node_len; $idx+=2)
|
||||
{
|
||||
if(!($node = $nodes[$idx]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(preg_match_all('@\s(loop|cond)="([^"]+)"@', $node, $matches))
|
||||
{
|
||||
// this tag
|
||||
$tag = substr($node, 1, strpos($node, ' ') - 1);
|
||||
|
||||
// if the vale of $closing is 0, it means 'skipping'
|
||||
$closing = 0;
|
||||
|
||||
// process opening tag
|
||||
foreach($matches[1] as $n => $stmt)
|
||||
{
|
||||
$expr = $matches[2][$n];
|
||||
$expr = self::_replaceVar($expr);
|
||||
$closing++;
|
||||
|
||||
switch($stmt)
|
||||
{
|
||||
case 'cond':
|
||||
if (preg_match('/^\$[\\\\\w\[\]\'":>-]+$/i', $expr))
|
||||
{
|
||||
$expr = "$expr ?? false";
|
||||
}
|
||||
$nodes[$idx - 1] .= "<?php if({$expr}){ ?>";
|
||||
break;
|
||||
case 'loop':
|
||||
if(!preg_match('@^(?:(.+?)=>(.+?)(?:,(.+?))?|(.*?;.*?;.*?)|(.+?)\s*=\s*(.+?))$@', $expr, $expr_m))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if($expr_m[1])
|
||||
{
|
||||
$expr_m[1] = trim($expr_m[1]);
|
||||
$expr_m[2] = trim($expr_m[2]);
|
||||
if(isset($expr_m[3]) && $expr_m[3])
|
||||
{
|
||||
$expr_m[2] .= '=>' . trim($expr_m[3]);
|
||||
}
|
||||
$nodes[$idx - 1] .= sprintf('<?php $__loop_tmp=%1$s;if($__loop_tmp)foreach($__loop_tmp as %2$s){ ?>', $expr_m[1], $expr_m[2]);
|
||||
}
|
||||
elseif(isset($expr_m[4]) && $expr_m[4])
|
||||
{
|
||||
$nodes[$idx - 1] .= "<?php for({$expr_m[4]}){ ?>";
|
||||
}
|
||||
elseif(isset($expr_m[5]) && $expr_m[5])
|
||||
{
|
||||
$nodes[$idx - 1] .= "<?php while({$expr_m[5]}={$expr_m[6]}){ ?>";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$node = preg_replace('@\s(loop|cond)="([^"]+)"@', '', $node);
|
||||
|
||||
// find closing tag
|
||||
$close_php = '<?php ' . str_repeat('}', $closing) . ' ?>';
|
||||
// self closing tag
|
||||
if($node[1] == '!' || substr($node, -2, 1) == '/' || isset($self_closing[$tag]))
|
||||
{
|
||||
$nodes[$idx + 1] = $close_php . $nodes[$idx + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$depth = 1;
|
||||
for($i = $idx + 2; $i < $node_len; $i+=2)
|
||||
{
|
||||
$nd = $nodes[$i];
|
||||
if(strpos($nd, $tag) === 1)
|
||||
{
|
||||
$depth++;
|
||||
}
|
||||
elseif(strpos($nd, '/' . $tag) === 1)
|
||||
{
|
||||
$depth--;
|
||||
if(!$depth)
|
||||
{
|
||||
$nodes[$i - 1] .= $nodes[$i] . $close_php;
|
||||
$nodes[$i] = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(strpos($node, '|cond="') !== false)
|
||||
{
|
||||
$node = preg_replace('@(\s[-\w:]+(?:="[^"]+?")?)\|cond="(.+?)"@s', '<?php if($2){ ?>$1<?php } ?>', $node);
|
||||
$node = self::_replaceVar($node);
|
||||
}
|
||||
|
||||
if($nodes[$idx] != $node)
|
||||
{
|
||||
$nodes[$idx] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
$content = implode('', $nodes);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* preg_replace_callback handler
|
||||
* replace php code.
|
||||
* @param array $m
|
||||
* @return string changed result
|
||||
*/
|
||||
private function _parseResource($m)
|
||||
{
|
||||
// {@ ... } or {$var} or {func(...)}
|
||||
if($m[1])
|
||||
{
|
||||
if(preg_match('@^(\w+)\(@', $m[1], $mm) && (!function_exists($mm[1]) && !in_array($mm[1], ['isset', 'unset', 'empty'])))
|
||||
{
|
||||
return $m[0];
|
||||
}
|
||||
|
||||
if($m[1][0] == '@')
|
||||
{
|
||||
$m[1] = self::_replaceVar(substr($m[1], 1));
|
||||
return "<?php {$m[1]} ?>";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get escape options.
|
||||
if($m[1] === '$content' && preg_match('@^layouts/.+/layout\.html$@', $this->template->relative_path))
|
||||
{
|
||||
$escape_option = 'noescape';
|
||||
}
|
||||
elseif(preg_match('/^\$(?:user_)?lang->[a-zA-Z0-9\_]+$/', $m[1]))
|
||||
{
|
||||
$escape_option = 'noescape';
|
||||
}
|
||||
elseif(preg_match('/^lang\(.+\)$/', $m[1]))
|
||||
{
|
||||
$escape_option = 'noescape';
|
||||
}
|
||||
else
|
||||
{
|
||||
$escape_option = $this->autoescape_config_exists ? 'auto' : 'noescape';
|
||||
}
|
||||
|
||||
// Separate filters from variable.
|
||||
if (preg_match('@^(.+?)(?<![|\s])((?:\|[a-z]{2}[a-z0-9_]+(?::.+)?)+)$@', $m[1], $mm))
|
||||
{
|
||||
$m[1] = $mm[1];
|
||||
$filters = array_map('trim', explode_with_escape('|', substr($mm[2], 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
$filters = array();
|
||||
}
|
||||
|
||||
// Process the variable.
|
||||
$var = self::_replaceVar($m[1]);
|
||||
|
||||
// Apply filters.
|
||||
foreach ($filters as $filter)
|
||||
{
|
||||
// Separate filter option from the filter name.
|
||||
if (preg_match('/^([a-z0-9_-]+):(.+)$/', $filter, $matches))
|
||||
{
|
||||
$filter = $matches[1];
|
||||
$filter_option = $matches[2];
|
||||
if (!self::_isVar($filter_option) && !preg_match("/^'.*'$/", $filter_option) && !preg_match('/^".*"$/', $filter_option))
|
||||
{
|
||||
$filter_option = "'" . escape_sqstr($filter_option) . "'";
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter_option = self::_replaceVar($filter_option);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter_option = null;
|
||||
}
|
||||
|
||||
// Apply each filter.
|
||||
switch ($filter)
|
||||
{
|
||||
case 'auto':
|
||||
case 'autoescape':
|
||||
case 'autolang':
|
||||
case 'escape':
|
||||
case 'noescape':
|
||||
$escape_option = $filter;
|
||||
break;
|
||||
|
||||
case 'escapejs':
|
||||
$var = "escape_js({$var})";
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
$var = "json_encode({$var})";
|
||||
break;
|
||||
|
||||
case 'strip':
|
||||
case 'strip_tags':
|
||||
$var = $filter_option ? "strip_tags({$var}, {$filter_option})" : "strip_tags({$var})";
|
||||
break;
|
||||
|
||||
case 'trim':
|
||||
$var = "trim({$var})";
|
||||
break;
|
||||
|
||||
case 'urlencode':
|
||||
$var = "rawurlencode({$var})";
|
||||
break;
|
||||
|
||||
case 'lower':
|
||||
$var = "strtolower({$var})";
|
||||
break;
|
||||
|
||||
case 'upper':
|
||||
$var = "strtoupper({$var})";
|
||||
break;
|
||||
|
||||
case 'nl2br':
|
||||
$var = $this->_applyEscapeOption($var, $escape_option);
|
||||
$var = "nl2br({$var})";
|
||||
$escape_option = 'noescape';
|
||||
break;
|
||||
|
||||
case 'join':
|
||||
$var = $filter_option ? "implode({$filter_option}, {$var})" : "implode(', ', {$var})";
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
$var = $filter_option ? "getDisplayDateTime(ztime({$var}), {$filter_option})" : "getDisplayDateTime(ztime({$var}), 'Y-m-d H:i:s')";
|
||||
break;
|
||||
|
||||
case 'format':
|
||||
case 'number_format':
|
||||
$var = $filter_option ? "number_format({$var}, {$filter_option})" : "number_format({$var})";
|
||||
break;
|
||||
|
||||
case 'shorten':
|
||||
case 'number_shorten':
|
||||
$var = $filter_option ? "number_shorten({$var}, {$filter_option})" : "number_shorten({$var})";
|
||||
break;
|
||||
|
||||
case 'link':
|
||||
$var = $this->_applyEscapeOption($var, $escape_option);
|
||||
if ($filter_option)
|
||||
{
|
||||
$filter_option = $this->_applyEscapeOption($filter_option, $escape_option);
|
||||
$var = "'<a href=\"' . ($filter_option) . '\">' . ($var) . '</a>'";
|
||||
}
|
||||
else
|
||||
{
|
||||
$var = "'<a href=\"' . ($var) . '\">' . ($var) . '</a>'";
|
||||
}
|
||||
$escape_option = 'noescape';
|
||||
break;
|
||||
|
||||
default:
|
||||
$filter = escape_sqstr($filter);
|
||||
$var = "'INVALID FILTER ({$filter})'";
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the escape option and return.
|
||||
return '<?php echo ' . $this->_applyEscapeOption($var, $escape_option) . ' ?>';
|
||||
}
|
||||
}
|
||||
|
||||
if($m[3])
|
||||
{
|
||||
$attr = array();
|
||||
if($m[5])
|
||||
{
|
||||
if(preg_match_all('@,(\w+)="([^"]+)"@', $m[6], $mm))
|
||||
{
|
||||
foreach($mm[1] as $idx => $name)
|
||||
{
|
||||
$attr[$name] = $mm[2][$idx];
|
||||
}
|
||||
}
|
||||
$attr['target'] = $m[5];
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!preg_match_all('@ (\w+)="([^"]+)"@', $m[6], $mm))
|
||||
{
|
||||
return $m[0];
|
||||
}
|
||||
foreach($mm[1] as $idx => $name)
|
||||
{
|
||||
$attr[$name] = $mm[2][$idx];
|
||||
}
|
||||
}
|
||||
|
||||
switch($m[3])
|
||||
{
|
||||
// <!--#include--> or <include ..>
|
||||
case 'include':
|
||||
if(!$this->template->relative_dirname || !$attr['target'])
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
if (preg_match('!^\\^/(.+)!', $attr['target'], $tmatches))
|
||||
{
|
||||
$pathinfo = pathinfo(\RX_BASEDIR . $tmatches[1]);
|
||||
$fileDir = $pathinfo['dirname'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$pathinfo = pathinfo($attr['target']);
|
||||
$fileDir = $this->_getRelativeDir($pathinfo['dirname']);
|
||||
}
|
||||
|
||||
if(!$fileDir)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
return "<?php \$__tpl=TemplateHandler::getInstance();echo \$__tpl->compile('{$fileDir}','{$pathinfo['basename']}') ?>";
|
||||
// <!--%load_js_plugin-->
|
||||
case 'load_js_plugin':
|
||||
$plugin = self::_replaceVar($m[5]);
|
||||
$s = "<!--#JSPLUGIN:{$plugin}-->";
|
||||
if(strpos($plugin, '$__Context') === false)
|
||||
{
|
||||
$plugin = "'{$plugin}'";
|
||||
}
|
||||
|
||||
$s .= "<?php Context::loadJavascriptPlugin({$plugin}); ?>";
|
||||
return $s;
|
||||
// <load ...> or <unload ...> or <!--%import ...--> or <!--%unload ...-->
|
||||
case 'import':
|
||||
case 'load':
|
||||
case 'unload':
|
||||
$metafile = '';
|
||||
$metavars = '';
|
||||
$replacements = HTMLDisplayHandler::$replacements;
|
||||
$attr['target'] = preg_replace(array_keys($replacements), array_values($replacements), $attr['target']);
|
||||
$pathinfo = pathinfo($attr['target']);
|
||||
$doUnload = ($m[3] === 'unload');
|
||||
$isRemote = !!preg_match('@^(https?:)?//@i', $attr['target']);
|
||||
|
||||
if(!$isRemote)
|
||||
{
|
||||
if (preg_match('!^\\^/(.+)!', $attr['target'], $tmatches))
|
||||
{
|
||||
$pathinfo = pathinfo($tmatches[1]);
|
||||
$relativeDir = $pathinfo['dirname'];
|
||||
$attr['target'] = $relativeDir . '/' . $pathinfo['basename'];
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!preg_match('@^\.?/@', $attr['target']))
|
||||
{
|
||||
$attr['target'] = './' . $attr['target'];
|
||||
}
|
||||
$relativeDir = $this->_getRelativeDir($pathinfo['dirname']);
|
||||
$attr['target'] = $relativeDir . '/' . $pathinfo['basename'];
|
||||
}
|
||||
}
|
||||
|
||||
switch($pathinfo['extension'])
|
||||
{
|
||||
case 'xml':
|
||||
if($isRemote || $doUnload)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
// language file?
|
||||
if($pathinfo['basename'] == 'lang.xml' || substr($pathinfo['dirname'], -5) == '/lang')
|
||||
{
|
||||
$result = "Context::loadLang('{$relativeDir}');";
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = "require_once('./classes/xml/XmlJsFilter.class.php');\$__xmlFilter=new XmlJsFilter('{$relativeDir}','{$pathinfo['basename']}');\$__xmlFilter->compile();";
|
||||
}
|
||||
break;
|
||||
case 'js':
|
||||
if($doUnload)
|
||||
{
|
||||
$result = vsprintf("Context::unloadFile('%s', '');", [$attr['target'] ?? '']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$metafile = isset($attr['target']) ? $attr['target'] : '';
|
||||
$result = vsprintf("Context::loadFile(['%s', '%s', '%s', '%s']);", [
|
||||
$attr['target'] ?? '', $attr['type'] ?? '', $isRemote ? $this->source_type : '', $attr['index'] ?? '',
|
||||
]);
|
||||
}
|
||||
break;
|
||||
case 'css':
|
||||
case 'less':
|
||||
case 'scss':
|
||||
if($doUnload)
|
||||
{
|
||||
$result = vsprintf("Context::unloadFile('%s', '', '%s');", [
|
||||
$attr['target'] ?? '', $attr['media'] ?? '',
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$metafile = isset($attr['target']) ? $attr['target'] : '';
|
||||
$metavars = isset($attr['vars']) ? ($attr['vars'] ? self::_replaceVar($attr['vars']) : '') : '';
|
||||
$result = vsprintf("Context::loadFile(['%s', '%s', '%s', '%s', %s]);", [
|
||||
$attr['target'] ?? '', $attr['media'] ?? '', $isRemote ? $this->source_type : '', $attr['index'] ?? '',
|
||||
isset($attr['vars']) ? ($attr['vars'] ? self::_replaceVar($attr['vars']) : '[]') : '[]',
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$result = "<?php {$result} ?>";
|
||||
if($metafile)
|
||||
{
|
||||
if(!$metavars)
|
||||
{
|
||||
$result = "<!--#Meta:{$metafile}-->" . $result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// LESS or SCSS needs the variables to be substituted.
|
||||
$result = "<!--#Meta:{$metafile}?{$metavars}-->" . $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
// <config ...>
|
||||
case 'config':
|
||||
$result = '';
|
||||
if(preg_match_all('@ (\w+)="([^"]+)"@', $m[6], $config_matches, PREG_SET_ORDER))
|
||||
{
|
||||
foreach($config_matches as $config_match)
|
||||
{
|
||||
$config_value = toBool(trim(strtolower($config_match[2]))) ? 'true' : 'false';
|
||||
$result .= "\$this->config->{$config_match[1]} = $config_value;";
|
||||
}
|
||||
}
|
||||
return "<?php {$result} ?>";
|
||||
}
|
||||
}
|
||||
|
||||
// <!--@..--> such as <!--@if($cond)-->, <!--@else-->, <!--@end-->
|
||||
if($m[7])
|
||||
{
|
||||
$m[7] = substr($m[7], 1);
|
||||
if(!$m[7])
|
||||
{
|
||||
return '<?php ' . self::_replaceVar($m[8]) . '{ ?>' . $m[9];
|
||||
}
|
||||
if(!preg_match('/^(?:((?:end)?(?:if|switch|for(?:each)?|while)|end)|(else(?:if)?)|(break@)?(case|default)|(break))$/', $m[7], $mm))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
if($mm[1])
|
||||
{
|
||||
if($mm[1][0] == 'e')
|
||||
{
|
||||
return '<?php } ?>' . $m[9];
|
||||
}
|
||||
|
||||
$precheck = '';
|
||||
if($mm[1] == 'switch')
|
||||
{
|
||||
$m[9] = '';
|
||||
}
|
||||
elseif($mm[1] == 'foreach')
|
||||
{
|
||||
$var = preg_replace('/^\s*\(\s*(.+?) .*$/', '$1', $m[8]);
|
||||
$precheck = "if({$var})";
|
||||
}
|
||||
return '<?php ' . self::_replaceVar($precheck . $m[7] . $m[8]) . '{ ?>' . $m[9];
|
||||
}
|
||||
if($mm[2])
|
||||
{
|
||||
return "<?php }{$m[7]}" . self::_replaceVar($m[8]) . "{ ?>" . $m[9];
|
||||
}
|
||||
if($mm[4])
|
||||
{
|
||||
return "<?php " . ($mm[3] ? 'break;' : '') . "{$m[7]} " . trim($m[8], '()') . ": ?>" . $m[9];
|
||||
}
|
||||
if($mm[5])
|
||||
{
|
||||
return "<?php break; ?>";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return $m[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply escape option to an expression.
|
||||
*/
|
||||
private function _applyEscapeOption($str, $escape_option)
|
||||
{
|
||||
if (preg_match('/^\$[\\\\\w\[\]\'":>-]+$/i', $str))
|
||||
{
|
||||
$str = preg_match('/^\$lang->/', $str) ? $str : "$str ?? ''";
|
||||
}
|
||||
|
||||
switch($escape_option)
|
||||
{
|
||||
case 'escape':
|
||||
return "htmlspecialchars({$str}, ENT_QUOTES, 'UTF-8', true)";
|
||||
case 'noescape':
|
||||
return "{$str}";
|
||||
case 'autoescape':
|
||||
return "htmlspecialchars({$str}, ENT_QUOTES, 'UTF-8', false)";
|
||||
case 'autolang':
|
||||
return "(preg_match('/^\\$(?:user_)?lang->[a-zA-Z0-9\_]+$/', {$str}) ? ({$str}) : htmlspecialchars({$str}, ENT_QUOTES, 'UTF-8', false))";
|
||||
case 'auto':
|
||||
default:
|
||||
return "(\$this->config->autoescape ? htmlspecialchars({$str}, ENT_QUOTES, 'UTF-8', false) : ({$str}))";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* change relative path
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
private function _getRelativeDir($path)
|
||||
{
|
||||
$_path = $path;
|
||||
|
||||
$fileDir = $this->template->absolute_dirname;
|
||||
if($path[0] != '/')
|
||||
{
|
||||
$path = strtr(realpath($fileDir . '/' . $path), '\\', '/');
|
||||
}
|
||||
|
||||
// for backward compatibility
|
||||
if(!$path)
|
||||
{
|
||||
$dirs = explode('/', $fileDir);
|
||||
$paths = explode('/', $_path);
|
||||
$idx = array_search($paths[0], $dirs);
|
||||
|
||||
if($idx !== false)
|
||||
{
|
||||
while($dirs[$idx] && $dirs[$idx] === $paths[0])
|
||||
{
|
||||
array_splice($dirs, $idx, 1);
|
||||
array_shift($paths);
|
||||
}
|
||||
$path = strtr(realpath($fileDir . '/' . implode('/', $paths)), '\\', '/');
|
||||
}
|
||||
}
|
||||
|
||||
$path = preg_replace('/^' . preg_quote(\RX_BASEDIR, '/') . '/', '', $path);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string seems to contain a variable.
|
||||
*
|
||||
* @param string $str
|
||||
* @return bool
|
||||
*/
|
||||
private static function _isVar($str)
|
||||
{
|
||||
return preg_match('@(?<!::|\\\\|(?<!eval\()\')\$([a-z_][a-z0-9_]*)@i', $str) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace PHP variables of $ character
|
||||
*
|
||||
* @param string $php
|
||||
* @return string
|
||||
*/
|
||||
private static function _replaceVar($php)
|
||||
{
|
||||
if(!strlen($php))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Replace variables that need to be enclosed in curly braces, using temporary entities to prevent double-replacement.
|
||||
$php = preg_replace_callback('@(?<!\$__Context)->\$([a-z_][a-z0-9_]*)@i', function($matches) {
|
||||
return '->' . self::_getTempEntityForChar('{') . '$__Context->' . $matches[1] . self::_getTempEntityForChar('}');
|
||||
}, $php);
|
||||
|
||||
// Replace all other variables with Context attributes.
|
||||
$php = preg_replace_callback('@(?<!::|\\\\|\$__Context->|(?<!eval\()\')\$([a-z_][a-z0-9_]*)@i', function($matches) {
|
||||
if (preg_match('/^(?:GLOBALS|_SERVER|_COOKIE|_ENV|_GET|_POST|_REQUEST|_SESSION|__Context|this)$/', $matches[1]))
|
||||
{
|
||||
return '$' . $matches[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return '$__Context->' . $matches[1];
|
||||
}
|
||||
}, $php);
|
||||
|
||||
return $php;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace temporary entities to curly braces.
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
private static function _replaceTempEntities($str)
|
||||
{
|
||||
return strtr($str, [
|
||||
'{' => '{',
|
||||
'}' => '}',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the temporary entity for a character.
|
||||
*
|
||||
* @param string $char
|
||||
* @return string
|
||||
*/
|
||||
private static function _getTempEntityForChar($char)
|
||||
{
|
||||
return '&#x' . strtoupper(bin2hex($char)) . ';';
|
||||
}
|
||||
}
|
||||
1091
common/framework/parsers/template/TemplateParser_v2.php
Normal file
1091
common/framework/parsers/template/TemplateParser_v2.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@
|
|||
location ~ ^/rhymix/modules/editor/(skins|styles)/.+\.html$ {
|
||||
# pass
|
||||
}
|
||||
location ~ ^/rhymix/(addons|common/tpl|files/ruleset|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml)$ {
|
||||
location ~ ^/rhymix/(addons|common/tpl|files/ruleset|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml|blade\.php)$ {
|
||||
return 403;
|
||||
}
|
||||
location ~ ^/rhymix/files/(attach|config|cache)/.+\.(ph(p|t|ar)?[0-9]?|p?html?|cgi|pl|exe|[aj]spx?|inc|bak)$ {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
location ~ ^/modules/editor/(skins|styles)/.+\.html$ {
|
||||
# pass
|
||||
}
|
||||
location ~ ^/(addons|common/tpl|files/ruleset|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml)$ {
|
||||
location ~ ^/(addons|common/tpl|files/ruleset|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml|blade\.php)$ {
|
||||
return 403;
|
||||
}
|
||||
location ~ ^/files/(attach|config|cache)/.+\.(ph(p|t|ar)?[0-9]?|p?html?|cgi|pl|exe|[aj]spx?|inc|bak)$ {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<script>
|
||||
top.location.reload();
|
||||
</script>
|
||||
top.location.reload();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -361,6 +361,8 @@ class Cleanup extends Base
|
|||
'modules/spamfilter/spamfilter.lib.php' => 'deleted',
|
||||
'modules/spamfilter/ruleset/' => 'deleted',
|
||||
'phpDoc/' => 'deleted:xe',
|
||||
'tests/unit/classes/template/' => 'deleted',
|
||||
'tests/unit/classes/TemplateHandlerTest.php' => 'deleted',
|
||||
'tools/dbxml_validator/' => 'deleted:xe',
|
||||
'tools/korea_ip_ranges/' => 'deleted',
|
||||
'tools/phpDoc/' => 'deleted',
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ class PageAdminView extends Page
|
|||
$security->encodeHTML('widget_list..title','module_info.mid');
|
||||
|
||||
// Load admin resources
|
||||
$oTemplate = TemplateHandler::getInstance();
|
||||
$oTemplate = Rhymix\Framework\Template::getInstance();
|
||||
$oTemplate->compile('modules/admin/tpl', '_admin_common.html');
|
||||
|
||||
// Set a template file
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ class PageMobile extends PageView
|
|||
{
|
||||
function _getArticleContent()
|
||||
{
|
||||
$oTemplate = &TemplateHandler::getInstance();
|
||||
|
||||
$oDocument = DocumentModel::getDocument(0);
|
||||
if($this->module_info->mdocument_srl ?? 0)
|
||||
{
|
||||
|
|
@ -22,6 +20,7 @@ class PageMobile extends PageView
|
|||
}
|
||||
Context::set('oDocument', $oDocument);
|
||||
|
||||
$oTemplate = Rhymix\Framework\Template::getInstance();
|
||||
$template_path = $this->getTemplatePath();
|
||||
if (preg_match('!/skins/!', $template_path))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -141,8 +141,6 @@ class PageView extends Page
|
|||
|
||||
function _getArticleContent()
|
||||
{
|
||||
$oTemplate = &TemplateHandler::getInstance();
|
||||
|
||||
$oDocument = DocumentModel::getDocument(0);
|
||||
if($this->module_info->document_srl ?? 0)
|
||||
{
|
||||
|
|
@ -152,6 +150,7 @@ class PageView extends Page
|
|||
}
|
||||
Context::set('oDocument', $oDocument);
|
||||
|
||||
$oTemplate = Rhymix\Framework\Template::getInstance();
|
||||
$page_content = $oTemplate->compile($this->getTemplatePath(), 'content');
|
||||
|
||||
return $page_content;
|
||||
|
|
@ -252,7 +251,7 @@ class PageView extends Page
|
|||
if ($this->proc_tpl)
|
||||
{
|
||||
// Store compiled template in a temporary file.
|
||||
$oTemplate = TemplateHandler::getInstance();
|
||||
$oTemplate = Rhymix\Framework\Template::getInstance();
|
||||
$real_target_dir = dirname($real_target_file);
|
||||
$tmp_cache_file = preg_replace('/\.cache\.php$/', '.compiled.php', $cache_file);
|
||||
$content = $oTemplate->compileDirect($real_target_dir . '/', basename($real_target_file));
|
||||
|
|
|
|||
|
|
@ -747,7 +747,7 @@ class WidgetController extends Widget
|
|||
}
|
||||
// Compilation
|
||||
$widgetstyle_path = $oWidgetModel->getWidgetStylePath($widgetStyle);
|
||||
$oTemplate = &TemplateHandler::getInstance();
|
||||
$oTemplate = Rhymix\Framework\Template::getInstance();
|
||||
$tpl = $oTemplate->compile($widgetstyle_path, 'widgetstyle');
|
||||
|
||||
return $tpl;
|
||||
|
|
|
|||
5
tests/_data/template/css/style.scss
Normal file
5
tests/_data/template/css/style.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.foo {
|
||||
.bar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
0
tests/_data/template/empty.html
Normal file
0
tests/_data/template/empty.html
Normal file
1
tests/_data/template/incl/eachtest.html
Normal file
1
tests/_data/template/incl/eachtest.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<div>{$var}</div>
|
||||
1
tests/_data/template/incl/empty.html
Normal file
1
tests/_data/template/incl/empty.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<div>Empty</div>
|
||||
6
tests/_data/template/incl/scopetest1.html
Normal file
6
tests/_data/template/incl/scopetest1.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<config version="2" />
|
||||
<div>{{ $foobar }}</div>
|
||||
<div>{{ $globalonly }}</div>
|
||||
@once
|
||||
@load ('../js/test.js')
|
||||
@endonce
|
||||
5
tests/_data/template/incl/scopetest2.html
Normal file
5
tests/_data/template/incl/scopetest2.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<config version="2" />
|
||||
<div class="self">{{ $foobar }}</div>
|
||||
<div class="incl">
|
||||
<include src="scopetest1.html" vars="['foobar' => 'Included #3']" />
|
||||
</div>
|
||||
3
tests/_data/template/js/test.js
Normal file
3
tests/_data/template/js/test.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
(function($) {
|
||||
// TEST
|
||||
})(jQuery);
|
||||
66
tests/_data/template/v2example.compiled.html
Normal file
66
tests/_data/template/v2example.compiled.html
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php if (!defined("RX_VERSION")) exit(); ?><?php $this->config->version = 2; ?>
|
||||
|
||||
|
||||
<div><?php echo $this->_v2_include('include', '^/common/tpl/refresh.html'); ?></div>
|
||||
<div><?php $this->_v2_loadResource('^/common/js/plugins/ckeditor/'); ?></div>
|
||||
<?php $this->_v2_loadResource('css/style.scss', 'print', '', []); ?>
|
||||
|
||||
<?php
|
||||
$__Context->foo = 'FOOFOO<"FOO">BAR';
|
||||
?>
|
||||
<?php
|
||||
$__Context->bar = ['Rhy', 'miX', 'is', 'da', 'BEST!'];
|
||||
?>
|
||||
|
||||
{{ $foo }}
|
||||
|
||||
|
||||
<form action="<?php echo $this->config->context === 'JS' ? escape_js(\RX_BASEURL) : htmlspecialchars(\RX_BASEURL, \ENT_QUOTES, 'UTF-8', false); ?>" method="post">
|
||||
<input type="hidden" name="_rx_csrf_token" value="<?php echo \Rhymix\Framework\Session::getGenericToken(); ?>" />
|
||||
<input type="text"<?php if (Context::getInstance()->get('foo')): ?> required="required"<?php endif; ?>>
|
||||
<input type="text" value="<?php echo $this->config->context === 'JS' ? escape_js($__Context->bar[0] ?? '') : htmlspecialchars($__Context->bar[0] ?? '', \ENT_QUOTES, 'UTF-8', false); ?>"<?php if ($__Context->bar[3] === 'da'): ?> required="required"<?php endif; ?> />
|
||||
</form>
|
||||
|
||||
<div<?php if (!(isset($__Context->baz))): ?> class="foobar"<?php endif; ?>>
|
||||
<?php if ($__Context->foo || $__Context->bar): ?>
|
||||
<p>Hello <?php if ($__Context->bar): ?><?php echo $__Context->foo ?? ''; ?><?php endif; ?></p>
|
||||
<p><?php echo $this->config->context === 'JS' ? escape_js(implode('|', array_map(function($i) { return strtoupper($i); }, $__Context->bar))) : htmlspecialchars(implode('|', array_map(function($i) { return strtoupper($i); }, $__Context->bar)), \ENT_QUOTES, 'UTF-8', false); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php ob_start(); $__last_fragment_name = 'rhymix'; ?>
|
||||
<?php $__tmp_RANDOM_LOOP_ID = Context::get('bar') ?? []; if($__tmp_RANDOM_LOOP_ID): $__loop_RANDOM_LOOP_ID = $this->_v2_initLoopVar("RANDOM_LOOP_ID", $__tmp_RANDOM_LOOP_ID); foreach ($__tmp_RANDOM_LOOP_ID as $__Context->k => $__Context->val): ?>
|
||||
<div>
|
||||
<?php if (empty($__Context->nosuchvar)): ?>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span <?php if ($__Context->k >= 2): ?>class="<?php echo $this->config->context === 'JS' ? escape_js($__Context->val ?? '') : htmlspecialchars($__Context->val ?? '', \ENT_QUOTES, 'UTF-8', false); ?>"<?php endif; ?>></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php $this->_v2_incrLoopVar($__loop_RANDOM_LOOP_ID); endforeach; $this->_v2_removeLoopVar($__loop_RANDOM_LOOP_ID); unset($__loop_RANDOM_LOOP_ID); else: ?><div>Nothing here...</div><?php endif; ?>
|
||||
<?php $this->_fragments[$__last_fragment_name] = ob_get_flush(); ?>
|
||||
|
||||
<?php (function($__filename, $__vars, $__varname, $__empty = null) { if (!$__vars): $__vars = []; if ($__empty): $__filename = $__empty; $__vars[] = ''; endif; endif; foreach ($__vars as $__var): echo $this->_v2_include("include", $__filename, [(string)$__varname => $__var]); endforeach; })('incl/eachtest', $__Context->bar, 'var'); ?>
|
||||
<?php (function($__filename, $__vars, $__varname, $__empty = null) { if (!$__vars): $__vars = []; if ($__empty): $__filename = $__empty; $__vars[] = ''; endif; endif; foreach ($__vars as $__var): echo $this->_v2_include("include", $__filename, [(string)$__varname => $__var]); endforeach; })('incl/eachtest', [], 'anything', 'incl/empty'); ?>
|
||||
|
||||
<?php if (!\Context::get('m')): ?>
|
||||
<p>The full class name is <?php echo htmlspecialchars(get_class(new Rhymix\Framework\Push), \ENT_QUOTES, 'UTF-8', true); ?>, <?php echo $this->config->context === 'JS' ? escape_js(Rhymix\Framework\Push::class) : htmlspecialchars(Rhymix\Framework\Push::class, \ENT_QUOTES, 'UTF-8', false); ?> really.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="barContainer" data-bar="<?php echo $this->config->context === 'JS' ? json_encode($__Context->bar ?? '', self::$_json_options) : htmlspecialchars(json_encode($__Context->bar ?? '', self::$_json_options), \ENT_QUOTES, 'UTF-8', false); ?>">
|
||||
<span<?php echo $this->_v2_buildAttribute('class', [
|
||||
'a-1',
|
||||
'font-normal' => $__Context->foo,
|
||||
'text-blue' => false,
|
||||
'bg-gray-200' => true
|
||||
]); ?>></span>
|
||||
<span<?php echo $this->_v2_buildAttribute('style', [
|
||||
'border-radius: 0.25rem',
|
||||
'margin: 1rem' => Context::get('bar'),
|
||||
'padding: 2rem' => false,
|
||||
]); ?>></span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript"<?php $this->config->context = "JS"; ?>>
|
||||
const foo = '<?php echo $this->config->context === 'JS' ? escape_js($__Context->foo ?? '') : htmlspecialchars($__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false); ?>';
|
||||
const bar = <?php echo $this->config->context === 'JS' ? json_encode($__Context->bar, self::$_json_options) : htmlspecialchars(json_encode($__Context->bar, self::$_json_options), \ENT_QUOTES, 'UTF-8', false); ?>;
|
||||
<?php $this->config->context = "HTML"; ?></script>
|
||||
62
tests/_data/template/v2example.executed.html
Normal file
62
tests/_data/template/v2example.executed.html
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
|
||||
|
||||
<div><script>
|
||||
top.location.reload();
|
||||
</script>
|
||||
</div>
|
||||
<div></div>
|
||||
|
||||
|
||||
{{ $foo }}
|
||||
|
||||
|
||||
<form action="/rhymix/" method="post">
|
||||
<input type="hidden" name="_rx_csrf_token" value="" />
|
||||
<input type="text" required="required">
|
||||
<input type="text" value="Rhy" required="required" />
|
||||
</form>
|
||||
|
||||
<div class="foobar">
|
||||
<p>Hello FOOFOO<"FOO">BAR</p>
|
||||
<p>RHY|MIX|IS|DA|BEST!</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span ></span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span ></span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span class="is"></span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span class="da"></span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span class="BEST!"></span>
|
||||
</div>
|
||||
|
||||
<div>Rhy</div>
|
||||
<div>miX</div>
|
||||
<div>is</div>
|
||||
<div>da</div>
|
||||
<div>BEST!</div>
|
||||
<div>Empty</div>
|
||||
|
||||
<p>The full class name is Rhymix\Framework\Push, Rhymix\Framework\Push really.</p>
|
||||
|
||||
<div class="barContainer" data-bar="["Rhy","miX","is","da","BEST!"]">
|
||||
<span class="a-1 font-normal bg-gray-200"></span>
|
||||
<span style="border-radius: 0.25rem; margin: 1rem"></span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
const foo = 'FOOFOO\u003C\u0022FOO\u0022\u003EBAR';
|
||||
const bar = ["Rhy","miX","is","da","BEST!"];
|
||||
</script>
|
||||
21
tests/_data/template/v2example.fragment.html
Normal file
21
tests/_data/template/v2example.fragment.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span ></span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span ></span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span class="is"></span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span class="da"></span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span class="BEST!"></span>
|
||||
</div>
|
||||
66
tests/_data/template/v2example.html
Normal file
66
tests/_data/template/v2example.html
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<config version="2" />
|
||||
|
||||
@use('Rhymix\Framework\Push', 'Push')
|
||||
<div>@include('^/common/tpl/refresh.html')</div>
|
||||
<div>@load('^/common/js/plugins/ckeditor/')</div>
|
||||
<load src="css/style.scss" media="print" />
|
||||
|
||||
<?php
|
||||
$foo = 'FOOFOO<"FOO">BAR';
|
||||
?>
|
||||
@php
|
||||
$bar = ['Rhy', 'miX', 'is', 'da', 'BEST!'];
|
||||
@endphp
|
||||
@verbatim
|
||||
{{ $foo }}
|
||||
@endverbatim
|
||||
|
||||
<form action="{\RX_BASEURL}" method="post">
|
||||
@csrf
|
||||
<input type="text" @required(Context::getInstance()->get('foo'))>
|
||||
<input type="text" value="{$bar[0]}" required|if="$bar[3] === 'da'" />
|
||||
</form>
|
||||
|
||||
<div class="foobar"|unless="isset($baz)">
|
||||
@if ($foo || $bar)
|
||||
<p>Hello @if ($bar){$foo|noescape}@endif</p>
|
||||
<p>{{ implode('|', array_map(function(\$i) { return strtoupper(\$i); }, $bar)) }}</p>
|
||||
@end
|
||||
</div>
|
||||
|
||||
@fragment('rhymix')
|
||||
@forelse (Context::get('bar') as $k => $val)
|
||||
<div>
|
||||
@empty ($nosuchvar)
|
||||
<img src="foo/../bar/rhymix.svg" alt="unit tests are cool" />
|
||||
<span <!--@if($k >= 2)-->class="{$val}"<!--@end-->></span>
|
||||
@endempty
|
||||
</div>
|
||||
@empty<div>Nothing here...</div>@end
|
||||
@endfragment
|
||||
|
||||
@each('incl/eachtest', $bar, 'var')
|
||||
@each('incl/eachtest', [], 'anything', 'incl/empty')
|
||||
|
||||
@desktop
|
||||
<p>The full class name is {get_class(new Push)|escape}, {Push::class} really.</p>
|
||||
@enddesktop
|
||||
|
||||
<div class="barContainer" data-bar="{$bar|json}">
|
||||
<span @class([
|
||||
'a-1',
|
||||
'font-normal' => $foo,
|
||||
'text-blue' => false,
|
||||
'bg-gray-200' => true
|
||||
])></span>
|
||||
<span @style([
|
||||
'border-radius: 0.25rem',
|
||||
'margin: 1rem' => Context::get('bar'),
|
||||
'padding: 2rem' => false,
|
||||
])></span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
const foo = '{{ $foo }}';
|
||||
const bar = @json($bar);
|
||||
</script>
|
||||
179
tests/_data/template/v2loops.executed.html
Normal file
179
tests/_data/template/v2loops.executed.html
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
<h1>Pets</h1>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<span>A red dog</span>
|
||||
<span>current index 0 of 5</span>
|
||||
<span>parent index 0 of 5</span>
|
||||
<span>first: bool(true)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A red cat</span>
|
||||
<span>current index 1 of 5</span>
|
||||
<span>parent index 0 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A red rabbit</span>
|
||||
<span>current index 2 of 5</span>
|
||||
<span>parent index 0 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A red panda</span>
|
||||
<span>current index 3 of 5</span>
|
||||
<span>parent index 0 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A red otter</span>
|
||||
<span>current index 4 of 5</span>
|
||||
<span>parent index 0 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(true)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A blue dog</span>
|
||||
<span>current index 0 of 5</span>
|
||||
<span>parent index 1 of 5</span>
|
||||
<span>first: bool(true)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A blue cat</span>
|
||||
<span>current index 1 of 5</span>
|
||||
<span>parent index 1 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A blue rabbit</span>
|
||||
<span>current index 2 of 5</span>
|
||||
<span>parent index 1 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A blue panda</span>
|
||||
<span>current index 3 of 5</span>
|
||||
<span>parent index 1 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A blue otter</span>
|
||||
<span>current index 4 of 5</span>
|
||||
<span>parent index 1 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(true)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A yellow dog</span>
|
||||
<span>current index 0 of 5</span>
|
||||
<span>parent index 2 of 5</span>
|
||||
<span>first: bool(true)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A yellow cat</span>
|
||||
<span>current index 1 of 5</span>
|
||||
<span>parent index 2 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A yellow rabbit</span>
|
||||
<span>current index 2 of 5</span>
|
||||
<span>parent index 2 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A yellow panda</span>
|
||||
<span>current index 3 of 5</span>
|
||||
<span>parent index 2 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A yellow otter</span>
|
||||
<span>current index 4 of 5</span>
|
||||
<span>parent index 2 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(true)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A black dog</span>
|
||||
<span>current index 0 of 5</span>
|
||||
<span>parent index 3 of 5</span>
|
||||
<span>first: bool(true)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A black cat</span>
|
||||
<span>current index 1 of 5</span>
|
||||
<span>parent index 3 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A black rabbit</span>
|
||||
<span>current index 2 of 5</span>
|
||||
<span>parent index 3 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A black panda</span>
|
||||
<span>current index 3 of 5</span>
|
||||
<span>parent index 3 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A black otter</span>
|
||||
<span>current index 4 of 5</span>
|
||||
<span>parent index 3 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(true)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A white dog</span>
|
||||
<span>current index 0 of 5</span>
|
||||
<span>parent index 4 of 5</span>
|
||||
<span>first: bool(true)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A white cat</span>
|
||||
<span>current index 1 of 5</span>
|
||||
<span>parent index 4 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A white rabbit</span>
|
||||
<span>current index 2 of 5</span>
|
||||
<span>parent index 4 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A white panda</span>
|
||||
<span>current index 3 of 5</span>
|
||||
<span>parent index 4 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(false)</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>A white otter</span>
|
||||
<span>current index 4 of 5</span>
|
||||
<span>parent index 4 of 5</span>
|
||||
<span>first: bool(false)</span>
|
||||
<span>last: bool(true)</span>
|
||||
</li>
|
||||
</ul>
|
||||
22
tests/_data/template/v2loops.html
Normal file
22
tests/_data/template/v2loops.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
@version(2)
|
||||
|
||||
@php
|
||||
$foo = ['red', 'blue', 'yellow', 'black', 'white'];
|
||||
$bar = ['dog', 'cat', 'rabbit', 'panda', 'otter'];
|
||||
@endphp
|
||||
|
||||
<h1>Pets</h1>
|
||||
|
||||
<ul>
|
||||
@foreach ($foo as $color)
|
||||
@foreach ($bar as $animal)
|
||||
<li>
|
||||
<span>A {{ $color }} {{ $animal }}</span>
|
||||
<span>current index {{ $loop->index }} of {{ $loop->count }}</span>
|
||||
<span>parent index {{ $loop->parent->index }} of {{ $loop->parent->count }}</span>
|
||||
<span>first: @dump($loop->first)</span>
|
||||
<span>last: @dump($loop->last)</span>
|
||||
</li>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</ul>
|
||||
7
tests/_data/template/v2pushstack.executed.html
Normal file
7
tests/_data/template/v2pushstack.executed.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
<ul>
|
||||
<li>Rhymix</li>
|
||||
<li>XE</li>
|
||||
<li>WordPress</li>
|
||||
<li>Drupal</li>
|
||||
</ul>
|
||||
35
tests/_data/template/v2pushstack.html
Normal file
35
tests/_data/template/v2pushstack.html
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
@version(2)
|
||||
|
||||
<?php $foo = 42; ?>
|
||||
|
||||
@pushOnce('cms')
|
||||
<li>XE</li>
|
||||
@endpushOnce
|
||||
|
||||
@pushonce('cms')
|
||||
<li>XE</li>
|
||||
@endPushOnce
|
||||
|
||||
@push('cms')
|
||||
<li>WordPress</li>
|
||||
@end
|
||||
|
||||
@pushIf($foo, 'cms')
|
||||
<li>Drupal</li>
|
||||
@endPushIf
|
||||
|
||||
@prependOnce('cms')
|
||||
<li>Rhymix</li>
|
||||
@endPrependOnce
|
||||
|
||||
@prependonce('cms')
|
||||
<li>Rhymix</li>
|
||||
@endprependonce
|
||||
|
||||
@prependIf(!$foo, 'cms')
|
||||
<li>Joomla</li>
|
||||
@end
|
||||
|
||||
<ul>
|
||||
@stack('cms')
|
||||
</ul>
|
||||
11
tests/_data/template/v2validation.executed.html
Normal file
11
tests/_data/template/v2validation.executed.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
<div class="alert">
|
||||
<p>You have an error!</p>
|
||||
<p><strong>[Ref. #2]</strong></p>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
<p>You have an error!</p>
|
||||
<p><strong>[Ref. #4]</strong></p>
|
||||
</div>
|
||||
|
||||
41
tests/_data/template/v2validation.html
Normal file
41
tests/_data/template/v2validation.html
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
@version(2)
|
||||
|
||||
<!--// No error yet -->
|
||||
@error('foo/bar/baz/1')
|
||||
<div class="alert">
|
||||
<p>{$XE_VALIDATOR_MESSAGE}</p>
|
||||
<p><strong>[Ref. #1]</strong></p>
|
||||
</div>
|
||||
@enderror
|
||||
|
||||
<!--// We set error ID and message now -->
|
||||
{@ $XE_VALIDATOR_ID = 'foo/bar/baz/1'}
|
||||
{@ $XE_VALIDATOR_MESSAGE = 'You have an error!'}
|
||||
|
||||
<!--// This should work -->
|
||||
@error('foo/bar/baz/3', 'foo/bar/baz/2', 'foo/bar/baz/1')
|
||||
<div class="alert">
|
||||
<p>{$XE_VALIDATOR_MESSAGE}</p>
|
||||
<p><strong>[Ref. #2]</strong></p>
|
||||
</div>
|
||||
@enderror
|
||||
|
||||
<!--// Wrong error ID -->
|
||||
@error('foo/bar/baz/6', 'foo/bar/baz/5', 'foo/bar/baz/4')
|
||||
<div class="alert">
|
||||
<p>{$XE_VALIDATOR_MESSAGE}</p>
|
||||
<p><strong>[Ref. #3]</strong></p>
|
||||
</div>
|
||||
@enderror
|
||||
|
||||
<!--// Check for any error, this should also work -->
|
||||
@error
|
||||
<div class="alert">
|
||||
<p>{$XE_VALIDATOR_MESSAGE}</p>
|
||||
<p><strong>[Ref. #4]</strong></p>
|
||||
</div>
|
||||
@enderror
|
||||
|
||||
<!--// Cleanup -->
|
||||
{@ $XE_VALIDATOR_ID = null}
|
||||
{@ $XE_VALIDATOR_MESSAGE = null}
|
||||
18
tests/_data/template/v2varscope.executed.html
Normal file
18
tests/_data/template/v2varscope.executed.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
<div class="global">
|
||||
<div>Rhymix Template</div>
|
||||
<div>Context Variable</div>
|
||||
</div>
|
||||
|
||||
<div class="test1">
|
||||
<div>Included #1</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="test2">
|
||||
<div class="self">Included #2</div>
|
||||
<div class="incl">
|
||||
<div>Included #3</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
18
tests/_data/template/v2varscope.html
Normal file
18
tests/_data/template/v2varscope.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
@version(2)
|
||||
|
||||
<?php
|
||||
$foobar = 'Rhymix Template';
|
||||
$globalonly = 'Context Variable';
|
||||
?>
|
||||
|
||||
<div class="global">
|
||||
@include ('incl/scopetest1.html')
|
||||
</div>
|
||||
|
||||
<div class="test1">
|
||||
@include ('incl/scopetest1.html', ['foobar' => 'Included #1'])
|
||||
</div>
|
||||
|
||||
<div class="test2">
|
||||
<include src="incl/scopetest2.html" vars="['foobar' => 'Included #2']" />
|
||||
</div>
|
||||
67
tests/unit/framework/TemplateTest.php
Normal file
67
tests/unit/framework/TemplateTest.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
class TemplateTest extends \Codeception\Test\Unit
|
||||
{
|
||||
public function _before()
|
||||
{
|
||||
Context::init();
|
||||
}
|
||||
|
||||
public function testIsRelativePath()
|
||||
{
|
||||
$tmpl = new \Rhymix\Framework\Template('./tests/_data/template', 'empty.html');
|
||||
|
||||
$this->assertTrue($tmpl->isRelativePath('foo.html'));
|
||||
$this->assertTrue($tmpl->isRelativePath('foo/bar.html'));
|
||||
$this->assertTrue($tmpl->isRelativePath('../foo/bar.html'));
|
||||
$this->assertTrue($tmpl->isRelativePath('foo/../bar.html'));
|
||||
$this->assertTrue($tmpl->isRelativePath('^/foo/../bar.html'));
|
||||
$this->assertFalse($tmpl->isRelativePath('/foo/bar.html'));
|
||||
$this->assertFalse($tmpl->isRelativePath('https://foo.com/bar.html'));
|
||||
$this->assertFalse($tmpl->isRelativePath('file:///C:/foo/bar.html'));
|
||||
$this->assertFalse($tmpl->isRelativePath('data:image/png;base64,AAAAAAAAAAA="'));
|
||||
$this->assertFalse($tmpl->isRelativePath('{$foo}'));
|
||||
}
|
||||
|
||||
public function testConvertPath()
|
||||
{
|
||||
$tmpl = new \Rhymix\Framework\Template('./tests/_data/template', 'empty.html');
|
||||
|
||||
$source = 'foo.html';
|
||||
$target = 'tests/_data/template/foo.html';
|
||||
$this->assertEquals($target, $tmpl->convertPath($source));
|
||||
|
||||
$source = 'foo/bar.js';
|
||||
$target = 'tests/_data/template/foo/bar.js';
|
||||
$this->assertEquals($target, $tmpl->convertPath($source));
|
||||
|
||||
$source = '../foo.scss';
|
||||
$target = 'tests/_data/foo.scss';
|
||||
$this->assertEquals($target, $tmpl->convertPath($source));
|
||||
|
||||
$source = '../../_output/foo/../bar.jpg';
|
||||
$target = 'tests/_output/bar.jpg';
|
||||
$this->assertEquals($target, $tmpl->convertPath($source));
|
||||
|
||||
$source = '/foo/bar.blade.php';
|
||||
$target = 'tests/_data/template/foo/bar.blade.php';
|
||||
$this->assertEquals($target, $tmpl->convertPath($source));
|
||||
|
||||
$source = '^/foo/bar.gif';
|
||||
$target = '/rhymix/foo/bar.gif';
|
||||
$this->assertEquals($target, $tmpl->convertPath($source));
|
||||
}
|
||||
|
||||
public function testNormalizePath()
|
||||
{
|
||||
$tmpl = new \Rhymix\Framework\Template('./tests/_data/template', 'empty.html');
|
||||
|
||||
$source = '/rhymix/foo/bar//../hello/world\\..';
|
||||
$target = '/rhymix/foo/hello';
|
||||
$this->assertEquals($target, $tmpl->normalizePath($source));
|
||||
|
||||
$source = '../foo\\bar/../baz/';
|
||||
$target = '../foo/baz/';
|
||||
$this->assertEquals($target, $tmpl->normalizePath($source));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
class TemplateHandlerTest extends \Codeception\TestCase\Test
|
||||
class TemplateParserV1Test extends \Codeception\Test\Unit
|
||||
{
|
||||
private $baseurl;
|
||||
private $prefix = '<?php if(!defined("__XE__"))exit;';
|
||||
private $prefix = '<?php if (!defined("RX_VERSION")) exit();';
|
||||
|
||||
public function _before()
|
||||
{
|
||||
$this->baseurl = '/' . basename(dirname(dirname(dirname(__DIR__)))) . '/';
|
||||
$this->baseurl = '/' . basename(dirname(dirname(dirname(dirname(__DIR__))))) . '/';
|
||||
}
|
||||
|
||||
public function testParse()
|
||||
|
|
@ -101,42 +101,42 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
// #include
|
||||
array(
|
||||
'<dummy /><!--#include("sample.html")--><div>This is another dummy</div>',
|
||||
'?><dummy /><?php $__tpl=TemplateHandler::getInstance();echo $__tpl->compile(\'tests/unit/classes/template\',\'sample.html\') ?><div>This is another dummy</div>'
|
||||
'?><dummy /><?php $__tpl=TemplateHandler::getInstance();echo $__tpl->compile(\'tests/_data/template\',\'sample.html\') ?><div>This is another dummy</div>'
|
||||
),
|
||||
// <include target="file">
|
||||
array(
|
||||
'<dummy /><include target="../sample.html" /><div>This is another dummy</div>',
|
||||
'?><dummy /><?php $__tpl=TemplateHandler::getInstance();echo $__tpl->compile(\'tests/unit/classes\',\'sample.html\') ?><div>This is another dummy</div>'
|
||||
'?><dummy /><?php $__tpl=TemplateHandler::getInstance();echo $__tpl->compile(\'tests/_data\',\'sample.html\') ?><div>This is another dummy</div>'
|
||||
),
|
||||
// <load target="../../modules/page/lang/lang.xml">
|
||||
array(
|
||||
'<dummy /><load target="../../../../modules/page/lang/lang.xml" /><dummy />',
|
||||
'<dummy /><load target="../../../modules/page/lang/lang.xml" /><dummy />',
|
||||
'?><dummy /><?php Context::loadLang(\'modules/page/lang\'); ?><dummy />'
|
||||
),
|
||||
// <load target="style.css">
|
||||
array(
|
||||
'<dummy /><load target="css/style.css" /><dummy />',
|
||||
'?><dummy /><!--#Meta:tests/unit/classes/template/css/style.css--><?php Context::loadFile([\'tests/unit/classes/template/css/style.css\', \'\', \'\', \'\', []]); ?><dummy />'
|
||||
'?><dummy /><!--#Meta:tests/_data/template/css/style.css--><?php Context::loadFile([\'tests/_data/template/css/style.css\', \'\', \'\', \'\', []]); ?><dummy />'
|
||||
),
|
||||
// <unload target="style.css">
|
||||
array(
|
||||
'<dummy /><unload target="css/style.css" /><dummy />',
|
||||
'?><dummy /><?php Context::unloadFile(\'tests/unit/classes/template/css/style.css\', \'\', \'\'); ?><dummy />'
|
||||
'?><dummy /><?php Context::unloadFile(\'tests/_data/template/css/style.css\', \'\', \'\'); ?><dummy />'
|
||||
),
|
||||
// <!--%import("../../modules/page/tpl/filter/insert_config.xml")-->
|
||||
array(
|
||||
'<dummy /><!--%import("../../../../modules/page/tpl/filter/insert_config.xml")--><dummy />',
|
||||
'<dummy /><!--%import("../../../modules/page/tpl/filter/insert_config.xml")--><dummy />',
|
||||
'?><dummy /><?php require_once(\'./classes/xml/XmlJsFilter.class.php\');$__xmlFilter=new XmlJsFilter(\'modules/page/tpl/filter\',\'insert_config.xml\');$__xmlFilter->compile(); ?><dummy />'
|
||||
),
|
||||
// <!--%import("../script.js",type="body")-->
|
||||
array(
|
||||
'<dummy /><!--%import("../script.js",type="body")--><dummy />',
|
||||
'?><dummy /><!--#Meta:tests/unit/classes/script.js--><?php Context::loadFile([\'tests/unit/classes/script.js\', \'body\', \'\', \'\']); ?><dummy />'
|
||||
'?><dummy /><!--#Meta:tests/_data/script.js--><?php Context::loadFile([\'tests/_data/script.js\', \'body\', \'\', \'\']); ?><dummy />'
|
||||
),
|
||||
// <!--%unload("../script.js",type="body")-->
|
||||
array(
|
||||
'<dummy /><!--%unload("../script.js",type="body")--><dummy />',
|
||||
'?><dummy /><?php Context::unloadFile(\'tests/unit/classes/script.js\', \'\'); ?><dummy />'
|
||||
'?><dummy /><?php Context::unloadFile(\'tests/_data/script.js\', \'\'); ?><dummy />'
|
||||
),
|
||||
// comment
|
||||
array(
|
||||
|
|
@ -151,11 +151,11 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
// relative path1
|
||||
array(
|
||||
'<img src="http://naver.com/naver.gif"><input type="image" src="../local.gif" />',
|
||||
'?><img src="http://naver.com/naver.gif"><input type="image" src="' . $this->baseurl . 'tests/unit/classes/local.gif" />'
|
||||
'?><img src="http://naver.com/naver.gif"><input type="image" src="' . $this->baseurl . 'tests/_data/local.gif" />'
|
||||
),
|
||||
// relative path2
|
||||
array(
|
||||
'<img src="http://naver.com/naver.gif"><input type="image" src="../../../dir/local.gif" />',
|
||||
'<img src="http://naver.com/naver.gif"><input type="image" src="../../dir/local.gif" />',
|
||||
'?><img src="http://naver.com/naver.gif"><input type="image" src="' . $this->baseurl . 'tests/dir/local.gif" />'
|
||||
),
|
||||
// error case
|
||||
|
|
@ -211,12 +211,12 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
// issue 512 - ignores <marquee>
|
||||
array(
|
||||
'<div class="topimgContex"><marquee direction="up" scrollamount="1" height="130" loop="infinity" behavior="lscro">{$lang->sl_show_topimgtext}</marquee></div>',
|
||||
'?><div class="topimgContex"><marquee direction="up" scrollamount="1" height="130" loop="infinity" behavior="lscro"><?php echo $lang->sl_show_topimgtext ?></marquee></div>'
|
||||
'?><div class="topimgContex"><marquee direction="up" scrollamount="1" height="130" loop="infinity" behavior="lscro"><?php echo $__Context->lang->sl_show_topimgtext ?? \'\' ?></marquee></div>'
|
||||
),
|
||||
// issue 584
|
||||
array(
|
||||
'<img cond="$oBodex->display_extra_images[\'mobile\'] && $arr_extra && $arr_extra->bodex->mobile" src="./images/common/mobile.gif" title="mobile" alt="mobile" />',
|
||||
'if($__Context->oBodex->display_extra_images[\'mobile\'] && $__Context->arr_extra && $__Context->arr_extra->bodex->mobile){ ?><img src="' . $this->baseurl . 'tests/unit/classes/template/images/common/mobile.gif" title="mobile" alt="mobile" /><?php } ?>'
|
||||
'if($__Context->oBodex->display_extra_images[\'mobile\'] && $__Context->arr_extra && $__Context->arr_extra->bodex->mobile){ ?><img src="' . $this->baseurl . 'tests/_data/template/images/common/mobile.gif" title="mobile" alt="mobile" /><?php } ?>'
|
||||
),
|
||||
// issue 831
|
||||
array(
|
||||
|
|
@ -226,7 +226,7 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
// issue 746
|
||||
array(
|
||||
'<img src="../whatever/img.png" />',
|
||||
'?><img src="' . $this->baseurl . 'tests/unit/classes/whatever/img.png" />'
|
||||
'?><img src="' . $this->baseurl . 'tests/_data/whatever/img.png" />'
|
||||
),
|
||||
// issue 696
|
||||
array(
|
||||
|
|
@ -236,35 +236,35 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
// https://github.com/xpressengine/xe-core/issues/1510
|
||||
array(
|
||||
'<img cond="$foo->bar" src="../common/mobile.gif" />',
|
||||
'if($__Context->foo->bar ?? false){ ?><img src="' . $this->baseurl . 'tests/unit/classes/common/mobile.gif" /><?php } ?>'
|
||||
'if($__Context->foo->bar ?? false){ ?><img src="' . $this->baseurl . 'tests/_data/common/mobile.gif" /><?php } ?>'
|
||||
),
|
||||
// https://github.com/xpressengine/xe-core/issues/1510
|
||||
array(
|
||||
'<img cond="$foo->bar > 100" alt="a!@#$%^&*()_-=[]{}?/" src="../common/mobile.gif" />',
|
||||
'if($__Context->foo->bar > 100){ ?><img alt="a!@#$%^&*()_-=[]{}?/" src="' . $this->baseurl . 'tests/unit/classes/common/mobile.gif" /><?php } ?>'
|
||||
'if($__Context->foo->bar > 100){ ?><img alt="a!@#$%^&*()_-=[]{}?/" src="' . $this->baseurl . 'tests/_data/common/mobile.gif" /><?php } ?>'
|
||||
),
|
||||
// https://github.com/xpressengine/xe-core/issues/1510
|
||||
array(
|
||||
'<img src="../common/mobile.gif" cond="$foo->bar" />',
|
||||
'if($__Context->foo->bar ?? false){ ?><img src="' . $this->baseurl . 'tests/unit/classes/common/mobile.gif" /><?php } ?>'
|
||||
'if($__Context->foo->bar ?? false){ ?><img src="' . $this->baseurl . 'tests/_data/common/mobile.gif" /><?php } ?>'
|
||||
),
|
||||
// https://github.com/xpressengine/xe-core/issues/1510
|
||||
array(
|
||||
'<img class="tmp_class" cond="!$module_info->title" src="../img/common/blank.gif" />',
|
||||
'if(!$__Context->module_info->title){ ?><img class="tmp_class" src="' . $this->baseurl . 'tests/unit/classes/img/common/blank.gif" /><?php } ?>'
|
||||
'if(!$__Context->module_info->title){ ?><img class="tmp_class" src="' . $this->baseurl . 'tests/_data/img/common/blank.gif" /><?php } ?>'
|
||||
),
|
||||
// https://github.com/xpressengine/xe-core/issues/1510
|
||||
array(
|
||||
'<img cond="$mi->title" class="tmp_class"|cond="$mi->use" src="../img/common/blank.gif" />',
|
||||
'if($__Context->mi->title ?? false){ ?><img<?php if($__Context->mi->use){ ?> class="tmp_class"<?php } ?> src="' . $this->baseurl . 'tests/unit/classes/img/common/blank.gif" /><?php } ?>'
|
||||
'if($__Context->mi->title ?? false){ ?><img<?php if($__Context->mi->use){ ?> class="tmp_class"<?php } ?> src="' . $this->baseurl . 'tests/_data/img/common/blank.gif" /><?php } ?>'
|
||||
),
|
||||
array(
|
||||
'<input foo="bar" /> <img cond="$foo->bar" alt="alt" src="../common/mobile.gif" />',
|
||||
'?><input foo="bar" /> <?php if($__Context->foo->bar ?? false){ ?><img alt="alt" src="' . $this->baseurl . 'tests/unit/classes/common/mobile.gif" /><?php } ?>'
|
||||
'?><input foo="bar" /> <?php if($__Context->foo->bar ?? false){ ?><img alt="alt" src="' . $this->baseurl . 'tests/_data/common/mobile.gif" /><?php } ?>'
|
||||
),
|
||||
array(
|
||||
'<input foo="bar" />' . "\r\n" . '<input foo="bar" /> <img cond="$foo->bar" alt="alt" src="../common/mobile.gif" />',
|
||||
'?><input foo="bar" />' . "\n" . '<input foo="bar" /> <?php if($__Context->foo->bar ?? false){ ?><img alt="alt" src="' . $this->baseurl . 'tests/unit/classes/common/mobile.gif" /><?php } ?>'
|
||||
'?><input foo="bar" />' . "\n" . '<input foo="bar" /> <?php if($__Context->foo->bar ?? false){ ?><img alt="alt" src="' . $this->baseurl . 'tests/_data/common/mobile.gif" /><?php } ?>'
|
||||
),
|
||||
array(
|
||||
'asf <img src="{$foo->bar}" />',
|
||||
|
|
@ -272,11 +272,11 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
),
|
||||
array(
|
||||
'<img alt="" '.PHP_EOL.' src="../whatever/img.png" />',
|
||||
'?><img alt="" '.PHP_EOL.' src="' . $this->baseurl . 'tests/unit/classes/whatever/img.png" />'
|
||||
'?><img alt="" '.PHP_EOL.' src="' . $this->baseurl . 'tests/_data/whatever/img.png" />'
|
||||
),
|
||||
array(
|
||||
'<input>asdf src="../img/img.gif" asdf</input> <img alt="src" src="../whatever/img.png" /> <input>asdf src="../img/img.gif" asdf</input>',
|
||||
'?><input>asdf src="../img/img.gif" asdf</input> <img alt="src" src="' . $this->baseurl . 'tests/unit/classes/whatever/img.png" /> <input>asdf src="../img/img.gif" asdf</input>'
|
||||
'?><input>asdf src="../img/img.gif" asdf</input> <img alt="src" src="' . $this->baseurl . 'tests/_data/whatever/img.png" /> <input>asdf src="../img/img.gif" asdf</input>'
|
||||
),
|
||||
array(
|
||||
'<input>asdf src="../img/img.gif" asdf</input>',
|
||||
|
|
@ -289,7 +289,7 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
// srcset (PR #1544)
|
||||
array(
|
||||
'<img src="./img/sticker_banner_960w.png" alt="this is a test image." srcset="https://abc.com/static/img/test@2x.png 2x, http://abc.com/static/test@2.5x.png 2.5x,../img/test@3x.png 3x, ../img/test_960w.png 960w, {$mid}/image.png 480w">',
|
||||
'?><img src="' . $this->baseurl . 'tests/unit/classes/template/img/sticker_banner_960w.png" alt="this is a test image." srcset="https://abc.com/static/img/test@2x.png 2x, http://abc.com/static/test@2.5x.png 2.5x, ' . $this->baseurl . 'tests/unit/classes/img/test@3x.png 3x, ' . $this->baseurl . 'tests/unit/classes/img/test_960w.png 960w, <?php echo $__Context->mid ?? \'\' ?>/image.png 480w">'
|
||||
'?><img src="' . $this->baseurl . 'tests/_data/template/img/sticker_banner_960w.png" alt="this is a test image." srcset="https://abc.com/static/img/test@2x.png 2x, http://abc.com/static/test@2.5x.png 2.5x, ' . $this->baseurl . 'tests/_data/img/test@3x.png 3x, ' . $this->baseurl . 'tests/_data/img/test_960w.png 960w, <?php echo $__Context->mid ?? \'\' ?>/image.png 480w">'
|
||||
),
|
||||
// Rhymix improvements (PR #604)
|
||||
array(
|
||||
|
|
@ -504,8 +504,7 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
|
||||
foreach ($tests as $test)
|
||||
{
|
||||
$tmpl = new TemplateHandlerWrapper;
|
||||
$tmpl->init(__DIR__ . '/template', 'no_file.html');
|
||||
$tmpl = new \Rhymix\Framework\Template('./tests/_data/template', 'empty.html');
|
||||
$result = $tmpl->parse($test[0]);
|
||||
$between = str_starts_with($test[1], '?>') ? '' : ' ';
|
||||
$this->assertEquals($this->prefix . $between . $test[1], $result);
|
||||
|
|
@ -514,36 +513,17 @@ class TemplateHandlerTest extends \Codeception\TestCase\Test
|
|||
|
||||
public function testParseNoContent()
|
||||
{
|
||||
$tmpl = new TemplateHandlerWrapper;
|
||||
$tmpl->init(__DIR__ . '/template', 'no_file.html');
|
||||
$tmpl = new \Rhymix\Framework\Template('./tests/_data/template', 'empty.html');
|
||||
$result = $tmpl->parse(null);
|
||||
|
||||
$this->assertEquals('', $result);
|
||||
}
|
||||
|
||||
public function testCompileDirect()
|
||||
{
|
||||
$tmpl = TemplateHandler::getInstance();
|
||||
$result = $tmpl->compileDirect(__DIR__ . '/template', 'sample.html');
|
||||
$tmpl = new \Rhymix\Framework\Template();
|
||||
$result = $tmpl->compileDirect('./tests/_data/template', 'v1example.html');
|
||||
$result = trim($result);
|
||||
|
||||
$this->assertEquals($this->prefix . ' if($__Context->has_blog ?? false){ ?><a href="http://mygony.com">Taggon\'s blog</a><?php } ?>'.PHP_EOL.'<!--#Meta://external.host/js.js--><?php Context::loadFile([\'//external.host/js.js\', \'\', \'tests\', \'\']); ?>', $result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TemplateHandlerWrapper extends \TemplateHandler {
|
||||
private $inst;
|
||||
|
||||
function __construct() {
|
||||
$this->inst = parent::getInstance();
|
||||
}
|
||||
|
||||
public function init($tpl_path, $tpl_filename, $tpl_file = '') {
|
||||
call_user_func(array($this->inst, 'init'), $tpl_path, $tpl_filename, $tpl_file);
|
||||
}
|
||||
|
||||
public function parse($buff = null) {
|
||||
return call_user_func(array($this->inst, 'parse'), $buff);
|
||||
}
|
||||
}
|
||||
1248
tests/unit/framework/parsers/TemplateParserV2Test.php
Normal file
1248
tests/unit/framework/parsers/TemplateParserV2Test.php
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue