From 0dff4ac6b4e93e26227d85768dbeb37db2696f45 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 24 Jul 2025 10:38:11 +0300 Subject: [PATCH] sanitize foreign fields --- application/Espo/Core/Record/Service.php | 51 +++++++++++++++++++ .../integration/Espo/Record/SanitizeTest.php | 25 +++++++++ 2 files changed, 76 insertions(+) diff --git a/application/Espo/Core/Record/Service.php b/application/Espo/Core/Record/Service.php index 1e79aefafa..854748af2c 100644 --- a/application/Espo/Core/Record/Service.php +++ b/application/Espo/Core/Record/Service.php @@ -44,6 +44,7 @@ use Espo\Core\ORM\Entity as CoreEntity; use Espo\Core\ORM\Repository\Option\RemoveOption; use Espo\Core\ORM\Repository\Option\SaveContext; use Espo\Core\ORM\Repository\Option\SaveOption; +use Espo\Core\ORM\Type\FieldType; use Espo\Core\Record\Access\LinkCheck; use Espo\Core\Record\ActionHistory\Action; use Espo\Core\Record\ActionHistory\ActionLogger; @@ -69,6 +70,7 @@ use Espo\Core\Record\Duplicator\EntityDuplicator; use Espo\Core\Record\Select\ApplierClassNameListProvider; use Espo\Core\Select\SearchParams; use Espo\Core\Di; +use Espo\ORM\Defs\Params\FieldParam; use Espo\ORM\Defs\Params\RelationParam; use Espo\ORM\Entity; use Espo\ORM\Name\Attribute; @@ -400,6 +402,55 @@ class Service implements Crud, $manager = $this->injectableFactory->create(SanitizeManager::class); $manager->process($this->entityType, $data); + + $this->sanitizeInputForeign($data); + } + + private function sanitizeInputForeign(stdClass $data): void + { + $entityDefs = $this->entityManager->getDefs()->getEntity($this->entityType); + + /** @var array $map */ + $map = []; + + foreach ($entityDefs->getFieldList() as $fieldDefs) { + if ($fieldDefs->getType() !== FieldType::FOREIGN) { + continue; + } + + $link = $fieldDefs->getParam(FieldParam::LINK); + $foreignField = $fieldDefs->getParam(FieldParam::FIELD); + + if (!$link || !$foreignField) { + continue; + } + + $foreignEntityType = $entityDefs->tryGetRelation($link)?->tryGetForeignEntityType(); + + if (!$foreignEntityType) { + continue; + } + + $id = $data->{$link . 'Id'} ?? null; + + if (!is_string($id)) { + continue; + } + + if (!array_key_exists($link, $map)) { + $map[$link] = $this->entityManager->getEntityById($foreignEntityType, $id);; + } + + $foreignEntity = $map[$link] ?? null; + + if (!$foreignEntity) { + continue; + } + + $field = $fieldDefs->getName(); + + $data->$field = $foreignEntity->get($foreignField); + } } protected function filterInput(stdClass $data): void diff --git a/tests/integration/Espo/Record/SanitizeTest.php b/tests/integration/Espo/Record/SanitizeTest.php index cb08f6289f..aa90997929 100644 --- a/tests/integration/Espo/Record/SanitizeTest.php +++ b/tests/integration/Espo/Record/SanitizeTest.php @@ -34,6 +34,7 @@ use Espo\Core\Record\ServiceContainer; use Espo\Core\Utils\Config\ConfigWriter; use Espo\Entities\User; use Espo\Modules\Crm\Entities\Account; +use Espo\Modules\Crm\Entities\Contact; use Espo\Modules\Crm\Entities\Meeting; use Espo\Modules\Crm\Entities\Opportunity; use Espo\Modules\Crm\Entities\Task; @@ -221,4 +222,28 @@ class SanitizeTest extends BaseTestCase $this->assertEquals('2030-12-10', $meeting->get('closeDate')); } + + public function testForeign(): void + { + $account = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, [ + 'type' => Account::TYPE_CUSTOMER, + ]); + + $serviceContainer = $this->getContainer()->getByClass(ServiceContainer::class); + + $contactService = $serviceContainer->getByClass(Contact::class); + + $data = (object) [ + 'lastName' => 'C-1', + 'accountId' => $account->getId(), + ]; + $contactService->sanitizeInput($data); + $this->assertEquals(Account::TYPE_CUSTOMER, $data->accountType ?? null); + + $data = (object) [ + 'lastName' => 'C-2', + ]; + $contactService->sanitizeInput($data); + $this->assertEquals(null, $data->accountType ?? null); + } }