diff --git a/src/Facades/Form.php b/src/Facades/Form.php index 9842ae975e..6bc7a1cf44 100644 --- a/src/Facades/Form.php +++ b/src/Facades/Form.php @@ -10,6 +10,8 @@ * @method static \Statamic\Contracts\Forms\Form findOrFail($handle) * @method static \Illuminate\Support\Collection all() * @method static \Statamic\Contracts\Forms\Form make($handle = null) + * @method static array extraConfigFor($handle) + * @method static void appendConfigFields($handle, $display, $fields) * * @see \Statamic\Contracts\Forms\FormRepository */ diff --git a/src/Forms/Form.php b/src/Forms/Form.php index ab5ab455a2..e5af45130d 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -8,6 +8,7 @@ use Statamic\Contracts\Forms\Form as FormContract; use Statamic\Contracts\Forms\Submission; use Statamic\Contracts\Forms\SubmissionQueryBuilder; +use Statamic\Data\ContainsData; use Statamic\Data\HasAugmentedInstance; use Statamic\Events\FormBlueprintFound; use Statamic\Events\FormCreated; @@ -29,7 +30,7 @@ class Form implements Arrayable, Augmentable, FormContract { - use FluentlyGetsAndSets, HasAugmentedInstance; + use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance; protected $handle; protected $title; @@ -41,6 +42,11 @@ class Form implements Arrayable, Augmentable, FormContract protected $afterSaveCallbacks = []; protected $withEvents = true; + public function __construct() + { + $this->data = collect(); + } + /** * Get or set the handle. * @@ -186,7 +192,7 @@ public function save() } } - $data = collect([ + $data = $this->data->merge(collect([ 'title' => $this->title, 'honeypot' => $this->honeypot, 'email' => collect(isset($this->email['to']) ? [$this->email] : $this->email)->map(function ($email) { @@ -196,7 +202,7 @@ public function save() return Arr::removeNullValues($email); })->all(), 'metrics' => $this->metrics, - ])->filter()->all(); + ]))->filter()->all(); if ($this->store === false) { $data['store'] = false; @@ -254,14 +260,20 @@ public function delete() */ public function hydrate() { - collect(YAML::parse(File::get($this->path()))) - ->filter(function ($value, $property) { - return in_array($property, [ - 'title', - 'honeypot', - 'store', - 'email', - ]); + $contents = YAML::parse(File::get($this->path())); + + $methods = [ + 'title', + 'honeypot', + 'store', + 'email', + ]; + + $this->merge(collect($contents)->except($methods)); + + collect($contents) + ->filter(function ($value, $property) use ($methods) { + return in_array($property, $methods); }) ->each(function ($value, $property) { $this->{$property}($value); diff --git a/src/Forms/FormRepository.php b/src/Forms/FormRepository.php index b18557fb3a..dbef859b5a 100644 --- a/src/Forms/FormRepository.php +++ b/src/Forms/FormRepository.php @@ -10,9 +10,12 @@ use Statamic\Facades\File; use Statamic\Facades\Folder; use Statamic\Forms\Exporters\ExporterRepository; +use Statamic\Support\Arr; +use Statamic\Support\Str; class FormRepository implements Contract { + private $configs = []; private $redirects = []; /** @@ -82,6 +85,37 @@ public function make($handle = null) return $form; } + public function appendConfigFields($handles, string $display, array $fields) + { + $this->configs[] = [ + 'display' => $display, + 'handles' => Arr::wrap($handles), + 'fields' => $fields, + ]; + } + + public function extraConfigFor($handle) + { + $reserved = ['title', 'honeypot', 'store', 'email']; + + return collect($this->configs) + ->filter(function ($config) use ($handle) { + return in_array('*', $config['handles']) || in_array($handle, $config['handles']); + }) + ->flatMap(function ($config) use ($reserved) { + + return [ + Str::snake($config['display']) => [ + 'display' => $config['display'], + 'fields' => collect($config['fields']) + ->filter(fn ($field, $index) => ! in_array($field['handle'] ?? $index, $reserved)) + ->all(), + ], + ]; + }) + ->all(); + } + public function redirect(string $form, Closure $callback) { $this->redirects[$form] = $callback; diff --git a/src/Http/Controllers/CP/Forms/FormsController.php b/src/Http/Controllers/CP/Forms/FormsController.php index a2c53b7399..73b2c65cfb 100644 --- a/src/Http/Controllers/CP/Forms/FormsController.php +++ b/src/Http/Controllers/CP/Forms/FormsController.php @@ -151,13 +151,13 @@ public function edit($form) { $this->authorize('edit', $form); - $values = [ + $values = array_merge($form->data()->all(), [ 'handle' => $form->handle(), 'title' => __($form->title()), 'honeypot' => $form->honeypot(), 'store' => $form->store(), 'email' => $form->email(), - ]; + ]); $fields = ($blueprint = $this->editFormBlueprint($form)) ->fields() @@ -182,11 +182,14 @@ public function update($form, Request $request) $values = $fields->process()->values()->all(); + $data = collect($values)->except(['title', 'honeypot', 'store', 'email']); + $form ->title($values['title']) ->honeypot($values['honeypot']) ->store($values['store']) - ->email($values['email']); + ->email($values['email']) + ->merge($data); $form->save(); @@ -202,7 +205,7 @@ public function destroy($form) protected function editFormBlueprint($form) { - return Blueprint::makeFromTabs([ + $fields = [ 'name' => [ 'display' => __('Name'), 'fields' => [ @@ -349,6 +352,24 @@ protected function editFormBlueprint($form) ], // metrics - ]); + // ... + + ]; + + foreach (Form::extraConfigFor($form->handle()) as $handle => $config) { + $merged = false; + foreach ($fields as $sectionHandle => $section) { + if ($section['display'] == $config['display']) { + $fields[$sectionHandle]['fields'] += $config['fields']; + $merged = true; + } + } + + if (! $merged) { + $fields[$handle] = $config; + } + } + + return Blueprint::makeFromTabs($fields); } } diff --git a/tests/Feature/Forms/EditFormTest.php b/tests/Feature/Forms/EditFormTest.php index 0721917a9c..db856a869e 100644 --- a/tests/Feature/Forms/EditFormTest.php +++ b/tests/Feature/Forms/EditFormTest.php @@ -49,4 +49,38 @@ public function it_denies_access_if_you_dont_have_permission() ->assertRedirect('/original') ->assertSessionHas('error'); } + + #[Test] + public function fields_can_be_added() + { + $this->setTestRoles(['test' => ['access cp', 'configure forms']]); + $user = User::make()->assignRole('test')->save(); + $form = tap(Form::make('test'))->save(); + + Form::appendConfigFields('*', 'Fields', [ + 'a' => ['type' => 'text', 'display' => 'First injected into fields section'], + 'b' => ['type' => 'text', 'display' => 'Second injected into fields section'], + ]); + Form::appendConfigFields('*', 'Additional Section', [ + 'c' => ['type' => 'text', 'display' => 'First injected into additional section'], + 'd' => ['type' => 'text', 'display' => 'Second injected into additional section'], + ]); + + $this + ->actingAs($user) + ->get(cp_route('forms.edit', $form->handle())) + ->assertSuccessful() + ->assertViewHas('form', $form) + ->assertSeeInOrder([ + 'Title', + 'Blueprint', + 'Honeypot', + 'First injected into fields section', + 'Second injected into fields section', + 'Store Submissions', + 'Additional Section', + 'First injected into additional section', + 'Second injected into additional section', + ]); + } } diff --git a/tests/Feature/Forms/UpdateFormTest.php b/tests/Feature/Forms/UpdateFormTest.php index a0d1e50830..65c1717f0a 100644 --- a/tests/Feature/Forms/UpdateFormTest.php +++ b/tests/Feature/Forms/UpdateFormTest.php @@ -108,6 +108,39 @@ public function it_updates_emails() ], $updated->email()); } + /** @test */ + public function it_updates_data() + { + $form = tap(Form::make('test'))->save(); + $this->assertNull($form->email()); + + Form::appendConfigFields('*', 'Test Config', [ + 'another_config' => [ + 'handle' => 'another_config', + 'field' => [ + 'type' => 'text', + ], + ], + 'some_config' => [ + 'handle' => 'some_config', + 'field' => [ + 'type' => 'text', + ], + ], + ]); + + $this + ->actingAs($this->userWithPermission()) + ->update($form, ['some_config' => 'foo', 'another_config' => 'bar']) + ->assertOk(); + + $updated = Form::all()->first(); + $this->assertEquals([ + 'another_config' => 'bar', + 'some_config' => 'foo', + ], $updated->data()->all()); + } + private function userWithoutPermission() { $this->setTestRoles(['test' => ['access cp']]); diff --git a/tests/Forms/FormRepositoryTest.php b/tests/Forms/FormRepositoryTest.php index e93b1fee8a..3f3011ad0e 100644 --- a/tests/Forms/FormRepositoryTest.php +++ b/tests/Forms/FormRepositoryTest.php @@ -42,4 +42,42 @@ public function test_find_or_fail_throws_exception_when_form_does_not_exist() $this->repo->findOrFail('does-not-exist'); } + + /** @test */ + public function it_registers_config() + { + $this->repo->appendConfigFields('test_form', 'Test Config', [ + 'alfa' => ['type' => 'text'], + 'bravo' => ['type' => 'text'], + ]); + + $this->repo->appendConfigFields('*', 'This Goes Everywhere', [ + ['charlie' => ['type' => 'text']], + ]); + + $this->assertEquals([ + 'test_config' => [ + 'display' => 'Test Config', + 'fields' => [ + 'alfa' => ['type' => 'text'], + 'bravo' => ['type' => 'text'], + ], + ], + 'this_goes_everywhere' => [ + 'display' => 'This Goes Everywhere', + 'fields' => [ + ['charlie' => ['type' => 'text']], + ], + ], + ], $this->repo->extraConfigFor('test_form')); + + $this->assertEquals([ + 'this_goes_everywhere' => [ + 'display' => 'This Goes Everywhere', + 'fields' => [ + ['charlie' => ['type' => 'text']], + ], + ], + ], $this->repo->extraConfigFor('another_form')); + } } diff --git a/tests/Forms/FormTest.php b/tests/Forms/FormTest.php index 13105f8b8c..46cc05daa9 100644 --- a/tests/Forms/FormTest.php +++ b/tests/Forms/FormTest.php @@ -33,13 +33,21 @@ public function it_saves_a_form() $form = Form::make('contact_us') ->title('Contact Us') - ->honeypot('winnie'); + ->honeypot('winnie') + ->data([ + 'foo' => 'bar', + 'roo' => 'rar', + ]); $form->save(); $this->assertEquals('contact_us', $form->handle()); $this->assertEquals('Contact Us', $form->title()); $this->assertEquals('winnie', $form->honeypot()); + $this->assertEquals([ + 'foo' => 'bar', + 'roo' => 'rar', + ], $form->data()->all()); Event::assertDispatched(FormCreating::class, function ($event) use ($form) { return $event->form === $form;