diff --git a/src/Facades/Antlers.php b/src/Facades/Antlers.php index e29d69889b..eeeed41ced 100644 --- a/src/Facades/Antlers.php +++ b/src/Facades/Antlers.php @@ -9,7 +9,7 @@ /** * @method static Parser parser() * @method static mixed usingParser(Parser $parser, \Closure $callback) - * @method static AntlersString parse(string $str, array $variables = []) + * @method static AntlersString parse(string $str, array $variables = [], bool $php = false) * @method static AntlersString parseUserContent(string $str, array $variables = []) * @method static string parseLoop(string $content, array $data, bool $supplement = true, array $context = []) * @method static array identifiers(string $content) diff --git a/src/Facades/Endpoint/Parse.php b/src/Facades/Endpoint/Parse.php index 6f447d9654..ebd59ffab6 100644 --- a/src/Facades/Endpoint/Parse.php +++ b/src/Facades/Endpoint/Parse.php @@ -22,7 +22,7 @@ class Parse */ public function template($str, $variables = [], $context = [], $php = false) { - return Antlers::parse($str, $variables, $context, $php); + return Antlers::parse($str, array_merge($variables, $context), $php); } /** diff --git a/src/Providers/ViewServiceProvider.php b/src/Providers/ViewServiceProvider.php index b16050a7f3..ed2ab54bbb 100644 --- a/src/Providers/ViewServiceProvider.php +++ b/src/Providers/ViewServiceProvider.php @@ -102,6 +102,7 @@ private function registerAntlers() $runtimeConfig->guardedContentVariablePatterns = config('statamic.antlers.guardedContentVariables', []); $runtimeConfig->guardedContentTagPatterns = config('statamic.antlers.guardedContentTags', []); $runtimeConfig->guardedContentModifiers = config('statamic.antlers.guardedContentModifiers', []); + $runtimeConfig->isPhpEnabled = config('statamic.antlers.allowPhp', true); $runtimeConfig->allowPhpInUserContent = config('statamic.antlers.allowPhpInContent', false); $runtimeConfig->allowMethodsInUserContent = config('statamic.antlers.allowMethodsInContent', false); diff --git a/src/View/Antlers/Antlers.php b/src/View/Antlers/Antlers.php index b1dc611cfb..0a6829ff3c 100644 --- a/src/View/Antlers/Antlers.php +++ b/src/View/Antlers/Antlers.php @@ -27,9 +27,17 @@ public function usingParser(Parser $parser, Closure $callback) return $contents; } - public function parse($str, $variables = []) + public function parse($str, $variables = [], $php = false) { - return $this->parser()->parse($str, $variables); + $parser = $this->parser(); + $previousState = GlobalRuntimeState::$isPhpEnabled; + GlobalRuntimeState::$isPhpEnabled = $php; + + try { + return $parser->parse($str, $variables); + } finally { + GlobalRuntimeState::$isPhpEnabled = $previousState; + } } public function parseUserContent($str, $variables = []) diff --git a/src/View/Antlers/Language/Runtime/GlobalRuntimeState.php b/src/View/Antlers/Language/Runtime/GlobalRuntimeState.php index a62a6a11df..09760f8048 100644 --- a/src/View/Antlers/Language/Runtime/GlobalRuntimeState.php +++ b/src/View/Antlers/Language/Runtime/GlobalRuntimeState.php @@ -184,6 +184,13 @@ public static function mergeTagRuntimeAssignments($assignments) */ public static $allowPhpInContent = false; + /** + * Controls whether PHP execution is globally enabled. + * + * @var bool + */ + public static $isPhpEnabled = true; + /** * Controls if method invocations are evaluated in user content. * @@ -273,6 +280,7 @@ public static function resetGlobalState() self::$abandonedNodes = []; self::$isEvaluatingUserData = false; self::$isEvaluatingData = false; + self::$isPhpEnabled = true; self::$userContentEvalState = null; StackReplacementManager::clearStackState(); diff --git a/src/View/Antlers/Language/Runtime/NodeProcessor.php b/src/View/Antlers/Language/Runtime/NodeProcessor.php index 901a5603a6..e1f4b5f121 100644 --- a/src/View/Antlers/Language/Runtime/NodeProcessor.php +++ b/src/View/Antlers/Language/Runtime/NodeProcessor.php @@ -1213,6 +1213,10 @@ public function reduce($processNodes) } if ($node instanceof PhpExecutionNode) { + if (! GlobalRuntimeState::$isPhpEnabled) { + continue; + } + if (GlobalRuntimeState::$isEvaluatingUserData && ! GlobalRuntimeState::$allowPhpInContent) { if (GlobalRuntimeState::$throwErrorOnAccessViolation) { throw ErrorFactory::makeRuntimeError( @@ -2453,7 +2457,7 @@ public function reduce($processNodes) // one last time to make sure we didn't miss anything. $this->stopMeasuringTag(); - if ($this->allowPhp) { + if ($this->allowPhp && GlobalRuntimeState::$isPhpEnabled) { $buffer = $this->evaluatePhp($buffer); } @@ -2468,6 +2472,10 @@ public function reduce($processNodes) */ protected function evaluatePhp($buffer) { + if (! GlobalRuntimeState::$isPhpEnabled) { + return is_array($buffer) ? $buffer : StringUtilities::sanitizePhp($buffer); + } + if (is_array($buffer) || $this->isLoopable($buffer)) { return $buffer; } @@ -2527,6 +2535,10 @@ protected function evaluateDirective(DirectiveNode $directive) protected function evaluateAntlersPhpNode(PhpExecutionNode $node) { + if (! GlobalRuntimeState::$isPhpEnabled) { + return ''; + } + if (! GlobalRuntimeState::$allowPhpInContent && GlobalRuntimeState::$isEvaluatingUserData) { return StringUtilities::sanitizePhp($node->content); } diff --git a/src/View/Antlers/Language/Runtime/RuntimeConfiguration.php b/src/View/Antlers/Language/Runtime/RuntimeConfiguration.php index 548be3452f..8cfefc5670 100644 --- a/src/View/Antlers/Language/Runtime/RuntimeConfiguration.php +++ b/src/View/Antlers/Language/Runtime/RuntimeConfiguration.php @@ -98,6 +98,13 @@ class RuntimeConfiguration */ public $guardedContentModifiers = []; + /** + * Controls whether PHP execution is globally enabled. + * + * @var bool + */ + public $isPhpEnabled = true; + /** * Indicates if PHP Code should be evaluated in user content. * diff --git a/src/View/Antlers/Language/Runtime/RuntimeParser.php b/src/View/Antlers/Language/Runtime/RuntimeParser.php index 10c424dd15..9f95dae068 100644 --- a/src/View/Antlers/Language/Runtime/RuntimeParser.php +++ b/src/View/Antlers/Language/Runtime/RuntimeParser.php @@ -138,6 +138,7 @@ public function __construct(DocumentParser $documentParser, NodeProcessor $nodeP */ public function setRuntimeConfiguration(RuntimeConfiguration $configuration) { + GlobalRuntimeState::$isPhpEnabled = $configuration->isPhpEnabled; GlobalRuntimeState::$allowPhpInContent = $configuration->allowPhpInUserContent; GlobalRuntimeState::$allowMethodsInContent = $configuration->allowMethodsInUserContent; GlobalRuntimeState::$throwErrorOnAccessViolation = $configuration->throwErrorOnAccessViolation; diff --git a/tests/Antlers/Runtime/PhpDisabledTest.php b/tests/Antlers/Runtime/PhpDisabledTest.php new file mode 100644 index 0000000000..3e19a4ef69 --- /dev/null +++ b/tests/Antlers/Runtime/PhpDisabledTest.php @@ -0,0 +1,51 @@ +assertSame('Before After', $result); + } + + public function test_it_ignores_inline_echo_blocks_when_disabled() + { + $result = (string) Antlers::parse('Before {{$ "hello" $}} After', []); + + $this->assertSame('Before After', $result); + } + + public function test_php_disabled_is_the_default() + { + $result = (string) Antlers::parse('Before {{? echo "hello"; ?}} After', []); + + $this->assertSame('Before After', $result); + } + + public function test_inline_php_tags_disabled_is_the_default() + { + $result = (string) Antlers::parse('Before After', []); + + $this->assertSame('Before <?php echo "hello"; ?> After', $result); + } + + public function test_it_allows_inline_echo_blocks_when_enabled() + { + $result = (string) Antlers::parse('Before {{$ "hello" $}} After', [], true); + + $this->assertSame('Before hello After', $result); + } + + public function test_it_allow_inline_php_blocks_when_enabled() + { + $result = (string) Antlers::parse('Before {{? echo "hello"; ?}} After', [], true); + + $this->assertSame('Before hello After', $result); + } +}