From 82fa0f01a6b018dbf9abccd0124f72149c2e2df6 Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Wed, 29 Jan 2025 15:57:19 +1300 Subject: [PATCH 1/3] ENH Scaffold GridFieldFilterHeader filters for ArrayList based GridFields. (#11575) --- src/Forms/GridField/GridFieldFilterHeader.php | 43 ++++++++++- .../GridField/GridFieldFilterHeaderTest.php | 76 +++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/Forms/GridField/GridFieldFilterHeader.php b/src/Forms/GridField/GridFieldFilterHeader.php index a6d2947f5c6..0bdb246fcee 100755 --- a/src/Forms/GridField/GridFieldFilterHeader.php +++ b/src/Forms/GridField/GridFieldFilterHeader.php @@ -11,7 +11,10 @@ use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\Schema\FormSchema; +use SilverStripe\ORM\DataList; use SilverStripe\ORM\Filterable; +use SilverStripe\ORM\Filters\PartialMatchFilter; +use SilverStripe\ORM\Search\BasicSearchContext; use SilverStripe\ORM\Search\SearchContext; use SilverStripe\ORM\SS_List; use SilverStripe\View\ArrayData; @@ -283,7 +286,17 @@ public function getSearchContext(GridField $gridField) . " or implement a getDefaultSearchContext() method on $modelClass" ); } - $this->searchContext = $singleton->getDefaultSearchContext(); + + $list = $gridField->getList(); + $searchContext = $singleton->getDefaultSearchContext(); + + // In case we are working with a list not backed by the database we need to convert the search context into a BasicSearchContext + // This is because the scaffolded filters use the ORM for data searching + if (!$list instanceof DataList) { + $searchContext = $this->getBasicSearchContext($gridField, $searchContext); + } + + $this->searchContext = $searchContext; } return $this->searchContext; @@ -491,4 +504,32 @@ private function getTitle(object $inst): string return ClassInfo::shortName($inst); } + + /** + * Transform search context into BasicSearchContext (preserves all relevant search settings) + */ + private function getBasicSearchContext(GridField $gridField, SearchContext $searchContext): BasicSearchContext + { + // Retrieve filters settings as these can be carried over as is + $defaultSearchFields = $searchContext->getSearchFields(); + $defaultFilters = $searchContext->getFilters(); + $list = $gridField->getList(); + + // Carry over any search form settings + $basicSearchContext = BasicSearchContext::create($gridField->getModelClass()); + $basicSearchContext->setFields($defaultSearchFields); + + // Carry over filter configuration (make changes to filter classes so they work with this list) + foreach ($defaultFilters as $defaultFilter) { + $fieldFilter = PartialMatchFilter::create( + // Use name instead of full name as this plain filter doesn't understand relations + $defaultFilter->getName(), + $defaultFilter->getValue(), + $defaultFilter->getModifiers(), + ); + $basicSearchContext->addFilter($fieldFilter); + } + + return $basicSearchContext; + } } diff --git a/tests/php/Forms/GridField/GridFieldFilterHeaderTest.php b/tests/php/Forms/GridField/GridFieldFilterHeaderTest.php index b205551b5e6..d10d84aab61 100644 --- a/tests/php/Forms/GridField/GridFieldFilterHeaderTest.php +++ b/tests/php/Forms/GridField/GridFieldFilterHeaderTest.php @@ -22,6 +22,10 @@ use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\Filters\PartialMatchFilter; +use SilverStripe\ORM\Filters\SearchFilter; +use SilverStripe\ORM\Search\BasicSearchContext; +use SilverStripe\ORM\Search\SearchContext; use SilverStripe\View\ArrayData; class GridFieldFilterHeaderTest extends SapphireTest @@ -268,4 +272,76 @@ public function testGetDisplayFieldsThrowsException() $component->getSearchContext($gridField); } + + public function testGetBasicSearchContext(): void + { + $arrayList = new ArrayList(); + $arrayList->setDataClass(Team::class); + $arrayListFilter = new GridFieldFilterHeader(); + $arrayListGridField = new GridField('dummy', 'dummy', $arrayList); + $arrayListSearchContext = $arrayListFilter->getSearchContext($arrayListGridField); + + $dataList = Team::get(); + $dataListFilter = new GridFieldFilterHeader(); + $dataListGridField = new GridField('dummy', 'dummy', $dataList); + $dataListSearchContext = $dataListFilter->getSearchContext($dataListGridField); + + $this->assertInstanceOf( + BasicSearchContext::class, + $arrayListSearchContext, + 'We expect a basic search context as our GridField list is provided via ArrayList' + ); + + $this->assertNotInstanceOf( + BasicSearchContext::class, + $dataListSearchContext, + 'We expect a regular search context as our GridField list is provided via DataList' + ); + + $arrayListSearchFields = $arrayListSearchContext + ->getSearchFields() + ->column('Name'); + + $dataListSearchFields = $dataListSearchContext + ->getSearchFields() + ->column('Name'); + + $this->assertSame( + $arrayListSearchFields, + $dataListSearchFields, + 'We expect the search fields to be the same regardless of how data is provided to the GridField' + ); + + $arrayListFilters = $arrayListSearchContext->getFilters(); + $dataListFilters = $dataListSearchContext->getFilters(); + + $getFilterName = static function (SearchFilter $filter): string { + return $filter->getName(); + }; + $arrayListSearchFilterNames = array_map($getFilterName, $arrayListFilters); + $dataListSearchFilterNames = array_map($getFilterName, $dataListFilters); + $arrayListSearchFilterNames = array_values($arrayListSearchFilterNames); + $dataListSearchFilterNames = array_values($dataListSearchFilterNames); + + $this->assertSame( + $arrayListSearchFilterNames, + $dataListSearchFilterNames, + 'We expect the search filters to be the same regardless of how data is provided to the GridField' + ); + + $getFilterType = static function (SearchFilter $filter): string { + return $filter::class; + }; + $arrayListSearchFilterTypes = array_map($getFilterType, $arrayListFilters); + $arrayListSearchFilterTypes = array_unique($arrayListSearchFilterTypes); + + $this->assertCount(1, $arrayListSearchFilterTypes, 'We expect all filters to be of the same type'); + $arrayListSearchFilterType = array_shift($arrayListSearchFilterTypes); + + $this->assertEquals( + PartialMatchFilter::class, + $arrayListSearchFilterType, + 'We expect partial match filters' + ); + } } From 06b7a8e9b74cf3b7605e89bc4fa54f898bfda15d Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Thu, 30 Jan 2025 10:17:49 +1300 Subject: [PATCH 2/3] API Deprecate FormField Value --- src/Forms/FormField.php | 2 ++ src/Forms/TextareaField.php | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/Forms/FormField.php b/src/Forms/FormField.php index 00eef25510c..fa4a7653634 100644 --- a/src/Forms/FormField.php +++ b/src/Forms/FormField.php @@ -448,9 +448,11 @@ public function getInputType() * * @see FormField::setSubmittedValue() * @return mixed + * @deprecated 5.4.0 Will be replaced by getFormattedValue() and getValue() */ public function Value() { + Deprecation::notice('5.4.0', 'Will be replaced by getFormattedValue() and getValue()'); return $this->value; } diff --git a/src/Forms/TextareaField.php b/src/Forms/TextareaField.php index 0dd025a7f74..55d73786305 100644 --- a/src/Forms/TextareaField.php +++ b/src/Forms/TextareaField.php @@ -2,6 +2,8 @@ namespace SilverStripe\Forms; +use SilverStripe\Dev\Deprecation; + /** * TextareaField creates a multi-line text field, * allowing more data to be entered than a standard @@ -206,9 +208,11 @@ public function getSchemaValidation() * Return value with all values encoded in html entities * * @return string Raw HTML + * @deprecated 5.4.0 Use getFormattedValueEntities() instead */ public function ValueEntities() { + Deprecation::noticeWithNoReplacment('5.4.0', 'Will be replaced by getFormattedValueEntities()'); return htmlentities($this->Value() ?? '', ENT_COMPAT, 'UTF-8'); } } From 70ed6566b3ef9e894de834ee1d26ee13032803ff Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Thu, 30 Jan 2025 10:56:39 +1300 Subject: [PATCH 3/3] API Deprecate passing null paramters for Form and ValidationResult methods --- src/Forms/Form.php | 24 +++++++ src/ORM/ValidationResult.php | 66 ++++++++++++++++++- .../Validation/ConstraintValidatorTest.php | 2 +- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/Forms/Form.php b/src/Forms/Form.php index a1a8af6e3b5..31493df977a 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -1183,6 +1183,14 @@ public function FieldMap() */ public function sessionMessage($message, $type = ValidationResult::TYPE_ERROR, $cast = ValidationResult::CAST_TEXT) { + if ($cast === null) { + Deprecation::notice( + '5.4.0', + 'Passing $cast as null is deprecated. Pass a ValidationResult::CAST_* constant instead.', + Deprecation::SCOPE_GLOBAL + ); + $cast = ValidationResult::CAST_TEXT; + } $this->setMessage($message, $type, $cast); $result = $this->getSessionValidationResult() ?: ValidationResult::create(); $result->addMessage($message, $type, null, $cast); @@ -1199,6 +1207,14 @@ public function sessionMessage($message, $type = ValidationResult::TYPE_ERROR, $ */ public function sessionError($message, $type = ValidationResult::TYPE_ERROR, $cast = ValidationResult::CAST_TEXT) { + if ($cast === null) { + Deprecation::notice( + '5.4.0', + 'Passing $cast as null is deprecated. Pass a ValidationResult::CAST_* constant instead.', + Deprecation::SCOPE_GLOBAL + ); + $cast = ValidationResult::CAST_TEXT; + } $this->setMessage($message, $type, $cast); $result = $this->getSessionValidationResult() ?: ValidationResult::create(); $result->addError($message, $type, null, $cast); @@ -1216,6 +1232,14 @@ public function sessionError($message, $type = ValidationResult::TYPE_ERROR, $ca */ public function sessionFieldError($message, $fieldName, $type = ValidationResult::TYPE_ERROR, $cast = ValidationResult::CAST_TEXT) { + if ($cast === null) { + Deprecation::notice( + '5.4.0', + 'Passing $cast as null is deprecated. Pass a ValidationResult::CAST_* constant instead.', + Deprecation::SCOPE_GLOBAL + ); + $cast = ValidationResult::CAST_TEXT; + } $this->setMessage($message, $type, $cast); $result = $this->getSessionValidationResult() ?: ValidationResult::create(); $result->addFieldMessage($fieldName, $message, $type, null, $cast); diff --git a/src/ORM/ValidationResult.php b/src/ORM/ValidationResult.php index c7ccb5f9996..47b96cf1c0a 100644 --- a/src/ORM/ValidationResult.php +++ b/src/ORM/ValidationResult.php @@ -85,7 +85,23 @@ public function __construct() */ public function addError($message, $messageType = ValidationResult::TYPE_ERROR, $code = null, $cast = ValidationResult::CAST_TEXT) { - return $this->addFieldError(null, $message, $messageType, $code, $cast); + if ($code === null) { + Deprecation::notice( + '5.4.0', + 'Passing $code as null is deprecated. Pass a blank string instead.', + Deprecation::SCOPE_GLOBAL + ); + $code = ''; + } + if ($cast === null) { + Deprecation::notice( + '5.4.0', + 'Passing $cast as null is deprecated. Pass a ValidationResult::CAST_* constant instead.', + Deprecation::SCOPE_GLOBAL + ); + $cast = ValidationResult::CAST_TEXT; + } + return $this->addFieldError('', $message, $messageType, $code, $cast); } /** @@ -108,6 +124,22 @@ public function addFieldError( $code = null, $cast = ValidationResult::CAST_TEXT ) { + if ($code === null) { + Deprecation::notice( + '5.4.0', + 'Passing $code as null is deprecated. Pass a blank string instead.', + Deprecation::SCOPE_GLOBAL + ); + $code = ''; + } + if ($cast === null) { + Deprecation::notice( + '5.4.0', + 'Passing $cast as null is deprecated. Pass a ValidationResult::CAST_* constant instead.', + Deprecation::SCOPE_GLOBAL + ); + $cast = ValidationResult::CAST_TEXT; + } $this->isValid = false; return $this->addFieldMessage($fieldName, $message, $messageType, $code, $cast); } @@ -126,6 +158,22 @@ public function addFieldError( */ public function addMessage($message, $messageType = ValidationResult::TYPE_ERROR, $code = null, $cast = ValidationResult::CAST_TEXT) { + if ($code === null) { + Deprecation::notice( + '5.4.0', + 'Passing $code as null is deprecated. Pass a blank string instead.', + Deprecation::SCOPE_GLOBAL + ); + $code = ''; + } + if ($cast === null) { + Deprecation::notice( + '5.4.0', + 'Passing $cast as null is deprecated. Pass a ValidationResult::CAST_* constant instead.', + Deprecation::SCOPE_GLOBAL + ); + $cast = ValidationResult::CAST_TEXT; + } return $this->addFieldMessage(null, $message, $messageType, $code, $cast); } @@ -149,6 +197,22 @@ public function addFieldMessage( $code = null, $cast = ValidationResult::CAST_TEXT ) { + if ($code === null) { + Deprecation::notice( + '5.4.0', + 'Passing $code as null is deprecated. Pass a blank string instead.', + Deprecation::SCOPE_GLOBAL + ); + $code = ''; + } + if ($cast === null) { + Deprecation::notice( + '5.4.0', + 'Passing $cast as null is deprecated. Pass a ValidationResult::CAST_* constant instead.', + Deprecation::SCOPE_GLOBAL + ); + $cast = ValidationResult::CAST_TEXT; + } if ($code && is_numeric($code)) { throw new InvalidArgumentException("Don't use a numeric code '$code'. Use a string."); } diff --git a/tests/php/Core/Validation/ConstraintValidatorTest.php b/tests/php/Core/Validation/ConstraintValidatorTest.php index 074340cfb73..d43cc3d3357 100644 --- a/tests/php/Core/Validation/ConstraintValidatorTest.php +++ b/tests/php/Core/Validation/ConstraintValidatorTest.php @@ -354,7 +354,7 @@ public function testValidateResults(mixed $value, Constraint|array $constraints, $this->assertCount($countViolations, $violations); foreach ($violations as $violation) { - $this->assertSame($fieldName ?: null, $violation['fieldName']); + $this->assertSame($fieldName ?: '', $violation['fieldName']); } }