From b2b45468c89007d205dac34c6efd4a56a8c4b4f7 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 9 Feb 2024 15:08:15 +0000 Subject: [PATCH 01/24] [4.x] Include honeypot in Alpine.js form data (#9498) --- src/Forms/JsDrivers/AbstractJsDriver.php | 1 + tests/Tags/Form/FormCreateAlpineTest.php | 8 ++++++-- tests/Tags/Form/FormCreateCustomDriverTest.php | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Forms/JsDrivers/AbstractJsDriver.php b/src/Forms/JsDrivers/AbstractJsDriver.php index 075f621f65..a864d196ae 100644 --- a/src/Forms/JsDrivers/AbstractJsDriver.php +++ b/src/Forms/JsDrivers/AbstractJsDriver.php @@ -136,6 +136,7 @@ protected function getInitialFormData() ->fields() ->preProcess() ->values() + ->when($this->form->honeypot(), fn ($fields, $honeypot) => $fields->merge([$honeypot => null])) ->map(function ($defaultProcessedValue, $handle) use ($oldValues) { return $oldValues->has($handle) ? $oldValues->get($handle) diff --git a/tests/Tags/Form/FormCreateAlpineTest.php b/tests/Tags/Form/FormCreateAlpineTest.php index e8e568059d..4648398e30 100644 --- a/tests/Tags/Form/FormCreateAlpineTest.php +++ b/tests/Tags/Form/FormCreateAlpineTest.php @@ -97,6 +97,7 @@ public function it_renders_x_data_on_form_tag() 'fav_animals' => [], 'fav_colour' => null, 'fav_subject' => null, + 'winnie' => null, ]); $expected = '
'; @@ -122,6 +123,7 @@ public function it_renders_x_data_with_old_data_on_form_tag() 'fav_animals' => [], 'fav_colour' => null, 'fav_subject' => null, + 'winnie' => null, ]); $expected = ''; @@ -142,6 +144,7 @@ public function it_renders_scoped_x_data_on_form_tag() 'fav_animals' => [], 'fav_colour' => null, 'fav_subject' => null, + 'winnie' => null, ], ]); @@ -170,6 +173,7 @@ public function it_renders_scoped_x_data_with_old_data_on_form_tag() 'fav_animals' => ['cat'], 'fav_colour' => null, 'fav_subject' => null, + 'winnie' => null, ], ]); @@ -194,7 +198,7 @@ public function it_renders_proper_x_data_for_multiple_select_field() ], ]; - $expected = 'x-data="'.$this->jsonEncode(['favourite_animals' => []]).'"'; + $expected = 'x-data="'.$this->jsonEncode(['favourite_animals' => [], 'winnie' => null]).'"'; $this->assertFieldRendersHtml($expected, $config, [], ['js' => 'alpine']); } @@ -211,7 +215,7 @@ public function it_renders_proper_x_data_for_multiple_assets_field() ], ]; - $expected = 'x-data="'.$this->jsonEncode(['selfies' => []]).'"'; + $expected = 'x-data="'.$this->jsonEncode(['selfies' => [], 'winnie' => null]).'"'; $this->assertFieldRendersHtml($expected, $config, [], ['js' => 'alpine']); } diff --git a/tests/Tags/Form/FormCreateCustomDriverTest.php b/tests/Tags/Form/FormCreateCustomDriverTest.php index 2e4daeb288..ba37e8519d 100644 --- a/tests/Tags/Form/FormCreateCustomDriverTest.php +++ b/tests/Tags/Form/FormCreateCustomDriverTest.php @@ -156,6 +156,7 @@ public function custom_driver_can_get_initial_form_data() 'name' => null, 'email' => null, 'message' => null, + 'winnie' => null, ]; $this->assertEquals($expected, $initialData); @@ -179,6 +180,7 @@ public function custom_driver_getting_initial_data_respects_old_data() 'name' => 'San Holo', 'email' => null, 'message' => null, + 'winnie' => null, ]; $this->assertEquals($expected, $initialData); From 461f8c716b9d27ef51743d0eca20838889aa7e15 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 9 Feb 2024 15:37:53 +0000 Subject: [PATCH 02/24] [4.x] Fix directory separator in Templates fieldtype on Windows (#9483) --- routes/cp.php | 4 +-- .../CP/API/TemplatesController.php | 11 ++++-- tests/Fieldtypes/TemplatesTest.php | 35 +++++++++++++++++++ .../templates/blog/index.antlers.html | 1 + 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 tests/Fieldtypes/TemplatesTest.php create mode 100644 tests/__fixtures__/templates/blog/index.antlers.html diff --git a/routes/cp.php b/routes/cp.php index c3dd3ef5e9..881bed8fdc 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -300,8 +300,8 @@ }); Route::group(['prefix' => 'api', 'as' => 'api.'], function () { - Route::resource('addons', AddonsApiController::class); - Route::resource('templates', TemplatesController::class); + Route::resource('addons', AddonsApiController::class)->only('index'); + Route::resource('templates', TemplatesController::class)->only('index'); }); Route::group(['prefix' => 'preferences', 'as' => 'preferences.'], function () { diff --git a/src/Http/Controllers/CP/API/TemplatesController.php b/src/Http/Controllers/CP/API/TemplatesController.php index f5bf71b2bd..fc42c5d732 100644 --- a/src/Http/Controllers/CP/API/TemplatesController.php +++ b/src/Http/Controllers/CP/API/TemplatesController.php @@ -5,6 +5,7 @@ use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use Statamic\Http\Controllers\CP\CpController; +use Statamic\Support\Str; class TemplatesController extends CpController { @@ -17,11 +18,17 @@ public function index() foreach ($iterator as $file) { if ($file->isFile()) { - $views->push(str_before(str_replace_first($path.DIRECTORY_SEPARATOR, '', $file->getPathname()), '.')); + $viewPath = Str::of($file->getPathname()) + ->after($path.DIRECTORY_SEPARATOR) + ->before('.') + ->replace('\\', '/') + ->toString(); + + $views->push($viewPath); } } - return $views->filter()->values(); + return $views->filter()->sort()->values(); }) ->values(); } diff --git a/tests/Fieldtypes/TemplatesTest.php b/tests/Fieldtypes/TemplatesTest.php new file mode 100644 index 0000000000..5848c325f3 --- /dev/null +++ b/tests/Fieldtypes/TemplatesTest.php @@ -0,0 +1,35 @@ +app['config']->set('view.paths', [ + __DIR__.'/../__fixtures__/templates', + ]); + } + + /** @test */ + public function it_returns_a_list_of_templates() + { + $this + ->actingAs(User::make()->makeSuper()->save()) + ->get(cp_route('api.templates.index')) + ->assertJson([ + 'blog/index', + 'conditions-literals', + 'five_hundred_nested_ifs', + 'nested-conditionals', + ]); + } +} diff --git a/tests/__fixtures__/templates/blog/index.antlers.html b/tests/__fixtures__/templates/blog/index.antlers.html new file mode 100644 index 0000000000..26be7babe6 --- /dev/null +++ b/tests/__fixtures__/templates/blog/index.antlers.html @@ -0,0 +1 @@ +

Blog Listing

From 71956583f6737046a8d24b5a129766423061d92a Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 9 Feb 2024 15:41:56 +0000 Subject: [PATCH 03/24] [4.x] Fix `$authenticatedUser` error with third-party addon events (#9490) --- src/Events/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Events/Event.php b/src/Events/Event.php index 1a74725925..d22e6c4c4c 100644 --- a/src/Events/Event.php +++ b/src/Events/Event.php @@ -10,7 +10,7 @@ abstract class Event { use Dispatchable; - public ?UserContract $authenticatedUser; + public ?UserContract $authenticatedUser = null; public static function dispatch() { From e259196e077a85cfff56953dfc3eb4c4e6786e50 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 9 Feb 2024 15:49:31 +0000 Subject: [PATCH 04/24] [4.x] Fix error from Code Fieldtype when switching sites in global (#9488) --- resources/js/components/fieldtypes/CodeFieldtype.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/js/components/fieldtypes/CodeFieldtype.vue b/resources/js/components/fieldtypes/CodeFieldtype.vue index 7a11f191dd..ce0c3964a5 100644 --- a/resources/js/components/fieldtypes/CodeFieldtype.vue +++ b/resources/js/components/fieldtypes/CodeFieldtype.vue @@ -136,6 +136,8 @@ export default { watch: { value(value, oldValue) { if (value.code == this.codemirror.doc.getValue()) return; + if (! value.code) value.code = ''; + this.codemirror.doc.setValue(value.code); }, readOnlyOption(val) { From e383870f70aab6c7debabc05c6a0f496c91d1220 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 9 Feb 2024 15:58:07 +0000 Subject: [PATCH 05/24] [4.x] Add GraphQL type for Group fieldtype (#9499) --- src/Fieldtypes/Group.php | 27 ++++++++++++++++++++++++++- src/GraphQL/Types/GroupType.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/GraphQL/Types/GroupType.php diff --git a/src/Fieldtypes/Group.php b/src/Fieldtypes/Group.php index 33a997e8a7..e86e1e7130 100644 --- a/src/Fieldtypes/Group.php +++ b/src/Fieldtypes/Group.php @@ -2,8 +2,12 @@ namespace Statamic\Fieldtypes; +use Statamic\Facades\GraphQL; use Statamic\Fields\Fields; use Statamic\Fields\Fieldtype; +use Statamic\Fields\Values; +use Statamic\GraphQL\Types\GroupType; +use Statamic\Support\Str; class Group extends Fieldtype { @@ -108,7 +112,7 @@ private function performAugmentation($value, $shallow) { $method = $shallow ? 'shallowAugment' : 'augment'; - return $this->fields()->addValues($value ?? [])->{$method}()->values()->all(); + return new Values($this->fields()->addValues($value ?? [])->{$method}()->values()->all()); } public function preProcessValidatable($value) @@ -122,4 +126,25 @@ public function preProcessValidatable($value) ->all(), ); } + + public function toGqlType() + { + return GraphQL::type($this->gqlItemTypeName()); + } + + public function addGqlTypes() + { + GraphQL::addType(new GroupType($this, $this->gqlItemTypeName())); + + $this->fields()->all()->each(function ($field) { + $field->fieldtype()->addGqlTypes(); + }); + } + + private function gqlItemTypeName() + { + return 'Group_'.collect($this->field->handlePath())->map(function ($part) { + return Str::studly($part); + })->join('_'); + } } diff --git a/src/GraphQL/Types/GroupType.php b/src/GraphQL/Types/GroupType.php new file mode 100644 index 0000000000..8f391f922f --- /dev/null +++ b/src/GraphQL/Types/GroupType.php @@ -0,0 +1,33 @@ +fieldtype = $fieldtype; + $this->attributes['name'] = $name; + } + + public function fields(): array + { + $fields = $this->fieldtype->fields()->toGql(); + + return $fields + ->map(function ($field) use ($fields) { + $field['resolve'] = function ($row, $args, $context, $info) use ($fields) { + return ($resolver = $fields[$info->fieldName]['resolve'] ?? null) + ? $resolver($row, $args, $context, $info) + : $row[$info->fieldName]; + }; + + return $field; + }) + ->all(); + } +} From 269f70e069d4f79285049e70c8e059e61effd039 Mon Sep 17 00:00:00 2001 From: Jack Sleight Date: Fri, 9 Feb 2024 16:27:28 +0000 Subject: [PATCH 06/24] [4.x] Add clear value button to popover date fieldtype (#9478) --- resources/js/components/fieldtypes/date/RangePopover.vue | 9 ++++++++- .../js/components/fieldtypes/date/SinglePopover.vue | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/resources/js/components/fieldtypes/date/RangePopover.vue b/resources/js/components/fieldtypes/date/RangePopover.vue index 6e1b6b6977..7f9c3cc36c 100644 --- a/resources/js/components/fieldtypes/date/RangePopover.vue +++ b/resources/js/components/fieldtypes/date/RangePopover.vue @@ -28,7 +28,7 @@
-
+
+
@@ -195,6 +198,10 @@ export default { }); }, + clear() { + this.$emit('input', null) + }, + resetPicker() { this.picker = this.$refs.picker; } diff --git a/resources/js/components/fieldtypes/date/SinglePopover.vue b/resources/js/components/fieldtypes/date/SinglePopover.vue index fb8addafff..587649977c 100644 --- a/resources/js/components/fieldtypes/date/SinglePopover.vue +++ b/resources/js/components/fieldtypes/date/SinglePopover.vue @@ -23,7 +23,7 @@
-
+
+
@@ -93,6 +96,10 @@ export default { this.$nextTick(() => this.$refs.popover?.close()); }, + clear() { + this.$emit('input', null) + }, + resetPicker() { this.picker = this.$refs.picker; } From 4b62dc18bf7c1aaae427842acb919af00467068e Mon Sep 17 00:00:00 2001 From: Peiman Nourani Date: Fri, 9 Feb 2024 21:30:18 +0330 Subject: [PATCH 07/24] [4.x] Translatable displays (#9450) --- resources/js/components/Breadcrumb.vue | 2 +- resources/js/components/ResourceDeleter.vue | 2 +- resources/js/components/SiteSelector.vue | 2 +- .../js/components/assets/Browser/Browser.vue | 2 +- .../js/components/blueprints/LinkFields.vue | 6 +++--- .../js/components/blueprints/Listing.vue | 2 +- .../js/components/blueprints/RegularField.vue | 2 +- .../js/components/blueprints/Section.vue | 2 +- resources/js/components/blueprints/Tab.vue | 2 +- .../collections/OneOrManySitesField.vue | 2 +- resources/js/components/collections/View.vue | 2 +- .../js/components/data-list/ColumnPicker.vue | 4 ++-- resources/js/components/data-list/Table.vue | 2 +- .../js/components/entries/PublishForm.vue | 6 +++--- .../components/field-validation/Builder.vue | 2 +- resources/js/components/fields/Settings.vue | 2 +- .../js/components/fieldsets/EditForm.vue | 2 +- .../components/fieldsets/FieldsetDeleter.vue | 2 +- resources/js/components/fieldsets/Listing.vue | 2 +- resources/js/components/globals/Sites.vue | 4 ++-- resources/js/components/inputs/Text.vue | 2 +- .../components/inputs/relationship/Item.vue | 6 +++--- .../inputs/relationship/SelectField.vue | 4 ++-- resources/js/components/nav/Builder.vue | 4 ++-- .../js/components/navigation/Listing.vue | 2 +- resources/js/components/roles/Listing.vue | 2 +- resources/js/components/roles/PublishForm.vue | 2 +- resources/js/components/terms/PublishForm.vue | 2 +- .../js/components/user-groups/Listing.vue | 2 +- resources/js/components/users/Listing.vue | 4 ++-- resources/views/blueprints/index.blade.php | 20 +++++++++---------- resources/views/collections/empty.blade.php | 2 +- .../views/forms/blueprints/edit.blade.php | 2 +- resources/views/forms/show.blade.php | 2 +- resources/views/partials/breadcrumb.blade.php | 2 +- resources/views/taxonomies/empty.blade.php | 2 +- resources/views/taxonomies/show.blade.php | 2 +- resources/views/usergroups/show.blade.php | 2 +- src/Auth/CorePermissions.php | 14 ++++++------- src/Fieldtypes/UserRoles.php | 4 ++-- src/Forms/Fieldtype.php | 4 ++-- .../Controllers/CP/Forms/FormsController.php | 4 ++-- .../CP/Preferences/Nav/RoleNavController.php | 2 +- .../Controllers/CP/Users/RolesController.php | 2 +- 44 files changed, 73 insertions(+), 73 deletions(-) diff --git a/resources/js/components/Breadcrumb.vue b/resources/js/components/Breadcrumb.vue index 9b85eafbd4..56e6d13119 100644 --- a/resources/js/components/Breadcrumb.vue +++ b/resources/js/components/Breadcrumb.vue @@ -2,7 +2,7 @@ diff --git a/resources/js/components/ResourceDeleter.vue b/resources/js/components/ResourceDeleter.vue index ce8049b28b..76e7fe1845 100644 --- a/resources/js/components/ResourceDeleter.vue +++ b/resources/js/components/ResourceDeleter.vue @@ -45,7 +45,7 @@ export default { }, modalTitle() { - return __('Delete :resource', {resource: this.title}); + return __('Delete :resource', {resource: __(this.title)}); }, modalBody() { diff --git a/resources/js/components/SiteSelector.vue b/resources/js/components/SiteSelector.vue index c999529f9f..163685c202 100644 --- a/resources/js/components/SiteSelector.vue +++ b/resources/js/components/SiteSelector.vue @@ -5,7 +5,7 @@ :value="site" :clearable="false" :searchable="false" - :get-option-label="site => site.name" + :get-option-label="site => __(site.name)" :options="sites" @input="$emit('input', $event)" /> diff --git a/resources/js/components/assets/Browser/Browser.vue b/resources/js/components/assets/Browser/Browser.vue index f26158c5dc..5226089cf7 100644 --- a/resources/js/components/assets/Browser/Browser.vue +++ b/resources/js/components/assets/Browser/Browser.vue @@ -23,7 +23,7 @@