Skip to content

Commit

Permalink
[9.x] Fixes blade tags issue #45424 (#45490)
Browse files Browse the repository at this point in the history
* Fixes #45424 blade issue with ")" character

* Fixes #45424 blade issue with "(" character

* formatting

* Add test for unclosed blade tag calls

Co-authored-by: Taylor Otwell <[email protected]>
  • Loading branch information
imanghafoori1 and taylorotwell authored Jan 10, 2023
1 parent 30408c5 commit e4fdb29
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 12 deletions.
65 changes: 58 additions & 7 deletions src/Illuminate/View/Compilers/BladeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -499,16 +499,67 @@ protected function compileExtensions($value)
/**
* Compile Blade statements that start with "@".
*
* @param string $value
* @param string $template
* @return string
*/
protected function compileStatements($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 find the right one.
while (isset($match[4]) &&
Str::endsWith($match[0], ')') &&
! $this->hasEvenNumberOfParentheses($match[0])) {
$rest = Str::before(Str::after($template, $match[0]), ')');

$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;
}

/**
* Determine if the given expression has the same number of opening and closing parentheses.
*
* @param string $expression
* @return bool
*/
protected function hasEvenNumberOfParentheses(string $expression)
{
return preg_replace_callback(
'/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', function ($match) {
return $this->compileStatement($match);
}, $value
);
$tokens = token_get_all('<?php '.$expression);

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

$opening = 0;
$closing = 0;

foreach ($tokens as $token) {
if ($token == ')') {
$closing++;
} elseif ($token == '(') {
$opening++;
}
}

return $opening === $closing;
}

/**
Expand Down
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(-(("])'));
}
}
59 changes: 54 additions & 5 deletions tests/View/Blade/BladePhpStatementsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,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 Expand Up @@ -92,4 +118,27 @@ public function testStringWithEscapingDataValue()

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

public function testUnclosedParenthesisForBladeTags()
{
$string = "<span @class(['(']></span>";
$expected = "<span class=\"<?php echo \Illuminate\Support\Arr::toCssClasses([]) ?>\"(['(']></span>";

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

$string = "<span @class(['']></span>";
$expected = "<span class=\"<?php echo \Illuminate\Support\Arr::toCssClasses([]) ?>\"(['']></span>";

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

$string = "<span @class([')']></span>";
$expected = "<span @class([')']></span>";

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

$string = "<span @class(['))']></span>";
$expected = "<span @class(['))']></span>";

$this->assertEquals($expected, $this->compiler->compileString($string));
}
}
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));
}
}

0 comments on commit e4fdb29

Please sign in to comment.