mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-09 11:44:10 +09:00
Merge branch 'rhymix:master' into master
This commit is contained in:
commit
d327bb1926
43 changed files with 588 additions and 221 deletions
|
|
@ -5,7 +5,7 @@
|
|||
"license": "GPL-2.0-or-later",
|
||||
"type": "project",
|
||||
"authors": [
|
||||
{ "name": "Rhymix Developers and Contributors", "email": "devops@rhymix.org" },
|
||||
{ "name": "Poesis Inc. and Contributors", "email": "devops@rhymix.org" },
|
||||
{ "name": "NAVER", "email": "developers@xpressengine.com" }
|
||||
],
|
||||
"config": {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/**
|
||||
* RX_VERSION is the version number of the Rhymix CMS.
|
||||
*/
|
||||
define('RX_VERSION', '2.1.21');
|
||||
define('RX_VERSION', '2.1.23');
|
||||
|
||||
/**
|
||||
* RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch.
|
||||
|
|
|
|||
|
|
@ -945,6 +945,31 @@ class Template
|
|||
return count($args) ? in_array((string)$validator_id, $args, true) : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current visitor is using a mobile device for v2.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _v2_isMobile(): bool
|
||||
{
|
||||
return UA::isMobile() && (config('mobile.tablets') || !UA::isTablet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Contextual escape function for v2.
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
protected function _v2_escape($str): string
|
||||
{
|
||||
switch ($this->config->context)
|
||||
{
|
||||
case 'JS': return escape_js(strval($str));
|
||||
default: return escape(strval($str));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lang shortcut for v2.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -450,14 +450,28 @@ class VariableBase
|
|||
}
|
||||
|
||||
// Check minimum and maximum lengths.
|
||||
$length = is_scalar($value) ? iconv_strlen($value, 'UTF-8') : (is_countable($value) ? count($value) : 1);
|
||||
if (isset($this->minlength) && $this->minlength > 0 && $length < $this->minlength)
|
||||
$length = null;
|
||||
if (isset($this->minlength) && $this->minlength > 0)
|
||||
{
|
||||
throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no less than ' . $this->minlength . ' characters');
|
||||
if ($length === null)
|
||||
{
|
||||
$length = is_scalar($value) ? mb_strlen($value, 'UTF-8') : (is_countable($value) ? count($value) : 1);
|
||||
}
|
||||
if ($length < $this->minlength)
|
||||
{
|
||||
throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no less than ' . $this->minlength . ' characters');
|
||||
}
|
||||
}
|
||||
if (isset($this->maxlength) && $this->maxlength > 0 && $length > $this->maxlength)
|
||||
if (isset($this->maxlength) && $this->maxlength > 0)
|
||||
{
|
||||
throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no more than ' . $this->maxlength . ' characters');
|
||||
if ($length === null)
|
||||
{
|
||||
$length = is_scalar($value) ? mb_strlen($value, 'UTF-8') : (is_countable($value) ? count($value) : 1);
|
||||
}
|
||||
if ($length > $this->maxlength)
|
||||
{
|
||||
throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no more than ' . $this->maxlength . ' characters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ class TemplateParser_v2
|
|||
'cannot' => ['if ($this->_v2_checkCapability(2, %s)):', 'endif;'],
|
||||
'canany' => ['if ($this->_v2_checkCapability(3, %s)):', 'endif;'],
|
||||
'guest' => ['if (!$this->user->isMember()):', 'endif;'],
|
||||
'desktop' => ["if (!\\Context::get('m')):", 'endif;'],
|
||||
'mobile' => ["if (\\Context::get('m')):", 'endif;'],
|
||||
'desktop' => ['if (!$this->_v2_isMobile()):', 'endif;'],
|
||||
'mobile' => ['if ($this->_v2_isMobile()):', 'endif;'],
|
||||
'env' => ['if (!empty($_ENV[%s])):', 'endif;'],
|
||||
'else' => ['else:'],
|
||||
'elseif' => ['elseif (%s):'],
|
||||
|
|
@ -179,20 +179,45 @@ class TemplateParser_v2
|
|||
*/
|
||||
protected function _addContextSwitches(string $content): string
|
||||
{
|
||||
return preg_replace_callback('#(<script\b([^>]*)|</script)#i', function($match) {
|
||||
// Inline styles.
|
||||
$content = preg_replace_callback('#(?<=\s)(style=")([^"]*?)"#i', function($match) {
|
||||
return $match[1] . '<?php $this->config->context = \'CSS\'; ?>' . $match[2] . '<?php $this->config->context = \'HTML\'; ?>"';
|
||||
}, $content);
|
||||
|
||||
// Inline scripts.
|
||||
$content = preg_replace_callback('#(?<=\s)(href="javascript:|on[a-z]+=")([^"]*?)"#i', function($match) {
|
||||
return $match[1] . '<?php $this->config->context = \'JS\'; ?>' . $match[2] . '<?php $this->config->context = \'HTML\'; ?>"';
|
||||
}, $content);
|
||||
|
||||
// <style> tags.
|
||||
$content = preg_replace_callback('#(<style\b([^>]*)|</style)#i', function($match) {
|
||||
if (substr($match[1], 1, 1) === '/')
|
||||
{
|
||||
return '<?php $this->config->context = "HTML"; ?>' . $match[1];
|
||||
return '<?php $this->config->context = \'HTML\'; ?>' . $match[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return $match[1] . '<?php $this->config->context = \'CSS\'; ?>';
|
||||
}
|
||||
}, $content);
|
||||
|
||||
// <script> tags that aren't links.
|
||||
$content = preg_replace_callback('#(<script\b([^>]*)|</script)#i', function($match) {
|
||||
if (substr($match[1], 1, 1) === '/')
|
||||
{
|
||||
return '<?php $this->config->context = \'HTML\'; ?>' . $match[1];
|
||||
}
|
||||
elseif (!str_contains($match[2] ?? '', 'src="'))
|
||||
{
|
||||
return $match[1] . '<?php $this->config->context = "JS"; ?>';
|
||||
return $match[1] . '<?php $this->config->context = \'JS\'; ?>';
|
||||
}
|
||||
else
|
||||
{
|
||||
return $match[0];
|
||||
}
|
||||
}, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -203,7 +228,7 @@ class TemplateParser_v2
|
|||
*/
|
||||
protected static function _removeContextSwitches(string $content): string
|
||||
{
|
||||
return preg_replace('#<\?php \$this->config->context = "[A-Z]+"; \?>#', '', $content);
|
||||
return preg_replace('#<\?php \$this->config->context = \'[A-Z]+\'; \?>#', '', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -235,7 +260,7 @@ class TemplateParser_v2
|
|||
$basepath = \RX_BASEURL . $this->template->relative_dirname;
|
||||
|
||||
// Convert all src and srcset attributes.
|
||||
$regexp = '#(<(?:img|audio|video|script|input|source|link)\s[^>]*)(src|srcset|poster)="([^"]+)"#';
|
||||
$regexp = '#(<(?:img|audio|video|script|input|source|link)\s[^>]*)(?<=\s)(src|srcset|poster)="([^"]+)"#';
|
||||
$content = preg_replace_callback($regexp, function($match) use ($basepath) {
|
||||
if ($match[2] !== 'srcset')
|
||||
{
|
||||
|
|
@ -735,6 +760,7 @@ class TemplateParser_v2
|
|||
* @dd($var, $var, ...)
|
||||
* @stack('name')
|
||||
* @url(['mid' => $mid, 'act' => $act])
|
||||
* @widget('name', $args)
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
|
|
@ -748,7 +774,7 @@ class TemplateParser_v2
|
|||
|
||||
// Insert JSON, lang codes, and dumps.
|
||||
$parentheses = self::_getRegexpForParentheses(2);
|
||||
$content = preg_replace_callback('#(?<!@)@(json|lang|dump|stack|url)\x20?('. $parentheses . ')#', function($match) {
|
||||
$content = preg_replace_callback('#(?<!@)@(json|lang|dump|dd|stack|url|widget)\x20?('. $parentheses . ')#', function($match) {
|
||||
$args = self::_convertVariableScope(substr($match[2], 1, -1));
|
||||
switch ($match[1])
|
||||
{
|
||||
|
|
@ -757,7 +783,7 @@ class TemplateParser_v2
|
|||
'json_encode(%s, self::$_json_options2) : ' .
|
||||
'htmlspecialchars(json_encode(%s, self::$_json_options), \ENT_QUOTES, \'UTF-8\', false); ?>', $args, $args);
|
||||
case 'lang':
|
||||
return sprintf('<?php echo $this->config->context === \'JS\' ? escape_js($this->_v2_lang(%s)) : $this->_v2_lang(%s); ?>', $args, $args);
|
||||
return sprintf('<?php echo $this->config->context === \'HTML\' ? $this->_v2_lang(%s) : $this->_v2_escape($this->_v2_lang(%s)); ?>', $args, $args);
|
||||
case 'dump':
|
||||
return sprintf('<?php ob_start(); var_dump(%s); \$__dump = ob_get_clean(); echo rtrim(\$__dump); ?>', $args);
|
||||
case 'dd':
|
||||
|
|
@ -765,7 +791,9 @@ class TemplateParser_v2
|
|||
case 'stack':
|
||||
return sprintf('<?php echo implode("\n", self::\$_stacks[%s] ?? []) . "\n"; ?>', $args);
|
||||
case 'url':
|
||||
return sprintf('<?php echo $this->config->context === \'JS\' ? escape_js(getNotEncodedUrl(%s)) : getUrl(%s); ?>', $args, $args);
|
||||
return sprintf('<?php echo $this->config->context === \'HTML\' ? getUrl(%s) : $this->_v2_escape(getNotEncodedUrl(%s)); ?>', $args, $args);
|
||||
case 'widget':
|
||||
return sprintf('<?php echo \WidgetController::getInstance()->execute(%s); ?>', $args);
|
||||
default:
|
||||
return $match[0];
|
||||
}
|
||||
|
|
@ -797,6 +825,15 @@ class TemplateParser_v2
|
|||
return $this->_arrangeOutputFilters($match);
|
||||
}, $content);
|
||||
|
||||
// Exclude {single} curly braces in non-HTML contexts.
|
||||
$content = preg_replace_callback('#(<\?php \$this->config->context = \'(?:CSS|JS)\'; \?>)(.*?)(<\?php \$this->config->context = \'HTML\'; \?>)#s', function($match) {
|
||||
$match[2] = preg_replace_callback('#(?<!\{)\{(?!\s)([^{}]+?)\}#', function($m) {
|
||||
$warning = preg_match('#^\$\w#', $m[1]) ? '<?php trigger_error("Template v1 syntax not allowed in CSS/JS context", \E_USER_WARNING); ?>' : '';
|
||||
return '{' . $warning . $m[1] . '}';
|
||||
}, $match[2]);
|
||||
return $match[1] . $match[2] . $match[3];
|
||||
}, $content);
|
||||
|
||||
// Convert {single} curly braces.
|
||||
$content = preg_replace_callback('#(?<!\{)\{(?!\s)([^{}]+?)\}#', [$this, '_arrangeOutputFilters'], $content);
|
||||
|
||||
|
|
@ -943,11 +980,11 @@ class TemplateParser_v2
|
|||
switch($option)
|
||||
{
|
||||
case 'autocontext':
|
||||
return "\$this->config->context === 'JS' ? escape_js({$str2}) : htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
|
||||
return "\$this->config->context === 'HTML' ? htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape({$str2})";
|
||||
case 'autocontext_json':
|
||||
return "\$this->config->context === 'JS' ? {$str2} : htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
|
||||
case 'autocontext_lang':
|
||||
return "\$this->config->context === 'JS' ? escape_js({$str2}) : ({$str})";
|
||||
return "\$this->config->context === 'HTML' ? ({$str}) : \$this->_v2_escape({$str2})";
|
||||
case 'autoescape':
|
||||
return "htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
|
||||
case 'autolang':
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/**
|
||||
* Function library for Rhymix
|
||||
*
|
||||
* Copyright (c) Rhymix Developers and Contributors
|
||||
* Copyright (c) Poesis Inc. and Contributors
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -205,7 +205,7 @@ function escape($str, bool $double_escape = true, bool $except_lang_code = false
|
|||
*/
|
||||
function escape_css(string $str): string
|
||||
{
|
||||
return preg_replace('/[^a-zA-Z0-9_.#\/-]/', '', (string)$str);
|
||||
return preg_replace('/[^a-zA-Z0-9_.,#%\/\'()\x20-]/', '', (string)$str);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -680,7 +680,14 @@ function utf8_mbencode($str): string
|
|||
*/
|
||||
function utf8_normalize_spaces($str, bool $multiline = false): string
|
||||
{
|
||||
return $multiline ? preg_replace('/((?!\x0A)[\pZ\pC])+/u', ' ', (string)$str) : preg_replace('/[\pZ\pC]+/u', ' ', (string)$str);
|
||||
if ($multiline)
|
||||
{
|
||||
return preg_replace(['/((?!\x0A)[\pZ\pC])+/u', '/\x20*\x0A\x20*/'], [' ', "\n"], (string)$str);
|
||||
}
|
||||
else
|
||||
{
|
||||
return preg_replace('/[\pZ\pC]+/u', ' ', (string)$str);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue