Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 39 additions & 1 deletion src/Forms/AugmentedForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,56 @@
namespace Statamic\Forms;

use Statamic\Data\AbstractAugmented;
use Statamic\Fields\Section;
use Statamic\Fields\Tab;
use Statamic\Statamic;

class AugmentedForm extends AbstractAugmented
{
public function keys()
{
$keys = ['handle', 'title', 'fields', 'status', 'api_url'];
$keys = ['handle', 'title', 'fields', 'sections', 'pages', 'status', 'require_login', 'restriction_message', 'api_url'];

if (! Statamic::isApiRoute()) {
$keys[] = 'honeypot';
}

return $keys;
}

public function sections(): array
{
return $this->data->sections()
->map(fn (Section $section) => $this->sectionToArray($section))
->all();
}

public function pages(): array
{
return $this->data->pages()->map(fn (Tab $page) => [
'id' => $page->handle(),
'display' => $page->display(),
'instructions' => $page->instructions(),
'sections' => $page->sections()->map(fn (Section $section) => $this->sectionToArray($section))->all(),
])->all();
}

private function sectionToArray(Section $section): array
{
return [
'display' => $section->display(),
'instructions' => $section->instructions(),
'fields' => $section->fields()->all()->map->toArray()->all(),
];
}

public function requireLogin(): bool
{
return (bool) $this->data->get('require_login');
}

public function restrictionMessage(): ?string
{
return $this->data->status() === 'open' ? null : $this->data->restrictionMessage();
}
}
19 changes: 13 additions & 6 deletions src/Forms/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Carbon\Carbon;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Collection;
use Statamic\Contracts\Data\Augmentable;
use Statamic\Contracts\Data\Augmented;
use Statamic\Contracts\Forms\Form as FormContract;
Expand All @@ -27,6 +28,7 @@
use Statamic\Facades\User;
use Statamic\Facades\YAML;
use Statamic\Fields\Blueprint;
use Statamic\Fields\Tab;
use Statamic\Forms\Exporters\Exporter;
use Statamic\Forms\Fields\FormFields;
use Statamic\Statamic;
Expand Down Expand Up @@ -302,16 +304,21 @@ public function email($emails = null)
return $this->fluentlyGetOrSet('email')->args(func_get_args());
}

/**
* Get the form fields off the blueprint.
*
* @return \Illuminate\Support\Collection
*/
public function fields()
public function fields(): Collection
{
return $this->blueprint()->fields()->all();
}

public function sections(): Collection
{
return $this->pages()->flatMap(fn (Tab $page) => $page->sections());
}

public function pages(): Collection
{
return $this->blueprint()->tabs()->values();
}

/**
* Get path.
*
Expand Down
2 changes: 2 additions & 0 deletions src/GraphQL/TypeRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Statamic\GraphQL\Types\DateRangeType;
use Statamic\GraphQL\Types\EntryInterface;
use Statamic\GraphQL\Types\FieldType;
use Statamic\GraphQL\Types\FormPageType;
use Statamic\GraphQL\Types\FormType;
use Statamic\GraphQL\Types\GlobalSetInterface;
use Statamic\GraphQL\Types\JsonArgument;
Expand Down Expand Up @@ -66,6 +67,7 @@ public function register()
GraphQL::addType(GlobalSetInterface::class);
GraphQL::addType(FieldType::class);
GraphQL::addType(SectionType::class);
GraphQL::addType(FormPageType::class);
GraphQL::addType(LinkValueType::class);

PageInterface::addTypes();
Expand Down
44 changes: 44 additions & 0 deletions src/GraphQL/Types/FormPageType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Statamic\GraphQL\Types;

use Statamic\Facades\GraphQL;

class FormPageType extends \Rebing\GraphQL\Support\Type
{
const NAME = 'FormPage';

protected $attributes = [
'name' => self::NAME,
];

public function fields(): array
{
return [
'id' => [
'type' => GraphQL::nonNull(GraphQL::string()),
'resolve' => function ($page) {
return $page->handle();
},
],
'display' => [
'type' => GraphQL::string(),
'resolve' => function ($page) {
return $page->display();
},
],
'instructions' => [
'type' => GraphQL::string(),
'resolve' => function ($page) {
return $page->instructions();
},
],
'sections' => [
'type' => GraphQL::listOf(GraphQL::type(SectionType::NAME)),
'resolve' => function ($page) {
return $page->sections()->all();
},
],
];
}
}
14 changes: 13 additions & 1 deletion src/GraphQL/Types/FormType.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public function fields(): array
'status' => [
'type' => GraphQL::nonNull(GraphQL::string()),
],
'require_login' => [
'type' => GraphQL::nonNull(GraphQL::boolean()),
],
'restriction_message' => [
'type' => GraphQL::string(),
],
'fields' => [
'type' => GraphQL::listOf(GraphQL::type(FieldType::NAME)),
'resolve' => function ($form, $args, $context, $info) {
Expand Down Expand Up @@ -59,7 +65,13 @@ public function fields(): array
'sections' => [
'type' => GraphQL::listOf(GraphQL::type(SectionType::NAME)),
'resolve' => function ($form, $args, $context, $info) {
return $form->blueprint()->tabs()->first()->sections()->all();
return $form->sections()->all();
},
],
'pages' => [
'type' => GraphQL::listOf(GraphQL::type(FormPageType::NAME)),
'resolve' => function ($form, $args, $context, $info) {
return $form->pages()->all();
},
],
])->map(function (array $arr) {
Expand Down
150 changes: 150 additions & 0 deletions tests/API/APITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Tests\API;

use Facades\Statamic\Console\Processes\Composer;
use Facades\Statamic\CP\LivePreview;
use Facades\Statamic\Fields\BlueprintRepository;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
Expand Down Expand Up @@ -282,6 +284,154 @@ public function it_only_queries_allowed_asset_containers()
$this->assertEndpointSuccessful('/api/asset-containers/avatars');
}

#[Test]
public function it_queries_form_sections()
{
Facades\Config::set('statamic.api.resources.forms', true);

Facades\Form::make('contact')->title('Contact Us')->formFields([
'sections' => [
[
'display' => 'Your Details',
'instructions' => 'Tell us who you are',
'fields' => [
['handle' => 'name', 'field' => ['type' => 'short_answer', 'display' => 'Your Name']],
],
],
[
'fields' => [
['handle' => 'message', 'field' => ['type' => 'long_answer', 'display' => 'Message', 'width' => 50]],
],
],
],
])->save();

$this
->get('/api/forms/contact')
->assertSuccessful()
->assertJsonPath('data.sections', [
[
'display' => 'Your Details',
'instructions' => 'Tell us who you are',
'fields' => [
'name' => ['type' => 'text', 'display' => 'Your Name', 'handle' => 'name', 'width' => 100],
],
],
[
'display' => null,
'instructions' => null,
'fields' => [
'message' => ['type' => 'textarea', 'display' => 'Message', 'width' => 50, 'handle' => 'message'],
],
],
]);
}

#[Test]
public function it_queries_form_pages()
{
Composer::shouldReceive('isInstalled')->with('statamic/forms-pro')->andReturn(true);

Facades\Config::set('statamic.api.resources.forms', true);

Facades\Form::make('contact')->title('Contact Us')->formFields([
'pages' => [
[
'id' => 'about_you',
'display' => 'About You',
'instructions' => 'Tell us who you are',
'sections' => [
[
'display' => 'Your Details',
'fields' => [
['handle' => 'name', 'field' => ['type' => 'short_answer', 'display' => 'Your Name']],
],
],
],
],
[
'id' => 'your_message',
'sections' => [
[
'fields' => [
['handle' => 'message', 'field' => ['type' => 'long_answer', 'display' => 'Message']],
],
],
],
],
],
])->save();

$this
->get('/api/forms/contact')
->assertSuccessful()
->assertJsonPath('data.pages', [
[
'id' => 'about_you',
'display' => 'About You',
'instructions' => 'Tell us who you are',
'sections' => [
[
'display' => 'Your Details',
'instructions' => null,
'fields' => [
'name' => ['type' => 'text', 'display' => 'Your Name', 'handle' => 'name', 'width' => 100],
],
],
],
],
[
'id' => 'your_message',
'display' => 'Page 2 of 2',
'instructions' => null,
'sections' => [
[
'display' => null,
'instructions' => null,
'fields' => [
'message' => ['type' => 'textarea', 'display' => 'Message', 'handle' => 'message', 'width' => 100],
],
],
],
],
])
->assertJsonPath('data.sections.*.display', ['Your Details', null]);
}

#[Test]
public function it_queries_form_access_restrictions()
{
Composer::shouldReceive('isInstalled')->with('statamic/forms-pro')->andReturn(true);
Carbon::setTestNow('2026-07-06 12:00:00');

Facades\Config::set('statamic.api.resources.forms', true);

Facades\Form::make('contact')->save();
Facades\Form::make('members')->data(['require_login' => true])->save();
Facades\Form::make('applications')->data(['close_date' => '2026-07-01 09:00', 'closed_message' => 'Applications have closed.'])->save();

$this
->get('/api/forms/contact')
->assertSuccessful()
->assertJsonPath('data.status', 'open')
->assertJsonPath('data.require_login', false)
->assertJsonPath('data.restriction_message', null);

$this
->get('/api/forms/members')
->assertSuccessful()
->assertJsonPath('data.status', 'open')
->assertJsonPath('data.require_login', true)
->assertJsonPath('data.restriction_message', null);

$this
->get('/api/forms/applications')
->assertSuccessful()
->assertJsonPath('data.status', 'closed')
->assertJsonPath('data.require_login', false)
->assertJsonPath('data.restriction_message', 'Applications have closed.');
}

#[Test]
public function it_selects_entry_fields()
{
Expand Down
Loading
Loading