Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Fixes blade tags issue #45424 🔧 #45490

Merged
merged 4 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions src/Illuminate/View/Compilers/BladeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -499,16 +499,35 @@ protected function compileExtensions($value)
/**
* Compile Blade statements that start with "@".
*
* @param string $value
* @param string $template
* @return string
*/
protected function compileStatements($value)
{
return preg_replace_callback(
'/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', function ($match) {
return $this->compileStatement($match);
}, $value
);
protected function compileStatements($template)
{
preg_match_all('/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( [\S\s]*? ) \))?/x', $template, $matches);
for ($i = 0; isset($matches[0][$i]); $i++) {
$match = [
$matches[0][$i],
$matches[1][$i],
$matches[2][$i],
$matches[3][$i] ?: null,
$matches[4][$i] ?: null,
];

// Here we check to see if we have properly found the closing parenthesis by
// regex pattern or not, and will recursively continue on to the next ")"
// then check again until the tokenizer confirms we found the right one
while (isset($match[4]) && Str::endsWith($match[0], ')') && ! $this->isProperMatch($match[0])) {
$rest = Str::before(Str::after($template, $match[0]), ')');
imanghafoori1 marked this conversation as resolved.
Show resolved Hide resolved
$match[0] = $match[0].$rest.')';
$match[3] = $match[3].$rest.')';
$match[4] = $match[4].$rest;
}

$template = Str::replaceFirst($match[0], $this->compileStatement($match), $template);
}

return $template;
}

/**
Expand Down Expand Up @@ -901,4 +920,25 @@ public function withoutComponentTags()
{
$this->compilesComponentTags = false;
}

protected function isProperMatch($match)
{
$tokens = token_get_all('<?php '.$match);

if (Arr::last($tokens) !== ')') {
return false;
}

$openings = 0;
$closings = 0;
foreach ($tokens as $token) {
if ($token == ')') {
$closings++;
} elseif ($token == '(') {
$openings++;
}
}

return $openings === $closings;
}
}
7 changes: 7 additions & 0 deletions tests/View/Blade/BladeIncludesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ class BladeIncludesTest extends AbstractBladeTestCase
public function testEachsAreCompiled()
{
$this->assertSame('<?php echo $__env->renderEach(\'foo\', \'bar\'); ?>', $this->compiler->compileString('@each(\'foo\', \'bar\')'));
$this->assertSame('<?php echo $__env->renderEach(\'foo\', \'(bar))\'); ?>', $this->compiler->compileString('@each(\'foo\', \'(bar))\')'));
$this->assertSame('<?php echo $__env->renderEach(name(foo)); ?>', $this->compiler->compileString('@each(name(foo))'));
}

public function testIncludesAreCompiled()
{
$this->assertSame('<?php echo $__env->make(\'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(\'foo\')'));
$this->assertSame('<?php echo $__env->make(\'foo\', [\'((\'], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(\'foo\', [\'((\'])'));
$this->assertSame('<?php echo $__env->make(\'foo\', [\'((a)\' => \'((a)\'], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(\'foo\', [\'((a)\' => \'((a)\'])'));
$this->assertSame('<?php echo $__env->make(name(foo), \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(name(foo))'));
}

Expand All @@ -31,12 +34,16 @@ public function testIncludeWhensAreCompiled()
public function testIncludeUnlessesAreCompiled()
{
$this->assertSame('<?php echo $__env->renderUnless(true, \'foo\', ["foo" => "bar"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\'])); ?>', $this->compiler->compileString('@includeUnless(true, \'foo\', ["foo" => "bar"])'));
$this->assertSame('<?php echo $__env->renderUnless(true, \'foo\', ["foo" => "bar_))-))>"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\'])); ?>', $this->compiler->compileString('@includeUnless(true, \'foo\', ["foo" => "bar_))-))>"])'));
$this->assertSame('<?php echo $__env->renderUnless($undefined ?? true, \'foo\', \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\'])); ?>', $this->compiler->compileString('@includeUnless($undefined ?? true, \'foo\')'));
}

public function testIncludeFirstsAreCompiled()
{
$this->assertSame('<?php echo $__env->first(["one", "two"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["one", "two"])'));
$this->assertSame('<?php echo $__env->first(["one", "two"], ["foo" => "bar"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["one", "two"], ["foo" => "bar"])'));
$this->assertSame('<?php echo $__env->first(["issue", "#45424)"], ["foo()" => "bar)-))"], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["issue", "#45424)"], ["foo()" => "bar)-))"])'));
$this->assertSame('<?php echo $__env->first(["issue", "#45424)"], ["foo" => "bar(-(("], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["issue", "#45424)"], ["foo" => "bar(-(("])'));
$this->assertSame('<?php echo $__env->first(["issue", "#45424)"], [(string) "foo()" => "bar(-(("], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@includeFirst(["issue", "#45424)"], [(string) "foo()" => "bar(-(("])'));
}
}
36 changes: 31 additions & 5 deletions tests/View/Blade/BladePhpStatementsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,41 @@ public function testVerbatimAndPhpStatementsDontGetMixedUp()
$this->assertEquals($expected, $this->compiler->compileString($string));
}

public function testStringWithParenthesisCannotBeCompiled()
public function testStringWithOpeningParenthesisCanBeCompiled()
{
$string = "@php(\$data = ['test' => ')'])";
$string = "@php(\$data = ['single' => ':(('])";
$expected = "<?php (\$data = ['single' => ':((']); ?>";
$this->assertEquals($expected, $this->compiler->compileString($string));

$string = "@php(\$data = ['single' => (string)':(('])";
$expected = "<?php (\$data = ['single' => (string)':((']); ?>";
$this->assertEquals($expected, $this->compiler->compileString($string));

$string = "@php(\$data = ['single' => '(()(('])";
$expected = "<?php (\$data = ['single' => '(()((']); ?>";
$this->assertEquals($expected, $this->compiler->compileString($string));
}

public function testStringWithParenthesisCanBeCompiled()
{
$string = "@php(\$data = ['single' => ')'])";
$expected = "<?php (\$data = ['single' => ')']); ?>";

$this->assertEquals($expected, $this->compiler->compileString($string));

$expected = "<?php (\$data = ['test' => ')']); ?>";
$string = "@php(\$data = ['(multiple)-))' => '((-))'])";
$expected = "<?php (\$data = ['(multiple)-))' => '((-))']); ?>";

$actual = "<?php (\$data = ['test' => '); ?>'])";
$this->assertEquals($expected, $this->compiler->compileString($string));

$string = "@php(\$data = [(int)'(multiple)-))' => (bool)'((casty))'])";
$expected = "<?php (\$data = [(int)'(multiple)-))' => (bool)'((casty))']); ?>";

$this->assertEquals($expected, $this->compiler->compileString($string));

$this->assertEquals($actual, $this->compiler->compileString($string));
$this->assertSame('<?php echo $__env->renderEach(\'foo\', \'b)a)r\'); ?>', $this->compiler->compileString('@each(\'foo\', \'b)a)r\')'));
$this->assertSame('<?php echo $__env->make(\'test_for\', [\'issue))\' => \'(issue#45424))\'], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>', $this->compiler->compileString('@include(\'test_for\', [\'issue))\' => \'(issue#45424))\'])'));
$this->assertSame('( <?php echo $__env->make(\'test_for\', [\'not_too_much))\' => \'(issue#45424))\'], \Illuminate\Support\Arr::except(get_defined_vars(), [\'__data\', \'__path\']))->render(); ?>))', $this->compiler->compileString('( @include(\'test_for\', [\'not_too_much))\' => \'(issue#45424))\'])))'));
}

public function testStringWithEmptyStringDataValue()
Expand Down
11 changes: 11 additions & 0 deletions tests/View/Blade/BladePushTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ public function testPushIsCompiled()
$this->assertEquals($expected, $this->compiler->compileString($string));
}

public function testPushIsCompiledWithParenthesis()
{
$string = '@push(\'foo):))\')
test
@endpush';
$expected = '<?php $__env->startPush(\'foo):))\'); ?>
test
<?php $__env->stopPush(); ?>';
$this->assertEquals($expected, $this->compiler->compileString($string));
}

public function testPushOnceIsCompiled()
{
$string = '@pushOnce(\'foo\', \'bar\')
Expand Down
1 change: 1 addition & 0 deletions tests/View/Blade/BladeSectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class BladeSectionTest extends AbstractBladeTestCase
public function testSectionStartsAreCompiled()
{
$this->assertSame('<?php $__env->startSection(\'foo\'); ?>', $this->compiler->compileString('@section(\'foo\')'));
$this->assertSame('<?php $__env->startSection(\'issue#18317 :))\'); ?>', $this->compiler->compileString('@section(\'issue#18317 :))\')'));
$this->assertSame('<?php $__env->startSection(name(foo)); ?>', $this->compiler->compileString('@section(name(foo))'));
}
}
4 changes: 4 additions & 0 deletions tests/View/Blade/BladeStackTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ public function testStackIsCompiled()
$string = '@stack(\'foo\')';
$expected = '<?php echo $__env->yieldPushContent(\'foo\'); ?>';
$this->assertEquals($expected, $this->compiler->compileString($string));

$string = '@stack(\'foo))\')';
$expected = '<?php echo $__env->yieldPushContent(\'foo))\'); ?>';
$this->assertEquals($expected, $this->compiler->compileString($string));
}
}
4 changes: 4 additions & 0 deletions tests/View/Blade/BladeUnsetStatementsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ public function testUnsetStatementsAreCompiled()
$string = '@unset ($unset)';
$expected = '<?php unset($unset); ?>';
$this->assertEquals($expected, $this->compiler->compileString($string));

$string = '@unset ($unset)))';
$expected = '<?php unset($unset); ?>))';
$this->assertEquals($expected, $this->compiler->compileString($string));
}
}