From 3bfc1112b65ad2f21b22fd9ee7b2dcde53726409 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Nov 2026 20:38:41 -0700 Subject: [PATCH 1/3] Respect field defaults when resolving queryable values --- src/Assets/Asset.php | 2 +- src/Auth/User.php | 2 +- src/Entries/Entry.php | 2 +- src/Fields/Field.php | 9 +++++++++ src/Forms/Form.php | 2 +- src/Forms/Submission.php | 2 +- src/Globals/Variables.php | 2 +- src/Taxonomies/LocalizedTerm.php | 2 +- tests/Fields/FieldTest.php | 28 +++++++++++++++++++++++++++ tests/Tags/Collection/EntriesTest.php | 14 ++++++++++++++ 10 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index a432150acbb..23120d98d2a 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -1151,7 +1151,7 @@ public function getQueryableValue(string $field) return $value; } - return $field->fieldtype()->toQueryableValue($value); + return $field->toQueryableValue($value); } private function queryableMethods(): array diff --git a/src/Auth/User.php b/src/Auth/User.php index 2468d97d724..3c0cae5bcbd 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -488,7 +488,7 @@ public function getQueryableValue(string $field) return $value; } - return $field->fieldtype()->toQueryableValue($value); + return $field->toQueryableValue($value); } private function queryableMethods(): array diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 86ed9d0c1f4..7255264e63c 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -1235,7 +1235,7 @@ public function getQueryableValue(string $field) return $value; } - return $field->fieldtype()->toQueryableValue($value); + return $field->toQueryableValue($value); } private function queryableMethods(): array diff --git a/src/Fields/Field.php b/src/Fields/Field.php index 45735cbda5b..0bc491be29c 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -315,6 +315,15 @@ public function defaultValue() return $this->config['default'] ?? $this->fieldtype()->defaultValue(); } + public function toQueryableValue($value) + { + if ($value === null && ! $this->hasComputedDefault()) { + $value = $this->defaultValue(); + } + + return $this->fieldtype()->toQueryableValue($value); + } + public function hasComputedDefault(): bool { if (! isset($this->config['default'])) { diff --git a/src/Forms/Form.php b/src/Forms/Form.php index c8833c92c3d..4e5254b8aaf 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -467,7 +467,7 @@ public function getQueryableValue(string $field) return $value; } - return $field->fieldtype()->toQueryableValue($value); + return $field->toQueryableValue($value); } private function queryableMethods(): array diff --git a/src/Forms/Submission.php b/src/Forms/Submission.php index 1ce97715656..b160b7e6cb7 100644 --- a/src/Forms/Submission.php +++ b/src/Forms/Submission.php @@ -278,7 +278,7 @@ public function getQueryableValue(string $field) return $value; } - return $field->fieldtype()->toQueryableValue($value); + return $field->toQueryableValue($value); } private function queryableMethods(): array diff --git a/src/Globals/Variables.php b/src/Globals/Variables.php index ae0512188a9..c6d0b4e0ee0 100644 --- a/src/Globals/Variables.php +++ b/src/Globals/Variables.php @@ -304,7 +304,7 @@ public function getQueryableValue(string $field) return $value; } - return $field->fieldtype()->toQueryableValue($value); + return $field->toQueryableValue($value); } private function queryableMethods(): array diff --git a/src/Taxonomies/LocalizedTerm.php b/src/Taxonomies/LocalizedTerm.php index 08d570fc54a..7c3e2b9e74c 100644 --- a/src/Taxonomies/LocalizedTerm.php +++ b/src/Taxonomies/LocalizedTerm.php @@ -500,7 +500,7 @@ public function getQueryableValue(string $field) return $value; } - return $field->fieldtype()->toQueryableValue($value); + return $field->toQueryableValue($value); } private function queryableMethods(): array diff --git a/tests/Fields/FieldTest.php b/tests/Fields/FieldTest.php index 7d1a5af0e13..58310c6ae47 100644 --- a/tests/Fields/FieldTest.php +++ b/tests/Fields/FieldTest.php @@ -748,4 +748,32 @@ public function it_gets_and_sets_the_form() $this->assertEquals($field, $return); $this->assertEquals($form, $field->form()); } + + #[Test] + public function it_uses_the_default_when_converting_a_null_value_to_a_queryable_value() + { + $field = new Field('test', ['type' => 'toggle', 'default' => true]); + + $this->assertTrue($field->toQueryableValue(null)); + $this->assertTrue($field->toQueryableValue(true)); + $this->assertFalse($field->toQueryableValue(false)); + } + + #[Test] + public function it_converts_a_null_value_to_a_queryable_value_when_there_is_no_default() + { + $this->assertFalse((new Field('test', ['type' => 'toggle']))->toQueryableValue(null)); + $this->assertNull((new Field('test', ['type' => 'text']))->toQueryableValue(null)); + $this->assertEquals('foo', (new Field('test', ['type' => 'text', 'default' => 'bar']))->toQueryableValue('foo')); + } + + #[Test] + public function it_does_not_use_a_computed_default_when_converting_to_a_queryable_value() + { + Fields::computedDefault('computed-value', fn () => 'computed defined default'); + + $field = new Field('test', ['type' => 'text', 'default' => 'computed:computed-value']); + + $this->assertNull($field->toQueryableValue(null)); + } } diff --git a/tests/Tags/Collection/EntriesTest.php b/tests/Tags/Collection/EntriesTest.php index d99325bc783..a34d0e1f6e5 100644 --- a/tests/Tags/Collection/EntriesTest.php +++ b/tests/Tags/Collection/EntriesTest.php @@ -253,6 +253,20 @@ public function it_limits_entries_with_offset_using_value_objects() ); } + #[Test] + public function it_filters_by_a_fields_default_value() + { + $blueprint = Blueprint::makeFromFields(['my_toggle' => ['type' => 'toggle', 'default' => true]])->setHandle('test'); + Blueprint::shouldReceive('in')->with('collections/test')->andReturn(collect([$blueprint])); + + $this->makeEntry('unsaved')->save(); + $this->makeEntry('explicit-true')->set('my_toggle', true)->save(); + $this->makeEntry('explicit-false')->set('my_toggle', false)->save(); + + $this->assertEqualsCanonicalizing(['unsaved', 'explicit-true'], $this->getEntryIds(['my_toggle:is' => 'true'])); + $this->assertEquals(['explicit-false'], $this->getEntryIds(['my_toggle:is' => 'false'])); + } + #[Test] public function it_filters_by_future_and_past() { From 9a5f00c749f46a50d0fbde081c3d186583fd7f6e Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Nov 2026 20:45:38 -0700 Subject: [PATCH 2/3] Skip date default of now when resolving queryable values --- src/Fieldtypes/Date.php | 4 ++++ tests/Fieldtypes/DateTest.php | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Fieldtypes/Date.php b/src/Fieldtypes/Date.php index 3d27b639196..5e01fcd5fd2 100644 --- a/src/Fieldtypes/Date.php +++ b/src/Fieldtypes/Date.php @@ -367,6 +367,10 @@ public function toGqlType() public function toQueryableValue($value) { + if ($value === 'now') { + return null; + } + return $this->augment($value); } diff --git a/tests/Fieldtypes/DateTest.php b/tests/Fieldtypes/DateTest.php index b0050887beb..ae32bb91ec2 100644 --- a/tests/Fieldtypes/DateTest.php +++ b/tests/Fieldtypes/DateTest.php @@ -836,6 +836,15 @@ public function it_includes_timezone_in_config_field_items() $this->assertEquals('timezones', $dateTimeSection['fields']['timezone']['dictionary']); } + #[Test] + public function it_does_not_use_a_default_of_now_as_the_queryable_value() + { + $field = new Field('test', ['type' => 'date', 'default' => 'now']); + + $this->assertNull($field->toQueryableValue(null)); + $this->assertEquals('2012-01-04', $field->toQueryableValue('2012-01-04')->format('Y-m-d')); + } + public function fieldtype($config = []) { $field = new Field('test', array_replace([ From 328a76a4ac726cceda6eb0ab879d70bff7e97a82 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 13 Nov 2026 20:50:52 -0700 Subject: [PATCH 3/3] Test queryable defaults for assets, users, forms, globals and terms --- tests/Assets/AssetTest.php | 15 +++++++++++++++ tests/Auth/UserContractTests.php | 15 +++++++++++++++ tests/Data/Globals/VariablesTest.php | 15 +++++++++++++++ tests/Data/Taxonomies/LocalizedTermTest.php | 16 ++++++++++++++++ tests/Forms/FormTest.php | 16 ++++++++++++++++ tests/Forms/SubmissionTest.php | 15 +++++++++++++++ 6 files changed, 92 insertions(+) diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index c5fde7bc263..f22d556593e 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -3075,4 +3075,19 @@ public function it_clones_internal_collections() $this->assertEquals('A', $asset->getSupplement('bar')); $this->assertEquals('B', $clone->getSupplement('bar')); } + + #[Test] + public function it_uses_a_fields_default_as_the_queryable_value() + { + $blueprint = Facades\Blueprint::makeFromFields(['foo' => ['type' => 'toggle', 'default' => true]]); + BlueprintRepository::shouldReceive('find')->with('assets/test_container')->andReturn($blueprint); + + $asset = (new Asset)->path('test.txt')->container($this->container); + + $this->assertTrue($asset->getQueryableValue('foo')); + + $asset->set('foo', false); + + $this->assertFalse($asset->getQueryableValue('foo')); + } } diff --git a/tests/Auth/UserContractTests.php b/tests/Auth/UserContractTests.php index f8834dd3889..197dbae87ba 100644 --- a/tests/Auth/UserContractTests.php +++ b/tests/Auth/UserContractTests.php @@ -821,4 +821,19 @@ public function __construct($handle) return $class; } + + #[Test] + public function it_uses_a_fields_default_as_the_queryable_value() + { + $blueprint = Blueprint::makeFromFields(['my_toggle' => ['type' => 'toggle', 'default' => true]]); + BlueprintRepository::shouldReceive('find')->with('user')->andReturn($blueprint); + + $user = $this->user(); + + $this->assertTrue($user->getQueryableValue('my_toggle')); + + $user->set('my_toggle', false); + + $this->assertFalse($user->getQueryableValue('my_toggle')); + } } diff --git a/tests/Data/Globals/VariablesTest.php b/tests/Data/Globals/VariablesTest.php index 2078e6bbf8b..1607924a727 100644 --- a/tests/Data/Globals/VariablesTest.php +++ b/tests/Data/Globals/VariablesTest.php @@ -421,4 +421,19 @@ public function it_can_be_found_via_the_data_repository_using_its_reference() $this->assertEquals($variables->id(), $found->id()); $this->assertEquals('bar', $found->get('foo')); } + + #[Test] + public function it_uses_a_fields_default_as_the_queryable_value() + { + $blueprint = Blueprint::makeFromFields(['foo' => ['type' => 'toggle', 'default' => true]]); + BlueprintRepository::shouldReceive('find')->with('globals.settings')->andReturn($blueprint); + + $variables = GlobalSet::make('settings')->in('en'); + + $this->assertTrue($variables->getQueryableValue('foo')); + + $variables->set('foo', false); + + $this->assertFalse($variables->getQueryableValue('foo')); + } } diff --git a/tests/Data/Taxonomies/LocalizedTermTest.php b/tests/Data/Taxonomies/LocalizedTermTest.php index 30e2c3c30bb..e343ece5633 100644 --- a/tests/Data/Taxonomies/LocalizedTermTest.php +++ b/tests/Data/Taxonomies/LocalizedTermTest.php @@ -305,4 +305,20 @@ public function it_gets_the_site_handle_as_the_queryable_site_value() $this->assertSame('en', (new LocalizedTerm(new Term, 'en'))->getQueryableValue('site')); $this->assertSame('removed', (new LocalizedTerm(new Term, 'removed'))->getQueryableValue('site')); } + + #[Test] + public function it_uses_a_fields_default_as_the_queryable_value() + { + $blueprint = Blueprint::makeFromFields(['foo' => ['type' => 'toggle', 'default' => true]]); + BlueprintRepository::shouldReceive('in')->with('taxonomies/tags')->andReturn(collect(['tags' => $blueprint])); + Taxonomy::make('tags')->save(); + + $localized = (new Term)->taxonomy('tags')->in('en'); + + $this->assertTrue($localized->getQueryableValue('foo')); + + $localized->set('foo', false); + + $this->assertFalse($localized->getQueryableValue('foo')); + } } diff --git a/tests/Forms/FormTest.php b/tests/Forms/FormTest.php index fe31e2da64b..6bddc535d1d 100644 --- a/tests/Forms/FormTest.php +++ b/tests/Forms/FormTest.php @@ -11,6 +11,7 @@ use Statamic\Events\FormDeleting; use Statamic\Events\FormSaved; use Statamic\Events\FormSaving; +use Statamic\Facades; use Statamic\Facades\File; use Statamic\Facades\Form; use Statamic\Fields\Blueprint; @@ -291,4 +292,19 @@ public function it_clones_internal_collections() $this->assertEquals('A', $form->getSupplement('bar')); $this->assertEquals('B', $clone->getSupplement('bar')); } + + #[Test] + public function it_uses_a_fields_default_as_the_queryable_value() + { + $blueprint = Facades\Blueprint::makeFromFields(['foo' => ['type' => 'toggle', 'default' => true]]); + Facades\Blueprint::shouldReceive('find')->with('forms.test')->andReturn($blueprint); + + $form = Form::make('test'); + + $this->assertTrue($form->getQueryableValue('foo')); + + $form->set('foo', false); + + $this->assertFalse($form->getQueryableValue('foo')); + } } diff --git a/tests/Forms/SubmissionTest.php b/tests/Forms/SubmissionTest.php index a2700882bc4..f3cb405a158 100644 --- a/tests/Forms/SubmissionTest.php +++ b/tests/Forms/SubmissionTest.php @@ -252,4 +252,19 @@ public function it_clones_internal_collections() $this->assertEquals('A', $submission->getSupplement('bar')); $this->assertEquals('B', $clone->getSupplement('bar')); } + + #[Test] + public function it_uses_a_fields_default_as_the_queryable_value() + { + $blueprint = Blueprint::makeFromFields(['foo' => ['type' => 'toggle', 'default' => true]]); + Blueprint::shouldReceive('find')->with('forms.test')->andReturn($blueprint); + + $submission = Form::make('test')->makeSubmission(); + + $this->assertTrue($submission->getQueryableValue('foo')); + + $submission->set('foo', false); + + $this->assertFalse($submission->getQueryableValue('foo')); + } }