From ce60ef3a73712f538dc109bff480d3efb230a613 Mon Sep 17 00:00:00 2001 From: Steve Hurst | morphsites Date: Fri, 10 Nov 2023 18:26:49 +0000 Subject: [PATCH 01/13] [4.x] Fix error when getting alt on bard image when asset is missing (#8959) --- src/Fieldtypes/Bard/ImageNode.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fieldtypes/Bard/ImageNode.php b/src/Fieldtypes/Bard/ImageNode.php index 1210e1119c..cffbae4a8b 100644 --- a/src/Fieldtypes/Bard/ImageNode.php +++ b/src/Fieldtypes/Bard/ImageNode.php @@ -62,11 +62,11 @@ public function renderHTML($node, $HTMLAttributes = []) protected function getUrl($id) { - return optional(Asset::find($id))->url(); + return Asset::find($id)?->url(); } protected function getAlt($id) { - return optional(Asset::find($id))->data()->get('alt'); + return Asset::find($id)?->data()->get('alt'); } } From 8ef3b8c7b058fa45710d29afedb6897ed70e7038 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 10 Nov 2023 15:30:36 -0500 Subject: [PATCH 02/13] [4.x] Fix impersonation redirect (#8973) --- src/Actions/Impersonate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Actions/Impersonate.php b/src/Actions/Impersonate.php index 8286f29f4a..ce3db84560 100644 --- a/src/Actions/Impersonate.php +++ b/src/Actions/Impersonate.php @@ -65,6 +65,6 @@ public function redirect($users, $values) return $url; } - return $users->first()->can('access cp') ? cp_route('dashboard') : '/'; + return $users->first()->can('access cp') ? cp_route('index') : '/'; } } From 30db081326dd45db71698603c494e6f0db66d680 Mon Sep 17 00:00:00 2001 From: Arthur Perton Date: Fri, 10 Nov 2023 22:06:49 +0100 Subject: [PATCH 03/13] [4.x] Fix new child entries not propagating to appropriate position in other sites trees (#7302) Co-authored-by: Jason Varga --- src/Entries/Entry.php | 31 +++++++++ .../Collections/LocalizeEntryController.php | 28 -------- tests/Data/Entries/EntryTest.php | 68 +++++++++++++++++++ 3 files changed, 99 insertions(+), 28 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 65e3b30ed9..d531638796 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -3,6 +3,7 @@ namespace Statamic\Entries; use ArrayAccess; +use Closure; use Facades\Statamic\Entries\InitiatorStack; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Responsable; @@ -722,6 +723,10 @@ public function makeLocalization($site) ->published($this->published) ->slug($this->slug()); + if ($callback = $this->addToStructure($site, $this->parent())) { + $localization->afterSave($callback); + } + if ($this->collection()->dated()) { $localization->date($this->date()); } @@ -729,6 +734,32 @@ public function makeLocalization($site) return $localization; } + private function addToStructure($site, $parent = null): ?Closure + { + // If it's orderable (linear - a max depth of 1) then don't add it. + if ($this->collection()->orderable()) { + return null; + } + + // Collection not structured? Don't add it. + if (! $structure = $this->collection()->structure()) { + return null; + } + + $tree = $structure->in($site); + $parent = optional($parent)->in($site); + + return function ($entry) use ($parent, $tree) { + if (! $parent || $parent->isRoot()) { + $tree->append($entry); + } else { + $tree->appendTo($parent->id(), $entry); + } + + $tree->save(); + }; + } + public function supplementTaxonomies() { // TODO: This is just here to make things work without rewriting a bunch of places. diff --git a/src/Http/Controllers/CP/Collections/LocalizeEntryController.php b/src/Http/Controllers/CP/Collections/LocalizeEntryController.php index 223f2cccde..a3bb7df97b 100644 --- a/src/Http/Controllers/CP/Collections/LocalizeEntryController.php +++ b/src/Http/Controllers/CP/Collections/LocalizeEntryController.php @@ -14,8 +14,6 @@ public function __invoke(Request $request, $collection, $entry) $localized = $entry->makeLocalization($site = $request->site); - $this->addToStructure($collection, $entry, $localized); - $localized->store(['user' => User::fromUser($request->user())]); return [ @@ -23,30 +21,4 @@ public function __invoke(Request $request, $collection, $entry) 'url' => $localized->editUrl(), ]; } - - private function addToStructure($collection, $entry, $localized) - { - // If it's orderable (linear - a max depth of 1) then don't add it. - if ($collection->orderable()) { - return; - } - - // Collection not structured? Don't add it. - if (! $structure = $collection->structure()) { - return; - } - - $tree = $structure->in($localized->locale()); - $parent = optional($entry->parent())->in($localized->locale()); - - $localized->afterSave(function ($localized) use ($parent, $tree) { - if (! $parent || $parent->isRoot()) { - $tree->append($localized); - } else { - $tree->appendTo($parent->id(), $localized); - } - - $tree->save(); - }); - } } diff --git a/tests/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index f0dcd3bc67..9b94cdbed1 100644 --- a/tests/Data/Entries/EntryTest.php +++ b/tests/Data/Entries/EntryTest.php @@ -1549,6 +1549,74 @@ public function it_does_not_propagate_existing_entries() $this->assertCount(1, Entry::all()); } + /** @test */ + public function it_adds_propagated_entry_to_structure() + { + Event::fake(); + + Facades\Site::setConfig([ + 'default' => 'en', + 'sites' => [ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'], + 'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => 'http://test.com/es/'], + ], + ]); + + $collection = (new Collection) + ->handle('pages') + ->sites(['en', 'fr', 'es']) + ->propagate(false) + ->save(); + + (new Entry)->locale('en')->id('en-1')->collection($collection)->save(); + (new Entry)->locale('en')->id('en-2')->collection($collection)->save(); + (new Entry)->locale('en')->id('en-3')->collection($collection)->save(); + + (new Entry)->locale('fr')->id('fr-1')->collection($collection)->origin('en-1')->save(); + (new Entry)->locale('fr')->id('fr-2')->collection($collection)->origin('en-2')->save(); + + (new Entry)->locale('es')->id('es-1')->collection($collection)->origin('en-1')->save(); + (new Entry)->locale('es')->id('es-3')->collection($collection)->origin('en-3')->save(); + + $collection->structureContents(['expects_root' => false])->save(); + $collection->structure()->in('en')->tree([['entry' => 'en-1'], ['entry' => 'en-2'], ['entry' => 'en-3']])->save(); + $collection->structure()->in('fr')->tree([['entry' => 'fr-1'], ['entry' => 'fr-2']])->save(); + $collection->structure()->in('es')->tree([['entry' => 'es-1'], ['entry' => 'es-3']])->save(); + + $collection->propagate(true); + + $en = (new Entry) + ->id('en-2-1') + ->locale('en') + ->collection($collection) + ->afterSave(function ($entry) { + $entry->collection()->structure()->in('en')->appendTo('en-2', $entry)->save(); + }); + + $en->save(); + + $this->assertIsObject($fr = $en->descendants()->get('fr')); + $this->assertIsObject($es = $en->descendants()->get('es')); + + $this->assertEquals([ + ['entry' => 'en-1'], + ['entry' => 'en-2', 'children' => [['entry' => $en->id()]]], + ['entry' => 'en-3'], + ], $collection->structure()->in('en')->tree()); + + $this->assertEquals([ + ['entry' => 'fr-1'], + ['entry' => 'fr-2', 'children' => [['entry' => $fr->id()]]], + ], $collection->structure()->in('fr')->tree()); + + $this->assertEquals([ + ['entry' => 'es-1'], + ['entry' => 'es-3'], + ['entry' => $es->id()], + ], $collection->structure()->in('es')->tree()); + } + /** @test */ public function if_saving_event_returns_false_the_entry_doesnt_save() { From 17b7ac79b4d0401860182c705b3710233cb579e1 Mon Sep 17 00:00:00 2001 From: Duncan McClean <19637309+duncanmcclean@users.noreply.github.com> Date: Sat, 11 Nov 2023 14:50:58 +0000 Subject: [PATCH 04/13] [4.x] Add Bard support to read_time modifier (#8976) Add Bard support to read_time modifier --- src/Modifiers/CoreModifiers.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Modifiers/CoreModifiers.php b/src/Modifiers/CoreModifiers.php index 1e5da9a999..52d51d0498 100644 --- a/src/Modifiers/CoreModifiers.php +++ b/src/Modifiers/CoreModifiers.php @@ -1810,6 +1810,14 @@ public function ray($value) */ public function readTime($value, $params) { + if (is_array($value)) { + $value = collect($value) + ->map(fn (Values $values) => $values->all()) + ->where('type', 'text') + ->map(fn ($item) => $item['text']->raw()) + ->implode(' '); + } + $words = $this->wordCount(strip_tags($value)); return ceil($words / Arr::get($params, 0, 200)); From 377ec1a416abdd789fe363c5afac0e2954d40028 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Sat, 11 Nov 2023 14:52:48 +0000 Subject: [PATCH 05/13] [4.x] Fix for edit form page saying edit collection (#8967) Fix for edit form page saying edit collection --- resources/js/components/collections/EditForm.vue | 3 ++- resources/views/forms/edit.blade.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/js/components/collections/EditForm.vue b/resources/js/components/collections/EditForm.vue index fe231fc7ce..0f76eaeab8 100644 --- a/resources/js/components/collections/EditForm.vue +++ b/resources/js/components/collections/EditForm.vue @@ -15,7 +15,7 @@
-

+

@@ -33,6 +33,7 @@ export default { props: { blueprint: Object, + editTitle: String, initialValues: Object, meta: Object, url: String diff --git a/resources/views/forms/edit.blade.php b/resources/views/forms/edit.blade.php index faf158b740..f063be49f6 100644 --- a/resources/views/forms/edit.blade.php +++ b/resources/views/forms/edit.blade.php @@ -5,6 +5,7 @@ Date: Sat, 11 Nov 2023 17:32:30 +0100 Subject: [PATCH 06/13] [4.x] French translations (#8977) Good for 4.33 --- resources/lang/fr/messages.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lang/fr/messages.php b/resources/lang/fr/messages.php index 9b48cccab5..841b3497f0 100644 --- a/resources/lang/fr/messages.php +++ b/resources/lang/fr/messages.php @@ -68,9 +68,9 @@ 'collections_route_instructions' => 'La route contrôle le modèle d’URL des entrées. Apprenez-en plus dans la [documentation](https://statamic.dev/collections#routing).', 'collections_sort_direction_instructions' => 'Le sens de tri par défaut.', 'collections_taxonomies_instructions' => 'Reliez les entrées de cette collection à des taxonomies. Les champs seront automatiquement ajoutés aux formulaires.', - 'duplicate_action_warning_localization' => 'Cette entrée est une traduction. L’entrée originale est dupliquée.', - 'duplicate_action_warning_localizations' => 'Une ou plusieurs entrées sélectionnées sont des traductions. Au lieu de cela, l’entrée originale est dupliquée.', - 'duplicate_action_localizations_confirmation' => 'Êtes-vous sûr de vouloir effectuer cette action ? Les traductions sont également dupliquées.', + 'duplicate_action_localizations_confirmation' => 'Êtes-vous sûr de vouloir effectuer cette action ? Les traductions seront également dupliquées.', + 'duplicate_action_warning_localization' => 'Cette entrée est une traduction. L’entrée originale sera dupliquée.', + 'duplicate_action_warning_localizations' => 'Une ou plusieurs entrées sélectionnées sont des traductions. Dans un tel cas, l’entrée originale sera dupliquée à la place.', 'email_utility_configuration_description' => 'Les paramètres de messagerie sont configurés dans :path', 'email_utility_description' => 'Vérifiez les paramètres de messagerie et envoyez des e-mails de test.', 'entry_origin_instructions' => 'La nouvelle traduction héritera des valeurs qu’a l’entrée dans le site sélectionné.', From 0862875cf7b4cdfdfdaf255cef9e35a3dacbec27 Mon Sep 17 00:00:00 2001 From: Duncan McClean <19637309+duncanmcclean@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:15:30 +0000 Subject: [PATCH 07/13] [4.x] Auto-populate Array Fieldtype Options (#8980) --- .../components/fieldtypes/ArrayFieldtype.vue | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/resources/js/components/fieldtypes/ArrayFieldtype.vue b/resources/js/components/fieldtypes/ArrayFieldtype.vue index bc98ff0f6c..2b1e054a85 100644 --- a/resources/js/components/fieldtypes/ArrayFieldtype.vue +++ b/resources/js/components/fieldtypes/ArrayFieldtype.vue @@ -61,7 +61,7 @@ - + @@ -210,7 +210,21 @@ export default { setKey(key) { this.selectedKey = key - } + }, + + keyUpdated(element) { + if (element.key === null || element.value !== null) { + return null; + } + + let value = element.key.charAt(0).toUpperCase() + element.key.slice(1); + + this.data.find((item, index) => { + if (item._id === element._id) { + this.data[index].value = value; + } + }) + }, } } From 48617626fbe110f5e288bb9d39a74bed7687459d Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Tue, 14 Nov 2023 13:45:57 +0000 Subject: [PATCH 08/13] [4.x] Only namespace asset validation attributes when on a CP route (#8987) --- src/Fieldtypes/Assets/DimensionsRule.php | 3 ++- src/Fieldtypes/Assets/ImageRule.php | 3 ++- src/Fieldtypes/Assets/MaxRule.php | 4 +++- src/Fieldtypes/Assets/MimesRule.php | 3 ++- src/Fieldtypes/Assets/MimetypesRule.php | 3 ++- src/Fieldtypes/Assets/MinRule.php | 4 +++- tests/Fieldtypes/AssetsTest.php | 14 ++++++++++++++ 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Fieldtypes/Assets/DimensionsRule.php b/src/Fieldtypes/Assets/DimensionsRule.php index 8f980a2da0..11c682f7f0 100644 --- a/src/Fieldtypes/Assets/DimensionsRule.php +++ b/src/Fieldtypes/Assets/DimensionsRule.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Validation\Rule; use Statamic\Facades\Asset; +use Statamic\Statamic; use Symfony\Component\HttpFoundation\File\UploadedFile; class DimensionsRule implements Rule @@ -63,7 +64,7 @@ public function passes($attribute, $value) */ public function message() { - return __('statamic::validation.dimensions'); + return __((Statamic::isCpRoute() ? 'statamic::' : '').'validation.dimensions'); } /** diff --git a/src/Fieldtypes/Assets/ImageRule.php b/src/Fieldtypes/Assets/ImageRule.php index ba3f6fb6e3..b32985ec0e 100644 --- a/src/Fieldtypes/Assets/ImageRule.php +++ b/src/Fieldtypes/Assets/ImageRule.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Validation\Rule; use Statamic\Facades\Asset; +use Statamic\Statamic; use Symfony\Component\HttpFoundation\File\UploadedFile; class ImageRule implements Rule @@ -46,6 +47,6 @@ public function passes($attribute, $value) */ public function message() { - return __('statamic::validation.image'); + return __((Statamic::isCpRoute() ? 'statamic::' : '').'validation.image'); } } diff --git a/src/Fieldtypes/Assets/MaxRule.php b/src/Fieldtypes/Assets/MaxRule.php index c9015a1441..8a5f04e86f 100644 --- a/src/Fieldtypes/Assets/MaxRule.php +++ b/src/Fieldtypes/Assets/MaxRule.php @@ -2,6 +2,8 @@ namespace Statamic\Fieldtypes\Assets; +use Statamic\Statamic; + class MaxRule extends SizeBasedRule { /** @@ -22,6 +24,6 @@ public function sizePasses($size) */ public function message() { - return str_replace(':max', $this->parameters[0], __('statamic::validation.max.file')); + return str_replace(':max', $this->parameters[0], __((Statamic::isCpRoute() ? 'statamic::' : '').'validation.max.file')); } } diff --git a/src/Fieldtypes/Assets/MimesRule.php b/src/Fieldtypes/Assets/MimesRule.php index 68206a4af2..485ac393cf 100644 --- a/src/Fieldtypes/Assets/MimesRule.php +++ b/src/Fieldtypes/Assets/MimesRule.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Validation\Rule; use Statamic\Facades\Asset; +use Statamic\Statamic; use Symfony\Component\HttpFoundation\File\UploadedFile; class MimesRule implements Rule @@ -48,6 +49,6 @@ public function passes($attribute, $value) */ public function message() { - return str_replace(':values', implode(', ', $this->parameters), __('statamic::validation.mimes')); + return str_replace(':values', implode(', ', $this->parameters), __((Statamic::isCpRoute() ? 'statamic::' : '').'validation.mimes')); } } diff --git a/src/Fieldtypes/Assets/MimetypesRule.php b/src/Fieldtypes/Assets/MimetypesRule.php index fd4a971ff9..ad1c82acaa 100644 --- a/src/Fieldtypes/Assets/MimetypesRule.php +++ b/src/Fieldtypes/Assets/MimetypesRule.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Validation\Rule; use Statamic\Facades\Asset; +use Statamic\Statamic; use Symfony\Component\HttpFoundation\File\UploadedFile; class MimetypesRule implements Rule @@ -43,6 +44,6 @@ public function passes($attribute, $value) */ public function message() { - return str_replace(':values', implode(', ', $this->parameters), __('statamic::validation.mimetypes')); + return str_replace(':values', implode(', ', $this->parameters), __((Statamic::isCpRoute() ? 'statamic::' : '').'validation.mimetypes')); } } diff --git a/src/Fieldtypes/Assets/MinRule.php b/src/Fieldtypes/Assets/MinRule.php index fe35ec8a3b..b37bfaa587 100644 --- a/src/Fieldtypes/Assets/MinRule.php +++ b/src/Fieldtypes/Assets/MinRule.php @@ -2,6 +2,8 @@ namespace Statamic\Fieldtypes\Assets; +use Statamic\Statamic; + class MinRule extends SizeBasedRule { /** @@ -22,6 +24,6 @@ public function sizePasses($size) */ public function message() { - return str_replace(':min', $this->parameters[0], __('statamic::validation.min.file')); + return str_replace(':min', $this->parameters[0], __((Statamic::isCpRoute() ? 'statamic::' : '').'validation.min.file')); } } diff --git a/tests/Fieldtypes/AssetsTest.php b/tests/Fieldtypes/AssetsTest.php index 636e63da38..1abd1d0548 100644 --- a/tests/Fieldtypes/AssetsTest.php +++ b/tests/Fieldtypes/AssetsTest.php @@ -106,6 +106,8 @@ public function it_shallow_augments_to_a_single_asset_when_max_files_is_one() /** @test */ public function it_replaces_dimensions_rule() { + config()->set('statamic.cp.route', '/'); + $replaced = $this->fieldtype(['validate' => ['dimensions:width=180,height=180']])->fieldRules(); $this->assertIsArray($replaced); @@ -117,6 +119,8 @@ public function it_replaces_dimensions_rule() /** @test */ public function it_replaces_image_rule() { + config()->set('statamic.cp.route', '/'); + $replaced = $this->fieldtype(['validate' => ['image']])->fieldRules(); $this->assertIsArray($replaced); @@ -128,6 +132,8 @@ public function it_replaces_image_rule() /** @test */ public function it_replaces_mimes_rule() { + config()->set('statamic.cp.route', '/'); + $replaced = $this->fieldtype(['validate' => ['mimes:jpg,png']])->fieldRules(); $this->assertIsArray($replaced); @@ -139,6 +145,8 @@ public function it_replaces_mimes_rule() /** @test */ public function it_replaces_mimestypes_rule() { + config()->set('statamic.cp.route', '/'); + $replaced = $this->fieldtype(['validate' => ['mimetypes:image/jpg,image/png']])->fieldRules(); $this->assertIsArray($replaced); @@ -150,6 +158,8 @@ public function it_replaces_mimestypes_rule() /** @test */ public function it_replaces_min_filesize_rule() { + config()->set('statamic.cp.route', '/'); + $replaced = $this->fieldtype(['validate' => ['min_filesize:100']])->fieldRules(); $this->assertIsArray($replaced); @@ -161,6 +171,8 @@ public function it_replaces_min_filesize_rule() /** @test */ public function it_replaces_max_filesize_rule() { + config()->set('statamic.cp.route', '/'); + $replaced = $this->fieldtype(['validate' => ['max_filesize:100']])->fieldRules(); $this->assertIsArray($replaced); @@ -172,6 +184,8 @@ public function it_replaces_max_filesize_rule() /** @test */ public function it_doesnt_replace_non_image_related_rule() { + config()->set('statamic.cp.route', '/'); + $replaced = $this->fieldtype(['validate' => ['file']])->fieldRules(); $this->assertIsArray($replaced); From 5f1509d9c976367037dab3907eb17bcb9014ebf9 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Tue, 14 Nov 2023 13:46:31 +0000 Subject: [PATCH 09/13] [4.x] Hide export submissions button when there are no valid exporters (#8985) --- resources/views/forms/show.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/forms/show.blade.php b/resources/views/forms/show.blade.php index 9c720c30e3..f7990842ff 100644 --- a/resources/views/forms/show.blade.php +++ b/resources/views/forms/show.blade.php @@ -30,7 +30,7 @@ @endcan - @if ($exporters = $form->exporters()) + @if (($exporters = $form->exporters()) && $exporters->isNotEmpty()) @foreach ($exporters as $exporter) From 4ef616921842a9e1939c5f178d336e44f09dd754 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 14 Nov 2023 12:01:50 -0500 Subject: [PATCH 10/13] [4.x] Nocache performance improvements (#8956) --- src/StaticCaching/NoCache/Session.php | 21 ++++++-- src/StaticCaching/NoCache/StringFragment.php | 16 +++--- src/StaticCaching/NoCache/Tags.php | 10 +++- src/StaticCaching/StaticCacheManager.php | 4 +- tests/StaticCaching/ManagerTest.php | 6 +++ tests/StaticCaching/NoCacheSessionTest.php | 22 +++++--- tests/StaticCaching/NocacheTagsTest.php | 54 ++++++++++++++++++++ 7 files changed, 113 insertions(+), 20 deletions(-) diff --git a/src/StaticCaching/NoCache/Session.php b/src/StaticCaching/NoCache/Session.php index d763ec9b31..f60f9c6b25 100644 --- a/src/StaticCaching/NoCache/Session.php +++ b/src/StaticCaching/NoCache/Session.php @@ -41,12 +41,12 @@ public function setUrl(string $url) */ public function regions(): Collection { - return $this->regions; + return $this->regions->mapWithKeys(fn ($key) => [$key => $this->region($key)]); } public function region(string $key): Region { - if ($region = $this->regions[$key] ?? null) { + if ($this->regions->contains($key) && ($region = Cache::get('nocache::region.'.$key))) { return $region; } @@ -57,14 +57,22 @@ public function pushRegion($contents, $context, $extension): StringRegion { $region = new StringRegion($this, trim($contents), $context, $extension); - return $this->regions[$region->key()] = $region; + $this->cacheRegion($region); + + $this->regions[] = $region->key(); + + return $region; } public function pushView($view, $context): ViewRegion { $region = new ViewRegion($this, $view, $context); - return $this->regions[$region->key()] = $region; + $this->cacheRegion($region); + + $this->regions[] = $region->key(); + + return $region; } public function cascade() @@ -115,4 +123,9 @@ private function restoreCascade() ->hydrate() ->toArray(); } + + private function cacheRegion(Region $region) + { + Cache::forever('nocache::region.'.$region->key(), $region); + } } diff --git a/src/StaticCaching/NoCache/StringFragment.php b/src/StaticCaching/NoCache/StringFragment.php index 6de7ca5780..4dc3025863 100644 --- a/src/StaticCaching/NoCache/StringFragment.php +++ b/src/StaticCaching/NoCache/StringFragment.php @@ -27,14 +27,18 @@ public function render(): string view()->addNamespace('nocache', $this->directory); File::makeDirectory($this->directory); - $this->createTemporaryView(); + $path = $this->createTemporaryView(); $this->data['__frontmatter'] = Arr::pull($this->data, 'view', []); - return view('nocache::'.$this->region, $this->data)->render(); + $rendered = view('nocache::'.$this->region, $this->data)->render(); + + File::delete($path); + + return $rendered; } - private function createTemporaryView() + private function createTemporaryView(): string { $path = vsprintf('%s/%s.%s', [ $this->directory, @@ -42,10 +46,10 @@ private function createTemporaryView() $this->extension, ]); - if (File::exists($path)) { - return; + if (! File::exists($path)) { + File::put($path, $this->contents); } - File::put($path, $this->contents); + return $path; } } diff --git a/src/StaticCaching/NoCache/Tags.php b/src/StaticCaching/NoCache/Tags.php index 0f174626e9..c26dc2b860 100644 --- a/src/StaticCaching/NoCache/Tags.php +++ b/src/StaticCaching/NoCache/Tags.php @@ -2,6 +2,8 @@ namespace Statamic\StaticCaching\NoCache; +use Statamic\Facades\Antlers; + class Tags extends \Statamic\Tags\Tags { public static $handle = 'nocache'; @@ -19,9 +21,15 @@ public function __construct(Session $nocache) public function index() { + if ($this->params->has('select')) { + $fields = $this->params->explode('select'); + } else { + $fields = Antlers::identifiers($this->content); + } + return $this ->nocache - ->pushRegion($this->content, $this->context->all(), 'antlers.html') + ->pushRegion($this->content, $this->context->only($fields)->all(), 'antlers.html') ->placeholder(); } } diff --git a/src/StaticCaching/StaticCacheManager.php b/src/StaticCaching/StaticCacheManager.php index 932adafb24..34bd5ae0c7 100644 --- a/src/StaticCaching/StaticCacheManager.php +++ b/src/StaticCaching/StaticCacheManager.php @@ -56,7 +56,9 @@ public function flush() $this->driver()->flush(); collect(Cache::get('nocache::urls', []))->each(function ($url) { - Cache::forget('nocache::session.'.md5($url)); + $session = Cache::get($sessionKey = 'nocache::session.'.md5($url)); + collect($session['regions'] ?? [])->each(fn ($region) => Cache::forget('nocache::region.'.$region)); + Cache::forget($sessionKey); }); Cache::forget('nocache::urls'); diff --git a/tests/StaticCaching/ManagerTest.php b/tests/StaticCaching/ManagerTest.php index db280f1997..be865df05e 100644 --- a/tests/StaticCaching/ManagerTest.php +++ b/tests/StaticCaching/ManagerTest.php @@ -22,6 +22,12 @@ public function it_flushes() StaticCache::extend('test', fn () => $mock); Cache::shouldReceive('get')->with('nocache::urls', [])->once()->andReturn(['/one', '/two']); + Cache::shouldReceive('get')->with('nocache::session.'.md5('/one'))->once()->andReturn(['regions' => ['r1', 'r2']]); + Cache::shouldReceive('get')->with('nocache::session.'.md5('/two'))->once()->andReturn(['regions' => ['r3', 'r4']]); + Cache::shouldReceive('forget')->with('nocache::region.r1')->once(); + Cache::shouldReceive('forget')->with('nocache::region.r2')->once(); + Cache::shouldReceive('forget')->with('nocache::region.r3')->once(); + Cache::shouldReceive('forget')->with('nocache::region.r4')->once(); Cache::shouldReceive('forget')->with('nocache::session.'.md5('/one'))->once(); Cache::shouldReceive('forget')->with('nocache::session.'.md5('/two'))->once(); Cache::shouldReceive('forget')->with('nocache::urls')->once(); diff --git a/tests/StaticCaching/NoCacheSessionTest.php b/tests/StaticCaching/NoCacheSessionTest.php index 753c569aa4..d5022fc16f 100644 --- a/tests/StaticCaching/NoCacheSessionTest.php +++ b/tests/StaticCaching/NoCacheSessionTest.php @@ -2,6 +2,7 @@ namespace Tests\StaticCaching; +use Carbon\Carbon; use Illuminate\Support\Facades\Cache; use Mockery; use Statamic\StaticCaching\NoCache\Session; @@ -75,6 +76,8 @@ public function it_gets_the_fragment_data() /** @test */ public function it_writes() { + Carbon::setTestNow('2014-02-15'); + // Testing that the cache key used is unique to the url. // The contents aren't really important. @@ -101,6 +104,11 @@ public function it_writes() ->with('nocache::urls', ['/', '/foo']) ->once(); + // When pushing regions, they will get written too... + Cache::shouldReceive('forever') + ->withArgs(fn ($arg) => str_starts_with($arg, 'nocache::region.')) + ->twice(); + tap(new Session('/'), function ($session) { $session->pushRegion('test', [], '.html'); })->write(); @@ -113,11 +121,10 @@ public function it_writes() /** @test */ public function it_restores_from_cache() { + Cache::forever('nocache::region.abc', $regionOne = Mockery::mock(StringRegion::class)); + Cache::forever('nocache::region.def', $regionTwo = Mockery::mock(StringRegion::class)); Cache::forever('nocache::session.'.md5('http://localhost/test'), [ - 'regions' => [ - $regionOne = Mockery::mock(StringRegion::class), - $regionTwo = Mockery::mock(StringRegion::class), - ], + 'regions' => ['abc', 'def'], ]); $this->createPage('/test', [ @@ -130,7 +137,7 @@ public function it_restores_from_cache() $session->restore(); - $this->assertEquals([$regionOne, $regionTwo], $session->regions()->all()); + $this->assertEquals(['abc' => $regionOne, 'def' => $regionTwo], $session->regions()->all()); $this->assertNotEquals([], $cascade = $session->cascade()); $this->assertEquals('/test', $cascade['url']); $this->assertEquals('Test page', $cascade['title']); @@ -202,10 +209,9 @@ public function it_restores_session_if_theres_a_nocache_placeholder_in_the_respo $this->viewShouldReturnRendered('default', 'Hello NOCACHE_PLACEHOLDER'); $this->createPage('test'); + Cache::forever('nocache::region.abc', $region = Mockery::mock(StringRegion::class)); Cache::put('nocache::session.'.md5('http://localhost/test'), [ - 'regions' => [ - 'abc' => $region = Mockery::mock(StringRegion::class), - ], + 'regions' => ['abc'], ]); $region->shouldReceive('render')->andReturn('world'); diff --git a/tests/StaticCaching/NocacheTagsTest.php b/tests/StaticCaching/NocacheTagsTest.php index 0272940d0f..f33f0314e8 100644 --- a/tests/StaticCaching/NocacheTagsTest.php +++ b/tests/StaticCaching/NocacheTagsTest.php @@ -2,7 +2,10 @@ namespace Tests\StaticCaching; +use Mockery; +use Statamic\Facades\Parse; use Statamic\StaticCaching\NoCache\Session; +use Statamic\StaticCaching\NoCache\StringRegion; use Tests\FakesContent; use Tests\FakesViews; use Tests\PreventSavingStacheItemsToDisk; @@ -99,4 +102,55 @@ public function it_can_keep_nested_nocache_tags_dynamic_inside_cache_tags() ->assertOk() ->assertSeeInOrder(['Updated', 'Updated', 'Existing', 'Updated']); } + + /** @test */ + public function it_only_adds_appropriate_fields_of_context_to_session() + { + // We will not add `baz` to the session because it is not used in the template. + // We will not add `nope` to the session because it is not in the context. + $expectedFields = ['foo', 'bar']; + $template = '{{ nocache }}{{ foo }}{{ bar }}{{ nope }}{{ /nocache }}'; + $context = [ + 'foo' => 'alfa', + 'bar' => 'bravo', + 'baz' => 'charlie', + ]; + + $region = Mockery::mock(StringRegion::class)->shouldReceive('placeholder')->andReturn('the placeholder')->getMock(); + + $this->mock(Session::class, fn ($mock) => $mock + ->shouldReceive('pushRegion') + ->withArgs(fn ($arg1, $arg2, $arg3) => array_keys($arg2) === $expectedFields) + ->once()->andReturn($region)); + + $this->assertEquals('the placeholder', $this->tag($template, $context)); + } + + /** @test */ + public function it_only_adds_explicitly_defined_fields_of_context_to_session() + { + // We will not add `bar` to the session because it is not explicitly defined. + // We will not add `nope` to the session because it is not in the context. + $expectedFields = ['foo', 'baz']; + $template = '{{ nocache select="foo|baz|nope" }}{{ foo }}{{ bar }}{{ nope }}{{ /nocache }}'; + $context = [ + 'foo' => 'alfa', + 'bar' => 'bravo', + 'baz' => 'charlie', + ]; + + $region = Mockery::mock(StringRegion::class)->shouldReceive('placeholder')->andReturn('the placeholder')->getMock(); + + $this->mock(Session::class, fn ($mock) => $mock + ->shouldReceive('pushRegion') + ->withArgs(fn ($arg1, $arg2, $arg3) => array_keys($arg2) === $expectedFields) + ->once()->andReturn($region)); + + $this->assertEquals('the placeholder', $this->tag($template, $context)); + } + + private function tag($tag, $data = []) + { + return (string) Parse::template($tag, $data); + } } From 334ebc1810b2e25c20d1cd47f058caf06f1b0ae2 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Tue, 14 Nov 2023 19:22:38 +0000 Subject: [PATCH 11/13] [4.x] Fix super not saving on eloquent users (#8979) Co-authored-by: Jason Varga --- src/Auth/Eloquent/User.php | 4 ++++ tests/Auth/Eloquent/EloquentUserTest.php | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Auth/Eloquent/User.php b/src/Auth/Eloquent/User.php index 416ef4a83c..605c0886eb 100644 --- a/src/Auth/Eloquent/User.php +++ b/src/Auth/Eloquent/User.php @@ -358,6 +358,10 @@ public function __set($key, $value) return $this->model()->timestamps = $value; } + if ($key === 'super') { + return $this->model()->super = $value; + } + return $this->$key = $value; } } diff --git a/tests/Auth/Eloquent/EloquentUserTest.php b/tests/Auth/Eloquent/EloquentUserTest.php index c21f5c391c..d1fab7abb0 100644 --- a/tests/Auth/Eloquent/EloquentUserTest.php +++ b/tests/Auth/Eloquent/EloquentUserTest.php @@ -139,4 +139,24 @@ public function it_gets_the_timestamps_property_from_the_model() $this->assertFalse($user->timestamps); } + + /** @test */ + public function it_gets_super_correctly_on_the_model() + { + $user = $this->makeUser(); + + $this->assertNull($user->super); + + $user->super = true; + $user->save(); + + $this->assertTrue($user->super); + $this->assertTrue($user->model()->super); + + $user->super = false; + $user->save(); + + $this->assertFalse($user->super); + $this->assertFalse($user->model()->super); + } } From 4c6fe041e2203a8033e5949ce4a5d9d6c0ad2411 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 14 Nov 2023 14:32:15 -0500 Subject: [PATCH 12/13] [4.x] More php file validation (#8991) --- src/Http/Controllers/CP/Assets/AssetsController.php | 2 +- src/Http/Controllers/CP/Fieldtypes/FilesFieldtypeController.php | 2 +- src/Http/Controllers/FormController.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Http/Controllers/CP/Assets/AssetsController.php b/src/Http/Controllers/CP/Assets/AssetsController.php index 2a1d5f7854..14b51f62c6 100644 --- a/src/Http/Controllers/CP/Assets/AssetsController.php +++ b/src/Http/Controllers/CP/Assets/AssetsController.php @@ -69,7 +69,7 @@ public function store(Request $request) 'container' => 'required', 'folder' => 'required', 'file' => ['file', function ($attribute, $value, $fail) { - if (in_array(trim(strtolower($value->getClientOriginalExtension())), ['php', 'php3', 'php4', 'php5', 'phtml'])) { + if (in_array(trim(strtolower($value->getClientOriginalExtension())), ['php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phar'])) { $fail(__('validation.uploaded')); } }], diff --git a/src/Http/Controllers/CP/Fieldtypes/FilesFieldtypeController.php b/src/Http/Controllers/CP/Fieldtypes/FilesFieldtypeController.php index 44fec25302..4709f602da 100644 --- a/src/Http/Controllers/CP/Fieldtypes/FilesFieldtypeController.php +++ b/src/Http/Controllers/CP/Fieldtypes/FilesFieldtypeController.php @@ -12,7 +12,7 @@ public function upload(Request $request) { $request->validate([ 'file' => ['file', function ($attribute, $value, $fail) { - if (in_array(trim(strtolower($value->getClientOriginalExtension())), ['php', 'php3', 'php4', 'php5', 'phtml'])) { + if (in_array(trim(strtolower($value->getClientOriginalExtension())), ['php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phar'])) { $fail(__('validation.uploaded')); } }], diff --git a/src/Http/Controllers/FormController.php b/src/Http/Controllers/FormController.php index 1fb0adc8e2..2f228ae254 100644 --- a/src/Http/Controllers/FormController.php +++ b/src/Http/Controllers/FormController.php @@ -178,7 +178,7 @@ protected function extraRules($fields) }) ->mapWithKeys(function ($field) { return [$field->handle().'.*' => ['file', function ($attribute, $value, $fail) { - if (in_array(trim(strtolower($value->getClientOriginalExtension())), ['php', 'php3', 'php4', 'php5', 'phtml'])) { + if (in_array(trim(strtolower($value->getClientOriginalExtension())), ['php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phar'])) { $fail(__('validation.uploaded')); } }]]; From 24b73ab9a6965e60b7c335b76f95fe57c9dbea67 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 14 Nov 2023 14:44:01 -0500 Subject: [PATCH 13/13] changelog --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65653a398f..8c690a1083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Release Notes +## 4.34.0 (2023-11-14) + +### What's new +- Auto-populate `array` fieldtype options. [#8980](https://github.com/statamic/cms/issues/8980) by @duncanmcclean +- Add Bard support to `read_time` modifier. [#8976](https://github.com/statamic/cms/issues/8976) by @duncanmcclean +- Antlers identifier finder. [#8965](https://github.com/statamic/cms/issues/8965) by @jasonvarga + +### What's improved +- Nocache performance improvements. [#8956](https://github.com/statamic/cms/issues/8956) by @jasonvarga +- French translations. [#8977](https://github.com/statamic/cms/issues/8977) by @ebeauchamps + +### What's fixed +- More php file validation. [#8991](https://github.com/statamic/cms/issues/8991) by @jasonvarga +- Fix super not saving on eloquent users. [#8979](https://github.com/statamic/cms/issues/8979) by @ryanmitchell +- Hide export submissions button when there are no valid exporters. [#8985](https://github.com/statamic/cms/issues/8985) by @ryanmitchell +- Only namespace asset validation attributes when on a CP route. [#8987](https://github.com/statamic/cms/issues/8987) by @ryanmitchell +- Fix for edit form page saying edit collection. [#8967](https://github.com/statamic/cms/issues/8967) by @ryanmitchell +- Fix new child entries not propagating to appropriate position in other sites trees. [#7302](https://github.com/statamic/cms/issues/7302) by @arthurperton +- Fix impersonation redirect. [#8973](https://github.com/statamic/cms/issues/8973) by @jasonvarga +- Fix error when getting alt on bard image when asset is missing. [#8959](https://github.com/statamic/cms/issues/8959) by @morphsteve +- Prevent requiring current password when changing another user's password. [#8966](https://github.com/statamic/cms/issues/8966) by @duncanmcclean +- Fix global attribute support on bard's small mark. [#8969](https://github.com/statamic/cms/issues/8969) by @jacksleight + + + ## 4.33.0 (2023-11-10) ### What's new