From 7154fa0f4adfb285337d140fd2165f8d57ba0d57 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 6 Feb 2019 14:24:20 +0200 Subject: [PATCH] field validation framework --- .../Espo/Core/FieldValidators/BaseType.php | 59 ++++++++++++++ .../Core/FieldValidators/CurrencyType.php | 37 +++++++++ .../Espo/Core/FieldValidators/FloatType.php | 37 +++++++++ .../Espo/Core/FieldValidators/IntType.php | 40 ++++++++++ .../Core/FieldValidators/PersonNameType.php | 51 ++++++++++++ .../Espo/Core/FieldValidators/VarcharType.php | 40 ++++++++++ .../Core/Loaders/FieldValidatorManager.php | 41 ++++++++++ .../Espo/Core/Utils/FieldManagerUtil.php | 10 +++ .../Espo/Core/Utils/FieldValidatorManager.php | 79 +++++++++++++++++++ .../Resources/metadata/fields/currency.json | 1 + .../Espo/Resources/metadata/fields/float.json | 1 + .../Espo/Resources/metadata/fields/int.json | 1 + .../Resources/metadata/fields/personName.json | 1 + .../Resources/metadata/fields/varchar.json | 1 + application/Espo/Services/Record.php | 58 ++++++++++---- client/src/field-manager.js | 4 + 16 files changed, 447 insertions(+), 14 deletions(-) create mode 100644 application/Espo/Core/FieldValidators/BaseType.php create mode 100644 application/Espo/Core/FieldValidators/CurrencyType.php create mode 100644 application/Espo/Core/FieldValidators/FloatType.php create mode 100644 application/Espo/Core/FieldValidators/IntType.php create mode 100644 application/Espo/Core/FieldValidators/PersonNameType.php create mode 100644 application/Espo/Core/FieldValidators/VarcharType.php create mode 100644 application/Espo/Core/Loaders/FieldValidatorManager.php create mode 100644 application/Espo/Core/Utils/FieldValidatorManager.php diff --git a/application/Espo/Core/FieldValidators/BaseType.php b/application/Espo/Core/FieldValidators/BaseType.php new file mode 100644 index 0000000000..6073cee41b --- /dev/null +++ b/application/Espo/Core/FieldValidators/BaseType.php @@ -0,0 +1,59 @@ +metadata = $metadata; + $this->fieldManagerUtil = $fieldManagerUtil; + } + + protected function getActualAttributeList(Entity $entity, string $field) : array + { + return $this->getFieldManagerUtil()->getActualAttributeList($entity->getEntityType(), $field); + } + + protected function getMetadata() : \Espo\Core\Utils\Metadata + { + return $this->metadata; + } + + protected function getFieldManagerUtil() : \Espo\Core\Utils\FieldManagerUtil + { + return $this->fieldManagerUtil; + } +} diff --git a/application/Espo/Core/FieldValidators/CurrencyType.php b/application/Espo/Core/FieldValidators/CurrencyType.php new file mode 100644 index 0000000000..e2734f5692 --- /dev/null +++ b/application/Espo/Core/FieldValidators/CurrencyType.php @@ -0,0 +1,37 @@ +has($field) && $entity->get($field) !== null; + } +} diff --git a/application/Espo/Core/FieldValidators/PersonNameType.php b/application/Espo/Core/FieldValidators/PersonNameType.php new file mode 100644 index 0000000000..e5c5b7eeb3 --- /dev/null +++ b/application/Espo/Core/FieldValidators/PersonNameType.php @@ -0,0 +1,51 @@ +getActualAttributeList($entity, $field) as $attribute) { + if ($attribute === 'salutation' . ucfirst($field)) { + continue; + } + if ($entity->has($attribute) && $entity->get($attribute) !== '') { + $isEmpty = false; + break; + } + } + if ($isEmpty) return false; + return true; + } +} diff --git a/application/Espo/Core/FieldValidators/VarcharType.php b/application/Espo/Core/FieldValidators/VarcharType.php new file mode 100644 index 0000000000..a7b2715112 --- /dev/null +++ b/application/Espo/Core/FieldValidators/VarcharType.php @@ -0,0 +1,40 @@ +has($field) && $entity->get($field) !== ''; + } +} diff --git a/application/Espo/Core/Loaders/FieldValidatorManager.php b/application/Espo/Core/Loaders/FieldValidatorManager.php new file mode 100644 index 0000000000..7bdd8fa9f5 --- /dev/null +++ b/application/Espo/Core/Loaders/FieldValidatorManager.php @@ -0,0 +1,41 @@ +getContainer()->get('metadata'), + $this->getContainer()->get('fieldManagerUtil') + ); + } +} diff --git a/application/Espo/Core/Utils/FieldManagerUtil.php b/application/Espo/Core/Utils/FieldManagerUtil.php index 7fa5e3e30e..6186bab9b3 100644 --- a/application/Espo/Core/Utils/FieldManagerUtil.php +++ b/application/Espo/Core/Utils/FieldManagerUtil.php @@ -185,4 +185,14 @@ class FieldManagerUtil $this->getFieldTypeAttributeListByType($fieldType, $name, 'notActual') ); } + + public function getEntityTypeFieldList($entityType) + { + return array_keys($this->getMetadata()->get(['entityDefs', $entityType, 'fields'], [])); + } + + public function getEntityTypeFieldParam($entityType, $field, $param) + { + return $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field, $param]); + } } diff --git a/application/Espo/Core/Utils/FieldValidatorManager.php b/application/Espo/Core/Utils/FieldValidatorManager.php new file mode 100644 index 0000000000..0d9c2f3983 --- /dev/null +++ b/application/Espo/Core/Utils/FieldValidatorManager.php @@ -0,0 +1,79 @@ +metadata = $metadata; + $this->fieldManagerUtil = $fieldManagerUtil; + } + + public function check(\Espo\ORM\Entity $entity, string $field, string $type, $data = null) : bool + { + if (!$data) $data = (object) []; + + $validationValue = $this->fieldManagerUtil->getEntityTypeFieldParam($entity->getEntityType(), $field, $type); + if (is_null($validationValue) || $validationValue === false) return true; + + $fieldType = $this->fieldManagerUtil->getEntityTypeFieldParam($entity->getEntityType(), $field, 'type'); + if (!array_key_exists($fieldType, $this->implHash)) { + $this->loadImpl($fieldType); + } + $impl = $this->implHash[$fieldType]; + + $methodName = 'check' . ucfirst($type); + + if (!method_exists($impl, $methodName)) return true; + + return $impl->$methodName($entity, $field, $validationValue, $data); + } + + protected function loadImpl(string $fieldType) + { + $className = $this->metadata->get(['fields', $fieldType, 'validatorClassName']); + + if (!$className) { + $className = '\\Espo\\Core\\FieldValidators\\' . ucfirst($fieldType) . 'Type'; + if (!class_exists($className)) { + $className = '\\Espo\\Core\\FieldValidators\\BaseType'; + } + } + + $this->implHash[$fieldType] = new $className($this->metadata, $this->fieldManagerUtil); + } +} diff --git a/application/Espo/Resources/metadata/fields/currency.json b/application/Espo/Resources/metadata/fields/currency.json index 793046b7d2..984b686b3d 100644 --- a/application/Espo/Resources/metadata/fields/currency.json +++ b/application/Espo/Resources/metadata/fields/currency.json @@ -52,6 +52,7 @@ "importDisabled": true } }, + "validationList": ["required"], "filter": true, "personalData": true } diff --git a/application/Espo/Resources/metadata/fields/float.json b/application/Espo/Resources/metadata/fields/float.json index 3850274344..d431d82737 100644 --- a/application/Espo/Resources/metadata/fields/float.json +++ b/application/Espo/Resources/metadata/fields/float.json @@ -26,6 +26,7 @@ "type":"bool" } ], + "validationList": ["required"], "filter": true, "fieldDefs":{ "notNull":false diff --git a/application/Espo/Resources/metadata/fields/int.json b/application/Espo/Resources/metadata/fields/int.json index b7750d1d84..12d60ce3b4 100644 --- a/application/Espo/Resources/metadata/fields/int.json +++ b/application/Espo/Resources/metadata/fields/int.json @@ -30,6 +30,7 @@ "type":"bool" } ], + "validationList": ["required"], "filter": true, "textFilter": true, "textFilterForeign": true, diff --git a/application/Espo/Resources/metadata/fields/personName.json b/application/Espo/Resources/metadata/fields/personName.json index 99ab13e12d..0936587f7e 100644 --- a/application/Espo/Resources/metadata/fields/personName.json +++ b/application/Espo/Resources/metadata/fields/personName.json @@ -35,6 +35,7 @@ "personalData": true, "textFilter": true, "fullTextSearch": true, + "validationList": ["required"], "fullTextSearchColumnList": [ "first", "last" diff --git a/application/Espo/Resources/metadata/fields/varchar.json b/application/Espo/Resources/metadata/fields/varchar.json index 36e844c28f..6ab066363e 100644 --- a/application/Espo/Resources/metadata/fields/varchar.json +++ b/application/Espo/Resources/metadata/fields/varchar.json @@ -31,6 +31,7 @@ "type":"bool" } ], + "validationList": ["required"], "filter": true, "personalData": true, "textFilter": true, diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index b0fa2cf0e1..2d003eb2f1 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -55,6 +55,7 @@ class Record extends \Espo\Core\Services\Base 'fileStorageManager', 'injectableFactory', 'fieldManagerUtil', + 'container', ]; protected $getEntityBeforeUpdate = false; @@ -117,6 +118,8 @@ class Record extends \Espo\Core\Services\Base protected $forceSelectAllAttributes = false; + protected $validateSkipFieldList = []; + const MAX_SELECT_TEXT_ATTRIBUTE_LENGTH = 5000; const FOLLOWERS_LIMIT = 4; @@ -495,16 +498,49 @@ class Record extends \Espo\Core\Services\Base return $this->getRepository()->save($entity); } - protected function isValid($entity) + public function processValidation(Entity $entity, $data) { - $fieldDefs = $entity->getAttributes(); - if ($entity->hasAttribute('name') && !empty($fieldDefs['name']['required'])) { - if (!$entity->get('name')) { - return false; + $fieldList = $this->getFieldManagerUtil()->getEntityTypeFieldList($this->entityType); + + foreach ($fieldList as $field) { + if (in_array($field, $this->validateSkipFieldList)) continue; + if (!$entity->isNew()) { + if (!$this->isFieldSetInData($data, $field)) continue; + } + $this->processDataValidationField($entity, $field, $data); + } + } + + protected function processDataValidationField(Entity $entity, $field, $data) + { + $fieldType = $this->getFieldManagerUtil()->getEntityTypeFieldParam($this->entityType, $field, 'type'); + if (!$fieldType) return; + + $fieldValidatorManager = $this->getInjection('container')->get('fieldValidatorManager'); + + $validationTypeList = $this->getMetadata()->get(['fields', $fieldType, 'validationList'], []); + + foreach ($validationTypeList as $validationType) { + $validationValue = $this->getFieldManagerUtil()->getEntityTypeFieldParam($this->entityType, $field, $validationType); + if (is_null($validationValue)) continue; + + if (!$fieldValidatorManager->check($entity, $field, $validationType, $validationValue, $data)) { + throw new BadRequest("Validation {$validationType}: {$field}."); } } + } - return true; + protected function isFieldSetInData($data, $field) + { + $attributeList = $this->getFieldManagerUtil()->getActualAttributeList($this->entityType, $field); + $isSet = false; + foreach ($attributeList as $attribute) { + if (property_exists($data, $attribute)) { + $isSet = true; + break; + } + } + return $isSet; } public function checkAssignment(Entity $entity) @@ -827,11 +863,9 @@ class Record extends \Espo\Core\Services\Base $this->populateDefaults($entity, $data); - $this->beforeCreateEntity($entity, $data); + $this->processValidation($entity, $data); - if (!$this->isValid($entity)) { - throw new BadRequest(); - } + $this->beforeCreateEntity($entity, $data); if (!$this->checkAssignment($entity)) { throw new Forbidden('Assignment permission failure'); @@ -895,10 +929,6 @@ class Record extends \Espo\Core\Services\Base $this->beforeUpdateEntity($entity, $data); - if (!$this->isValid($entity)) { - throw new BadRequest(); - } - if (!$this->checkAssignment($entity)) { throw new Forbidden(); } diff --git a/client/src/field-manager.js b/client/src/field-manager.js index 6db9787c40..772162956e 100644 --- a/client/src/field-manager.js +++ b/client/src/field-manager.js @@ -145,6 +145,10 @@ return Object.keys(this.metadata.get(['entityDefs', entityType, 'fields']) || {}); }, + getEntityTypeFieldParam: function (entityType, field, param) { + this.metadata.get(['entityDefs', entityType, 'fields', field, param]); + }, + getViewName: function (fieldType) { if (fieldType in this.defs) { if ('view' in this.defs[fieldType]) {