Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b70221
Initial concept
ryanmitchell Jul 26, 2023
40c5da8
Add some test coverage
ryanmitchell Aug 1, 2023
1df616e
Remove un-needed use statements
ryanmitchell Aug 1, 2023
3ecdeec
:beer:
ryanmitchell Aug 1, 2023
174f0b4
Change method signature to make it more intuitive
ryanmitchell Aug 1, 2023
96171a7
Missed this test update
ryanmitchell Aug 1, 2023
77a36f1
Use the correct reserved words
ryanmitchell Aug 1, 2023
ef2ce6b
Change method name to appendConfigFields for consistency
ryanmitchell Sep 20, 2023
a354d17
Merge branch '4.x' into feature/form-config-config
ryanmitchell Oct 10, 2023
bef04b1
Merge branch '4.x' into feature/form-config-config
ryanmitchell Nov 13, 2023
f9f0c19
Merge branch '4.x' into pr/8491
duncanmcclean Nov 22, 2023
896deeb
Merge branch '4.x' into pr/8491
duncanmcclean Dec 4, 2023
9d88f0a
Add ability to add to existing sections
ryanmitchell Dec 4, 2023
7933ff0
:beer:
ryanmitchell Dec 4, 2023
a357911
Merge branch '4.x' into feature/form-config-config
ryanmitchell Jan 10, 2024
77852db
Merge branch '4.x' into pr/8491
duncanmcclean Feb 13, 2024
36eca54
Merge branch '5.x' into feature/form-config-config
ryanmitchell May 10, 2024
29dcafa
:beer:
ryanmitchell May 10, 2024
69e40b5
Yep
ryanmitchell May 10, 2024
1f03cca
Merge branch '5.x' into feature/form-config-config
jasonvarga Jul 29, 2024
9e64231
Add test to assert fields can be added to existing sections
jasonvarga Jul 29, 2024
af065a6
method name
jasonvarga Jul 29, 2024
1ae9b25
my brain wasnt braining
jasonvarga Jul 29, 2024
5e98987
use the property
jasonvarga Jul 29, 2024
7b25e50
adjust test and use snake case
jasonvarga Jul 30, 2024
eb4dbbd
key by handle, it doesnt really matter, but the rest of it is.
jasonvarga Jul 30, 2024
31c2841
add hints to facade
jasonvarga Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Facades/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
*/
Expand Down
34 changes: 23 additions & 11 deletions src/Forms/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -29,7 +30,7 @@

class Form implements Arrayable, Augmentable, FormContract
{
use FluentlyGetsAndSets, HasAugmentedInstance;
use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance;

protected $handle;
protected $title;
Expand All@@ -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.
*
Expand DownExpand Up@@ -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) {
Expand All@@ -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;
Expand DownExpand Up@@ -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);
Expand Down
34 changes: 34 additions & 0 deletions src/Forms/FormRepository.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 = [];

/**
Expand DownExpand Up@@ -82,6 +85,37 @@ public function make($handle = null)
return $form;
}

public function appendConfigFields($handles, string $display, array $fields)
Comment thread
duncanmcclean marked this conversation as resolved.
{
$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;
Expand Down
31 changes: 26 additions & 5 deletions src/Http/Controllers/CP/Forms/FormsController.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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()
Expand All@@ -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();

Expand All@@ -202,7 +205,7 @@ public function destroy($form)

protected function editFormBlueprint($form)
{
return Blueprint::makeFromTabs([
$fields = [
'name' => [
'display' => __('Name'),
'fields' => [
Expand DownExpand Up@@ -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);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Forms/EditFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',
]);
}
}
33 changes: 33 additions & 0 deletions tests/Feature/Forms/UpdateFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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']]);
Expand Down
38 changes: 38 additions & 0 deletions tests/Forms/FormRepositoryTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'));
}
}
10 changes: 9 additions & 1 deletion tests/Forms/FormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b70221
Initial concept
ryanmitchell Jul 26, 2023
40c5da8
Add some test coverage
ryanmitchell Aug 1, 2023
1df616e
Remove un-needed use statements
ryanmitchell Aug 1, 2023
3ecdeec
:beer:
ryanmitchell Aug 1, 2023
174f0b4
Change method signature to make it more intuitive
ryanmitchell Aug 1, 2023
96171a7
Missed this test update
ryanmitchell Aug 1, 2023
77a36f1
Use the correct reserved words
ryanmitchell Aug 1, 2023
ef2ce6b
Change method name to appendConfigFields for consistency
ryanmitchell Sep 20, 2023
a354d17
Merge branch '4.x' into feature/form-config-config
ryanmitchell Oct 10, 2023
bef04b1
Merge branch '4.x' into feature/form-config-config
ryanmitchell Nov 13, 2023
f9f0c19
Merge branch '4.x' into pr/8491
duncanmcclean Nov 22, 2023
896deeb
Merge branch '4.x' into pr/8491
duncanmcclean Dec 4, 2023
9d88f0a
Add ability to add to existing sections
ryanmitchell Dec 4, 2023
7933ff0
:beer:
ryanmitchell Dec 4, 2023
a357911
Merge branch '4.x' into feature/form-config-config
ryanmitchell Jan 10, 2024
77852db
Merge branch '4.x' into pr/8491
duncanmcclean Feb 13, 2024
36eca54
Merge branch '5.x' into feature/form-config-config
ryanmitchell May 10, 2024
29dcafa
:beer:
ryanmitchell May 10, 2024
69e40b5
Yep
ryanmitchell May 10, 2024
1f03cca
Merge branch '5.x' into feature/form-config-config
jasonvarga Jul 29, 2024
9e64231
Add test to assert fields can be added to existing sections
jasonvarga Jul 29, 2024
af065a6
method name
jasonvarga Jul 29, 2024
1ae9b25
my brain wasnt braining
jasonvarga Jul 29, 2024
5e98987
use the property
jasonvarga Jul 29, 2024
7b25e50
adjust test and use snake case
jasonvarga Jul 30, 2024
eb4dbbd
key by handle, it doesnt really matter, but the rest of it is.
jasonvarga Jul 30, 2024
31c2841
add hints to facade
jasonvarga Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Facades/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
*/
Expand Down
34 changes: 23 additions & 11 deletions src/Forms/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -29,7 +30,7 @@

class Form implements Arrayable, Augmentable, FormContract
{
use FluentlyGetsAndSets, HasAugmentedInstance;
use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance;

protected $handle;
protected $title;
Expand All@@ -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.
*
Expand DownExpand Up@@ -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) {
Expand All@@ -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;
Expand DownExpand Up@@ -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);
Expand Down
34 changes: 34 additions & 0 deletions src/Forms/FormRepository.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 = [];

/**
Expand DownExpand Up@@ -82,6 +85,37 @@ public function make($handle = null)
return $form;
}

public function appendConfigFields($handles, string $display, array $fields)
Comment thread
duncanmcclean marked this conversation as resolved.
{
$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;
Expand Down
31 changes: 26 additions & 5 deletions src/Http/Controllers/CP/Forms/FormsController.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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()
Expand All@@ -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();

Expand All@@ -202,7 +205,7 @@ public function destroy($form)

protected function editFormBlueprint($form)
{
return Blueprint::makeFromTabs([
$fields = [
'name' => [
'display' => __('Name'),
'fields' => [
Expand DownExpand Up@@ -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);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Forms/EditFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',
]);
}
}
33 changes: 33 additions & 0 deletions tests/Feature/Forms/UpdateFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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']]);
Expand Down
38 changes: 38 additions & 0 deletions tests/Forms/FormRepositoryTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'));
}
}
10 changes: 9 additions & 1 deletion tests/Forms/FormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b70221
Initial concept
ryanmitchell Jul 26, 2023
40c5da8
Add some test coverage
ryanmitchell Aug 1, 2023
1df616e
Remove un-needed use statements
ryanmitchell Aug 1, 2023
3ecdeec
:beer:
ryanmitchell Aug 1, 2023
174f0b4
Change method signature to make it more intuitive
ryanmitchell Aug 1, 2023
96171a7
Missed this test update
ryanmitchell Aug 1, 2023
77a36f1
Use the correct reserved words
ryanmitchell Aug 1, 2023
ef2ce6b
Change method name to appendConfigFields for consistency
ryanmitchell Sep 20, 2023
a354d17
Merge branch '4.x' into feature/form-config-config
ryanmitchell Oct 10, 2023
bef04b1
Merge branch '4.x' into feature/form-config-config
ryanmitchell Nov 13, 2023
f9f0c19
Merge branch '4.x' into pr/8491
duncanmcclean Nov 22, 2023
896deeb
Merge branch '4.x' into pr/8491
duncanmcclean Dec 4, 2023
9d88f0a
Add ability to add to existing sections
ryanmitchell Dec 4, 2023
7933ff0
:beer:
ryanmitchell Dec 4, 2023
a357911
Merge branch '4.x' into feature/form-config-config
ryanmitchell Jan 10, 2024
77852db
Merge branch '4.x' into pr/8491
duncanmcclean Feb 13, 2024
36eca54
Merge branch '5.x' into feature/form-config-config
ryanmitchell May 10, 2024
29dcafa
:beer:
ryanmitchell May 10, 2024
69e40b5
Yep
ryanmitchell May 10, 2024
1f03cca
Merge branch '5.x' into feature/form-config-config
jasonvarga Jul 29, 2024
9e64231
Add test to assert fields can be added to existing sections
jasonvarga Jul 29, 2024
af065a6
method name
jasonvarga Jul 29, 2024
1ae9b25
my brain wasnt braining
jasonvarga Jul 29, 2024
5e98987
use the property
jasonvarga Jul 29, 2024
7b25e50
adjust test and use snake case
jasonvarga Jul 30, 2024
eb4dbbd
key by handle, it doesnt really matter, but the rest of it is.
jasonvarga Jul 30, 2024
31c2841
add hints to facade
jasonvarga Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Facades/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
*/
Expand Down
34 changes: 23 additions & 11 deletions src/Forms/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -29,7 +30,7 @@

class Form implements Arrayable, Augmentable, FormContract
{
use FluentlyGetsAndSets, HasAugmentedInstance;
use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance;

protected $handle;
protected $title;
Expand All@@ -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.
*
Expand DownExpand Up@@ -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) {
Expand All@@ -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;
Expand DownExpand Up@@ -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);
Expand Down
34 changes: 34 additions & 0 deletions src/Forms/FormRepository.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 = [];

/**
Expand DownExpand Up@@ -82,6 +85,37 @@ public function make($handle = null)
return $form;
}

public function appendConfigFields($handles, string $display, array $fields)
Comment thread
duncanmcclean marked this conversation as resolved.
{
$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;
Expand Down
31 changes: 26 additions & 5 deletions src/Http/Controllers/CP/Forms/FormsController.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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()
Expand All@@ -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();

Expand All@@ -202,7 +205,7 @@ public function destroy($form)

protected function editFormBlueprint($form)
{
return Blueprint::makeFromTabs([
$fields = [
'name' => [
'display' => __('Name'),
'fields' => [
Expand DownExpand Up@@ -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);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Forms/EditFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',
]);
}
}
33 changes: 33 additions & 0 deletions tests/Feature/Forms/UpdateFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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']]);
Expand Down
38 changes: 38 additions & 0 deletions tests/Forms/FormRepositoryTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'));
}
}
10 changes: 9 additions & 1 deletion tests/Forms/FormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b70221
Initial concept
ryanmitchell Jul 26, 2023
40c5da8
Add some test coverage
ryanmitchell Aug 1, 2023
1df616e
Remove un-needed use statements
ryanmitchell Aug 1, 2023
3ecdeec
:beer:
ryanmitchell Aug 1, 2023
174f0b4
Change method signature to make it more intuitive
ryanmitchell Aug 1, 2023
96171a7
Missed this test update
ryanmitchell Aug 1, 2023
77a36f1
Use the correct reserved words
ryanmitchell Aug 1, 2023
ef2ce6b
Change method name to appendConfigFields for consistency
ryanmitchell Sep 20, 2023
a354d17
Merge branch '4.x' into feature/form-config-config
ryanmitchell Oct 10, 2023
bef04b1
Merge branch '4.x' into feature/form-config-config
ryanmitchell Nov 13, 2023
f9f0c19
Merge branch '4.x' into pr/8491
duncanmcclean Nov 22, 2023
896deeb
Merge branch '4.x' into pr/8491
duncanmcclean Dec 4, 2023
9d88f0a
Add ability to add to existing sections
ryanmitchell Dec 4, 2023
7933ff0
:beer:
ryanmitchell Dec 4, 2023
a357911
Merge branch '4.x' into feature/form-config-config
ryanmitchell Jan 10, 2024
77852db
Merge branch '4.x' into pr/8491
duncanmcclean Feb 13, 2024
36eca54
Merge branch '5.x' into feature/form-config-config
ryanmitchell May 10, 2024
29dcafa
:beer:
ryanmitchell May 10, 2024
69e40b5
Yep
ryanmitchell May 10, 2024
1f03cca
Merge branch '5.x' into feature/form-config-config
jasonvarga Jul 29, 2024
9e64231
Add test to assert fields can be added to existing sections
jasonvarga Jul 29, 2024
af065a6
method name
jasonvarga Jul 29, 2024
1ae9b25
my brain wasnt braining
jasonvarga Jul 29, 2024
5e98987
use the property
jasonvarga Jul 29, 2024
7b25e50
adjust test and use snake case
jasonvarga Jul 30, 2024
eb4dbbd
key by handle, it doesnt really matter, but the rest of it is.
jasonvarga Jul 30, 2024
31c2841
add hints to facade
jasonvarga Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Facades/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
*/
Expand Down
34 changes: 23 additions & 11 deletions src/Forms/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -29,7 +30,7 @@

class Form implements Arrayable, Augmentable, FormContract
{
use FluentlyGetsAndSets, HasAugmentedInstance;
use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance;

protected $handle;
protected $title;
Expand All@@ -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.
*
Expand DownExpand Up@@ -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) {
Expand All@@ -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;
Expand DownExpand Up@@ -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);
Expand Down
34 changes: 34 additions & 0 deletions src/Forms/FormRepository.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 = [];

/**
Expand DownExpand Up@@ -82,6 +85,37 @@ public function make($handle = null)
return $form;
}

public function appendConfigFields($handles, string $display, array $fields)
Comment thread
duncanmcclean marked this conversation as resolved.
{
$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;
Expand Down
31 changes: 26 additions & 5 deletions src/Http/Controllers/CP/Forms/FormsController.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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()
Expand All@@ -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();

Expand All@@ -202,7 +205,7 @@ public function destroy($form)

protected function editFormBlueprint($form)
{
return Blueprint::makeFromTabs([
$fields = [
'name' => [
'display' => __('Name'),
'fields' => [
Expand DownExpand Up@@ -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);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Forms/EditFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',
]);
}
}
33 changes: 33 additions & 0 deletions tests/Feature/Forms/UpdateFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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']]);
Expand Down
38 changes: 38 additions & 0 deletions tests/Forms/FormRepositoryTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'));
}
}
10 changes: 9 additions & 1 deletion tests/Forms/FormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b70221
Initial concept
ryanmitchell Jul 26, 2023
40c5da8
Add some test coverage
ryanmitchell Aug 1, 2023
1df616e
Remove un-needed use statements
ryanmitchell Aug 1, 2023
3ecdeec
:beer:
ryanmitchell Aug 1, 2023
174f0b4
Change method signature to make it more intuitive
ryanmitchell Aug 1, 2023
96171a7
Missed this test update
ryanmitchell Aug 1, 2023
77a36f1
Use the correct reserved words
ryanmitchell Aug 1, 2023
ef2ce6b
Change method name to appendConfigFields for consistency
ryanmitchell Sep 20, 2023
a354d17
Merge branch '4.x' into feature/form-config-config
ryanmitchell Oct 10, 2023
bef04b1
Merge branch '4.x' into feature/form-config-config
ryanmitchell Nov 13, 2023
f9f0c19
Merge branch '4.x' into pr/8491
duncanmcclean Nov 22, 2023
896deeb
Merge branch '4.x' into pr/8491
duncanmcclean Dec 4, 2023
9d88f0a
Add ability to add to existing sections
ryanmitchell Dec 4, 2023
7933ff0
:beer:
ryanmitchell Dec 4, 2023
a357911
Merge branch '4.x' into feature/form-config-config
ryanmitchell Jan 10, 2024
77852db
Merge branch '4.x' into pr/8491
duncanmcclean Feb 13, 2024
36eca54
Merge branch '5.x' into feature/form-config-config
ryanmitchell May 10, 2024
29dcafa
:beer:
ryanmitchell May 10, 2024
69e40b5
Yep
ryanmitchell May 10, 2024
1f03cca
Merge branch '5.x' into feature/form-config-config
jasonvarga Jul 29, 2024
9e64231
Add test to assert fields can be added to existing sections
jasonvarga Jul 29, 2024
af065a6
method name
jasonvarga Jul 29, 2024
1ae9b25
my brain wasnt braining
jasonvarga Jul 29, 2024
5e98987
use the property
jasonvarga Jul 29, 2024
7b25e50
adjust test and use snake case
jasonvarga Jul 30, 2024
eb4dbbd
key by handle, it doesnt really matter, but the rest of it is.
jasonvarga Jul 30, 2024
31c2841
add hints to facade
jasonvarga Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Facades/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
*/
Expand Down
34 changes: 23 additions & 11 deletions src/Forms/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -29,7 +30,7 @@

class Form implements Arrayable, Augmentable, FormContract
{
use FluentlyGetsAndSets, HasAugmentedInstance;
use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance;

protected $handle;
protected $title;
Expand All@@ -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.
*
Expand DownExpand Up@@ -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) {
Expand All@@ -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;
Expand DownExpand Up@@ -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);
Expand Down
34 changes: 34 additions & 0 deletions src/Forms/FormRepository.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 = [];

/**
Expand DownExpand Up@@ -82,6 +85,37 @@ public function make($handle = null)
return $form;
}

public function appendConfigFields($handles, string $display, array $fields)
Comment thread
duncanmcclean marked this conversation as resolved.
{
$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;
Expand Down
31 changes: 26 additions & 5 deletions src/Http/Controllers/CP/Forms/FormsController.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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()
Expand All@@ -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();

Expand All@@ -202,7 +205,7 @@ public function destroy($form)

protected function editFormBlueprint($form)
{
return Blueprint::makeFromTabs([
$fields = [
'name' => [
'display' => __('Name'),
'fields' => [
Expand DownExpand Up@@ -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);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Forms/EditFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',
]);
}
}
33 changes: 33 additions & 0 deletions tests/Feature/Forms/UpdateFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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']]);
Expand Down
38 changes: 38 additions & 0 deletions tests/Forms/FormRepositoryTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'));
}
}
10 changes: 9 additions & 1 deletion tests/Forms/FormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b70221
Initial concept
ryanmitchell Jul 26, 2023
40c5da8
Add some test coverage
ryanmitchell Aug 1, 2023
1df616e
Remove un-needed use statements
ryanmitchell Aug 1, 2023
3ecdeec
:beer:
ryanmitchell Aug 1, 2023
174f0b4
Change method signature to make it more intuitive
ryanmitchell Aug 1, 2023
96171a7
Missed this test update
ryanmitchell Aug 1, 2023
77a36f1
Use the correct reserved words
ryanmitchell Aug 1, 2023
ef2ce6b
Change method name to appendConfigFields for consistency
ryanmitchell Sep 20, 2023
a354d17
Merge branch '4.x' into feature/form-config-config
ryanmitchell Oct 10, 2023
bef04b1
Merge branch '4.x' into feature/form-config-config
ryanmitchell Nov 13, 2023
f9f0c19
Merge branch '4.x' into pr/8491
duncanmcclean Nov 22, 2023
896deeb
Merge branch '4.x' into pr/8491
duncanmcclean Dec 4, 2023
9d88f0a
Add ability to add to existing sections
ryanmitchell Dec 4, 2023
7933ff0
:beer:
ryanmitchell Dec 4, 2023
a357911
Merge branch '4.x' into feature/form-config-config
ryanmitchell Jan 10, 2024
77852db
Merge branch '4.x' into pr/8491
duncanmcclean Feb 13, 2024
36eca54
Merge branch '5.x' into feature/form-config-config
ryanmitchell May 10, 2024
29dcafa
:beer:
ryanmitchell May 10, 2024
69e40b5
Yep
ryanmitchell May 10, 2024
1f03cca
Merge branch '5.x' into feature/form-config-config
jasonvarga Jul 29, 2024
9e64231
Add test to assert fields can be added to existing sections
jasonvarga Jul 29, 2024
af065a6
method name
jasonvarga Jul 29, 2024
1ae9b25
my brain wasnt braining
jasonvarga Jul 29, 2024
5e98987
use the property
jasonvarga Jul 29, 2024
7b25e50
adjust test and use snake case
jasonvarga Jul 30, 2024
eb4dbbd
key by handle, it doesnt really matter, but the rest of it is.
jasonvarga Jul 30, 2024
31c2841
add hints to facade
jasonvarga Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Facades/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
*/
Expand Down
34 changes: 23 additions & 11 deletions src/Forms/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -29,7 +30,7 @@

class Form implements Arrayable, Augmentable, FormContract
{
use FluentlyGetsAndSets, HasAugmentedInstance;
use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance;

protected $handle;
protected $title;
Expand All@@ -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.
*
Expand DownExpand Up@@ -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) {
Expand All@@ -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;
Expand DownExpand Up@@ -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);
Expand Down
34 changes: 34 additions & 0 deletions src/Forms/FormRepository.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 = [];

/**
Expand DownExpand Up@@ -82,6 +85,37 @@ public function make($handle = null)
return $form;
}

public function appendConfigFields($handles, string $display, array $fields)
Comment thread
duncanmcclean marked this conversation as resolved.
{
$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;
Expand Down
31 changes: 26 additions & 5 deletions src/Http/Controllers/CP/Forms/FormsController.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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()
Expand All@@ -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();

Expand All@@ -202,7 +205,7 @@ public function destroy($form)

protected function editFormBlueprint($form)
{
return Blueprint::makeFromTabs([
$fields = [
'name' => [
'display' => __('Name'),
'fields' => [
Expand DownExpand Up@@ -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);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Forms/EditFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',
]);
}
}
33 changes: 33 additions & 0 deletions tests/Feature/Forms/UpdateFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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']]);
Expand Down
38 changes: 38 additions & 0 deletions tests/Forms/FormRepositoryTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'));
}
}
10 changes: 9 additions & 1 deletion tests/Forms/FormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b70221
Initial concept
ryanmitchell Jul 26, 2023
40c5da8
Add some test coverage
ryanmitchell Aug 1, 2023
1df616e
Remove un-needed use statements
ryanmitchell Aug 1, 2023
3ecdeec
:beer:
ryanmitchell Aug 1, 2023
174f0b4
Change method signature to make it more intuitive
ryanmitchell Aug 1, 2023
96171a7
Missed this test update
ryanmitchell Aug 1, 2023
77a36f1
Use the correct reserved words
ryanmitchell Aug 1, 2023
ef2ce6b
Change method name to appendConfigFields for consistency
ryanmitchell Sep 20, 2023
a354d17
Merge branch '4.x' into feature/form-config-config
ryanmitchell Oct 10, 2023
bef04b1
Merge branch '4.x' into feature/form-config-config
ryanmitchell Nov 13, 2023
f9f0c19
Merge branch '4.x' into pr/8491
duncanmcclean Nov 22, 2023
896deeb
Merge branch '4.x' into pr/8491
duncanmcclean Dec 4, 2023
9d88f0a
Add ability to add to existing sections
ryanmitchell Dec 4, 2023
7933ff0
:beer:
ryanmitchell Dec 4, 2023
a357911
Merge branch '4.x' into feature/form-config-config
ryanmitchell Jan 10, 2024
77852db
Merge branch '4.x' into pr/8491
duncanmcclean Feb 13, 2024
36eca54
Merge branch '5.x' into feature/form-config-config
ryanmitchell May 10, 2024
29dcafa
:beer:
ryanmitchell May 10, 2024
69e40b5
Yep
ryanmitchell May 10, 2024
1f03cca
Merge branch '5.x' into feature/form-config-config
jasonvarga Jul 29, 2024
9e64231
Add test to assert fields can be added to existing sections
jasonvarga Jul 29, 2024
af065a6
method name
jasonvarga Jul 29, 2024
1ae9b25
my brain wasnt braining
jasonvarga Jul 29, 2024
5e98987
use the property
jasonvarga Jul 29, 2024
7b25e50
adjust test and use snake case
jasonvarga Jul 30, 2024
eb4dbbd
key by handle, it doesnt really matter, but the rest of it is.
jasonvarga Jul 30, 2024
31c2841
add hints to facade
jasonvarga Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Facades/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
*/
Expand Down
34 changes: 23 additions & 11 deletions src/Forms/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -29,7 +30,7 @@

class Form implements Arrayable, Augmentable, FormContract
{
use FluentlyGetsAndSets, HasAugmentedInstance;
use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance;

protected $handle;
protected $title;
Expand All@@ -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.
*
Expand DownExpand Up@@ -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) {
Expand All@@ -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;
Expand DownExpand Up@@ -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);
Expand Down
34 changes: 34 additions & 0 deletions src/Forms/FormRepository.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 = [];

/**
Expand DownExpand Up@@ -82,6 +85,37 @@ public function make($handle = null)
return $form;
}

public function appendConfigFields($handles, string $display, array $fields)
Comment thread
duncanmcclean marked this conversation as resolved.
{
$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;
Expand Down
31 changes: 26 additions & 5 deletions src/Http/Controllers/CP/Forms/FormsController.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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()
Expand All@@ -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();

Expand All@@ -202,7 +205,7 @@ public function destroy($form)

protected function editFormBlueprint($form)
{
return Blueprint::makeFromTabs([
$fields = [
'name' => [
'display' => __('Name'),
'fields' => [
Expand DownExpand Up@@ -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);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Forms/EditFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',
]);
}
}
33 changes: 33 additions & 0 deletions tests/Feature/Forms/UpdateFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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']]);
Expand Down
38 changes: 38 additions & 0 deletions tests/Forms/FormRepositoryTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'));
}
}
10 changes: 9 additions & 1 deletion tests/Forms/FormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8b70221
Initial concept
ryanmitchell Jul 26, 2023
40c5da8
Add some test coverage
ryanmitchell Aug 1, 2023
1df616e
Remove un-needed use statements
ryanmitchell Aug 1, 2023
3ecdeec
:beer:
ryanmitchell Aug 1, 2023
174f0b4
Change method signature to make it more intuitive
ryanmitchell Aug 1, 2023
96171a7
Missed this test update
ryanmitchell Aug 1, 2023
77a36f1
Use the correct reserved words
ryanmitchell Aug 1, 2023
ef2ce6b
Change method name to appendConfigFields for consistency
ryanmitchell Sep 20, 2023
a354d17
Merge branch '4.x' into feature/form-config-config
ryanmitchell Oct 10, 2023
bef04b1
Merge branch '4.x' into feature/form-config-config
ryanmitchell Nov 13, 2023
f9f0c19
Merge branch '4.x' into pr/8491
duncanmcclean Nov 22, 2023
896deeb
Merge branch '4.x' into pr/8491
duncanmcclean Dec 4, 2023
9d88f0a
Add ability to add to existing sections
ryanmitchell Dec 4, 2023
7933ff0
:beer:
ryanmitchell Dec 4, 2023
a357911
Merge branch '4.x' into feature/form-config-config
ryanmitchell Jan 10, 2024
77852db
Merge branch '4.x' into pr/8491
duncanmcclean Feb 13, 2024
36eca54
Merge branch '5.x' into feature/form-config-config
ryanmitchell May 10, 2024
29dcafa
:beer:
ryanmitchell May 10, 2024
69e40b5
Yep
ryanmitchell May 10, 2024
1f03cca
Merge branch '5.x' into feature/form-config-config
jasonvarga Jul 29, 2024
9e64231
Add test to assert fields can be added to existing sections
jasonvarga Jul 29, 2024
af065a6
method name
jasonvarga Jul 29, 2024
1ae9b25
my brain wasnt braining
jasonvarga Jul 29, 2024
5e98987
use the property
jasonvarga Jul 29, 2024
7b25e50
adjust test and use snake case
jasonvarga Jul 30, 2024
eb4dbbd
key by handle, it doesnt really matter, but the rest of it is.
jasonvarga Jul 30, 2024
31c2841
add hints to facade
jasonvarga Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Facades/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
*/
Expand Down
34 changes: 23 additions & 11 deletions src/Forms/Form.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -29,7 +30,7 @@

class Form implements Arrayable, Augmentable, FormContract
{
use FluentlyGetsAndSets, HasAugmentedInstance;
use ContainsData, FluentlyGetsAndSets, HasAugmentedInstance;

protected $handle;
protected $title;
Expand All@@ -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.
*
Expand DownExpand Up@@ -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) {
Expand All@@ -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;
Expand DownExpand Up@@ -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);
Expand Down
34 changes: 34 additions & 0 deletions src/Forms/FormRepository.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 = [];

/**
Expand DownExpand Up@@ -82,6 +85,37 @@ public function make($handle = null)
return $form;
}

public function appendConfigFields($handles, string $display, array $fields)
Comment thread
duncanmcclean marked this conversation as resolved.
{
$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;
Expand Down
31 changes: 26 additions & 5 deletions src/Http/Controllers/CP/Forms/FormsController.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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()
Expand All@@ -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();

Expand All@@ -202,7 +205,7 @@ public function destroy($form)

protected function editFormBlueprint($form)
{
return Blueprint::makeFromTabs([
$fields = [
'name' => [
'display' => __('Name'),
'fields' => [
Expand DownExpand Up@@ -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);
}
}
34 changes: 34 additions & 0 deletions tests/Feature/Forms/EditFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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',
]);
}
}
33 changes: 33 additions & 0 deletions tests/Feature/Forms/UpdateFormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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']]);
Expand Down
38 changes: 38 additions & 0 deletions tests/Forms/FormRepositoryTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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'));
}
}
10 changes: 9 additions & 1 deletion tests/Forms/FormTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down