From 5f1cb08b89240c6645966ee47303f77d6b02a256 Mon Sep 17 00:00:00 2001 From: Kurund Jalmi Date: Mon, 1 Aug 2022 13:18:09 +0100 Subject: [PATCH] add server side validation for formbuilder --- .../core/Civi/Api4/Action/Afform/Submit.php | 78 ++++++++++++++++++ ext/afform/core/ang/af/afForm.component.js | 6 ++ .../phpunit/api/v4/AfformContactUsageTest.php | 81 +++++++++++++++++++ 3 files changed, 165 insertions(+) diff --git a/ext/afform/core/Civi/Api4/Action/Afform/Submit.php b/ext/afform/core/Civi/Api4/Action/Afform/Submit.php index b58f87b435..8844060d01 100644 --- a/ext/afform/core/Civi/Api4/Action/Afform/Submit.php +++ b/ext/afform/core/Civi/Api4/Action/Afform/Submit.php @@ -67,6 +67,12 @@ class Submit extends AbstractProcessor { } } } + + // validate the submitted values for required + if (!$this->validateForm()) { + throw new \Exception(ts('Please fill all required fields.')); + } + $entityWeights = \Civi\Afform\Utils::getEntityWeights($this->_formDataModel->getEntities(), $entityValues); foreach ($entityWeights as $entityName) { $entityType = $this->_formDataModel->getEntity($entityName)['type']; @@ -110,6 +116,78 @@ class Submit extends AbstractProcessor { return $combined; } + /** + * Function to validate the submitted values + * + * @return boolean + */ + private function validateForm() { + $isValid = TRUE; + + // get feild flatterned defintions + $fieldDefinitions = $this->getAllFieldDefinitions(); + + // loops through each field and validates the submitted values + foreach ($fieldDefinitions as $entityName => $field) { + foreach ($this->values[$entityName] as $index => $values) { + foreach ($field as $fieldName => $fieldDefinition) { + if ($fieldName !== 'joins') { + if (!empty($fieldDefinition['required']) && empty($values['fields'][$fieldName])) { + $isValid = FALSE; + } + } + else { + // loop through each joins and validates the submitted values + foreach ($field['joins'] as $joinName => $joinField) { + foreach ($values['joins'][$joinName] as $joinIndex => $joinValues) { + foreach ($joinField as $joinFieldName => $joinFieldDefinition) { + if (!empty($joinFieldDefinition['required']) && empty($joinValues[$joinFieldName])) { + $isValid = FALSE; + } + } + } + } + } + } + } + } + + return $isValid; + } + + /** + * Get all field definitions for this form + * + * @return array $fieldDefinitions + */ + private function getAllFieldDefinitions() { + $fieldDefinitions = []; + + foreach ($this->_formDataModel->getEntities() as $entityName => $entity) { + // main entity fields + foreach ($entity['fields'] as $field => $attributes) { + if (!empty($attributes['defn'])) { + $fieldDefinitions[$entityName][$field] = array_merge(['name' => $field], $attributes['defn']); + } + } + + // join entity fields + if (!empty($entity['joins'])) { + foreach ($entity['joins'] as $joinEntityName => $joinEntity) { + if (!empty($joinEntity['fields'])) { + foreach ($joinEntity['fields'] as $joinField => $joinAttributes) { + if (!empty($joinAttributes['defn'])) { + $fieldDefinitions[$entityName]['joins'][$joinEntityName][$joinField] = array_merge(['name' => $joinField], $joinAttributes['defn']); + } + } + } + } + } + } + + return $fieldDefinitions; + } + /** * Replace Entity reference fields with the id of the referenced entity. * @param string $entityName diff --git a/ext/afform/core/ang/af/afForm.component.js b/ext/afform/core/ang/af/afForm.component.js index 19fb77e26c..097ebbb46e 100644 --- a/ext/afform/core/ang/af/afForm.component.js +++ b/ext/afform/core/ang/af/afForm.component.js @@ -164,6 +164,12 @@ } else { postProcess(); } + }) + .catch(function(error) { + status.resolve(); + status = CRM.status(error.error_message, 'error'); + $element.unblock(); + CRM.alert(error.error_message, ts('Form Error')); }); }; } diff --git a/ext/afform/mock/tests/phpunit/api/v4/AfformContactUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/AfformContactUsageTest.php index f338f5980e..ac5203e4dd 100644 --- a/ext/afform/mock/tests/phpunit/api/v4/AfformContactUsageTest.php +++ b/ext/afform/mock/tests/phpunit/api/v4/AfformContactUsageTest.php @@ -57,6 +57,20 @@ EOHTML; +EOHTML; + self::$layouts['updateInfo'] = << + +
+ + +
+
+ +
+
+
+ EOHTML; } @@ -410,4 +424,71 @@ EOHTML; $this->assertEquals('New', $result['middle_name']); } + public function testFormValidationEntityFields(): void { + $this->useValues([ + 'layout' => self::$layouts['updateInfo'], + 'permission' => CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION, + ]); + + $values = [ + 'Individual1' => [ + [ + 'fields' => [], + 'joins' => [ + 'Email' => [ + ['email' => 'test@example.org'], + ], + ], + ], + ], + ]; + + $this->expectException(\Exception::class); + + try { + Civi\Api4\Afform::submit() + ->setName($this->formName) + ->setValues($values) + ->execute(); + } + catch (\CRM_Core_Exception $e) { + // Should fail required fields missing + } + + } + + public function testFormValidationEntityJoinFields(): void { + $this->useValues([ + 'layout' => self::$layouts['updateInfo'], + 'permission' => CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION, + ]); + + $values = [ + 'Individual1' => [ + [ + 'fields' => [ + 'first_name' => 'Jane', + 'last_name' => 'Doe', + ], + 'joins' => [ + 'Email' => [[]], + ], + ], + ], + ]; + + $this->expectException(\Exception::class); + + try { + Civi\Api4\Afform::submit() + ->setName($this->formName) + ->setValues($values) + ->execute(); + } + catch (\CRM_Core_Exception $e) { + // Should fail required fields missing + } + + } + } -- 2.25.1