diff --git a/resources/js/components/Publish/PublishForm.vue b/resources/js/components/Publish/PublishForm.vue index 16085c94..60282d28 100644 --- a/resources/js/components/Publish/PublishForm.vue +++ b/resources/js/components/Publish/PublishForm.vue @@ -180,33 +180,8 @@ export default { }, created() { - // If we're creating a resource through the 'Create' on a HasMany field somewhere, fill any fields... if (this.publishContainer.includes('relate-fieldtype-inline')) { - this.values['from_inline_publish_form'] = true - - this.initialBlueprint.tabs.forEach((tab) => { - tab.sections.forEach((section) => { - section.fields.forEach((field) => { - if ( - field.type === 'belongs_to' && - field.resource === window.Runway.currentResource - ) { - let alreadyExists = this.values[field.handle].includes( - window.Runway.currentRecord.id - ) - - if (!alreadyExists) { - this.values[field.handle].push( - window.Runway.currentRecord.id - ) - this.meta[field.handle].data = [ - window.Runway.currentRecord, - ] - } - } - }) - }) - }) + this.prefillBelongsToField() } }, @@ -295,6 +270,30 @@ export default { } ) }, + + /** + * When creating a new model via the HasMany fieldtype, pre-fill the belongs_to field to the current record. + */ + prefillBelongsToField() { + this.values['from_inline_publish_form'] = true + + this.initialBlueprint.tabs.forEach((tab) => { + tab.sections.forEach((section) => { + section.fields + .filter((field) => { + return field.type === 'belongs_to' || field.resource === window.Runway.currentResource; + }) + .forEach((field) => { + let alreadyExists = this.values[field.handle].includes(window.Runway.currentRecord.id) + + if (!alreadyExists) { + this.values[field.handle].push(window.Runway.currentRecord.id) + this.meta[field.handle].data = [window.Runway.currentRecord] + } + }) + }) + }) + }, }, watch: { diff --git a/src/Actions/DuplicateModel.php b/src/Actions/DuplicateModel.php new file mode 100644 index 00000000..4e3f57b6 --- /dev/null +++ b/src/Actions/DuplicateModel.php @@ -0,0 +1,63 @@ +readOnly() !== true; + } + + public function visibleToBulk($items) + { + return $items + ->map(fn ($item) => $this->visibleTo($item)) + ->filter(fn ($isVisible) => $isVisible === true) + ->count() === $items->count(); + } + + public function authorize($user, $item) + { + $resource = Runway::findResourceByModel($item); + + return $user->can('create', $resource); + } + + public function buttonText() + { + /* @translation */ + return 'Duplicate|Duplicate :count items?'; + } + + public function run($items, $values) + { + $resource = Runway::findResourceByModel($items->first()); + + $items->each(function (Model $item) use ($resource) { + $duplicateModel = $item->replicate(); + + if ($resource->titleField()) { + $duplicateModel->{$resource->titleField()} = $duplicateModel->{$resource->titleField()}.' (Duplicate)'; + } + + $duplicateModel->save(); + }); + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 39331d97..e3337149 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -26,6 +26,7 @@ class ServiceProvider extends AddonServiceProvider protected $actions = [ Actions\DeleteModel::class, + Actions\DuplicateModel::class, ]; protected $commands = [ diff --git a/tests/Actions/DuplicateModelTest.php b/tests/Actions/DuplicateModelTest.php new file mode 100644 index 00000000..7b25136c --- /dev/null +++ b/tests/Actions/DuplicateModelTest.php @@ -0,0 +1,139 @@ +assertEquals('Duplicate', DuplicateModel::title()); + } + + /** @test */ + public function is_visible_to_eloquent_model() + { + $visibleTo = (new DuplicateModel())->visibleTo(Post::factory()->create()); + + $this->assertTrue($visibleTo); + } + + /** @test */ + public function is_not_visible_to_eloquent_model_when_resource_is_read_only() + { + Config::set('runway.resources.DoubleThreeDigital\Runway\Tests\Fixtures\Models\Post.read_only', true); + Runway::discoverResources(); + + $visibleTo = (new DuplicateModel())->visibleTo(Post::factory()->create()); + + $this->assertFalse($visibleTo); + } + + /** @test */ + public function is_not_visible_to_eloquent_model_without_a_runway_resource() + { + $model = new class extends Model + { + protected $table = 'posts'; + }; + + $visibleTo = (new DuplicateModel())->visibleTo(new $model); + + $this->assertFalse($visibleTo); + } + + /** @test */ + public function is_not_visible_to_entry() + { + Collection::make('posts')->save(); + + $visibleTo = (new DuplicateModel())->visibleTo( + tap(Entry::make()->collection('posts')->slug('hello-world'))->save() + ); + + $this->assertFalse($visibleTo); + } + + /** @test */ + public function is_visible_to_eloquent_models_in_bulk() + { + $posts = Post::factory()->count(3)->create(); + + $visibleToBulk = (new DuplicateModel())->visibleToBulk($posts); + + $this->assertTrue($visibleToBulk); + } + + /** @test */ + public function is_not_visible_to_entries_in_bulk() + { + Collection::make('posts')->save(); + + $entries = collect([ + tap(Entry::make()->collection('posts')->slug('hello-world'))->save(), + tap(Entry::make()->collection('posts')->slug('foo-bar'))->save(), + tap(Entry::make()->collection('posts')->slug('bye-bye'))->save(), + ]); + + $visibleToBulk = (new DuplicateModel())->visibleToBulk($entries); + + $this->assertFalse($visibleToBulk); + } + + /** @test */ + public function super_user_is_authorized() + { + $user = User::make()->makeSuper()->save(); + + $authorize = (new DuplicateModel())->authorize($user, Post::factory()->create()); + + $this->assertTrue($authorize); + } + + /** @test */ + public function user_with_permission_is_authorized() + { + Role::make('editor')->addPermission('create post')->save(); + + $user = User::make()->assignRole('editor')->save(); + + $authorize = (new DuplicateModel())->authorize($user, Post::factory()->create()); + + $this->assertTrue($authorize); + + Role::find('editor')->delete(); + } + + /** @test */ + public function user_without_permission_is_not_authorized() + { + $user = User::make()->save(); + + $authorize = (new DuplicateModel())->authorize($user, Post::factory()->create()); + + $this->assertFalse($authorize); + } + + /** @test */ + public function it_duplicates_models() + { + $post = Post::factory()->create(['title' => 'Hello World']); + + $this->assertCount(1, Post::where('title', 'like', 'Hello World%')->get()); + + (new DuplicateModel)->run(collect([$post]), []); + + $this->assertCount(2, Post::where('title', 'like', 'Hello World%')->get()); + } +}