From 28034c7d0011964d82bfad661e3bc9ae37949416 Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Mon, 31 Jul 2023 11:17:02 -0400 Subject: [PATCH 001/294] [4.x] Fix the History Icon path (#8517) Fix the History Icon path --- resources/js/components/entries/PublishForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/entries/PublishForm.vue b/resources/js/components/entries/PublishForm.vue index 172da2329e..0faab0b0db 100644 --- a/resources/js/components/entries/PublishForm.vue +++ b/resources/js/components/entries/PublishForm.vue @@ -156,7 +156,7 @@ class="flex items-center justify-center mt-4 btn-flat px-2 w-full" v-if="!isCreating && revisionsEnabled" @click="showRevisionHistory = true"> - + {{ __('View History') }} From 2f6ece84f4db905e4b31e98bf38986027d987014 Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Tue, 1 Aug 2023 11:57:42 -0400 Subject: [PATCH 002/294] [4.x] Don't slugify apostrophes. (#8524) Don't slugify apostrophes. Closes #35823 --- resources/js/plugins/slugify.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/js/plugins/slugify.js b/resources/js/plugins/slugify.js index 2be2819da9..81a1ff992a 100644 --- a/resources/js/plugins/slugify.js +++ b/resources/js/plugins/slugify.js @@ -9,6 +9,9 @@ export default { lang = lang ?? site?.lang ?? Statamic.$config.get('lang'); const custom = Statamic.$config.get(`charmap.${lang}`) ?? undefined; + // Remove apostrophes in all languages + custom["'"] = ""; + return getSlug(text, { separator: glue || '-', lang, From aceb5ff9eff7d4c405b93a606b205113b8027252 Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Tue, 1 Aug 2023 11:57:52 -0400 Subject: [PATCH 003/294] [4.x] Fix link insert cancel in Markdown field. (#8525) Fixes link insert cancel in Markdown field. Closes #5972 --- .../js/components/fieldtypes/markdown/MarkdownFieldtype.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/fieldtypes/markdown/MarkdownFieldtype.vue b/resources/js/components/fieldtypes/markdown/MarkdownFieldtype.vue index 3c33c5850e..551298c929 100644 --- a/resources/js/components/fieldtypes/markdown/MarkdownFieldtype.vue +++ b/resources/js/components/fieldtypes/markdown/MarkdownFieldtype.vue @@ -440,7 +440,7 @@ export default { if (! url) { url = prompt(__('Enter URL'), 'https://'); if (! url) { - url = ''; + return; } } From 1fc1c6d0cb555902a2c5dbb951de7d40b73cc10b Mon Sep 17 00:00:00 2001 From: Oliver Mesieh Date: Tue, 1 Aug 2023 17:59:21 +0200 Subject: [PATCH 004/294] [4.x] Improve Bard Inline Image Extension (#8131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix bard image cannot be dragged when using the image container as drag handle * Replace inline image buttons with toolbar * Add outline to image container when selected * Replace inline input field with asset editor for editing alt attribute This introduces a new `showToolbar` prop for the Editor component. The Editor toolbar should be hidden when the user just wants to edit the alt meta value. * Improve toolbar button label visibility when bard is nested * Do not show bard set button when current selection is after inline image * Align image container border color when selected * Keep the previous value of the alt attr as long as the user doesn’t overwrite it * Hide bard set button when current selection is before the node * Swap back to default buttons, dark buttons should only be used against dark backgrounds. * Bring back alt input field to allow overriding bard alt attribute value * Fix default input state * Make sure to remove the alt if you untoggle the Alt field * Allow Bard images to fall back to their default alt * Fix code style --------- Co-authored-by: Jack McDade --- .../js/components/assets/Editor/Editor.vue | 11 ++- .../fieldtypes/bard/BardFieldtype.vue | 4 +- .../js/components/fieldtypes/bard/Image.js | 2 +- .../js/components/fieldtypes/bard/Image.vue | 91 +++++++++++++------ src/Fieldtypes/Bard/ImageNode.php | 9 +- 5 files changed, 84 insertions(+), 33 deletions(-) diff --git a/resources/js/components/assets/Editor/Editor.vue b/resources/js/components/assets/Editor/Editor.vue index ee65264e52..ce6f193d28 100644 --- a/resources/js/components/assets/Editor/Editor.vue +++ b/resources/js/components/assets/Editor/Editor.vue @@ -29,7 +29,7 @@
-
+
+ From 39293da955babf25ca41c7f1ead509f2b4245491 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 17 Aug 2023 13:49:16 -0400 Subject: [PATCH 047/294] [4.x] Make uploader synchronous (#8592) --- resources/js/components/assets/Uploader.vue | 25 ++++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/resources/js/components/assets/Uploader.vue b/resources/js/components/assets/Uploader.vue index 90cedf1d31..b8a78a9474 100644 --- a/resources/js/components/assets/Uploader.vue +++ b/resources/js/components/assets/Uploader.vue @@ -71,6 +71,7 @@ export default { uploads(uploads) { this.$emit('updated', uploads); + this.processUploadQueue(); } }, @@ -127,14 +128,8 @@ export default { basename: file.name, extension: file.name.split('.').pop(), percent: 0, - errorMessage: null - }); - - upload.upload().then(response => { - const json = JSON.parse(response.data); - response.status === 200 - ? this.handleUploadSuccess(id, json) - : this.handleUploadError(id, status, json); + errorMessage: null, + instance: upload }); }, @@ -174,6 +169,20 @@ export default { return form; }, + processUploadQueue() { + if (this.uploads.length === 0) return; + + const upload = this.uploads[0]; + const id = upload.id; + + upload.instance.upload().then(response => { + const json = JSON.parse(response.data); + response.status === 200 + ? this.handleUploadSuccess(id, json) + : this.handleUploadError(id, status, json); + }); + }, + handleUploadSuccess(id, response) { this.$emit('upload-complete', response.data, this.uploads); this.uploads.splice(this.findUploadIndex(id), 1); From 08b2bd8505e2d9f2df36b5167d6d1abe85293b9e Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 17 Aug 2023 14:09:23 -0400 Subject: [PATCH 048/294] changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c4b8e7f1..7cccb2cf31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Release Notes +## 4.18.0 (2023-08-17) + +### What's new +- Expose `uniqid` JS function for generating unique IDs. [#8571](https://github.com/statamic/cms/issues/8571) by @jacksleight +- Allow renaming of row id handle in Grid, Bard, and Replicator. [#8407](https://github.com/statamic/cms/issues/8407) by @jonassiewertsen +- Support arbitrary attributes on the vite tag. [#8305](https://github.com/statamic/cms/issues/8305) by @jackmcdade + +### What's fixed +- Make uploader synchronous. [#8592](https://github.com/statamic/cms/issues/8592) by @jasonvarga +- Fix alignment of menu icon. [#8589](https://github.com/statamic/cms/issues/8589) by @caseydwyer +- Pint updates. [#8586](https://github.com/statamic/cms/issues/8586) by @jasonvarga +- Fix slugify error. [#8583](https://github.com/statamic/cms/issues/8583) by @jasonvarga +- Only save generated title if it's different. [#8101](https://github.com/statamic/cms/issues/8101) by @aerni +- Make the views field handle reserved. [#8576](https://github.com/statamic/cms/issues/8576) by @jasonvarga +- Fix special character handling in created CP nav sections. [#8568](https://github.com/statamic/cms/issues/8568) by @jesseleite + + + ## 4.17.0 (2023-08-10) ### What's improved From 514890035b344029e02313cb6cff71600e3eb53f Mon Sep 17 00:00:00 2001 From: Andreas Bohman Date: Mon, 21 Aug 2023 15:17:38 +0200 Subject: [PATCH 049/294] [4.x] Swedish translations (#8600) A few nice-to-haves in Swedish --- resources/lang/sv.json | 7 +++++++ resources/lang/sv/messages.php | 3 +++ 2 files changed, 10 insertions(+) diff --git a/resources/lang/sv.json b/resources/lang/sv.json index 6879a00bc3..0f2837a027 100644 --- a/resources/lang/sv.json +++ b/resources/lang/sv.json @@ -62,9 +62,12 @@ "and": "och", "and :count more": "och :count till", "Any of the following conditions pass": "Något av följande villkor går igenom", + "Appearance": "Utseende", + "Appearance & Behavior": "Utseende & beteenden", "Append": "Lägg till efter", "Application Cache": "Applikationscache", "Application cache cleared.": "Applikationscachen rensades.", + "Apply": "Verkställ", "Apply Link": "Använd länk", "Are you sure you want to delete this column?": "Är du säker på att du vill radera den här kolumnen?", "Are you sure you want to delete this entry?": "Är du säker på att du vill radera det här inlägget?", @@ -155,6 +158,7 @@ "Columns": "Kolumner", "Columns have been reset to their defaults.": "Kolumnerna har återställts till sina standardinställningar.", "Commit": "Commit", + "Common": "Generellt", "Conditions": "Villkor", "Configuration": "Konfiguration", "Configuration is cached": "Konfigurationen är cachad", @@ -250,6 +254,7 @@ "Disk": "Disk", "Dismiss": "Avfärda", "Display": "Visa", + "Display Label": "Visningsetikett", "Display HTML": "Visa HTML", "Displayed Columns": "Visade kolumner", "Documentation": "Dokumentation", @@ -261,6 +266,7 @@ "Driver": "Driver", "Drop File to Upload": "Släpp fil för att ladda upp", "DummyClass": "DummyClass", + "Duplicate": "Duplicera", "Duplicate ID Regenerated": "Dubblett-ID har återskapats", "Duplicate IDs": "Dubbletter av ID:n", "Duplicate Row": "Duplicera rad", @@ -829,6 +835,7 @@ "View Site": "Visa webbplats", "View Useful Links": "Visa användbara länkar", "Views created successfully": "Vyerna skapades", + "Visibility": "Synlighet", "Visit URL": "Besök URL", "Warm": "Värm", "Whoops!": "Oj då!", diff --git a/resources/lang/sv/messages.php b/resources/lang/sv/messages.php index 7cec798020..7af8d058b8 100644 --- a/resources/lang/sv/messages.php +++ b/resources/lang/sv/messages.php @@ -62,11 +62,13 @@ 'fields_blueprints_description' => 'Ritningar definierar fälten för innehållsstrukturer som samlingar, taxonomier, användare och formulär.', 'fields_default_instructions' => 'Ställ in standardvärdet.', 'fields_display_instructions' => 'Fältets etikett visas i kontrollpanelen.', + 'fields_duplicate_instructions' => 'Om detta fält ska inkluderas när objektet dupliceras.', 'fields_fieldsets_description' => 'Fältuppsättningar är enkla, flexibla och helt valfria grupperingar av fält som hjälper till att organisera återanvändbara, förkonfigurerade fält.', 'fields_handle_instructions' => 'Fältets mallvariabel.', 'fields_instructions_instructions' => 'Visas under fältets visningsetikett, som just denna text. Markdown stöds.', 'fields_instructions_position_instructions' => 'Där instruktionerna ska placeras i förhållande till fältet.', 'fields_listable_instructions' => 'Kontrollera kolumnsynligheten för detta fält.', + 'fields_visibility_instructions' => 'Kontrollera synligheten i redigeringsformulär.', 'fieldset_import_fieldset_instructions' => 'Fältuppsättningen som ska importeras.', 'fieldset_import_prefix_instructions' => 'Prefixet som ska tillämpas på varje fält när de importeras. t.ex. hjälte_', 'fieldset_intro' => 'Fältuppsättningar är ett valfritt komplement till ritningar, och fungerar som återanvändbara delar som kan användas i ritningar.', @@ -182,4 +184,5 @@ 'user_wizard_name_instructions' => 'Lämna namnet tomt för att låta användaren fylla i det.', 'user_wizard_roles_groups_intro' => 'Användare kan tilldelas roller som anpassar deras behörigheter, åtkomst och förmågor i hela kontrollpanelen.', 'user_wizard_super_admin_instructions' => 'Superadmins har fullständig kontroll och tillgång till allt i kontrollpanelen. Ge denna roll klokt.', + 'view_more_count' => 'Visa :count till', ]; From af61e6ba6964fb067d57b9ff0fab35d294cd01f4 Mon Sep 17 00:00:00 2001 From: Andreas Bohman Date: Mon, 21 Aug 2023 16:52:37 +0200 Subject: [PATCH 050/294] [4.x] replace hard coded text with i18n (#8601) --- resources/views/auth/login.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 3dbd7d0a98..5a18356659 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -21,7 +21,7 @@
@if($emailLoginEnabled) -
— or —
+
— {{ __('or') }} —
From f700dd9754513720a54a0f61042bf169db3b54c4 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 22 Aug 2023 10:02:56 -0400 Subject: [PATCH 052/294] [4.x] Add a way to determine which entry saved event was the initiator (#8605) --- src/Entries/Entry.php | 5 +++ src/Entries/InitiatorStack.php | 48 +++++++++++++++++++++++++ src/Events/EntrySaved.php | 8 +++++ tests/Data/Entries/EntryTest.php | 50 ++++++++++++++++++++++++++ tests/Search/UpdateItemIndexesTest.php | 8 ++--- 5 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 src/Entries/InitiatorStack.php diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 8e95457db1..685a84d241 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -3,6 +3,7 @@ namespace Statamic\Entries; use ArrayAccess; +use Facades\Statamic\Entries\InitiatorStack; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Responsable; use Illuminate\Support\Carbon; @@ -336,6 +337,8 @@ public function save() $this->ancestors()->each(fn ($entry) => Blink::forget('entry-descendants-'.$entry->id())); + $stack = InitiatorStack::entry($this)->push(); + $this->directDescendants()->each->save(); $this->taxonomize(); @@ -362,6 +365,8 @@ public function save() }); } + $stack->pop(); + return true; } diff --git a/src/Entries/InitiatorStack.php b/src/Entries/InitiatorStack.php new file mode 100644 index 0000000000..09b08e8c22 --- /dev/null +++ b/src/Entries/InitiatorStack.php @@ -0,0 +1,48 @@ +entry = $entry; + $this->key = 'entry-event-initiator-'.$entry->root()->id(); + $this->stack = Blink::get($this->key) ?? collect(); + + return $this; + } + + public function push() + { + $initiator = $this->stack->first() ?? $this->entry; + + $initiatorIsAncestor = $this->entry + ->ancestors() + ->contains(fn ($entry) => $entry->id() === $initiator->id()); + + if ($this->stack->isEmpty() || $initiatorIsAncestor) { + $this->stack->push($this->entry); + Blink::put($this->key, $this->stack); + } + + return $this; + } + + public function initiator(): ?Entry + { + return $this->stack->first(); + } + + public function pop() + { + $this->stack->pop(); + } +} diff --git a/src/Events/EntrySaved.php b/src/Events/EntrySaved.php index 1b3ecb1926..29513b6d8f 100644 --- a/src/Events/EntrySaved.php +++ b/src/Events/EntrySaved.php @@ -2,19 +2,27 @@ namespace Statamic\Events; +use Facades\Statamic\Entries\InitiatorStack; use Statamic\Contracts\Git\ProvidesCommitMessage; class EntrySaved extends Event implements ProvidesCommitMessage { public $entry; + public $initiator; public function __construct($entry) { $this->entry = $entry; + $this->initiator = InitiatorStack::entry($entry)->initiator(); } public function commitMessage() { return __('Entry saved', [], config('statamic.git.locale')); } + + public function isInitiator() + { + return $this->entry->id() === $this->initiator->id(); + } } diff --git a/tests/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index a1c92be9da..4860561ab8 100644 --- a/tests/Data/Entries/EntryTest.php +++ b/tests/Data/Entries/EntryTest.php @@ -2237,4 +2237,54 @@ public function __call($method, $args) $this->assertCount(2, $fakeBlink->calls['origin-Entry-2']); $this->assertCount(2, $fakeBlink->calls['origin-Entry-3']); } + + /** @test */ + public function initially_saved_entry_gets_put_into_events() + { + Facades\Site::setConfig([ + 'default' => 'en', + 'sites' => [ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => '/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => '/fr/'], + 'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => '/de/'], + 'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => '/es/'], + ], + ]); + + // Bunch of localizations of the same entry. + $one = EntryFactory::collection('test')->id('1')->locale('en')->data(['foo' => 'root'])->create(); + $two = EntryFactory::collection('test')->id('2')->origin('1')->locale('fr')->create(); + $three = EntryFactory::collection('test')->id('3')->origin('2')->locale('de')->create(); + $four = EntryFactory::collection('test')->id('4')->origin('3')->locale('es')->create(); + + // Separate entry with localization. + $five = EntryFactory::collection('test')->id('5')->locale('en')->create(); + $six = EntryFactory::collection('test')->id('6')->origin('5')->locale('fr')->create(); + + // Yet another separate entry. + $seven = EntryFactory::collection('test')->id('7')->create(); + + // Avoid using a fake so we can use a real listener. + $events = collect(); + Event::listen(function (EntrySaved $event) use ($five, &$events) { + $events[] = $event; + + // Save unrelated entry during the localization recursion. + if ($event->entry->id() === '3') { + $five->save(); + } + }); + + $two->save(); + $seven->save(); + + $this->assertEquals([ + ['4', '2'], + ['3', '2'], + ['6', '5'], + ['5', '5'], + ['2', '2'], + ['7', '7'], + ], $events->map(fn ($event) => [$event->entry->id(), $event->initiator->id()])->all()); + } } diff --git a/tests/Search/UpdateItemIndexesTest.php b/tests/Search/UpdateItemIndexesTest.php index eaade70cf0..7089708c23 100644 --- a/tests/Search/UpdateItemIndexesTest.php +++ b/tests/Search/UpdateItemIndexesTest.php @@ -4,8 +4,8 @@ use Mockery; use Statamic\Contracts\Search\Searchable; -use Statamic\Events\EntryDeleted; -use Statamic\Events\EntrySaved; +use Statamic\Events\UserDeleted; +use Statamic\Events\UserSaved; use Statamic\Facades\Search; use Statamic\Search\UpdateItemIndexes; use Tests\TestCase; @@ -19,7 +19,7 @@ public function it_updates_indexes_on_save() Search::shouldReceive('updateWithinIndexes')->with($item)->once(); - $event = new EntrySaved($item); + $event = new UserSaved($item); $listener = new UpdateItemIndexes; @@ -33,7 +33,7 @@ public function it_updates_indexes_on_delete() Search::shouldReceive('deleteFromIndexes')->with($item)->once(); - $event = new EntryDeleted($item); + $event = new UserDeleted($item); $listener = new UpdateItemIndexes; From a0f7252e1983aa1bd4175d19ec4f208c21326676 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 22 Aug 2023 10:13:38 -0400 Subject: [PATCH 053/294] [4.x] Rename method (#8608) --- src/Events/EntrySaved.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Events/EntrySaved.php b/src/Events/EntrySaved.php index 29513b6d8f..423ce0ddf6 100644 --- a/src/Events/EntrySaved.php +++ b/src/Events/EntrySaved.php @@ -21,7 +21,7 @@ public function commitMessage() return __('Entry saved', [], config('statamic.git.locale')); } - public function isInitiator() + public function isInitial() { return $this->entry->id() === $this->initiator->id(); } From 4f8666114750b1572a3a5b07c7313652751758f8 Mon Sep 17 00:00:00 2001 From: Florian Langer Date: Tue, 22 Aug 2023 16:40:05 +0200 Subject: [PATCH 054/294] [4.x] Ensure Dropdown List Remains Anchored on Resize (#8607) --- resources/js/mixins/PositionsSelectOptions.js | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/resources/js/mixins/PositionsSelectOptions.js b/resources/js/mixins/PositionsSelectOptions.js index 5c418bc047..1bdd9c456f 100644 --- a/resources/js/mixins/PositionsSelectOptions.js +++ b/resources/js/mixins/PositionsSelectOptions.js @@ -1,27 +1,33 @@ -import { computePosition, offset, flip } from '@floating-ui/dom'; +import { computePosition, offset, flip, autoUpdate } from '@floating-ui/dom'; export default { - methods: { + positionOptions(dropdownList, component, {width}) { + dropdownList.style.width = width; - positionOptions(dropdownList, component, { width }) { - dropdownList.style.width = width - - computePosition(component.$refs.toggle, dropdownList, { - placement: 'bottom', - middleware: [ - offset({ mainAxis: 0, crossAxis: -1 }), - flip(), - ] - }).then(({ x, y }) => { - Object.assign(dropdownList.style, { + function updatePosition() { + computePosition(component.$refs.toggle, dropdownList, { + placement: 'bottom', + middleware: [ + offset({mainAxis: 0, crossAxis: -1}), + flip(), + ] + }).then(({x, y}) => { // Round to avoid blurry text - left: `${Math.round(x)}px`, - top: `${Math.round(y)}px`, + Object.assign(dropdownList.style, { + left: `${Math.round(x)}px`, + top: `${Math.round(y)}px`, + }); }); - }); - } + } - } + const cleanup = autoUpdate( + component.$refs.toggle, + dropdownList, + updatePosition + ); + this.$once('hook:destroyed', cleanup); + } + } } From 04694499cc5d82921338967b0a84f76f7e05d0da Mon Sep 17 00:00:00 2001 From: Oliver Mesieh Date: Tue, 22 Aug 2023 17:07:32 +0200 Subject: [PATCH 055/294] [4.x] Fix Bard set picker position (#8574) --- resources/css/components/fieldtypes/bard.css | 2 +- resources/js/components/fieldtypes/bard/BardFieldtype.vue | 4 ++-- .../js/components/fieldtypes/bard/FloatingMenuPlugin.js | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/resources/css/components/fieldtypes/bard.css b/resources/css/components/fieldtypes/bard.css index ea7f4ce87b..035e01efa9 100644 --- a/resources/css/components/fieldtypes/bard.css +++ b/resources/css/components/fieldtypes/bard.css @@ -159,7 +159,7 @@ } .bard-add-set-button { - @apply flex items-center justify-center absolute -left-6 top-[-6px] @xl/bard:-left-8 z-1; + @apply flex items-center justify-center absolute -left-4 top-[-6px] z-1; } .bard-footer-toolbar { diff --git a/resources/js/components/fieldtypes/bard/BardFieldtype.vue b/resources/js/components/fieldtypes/bard/BardFieldtype.vue index bc5a798c0e..7df9cf97fe 100644 --- a/resources/js/components/fieldtypes/bard/BardFieldtype.vue +++ b/resources/js/components/fieldtypes/bard/BardFieldtype.vue @@ -58,7 +58,7 @@ :should-show="shouldShowSetButton" :is-showing="showAddSetButton" v-if="editor" - v-slot="{ x, y }" + v-slot="{ y }" @shown="showAddSetButton = true" @hidden="showAddSetButton = false" > @@ -72,7 +72,7 @@ - - + + + -
+
From 28b42a17159b7e5b5f3becd182384f51aa7449fa Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Thu, 14 Sep 2023 13:02:09 -0400 Subject: [PATCH 090/294] [4.x] Add placeholder text to make Taggable usage more clear (#8703) Add placeholder text to make Taggable usage more clear Closes #8534 --- resources/lang/en/fieldtypes.php | 1 + src/Fieldtypes/Taggable.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/lang/en/fieldtypes.php b/resources/lang/en/fieldtypes.php index 092a5f2fca..de915aebbf 100644 --- a/resources/lang/en/fieldtypes.php +++ b/resources/lang/en/fieldtypes.php @@ -154,6 +154,7 @@ 'structures.title' => 'Structures', 'table.title' => 'Table', 'taggable.title' => 'Taggable', + 'taggable.config.placeholder' => 'Type and press ↩ Enter', 'taxonomies.title' => 'Taxonomies', 'template.config.blueprint' => 'Adds a "map to blueprint" option. Learn more in the [documentation](https://statamic.dev/views#inferring-templates-from-entry-blueprints).', 'template.config.folder' => 'Only show templates in this folder.', diff --git a/src/Fieldtypes/Taggable.php b/src/Fieldtypes/Taggable.php index 86d5c4bda0..85d55eef5b 100644 --- a/src/Fieldtypes/Taggable.php +++ b/src/Fieldtypes/Taggable.php @@ -18,7 +18,7 @@ protected function configFieldItems(): array 'display' => __('Placeholder'), 'instructions' => __('statamic::fieldtypes.select.config.placeholder'), 'type' => 'text', - 'default' => '', + 'default' => __('statamic::fieldtypes.taggable.config.placeholder'), ], ]; } From a0de1bf0299d26863713fd26382180d9d74b7049 Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Sat, 16 Sep 2023 16:05:54 -0400 Subject: [PATCH 091/294] [4.x] Autofocus on new array field row's first input (#8710) * Autofocus on new array field row's first input * Busted. --- resources/js/components/fieldtypes/ArrayFieldtype.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/js/components/fieldtypes/ArrayFieldtype.vue b/resources/js/components/fieldtypes/ArrayFieldtype.vue index 0324cca9a8..bc98ff0f6c 100644 --- a/resources/js/components/fieldtypes/ArrayFieldtype.vue +++ b/resources/js/components/fieldtypes/ArrayFieldtype.vue @@ -181,6 +181,9 @@ export default { methods: { addValue() { this.data.push(this.newSortableValue()); + this.$nextTick(() => { + this.$el.querySelector('tr:last-child input').focus(); + }); }, confirmDeleteValue(index) { From 30c53bbffd5176cde2cdb63eab93c12a88b08dcc Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Sun, 17 Sep 2023 20:05:52 -0400 Subject: [PATCH 092/294] [4.x] Stop forcing max_items: 1 on form fields (#8713) Stop forcing max_items: 1 on form fields Closes #3809 --- src/Forms/Fieldtype.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Forms/Fieldtype.php b/src/Forms/Fieldtype.php index f066efbde2..9fb8fe87e7 100644 --- a/src/Forms/Fieldtype.php +++ b/src/Forms/Fieldtype.php @@ -31,7 +31,6 @@ protected function configFieldItems(): array 'display' => __('Max Items'), 'default' => 1, 'instructions' => __('statamic::fieldtypes.form.config.max_items'), - 'min' => 1, ], 'query_scopes' => [ 'display' => __('Query Scopes'), From 4c23c87dcc266f65690c3b17b24f7f7312915399 Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Mon, 18 Sep 2023 10:04:27 -0400 Subject: [PATCH 093/294] [4.x] More thoroughly escape and truncate Code replicator previews (#8718) More thoroughly escape and truncate Code replicator previews Closes #8596 --- resources/js/bootstrap/globals.js | 14 ++++++++++++++ .../js/components/fieldtypes/CodeFieldtype.vue | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/resources/js/bootstrap/globals.js b/resources/js/bootstrap/globals.js index 51a065d191..b35b6c9257 100644 --- a/resources/js/bootstrap/globals.js +++ b/resources/js/bootstrap/globals.js @@ -89,3 +89,17 @@ export function utf8btoa(stringToEncode) { export function uniqid() { return uid(); } + +export function truncate(string, length, ending='...') { + if (string.length <= length) return string; + + return string.substring(0, length - ending.length) + ending; +} + +export function escapeHtml(string) { + return string.replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); +} diff --git a/resources/js/components/fieldtypes/CodeFieldtype.vue b/resources/js/components/fieldtypes/CodeFieldtype.vue index e187c34551..2d7c8165c3 100644 --- a/resources/js/components/fieldtypes/CodeFieldtype.vue +++ b/resources/js/components/fieldtypes/CodeFieldtype.vue @@ -105,7 +105,7 @@ export default { return 'theme-' + this.config.theme; }, replicatorPreview() { - return this.value.code ? this.value.code.replace('<', '<') : ''; + return this.value.code ? truncate(escapeHtml(this.value.code), 60) : ''; }, readOnlyOption() { return this.isReadOnly ? 'nocursor' : false; From d5ec4e8e1110b19f602e79139c45f7a3b1a511d4 Mon Sep 17 00:00:00 2001 From: Wiebke Vogel <54707973+wiebkevogel@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:45:01 +0200 Subject: [PATCH 094/294] [4.x] Prevent deletion of selection when filtering in stack selector (#8693) --- resources/js/components/inputs/relationship/Selector.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/inputs/relationship/Selector.vue b/resources/js/components/inputs/relationship/Selector.vue index 54ee61ab91..c5b9af8b1d 100644 --- a/resources/js/components/inputs/relationship/Selector.vue +++ b/resources/js/components/inputs/relationship/Selector.vue @@ -31,7 +31,7 @@ :active-filter-badges="activeFilterBadges" :active-count="activeFilterCount" :search-query="searchQuery" - @changed="filterChanged" + @changed="filterChanged($event, false)" />
From 5d022245618273bd7d5b04e225a1ebc2248ce144 Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Mon, 18 Sep 2023 11:17:02 -0400 Subject: [PATCH 095/294] [4.x] Fix fluent tag camelCase params (#8715) Co-authored-by: Jason Varga --- src/Tags/FluentTag.php | 2 +- tests/Tags/FluentTagTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Tags/FluentTag.php b/src/Tags/FluentTag.php index 1f5279e8c2..b4c15bdde3 100644 --- a/src/Tags/FluentTag.php +++ b/src/Tags/FluentTag.php @@ -183,7 +183,7 @@ public function params($params) */ public function __call($param, $args) { - $this->param($param, $args[0] ?? true); + $this->param(Str::snake($param), $args[0] ?? true); return $this; } diff --git a/tests/Tags/FluentTagTest.php b/tests/Tags/FluentTagTest.php index 0e1757052f..2fff5f62e9 100644 --- a/tests/Tags/FluentTagTest.php +++ b/tests/Tags/FluentTagTest.php @@ -52,6 +52,7 @@ public function it_handles_params_fluently($usedTag, $expectedTagName, $expected 'params' => [ 'sort' => 'slug:desc', 'limit' => 3, + 'alfa_bravo' => 'charlie', 'title:contains' => 'chewy', 'slug:contains' => 'han', 'description:contains' => 'luke', @@ -68,6 +69,7 @@ public function it_handles_params_fluently($usedTag, $expectedTagName, $expected $fluentTag = FluentTag::make($usedTag) ->sort('slug:desc') ->limit(3) + ->alfaBravo('charlie') ->param('title:contains', 'chewy') ->params([ 'slug:contains' => 'han', From 8d21d6f19d5e64e80aa12bf91e4774fece18780e Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Mon, 18 Sep 2023 11:28:45 -0400 Subject: [PATCH 096/294] [4.x] Fix sidebar's empty card when no actions are present (#8720) Fix sidebar's empty card when no actions are present Closes #8393 --- resources/css/components/publish.css | 4 ---- resources/js/components/entries/PublishForm.vue | 2 +- resources/js/components/publish/Tabs.vue | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/resources/css/components/publish.css b/resources/css/components/publish.css index 8239a0fb2e..5aae6fccb0 100644 --- a/resources/css/components/publish.css +++ b/resources/css/components/publish.css @@ -97,10 +97,6 @@ code.parent-url { margin-left: 20px; width: 300px; - .publish-tab-actions { - @apply border-b; - } - .publish-fields .form-group { @apply p-4; } diff --git a/resources/js/components/entries/PublishForm.vue b/resources/js/components/entries/PublishForm.vue index 0faab0b0db..54956e890a 100644 --- a/resources/js/components/entries/PublishForm.vue +++ b/resources/js/components/entries/PublishForm.vue @@ -98,7 +98,7 @@ @blur="container.$emit('blur', $event)" > From 327a1ea7c518a0ab79a6c2564d5b72ddcb7cf6a1 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 7 Nov 2023 13:44:13 -0500 Subject: [PATCH 241/294] [4.x] Fix duplicate entry action translation (#8946) --- src/Actions/DuplicateEntry.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Actions/DuplicateEntry.php b/src/Actions/DuplicateEntry.php index 0737468330..c7e1a35f47 100644 --- a/src/Actions/DuplicateEntry.php +++ b/src/Actions/DuplicateEntry.php @@ -27,7 +27,8 @@ public function confirmationText() ->contains(fn ($entry) => $entry->descendants()->count()); if ($hasDescendants) { - return 'duplicate_action_localizations_confirmation'; + /** @translation */ + return 'statamic::messages.duplicate_action_localizations_confirmation'; } return parent::confirmationText(); @@ -36,9 +37,13 @@ public function confirmationText() public function warningText() { if ($this->items->contains(fn ($entry) => $entry->hasOrigin())) { - return $this->items->count() === 1 - ? 'duplicate_action_warning_localization' - : 'duplicate_action_warning_localizations'; + if ($this->items->count() === 1) { + /** @translation */ + return 'statamic::messages.duplicate_action_warning_localization'; + } + + /** @translation */ + return 'statamic::messages.duplicate_action_warning_localizations'; } } From a9b71e4e60b7f5ef786c05dc65459920a83d8f60 Mon Sep 17 00:00:00 2001 From: Duncan McClean <19637309+duncanmcclean@users.noreply.github.com> Date: Tue, 7 Nov 2023 21:14:43 +0000 Subject: [PATCH 242/294] [4.x] Entries Fieldtype: Use columns from preferences (#8900) --- src/Fieldtypes/Entries.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Fieldtypes/Entries.php b/src/Fieldtypes/Entries.php index 8738c3d1f7..6564bb7163 100644 --- a/src/Fieldtypes/Entries.php +++ b/src/Fieldtypes/Entries.php @@ -5,6 +5,7 @@ use Illuminate\Support\Collection as SupportCollection; use Statamic\Contracts\Data\Localization; use Statamic\Contracts\Entries\Entry as EntryContract; +use Statamic\CP\Column; use Statamic\Exceptions\CollectionNotFoundException; use Statamic\Facades\Collection; use Statamic\Facades\Entry; @@ -375,6 +376,22 @@ public function toGqlType() public function getColumns() { + if (count($this->getConfiguredCollections()) === 1) { + $columns = $this->getBlueprint()->columns(); + + $status = Column::make('status') + ->listable(true) + ->visible(true) + ->defaultVisibility(true) + ->sortable(false); + + $columns->put('status', $status); + + $columns->setPreferred("collections.{$this->getConfiguredCollections()[0]}.columns"); + + return $columns->rejectUnlisted()->values(); + } + return $this->getBlueprint()->columns()->values()->all(); } From 7c27413680a046e7ade3f4ff0510a698a39a65d3 Mon Sep 17 00:00:00 2001 From: Oliver Mesieh Date: Wed, 8 Nov 2023 15:25:11 +0100 Subject: [PATCH 243/294] [4.x] Add cmd+k support for Bard (#8950) --- resources/js/components/Popover.vue | 2 +- resources/js/components/fieldtypes/bard/Link.js | 6 ++++++ .../components/fieldtypes/bard/LinkToolbarButton.vue | 11 ++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/resources/js/components/Popover.vue b/resources/js/components/Popover.vue index f92caf87ec..75d64d2eb5 100644 --- a/resources/js/components/Popover.vue +++ b/resources/js/components/Popover.vue @@ -100,7 +100,7 @@ export default { if (this.disabled) return; this.isOpen = true; - this.escBinding = this.$keys.bind('esc', e => this.close()); + this.escBinding = this.$keys.bindGlobal('esc', e => this.close()); this.$nextTick(() => { this.cleanupAutoUpdater = autoUpdate(this.$refs.trigger.firstChild, this.$refs.popover, this.computePosition); diff --git a/resources/js/components/fieldtypes/bard/Link.js b/resources/js/components/fieldtypes/bard/Link.js index 0351bea5df..1a003c6db9 100644 --- a/resources/js/components/fieldtypes/bard/Link.js +++ b/resources/js/components/fieldtypes/bard/Link.js @@ -64,6 +64,12 @@ export const Link = Mark.create({ ] }, + addKeyboardShortcuts() { + return { + 'Mod-k': () => this.options.vm.$emit('link-toggle'), + } + }, + addProseMirrorPlugins() { const vm = this.options.vm; return [ diff --git a/resources/js/components/fieldtypes/bard/LinkToolbarButton.vue b/resources/js/components/fieldtypes/bard/LinkToolbarButton.vue index 372e7478fe..dd5cfd0bb3 100644 --- a/resources/js/components/fieldtypes/bard/LinkToolbarButton.vue +++ b/resources/js/components/fieldtypes/bard/LinkToolbarButton.vue @@ -71,8 +71,17 @@ export default { this.close(); this.editor.view.dom.focus(); } + }, - } + created() { + this.bard.$on('link-toggle', () => { + this.toggleLinkToolbar(); + this.$refs.popover.toggle(); + }); + }, + beforeDestroy() { + this.bard.$off('link-toggle'); + } } From 8db1ad71ab1af571ffd33cd7f367c18982705f00 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 8 Nov 2023 14:35:42 +0000 Subject: [PATCH 244/294] [4.x] Add pint cli in require-dev (#8955) --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index f159eb262d..1bbf69cc6b 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,7 @@ "require-dev": { "fakerphp/faker": "~1.10", "google/cloud-translate": "^1.6", + "laravel/pint": "^1.0", "mockery/mockery": "^1.3.3", "orchestra/testbench": "^7.0 || ^8.0", "phpunit/phpunit": "^9.0" From 072c73c01e8c71a1343322806e36747f203346ec Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 8 Nov 2023 14:36:24 +0000 Subject: [PATCH 245/294] [4.x] Bind `AssetContainerContents` to the service provider (#8954) --- src/Assets/AssetContainer.php | 2 +- src/Assets/AssetContainerContents.php | 4 +++- src/Providers/FilesystemServiceProvider.php | 5 +++++ tests/Assets/AssetFolderTest.php | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Assets/AssetContainer.php b/src/Assets/AssetContainer.php index a3228f6157..762a937bd5 100644 --- a/src/Assets/AssetContainer.php +++ b/src/Assets/AssetContainer.php @@ -282,7 +282,7 @@ public function listContents() public function contents() { return Blink::once('asset-listing-cache-'.$this->handle(), function () { - return new AssetContainerContents($this); + return app(AssetContainerContents::class)->container($this); }); } diff --git a/src/Assets/AssetContainerContents.php b/src/Assets/AssetContainerContents.php index b4e24e8c5c..52fffd5317 100644 --- a/src/Assets/AssetContainerContents.php +++ b/src/Assets/AssetContainerContents.php @@ -16,9 +16,11 @@ class AssetContainerContents protected $filteredFiles; protected $filteredDirectories; - public function __construct($container) + public function container($container) { $this->container = $container; + + return $this; } /** diff --git a/src/Providers/FilesystemServiceProvider.php b/src/Providers/FilesystemServiceProvider.php index 9ce6d8c3db..04e0f75865 100644 --- a/src/Providers/FilesystemServiceProvider.php +++ b/src/Providers/FilesystemServiceProvider.php @@ -3,6 +3,7 @@ namespace Statamic\Providers; use Illuminate\Support\ServiceProvider; +use Statamic\Assets\AssetContainerContents; use Statamic\Filesystem\FilesystemAdapter; class FilesystemServiceProvider extends ServiceProvider @@ -13,6 +14,10 @@ public function register() return new FilesystemAdapter($this->app->make('files'), base_path()); }); + $this->app->bind(AssetContainerContents::class, function () { + return new AssetContainerContents(); + }); + $paths = [ 'standard' => base_path(), 'content' => base_path('content'), diff --git a/tests/Assets/AssetFolderTest.php b/tests/Assets/AssetFolderTest.php index f964e5709a..a9b45e5eeb 100644 --- a/tests/Assets/AssetFolderTest.php +++ b/tests/Assets/AssetFolderTest.php @@ -192,7 +192,7 @@ public function it_creates_directory_when_saving() Storage::fake('local'); $container = $this->mock(AssetContainer::class); - $container->shouldReceive('contents')->andReturn(new AssetContainerContents($container)); + $container->shouldReceive('contents')->andReturn((new AssetContainerContents)->container($container)); $container->shouldReceive('disk')->andReturn(new FlysystemAdapter($disk = Storage::disk('local'))); $container->shouldReceive('foldersCacheKey')->andReturn('irrelevant for test'); $container->shouldReceive('handle')->andReturn('local'); @@ -216,7 +216,7 @@ public function it_adds_a_gitkeep_file_when_saving() Storage::fake('local'); $container = $this->mock(AssetContainer::class); - $container->shouldReceive('contents')->andReturn(new AssetContainerContents($container)); + $container->shouldReceive('contents')->andReturn((new AssetContainerContents)->container($container)); $container->shouldReceive('disk')->andReturn(new FlysystemAdapter($disk = Storage::disk('local'))); $container->shouldReceive('foldersCacheKey')->andReturn('irrelevant for test'); $container->shouldReceive('handle')->andReturn('local'); From 5e3c0defb73001bc5d9100c8565cdca270daa98e Mon Sep 17 00:00:00 2001 From: Duncan McClean <19637309+duncanmcclean@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:46:48 +0000 Subject: [PATCH 246/294] [4.x] Remove debounce when renaming assets & folders (#8953) --- .../js/components/assets/Browser/CreateFolder.vue | 1 + .../js/components/fieldtypes/TextFieldtype.vue | 14 ++++++++++++-- src/Actions/RenameAsset.php | 1 + src/Actions/RenameAssetFolder.php | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/resources/js/components/assets/Browser/CreateFolder.vue b/resources/js/components/assets/Browser/CreateFolder.vue index a59043ce19..5a82d54af1 100644 --- a/resources/js/components/assets/Browser/CreateFolder.vue +++ b/resources/js/components/assets/Browser/CreateFolder.vue @@ -17,6 +17,7 @@ :instructions="__('messages.asset_folders_directory_instructions')" :focus="true" :required="true" + :config="{ debounce: false }" v-model="directory" /> diff --git a/resources/js/components/fieldtypes/TextFieldtype.vue b/resources/js/components/fieldtypes/TextFieldtype.vue index 16de06afe8..bf2cc91059 100644 --- a/resources/js/components/fieldtypes/TextFieldtype.vue +++ b/resources/js/components/fieldtypes/TextFieldtype.vue @@ -13,7 +13,7 @@ :placeholder="__(config.placeholder)" :name="name" :id="fieldId" - @input="updateDebounced" + @input="inputUpdated" @focus="$emit('focus')" @blur="$emit('blur')" /> @@ -24,7 +24,17 @@ import Fieldtype from './Fieldtype.vue'; export default { - mixins: [Fieldtype] + mixins: [Fieldtype], + + methods: { + inputUpdated(value) { + if (! this.config.debounce) { + return this.update(value) + } + + this.updateDebounced(value) + } + } } diff --git a/src/Actions/RenameAsset.php b/src/Actions/RenameAsset.php index 89f3e3140b..5c6e4256fa 100644 --- a/src/Actions/RenameAsset.php +++ b/src/Actions/RenameAsset.php @@ -51,6 +51,7 @@ protected function fieldItems() 'classes' => 'mousetrap', 'focus' => true, 'placeholder' => $this->items->containsOneItem() ? $this->items->first()->filename() : null, + 'debounce' => false, ], ]; } diff --git a/src/Actions/RenameAssetFolder.php b/src/Actions/RenameAssetFolder.php index 79d29638a3..bbdefb70ea 100644 --- a/src/Actions/RenameAssetFolder.php +++ b/src/Actions/RenameAssetFolder.php @@ -46,6 +46,7 @@ protected function fieldItems() 'validate' => 'required|alpha_dash', 'classes' => 'mousetrap', 'focus' => true, + 'debounce' => false, ], ]; } From e609fe08fc9ee4e6c0e88b46c7cf3d209c7051bb Mon Sep 17 00:00:00 2001 From: Duncan McClean <19637309+duncanmcclean@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:47:01 +0000 Subject: [PATCH 247/294] [4.x] Fix entries fieldtype not respecting collection sort column & direction (#8894) Co-authored-by: Jason Varga --- .../relationship/RelationshipFieldtype.vue | 2 ++ .../inputs/relationship/RelationshipInput.vue | 12 ++++++++++-- src/Fieldtypes/Entries.php | 18 ++++++++++++++---- src/Fieldtypes/Relationship.php | 12 ++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/resources/js/components/fieldtypes/relationship/RelationshipFieldtype.vue b/resources/js/components/fieldtypes/relationship/RelationshipFieldtype.vue index 63ba451597..90962a95b8 100644 --- a/resources/js/components/fieldtypes/relationship/RelationshipFieldtype.vue +++ b/resources/js/components/fieldtypes/relationship/RelationshipFieldtype.vue @@ -25,6 +25,8 @@ :read-only="isReadOnly" :taggable="taggable" :tree="meta.tree" + :initial-sort-column="meta.initialSortColumn" + :initial-sort-direction="meta.initialSortDirection" @focus="$emit('focus')" @blur="$emit('blur')" @input="update" diff --git a/resources/js/components/inputs/relationship/RelationshipInput.vue b/resources/js/components/inputs/relationship/RelationshipInput.vue index 5aac36bb21..3ba22ab8ee 100644 --- a/resources/js/components/inputs/relationship/RelationshipInput.vue +++ b/resources/js/components/inputs/relationship/RelationshipInput.vue @@ -70,8 +70,8 @@ :selections-url="selectionsUrl" :site="site" :initial-columns="columns" - initial-sort-column="title" - initial-sort-direction="asc" + :initial-sort-column="initialSortColumn" + :initial-sort-direction="initialSortDirection" :initial-selections="value" :max-selections="maxItems" :search="search" @@ -132,6 +132,14 @@ export default { default: () => [] }, tree: Object, + initialSortColumn: { + type: String, + default: 'title' + }, + initialSortDirection: { + type: String, + default: 'asc' + } }, components: { diff --git a/src/Fieldtypes/Entries.php b/src/Fieldtypes/Entries.php index 6564bb7163..13786461ef 100644 --- a/src/Fieldtypes/Entries.php +++ b/src/Fieldtypes/Entries.php @@ -166,9 +166,9 @@ protected function getFirstCollectionFromRequest($request) public function getSortColumn($request) { - $column = $request->get('sort'); + $column = $request->sort ?? 'title'; - if (! $column && ! $request->search) { + if (! $request->sort && ! $request->search && count($this->getConfiguredCollections()) < 2) { $column = $this->getFirstCollectionFromRequest($request)->sortField(); } @@ -177,15 +177,25 @@ public function getSortColumn($request) public function getSortDirection($request) { - $order = $request->get('order', 'asc'); + $order = $request->order ?? 'asc'; - if (! $request->sort && ! $request->search) { + if (! $request->sort && ! $request->search && count($this->getConfiguredCollections()) < 2) { $order = $this->getFirstCollectionFromRequest($request)->sortDirection(); } return $order; } + public function initialSortColumn() + { + return $this->getSortColumn(optional()); + } + + public function initialSortDirection() + { + return $this->getSortDirection(optional()); + } + protected function getIndexQuery($request) { $query = Entry::query(); diff --git a/src/Fieldtypes/Relationship.php b/src/Fieldtypes/Relationship.php index 3a1eef1c73..b318b55681 100644 --- a/src/Fieldtypes/Relationship.php +++ b/src/Fieldtypes/Relationship.php @@ -134,6 +134,8 @@ public function preload() 'formComponent' => $this->getFormComponent(), 'formComponentProps' => $this->getFormComponentProps(), 'taggable' => $this->getTaggable(), + 'initialSortColumn' => $this->initialSortColumn(), + 'initialSortDirection' => $this->initialSortDirection(), ]; } @@ -296,6 +298,16 @@ public function getSortDirection($request) return $request->get('order', 'asc'); } + public function initialSortColumn() + { + return 'title'; + } + + public function initialSortDirection() + { + return 'asc'; + } + protected function getTaggable() { return $this->taggable; From f6c688154f6bdbd0b67039f8f11dcd98ba061e77 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 10 Nov 2023 10:11:25 -0500 Subject: [PATCH 248/294] [4.x] Front-end form asset field php file validation (#8968) --- src/Http/Controllers/FormController.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Http/Controllers/FormController.php b/src/Http/Controllers/FormController.php index 2d8391edb7..1fb0adc8e2 100644 --- a/src/Http/Controllers/FormController.php +++ b/src/Http/Controllers/FormController.php @@ -177,7 +177,11 @@ protected function extraRules($fields) return $field->fieldtype()->handle() === 'assets'; }) ->mapWithKeys(function ($field) { - return [$field->handle().'.*' => 'file']; + return [$field->handle().'.*' => ['file', function ($attribute, $value, $fail) { + if (in_array(trim(strtolower($value->getClientOriginalExtension())), ['php', 'php3', 'php4', 'php5', 'phtml'])) { + $fail(__('validation.uploaded')); + } + }]]; }) ->all(); From 1c708549b927ef213be43b4044fc2d3e45d8843f Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 10 Nov 2023 10:15:14 -0500 Subject: [PATCH 249/294] changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13883b1db5..7a214c0dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Release Notes +## 4.33.0 (2023-11-10) + +### What's new +- Bard supports cmd+k for links. [#8950](https://github.com/statamic/cms/issues/8950) by @o1y +- The Entries fieldtype use columns from preferences in the stack selector. [#8900](https://github.com/statamic/cms/issues/8900) by @duncanmcclean +- Bind `AssetContainerContents` to the service provider. [#8954](https://github.com/statamic/cms/issues/8954) by @ryanmitchell +- Require `pint` in dev. [#8955](https://github.com/statamic/cms/issues/8955) by @ryanmitchell + +### What's fixed +- Front-end form asset field php file validation. [#8968](https://github.com/statamic/cms/issues/8968) by @jasonvarga +- Fix entries fieldtype not respecting collection sort column & direction. [#8894](https://github.com/statamic/cms/issues/8894) by @duncanmcclean +- Fix duplicate entry action translation. [#8946](https://github.com/statamic/cms/issues/8946) by @jasonvarga +- Fix SortableList not reacting to disabled prop changes. [#8949](https://github.com/statamic/cms/issues/8949) by @duncanmcclean +- Remove debounce when renaming assets & folders. [#8953](https://github.com/statamic/cms/issues/8953) by @duncanmcclean +- Use translations from fallback locale when primary locale is missing translations. [#8940](https://github.com/statamic/cms/issues/8940) by @duncanmcclean + + + ## 4.32.0 (2023-11-03) ### What's new From f8367de8d108a0b746b2cdb87f01747e74775267 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 10 Nov 2023 10:47:33 -0500 Subject: [PATCH 250/294] changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a214c0dc7..65653a398f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,14 @@ - Bard supports cmd+k for links. [#8950](https://github.com/statamic/cms/issues/8950) by @o1y - The Entries fieldtype use columns from preferences in the stack selector. [#8900](https://github.com/statamic/cms/issues/8900) by @duncanmcclean - Bind `AssetContainerContents` to the service provider. [#8954](https://github.com/statamic/cms/issues/8954) by @ryanmitchell +- Support arrays in wrap modifier [#8942](https://github.com/statamic/cms/issues/8942) by @jacksleight +- Add `mount` to augmented collection. [#8928](https://github.com/statamic/cms/issues/8928) by @duncanmcclean - Require `pint` in dev. [#8955](https://github.com/statamic/cms/issues/8955) by @ryanmitchell +### What's improved +- French translations. [#8945](https://github.com/statamic/cms/issues/8945) [#8934](https://github.com/statamic/cms/issues/8934) by @ebeauchamps +- German translations. [#8939](https://github.com/statamic/cms/issues/8939) by @doriengr + ### What's fixed - Front-end form asset field php file validation. [#8968](https://github.com/statamic/cms/issues/8968) by @jasonvarga - Fix entries fieldtype not respecting collection sort column & direction. [#8894](https://github.com/statamic/cms/issues/8894) by @duncanmcclean @@ -15,6 +21,12 @@ - Fix SortableList not reacting to disabled prop changes. [#8949](https://github.com/statamic/cms/issues/8949) by @duncanmcclean - Remove debounce when renaming assets & folders. [#8953](https://github.com/statamic/cms/issues/8953) by @duncanmcclean - Use translations from fallback locale when primary locale is missing translations. [#8940](https://github.com/statamic/cms/issues/8940) by @duncanmcclean +- Fix missing title on relationship fields in multi-site. [#8936](https://github.com/statamic/cms/issues/8936) by @duncanmcclean +- Prevent ensuring fields on entries if they already exist. [#8926](https://github.com/statamic/cms/issues/8926) by @duncanmcclean +- Fix `statamic.web` middleware not being merged. [#8935](https://github.com/statamic/cms/issues/8935) by @duncanmcclean +- Fix infinite loop on listing table of mounted collection. [#8937](https://github.com/statamic/cms/issues/8937) by @duncanmcclean +- Fix "Always Save" toggle not being saved when used on linked field. [#8927](https://github.com/statamic/cms/issues/8927) by @duncanmcclean +- Fix slug field not targeting sibling fields inside a replicator. [#8929](https://github.com/statamic/cms/issues/8929) by @duncanmcclean From b39a14509cbe97d6058b07b629dca2aeb4b8b624 Mon Sep 17 00:00:00 2001 From: Jack Sleight Date: Fri, 10 Nov 2023 15:58:35 +0000 Subject: [PATCH 251/294] [4.x] Add global attribute support to bard's small mark (#8969) --- resources/js/components/fieldtypes/bard/Small.js | 12 +++++++++--- src/Fieldtypes/Bard/Marks/Small.php | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/resources/js/components/fieldtypes/bard/Small.js b/resources/js/components/fieldtypes/bard/Small.js index 9157b23d43..8198f9e025 100644 --- a/resources/js/components/fieldtypes/bard/Small.js +++ b/resources/js/components/fieldtypes/bard/Small.js @@ -1,9 +1,15 @@ -import { Mark } from '@tiptap/core'; +import { Mark, mergeAttributes } from '@tiptap/core'; export const Small = Mark.create({ name: 'small', + addOptions() { + return { + HTMLAttributes: {}, + } + }, + parseHTML() { return [ { @@ -12,8 +18,8 @@ export const Small = Mark.create({ ] }, - renderHTML() { - return ['small', 0] + renderHTML({ HTMLAttributes }) { + return ['small', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0] }, addCommands() { diff --git a/src/Fieldtypes/Bard/Marks/Small.php b/src/Fieldtypes/Bard/Marks/Small.php index 0b475a8dfe..33eb90d873 100644 --- a/src/Fieldtypes/Bard/Marks/Small.php +++ b/src/Fieldtypes/Bard/Marks/Small.php @@ -3,11 +3,19 @@ namespace Statamic\Fieldtypes\Bard\Marks; use Tiptap\Core\Mark; +use Tiptap\Utils\HTML; class Small extends Mark { public static $name = 'small'; + public function addOptions() + { + return [ + 'HTMLAttributes' => [], + ]; + } + public function parseHTML() { return [ @@ -17,8 +25,12 @@ public function parseHTML() ]; } - public function renderHTML($mark) + public function renderHTML($mark, $HTMLAttributes = []) { - return ['small', 0]; + return [ + 'small', + HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), + 0, + ]; } } From 5e9d787552188efa4cfe7ddacaf7a600936314fc Mon Sep 17 00:00:00 2001 From: Duncan McClean <19637309+duncanmcclean@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:01:18 +0000 Subject: [PATCH 252/294] [4.x] Don't require current password when changing another user's password (#8966) --- resources/js/components/users/ChangePassword.vue | 2 ++ resources/js/components/users/PublishForm.vue | 4 +++- resources/views/users/edit.blade.php | 1 + src/Http/Controllers/CP/Users/PasswordController.php | 11 ++++++++--- src/Http/Controllers/CP/Users/UsersController.php | 1 + 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/resources/js/components/users/ChangePassword.vue b/resources/js/components/users/ChangePassword.vue index b81fd8cf7a..b30eafb39e 100644 --- a/resources/js/components/users/ChangePassword.vue +++ b/resources/js/components/users/ChangePassword.vue @@ -10,6 +10,7 @@
@@ -75,7 +76,8 @@ export default { actions: Object, method: String, canEditPassword: Boolean, - canEditBlueprint: Boolean + canEditBlueprint: Boolean, + requiresCurrentPassword: Boolean, }, data() { diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 749cfe2355..856a61c8f0 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -14,6 +14,7 @@ :initial-meta="{{ json_encode($meta) }}" :can-edit-password="{{ Statamic\Support\Str::bool($canEditPassword) }}" :can-edit-blueprint="{{ Statamic\Support\Str::bool($user->can('configure fields')) }}" + :requires-current-password="{{ Statamic\Support\Str::bool($requiresCurrentPassword) }}" > @endsection diff --git a/src/Http/Controllers/CP/Users/PasswordController.php b/src/Http/Controllers/CP/Users/PasswordController.php index da60a31448..53507394ff 100644 --- a/src/Http/Controllers/CP/Users/PasswordController.php +++ b/src/Http/Controllers/CP/Users/PasswordController.php @@ -16,10 +16,15 @@ public function update(Request $request, $user) $this->authorize('editPassword', $user); - $request->validate([ - 'current_password' => ['required', 'current_password'], + $rules = [ 'password' => ['required', 'confirmed', Password::default()], - ]); + ]; + + if ($request->user()->id === $user) { + $rules['current_password'] = ['required', 'current_password']; + } + + $request->validate($rules); $user->password($request->password)->save(); diff --git a/src/Http/Controllers/CP/Users/UsersController.php b/src/Http/Controllers/CP/Users/UsersController.php index 247cc039a5..8324b53264 100644 --- a/src/Http/Controllers/CP/Users/UsersController.php +++ b/src/Http/Controllers/CP/Users/UsersController.php @@ -233,6 +233,7 @@ public function edit(Request $request, $user) 'editBlueprint' => cp_route('users.blueprint.edit'), ], 'canEditPassword' => User::fromUser($request->user())->can('editPassword', $user), + 'requiresCurrentPassword' => $request->user()->id === $user->id(), ]; if ($request->wantsJson()) { From b737cec150874abeb727d8df3b37110b86addb19 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 10 Nov 2023 11:48:58 -0500 Subject: [PATCH 253/294] [4.x] Antlers identifier finder (#8965) --- src/View/Antlers/Antlers.php | 6 + .../Language/Parser/IdentifierFinder.php | 104 ++++++++++++++++++ tests/Antlers/Parser/IdentifierFinderTest.php | 32 ++++++ 3 files changed, 142 insertions(+) create mode 100644 src/View/Antlers/Language/Parser/IdentifierFinder.php create mode 100644 tests/Antlers/Parser/IdentifierFinderTest.php diff --git a/src/View/Antlers/Antlers.php b/src/View/Antlers/Antlers.php index 33d1a1bfdf..b0fc4f9c43 100644 --- a/src/View/Antlers/Antlers.php +++ b/src/View/Antlers/Antlers.php @@ -4,6 +4,7 @@ use Closure; use Statamic\Contracts\View\Antlers\Parser; +use Statamic\View\Antlers\Language\Parser\IdentifierFinder; class Antlers { @@ -43,4 +44,9 @@ public function parseLoop($content, $data, $supplement = true, $context = []) { return new AntlersLoop($this->parser(), $content, $data, $supplement, $context); } + + public function identifiers(string $content): array + { + return (new IdentifierFinder)->getIdentifiers($content); + } } diff --git a/src/View/Antlers/Language/Parser/IdentifierFinder.php b/src/View/Antlers/Language/Parser/IdentifierFinder.php new file mode 100644 index 0000000000..c44ba03d4f --- /dev/null +++ b/src/View/Antlers/Language/Parser/IdentifierFinder.php @@ -0,0 +1,104 @@ +documentParser = new DocumentParser; + } + + public function getIdentifiers(string $content): array + { + $this->documentParser->resetState(); + $this->documentParser->parse($content); + + // Get nodes will return a "flat" list of nodes. + // This will make things a bit easier on us. + $antlersNodes = collect($this->documentParser->getNodes())->filter(function ($node) { + return $node instanceof AntlersNode && ! $node->isComment && ! ($node->isClosingTag && ! $node->isSelfClosing); + })->values()->all(); + + $identifiers = []; + + foreach ($antlersNodes as $node) { + $identifiers = array_merge($this->processNode($node, $identifiers)); + } + + return collect($identifiers)->filter(function ($identifier) { + // Quick and dirty check to see if we care about this identifier. + return ! Str::contains($identifier, ['.', ':', '[', ']', '(', ')', '{', '}']); + })->unique()->values()->all(); + } + + protected function processVarReference(AntlersNode $node, VariableReference $reference, array $identifiers): array + { + foreach ($reference->pathParts as $part) { + if ($part instanceof PathNode) { + if (! array_key_exists($part->name, $node->interpolationRegions)) { + $identifiers[] = $part->name; + } + } elseif ($part instanceof VariableReference) { + $identifiers = array_merge($this->processVarReference($node, $part, $identifiers)); + } + } + + return $identifiers; + } + + protected function processNode(AntlersNode $node, array $identifiers): array + { + if ($node->name != null) { + $identifiers[] = $node->name->name; + + if ($node->name->methodPart && ! Str::contains($node->name->methodPart, ':')) { + $identifiers[] = $node->name->methodPart; + } + + if ($node->pathReference != null) { + $identifiers = array_merge($this->processVarReference($node, $node->pathReference, $identifiers)); + } + + if (! empty($node->processedInterpolationRegions)) { + foreach ($node->processedInterpolationRegions as $region) { + if (count($region) == 0 || ! $region[0] instanceof AntlersNode) { + continue; + } + + $identifiers = array_merge($this->processNode($region[0], $identifiers)); + } + } + + if ($node->hasParameters) { + foreach ($node->parameters as $parameter) { + if ($parameter->isVariableReference) { + $identifiers[] = $parameter->value; + } + } + } else { + if (! empty($node->runtimeNodes)) { + foreach ($node->runtimeNodes as $runtimeNode) { + if (! $runtimeNode instanceof VariableNode) { + continue; + } + + if ($runtimeNode->variableReference == null) { + $identifiers[] = $runtimeNode->name; + } + } + } + } + } + + return $identifiers; + } +} diff --git a/tests/Antlers/Parser/IdentifierFinderTest.php b/tests/Antlers/Parser/IdentifierFinderTest.php new file mode 100644 index 0000000000..524625e564 --- /dev/null +++ b/tests/Antlers/Parser/IdentifierFinderTest.php @@ -0,0 +1,32 @@ +assertEquals([ + 'foo', + 'bar', + 'baz', + 'collection', + 'blog', + 'delta', + 'echo', + 'the_limit', + 'title', + ], (new IdentifierFinder)->getIdentifiers($template)); + } +} From ce60ef3a73712f538dc109bff480d3efb230a613 Mon Sep 17 00:00:00 2001 From: Steve Hurst | morphsites Date: Fri, 10 Nov 2023 18:26:49 +0000 Subject: [PATCH 254/294] [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 255/294] [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 256/294] [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 257/294] [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 258/294] [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 259/294] [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 260/294] [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 261/294] [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 262/294] [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 263/294] [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 264/294] [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 265/294] [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 266/294] 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 From ed4080f7d9bce6c1844b97a8c8ba39b5d3488cbe Mon Sep 17 00:00:00 2001 From: Jeroen Peters Date: Tue, 14 Nov 2023 22:12:11 +0100 Subject: [PATCH 267/294] [4.x] Dutch translations (#8993) Co-authored-by: Jeroen Peters --- resources/lang/nl.json | 3 +-- resources/lang/nl/fieldtypes.php | 1 + resources/lang/nl/messages.php | 3 +++ resources/lang/nl/permissions.php | 2 ++ resources/lang/nl/validation.php | 1 + 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/lang/nl.json b/resources/lang/nl.json index c879e36212..c88108b41a 100644 --- a/resources/lang/nl.json +++ b/resources/lang/nl.json @@ -394,8 +394,6 @@ "Expand\/Collapse Sets": "Set in-\/uitklappen", "Expect a root page": "Verwacht een root-pagina", "Expired": "Verlopen", - "Export as CSV": "Als CSV exporteren", - "Export as JSON": "Als JSON exporteren", "Export Submissions": "Inzendingen exporteren", "External link": "Externe link", "False": "Niet waar", @@ -780,6 +778,7 @@ "Scaffold Views": "Views genereren", "Scheduled": "Gepland", "Search": "Zoeken", + "Search Index": "Zoekindex", "Search Indexes": "Zoekindexen", "Search Sets": "Sets zoeken", "Search...": "Zoek ...", diff --git a/resources/lang/nl/fieldtypes.php b/resources/lang/nl/fieldtypes.php index 4bed4bdc2b..da35c32e71 100644 --- a/resources/lang/nl/fieldtypes.php +++ b/resources/lang/nl/fieldtypes.php @@ -74,6 +74,7 @@ 'entries.config.collections' => 'Kies welke collecties gekozen kunnen worden door gebruikers.', 'entries.config.create' => 'Het aanmaken van nieuwe entries toestaan.', 'entries.config.query_scopes' => 'Kies welke query scopes toegepast worden bij het ophalen van entries.', + 'entries.config.search_index' => 'Zoekindex', 'entries.title' => 'Entries', 'float.title' => 'Float', 'form.config.max_items' => 'Het maximum aantal selecteerbare formulieren.', diff --git a/resources/lang/nl/messages.php b/resources/lang/nl/messages.php index f452290eea..3af0a25232 100644 --- a/resources/lang/nl/messages.php +++ b/resources/lang/nl/messages.php @@ -68,6 +68,9 @@ 'collections_route_instructions' => 'De route definieert het URL-patroon. Lees meer in de [documentatie](https://statamic.dev/collections#routing).', 'collections_sort_direction_instructions' => 'De standaard sorteervolgorde.', 'collections_taxonomies_instructions' => 'Koppel entries in deze collectie aan taxonomieën. Velden worden automatisch toegevoegd aan publiceerformulieren.', + 'duplicate_action_localizations_confirmation' => 'Weet je zeker dat je deze actie wilt uitvoeren? Alle lokalisaties worden dan ook gedupliceerd.', + 'duplicate_action_warning_localization' => 'Deze entry is een lokalisatie. De originele entry zal worden gedupliceerd.', + 'duplicate_action_warning_localizations' => 'Één of meer van deze entries zijn lokalisaties. In die gevallen zal het origineel gedupliceerd worden.', 'email_utility_configuration_description' => 'E-mailsettings worden geconfigureerd in :path', 'email_utility_description' => 'Bekijk e-mailconfiguratiesettings and verstuur testmails.', 'entry_origin_instructions' => 'De nieuwe lokalisatie neemt waarden over van het item op de geselecteerde site.', diff --git a/resources/lang/nl/permissions.php b/resources/lang/nl/permissions.php index 26eba89e23..1e1746ee06 100644 --- a/resources/lang/nl/permissions.php +++ b/resources/lang/nl/permissions.php @@ -12,6 +12,8 @@ 'configure_addons_desc' => 'Toegang tot addons, met de mogelijkheid om addons te installeren en te verwijderen.', 'manage_preferences' => 'Beheer Instellingen', 'manage_preferences_desc' => 'Mogelijkheid om globale en rol specifieke voorkeuren aan te passen.', + 'group_sites' => 'Sites', + 'access_{site}_site' => 'Toegang tot site :site', 'group_collections' => 'Collecties', 'configure_collections' => 'Configureer collecties', 'configure_collections_desc' => 'Geeft toegang tot alle collectiegerelateerde rechten', diff --git a/resources/lang/nl/validation.php b/resources/lang/nl/validation.php index 80d05e4594..0f662dd79b 100644 --- a/resources/lang/nl/validation.php +++ b/resources/lang/nl/validation.php @@ -153,6 +153,7 @@ 'date_fieldtype_end_date_required' => 'Einddatum is verplicht.', 'date_fieldtype_end_date_invalid' => 'Dit is geen geldige einddatum.', 'code_fieldtype_rulers' => 'Dit is ongeldig.', + 'options_require_keys' => 'Voor elke optie is een key verplicht. ', 'custom.attribute-name.rule-name' => 'aangepast-bericht', 'attributes' => [], ]; From 0ef15f0cd28b99c82ae773eaaeb0194a5fb97071 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Wed, 15 Nov 2023 09:59:31 -0500 Subject: [PATCH 268/294] [4.x] Add ability to customize bard/replicator set icons directory (#8931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show default icon directory and folder settings usin placeholders. * Wip. * Use provider implementation instead. * Use user’s config in set / set group icon selectors. * Allow use of named arguments, if they just want to pass `$folder`. * Rework `` tp support custom icons. * Revert "Rework `` tp support custom icons." This reverts commit ca573debbcdb74c4302ff57b8c5b049524a92ea0. * Flesh out custom icons static method, on `Sets` instead of `Bard`. * Use svg html passed from server in set picker. * Reference more generic keys in blueprint icon selectors. * Backend handles this now. * Rename setter method. * No comment * Cleanup fallback handling. * Camel case like other provided json vars. * Ensure we provide default icon directory and folder to script. * Get icon html here as well. * Render using v-html. * Set default icon. * Docblock. * Fix issue with custom `$folder` not being respected. * Prepare custom SVG icon contents for script. * Provide custom SVG icon contents to script. * Revert this icon html functionality, since we’re providing that to script for component now. * We’re in a Vue component, so just use methods instead of vanilla JS funcs. * Render using `` again, but pass custom directory. * If custom icon is available, render that instead. * Clean up naming. * Update set picker as well. * Fix fallback. * Always show icon components. * Show custom icon, or fallback to Statamic when not available. * Pass failing tests. * Test that user can provide custom icon base dir and/or sub-folder to script. * We shouldn’t need to provide default directory to script. * Mounted is cleaner than created + nextTick. * Fix rendering of icons in blueprint builder. * Fix fallback icon handling. * Fix more fallback icon handling. * Fix custom icon handling when configuring custom directory AND sub-folder. * Update tests. --- resources/js/components/SvgIcon.vue | 66 ++++++++++++++----- .../js/components/blueprints/Section.vue | 26 ++++++-- resources/js/components/blueprints/Tab.vue | 22 ++++++- .../fieldtypes/replicator/SetPicker.vue | 47 ++++++++++--- src/Fieldtypes/Icon.php | 34 ++++++++++ src/Fieldtypes/Sets.php | 53 +++++++++++++-- .../View/Composers/JavascriptComposer.php | 2 + src/Providers/CpServiceProvider.php | 3 + tests/Fieldtypes/SetsTest.php | 44 +++++++++++++ 9 files changed, 260 insertions(+), 37 deletions(-) diff --git a/resources/js/components/SvgIcon.vue b/resources/js/components/SvgIcon.vue index 62ed20620b..a84addd2e2 100644 --- a/resources/js/components/SvgIcon.vue +++ b/resources/js/components/SvgIcon.vue @@ -4,33 +4,57 @@ diff --git a/resources/js/components/blueprints/Section.vue b/resources/js/components/blueprints/Section.vue index efe071af78..55a0dcd878 100644 --- a/resources/js/components/blueprints/Section.vue +++ b/resources/js/components/blueprints/Section.vue @@ -7,7 +7,7 @@
- +