diff --git a/common/framework/Template.php b/common/framework/Template.php
index bf9582449..f4b8ca76b 100644
--- a/common/framework/Template.php
+++ b/common/framework/Template.php
@@ -37,6 +37,7 @@ class Template
*/
protected static $_mtime;
protected static $_delay_compile;
+ protected static $_json_options;
/**
* Provided for compatibility with old TemplateHandler.
@@ -76,6 +77,10 @@ class Template
{
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)
@@ -767,7 +772,42 @@ class Template
case 'admin': return $this->user->isAdmin();
case 'manager': return $grant->manager ?? false;
case 'member': return $this->user->isMember();
- default: return $grant->$type ?? false;
+ 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;
}
}
diff --git a/common/framework/parsers/template/TemplateParser_v2.php b/common/framework/parsers/template/TemplateParser_v2.php
index 8591cd1c8..a6b9e5624 100644
--- a/common/framework/parsers/template/TemplateParser_v2.php
+++ b/common/framework/parsers/template/TemplateParser_v2.php
@@ -88,9 +88,13 @@ class TemplateParser_v2
'empty' => ['if (empty(%s)):', 'endif;'],
'admin' => ['if ($this->user->isAdmin()):', 'endif;'],
'auth' => ['if ($this->_v2_checkAuth(%s)):', 'endif;'],
+ 'can' => ['if ($this->_v2_checkCapability(1, %s)):', 'endif;'],
+ '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->m):', 'endif;'],
'mobile' => ['if ($__Context->m):', 'endif;'],
+ 'env' => ['if (!empty($_ENV[%s])):', 'endif;'],
'else' => ['else:'],
'elseif' => ['elseif (%s):'],
'case' => ['case %s:'],
@@ -665,8 +669,8 @@ class TemplateParser_v2
if ($match[1] === 'json')
{
return sprintf('config->context === \'JS\' ? ' .
- 'json_encode(%s, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT) : ' .
- 'htmlspecialchars(json_encode(%s, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT), \ENT_QUOTES, \'UTF-8\', false); ?>', $args, $args);
+ 'json_encode(%s, self::$_json_options) : ' .
+ 'htmlspecialchars(json_encode(%s, self::$_json_options), \ENT_QUOTES, \'UTF-8\', false); ?>', $args, $args);
}
elseif ($match[1] === 'lang')
{
@@ -726,23 +730,30 @@ class TemplateParser_v2
*/
protected function _arrangeOutputFilters(array $match): string
{
- // Escape is 'autoescape' by default.
- $escape_option = 'autoescape';
-
// Split content into filters.
$filters = array_map('trim', preg_split('#(? '|']);
- // Convert variable scope before applying filters.
- $str = $this->_escapeCurly($str);
- $str = $this->_convertVariableScope($str);
+ // Set default escape option.
+ if (preg_match('/^\\$(?:user_)?lang->\\w+$/', $str))
+ {
+ $escape_option = 'autocontext_lang';
+ }
+ else
+ {
+ $escape_option = 'autocontext';
+ }
// Prevent null errors.
- if (preg_match('#^\$[\\\\\w\[\]\'":>-]+$#', $str))
+ if (preg_match('#^\$[\\\\\w\[\]\'":>-]+$#', $str) && !str_starts_with($str, '$lang->'))
{
- $str = preg_match('/^\$lang->/', $str) ? $str : "$str ?? ''";
+ $str = "$str ?? ''";
}
+ // Convert variable scope and escape any curly braces.
+ $str = $this->_escapeCurly($str);
+ $str = $this->_convertVariableScope($str);
+
// Apply filters.
foreach ($filters as $filter)
{
@@ -777,8 +788,8 @@ class TemplateParser_v2
$escape_option = 'noescape';
break;
case 'json':
- $str = "json_encode({$str}, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT)";
- $escape_option = 'autocontext';
+ $str = "json_encode({$str}, self::\$_json_options)";
+ $escape_option = 'autocontext_json';
break;
case 'strip':
case 'strip_tags':
@@ -797,7 +808,7 @@ class TemplateParser_v2
$str = "strtoupper({$str})";
break;
case 'nl2br':
- $str = self::_applyEscapeOption($str, $escape_option);
+ $str = self::_applyEscapeOption($str, $escape_option === 'autocontext' ? 'autoescape' : $escape_option);
$str = "nl2br({$str})";
$escape_option = 'noescape';
break;
@@ -816,10 +827,10 @@ class TemplateParser_v2
$str = $filter_option ? "number_shorten({$str}, {$filter_option})" : "number_shorten({$str})";
break;
case 'link':
- $str = self::_applyEscapeOption($str, $escape_option);
+ $str = self::_applyEscapeOption($str, $escape_option === 'autocontext' ? 'autoescape' : $escape_option);
if ($filter_option)
{
- $filter_option = self::_applyEscapeOption($filter_option, $escape_option);
+ $filter_option = self::_applyEscapeOption($filter_option, $escape_option === 'autocontext' ? 'autoescape' : $escape_option);
$str = "'' . ($str) . ''";
}
else
@@ -847,14 +858,19 @@ class TemplateParser_v2
*/
protected static function _applyEscapeOption(string $str, string $option): string
{
+ $str2 = strtr($str, ["\n" => ' ']);
switch($option)
{
case 'autocontext':
- return "\$this->config->context === 'JS' ? ({$str}) : htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
+ return "\$this->config->context === 'JS' ? escape_js({$str2}) : htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
+ 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})";
case 'autoescape':
return "htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
case 'autolang':
- return "(preg_match('/^\\$(?:user_)?lang->\\w+$/', {$str}) ? ({$str}) : htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false))";
+ return "(preg_match('/^\\\\\$(?:user_)?lang->\\w+$/', {$str2}) ? ({$str}) : htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false))";
case 'escape':
return "htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', true)";
case 'noescape':
diff --git a/tests/_data/template/v2example.compiled.html b/tests/_data/template/v2example.compiled.html
index 28d4b82eb..b8a1fb282 100644
--- a/tests/_data/template/v2example.compiled.html
+++ b/tests/_data/template/v2example.compiled.html
@@ -6,7 +6,7 @@
_v2_loadResource('css/style.scss', 'print', '', []); ?>
foo = 'FOOFOOFOO';
+ $__Context->foo = 'FOOFOO<"FOO">BAR';
?>
bar = ['Rhy', 'miX', 'is', 'da', 'BEST!'];
@@ -15,38 +15,38 @@
{{ $foo }}
-
baz))): ?> class="foobar">
foo || $__Context->bar): ?>
Hello bar): ?>foo ?? ''; ?>
-
bar)), \ENT_QUOTES, 'UTF-8', false); ?>
+
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); ?>
-_v2_initLoopVar("RANDOM_LOOP_ID", $__tmp_RANDOM_LOOP_ID); foreach ($__tmp_RANDOM_LOOP_ID as $__Context->k => $__Context->val): ?>
+_v2_initLoopVar("64b3371f38fea1", $__tmp_64b3371f38fea1); foreach ($__tmp_64b3371f38fea1 as $__Context->k => $__Context->val): ?>
nosuchvar)): ?>

-
k >= 2): ?>class="val ?? '', \ENT_QUOTES, 'UTF-8', false); ?>">
+
k >= 2): ?>class="config->context === 'JS' ? escape_js($__Context->val ?? '') : htmlspecialchars($__Context->val ?? '', \ENT_QUOTES, 'UTF-8', false); ?>">
-_v2_incrLoopVar($__loop_RANDOM_LOOP_ID); endforeach; $this->_v2_removeLoopVar($__loop_RANDOM_LOOP_ID); unset($__loop_RANDOM_LOOP_ID); else: ?>Nothing here...
+_v2_incrLoopVar($__loop_64b3371f38fea1); endforeach; $this->_v2_removeLoopVar($__loop_64b3371f38fea1); unset($__loop_64b3371f38fea1); else: ?>Nothing here...
_fragments[$__last_fragment_name] = ob_get_flush(); ?>
_v2_include("include", $__filename, [(string)$__varname => $__var]); endforeach; })('incl/eachtest', $__Context->bar, 'var'); ?>
_v2_include("include", $__filename, [(string)$__varname => $__var]); endforeach; })('incl/eachtest', [], 'anything', 'incl/empty'); ?>
m): ?>
- The full class name is , really.
+ The full class name is , config->context === 'JS' ? escape_js(Rhymix\Framework\Push::class) : htmlspecialchars(Rhymix\Framework\Push::class, \ENT_QUOTES, 'UTF-8', false); ?> really.
-
+
_v2_buildAttribute('class', [
'a-1',
'font-normal' => $__Context->foo,
@@ -61,5 +61,6 @@
diff --git a/tests/_data/template/v2example.executed.html b/tests/_data/template/v2example.executed.html
index bce0e828b..1baff6af2 100644
--- a/tests/_data/template/v2example.executed.html
+++ b/tests/_data/template/v2example.executed.html
@@ -17,7 +17,7 @@
-
Hello FOOFOOFOO
+
Hello FOOFOO<"FOO">BAR
RHY|MIX|IS|DA|BEST!
@@ -57,5 +57,6 @@
diff --git a/tests/_data/template/v2example.html b/tests/_data/template/v2example.html
index 306b90af9..2b57d75d1 100644
--- a/tests/_data/template/v2example.html
+++ b/tests/_data/template/v2example.html
@@ -6,7 +6,7 @@
BAR';
?>
@php
$bar = ['Rhy', 'miX', 'is', 'da', 'BEST!'];
@@ -61,5 +61,6 @@
diff --git a/tests/unit/framework/parsers/TemplateParserV2Test.php b/tests/unit/framework/parsers/TemplateParserV2Test.php
index f5b517633..0fc8c750f 100644
--- a/tests/unit/framework/parsers/TemplateParserV2Test.php
+++ b/tests/unit/framework/parsers/TemplateParserV2Test.php
@@ -234,7 +234,7 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
{
// Basic usage of XE-style single braces
$source = '{$var}';
- $target = "var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(\$__Context->var ?? '') : htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Single braces with space at beginning will not be parsed
@@ -244,22 +244,22 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// Single braces with space at end are OK
$source = '{$var }';
- $target = "var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(\$__Context->var ?? '') : htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Correct handling of object property and array access
$source = '{Context::getRequestVars()->$foo[$bar]}';
- $target = "{\$__Context->foo}[\$__Context->bar], \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(Context::getRequestVars()->{\$__Context->foo}[\$__Context->bar]) : htmlspecialchars(Context::getRequestVars()->{\$__Context->foo}[\$__Context->bar], \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Basic usage of Blade-style double braces
$source = '{{ $var }}';
- $target = "var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(\$__Context->var ?? '') : htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Double braces without spaces are OK
$source = '{{$var}}';
- $target = "var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(\$__Context->var ?? '') : htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Literal double braces
@@ -273,13 +273,13 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$this->assertEquals($target, $this->_parse($source));
// Callback function inside echo statement
- $source = '{{ implode("|", array_map(function(\$i) { return \$i + 1; }, $list) }}';
- $target = "list), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $source = '{{ implode("|", array_map(function(\$i) { return \$i + 1; }, $list) | noescape }}';
+ $target = "list); ?>";
$this->assertEquals($target, $this->_parse($source));
// Multiline echo statement
$source = '{{ $foo ?' . "\n" . ' date($foo) :' . "\n" . ' toBool($bar) }}';
- $target = "foo ?\n date(\$__Context->foo) :\n toBool(\$__Context->bar), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(\$__Context->foo ? date(\$__Context->foo) : toBool(\$__Context->bar)) : htmlspecialchars(\$__Context->foo ?\n date(\$__Context->foo) :\n toBool(\$__Context->bar), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
}
@@ -315,9 +315,17 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
- // Autolang
+ // Autolang (lang codes are not escaped, but escape_js() is applied in JS context)
$source = '{{ $foo|autolang }}';
- $target = "\w+$/', \$__Context->foo ?? '') ? (\$__Context->foo ?? '') : htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)); ?>";
+ $target = "\w+$/', \$__Context->foo ?? '') ? (\$__Context->foo ?? '') : htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)); ?>";
+ $this->assertEquals($target, $this->_parse($source));
+
+ $source = '{{ $lang->cmd_hello_world }}';
+ $target = "config->context === 'JS' ? escape_js(\$__Context->lang->cmd_hello_world) : (\$__Context->lang->cmd_hello_world); ?>";
+ $this->assertEquals($target, $this->_parse($source));
+
+ $source = '{{ $user_lang->user_lang_1234567890 }}';
+ $target = "config->context === 'JS' ? escape_js(\$__Context->user_lang->user_lang_1234567890 ?? '') : (\$__Context->user_lang->user_lang_1234567890 ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Escape
@@ -344,19 +352,19 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$source = '{{ $foo|json }}';
$target = implode('', [
"config->context === 'JS' ? ",
- "(json_encode(\$__Context->foo ?? '', \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT)) : ",
- "htmlspecialchars(json_encode(\$__Context->foo ?? '', \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT), \ENT_QUOTES, 'UTF-8', false); ?>",
+ "json_encode(\$__Context->foo ?? '', self::\$_json_options) : ",
+ "htmlspecialchars(json_encode(\$__Context->foo ?? '', self::\$_json_options), \ENT_QUOTES, 'UTF-8', false); ?>",
]);
$this->assertEquals($target, $this->_parse($source));
// strip_tags
$source = '{{ $foo|strip }}';
- $target = "foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(strip_tags(\$__Context->foo ?? '')) : htmlspecialchars(strip_tags(\$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// strip_tags (alternate name)
$source = '{{ $foo|upper|strip_tags }}';
- $target = "foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(strip_tags(strtoupper(\$__Context->foo ?? ''))) : htmlspecialchars(strip_tags(strtoupper(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Trim
@@ -366,12 +374,12 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// URL encode
$source = '{{ $foo|trim|urlencode }}';
- $target = "foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(rawurlencode(trim(\$__Context->foo ?? ''))) : htmlspecialchars(rawurlencode(trim(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Lowercase
$source = '{{ $foo|trim|lower }}';
- $target = "foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(strtolower(trim(\$__Context->foo ?? ''))) : htmlspecialchars(strtolower(trim(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Uppercase
@@ -391,62 +399,62 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// Array join (default joiner is comma)
$source = '{{ $foo|join }}';
- $target = "foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(implode(', ', \$__Context->foo ?? '')) : htmlspecialchars(implode(', ', \$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Array join (custom joiner)
$source = '{{ $foo|join:"!@!" }}';
- $target = "foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(implode(\"!@!\", \$__Context->foo ?? '')) : htmlspecialchars(implode(\"!@!\", \$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Date conversion (default format)
$source = '{{ $item->regdate | date }}';
- $target = "item->regdate ?? ''), 'Y-m-d H:i:s'), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'Y-m-d H:i:s')) : htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'Y-m-d H:i:s'), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Date conversion (custom format)
$source = "{{ \$item->regdate | date:'n/j H:i' }}";
- $target = "item->regdate ?? ''), 'n/j H:i'), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'n/j H:i')) : htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'n/j H:i'), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Date conversion (custom format in variable)
$source = "{{ \$item->regdate | date:\$format }}";
- $target = "item->regdate ?? ''), \$__Context->format), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), \$__Context->format)) : htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), \$__Context->format), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number format
$source = '{{ $num | format }}';
- $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(number_format(\$__Context->num ?? '')) : htmlspecialchars(number_format(\$__Context->num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number format (alternate name)
$source = '{{ $num | number_format }}';
- $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $target = "config->context === 'JS' ? escape_js(number_format(\$__Context->num ?? '')) : htmlspecialchars(number_format(\$__Context->num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number format (custom format)
- $source = '{{ $num | number_format:6 }}';
- $target = "num ?? '', '6'), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $source = '{{ $num | number_format:6 | noescape }}';
+ $target = "num ?? '', '6'); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number format (custom format in variable)
- $source = '{{ $num | number_format:$digits }}';
- $target = "num ?? '', \$__Context->digits), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $source = '{{ $num | number_format:$digits | noescape }}';
+ $target = "num ?? '', \$__Context->digits); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number shorten
- $source = '{{ $num | shorten }}';
- $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $source = '{{ $num | shorten | noescape }}';
+ $target = "num ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number shorten (alternate name)
- $source = '{{ $num | number_shorten }}';
- $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $source = '{{ $num | number_shorten | noescape }}';
+ $target = "num ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number shorten (custom format)
- $source = '{{ $num | number_shorten:1 }}';
- $target = "num ?? '', '1'), \ENT_QUOTES, 'UTF-8', false); ?>";
+ $source = '{{ $num | number_shorten:1 | noescape }}';
+ $target = "num ?? '', '1'); ?>";
$this->assertEquals($target, $this->_parse($source));
// Link
@@ -489,12 +497,12 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// $lang
$source = "{!! \$lang->cmd_yes !!}";
- $target = "lang->cmd_yes ?? ''; ?>";
+ $target = "lang->cmd_yes; ?>";
$this->assertEquals($target, $this->_parse($source));
// $loop
$source = "{!! \$loop->first !!}";
- $target = "first; ?>";
+ $target = "first ?? ''; ?>";
$this->assertEquals($target, $this->_parse($source));
// Escaped dollar sign
@@ -731,7 +739,7 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
]);
$target = implode("\n", [
"_v2_errorExists('email', 'login')): ?>",
- "message ?? '', \ENT_QUOTES, 'UTF-8', false); ?>",
+ "config->context === 'JS' ? escape_js(\$__Context->message ?? '') : htmlspecialchars(\$__Context->message ?? '', \ENT_QUOTES, 'UTF-8', false); ?>",
'',
]);
$this->assertEquals($target, $this->_parse($source));
@@ -800,6 +808,34 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
'',
]);
$this->assertEquals($target, $this->_parse($source));
+
+ // @can and @cannot, @canany
+ $source = implode("\n", [
+ "@can('foo')",
+ 'Hello World',
+ '@endcan',
+ "",
+ "@canany(['foo', 'bar'])",
+ 'Goodbye World',
+ '',
+ ''
+ ]);
+ $target = implode("\n", [
+ '_v2_checkCapability(1, \'foo\')): ?>',
+ 'Hello World',
+ '',
+ '_v2_checkCapability(2, \'bar\')): ?>',
+ '_v2_checkCapability(3, [\'foo\', \'bar\'])): ?>',
+ 'Goodbye World',
+ '',
+ '',
+ ]);
+ $this->assertEquals($target, $this->_parse($source));
+
+ // @env
+ $source = "@env('foo') FOO @endenv";
+ $target = ' FOO ';
+ $this->assertEquals($target, $this->_parse($source));
}
public function testInlineConditions()
@@ -866,8 +902,8 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$source = '@json($var)';
$target = implode('', [
'config->context === \'JS\' ? ',
- 'json_encode($__Context->var, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT) : ',
- 'htmlspecialchars(json_encode($__Context->var, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT), \ENT_QUOTES, \'UTF-8\', false); ?>',
+ 'json_encode($__Context->var, self::$_json_options) : ',
+ 'htmlspecialchars(json_encode($__Context->var, self::$_json_options), \ENT_QUOTES, \'UTF-8\', false); ?>',
]);
$this->assertEquals($target, $this->_parse($source));
@@ -875,8 +911,8 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$source = '@json(["foo" => 1, "bar" => 2])';
$target = implode('', [
'config->context === \'JS\' ? ',
- 'json_encode(["foo" => 1, "bar" => 2], \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT) : ',
- 'htmlspecialchars(json_encode(["foo" => 1, "bar" => 2], \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG | \JSON_HEX_QUOT), \ENT_QUOTES, \'UTF-8\', false); ?>',
+ 'json_encode(["foo" => 1, "bar" => 2], self::$_json_options) : ',
+ 'htmlspecialchars(json_encode(["foo" => 1, "bar" => 2], self::$_json_options), \ENT_QUOTES, \'UTF-8\', false); ?>',
]);
$this->assertEquals($target, $this->_parse($source));