diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 3042b9a594c..cb91423dde1 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -1041,7 +1041,7 @@ public function autoGeneratedTitle() // Since the slug is generated from the title, we'll avoid augmenting // the slug which could result in an infinite loop in some cases. - $title = $this->withLocale($this->site()->lang(), fn () => (string) Antlers::parseUserContent($format, $this->augmented()->except('slug')->all())); + $title = $this->withLocale($this->site()->lang(), fn () => (string) Antlers::parse($format, $this->augmented()->except('slug')->all())); return trim($title); } @@ -1065,7 +1065,7 @@ private function resolvePreviewTargetUrl($format) }, $format); } - return (string) Antlers::parseUserContent($format, array_merge($this->routeData(), [ + return (string) Antlers::parse($format, array_merge($this->routeData(), [ 'config' => Cascade::config(), 'site' => $this->site(), 'uri' => $this->uri(), diff --git a/src/Facades/Antlers.php b/src/Facades/Antlers.php index e29d69889bd..c6b285ce9c0 100644 --- a/src/Facades/Antlers.php +++ b/src/Facades/Antlers.php @@ -9,9 +9,8 @@ /** * @method static Parser parser() * @method static mixed usingParser(Parser $parser, \Closure $callback) - * @method static AntlersString parse(string $str, array $variables = []) - * @method static AntlersString parseUserContent(string $str, array $variables = []) - * @method static string parseLoop(string $content, array $data, bool $supplement = true, array $context = []) + * @method static AntlersString parse(string $str, array $variables = [], bool $trusted = false) + * @method static string parseLoop(string $content, array $data, bool $supplement = true, array $context = [], bool $trusted = false) * @method static array identifiers(string $content) * * @see \Statamic\View\Antlers\Antlers diff --git a/src/Facades/Endpoint/Parse.php b/src/Facades/Endpoint/Parse.php index 6f447d96541..1f7865da6d0 100644 --- a/src/Facades/Endpoint/Parse.php +++ b/src/Facades/Endpoint/Parse.php @@ -17,12 +17,12 @@ class Parse * @param string $str String to parse * @param array $variables Variables to use * @param array $context Contextual variables to also use - * @param bool $php Whether PHP should be allowed + * @param bool $trusted Whether the template should be treated as trusted * @return string */ - public function template($str, $variables = [], $context = [], $php = false) + public function template($str, $variables = [], $context = [], $trusted = false) { - return Antlers::parse($str, $variables, $context, $php); + return Antlers::parse($str, array_merge($variables, $context), $trusted); } /** @@ -32,12 +32,12 @@ public function template($str, $variables = [], $context = [], $php = false) * @param array $data Variables to use, in a multidimensional array * @param bool $supplement Whether to supplement with contextual values * @param array $context Contextual variables to also use - * @param bool $php Whether PHP should be allowed + * @param bool $trusted Whether the template should be treated as trusted * @return string */ - public function templateLoop($content, $data, $supplement = true, $context = [], $php = false) + public function templateLoop($content, $data, $supplement = true, $context = [], $trusted = false) { - return Antlers::parseLoop($content, $data, $supplement, $context, $php); + return Antlers::parseLoop($content, $data, $supplement, $context, $trusted); } /** diff --git a/src/Facades/Parse.php b/src/Facades/Parse.php index e7e44198566..eb2de226837 100644 --- a/src/Facades/Parse.php +++ b/src/Facades/Parse.php @@ -6,8 +6,8 @@ use Statamic\View\Antlers\AntlersString; /** - * @method static AntlersString template($str, $variables = [], $context = [], $php = false) - * @method static string templateLoop($content, $data, $supplement = true, $context = [], $php = false) + * @method static AntlersString template($str, $variables = [], $context = [], $trusted = false) + * @method static string templateLoop($content, $data, $supplement = true, $context = [], $trusted = false) * @method static array YAML($str) * @method static array frontMatter($string) * @method static mixed env($val) diff --git a/src/Forms/Email.php b/src/Forms/Email.php index 0ce40bf5671..076bc20b279 100644 --- a/src/Forms/Email.php +++ b/src/Forms/Email.php @@ -245,7 +245,7 @@ protected function parseConfig(array $config) return collect($config)->map(function ($value) { $value = Parse::env($value); // deprecated - return (string) Antlers::parseUserContent($value, array_merge( + return (string) Antlers::parse($value, array_merge( ['config' => Cascade::config()], $this->getGlobalsData(), $this->submissionData, diff --git a/src/Modifiers/CoreModifiers.php b/src/Modifiers/CoreModifiers.php index b6f8e5449b9..827d121d4ed 100644 --- a/src/Modifiers/CoreModifiers.php +++ b/src/Modifiers/CoreModifiers.php @@ -31,6 +31,7 @@ use Statamic\Support\Dumper; use Statamic\Support\Html; use Statamic\Support\Str; +use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState; use Stringy\StaticStringy as Stringy; class CoreModifiers extends Modifier @@ -119,7 +120,9 @@ public function ampersandList($value, $params) */ public function antlers($value, $params, $context) { - return (string) Antlers::parse($value, $context); + $trusted = Arr::get($params, 0) === 'trusted' && ! GlobalRuntimeState::$isEvaluatingUserData; + + return (string) Antlers::parse($value, $context, $trusted); } /** @@ -1872,7 +1875,7 @@ public function partial($value, $params, $context) $partial = 'partials/'.$name.'.html'; - return Parse::template(File::disk('resources')->get($partial), $value); + return Parse::template(File::disk('resources')->get($partial), $value, trusted: true); } /** diff --git a/src/Providers/ViewServiceProvider.php b/src/Providers/ViewServiceProvider.php index b16050a7f36..3da868a5c41 100644 --- a/src/Providers/ViewServiceProvider.php +++ b/src/Providers/ViewServiceProvider.php @@ -102,6 +102,99 @@ private function registerAntlers() $runtimeConfig->guardedContentVariablePatterns = config('statamic.antlers.guardedContentVariables', []); $runtimeConfig->guardedContentTagPatterns = config('statamic.antlers.guardedContentTags', []); $runtimeConfig->guardedContentModifiers = config('statamic.antlers.guardedContentModifiers', []); + $runtimeConfig->allowedContentTagPatterns = config('statamic.antlers.allowedContentTags', [ + 'obfuscate:*', + 'trans:*', + 'trans_choice:*', + 'widont:*', + ]); + $runtimeConfig->allowedContentModifiers = config('statamic.antlers.allowedContentModifiers', [ + 'add_query_param', + 'add_slashes', + 'ascii', + 'at', + 'background_position', + 'bool_string', + 'camelize', + 'cdata', + 'ceil', + 'collapse_whitespace', + 'count_substring', + 'dashify', + 'decode', + 'deslugify', + 'divide', + 'ends_with', + 'ensure_left', + 'ensure_right', + 'entities', + 'explode', + 'extension', + 'floor', + 'format', + 'format_number', + 'format_translated', + 'has_lower_case', + 'has_upper_case', + 'headline', + 'hex_to_rgb', + 'insert', + 'is_alpha', + 'is_alphanumeric', + 'is_blank', + 'is_email', + 'is_external_url', + 'is_json', + 'is_lowercase', + 'is_numeric', + 'is_uppercase', + 'is_url', + 'join', + 'kebab', + 'lcfirst', + 'localize', + 'upper', + 'lower', + 'md5', + 'mod', + 'multiply', + 'obfuscate', + 'obfuscate_email', + 'parse_url', + 'pathinfo', + 'rawurlencode', + 'remove_left', + 'remove_query_param', + 'remove_right', + 'replace', + 'round', + 'safe_truncate', + 'sanitize', + 'slugify', + 'snake', + 'starts_with', + 'str_pad', + 'str_pad_both', + 'str_pad_left', + 'str_pad_right', + 'strip_tags', + 'studly', + 'subtract', + 'substr', + 'sum', + 'swap_case', + 'title', + 'to_bool', + 'to_string', + 'trans', + 'trans_choice', + 'trim', + 'truncate', + 'ucfirst', + 'urldecode', + 'urlencode', + 'widont', + ]); $runtimeConfig->allowPhpInUserContent = config('statamic.antlers.allowPhpInContent', false); $runtimeConfig->allowMethodsInUserContent = config('statamic.antlers.allowMethodsInContent', false); diff --git a/src/Tags/Tags.php b/src/Tags/Tags.php index b001cf348e3..4f32b7024d7 100644 --- a/src/Tags/Tags.php +++ b/src/Tags/Tags.php @@ -10,6 +10,7 @@ use Statamic\Facades\Antlers; use Statamic\Support\Arr; use Statamic\Support\Traits\Hookable; +use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState; abstract class Tags { @@ -207,8 +208,10 @@ public function parse($data = []) } return Antlers::usingParser($this->parser, function ($antlers) use ($data) { + $trusted = ! GlobalRuntimeState::$isEvaluatingUserData; + return $antlers - ->parse($this->content, array_merge($this->context->all(), $data)) + ->parse($this->content, array_merge($this->context->all(), $data), $trusted) ->withoutExtractions(); }); } @@ -244,8 +247,10 @@ public function parseLoop($data, $supplement = true) } return Antlers::usingParser($this->parser, function ($antlers) use ($data, $supplement) { + $trusted = ! GlobalRuntimeState::$isEvaluatingUserData; + return $antlers - ->parseLoop($this->content, $data, $supplement, $this->context->all()) + ->parseLoop($this->content, $data, $supplement, $this->context->all(), $trusted) ->withoutExtractions(); }); } diff --git a/src/View/Antlers/Antlers.php b/src/View/Antlers/Antlers.php index b1dc611cfbc..606ac973cb0 100644 --- a/src/View/Antlers/Antlers.php +++ b/src/View/Antlers/Antlers.php @@ -27,20 +27,16 @@ public function usingParser(Parser $parser, Closure $callback) return $contents; } - public function parse($str, $variables = []) + public function parse($str, $variables = [], $trusted = false) { - return $this->parser()->parse($str, $variables); - } - - public function parseUserContent($str, $variables = []) - { - $isEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData; - GlobalRuntimeState::$isEvaluatingUserData = true; + $parser = $this->parser(); + $previousState = GlobalRuntimeState::$isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = ! $trusted; try { - return $this->parser()->parse($str, $variables); + return $parser->parse($str, $variables); } finally { - GlobalRuntimeState::$isEvaluatingUserData = $isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = $previousState; } } @@ -51,11 +47,12 @@ public function parseUserContent($str, $variables = []) * @param array $data * @param bool $supplement * @param array $context + * @param bool $trusted * @return string */ - public function parseLoop($content, $data, $supplement = true, $context = []) + public function parseLoop($content, $data, $supplement = true, $context = [], $trusted = false) { - return new AntlersLoop($this->parser(), $content, $data, $supplement, $context); + return new AntlersLoop($this->parser(), $content, $data, $supplement, $context, $trusted); } public function identifiers(string $content): array diff --git a/src/View/Antlers/AntlersLoop.php b/src/View/Antlers/AntlersLoop.php index d3e64222e57..4af1a1b4bc9 100644 --- a/src/View/Antlers/AntlersLoop.php +++ b/src/View/Antlers/AntlersLoop.php @@ -2,6 +2,8 @@ namespace Statamic\View\Antlers; +use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState; + class AntlersLoop extends AntlersString { protected $parser; @@ -9,17 +11,31 @@ class AntlersLoop extends AntlersString protected $variables; protected $supplement; protected $context; + protected $trusted; - public function __construct($parser, $string, $variables, $supplement, $context) + public function __construct($parser, $string, $variables, $supplement, $context, $trusted = false) { $this->parser = $parser; $this->string = $string; $this->variables = $variables; $this->supplement = $supplement; $this->context = $context; + $this->trusted = $trusted; } public function __toString() + { + $previousIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = ! $this->trusted; + + try { + return $this->renderLoopContent(); + } finally { + GlobalRuntimeState::$isEvaluatingUserData = $previousIsEvaluatingUserData; + } + } + + private function renderLoopContent() { $total = count($this->variables); $i = 0; diff --git a/src/View/Antlers/Language/Runtime/GlobalRuntimeState.php b/src/View/Antlers/Language/Runtime/GlobalRuntimeState.php index a0d2b481508..e0ebc3b05ca 100644 --- a/src/View/Antlers/Language/Runtime/GlobalRuntimeState.php +++ b/src/View/Antlers/Language/Runtime/GlobalRuntimeState.php @@ -77,7 +77,7 @@ class GlobalRuntimeState * * @var bool */ - public static $isEvaluatingUserData = false; + public static $isEvaluatingUserData = true; public static $isEvaluatingData = false; @@ -163,6 +163,13 @@ public static function mergeTagRuntimeAssignments($assignments) */ public static $bannedContentTagPaths = []; + /** + * A list of all allowed content tag paths. + * + * @var string[] + */ + public static $allowedContentTagPaths = []; + /** * A list of all invalid modifier paths. * @@ -177,6 +184,13 @@ public static function mergeTagRuntimeAssignments($assignments) */ public static $bannedContentModifierPaths = []; + /** + * A list of all allowed content modifier paths. + * + * @var string[] + */ + public static $allowedContentModifierPaths = []; + /** * Controls if PHP is evaluated in user content. * @@ -233,7 +247,7 @@ public static function resetGlobalState() self::$yieldCount = 0; self::$yieldStacks = []; self::$abandonedNodes = []; - self::$isEvaluatingUserData = false; + self::$isEvaluatingUserData = true; self::$isEvaluatingData = false; self::$userContentEvalState = null; diff --git a/src/View/Antlers/Language/Runtime/ModifierManager.php b/src/View/Antlers/Language/Runtime/ModifierManager.php index f715550bbaf..8f7871555c7 100644 --- a/src/View/Antlers/Language/Runtime/ModifierManager.php +++ b/src/View/Antlers/Language/Runtime/ModifierManager.php @@ -38,11 +38,35 @@ public static function guardRuntimeModifier($modifierName) self::$lastModifierName = $modifierName; if (GlobalRuntimeState::$isEvaluatingUserData) { + $allowList = GlobalRuntimeState::$allowedContentModifierPaths; $guardList = GlobalRuntimeState::$bannedContentModifierPaths; - } else { - $guardList = GlobalRuntimeState::$bannedModifierPaths; + + $isAllowed = Str::is($allowList, $modifierName); + $isBlocked = ! empty($guardList) && Str::is($guardList, $modifierName); + + if (! $isAllowed || $isBlocked) { + Log::warning('Runtime Access Violation: '.$modifierName, [ + 'modifier' => $modifierName, + 'file' => GlobalRuntimeState::$currentExecutionFile, + 'trace' => GlobalRuntimeState::$templateFileStack, + ]); + + if (GlobalRuntimeState::$throwErrorOnAccessViolation) { + throw ErrorFactory::makeRuntimeError( + AntlersErrorCodes::RUNTIME_PROTECTED_MODIFIER_ACCESS, + null, + 'Protected tag access.' + ); + } + + return false; + } + + return true; } + $guardList = GlobalRuntimeState::$bannedModifierPaths; + if (empty($guardList)) { return true; } diff --git a/src/View/Antlers/Language/Runtime/NodeProcessor.php b/src/View/Antlers/Language/Runtime/NodeProcessor.php index 4450d838d32..ba675fa799b 100644 --- a/src/View/Antlers/Language/Runtime/NodeProcessor.php +++ b/src/View/Antlers/Language/Runtime/NodeProcessor.php @@ -1075,11 +1075,35 @@ public function evaluateDeferredVariable(AbstractNode $deferredNode) public function guardRuntimeTag($tagCheck) { if (GlobalRuntimeState::$isEvaluatingUserData) { + $allowList = GlobalRuntimeState::$allowedContentTagPaths; $guardList = GlobalRuntimeState::$bannedContentTagPaths; - } else { - $guardList = GlobalRuntimeState::$bannedTagPaths; + + $isAllowed = Str::is($allowList, $tagCheck); + $isBlocked = ! empty($guardList) && Str::is($guardList, $tagCheck); + + if (! $isAllowed || $isBlocked) { + Log::warning('Runtime Access Violation: '.$tagCheck, [ + 'tag' => $tagCheck, + 'file' => GlobalRuntimeState::$currentExecutionFile, + 'trace' => GlobalRuntimeState::$templateFileStack, + ]); + + if (GlobalRuntimeState::$throwErrorOnAccessViolation) { + throw ErrorFactory::makeRuntimeError( + AntlersErrorCodes::RUNTIME_PROTECTED_TAG_ACCESS, + null, + 'Protected tag access.' + ); + } + + return false; + } + + return true; } + $guardList = GlobalRuntimeState::$bannedTagPaths; + if (empty($guardList)) { return true; } diff --git a/src/View/Antlers/Language/Runtime/RuntimeConfiguration.php b/src/View/Antlers/Language/Runtime/RuntimeConfiguration.php index 548be3452f0..8226380984f 100644 --- a/src/View/Antlers/Language/Runtime/RuntimeConfiguration.php +++ b/src/View/Antlers/Language/Runtime/RuntimeConfiguration.php @@ -84,6 +84,13 @@ class RuntimeConfiguration */ public $guardedContentTagPatterns = []; + /** + * A list of all allowed content tag patterns. + * + * @var string[] + */ + public $allowedContentTagPatterns = []; + /** * A list of all invalid modifier patterns. * @@ -98,6 +105,13 @@ class RuntimeConfiguration */ public $guardedContentModifiers = []; + /** + * A list of all allowed content modifier patterns. + * + * @var string[] + */ + public $allowedContentModifiers = []; + /** * 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 4d7d32461ab..9344ca09dbd 100644 --- a/src/View/Antlers/Language/Runtime/RuntimeParser.php +++ b/src/View/Antlers/Language/Runtime/RuntimeParser.php @@ -140,8 +140,10 @@ public function setRuntimeConfiguration(RuntimeConfiguration $configuration) GlobalRuntimeState::$bannedContentVarPaths = $configuration->guardedContentVariablePatterns; GlobalRuntimeState::$bannedTagPaths = $configuration->guardedTagPatterns; GlobalRuntimeState::$bannedContentTagPaths = $configuration->guardedContentTagPatterns; + GlobalRuntimeState::$allowedContentTagPaths = $configuration->allowedContentTagPatterns; GlobalRuntimeState::$bannedModifierPaths = $configuration->guardedModifiers; GlobalRuntimeState::$bannedContentModifierPaths = $configuration->guardedContentModifiers; + GlobalRuntimeState::$allowedContentModifierPaths = $configuration->allowedContentModifiers; $this->nodeProcessor->setRuntimeConfiguration($configuration); @@ -688,10 +690,11 @@ private function cloneRuntimeParser() $this->antlersLexer, $this->antlersParser ))->allowPhp($this->allowPhp); - // If we are evaluating a tag's scope, we still - // want the overall parser instances to be - // isolated, but we also need the Cascade. - if (GlobalRuntimeState::$evaulatingTagContents) { + foreach ($this->preParsers as $preParser) { + $parser->preparse($preParser); + } + + if ($this->cascade != null) { $parser->cascade($this->cascade); } @@ -758,7 +761,22 @@ public function cascade($cascade) public function parseView($view, $text, $data = []) { + $previousIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = false; + $existingView = $this->view; + try { + return $this->renderViewContent($view, $text, $data); + } finally { + $this->view = $existingView; + array_pop(GlobalRuntimeState::$templateFileStack); + GlobalRuntimeState::$currentExecutionFile = $this->view; + GlobalRuntimeState::$isEvaluatingUserData = $previousIsEvaluatingUserData; + } + } + + private function renderViewContent($view, $text, $data = []) + { $this->view = $view; GlobalRuntimeState::$templateFileStack[] = [$view, null]; @@ -776,15 +794,7 @@ public function parseView($view, $text, $data = []) 'view' => $this->cascade->getViewData($view), ]); - $parsed = $this->renderText($text, $data); - - $this->view = $existingView; - - array_pop(GlobalRuntimeState::$templateFileStack); - - GlobalRuntimeState::$currentExecutionFile = $this->view; - - return $parsed; + return $this->renderText($text, $data); } public function injectNoparse($text) diff --git a/tests/Antlers/ParseUserContentTest.php b/tests/Antlers/ParseUserContentTest.php deleted file mode 100644 index 5177ee45152..00000000000 --- a/tests/Antlers/ParseUserContentTest.php +++ /dev/null @@ -1,91 +0,0 @@ -assertSame( - (string) Antlers::parse('Hello {{ name }}!', ['name' => 'Jason']), - (string) Antlers::parseUserContent('Hello {{ name }}!', ['name' => 'Jason']) - ); - } - - #[Test] - public function it_blocks_php_nodes_in_user_content_mode() - { - Log::shouldReceive('warning') - ->once() - ->with('PHP Node evaluated in user content: {{? echo Str::upper(\'hello\') ?}}', \Mockery::type('array')); - - $result = (string) Antlers::parseUserContent('Text: {{? echo Str::upper(\'hello\') ?}}'); - - $this->assertSame('Text: ', $result); - } - - #[Test] - public function it_blocks_method_calls_in_user_content_mode() - { - Log::shouldReceive('warning') - ->once() - ->with('Method call evaluated in user content.', \Mockery::type('array')); - - $result = (string) Antlers::parseUserContent('{{ object:method("hello") }}', [ - 'object' => new ClassOne(), - ]); - - $this->assertSame('', $result); - } - - #[Test] - public function it_restores_user_data_flag_after_successful_parse() - { - GlobalRuntimeState::$isEvaluatingUserData = false; - - Antlers::parseUserContent('Hello {{ name }}!', ['name' => 'Jason']); - - $this->assertFalse(GlobalRuntimeState::$isEvaluatingUserData); - } - - #[Test] - public function it_restores_user_data_flag_after_parse_exceptions() - { - GlobalRuntimeState::$isEvaluatingUserData = false; - $parser = \Mockery::mock(Parser::class); - $parser->shouldReceive('parse') - ->once() - ->andThrow(new \RuntimeException('Failed to parse user content.')); - - try { - Antlers::usingParser($parser, function ($antlers) { - $antlers->parseUserContent('Hello {{ name }}', ['name' => 'Jason']); - }); - - $this->fail('Expected RuntimeException to be thrown.'); - } catch (\RuntimeException $exception) { - $this->assertSame('Failed to parse user content.', $exception->getMessage()); - } - - $this->assertFalse(GlobalRuntimeState::$isEvaluatingUserData); - } -} diff --git a/tests/Antlers/ParserTestCase.php b/tests/Antlers/ParserTestCase.php index a51e64bed02..d6dacb7eebc 100644 --- a/tests/Antlers/ParserTestCase.php +++ b/tests/Antlers/ParserTestCase.php @@ -163,9 +163,10 @@ protected function parseTemplate($template) return $documentParser->getRenderNodes(); } - protected function parser($data = [], $withCoreTagsAndModifiers = false) + protected function parser($data = [], $withCoreTagsAndModifiers = false, $trusted = true) { GlobalRuntimeState::resetGlobalState(); + GlobalRuntimeState::$isEvaluatingUserData = ! $trusted; $documentParser = new DocumentParser(); $loader = new Loader(); @@ -184,9 +185,10 @@ protected function parser($data = [], $withCoreTagsAndModifiers = false) return new RuntimeParser($documentParser, $processor, new AntlersLexer(), new LanguageParser()); } - protected function renderStringWithConfiguration($text, RuntimeConfiguration $config, $data = [], $withCoreTagsAndModifiers = false) + protected function renderStringWithConfiguration($text, RuntimeConfiguration $config, $data = [], $withCoreTagsAndModifiers = false, $trusted = true) { GlobalRuntimeState::resetGlobalState(); + GlobalRuntimeState::$isEvaluatingUserData = ! $trusted; $documentParser = new DocumentParser(); $loader = new Loader(); @@ -212,10 +214,11 @@ protected function renderStringWithConfiguration($text, RuntimeConfiguration $co return (string) $runtimeParser->parse($text, $data); } - protected function renderString($text, $data = [], $withCoreTagsAndModifiers = false) + protected function renderString($text, $data = [], $withCoreTagsAndModifiers = false, $trusted = true) { ModifierManager::$statamicModifiers = null; GlobalRuntimeState::resetGlobalState(); + GlobalRuntimeState::$isEvaluatingUserData = ! $trusted; $documentParser = new DocumentParser(); $loader = new Loader(); @@ -253,6 +256,18 @@ protected function getParsedRuntimeNodes($text) } protected function getBoolResult($text, $data) + { + $previousState = GlobalRuntimeState::$isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = false; + + try { + return $this->_getBoolResult($text, $data); + } finally { + GlobalRuntimeState::$isEvaluatingUserData = $previousState; + } + } + + private function _getBoolResult($text, $data) { // Create a wrapper region we can get a node from. $nodeText = '{{ '.$text.' }}'; @@ -272,6 +287,18 @@ protected function getBoolResult($text, $data) } protected function evaluateRaw($text, $data = []) + { + $previousState = GlobalRuntimeState::$isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = false; + + try { + return $this->_evaluateRaw($text, $data); + } finally { + GlobalRuntimeState::$isEvaluatingUserData = $previousState; + } + } + + private function _evaluateRaw($text, $data = []) { $text = StringUtilities::normalizeLineEndings($text); @@ -299,6 +326,18 @@ protected function evaluateRaw($text, $data = []) } protected function evaluateBoth($text, $data = []) + { + $previousState = GlobalRuntimeState::$isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = false; + + try { + return $this->_evaluateBoth($text, $data); + } finally { + GlobalRuntimeState::$isEvaluatingUserData = $previousState; + } + } + + private function _evaluateBoth($text, $data = []) { // Create a wrapper region we can get a node from. $nodeText = '{{ '.$text.' }}'; @@ -324,6 +363,18 @@ protected function evaluateBoth($text, $data = []) } protected function evaluate($text, $data = []) + { + $previousState = GlobalRuntimeState::$isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = false; + + try { + return $this->_evaluate($text, $data); + } finally { + GlobalRuntimeState::$isEvaluatingUserData = $previousState; + } + } + + private function _evaluate($text, $data = []) { // Create a wrapper region we can get a node from. $nodeText = '{{ '.$text.' }}'; diff --git a/tests/Antlers/Runtime/ContentAllowListTest.php b/tests/Antlers/Runtime/ContentAllowListTest.php new file mode 100644 index 00000000000..42f70c27ca5 --- /dev/null +++ b/tests/Antlers/Runtime/ContentAllowListTest.php @@ -0,0 +1,207 @@ +makeAntlersTextValue('{{ title | upper }}'); + $result = $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + 'title' => 'hello', + ], true, true); + + $this->assertSame('HELLO', $result); + } + + #[Test] + public function disallowed_modifier_is_blocked_in_user_content() + { + GlobalRuntimeState::$allowedContentModifierPaths = ['upper']; + + $value = $this->makeAntlersTextValue('{{ title | lower }}'); + $result = $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + 'title' => 'HELLO', + ], true, true); + + $this->assertSame('HELLO', $result); + } + + #[Test] + public function empty_modifier_allow_list_blocks_all_modifiers_in_user_content() + { + GlobalRuntimeState::$allowedContentModifierPaths = []; + + $value = $this->makeAntlersTextValue('{{ title | upper }}'); + $result = $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + 'title' => 'hello', + ], true, true); + + $this->assertSame('hello', $result); + } + + #[Test] + public function modifier_block_list_overrides_modifier_allow_list_in_user_content() + { + GlobalRuntimeState::$allowedContentModifierPaths = ['upper']; + GlobalRuntimeState::$bannedContentModifierPaths = ['upper']; + + $value = $this->makeAntlersTextValue('{{ title | upper }}'); + $result = $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + 'title' => 'hello', + ], true, true); + + $this->assertSame('hello', $result); + } + + #[Test] + public function disallowed_modifier_throws_when_access_violations_are_enabled() + { + GlobalRuntimeState::$allowedContentModifierPaths = ['upper']; + GlobalRuntimeState::$throwErrorOnAccessViolation = true; + + $this->expectException(RuntimeException::class); + + $value = $this->makeAntlersTextValue('{{ title | lower }}'); + $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + 'title' => 'HELLO', + ], true, true); + } + + #[Test] + public function allow_list_does_not_affect_modifier_usage_in_trusted_templates() + { + GlobalRuntimeState::$allowedContentModifierPaths = []; + + $result = $this->renderString('{{ title | lower }}', [ + 'title' => 'HELLO', + ], true, true); + + $this->assertSame('hello', $result); + } + + #[Test] + public function allowed_tag_pattern_can_be_used_in_user_content() + { + $this->registerRuntimeTestTag(); + GlobalRuntimeState::$allowedContentTagPaths = ['runtime_test_tag:*']; + + $value = $this->makeAntlersTextValue('{{ runtime_test_tag }}'); + $result = $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + ], true, true); + + $this->assertSame('tag-ok', $result); + } + + #[Test] + public function disallowed_tag_pattern_is_blocked_in_user_content() + { + $this->registerRuntimeTestTag(); + GlobalRuntimeState::$allowedContentTagPaths = ['other_tag']; + + $value = $this->makeAntlersTextValue('{{ runtime_test_tag }}'); + $result = $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + ], true, true); + + $this->assertSame('', $result); + } + + #[Test] + public function empty_tag_allow_list_blocks_all_tags_in_user_content() + { + $this->registerRuntimeTestTag(); + GlobalRuntimeState::$allowedContentTagPaths = []; + + $value = $this->makeAntlersTextValue('{{ runtime_test_tag }}'); + $result = $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + ], true, true); + + $this->assertSame('', $result); + } + + #[Test] + public function tag_block_list_overrides_tag_allow_list_in_user_content() + { + $this->registerRuntimeTestTag(); + GlobalRuntimeState::$allowedContentTagPaths = ['runtime_test_tag:*']; + GlobalRuntimeState::$bannedContentTagPaths = ['runtime_test_tag:*']; + + $value = $this->makeAntlersTextValue('{{ runtime_test_tag }}'); + $result = $this->renderString('{{ text_field }}', [ + 'text_field' => $value, + ], true, true); + + $this->assertSame('', $result); + } + + #[Test] + public function allow_list_does_not_affect_tag_usage_in_trusted_templates() + { + $this->registerRuntimeTestTag(); + GlobalRuntimeState::$allowedContentTagPaths = []; + GlobalRuntimeState::$bannedTagPaths = ['another_tag']; + + $result = $this->renderString('{{ runtime_test_tag }}', [], true, true); + $this->assertSame('tag-ok', $result); + } + + private function makeAntlersTextValue(string $template): Value + { + $textFieldtype = new Text(); + $field = new Field('text_field', [ + 'type' => 'text', + 'antlers' => true, + ]); + + $textFieldtype->setField($field); + + return new Value($template, 'text_field', $textFieldtype); + } + + private function registerRuntimeTestTag(): void + { + (new class extends Tags + { + public static $handle = 'runtime_test_tag'; + + public function index() + { + return 'tag-ok'; + } + })::register(); + } +} diff --git a/tests/Antlers/Runtime/MethodCallTest.php b/tests/Antlers/Runtime/MethodCallTest.php index 4f84b977f95..d7f4782f132 100644 --- a/tests/Antlers/Runtime/MethodCallTest.php +++ b/tests/Antlers/Runtime/MethodCallTest.php @@ -33,10 +33,10 @@ public function test_methods_can_be_called() $this->assertSame('Value: hello', $this->renderString('{{ object:method("hello"):methodTwo() }}', [ 'object' => $object, - ])); + ], false, true)); $this->assertSame('String: hello', $this->renderString('{{ object:method("hello") }}', [ 'object' => $object, - ])); + ], false, true)); } public function test_chained_methods_colon_syntax() @@ -45,7 +45,7 @@ public function test_chained_methods_colon_syntax() $this->assertSame('Value: hello', $this->renderString('{{ object:method("hello"):methodTwo() }}', [ 'object' => $object, - ])); + ], false, true)); } public function test_chained_methods_dot_syntax() @@ -54,7 +54,7 @@ public function test_chained_methods_dot_syntax() $this->assertSame('Value: hello', $this->renderString('{{ object.method("hello").methodTwo() }}', [ 'object' => $object, - ])); + ], false, true)); } public function test_chained_methods_mixed_syntax() @@ -63,7 +63,7 @@ public function test_chained_methods_mixed_syntax() $this->assertSame('Value: hello', $this->renderString('{{ object:method("hello").methodTwo() }}', [ 'object' => $object, - ])); + ], false, true)); } public function test_method_calls_can_be_used_within_conditions_without_explicit_logic_groups() @@ -78,7 +78,7 @@ public function test_method_calls_can_be_used_within_conditions_without_explicit {{ if title && title:length() < 15 }}Yes{{ else }}No{{ endif }} EOT; - $this->assertSame('Yes', $this->renderString($template, $data)); + $this->assertSame('Yes', $this->renderString($template, $data, false, true)); } public function test_method_calls_can_be_used_within_conditions_without_explicit_logic_groups_dot_syntax() @@ -93,7 +93,7 @@ public function test_method_calls_can_be_used_within_conditions_without_explicit {{ if title && title.length() < 15 }}Yes{{ else }}No{{ endif }} EOT; - $this->assertSame('Yes', $this->renderString($template, $data)); + $this->assertSame('Yes', $this->renderString($template, $data, false, true)); } public function test_method_calls_can_be_used_within_conditions_without_explicit_logic_groups_arrow_syntax() @@ -108,7 +108,7 @@ public function test_method_calls_can_be_used_within_conditions_without_explicit {{ if title && title->length() < 15 }}Yes{{ else }}No{{ endif }} EOT; - $this->assertSame('Yes', $this->renderString($template, $data)); + $this->assertSame('Yes', $this->renderString($template, $data, false, true)); } public function test_method_calls_can_be_used_within_conditions_without_explicit_logic_groups_arrow_syntax_with_strict_var() @@ -123,7 +123,7 @@ public function test_method_calls_can_be_used_within_conditions_without_explicit {{ if title && $title->length() < 15 }}Yes{{ else }}No{{ endif }} EOT; - $this->assertSame('Yes', $this->renderString($template, $data)); + $this->assertSame('Yes', $this->renderString($template, $data, false, true)); } public function test_method_calls_can_have_modifiers_applied() @@ -234,7 +234,7 @@ public function test_method_calls_can_have_modifiers_applied() 2012-11-05 00:00:00 EOT; - $this->assertSame($expected, trim($this->renderString($template, $data, true))); + $this->assertSame($expected, trim($this->renderString($template, $data, true, true))); } public function test_method_calls_not_get_called_more_than_declared() @@ -245,7 +245,7 @@ public function test_method_calls_not_get_called_more_than_declared() {{ counter:increment():increment():increment() }} EOT; - $this->assertSame('Count: 3', $this->renderString($template, ['counter' => $counter])); + $this->assertSame('Count: 3', $this->renderString($template, ['counter' => $counter], false, true)); } public function test_dangling_chained_method_calls() @@ -257,7 +257,7 @@ public function test_dangling_chained_method_calls() toAtomString() }} ANTLERS; - $result = $this->renderString($template, ['datetime' => new TestDateTime]); + $result = $this->renderString($template, ['datetime' => new TestDateTime], false, true); $this->assertSame('2001-10-22T00:00:00+00:00', $result); } @@ -281,7 +281,7 @@ public function test_method_calls_blocked_in_user_content() $result = $this->renderString('{{ text_field }}', [ 'text_field' => $value, 'object' => $object, - ]); + ], false, true); $this->assertSame('', $result); } @@ -303,7 +303,7 @@ public function test_method_calls_allowed_in_user_content_when_configured() $result = $this->renderString('{{ text_field }}', [ 'text_field' => $value, 'object' => $object, - ]); + ], false, true); $this->assertSame('String: hello', $result); @@ -329,7 +329,7 @@ public function test_method_calls_in_user_content_throw_when_configured() $this->renderString('{{ text_field }}', [ 'text_field' => $value, 'object' => $object, - ]); + ], false, true); GlobalRuntimeState::$throwErrorOnAccessViolation = false; } @@ -340,7 +340,7 @@ public function test_method_calls_still_work_in_templates() $this->assertSame('String: hello', $this->renderString('{{ object:method("hello") }}', [ 'object' => $object, - ])); + ], false, true)); } public function test_nested_value_does_not_reset_user_data_flag() @@ -372,7 +372,7 @@ public function test_nested_value_does_not_reset_user_data_flag() 'outer_field' => $outerValue, 'nested_field' => $nestedValue, 'object' => $object, - ]); + ], false, true); $this->assertSame('Hello', $result); } diff --git a/tests/Antlers/Runtime/PhpDisabledTest.php b/tests/Antlers/Runtime/PhpDisabledTest.php new file mode 100644 index 00000000000..2e445e72d64 --- /dev/null +++ b/tests/Antlers/Runtime/PhpDisabledTest.php @@ -0,0 +1,139 @@ +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); + } + + public function test_method_calls_are_not_evaluated_when_php_is_disabled() + { + $helper = new class() + { + public $wasCalled = false; + + public function mutate() + { + $this->wasCalled = true; + + return 'changed'; + } + }; + + $result = (string) Antlers::parse('{{ helper:mutate() }}', [ + 'helper' => $helper, + ], false); + + $this->assertSame('', $result); + $this->assertFalse($helper->wasCalled); + } + + public function test_method_calls_are_evaluated_when_php_is_enabled() + { + $helper = new class() + { + public $wasCalled = false; + + public function mutate() + { + $this->wasCalled = true; + + return 'changed'; + } + }; + + $result = (string) Antlers::parse('{{ helper:mutate() }}', [ + 'helper' => $helper, + ], true); + + $this->assertSame('changed', $result); + $this->assertTrue($helper->wasCalled); + } + + public function test_strict_variable_method_calls_are_not_evaluated_when_php_is_disabled() + { + $helper = new class() + { + public $wasCalled = false; + + public function mutate() + { + $this->wasCalled = true; + + return 'changed'; + } + }; + + $result = (string) Antlers::parse('{{ $helper->mutate() }}', [ + 'helper' => $helper, + ], false); + + $this->assertSame('', $result); + $this->assertFalse($helper->wasCalled); + } + + public function test_strict_variable_method_calls_are_evaluated_when_php_is_enabled() + { + $helper = new class() + { + public $wasCalled = false; + + public function mutate() + { + $this->wasCalled = true; + + return 'changed'; + } + }; + + $result = (string) Antlers::parse('{{ $helper->mutate() }}', [ + 'helper' => $helper, + ], true); + + $this->assertSame('changed', $result); + $this->assertTrue($helper->wasCalled); + } +} diff --git a/tests/Antlers/Runtime/PhpEnabledTest.php b/tests/Antlers/Runtime/PhpEnabledTest.php index cd1122defbb..63f02d46296 100644 --- a/tests/Antlers/Runtime/PhpEnabledTest.php +++ b/tests/Antlers/Runtime/PhpEnabledTest.php @@ -3,7 +3,6 @@ namespace Tests\Antlers\Runtime; use Illuminate\Support\Facades\Log; -use PHPUnit\Framework\Attributes\Test; use Statamic\Fields\Field; use Statamic\Fields\Fieldtype; use Statamic\Fields\Value; @@ -27,7 +26,7 @@ public function test_php_has_access_to_scope_data() $this->assertEquals( 'Hello wildernessWILDERNESS!', - (string) $this->parser($data)->allowPhp()->parse('Hello {{ string }}', $data) + (string) $this->parser($data, false, true)->allowPhp()->parse('Hello {{ string }}', $data) ); } @@ -52,7 +51,7 @@ public function test_php_can_be_used_to_output_evaluated_antlers() $data = ['title' => 'Hello, there!']; $expected = StringUtilities::normalizeLineEndings($expected); - $result = StringUtilities::normalizeLineEndings((string) $this->parser($data)->allowPhp()->parse($template, $data)); + $result = StringUtilities::normalizeLineEndings((string) $this->parser($data, false, true)->allowPhp()->parse($template, $data)); $this->assertSame($expected, $result); } @@ -89,7 +88,7 @@ public function test_php_variable_access_inside_loops() EOT; $expected = StringUtilities::normalizeLineEndings($expected); - $result = StringUtilities::normalizeLineEndings((string) $this->parser($data)->allowPhp()->parse($template, $data)); + $result = StringUtilities::normalizeLineEndings((string) $this->parser($data, false, true)->allowPhp()->parse($template, $data)); $this->assertSame($expected, $result); } @@ -140,7 +139,7 @@ public function config(?string $key = null, $fallback = null) $expected = StringUtilities::normalizeLineEndings($expected); $results = StringUtilities::normalizeLineEndings( - (string) $this->parser($data)->setRuntimeConfiguration($config)->allowPhp()->parse($template, $data) + (string) $this->parser($data, false, true)->setRuntimeConfiguration($config)->allowPhp()->parse($template, $data) ); $this->assertSame($expected, $results); @@ -188,7 +187,7 @@ public function config(?string $key = null, $fallback = null) $expected = StringUtilities::normalizeLineEndings($expected); $results = StringUtilities::normalizeLineEndings( - (string) $this->parser($data)->setRuntimeConfiguration($config)->allowPhp()->parse($template, $data) + (string) $this->parser($data, false, true)->setRuntimeConfiguration($config)->allowPhp()->parse($template, $data) ); $this->assertSame($expected, $results); @@ -317,7 +316,7 @@ public function test_implicit_antlers_php_node() } $results = StringUtilities::normalizeLineEndings( - (string) $this->parser($data)->allowPhp()->parse($template, $data) + (string) $this->parser($data, false, true)->allowPhp()->parse($template, $data) ); $expected = StringUtilities::normalizeLineEndings($expected); @@ -446,7 +445,7 @@ public function test_antlers_php_node_can_return_assignments() } $results = StringUtilities::normalizeLineEndings( - (string) $this->parser($data)->allowPhp()->parse($template, $data) + (string) $this->parser($data, false, true)->allowPhp()->parse($template, $data) ); $expected = StringUtilities::normalizeLineEndings($expected); @@ -460,7 +459,7 @@ public function test_antlers_php_node_does_not_remove_literal() {{? $var_1 = 'blog'; $var_2 = 'news'; ?}}ABC{{ var_2 }} EOT; - $this->assertSame('ABCnews', $this->renderString($template)); + $this->assertSame('ABCnews', $this->renderString($template, [], false, true)); } public function test_antlers_php_echo_node() @@ -470,7 +469,7 @@ public function test_antlers_php_echo_node()

Literal Content. {{$ $var $}}

EOT; - $this->assertSame('

Literal Content. hi!

', trim($this->renderString($template))); + $this->assertSame('

Literal Content. hi!

', trim($this->renderString($template, [], false, true))); } public function test_php_node_assignments_within_loops() @@ -511,7 +510,7 @@ public function test_php_node_assignments_within_loops() <1> EOT; - $this->assertSame($expected, trim($this->renderString($template, $data))); + $this->assertSame($expected, trim($this->renderString($template, $data, false, true))); } public function test_assignments_from_php_nodes() @@ -533,7 +532,7 @@ public function test_assignments_from_php_nodes() EOT; - $result = $this->renderString($template, [], true); + $result = $this->renderString($template, [], true, true); $this->assertStringContainsString('', $result); $this->assertStringContainsString('', $result); } diff --git a/tests/Antlers/ScratchTest.php b/tests/Antlers/ScratchTest.php index ea3cf7b3bf5..111e220ce81 100644 --- a/tests/Antlers/ScratchTest.php +++ b/tests/Antlers/ScratchTest.php @@ -22,7 +22,7 @@ public function tag_variables_should_not_leak_outside_its_tag_pair() $template = '{{ title }} {{ collection:test }}{{ title }} {{ /collection:test }} {{ title }}'; $expected = 'Outside One Two Outside'; - $parsed = (string) Antlers::parse($template, ['title' => 'Outside']); + $parsed = (string) Antlers::parse($template, ['title' => 'Outside'], true); $this->assertEquals($expected, $parsed); } @@ -40,9 +40,9 @@ public function interpolated_parameter_with_extra_space_should_work() { $this->app['statamic.tags']['test'] = \Tests\Fixtures\Addon\Tags\TestTags::class; - $this->assertEquals('baz', (string) Antlers::parse('{{ test variable="{bar }" }}', ['bar' => 'baz'])); - $this->assertEquals('baz', (string) Antlers::parse('{{ test variable="{ bar}" }}', ['bar' => 'baz'])); - $this->assertEquals('baz', (string) Antlers::parse('{{ test variable="{ bar }" }}', ['bar' => 'baz'])); + $this->assertEquals('baz', (string) Antlers::parse('{{ test variable="{bar }" }}', ['bar' => 'baz'], true)); + $this->assertEquals('baz', (string) Antlers::parse('{{ test variable="{ bar}" }}', ['bar' => 'baz'], true)); + $this->assertEquals('baz', (string) Antlers::parse('{{ test variable="{ bar }" }}', ['bar' => 'baz'], true)); } public function test_runtime_can_parse_expanded_ascii_characters() diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 8493c26d4f8..69646d24022 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -2453,14 +2453,14 @@ public function it_augments_in_the_parser() $container->shouldReceive('url')->andReturn('/container'); $asset = (new Asset)->container($container)->path('path/to/test.txt'); - $this->assertEquals('/container/path/to/test.txt', Antlers::parse('{{ asset }}', ['asset' => $asset])); + $this->assertEquals('/container/path/to/test.txt', Antlers::parse('{{ asset }}', ['asset' => $asset], true)); - $this->assertEquals('path/to/test.txt', Antlers::parse('{{ asset }}{{ path }}{{ /asset }}', ['asset' => $asset])); + $this->assertEquals('path/to/test.txt', Antlers::parse('{{ asset }}{{ path }}{{ /asset }}', ['asset' => $asset], true)); - $this->assertEquals('test.txt', Antlers::parse('{{ asset:basename }}', ['asset' => $asset])); + $this->assertEquals('test.txt', Antlers::parse('{{ asset:basename }}', ['asset' => $asset], true)); // The "asset" Tag will output nothing when an invalid asset src is passed. It doesn't throw an exception. - $this->assertEquals('', Antlers::parse('{{ asset src="invalid" }}{{ basename }}{{ /asset }}', ['asset' => $asset])); + $this->assertEquals('', Antlers::parse('{{ asset src="invalid" }}{{ basename }}{{ /asset }}', ['asset' => $asset], true)); } #[Test] diff --git a/tests/Data/Entries/CollectionTest.php b/tests/Data/Entries/CollectionTest.php index 44060a0d9af..a62e7f0d1a4 100644 --- a/tests/Data/Entries/CollectionTest.php +++ b/tests/Data/Entries/CollectionTest.php @@ -20,7 +20,6 @@ use Statamic\Exceptions\CollectionNotFoundException; use Statamic\Facades; use Statamic\Facades\Antlers; -use Statamic\Facades\Site; use Statamic\Facades\User; use Statamic\Fields\Blueprint; use Statamic\Structures\CollectionStructure; @@ -813,12 +812,12 @@ public function it_augments_in_the_parser() $this->assertEquals('test', Antlers::parse('{{ collection }}', ['collection' => $collection])); - $this->assertEquals('test Test', Antlers::parse('{{ collection }}{{ handle }} {{ title }}{{ /collection }}', ['collection' => $collection])); + $this->assertEquals('test Test', Antlers::parse('{{ collection }}{{ handle }} {{ title }}{{ /collection }}', ['collection' => $collection], true)); $this->assertEquals('test', Antlers::parse('{{ collection:handle }}', ['collection' => $collection])); try { - Antlers::parse('{{ collection from="somewhere" }}{{ title }}{{ /collection }}', ['collection' => $collection]); + Antlers::parse('{{ collection from="somewhere" }}{{ title }}{{ /collection }}', ['collection' => $collection], true); $this->fail('Exception not thrown'); } catch (CollectionNotFoundException $e) { $this->assertEquals('Collection [somewhere] not found', $e->getMessage()); diff --git a/tests/Fieldtypes/IconTest.php b/tests/Fieldtypes/IconTest.php index 13c1b6dc3b5..5d284f04b6a 100644 --- a/tests/Fieldtypes/IconTest.php +++ b/tests/Fieldtypes/IconTest.php @@ -14,7 +14,7 @@ class IconTest extends TestCase #[Test] public function it_finds_default_icons() { - $result = (string) Antlers::parse('{{ svg src="{test|raw}" }}', ['test' => new Value('add', $this->fieldtype())]); + $result = (string) Antlers::parse('{{ svg src="{test|raw}" }}', ['test' => new Value('add', $this->fieldtype())], true); $this->assertStringContainsString(' new Value('add', $this->fieldtype())]); + $result = (string) Antlers::parse('{{ svg :src="test" class="w-4 h-4" sanitize="false" }}', ['test' => new Value('add', $this->fieldtype())], true); $this->assertStringContainsString('fieldtype(['smartypants' => true, 'antlers' => true]); $value = <<<'EOT' -{{ "this is a string" | replace(" is ", " isnt ") | reverse }} +{{ "this is a string" | replace(" is ", " isnt ") | upper }} EOT; $value = new Value($value, 'markdown', $md); $expected = <<<'EOT' -

gnirts a tnsi siht

+

THIS ISNT A STRING

EOT; $this->assertEqualsTrimmed($expected, $value->antlersValue(app(\Statamic\Contracts\View\Antlers\Parser::class), [])); diff --git a/tests/Modifiers/AntlersTest.php b/tests/Modifiers/AntlersTest.php index 2141ab96434..534c7ab87a1 100644 --- a/tests/Modifiers/AntlersTest.php +++ b/tests/Modifiers/AntlersTest.php @@ -4,10 +4,19 @@ use PHPUnit\Framework\Attributes\Test; use Statamic\Modifiers\Modify; +use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState; use Tests\TestCase; class AntlersTest extends TestCase { + public function tearDown(): void + { + GlobalRuntimeState::$allowedContentModifierPaths = []; + GlobalRuntimeState::$isEvaluatingUserData = true; + + parent::tearDown(); + } + #[Test] public function it_parses_as_antlers(): void { @@ -15,8 +24,24 @@ public function it_parses_as_antlers(): void $this->assertEquals('foo alfa bar bravo', $modified); } - private function modify($value, array $context = []) + #[Test] + public function trusted_argument_does_not_escalate_when_current_runtime_is_untrusted(): void + { + GlobalRuntimeState::$isEvaluatingUserData = true; + + $this->assertSame('foo bar ', $this->modify('foo {{ foo }} {{$ "hello" $}}', ['foo' => 'bar'], ['trusted'])); + } + + #[Test] + public function trusted_argument_parses_in_trusted_mode_when_current_runtime_is_already_trusted(): void + { + GlobalRuntimeState::$isEvaluatingUserData = false; + + $this->assertSame('foo bar hello', $this->modify('foo {{ foo }} {{$ "hello" $}}', ['foo' => 'bar'], ['trusted'])); + } + + private function modify($value, array $context = [], array $params = []) { - return Modify::value($value)->context($context)->antlers()->fetch(); + return Modify::value($value)->context($context)->antlers($params)->fetch(); } } diff --git a/tests/Modifiers/CompactTest.php b/tests/Modifiers/CompactTest.php index e03d972b3e3..247b0a2a87e 100644 --- a/tests/Modifiers/CompactTest.php +++ b/tests/Modifiers/CompactTest.php @@ -25,7 +25,7 @@ class CompactTest extends TestCase private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/Sites/SiteTest.php b/tests/Sites/SiteTest.php index 9dcdfe64aba..93ebb17f629 100644 --- a/tests/Sites/SiteTest.php +++ b/tests/Sites/SiteTest.php @@ -293,7 +293,7 @@ public function it_casts_the_handle_to_a_string() $site = new Site('test', []); $this->assertSame('test', (string) $site); - $this->assertEquals('test', Antlers::parse('{{ site }}', ['site' => $site])); + $this->assertEquals('test', Antlers::parse('{{ site }}', ['site' => $site], true)); } #[Test] diff --git a/tests/StaticCaching/NocacheTagsTest.php b/tests/StaticCaching/NocacheTagsTest.php index e21daa639f9..5a377c5e9a2 100644 --- a/tests/StaticCaching/NocacheTagsTest.php +++ b/tests/StaticCaching/NocacheTagsTest.php @@ -202,6 +202,6 @@ public function it_only_adds_explicitly_defined_fields_of_context_to_session() private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } } diff --git a/tests/Tags/CacheTagTest.php b/tests/Tags/CacheTagTest.php index 390491f7e61..c1b95211158 100644 --- a/tests/Tags/CacheTagTest.php +++ b/tests/Tags/CacheTagTest.php @@ -347,6 +347,6 @@ private function tag($tag, $data = []) { GlobalRuntimeState::resetGlobalState(); - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } } diff --git a/tests/Tags/ChildrenTest.php b/tests/Tags/ChildrenTest.php index 7dc283db9d8..6452c7f17dd 100644 --- a/tests/Tags/ChildrenTest.php +++ b/tests/Tags/ChildrenTest.php @@ -29,7 +29,7 @@ public function setUp(): void private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } private function setUpEntries() diff --git a/tests/Tags/CookieTagTest.php b/tests/Tags/CookieTagTest.php index dd8a542634f..96fe3e46151 100644 --- a/tests/Tags/CookieTagTest.php +++ b/tests/Tags/CookieTagTest.php @@ -13,13 +13,13 @@ public function it_gets_cookie_value() { request()->cookies->set('nineties', 'rad'); - $this->assertEquals('rad', Antlers::parse('{{ cookie:value key="nineties" }}')); + $this->assertEquals('rad', Antlers::parse('{{ cookie:value key="nineties" }}', [], true)); } #[Test] public function it_gets_default_cookie_value() { - $this->assertEquals('1', Antlers::parse('{{ cookie:value key="nineties" default="1" }}')); + $this->assertEquals('1', Antlers::parse('{{ cookie:value key="nineties" default="1" }}', [], true)); } #[Test] @@ -27,7 +27,7 @@ public function it_gets_cookie_value_using_wildcard() { request()->cookies->set('nineties', 'rad'); - $this->assertEquals('rad', Antlers::parse('{{ cookie:nineties }}')); - $this->assertEquals('rad', Antlers::parse('{{ cookie:key }}', ['key' => 'nineties'])); + $this->assertEquals('rad', Antlers::parse('{{ cookie:nineties }}', [], true)); + $this->assertEquals('rad', Antlers::parse('{{ cookie:key }}', ['key' => 'nineties'], true)); } } diff --git a/tests/Tags/Dictionary/DictionaryTagTest.php b/tests/Tags/Dictionary/DictionaryTagTest.php index 5270ac82bbc..534efcaa2a1 100644 --- a/tests/Tags/Dictionary/DictionaryTagTest.php +++ b/tests/Tags/Dictionary/DictionaryTagTest.php @@ -84,7 +84,7 @@ public function it_can_be_filtered_using_a_query_scope() private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } } diff --git a/tests/Tags/Form/FormTestCase.php b/tests/Tags/Form/FormTestCase.php index c6ee6695acf..1629e875d6d 100644 --- a/tests/Tags/Form/FormTestCase.php +++ b/tests/Tags/Form/FormTestCase.php @@ -69,7 +69,7 @@ public function post($uri, array $data = [], array $headers = []) protected function tag($tag, $params = []) { - return Parse::template($tag, $params); + return Parse::template($tag, $params, trusted: true); } protected function createForm($blueprintContents = null, $handle = null) diff --git a/tests/Tags/GetContentTagTest.php b/tests/Tags/GetContentTagTest.php index d4656a5a5a6..e7bf160d877 100644 --- a/tests/Tags/GetContentTagTest.php +++ b/tests/Tags/GetContentTagTest.php @@ -175,6 +175,6 @@ public function it_returns_the_entries_if_theyre_already_entries_using_shorthand private function assertParseEquals($expected, $template, $context = []) { - $this->assertEquals($expected, (string) Antlers::parse($template, $context)); + $this->assertEquals($expected, (string) Antlers::parse($template, $context, true)); } } diff --git a/tests/Tags/GetErrorTest.php b/tests/Tags/GetErrorTest.php index 86e846eef23..5975c0a3781 100644 --- a/tests/Tags/GetErrorTest.php +++ b/tests/Tags/GetErrorTest.php @@ -12,7 +12,7 @@ class GetErrorTest extends TestCase { private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/Tags/GetErrorsTest.php b/tests/Tags/GetErrorsTest.php index b90edb8d244..c507524375e 100644 --- a/tests/Tags/GetErrorsTest.php +++ b/tests/Tags/GetErrorsTest.php @@ -13,7 +13,7 @@ class GetErrorsTest extends TestCase { private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/Tags/GetSiteTagTest.php b/tests/Tags/GetSiteTagTest.php index 212c812ed34..c41ca637bec 100644 --- a/tests/Tags/GetSiteTagTest.php +++ b/tests/Tags/GetSiteTagTest.php @@ -24,12 +24,12 @@ public function it_gets_site_by_handle() { $this->assertEquals( 'English', - Antlers::parse('{{ get_site handle="english" }}{{ name }}{{ /get_site }}') + Antlers::parse('{{ get_site handle="english" }}{{ name }}{{ /get_site }}', [], true) ); $this->assertEquals( 'French', - Antlers::parse('{{ get_site:french }}{{ name }}{{ /get_site:french }}') + Antlers::parse('{{ get_site:french }}{{ name }}{{ /get_site:french }}', [], true) ); } @@ -38,7 +38,7 @@ public function it_can_be_used_as_single_tag() { $this->assertEquals( 'en_US', - Antlers::parse('{{ get_site:english:locale }}') + Antlers::parse('{{ get_site:english:locale }}', [], true) ); } @@ -47,7 +47,7 @@ public function it_throws_exception_if_handle_is_missing() { $this->expectExceptionMessage('A site handle is required.'); - Antlers::parse('{{ get_site }}{{ name }}{{ /get_site }}'); + Antlers::parse('{{ get_site }}{{ name }}{{ /get_site }}', [], true); } #[Test] @@ -55,6 +55,6 @@ public function it_throws_exception_if_site_doesnt_exist() { $this->expectExceptionMessage('Site [nonexistent] does not exist.'); - Antlers::parse('{{ get_site handle="nonexistent" }}{{ name }}{{ /get_site }}'); + Antlers::parse('{{ get_site handle="nonexistent" }}{{ name }}{{ /get_site }}', [], true); } } diff --git a/tests/Tags/GlideTest.php b/tests/Tags/GlideTest.php index aae0616e1e4..920aa24a253 100644 --- a/tests/Tags/GlideTest.php +++ b/tests/Tags/GlideTest.php @@ -50,7 +50,7 @@ public function it_outputs_an_absolute_url_by_default_when_the_glide_route_is_ab */ public function it_outputs_an_absolute_url_when_the_url_does_not_have_a_valid_extension() { - $parse = (string) Parse::template('{{ glide src="https://statamic.com/foo" }}'); + $parse = (string) Parse::template('{{ glide src="https://statamic.com/foo" }}', trusted: true); $this->assertSame('https://statamic.com/foo', $parse); } @@ -64,7 +64,7 @@ public function it_outputs_a_data_url() {{ glide:data_url :src="foo" }} EOT; - $this->assertStringStartsWith('data:image/jpeg;base64', (string) Parse::template($tag, ['foo' => 'bar.jpg'])); + $this->assertStringStartsWith('data:image/jpeg;base64', (string) Parse::template($tag, ['foo' => 'bar.jpg'], trusted: true)); } #[Test] @@ -76,7 +76,7 @@ public function it_treats_assets_urls_starting_with_the_app_url_as_internal_asse { $this->createImageInPublicDirectory(); - $result = (string) Parse::template('{{ glide:foo width="100" }}', ['foo' => 'http://localhost/glide/bar.jpg']); + $result = (string) Parse::template('{{ glide:foo width="100" }}', ['foo' => 'http://localhost/glide/bar.jpg'], trusted: true); $this->assertStringStartsWith('/img/glide/bar.jpg', $result); } @@ -132,6 +132,6 @@ private function absoluteTestTag($absolute = null) {{ glide:foo width="100" $absoluteParam }} EOT; - return (string) Parse::template($tag, ['foo' => 'bar.jpg']); + return (string) Parse::template($tag, ['foo' => 'bar.jpg'], trusted: true); } } diff --git a/tests/Tags/IncrementTest.php b/tests/Tags/IncrementTest.php index 83c643dbf32..5f28e4c7689 100644 --- a/tests/Tags/IncrementTest.php +++ b/tests/Tags/IncrementTest.php @@ -29,7 +29,7 @@ class IncrementTest extends TestCase private function tag($tag, $context = []) { - return (string) Parse::template($tag, $context); + return (string) Parse::template($tag, $context, trusted: true); } #[Test] diff --git a/tests/Tags/InstalledTest.php b/tests/Tags/InstalledTest.php index 37a99c859c9..481d653cda9 100644 --- a/tests/Tags/InstalledTest.php +++ b/tests/Tags/InstalledTest.php @@ -18,7 +18,7 @@ public function setUp(): void private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/Tags/IterateTest.php b/tests/Tags/IterateTest.php index 227cafda860..c2cc8e967ef 100644 --- a/tests/Tags/IterateTest.php +++ b/tests/Tags/IterateTest.php @@ -46,6 +46,6 @@ public static function iterateProvider() private function tag($tag, $context = []) { - return (string) Parse::template($tag, $context); + return (string) Parse::template($tag, $context, trusted: true); } } diff --git a/tests/Tags/LinkTest.php b/tests/Tags/LinkTest.php index 86bd867ac66..e4b6e39d4e0 100644 --- a/tests/Tags/LinkTest.php +++ b/tests/Tags/LinkTest.php @@ -23,7 +23,7 @@ public function setUp(): void private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/Tags/LoaderTest.php b/tests/Tags/LoaderTest.php index 93d1d0e512a..a6d3b500e79 100644 --- a/tests/Tags/LoaderTest.php +++ b/tests/Tags/LoaderTest.php @@ -25,7 +25,7 @@ public function loading_a_tag_will_run_the_init_hook() return $next($payload); }); - $this->assertEquals('bar', (string) Antlers::parse('{{ test :variable="foo" }}', ['foo' => 'bar'])); + $this->assertEquals('bar', (string) Antlers::parse('{{ test :variable="foo" }}', ['foo' => 'bar'], true)); $this->assertEquals(['variable' => 'bar', 'alfa' => 'bravo'], $tag->params->all()); } } diff --git a/tests/Tags/LocalesTagTest.php b/tests/Tags/LocalesTagTest.php index dc4ece60c2a..f666ff48982 100644 --- a/tests/Tags/LocalesTagTest.php +++ b/tests/Tags/LocalesTagTest.php @@ -60,7 +60,7 @@ public function setUp(): void private function tag($tag, $context = []) { - return (string) Parse::template($tag, $context); + return (string) Parse::template($tag, $context, trusted: true); } #[Test] diff --git a/tests/Tags/MountUrlTagTest.php b/tests/Tags/MountUrlTagTest.php index 556c01bf48b..ca9d0bb149d 100644 --- a/tests/Tags/MountUrlTagTest.php +++ b/tests/Tags/MountUrlTagTest.php @@ -78,6 +78,6 @@ public static function mountProvider() private function assertParseEquals($expected, $template, $context = []) { - $this->assertEquals($expected, (string) Antlers::parse($template, $context)); + $this->assertEquals($expected, (string) Antlers::parse($template, $context, true)); } } diff --git a/tests/Tags/ParametersTest.php b/tests/Tags/ParametersTest.php index 3cb4ef5f84e..116c848cbf6 100644 --- a/tests/Tags/ParametersTest.php +++ b/tests/Tags/ParametersTest.php @@ -7,6 +7,7 @@ use Statamic\Fields\Value; use Statamic\Tags\Context; use Statamic\Tags\Parameters; +use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState; use Tests\TestCase; class ParametersTest extends TestCase @@ -65,6 +66,13 @@ public function augment($value) ], $context); } + public function tearDown(): void + { + GlobalRuntimeState::$isEvaluatingUserData = true; + + parent::tearDown(); + } + #[Test] public function it_gets_all_parameters() { @@ -243,6 +251,9 @@ public function augmentedArrayData() #[Test] public function it_can_use_modifiers() { + // Equivalent to "trusted = true" for runtime evaluation. + GlobalRuntimeState::$isEvaluatingUserData = false; + $context = new Context(['foo' => 'bar']); $params = Parameters::make([ diff --git a/tests/Tags/ParentTest.php b/tests/Tags/ParentTest.php index 65e959d3ebc..818b1ffefe7 100644 --- a/tests/Tags/ParentTest.php +++ b/tests/Tags/ParentTest.php @@ -25,7 +25,7 @@ public function setUp(): void private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } private function setUpEntries() diff --git a/tests/Tags/PartialTagsTest.php b/tests/Tags/PartialTagsTest.php index fb259257a35..125982408ea 100644 --- a/tests/Tags/PartialTagsTest.php +++ b/tests/Tags/PartialTagsTest.php @@ -19,7 +19,7 @@ public function setUp(): void private function tag($tag, $context = []) { - return (string) Parse::template($tag, $context); + return (string) Parse::template($tag, $context, trusted: true); } protected function partialTag($src, $params = '') diff --git a/tests/Tags/PathTest.php b/tests/Tags/PathTest.php index aa56d7e3833..9bfd06ad057 100644 --- a/tests/Tags/PathTest.php +++ b/tests/Tags/PathTest.php @@ -22,7 +22,7 @@ public function setUp(): void private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } private function setSiteUrl($url) diff --git a/tests/Tags/RangeTest.php b/tests/Tags/RangeTest.php index 2b40d2e959d..995b071a596 100644 --- a/tests/Tags/RangeTest.php +++ b/tests/Tags/RangeTest.php @@ -10,7 +10,7 @@ class RangeTest extends TestCase { private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/Tags/RedirectTest.php b/tests/Tags/RedirectTest.php index f109fe64eb3..ec715fceb5d 100644 --- a/tests/Tags/RedirectTest.php +++ b/tests/Tags/RedirectTest.php @@ -34,7 +34,7 @@ protected function resolveApplicationConfiguration($app) private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/Tags/SearchTest.php b/tests/Tags/SearchTest.php index 67dd8360442..15c316bb41b 100644 --- a/tests/Tags/SearchTest.php +++ b/tests/Tags/SearchTest.php @@ -16,7 +16,7 @@ class SearchTest extends TestCase private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/Tags/SessionTagTest.php b/tests/Tags/SessionTagTest.php index e98139a6659..5d19c3c1bb6 100644 --- a/tests/Tags/SessionTagTest.php +++ b/tests/Tags/SessionTagTest.php @@ -13,7 +13,7 @@ public function it_gets_session_value() { session()->put('nineties', 'rad'); - $this->assertEquals('rad', Antlers::parse('{{ session:value key="nineties" }}')); + $this->assertEquals('rad', Antlers::parse('{{ session:value key="nineties" }}', [], true)); } #[Test] @@ -21,8 +21,8 @@ public function it_gets_session_array_value() { session()->put('things', ['nineties' => 'rad']); - $this->assertEquals('rad', Antlers::parse('{{ session:value key="things.nineties" }}')); - $this->assertEquals('rad', Antlers::parse('{{ session:value key="things:nineties" }}')); + $this->assertEquals('rad', Antlers::parse('{{ session:value key="things.nineties" }}', [], true)); + $this->assertEquals('rad', Antlers::parse('{{ session:value key="things:nineties" }}', [], true)); } #[Test] @@ -30,9 +30,9 @@ public function it_gets_session_value_using_wildcard() { session()->put('nineties', 'rad'); - $this->assertEquals('rad', Antlers::parse('{{ session:nineties }}')); - $this->assertEquals('rad', Antlers::parse('{{ session:key }}', ['key' => 'nineties'])); - $this->assertEquals('rad', Antlers::parse('{{ session:key }}', ['key' => 'nineties'])); + $this->assertEquals('rad', Antlers::parse('{{ session:nineties }}', [], true)); + $this->assertEquals('rad', Antlers::parse('{{ session:key }}', ['key' => 'nineties'], true)); + $this->assertEquals('rad', Antlers::parse('{{ session:key }}', ['key' => 'nineties'], true)); } #[Test] @@ -40,9 +40,9 @@ public function it_gets_session_array_value_using_wildcard() { session()->put('things', ['nineties' => 'rad']); - $this->assertEquals('rad', Antlers::parse('{{ session:things.nineties }}')); - $this->assertEquals('rad', Antlers::parse('{{ session:things:nineties }}')); - $this->assertEquals('rad', Antlers::parse('{{ session:key }}', ['key' => 'things.nineties'])); - $this->assertEquals('rad', Antlers::parse('{{ session:key }}', ['key' => 'things:nineties'])); + $this->assertEquals('rad', Antlers::parse('{{ session:things.nineties }}', [], true)); + $this->assertEquals('rad', Antlers::parse('{{ session:things:nineties }}', [], true)); + $this->assertEquals('rad', Antlers::parse('{{ session:key }}', ['key' => 'things.nineties'], true)); + $this->assertEquals('rad', Antlers::parse('{{ session:key }}', ['key' => 'things:nineties'], true)); } } diff --git a/tests/Tags/StructureTagTest.php b/tests/Tags/StructureTagTest.php index 03baf77b66b..87837673310 100644 --- a/tests/Tags/StructureTagTest.php +++ b/tests/Tags/StructureTagTest.php @@ -78,7 +78,7 @@ public function it_renders_a_nav() $this->assertXmlStringEqualsXmlString($expected, (string) Antlers::parse($template, [ 'foo' => 'bar', // to test that cascade is inherited. 'title' => 'outer title', // to test that cascade the page's data takes precedence over the cascading data. - ])); + ], true)); } #[Test] @@ -143,7 +143,7 @@ public function it_renders_a_nav_with_selected_fields() $parsed = (string) Antlers::parse($template, [ 'foo' => 'bar', // to test that cascade is inherited. 'title' => 'outer title', // to test that cascade the page's data takes precedence over the cascading data. - ]); + ], true); // This is really what we're interested in testing. The "Two" entry has a foo value // of "notbar", but we're only selecting the title, so we shouldn't get the real value. @@ -217,7 +217,7 @@ public function it_renders_a_nav_with_scope() 'foo' => 'bar', // to test that cascade is inherited. 'title' => 'outer title', // to test that cascade the page's data takes precedence over the cascading data. 'nav_title' => 'outer nav_title', // to test that the cascade doesn't leak into the iterated scope - ])); + ], true)); } #[Test] @@ -259,7 +259,7 @@ public function it_renders_a_nav_with_as() $this->assertXmlStringEqualsXmlString($expected, (string) Antlers::parse($template, [ 'foo' => 'bar', // to test that cascade is inherited. - ])); + ], true)); } #[Test] @@ -338,31 +338,31 @@ public function it_sets_is_current_and_is_parent_for_a_nav() ]); $mock->shouldReceive('getCurrent')->once()->andReturn('/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[home=parent][1=current][1-1][1-1-1][1-1-1-1][2][3]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/1/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[home=parent][1=parent][1-1=current][1-1-1][1-1-1-1][2][3]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/1/1/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[home=parent][1=parent][1-1=parent][1-1-1=current][1-1-1-1][2][3]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/1/1/1/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[home=parent][1=parent][1-1=parent][1-1-1=parent][1-1-1-1=current][2][3]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/2'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[home=parent][1][1-1][1-1-1][1-1-1-1][2=current][3]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[home=current][1][1-1][1-1-1][1-1-1-1][2][3]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/other'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[home=parent][1][1-1][1-1-1][1-1-1-1][2][3]', $result); // Only the last child has an URL. @@ -377,15 +377,15 @@ public function it_sets_is_current_and_is_parent_for_a_nav() ]); $mock->shouldReceive('getCurrent')->once()->andReturn('/1/1/1/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1=parent][1-1=parent][1-1-1=parent][1-1-1-1=current]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1][1-1][1-1-1][1-1-1-1]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/other'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1][1-1][1-1-1][1-1-1-1]', $result); // Only the top parent has an URL. @@ -400,15 +400,15 @@ public function it_sets_is_current_and_is_parent_for_a_nav() ]); $mock->shouldReceive('getCurrent')->once()->andReturn('/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1=current][1-1][1-1-1][1-1-1-1]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1][1-1][1-1-1][1-1-1-1]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/other'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1][1-1][1-1-1][1-1-1-1]', $result); } @@ -430,7 +430,7 @@ public function it_sets_is_parent_based_on_the_url_too() EntryFactory::collection('rad')->id('3')->slug('3')->data(['title' => 'Three'])->create(); $mock->shouldReceive('getCurrent')->once()->andReturn('/1/2/3'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1=parent][2=parent]', $result); } @@ -470,31 +470,31 @@ public function it_sets_is_current_and_is_parent_for_a_collection() \Statamic\Facades\URL::swap($mock); $mock->shouldReceive('getCurrent')->once()->andReturn('/'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1][1-1][1-1-1][1-1-1-1][2]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/other'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1][1-1][1-1-1][1-1-1-1][2]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/2'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1][1-1][1-1-1][1-1-1-1][2=current]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1=current][1-1][1-1-1][1-1-1-1][2]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/1/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1=parent][1-1=current][1-1-1][1-1-1-1][2]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/1/1/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1=parent][1-1=parent][1-1-1=current][1-1-1-1][2]', $result); $mock->shouldReceive('getCurrent')->once()->andReturn('/1/1/1/1'); - $result = (string) Antlers::parse($template); + $result = (string) Antlers::parse($template, [], true); $this->assertEquals('[1=parent][1-1=parent][1-1-1=parent][1-1-1-1=current][2]', $result); } @@ -530,7 +530,7 @@ public function it_doesnt_render_anything_when_nav_from_is_invalid() $this->assertXmlStringEqualsXmlString($expected, (string) Antlers::parse($template, [ 'title' => 'outer title', // to test that cascade the page's data takes precedence over the cascading data. - ])); + ], true)); } private function makeNav($tree) @@ -544,7 +544,7 @@ private function makeNav($tree) private function parseBasicTemplate($handle, $params = null) { - return (string) Antlers::parse($this->createBasicTemplate($handle, $params)); + return (string) Antlers::parse($this->createBasicTemplate($handle, $params), [], true); } private function createBasicTemplate($handle, $params = null) diff --git a/tests/Tags/SvgTagTest.php b/tests/Tags/SvgTagTest.php index 47b17e25a43..d8d90a6b981 100644 --- a/tests/Tags/SvgTagTest.php +++ b/tests/Tags/SvgTagTest.php @@ -20,7 +20,7 @@ public function setUp(): void private function tag($tag, $variables = []) { - return Parse::template($tag, $variables); + return Parse::template($tag, $variables, trusted: true); } #[Test] diff --git a/tests/Tags/ThemeTagsTest.php b/tests/Tags/ThemeTagsTest.php index 0c57b50caef..81e628cb787 100644 --- a/tests/Tags/ThemeTagsTest.php +++ b/tests/Tags/ThemeTagsTest.php @@ -21,7 +21,7 @@ public function setUp(): void private function tag($tag): string { - return Parse::template($tag, []); + return Parse::template($tag, trusted: true); } public function testOutputsThemedJs() diff --git a/tests/Tags/User/ForgotPasswordFormTest.php b/tests/Tags/User/ForgotPasswordFormTest.php index 4c2ab6c4cb9..132b0637b56 100644 --- a/tests/Tags/User/ForgotPasswordFormTest.php +++ b/tests/Tags/User/ForgotPasswordFormTest.php @@ -16,7 +16,7 @@ class ForgotPasswordFormTest extends TestCase private function tag($tag) { - return Parse::template($tag, []); + return Parse::template($tag, trusted: true); } #[Test] diff --git a/tests/Tags/User/LoginFormTest.php b/tests/Tags/User/LoginFormTest.php index 103a72c0b01..bef0c96530a 100644 --- a/tests/Tags/User/LoginFormTest.php +++ b/tests/Tags/User/LoginFormTest.php @@ -15,7 +15,7 @@ class LoginFormTest extends TestCase private function tag($tag) { - return Parse::template($tag, []); + return Parse::template($tag, trusted: true); } #[Test] diff --git a/tests/Tags/User/PasswordFormTest.php b/tests/Tags/User/PasswordFormTest.php index 35855b1051d..14652cab5a5 100644 --- a/tests/Tags/User/PasswordFormTest.php +++ b/tests/Tags/User/PasswordFormTest.php @@ -16,7 +16,7 @@ class PasswordFormTest extends TestCase private function tag($tag) { - return Parse::template($tag, []); + return Parse::template($tag, trusted: true); } #[Test] diff --git a/tests/Tags/User/ProfileFormTest.php b/tests/Tags/User/ProfileFormTest.php index e7276ccf239..edbdba5e96c 100644 --- a/tests/Tags/User/ProfileFormTest.php +++ b/tests/Tags/User/ProfileFormTest.php @@ -16,7 +16,7 @@ class ProfileFormTest extends TestCase private function tag($tag) { - return Parse::template($tag, []); + return Parse::template($tag, trusted: true); } #[Test] diff --git a/tests/Tags/User/RegisterFormTest.php b/tests/Tags/User/RegisterFormTest.php index f86fcec19fb..7d3114a97a4 100644 --- a/tests/Tags/User/RegisterFormTest.php +++ b/tests/Tags/User/RegisterFormTest.php @@ -19,7 +19,7 @@ class RegisterFormTest extends TestCase private function tag($tag) { - return Parse::template($tag, []); + return Parse::template($tag, trusted: true); } #[Test] diff --git a/tests/Tags/User/ResetPasswordFormTest.php b/tests/Tags/User/ResetPasswordFormTest.php index 035f9404891..941ab966f38 100644 --- a/tests/Tags/User/ResetPasswordFormTest.php +++ b/tests/Tags/User/ResetPasswordFormTest.php @@ -13,7 +13,7 @@ class ResetPasswordFormTest extends TestCase private function tag($tag) { - return Parse::template($tag, []); + return Parse::template($tag, trusted: true); } #[Test] diff --git a/tests/Tags/User/UserTagsTest.php b/tests/Tags/User/UserTagsTest.php index 2f1a9f2a85f..3f1cdb73efd 100644 --- a/tests/Tags/User/UserTagsTest.php +++ b/tests/Tags/User/UserTagsTest.php @@ -22,7 +22,7 @@ class UserTagsTest extends TestCase private function tag($tag, $params = []) { - return Parse::template($tag, $params); + return Parse::template($tag, $params, trusted: true); } #[Test] diff --git a/tests/Tags/UserGroupsTagTest.php b/tests/Tags/UserGroupsTagTest.php index 362bbdb430a..e9c366ed418 100644 --- a/tests/Tags/UserGroupsTagTest.php +++ b/tests/Tags/UserGroupsTagTest.php @@ -62,6 +62,6 @@ public function it_outputs_no_results_when_finding_multiple_groups() private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } } diff --git a/tests/Tags/UserRolesTagTest.php b/tests/Tags/UserRolesTagTest.php index d413d791f04..fcd4bffe6b4 100644 --- a/tests/Tags/UserRolesTagTest.php +++ b/tests/Tags/UserRolesTagTest.php @@ -62,6 +62,6 @@ public function it_outputs_no_results_when_finding_multiple_roles() private function tag($tag, $data = []) { - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } } diff --git a/tests/Tags/UsersTagsTest.php b/tests/Tags/UsersTagsTest.php index 616979af82c..d471e278d68 100644 --- a/tests/Tags/UsersTagsTest.php +++ b/tests/Tags/UsersTagsTest.php @@ -18,7 +18,7 @@ class UsersTagsTest extends TestCase private function tag($tag) { - return Parse::template($tag, []); + return Parse::template($tag, trusted: true); } #[Test] diff --git a/tests/Tags/ViteTest.php b/tests/Tags/ViteTest.php index cf642b00b6e..48c850d8f70 100644 --- a/tests/Tags/ViteTest.php +++ b/tests/Tags/ViteTest.php @@ -19,7 +19,7 @@ private function tag($tag, $data = []) { $this->withFakeVite(); - return (string) Parse::template($tag, $data); + return (string) Parse::template($tag, $data, trusted: true); } #[Test] diff --git a/tests/View/Blade/AntlersComponents/ComponentCompilerTest.php b/tests/View/Blade/AntlersComponents/ComponentCompilerTest.php index e4a6f7dd005..ccf54e3f89f 100644 --- a/tests/View/Blade/AntlersComponents/ComponentCompilerTest.php +++ b/tests/View/Blade/AntlersComponents/ComponentCompilerTest.php @@ -328,7 +328,7 @@ public function index() $this->assertSame( 'Hello, Antlers!', - (string) Antlers::parse('{{ my_tag }}'), + (string) Antlers::parse('{{ my_tag }}', [], true), ); }