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 = '
';
$target = '
';
$this->assertEquals($target, $this->_parse($source));
//