Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/Entries/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
use Statamic\Support\Arr;
use Statamic\Support\Str;
use Statamic\Support\Traits\FluentlyGetsAndSets;
use Statamic\View\Cascade;

class Entry implements Arrayable, ArrayAccess, Augmentable, BulkAugmentable, ContainsQueryableValues, Contract, Localization, Protectable, ResolvesValuesContract, Responsable, SearchableContract
{
Expand Down Expand Up @@ -1040,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::parse($format, $this->augmented()->except('slug')->all()));
$title = $this->withLocale($this->site()->lang(), fn () => (string) Antlers::parseUserContent($format, $this->augmented()->except('slug')->all()));

return trim($title);
}
Expand All @@ -1064,8 +1065,8 @@ private function resolvePreviewTargetUrl($format)
}, $format);
}

return (string) Antlers::parse($format, array_merge($this->routeData(), [
'config' => config()->all(),
return (string) Antlers::parseUserContent($format, array_merge($this->routeData(), [
'config' => Cascade::config(),
'site' => $this->site(),
'uri' => $this->uri(),
'url' => $this->url(),
Expand Down
1 change: 1 addition & 0 deletions src/Facades/Antlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @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 array identifiers(string $content)
*
Expand Down
7 changes: 4 additions & 3 deletions src/Forms/Email.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Statamic\Sites\Site;
use Statamic\Support\Arr;
use Statamic\Support\Str;
use Statamic\View\Cascade;

use function Statamic\trans as __;

Expand Down Expand Up @@ -170,7 +171,7 @@ protected function addData()
$data = array_merge($augmented, $this->getGlobalsData(), [
'form_config' => $formConfig,
'email_config' => $this->config,
'config' => config()->all(),
'config' => Cascade::config(),
'fields' => $fields,
'site_url' => Config::getSiteUrl(),
'date' => now(),
Expand Down Expand Up @@ -244,8 +245,8 @@ protected function parseConfig(array $config)
return collect($config)->map(function ($value) {
$value = Parse::env($value); // deprecated

return (string) Antlers::parse($value, array_merge(
['config' => config()->all()],
return (string) Antlers::parseUserContent($value, array_merge(
['config' => Cascade::config()],
$this->getGlobalsData(),
$this->submissionData,
));
Expand Down
11 changes: 10 additions & 1 deletion src/Sites/Site.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
use Statamic\Support\Arr;
use Statamic\Support\Str;
use Statamic\Support\TextDirection;
use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;
use Statamic\View\Antlers\Language\Runtime\RuntimeParser;
use Statamic\View\Cascade;

class Site implements Augmentable
{
Expand Down Expand Up @@ -129,7 +131,14 @@ protected function resolveAntlersValue($value)
->all();
}

return (string) app(RuntimeParser::class)->parse($value, ['config' => config()->all()]);
$isEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
GlobalRuntimeState::$isEvaluatingUserData = true;

try {
return (string) app(RuntimeParser::class)->parse($value, ['config' => Cascade::config()]);
} finally {
GlobalRuntimeState::$isEvaluatingUserData = $isEvaluatingUserData;
}
}

private function removePath($url)
Expand Down
13 changes: 13 additions & 0 deletions src/View/Antlers/Antlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Closure;
use Statamic\Contracts\View\Antlers\Parser;
use Statamic\View\Antlers\Language\Parser\IdentifierFinder;
use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;

class Antlers
{
Expand All @@ -31,6 +32,18 @@ public function parse($str, $variables = [])
return $this->parser()->parse($str, $variables);
}

public function parseUserContent($str, $variables = [])
{
$isEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
GlobalRuntimeState::$isEvaluatingUserData = true;

try {
return $this->parser()->parse($str, $variables);
} finally {
GlobalRuntimeState::$isEvaluatingUserData = $isEvaluatingUserData;
}
}

/**
* Iterate over an array and parse the string/template for each.
*
Expand Down
139 changes: 138 additions & 1 deletion src/View/Cascade.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ private function contextualVariables()
'xml_header' => '<?xml version="1.0" encoding="utf-8" ?>', // @TODO remove and document new best practice
'csrf_token' => csrf_token(),
'csrf_field' => csrf_field(),
'config' => config()->all(),
'config' => static::config(),
'response_code' => 200,

// Auth
Expand Down Expand Up @@ -247,4 +247,141 @@ public function clearSections()

return $this;
}

public static function config(): array
{
$defaults = [
'app.name',
'app.env',
'app.debug',
'app.url',
'app.asset_url',
'app.locale',
'app.fallback_locale',
'app.timezone',
'auth.defaults',
'auth.guards',
'auth.passwords',
'broadcasting.default',
'cache.default',
'filesystems.default',
'mail.default',
'mail.from',
'queue.default',
'session.lifetime',
'session.expire_on_close',
'session.driver',
'statamic.assets.image_manipulation',
'statamic.assets.auto_crop',
'statamic.assets.thumbnails',
'statamic.assets.video_thumbnails',
'statamic.assets.google_docs_viewer',
'statamic.assets.cache_meta',
'statamic.assets.focal_point_editor',
'statamic.assets.lowercase',
'statamic.assets.svg_sanitization_on_upload',
'statamic.assets.ffmpeg',
'statamic.assets.set_preview_images',
'statamic.autosave',
'statamic.cp',
'statamic.editions',
'statamic.forms.email_view_folder',
'statamic.forms.send_email_job',
'statamic.forms.exporters',
'statamic.git.enabled',
'statamic.git.automatic',
'statamic.git.queue_connection',
'statamic.git.dispatch_delay',
'statamic.git.use_authenticated',
'statamic.git.user',
'statamic.git.binary',
'statamic.git.commands',
'statamic.git.push',
'statamic.git.ignored_events',
'statamic.git.locale',
'statamic.graphql',
'statamic.live_preview',
'statamic.markdown',
'statamic.oauth',
'statamic.protect.default',
'statamic.revisions',
'statamic.routes',
'statamic.search.default',
'statamic.search.indexes',
'statamic.search.defaults',
'statamic.search.queue',
'statamic.search.queue_connection',
'statamic.search.chunk_size',
'statamic.stache.watcher',
'statamic.stache.cache_store',
'statamic.stache.indexes',
'statamic.stache.lock',
'statamic.stache.warming',
'statamic.static_caching.strategy',
'statamic.static_caching.strategies',
'statamic.static_caching.exclude',
'statamic.static_caching.invalidation',
'statamic.static_caching.ignore_query_strings',
'statamic.static_caching.allowed_query_strings',
'statamic.static_caching.disallowed_query_strings',
'statamic.static_caching.nocache',
'statamic.static_caching.nocache_db_connection',
'statamic.static_caching.replacers',
'statamic.static_caching.warm_queue',
'statamic.static_caching.warm_queue_connection',
'statamic.static_caching.warm_insecure',
'statamic.static_caching.background_recache',
'statamic.static_caching.recache_token_parameter',
'statamic.static_caching.share_errors',
'statamic.system.multisite',
'statamic.system.send_powered_by_header',
'statamic.system.date_format',
'statamic.system.display_timezone',
'statamic.system.localize_dates_in_modifiers',
'statamic.system.charset',
'statamic.system.track_last_update',
'statamic.system.cache_tags_enabled',
'statamic.system.php_memory_limit',
'statamic.system.php_max_execution_time',
'statamic.system.ajax_timeout',
'statamic.system.pcre_backtrack_limit',
'statamic.system.debugbar',
'statamic.system.ascii_replace_extra_symbols',
'statamic.system.update_references',
'statamic.system.always_augment_to_query',
'statamic.system.row_id_handle',
'statamic.system.fake_sql_queries',
'statamic.system.layout',
'statamic.templates',
'statamic.users.repository',
'statamic.users.avatars',
'statamic.users.new_user_roles',
'statamic.users.new_user_groups',
'statamic.users.wizard_invitation',
'statamic.users.passwords',
'statamic.users.database',
'statamic.users.tables',
'statamic.users.guards',
'statamic.users.impersonate',
'statamic.users.elevated_session_duration',
'statamic.users.two_factor_enforced_roles',
'statamic.users.sort_field',
'statamic.users.sort_direction',
'statamic.webauthn',
];

$allowed = collect((array) config('statamic.system.view_config_allowlist', $defaults))
->flatMap(fn ($key) => $key === '@default' ? $defaults : [$key])
->unique()->values()->all();

return array_reduce($allowed, function ($config, $key) {
$value = config($key);

if (! is_null($value)) {
Arr::set($config, $key, $value);
}

return $config;
}, []);
}
}
91 changes: 91 additions & 0 deletions tests/Antlers/ParseUserContentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

namespace Tests\Antlers;

use Illuminate\Support\Facades\Log;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Contracts\View\Antlers\Parser;
use Statamic\Facades\Antlers;
use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;
use Tests\Antlers\Fixtures\MethodClasses\ClassOne;
use Tests\TestCase;

class ParseUserContentTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

GlobalRuntimeState::resetGlobalState();
GlobalRuntimeState::$throwErrorOnAccessViolation = false;
GlobalRuntimeState::$allowPhpInContent = false;
GlobalRuntimeState::$allowMethodsInContent = false;
}

#[Test]
public function it_parses_templates_like_standard_parse_for_basic_content()
{
$this->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);
}
}
1 change: 1 addition & 0 deletions tests/Sites/SitesConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public function it_resolves_antlers_when_resolving_sites()
]);

Config::set('statamic.some_addon.theme', 'sunset');
Config::set('statamic.system.view_config_allowlist', ['@default', 'app.faker_locale', 'statamic.some_addon.theme']);

Site::setSites([
'default' => [
Expand Down
Loading