From 404590193a02ffd1ecc244e33e1ddf637e987f8e Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Tue, 30 Apr 2024 12:12:50 +1200 Subject: [PATCH] FIX Improve non-inline validation --- src/Forms/ElementalAreaField.php | 79 +++++++++++++++++++++++----- src/Forms/TextCheckboxGroupField.php | 52 ++++++++++++++++++ src/Models/BaseElement.php | 2 + 3 files changed, 120 insertions(+), 13 deletions(-) diff --git a/src/Forms/ElementalAreaField.php b/src/Forms/ElementalAreaField.php index eb95ddb2..812f4930 100644 --- a/src/Forms/ElementalAreaField.php +++ b/src/Forms/ElementalAreaField.php @@ -10,11 +10,14 @@ use SilverStripe\Forms\CompositeField; use SilverStripe\Forms\FieldGroup; use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\Form; use SilverStripe\Forms\FormField; use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\TabSet; use SilverStripe\ORM\DataObjectInterface; use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass; +use SilverStripe\Forms\GridField\GridFieldDetailForm; +use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest; class ElementalAreaField extends GridField { @@ -214,33 +217,83 @@ public function setSubmittedValue($value, $data = null) return $this->setValue(json_decode($value ?? '', true)); } + /** + * This will perform FormField validation + * DataObject validation will happen in saveInto() as part of $element->write() + */ + public function validate($validator) + { + foreach ($this->getElementData() as $arr) { + /** @var BaseElement $element */ + /** @var array $data */ + $element = $arr['element']; + $data = $arr['data']; + $elementForm = $this->getElementForm($element); + if (!$elementForm) { + continue; + } + $elementForm->loadDataFrom($data); + $formValidatorResult = $elementForm->validationResult(); + if (!$formValidatorResult->isValid()) { + $validator->getResult()->combineAnd($formValidatorResult); + } + } + return $validator; + } + public function saveInto(DataObjectInterface $dataObject) { parent::saveInto($dataObject); - - $elementData = $this->Value(); - $idPrefixLength = strlen(sprintf(ElementalAreaController::FORM_NAME_TEMPLATE ?? '', '')); - - if (!$elementData) { - return; + foreach ($this->getElementData() as $arr) { + /** @var BaseElement $element */ + /** @var array $data */ + $element = $arr['element']; + $data = $arr['data']; + $element->updateFromFormData($data); + $element->write(); } + } - foreach ($elementData as $form => $data) { + private function getElementData(): array + { + $elementData = []; + $value = $this->Value(); + if (!$value) { + return []; + } + $idPrefixLength = strlen(sprintf(ElementalAreaController::FORM_NAME_TEMPLATE ?? '', '')); + foreach ($value as $form => $data) { // Extract the ID $elementId = (int) substr($form ?? '', $idPrefixLength ?? 0); - /** @var BaseElement $element */ $element = $this->getArea()->Elements()->byID($elementId); - if (!$element) { // Ignore invalid elements continue; } - $data = ElementalAreaController::removeNamespacesFromFields($data, $element->ID); - - $element->updateFromFormData($data); - $element->write(); + $elementData[] = ['element' => $element, 'data' => $data]; } + return $elementData; + } + + private function getElementForm(BaseElement $element): ?Form + { + // This is essentially the same method used to generate a form to edit an element + // that a non-inline edit for will use - see GridFieldDetailForm::handleItem() + $requestHandler = $requestHandler = $this->getForm()->getController(); + $gridFieldDetailForm = new GridFieldDetailForm(); + // The validator need to be explicitly copied from the element to the form + $validator = $element->getCMSCompositeValidator(); + $gridFieldDetailForm->setValidator($validator); + $gridFieldDetailFormItemRequest = new GridFieldDetailForm_ItemRequest( + $this, + $gridFieldDetailForm, + $element, + $requestHandler, + '' + ); + $form = $gridFieldDetailFormItemRequest->ItemEditForm(); + return $form; } } diff --git a/src/Forms/TextCheckboxGroupField.php b/src/Forms/TextCheckboxGroupField.php index 27c9c392..403c8d08 100644 --- a/src/Forms/TextCheckboxGroupField.php +++ b/src/Forms/TextCheckboxGroupField.php @@ -32,6 +32,58 @@ public function __construct($title = null) $this->setTitle($title); } + /** + * @return string + */ + public function getMessage() + { + $message = parent::getMessage(); + if ($message) { + return $message; + } + foreach ($this->getChildren() as $field) { + $message = $field->getMessage(); + if ($message) { + return $message; + } + } + return ''; + } + + /** + * @return string + */ + public function getMessageType() + { + $message = parent::getMessage(); + if ($message) { + return parent::getMessageType(); + } + foreach ($this->getChildren() as $field) { + if ($field->getMessage()) { + return $field->getMessageType(); + } + } + return ''; + } + + /** + * @return string + */ + public function getMessageCast() + { + $message = parent::getMessage(); + if ($message) { + return parent::getMessageCast(); + } + foreach ($this->getChildren() as $field) { + if ($field->getMessage()) { + return $field->getMessageCast(); + } + } + return ''; + } + /** * Don't use the custom template for readonly states * diff --git a/src/Models/BaseElement.php b/src/Models/BaseElement.php index e1078558..54b8c312 100644 --- a/src/Models/BaseElement.php +++ b/src/Models/BaseElement.php @@ -654,6 +654,7 @@ public function getRenderTemplates($suffix = '') */ public function updateFromFormData($data) { + /** @var FieldList $cmsFields */ $cmsFields = $this->getCMSFields(); foreach ($data as $field => $datum) { @@ -664,6 +665,7 @@ public function updateFromFormData($data) } $field->setSubmittedValue($datum); + // $field->validate(); $field->saveInto($this); } }