config->version = 2; ?>'; private $baseurl; public function _before() { $this->baseurl = '/' . basename(dirname(dirname(dirname(dirname(__DIR__))))) . '/'; } public function testVersion() { // Extension is .html and config is explicitly declared $source = '' . "\n" . '
{{ RX_VERSION|noescape }}
'; $target = '
'; $this->assertEquals("\n" . $target, $this->_parse($source), false); $source = '@version(2)' . "\n" . '
@php func_get_args(); @endphp
'; $target = '
'; $this->assertEquals("\n" . $target, $this->_parse($source), false); // Extension is .blade.php and config is not declared $source = ''; $target = ' disabled="disabled">'; $this->assertEquals($target, $this->_parse($source)); // Extension is .blade.php but version is incorrectly declared: will be parsed as v1 $source = '@version(1)' . "\n" . ''; $target = ''; $this->assertStringContainsString($target, $this->_parse($source)); } public function testClassAliases() { // XE-style $source = '' . "\n" . '{@ $foo = TemplateHandler::getInstance()}'; $target = "\n" . 'foo = Rhymix\Framework\Template::getInstance() ?>'; $this->assertEquals($target, $this->_parse($source)); // Blade-style $source = "@use('Rhymix\Framework\Template', 'TemplateHandler')" . "\n" . '{@ $foo = new TemplateHandler()}'; $target = "\n" . 'foo = new Rhymix\Framework\Template() ?>'; $this->assertEquals($target, $this->_parse($source)); } public function testInclude() { // Basic usage $source = ''; $target = 'relative_dirname, "foobar", "html"); echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Legacy 'target' attribute $source = ''; $target = 'relative_dirname, "subdir/foobar", "html"); echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Conditional include $source = ''; $target = 'relative_dirname, "../up/foobar", "html"); echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Conditional include with legacy 'cond' attribute $source = ''; $target = 'relative_dirname, "legacy/cond.statement.html", "html"); echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Path relative to Rhymix installation directory $source = ''; $target = 'compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Unless $source = ''; $target = 'compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // With variables $source = ''; $target = 'relative_dirname, "foobar", "html"); $__tpl->setVars($__Context->vars); echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // With array literal passed as variables $source = ''; $target = 'relative_dirname, "foobar", "html"); $__tpl->setVars([\'foo\' => \'bar\']); echo $__tpl->compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); // Blade-style @include $source = "@include ('foobar')"; $target = implode(' ', [ 'setVars($__vars);', 'echo $__tpl->compile(); })($this->relative_dirname, \'foobar\'); ?>' ]); $this->assertEquals($target, $this->_parse($source)); // Blade-style @include with variable in filename $source = "@include(\$var)"; $target = implode(' ', [ 'setVars($__vars);', 'echo $__tpl->compile(); })($this->relative_dirname, $__Context->var); ?>' ]); $this->assertEquals($target, $this->_parse($source)); // Blade-style @include with path relative to Rhymix installation directory $source = '@include ("^/common/js/plugins/foobar/baz.blade.php")'; $target = implode(' ', [ 'setVars($__vars);', 'echo $__tpl->compile(); })("common/js/plugins/foobar", "baz.blade.php"); ?>' ]); $this->assertEquals($target, $this->_parse($source)); // Blade-style @includeIf with variables $source = "@includeIf('dir/foobar', \$vars)"; $target = implode(' ', [ 'exists()) return;', 'if ($__vars) $__tpl->setVars($__vars);', 'echo $__tpl->compile(); })($this->relative_dirname, \'dir/foobar\', $__Context->vars); ?>' ]); $this->assertEquals($target, $this->_parse($source)); // Blade-style @includeWhen $source = "@includeWhen(\$foo->isBar(), '../../foobar.html', \$vars)"; $target = implode(' ', [ 'setVars($__vars);', 'echo $__tpl->compile(); })("includeWhen", $this->relative_dirname, $__Context->foo->isBar(), \'../../foobar.html\', $__Context->vars); ?>' ]); $this->assertEquals($target, $this->_parse($source)); // Blade-style @includeUnless with path relative to Rhymix installation directory $source = "@includeUnless (false, '^common/tpl/foobar.html', \$vars)"; $target = implode(' ', [ 'setVars($__vars);', 'echo $__tpl->compile(); })("includeUnless", "common/tpl", false, \'foobar.html\', $__Context->vars); ?>' ]); $this->assertEquals($target, $this->_parse($source)); // Blade-style @each $source = "@each('incl/eachtest', \$jobs, 'job')"; $target = 'foreach ($__vars as $__var):'; $this->assertStringContainsString($target, $this->_parse($source)); // Blade-style @each with fallback template $source = "@each('incl/eachtest', \$jobs, 'job', 'incl/empty')"; $target = 'if ($__empty): $__path = $__empty;'; $this->assertStringContainsString($target, $this->_parse($source)); } public function testAssetLoading() { // CSS, SCSS, LESS with media and variables $source = ''; $target = "foo]); ?>"; $this->assertEquals($target, $this->_parse($source)); $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); // JS with type and index $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); // External script $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); // External webfont $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Path relative to Rhymix installation directory $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); // JS plugin $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Lang file $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); $source = ''; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Blade-style SCSS with media and variables $source = "@load('assets/hello.scss', 'print', \$vars)"; $target = "vars]); ?>"; $this->assertEquals($target, $this->_parse($source)); $source = "@load ('../hello.css', 'screen')"; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Blade-style JS with type and index $source = "@load('assets/hello.js', 'body', 10)"; $target = ""; $this->assertEquals($target, $this->_parse($source)); $source = "@load ('assets/hello.js', 'head')"; $target = ""; $this->assertEquals($target, $this->_parse($source)); $source = "@load ('assets/hello.js')"; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Blade-style external script $source = "@load ('//cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.min.js')"; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Blade-style external webfont $source = "@load('https://fonts.googleapis.com/css2?family=Roboto&display=swap')"; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Blade-style path relative to Rhymix installation directory $source = '@load ("^/common/js/foobar.js")'; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Blade-style JS plugin $source = "@load('^/common/js/plugins/ckeditor/')"; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Blade-style lang file $source = "@load('^/modules/member/lang')"; $target = ""; $this->assertEquals($target, $this->_parse($source)); $source = '@load("^/modules/legacy_module/lang/lang.xml")'; $target = ""; $this->assertEquals($target, $this->_parse($source)); } public function testEchoStatements() { // Basic usage of XE-style single braces $source = '{$var}'; $target = "var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>"; $this->assertEquals($target, $this->_parse($source)); // Single braces with space at beginning will not be parsed $source = '{ $var}'; $target = '{ $var}'; $this->assertEquals($target, $this->_parse($source)); // Single braces with space at end are OK $source = '{$var }'; $target = "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); ?>"; $this->assertEquals($target, $this->_parse($source)); // Basic usage of Blade-style double braces $source = '{{ $var }}'; $target = "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); ?>"; $this->assertEquals($target, $this->_parse($source)); // Literal double braces $source = '@{{ $var }}'; $target = '{{ $var }}'; $this->assertEquals($target, $this->_parse($source)); // Blade-style shortcut for unescaped output $source = '{!! Context::getInstance()->get($var) !!}'; $target = "get(\$__Context->var); ?>"; $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); ?>"; $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); ?>"; $this->assertEquals($target, $this->_parse($source)); } public function testOutputFilters() { // Filters with no whitespace $source = '{$foo|upper|noescape}'; $target = "foo ?? ''); ?>"; $this->assertEquals($target, $this->_parse($source)); // Randomly distributed whitespace $source = '{$foo | upper |noescape }'; $target = "foo ?? ''); ?>"; $this->assertEquals($target, $this->_parse($source)); // Pipe character in filter option $source = "{\$foo|join:'|'|noescape}"; $target = "foo ?? ''); ?>"; $this->assertEquals($target, $this->_parse($source)); // Pipe character in filter option, escaped $source = "{\$foo|join:'foo\|bar'|noescape}"; $target = "foo ?? ''); ?>"; $this->assertEquals($target, $this->_parse($source)); // Pipe character in OR operator $source = '{$foo || $bar | noescape}'; $target = "foo || \$__Context->bar; ?>"; $this->assertEquals($target, $this->_parse($source)); // Autoescape $source = '{{ $foo|autoescape }}'; $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false); ?>"; $this->assertEquals($target, $this->_parse($source)); // Autolang $source = '{{ $foo|autolang }}'; $target = "\w+$/', \$__Context->foo ?? '') ? (\$__Context->foo ?? '') : htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)); ?>"; $this->assertEquals($target, $this->_parse($source)); // Escape $source = '{{ $foo|escape }}'; $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', true); ?>"; $this->assertEquals($target, $this->_parse($source)); // Noescape $source = '{{ $foo|escape|noescape }}'; $target = "foo ?? ''; ?>"; $this->assertEquals($target, $this->_parse($source)); // Escape for Javascript $source = '{{ $foo|js }}'; $target = "foo ?? ''); ?>"; $this->assertEquals($target, $this->_parse($source)); // Escape for Javascript (alternate name) $source = '{{ $foo|escapejs }}'; $target = "foo ?? ''); ?>"; $this->assertEquals($target, $this->_parse($source)); // JSON using context-aware escape $source = '{{ $foo|json }}'; $target = implode('', [ "config->context === 'JS' ? ", "(json_encode(\$__Context->foo ?? '', \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)) : ", "htmlspecialchars(json_encode(\$__Context->foo ?? '', \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES), \ENT_QUOTES, 'UTF-8', false); ?>", ]); $this->assertEquals($target, $this->_parse($source)); // strip_tags $source = '{{ $foo|strip }}'; $target = "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); ?>"; $this->assertEquals($target, $this->_parse($source)); // Trim $source = '{{ $foo|trim|noescape }}'; $target = "foo ?? ''); ?>"; $this->assertEquals($target, $this->_parse($source)); // URL encode $source = '{{ $foo|trim|urlencode }}'; $target = "foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>"; $this->assertEquals($target, $this->_parse($source)); // Lowercase $source = '{{ $foo|trim|lower }}'; $target = "foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>"; $this->assertEquals($target, $this->_parse($source)); // Uppercase $source = '{{ $foo|upper|escape }}'; $target = "foo ?? ''), \ENT_QUOTES, 'UTF-8', true); ?>"; $this->assertEquals($target, $this->_parse($source)); // nl2br() $source = '{{ $foo|nl2br }}'; $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false)); ?>"; $this->assertEquals($target, $this->_parse($source)); // nl2br() with gratuitous escape $source = '{{ $foo|nl2br|escape }}'; $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false)), \ENT_QUOTES, 'UTF-8', true); ?>"; $this->assertEquals($target, $this->_parse($source)); // Array join (default joiner is comma) $source = '{{ $foo|join }}'; $target = "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); ?>"; $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); ?>"; $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); ?>"; $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); ?>"; $this->assertEquals($target, $this->_parse($source)); // Number format $source = '{{ $num | format }}'; $target = "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); ?>"; $this->assertEquals($target, $this->_parse($source)); // Number format (custom format) $source = '{{ $num | number_format:6 }}'; $target = "num ?? '', '6'), \ENT_QUOTES, 'UTF-8', false); ?>"; $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); ?>"; $this->assertEquals($target, $this->_parse($source)); // Number shorten $source = '{{ $num | shorten }}'; $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; $this->assertEquals($target, $this->_parse($source)); // Number shorten (alternate name) $source = '{{ $num | number_shorten }}'; $target = "num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>"; $this->assertEquals($target, $this->_parse($source)); // Number shorten (custom format) $source = '{{ $num | number_shorten:1 }}'; $target = "num ?? '', '1'), \ENT_QUOTES, 'UTF-8', false); ?>"; $this->assertEquals($target, $this->_parse($source)); // Link $source = '{{ $foo|link }}'; $target = "foo ?? '', \ENT_QUOTES, 'UTF-8', false)) . '\">' . (htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)) . ''; ?>"; $this->assertEquals($target, $this->_parse($source)); // Link (custom link text) $source = '{{ $foo|link:"Hello World" }}'; $target = "' . (htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)) . ''; ?>"; $this->assertEquals($target, $this->_parse($source)); // Link (custom link text in variable) $source = '{{ $foo|link:$bar->baz[0] }}'; $target = "bar->baz[0], \ENT_QUOTES, 'UTF-8', false)) . '\">' . (htmlspecialchars(\$__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false)) . ''; ?>"; $this->assertEquals($target, $this->_parse($source)); } public function testVariableScopeConversion() { // Local variable $source = '{$foo|noescape}'; $target = "foo ?? ''; ?>"; $this->assertEquals($target, $this->_parse($source)); // Class and array keys $source = '{!! ClassName::getInstance()->$foo[$bar] !!}'; $target = "{\$__Context->foo}[\$__Context->bar]; ?>"; $this->assertEquals($target, $this->_parse($source)); // Superglobals $source = "{!! \$_SERVER['HTTP_USER_AGENT'] . \$GLOBALS[\$_GET['foo']] !!}"; $target = ""; $this->assertEquals($target, $this->_parse($source)); // $this $source = "{!! \$this->func(\$args) !!}"; $target = "func(\$__Context->args); ?>"; $this->assertEquals($target, $this->_parse($source)); // $lang $source = "{!! \$lang->cmd_yes !!}"; $target = "lang->cmd_yes ?? ''; ?>"; $this->assertEquals($target, $this->_parse($source)); // $loop $source = "{!! \$loop->first !!}"; $target = "first ?? ''; ?>"; $this->assertEquals($target, $this->_parse($source)); // Escaped dollar sign $source = "{!! \\\$escaped !!}"; $target = ""; $this->assertEquals($target, $this->_parse($source)); // Escaped and unescaped variables used together in closure $source = "{!! (function(\\\$i) use(\$__Context) { return \\\$i * \$j; })(\$k); !!}"; $target = "j; })(\$__Context->k);; ?>"; $this->assertEquals($target, $this->_parse($source)); } public function testPathConversion() { // Image $source = 'foo'; $target = 'foo'; $this->assertEquals($target, $this->_parse($source)); //