field validation refactoring
This commit is contained in:
@@ -32,12 +32,16 @@ namespace Espo\Core\FieldValidation;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\BadRequest,
|
||||
Utils\Metadata,
|
||||
Utils\FieldUtil,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
|
||||
/**
|
||||
* A field validation manager.
|
||||
*/
|
||||
class FieldValidationManager
|
||||
{
|
||||
private $checkerCache = [];
|
||||
@@ -55,6 +59,52 @@ class FieldValidationManager
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process validation.
|
||||
*
|
||||
* @param Entity $entity An entity.
|
||||
* @param ?StdClass $data Raw request payload data.
|
||||
* @param ?Params $params Validation additional parameters.
|
||||
|
||||
*
|
||||
* @throws BadRequest If data is not valid.
|
||||
*/
|
||||
public function process(Entity $entity, ?StdClass $data = null, ?Params $params = null) : void
|
||||
{
|
||||
$dataIsSet = $data !== null;
|
||||
|
||||
if (!$data) {
|
||||
$data = $data ?? (object) [];
|
||||
}
|
||||
|
||||
if (!$params) {
|
||||
$params = new Params();
|
||||
}
|
||||
|
||||
$fieldList = $this->fieldUtil->getEntityTypeFieldList($entity->getEntityType());
|
||||
|
||||
$skipFieldList = $params->getSkipFieldList();
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
if (in_array($field, $skipFieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!$entity->isNew() &&
|
||||
$dataIsSet &&
|
||||
!$this->isFieldSetInData($entity->getEntityType(), $field, $data)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->processField($entity, $field, $params, $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a specific field for a specific validation type.
|
||||
*/
|
||||
public function check(Entity $entity, string $field, string $type, ?StdClass $data = null) : bool
|
||||
{
|
||||
if (!$data) {
|
||||
@@ -89,6 +139,34 @@ class FieldValidationManager
|
||||
return true;
|
||||
}
|
||||
|
||||
private function processField(Entity $entity, string $field, Params $params, StdClass $data) : void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$fieldType = $this->fieldUtil->getEntityTypeFieldParam($entityType, $field, 'type');
|
||||
|
||||
$validationList = $this->metadata->get(['fields', $fieldType, 'validationList'], []);
|
||||
$mandatoryValidationList = $this->metadata->get(['fields', $fieldType, 'mandatoryValidationList'], []);
|
||||
|
||||
foreach ($validationList as $type) {
|
||||
$value = $this->fieldUtil->getEntityTypeFieldParam($entityType, $field, $type);
|
||||
|
||||
if (is_null($value) && !in_array($type, $mandatoryValidationList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($field, $params->getTypeSkipFieldList($type))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $this->check($entity, $field, $type, $data);
|
||||
|
||||
if (!$result) {
|
||||
throw new BadRequest("Not valid data. Field: {$field}, validation type: {$type}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processFieldCheck(
|
||||
string $entityType, string $type, Entity $entity, string $field, $validationValue
|
||||
) : bool {
|
||||
@@ -150,4 +228,21 @@ class FieldValidationManager
|
||||
|
||||
$this->checkerCache[$key] = $this->factory->create($entityType, $field);
|
||||
}
|
||||
|
||||
private function isFieldSetInData(string $entityType, string $field, StdClass $data) : bool
|
||||
{
|
||||
$attributeList = $this->fieldUtil->getActualAttributeList($entityType, $field);
|
||||
|
||||
$isSet = false;
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
if (property_exists($data, $attribute)) {
|
||||
$isSet = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $isSet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\FieldValidation;
|
||||
|
||||
class Params
|
||||
{
|
||||
private $skipFieldList = [];
|
||||
|
||||
private $typeSkipFieldListData = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* A field list that will be skipped in validation.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getSkipFieldList() : array
|
||||
{
|
||||
return $this->skipFieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* A field list that will be skipped in validation for a specific validation type.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getTypeSkipFieldList(string $type) : array
|
||||
{
|
||||
return $this->typeSkipFieldListData[$type] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a specified field list that will be skipped in validation.
|
||||
*
|
||||
* @param array<string> $list
|
||||
*/
|
||||
public function withSkipFieldList(array $list) : self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->skipFieldList = $list;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a specified field list that will be skipped in validation for a specific validation type.
|
||||
*
|
||||
* @param array<string> $list
|
||||
*/
|
||||
public function withTypeSkipFieldList(string $type, array $list) : self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->typeSkipFieldListData[$type] = $list;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty instance.
|
||||
*/
|
||||
public function fromNothing() : self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ use Espo\Core\{
|
||||
Action\Params as ActionParams,
|
||||
Action\Data as ActionData,
|
||||
Action\ActionFactory,
|
||||
FieldValidation\Params as FieldValidationParams,
|
||||
};
|
||||
|
||||
use Espo\Tools\{
|
||||
@@ -179,6 +180,8 @@ class Record implements Crud,
|
||||
|
||||
protected $validateSkipFieldList = [];
|
||||
|
||||
protected $validateRequiredSkipFieldList = [];
|
||||
|
||||
protected $findDuplicatesSelectAttributeList = ['id', 'name'];
|
||||
|
||||
protected $duplicateIgnoreFieldList = [];
|
||||
@@ -295,6 +298,9 @@ class Record implements Crud,
|
||||
return $this->serviceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Since v6.2.0.
|
||||
*/
|
||||
protected function getSelectManagerFactory()
|
||||
{
|
||||
return $this->selectManagerFactory;
|
||||
@@ -709,6 +715,9 @@ class Record implements Crud,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
protected function getSelectManager($entityType = null)
|
||||
{
|
||||
if (!$entityType) {
|
||||
@@ -723,74 +732,26 @@ class Record implements Crud,
|
||||
$this->getRepository()->save($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function processValidation(Entity $entity, $data)
|
||||
{
|
||||
$fieldList = $this->fieldUtil->getEntityTypeFieldList($this->entityType);
|
||||
$params = FieldValidationParams
|
||||
::fromNothing()
|
||||
->withSkipFieldList($this->validateSkipFieldList)
|
||||
->withTypeSkipFieldList('required', $this->validateRequiredSkipFieldList);
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
if (in_array($field, $this->validateSkipFieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
if (!$this->isFieldSetInData($data, $field)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->processValidationField($entity, $field, $data);
|
||||
}
|
||||
}
|
||||
|
||||
protected function processValidationField(Entity $entity, string $field, $data)
|
||||
{
|
||||
$fieldType = $this->fieldUtil->getEntityTypeFieldParam($this->entityType, $field, 'type');
|
||||
|
||||
$validationList = $this->getMetadata()->get(['fields', $fieldType, 'validationList'], []);
|
||||
$mandatoryValidationList = $this->getMetadata()->get(['fields', $fieldType, 'mandatoryValidationList'], []);
|
||||
|
||||
foreach ($validationList as $type) {
|
||||
$value = $this->fieldUtil->getEntityTypeFieldParam($this->entityType, $field, $type);
|
||||
|
||||
if (is_null($value)) {
|
||||
if (!in_array($type, $mandatoryValidationList)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$skipPropertyName = 'validate' . ucfirst($type) . 'SkipFieldList';
|
||||
|
||||
if (property_exists($this, $skipPropertyName)) {
|
||||
$skipList = $this->$skipPropertyName;
|
||||
|
||||
if (in_array($field, $skipList)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->fieldValidationManager->check($entity, $field, $type, $data)) {
|
||||
throw new BadRequest("Not valid data. Field: '{$field}', type: {$type}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function isFieldSetInData(StdClass $data, string $field) : bool
|
||||
{
|
||||
$attributeList = $this->fieldUtil->getActualAttributeList($this->entityType, $field);
|
||||
|
||||
$isSet = false;
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
if (property_exists($data, $attribute)) {
|
||||
$isSet = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $isSet;
|
||||
$this->fieldValidationManager->process($entity, $data, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function processAssignmentCheck(Entity $entity)
|
||||
{
|
||||
if (!$this->checkAssignment($entity)) {
|
||||
|
||||
@@ -29,12 +29,10 @@
|
||||
|
||||
namespace Espo\Services;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\Forbidden,
|
||||
ApplicationState,
|
||||
Acl,
|
||||
InjectableFactory,
|
||||
@@ -48,17 +46,28 @@ use Espo\Core\{
|
||||
Currency\DatabasePopulator as CurrencyDatabasePopulator,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
|
||||
class Settings
|
||||
{
|
||||
protected $applicationState;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $configWriter;
|
||||
|
||||
protected $fieldUtil;
|
||||
|
||||
protected $metadata;
|
||||
|
||||
protected $acl;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $dataManager;
|
||||
|
||||
protected $fieldValidationManager;
|
||||
|
||||
protected $injectableFactory;
|
||||
|
||||
public function __construct(
|
||||
@@ -85,7 +94,7 @@ class Settings
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
public function getConfigData()
|
||||
public function getConfigData() : StdClass
|
||||
{
|
||||
$data = $this->config->getAllData();
|
||||
|
||||
@@ -200,7 +209,7 @@ class Settings
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setConfigData($data)
|
||||
public function setConfigData(StdClass $data) : void
|
||||
{
|
||||
$user = $this->applicationState->getUser();
|
||||
|
||||
@@ -232,6 +241,8 @@ class Settings
|
||||
|
||||
$entity->set($data);
|
||||
|
||||
$entity->setAsNotNew();
|
||||
|
||||
$this->processValidation($entity, $data);
|
||||
|
||||
if (
|
||||
@@ -255,12 +266,12 @@ class Settings
|
||||
}
|
||||
}
|
||||
|
||||
protected function populateDatabaseWithCurrencyRates()
|
||||
protected function populateDatabaseWithCurrencyRates() : void
|
||||
{
|
||||
$this->injectableFactory->create(CurrencyDatabasePopulator::class)->process();
|
||||
}
|
||||
|
||||
protected function filterData($data)
|
||||
protected function filterData(StdCLass $data) : void
|
||||
{
|
||||
$user = $this->applicationState->getUser();
|
||||
|
||||
@@ -287,7 +298,7 @@ class Settings
|
||||
}
|
||||
}
|
||||
|
||||
public function getAdminOnlyItemList()
|
||||
public function getAdminOnlyItemList() : array
|
||||
{
|
||||
$itemList = $this->config->getAdminOnlyItemList();
|
||||
|
||||
@@ -303,7 +314,7 @@ class Settings
|
||||
return $itemList;
|
||||
}
|
||||
|
||||
public function getUserOnlyItemList()
|
||||
public function getUserOnlyItemList() : array
|
||||
{
|
||||
$itemList = $this->config->getUserOnlyItemList();
|
||||
|
||||
@@ -320,7 +331,7 @@ class Settings
|
||||
return $itemList;
|
||||
}
|
||||
|
||||
public function getSystemOnlyItemList()
|
||||
public function getSystemOnlyItemList() : array
|
||||
{
|
||||
$itemList = $this->config->getSystemOnlyItemList();
|
||||
|
||||
@@ -337,7 +348,7 @@ class Settings
|
||||
return $itemList;
|
||||
}
|
||||
|
||||
public function getGlobalItemList()
|
||||
public function getGlobalItemList() : array
|
||||
{
|
||||
$itemList = $this->config->get('globalItems', []);
|
||||
|
||||
@@ -354,52 +365,8 @@ class Settings
|
||||
return $itemList;
|
||||
}
|
||||
|
||||
protected function processValidation(Entity $entity, $data)
|
||||
protected function processValidation(Entity $entity, StdClass $data) : void
|
||||
{
|
||||
$fieldList = $this->fieldUtil->getEntityTypeFieldList('Settings');
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
if (!$this->isFieldSetInData($data, $field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->processValidationField($entity, $field, $data);
|
||||
}
|
||||
}
|
||||
|
||||
protected function processValidationField(Entity $entity, string $field, $data)
|
||||
{
|
||||
$fieldType = $this->fieldUtil->getEntityTypeFieldParam('Settings', $field, 'type');
|
||||
$validationList = $this->metadata->get(['fields', $fieldType, 'validationList'], []);
|
||||
$mandatoryValidationList = $this->metadata->get(['fields', $fieldType, 'mandatoryValidationList'], []);
|
||||
|
||||
foreach ($validationList as $type) {
|
||||
$value = $this->fieldUtil->getEntityTypeFieldParam('Settings', $field, $type);
|
||||
|
||||
if (is_null($value) && !in_array($type, $mandatoryValidationList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->fieldValidationManager->check($entity, $field, $type, $data)) {
|
||||
throw new BadRequest("Not valid data. Field: '{$field}', type: {$type}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function isFieldSetInData($data, $field)
|
||||
{
|
||||
$attributeList = $this->fieldUtil->getActualAttributeList('Settings', $field);
|
||||
|
||||
$isSet = false;
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
if (property_exists($data, $attribute)) {
|
||||
$isSet = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $isSet;
|
||||
$this->fieldValidationManager->process($entity, $data);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user