From 3e1587c2ac98311fa5ef181ea8a0fe2fc27a79a4 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 16 Oct 2023 00:30:56 +0900 Subject: [PATCH] Fix variable scope and absolute path handling in include syntax of template v2 --- .../parsers/template/TemplateParser_v2.php | 66 ++++++++++++----- .../parsers/TemplateParserV2Test.php | 74 +++++++++++++++++++ 2 files changed, 123 insertions(+), 17 deletions(-) diff --git a/common/framework/parsers/template/TemplateParser_v2.php b/common/framework/parsers/template/TemplateParser_v2.php index dc936374e..e74e796b3 100644 --- a/common/framework/parsers/template/TemplateParser_v2.php +++ b/common/framework/parsers/template/TemplateParser_v2.php @@ -309,6 +309,8 @@ class TemplateParser_v2 // Convert XE-style include directives. $regexp = '#^[\x09\x20]*()[\x09\x20]*$#m'; $content = preg_replace_callback($regexp, function($match) { + + // Convert the path if necessary. $attrs = self::_getTagAttributes($match[1]); $path = $attrs['src'] ?? ($attrs['target'] ?? null); if (!$path) return $match[0]; @@ -318,9 +320,13 @@ class TemplateParser_v2 $dir = '"' . (str_contains($m[1], '/') ? dirname($m[1]) : '') . '"'; $path = basename($m[1]); } + + // Generate the code to create a new Template object and compile it. $tpl = 'template->extension ?: 'auto') . '"); '; - $tpl .= !empty($attrs['vars']) ? ' $__tpl->setVars(' . $attrs['vars'] . '); ' : ''; + $tpl .= !empty($attrs['vars']) ? '$__tpl->setVars(' . self::_convertVariableScope($attrs['vars']) . '); ' : ''; $tpl .= 'echo $__tpl->compile(); ?>'; + + // Add conditions around the code. if (!empty($attrs['if']) || !empty($attrs['when']) || !empty($attrs['cond'])) { $condition = $attrs['if'] ?? ($attrs['when'] ?? $attrs['cond']); @@ -337,30 +343,56 @@ class TemplateParser_v2 // Convert Blade-style include directives. $regexp = '#^[\x09\x20]*(?template->extension === 'blade.php' ? 'blade.php' : 'html'; - if ($match[1] === 'include') + $dir = '$this->relative_dirname'; + if ($match[1] === 'include' || $match[1] === 'includeIf') { - $tpl = 'relative_dirname, $__path, "' . $extension . '"); '; - $tpl .= 'if ($__vars) $__tpl->setVars($__vars); ' ; - $tpl .= 'echo $__tpl->compile(); })(' . $match[2] . '); ?>'; - } - elseif ($match[1] === 'includeIf') - { - $tpl = 'relative_dirname, $__path, "' . $extension . '"); '; - $tpl .= 'if (!$__tpl->exists()) return; '; - $tpl .= 'if ($__vars) $__tpl->setVars($__vars); ' ; - $tpl .= 'echo $__tpl->compile(); })(' . $match[2] . '); ?>'; + $path = preg_match('#^([\'"])([^\'"]+)\1#', $match[2], $m) ? $m[2] : ''; + if (preg_match('#^\^/?(\w.+)$#s', $path, $mm)) + { + $dir = '"' . escape_dqstr(str_contains($mm[1], '/') ? dirname($mm[1]) : '') . '"'; + $filename = basename($mm[1]); + $match[2] = preg_replace('#^([\'"])([^\'"]+)\1#', '$1' . $filename . '$1', $match[2]); + } } else { - $tpl = 'setVars($__vars); ' ; + $tpl .= 'echo $__tpl->compile(); })(' . $dir . ', ' . $match[2] . '); ?>'; + } + elseif ($match[1] === 'includeIf') + { + $tpl = 'exists()) return; '; + $tpl .= 'if ($__vars) $__tpl->setVars($__vars); ' ; + $tpl .= 'echo $__tpl->compile(); })(' . $dir . ', ' . $match[2] . '); ?>'; + } + else + { + $tpl = 'relative_dirname, $__path, "' . $extension . '"); '; + $tpl .= '$__tpl = new \Rhymix\Framework\Template($__dir, $__path, "' . $extension . '"); '; $tpl .= 'if ($__vars) $__tpl->setVars($__vars); ' ; - $tpl .= 'echo $__tpl->compile(); })("' . $match[1] . '", ' . $match[2] . '); ?>'; + $tpl .= 'echo $__tpl->compile(); })("' . $match[1] . '", ' . $dir . ', ' . $match[2] . '); ?>'; } return self::_escapeVars($tpl); }, $content); diff --git a/tests/unit/framework/parsers/TemplateParserV2Test.php b/tests/unit/framework/parsers/TemplateParserV2Test.php index 9af007a48..4e63e948b 100644 --- a/tests/unit/framework/parsers/TemplateParserV2Test.php +++ b/tests/unit/framework/parsers/TemplateParserV2Test.php @@ -77,6 +77,80 @@ class TemplateParserV2Test extends \Codeception\Test\Unit $target = 'compile(); ?>'; $this->assertEquals($target, $this->_parse($source)); + // With variables + $source = ''; + $target = 'relative_dirname, "foobar", "blade.php"); $__tpl->setVars($__Context->vars); echo $__tpl->compile(); ?>'; + $this->assertEquals($target, $this->_parse($source)); + + // With array literal passed as variables + $source = ''; + $target = 'relative_dirname, "foobar", "blade.php"); $__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)); } public function testAssetLoading()