relation save processing refactoring
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<?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\FieldProcessing\Relation;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\Entity,
|
||||
ORM\EntityManager,
|
||||
};
|
||||
|
||||
class LinkMultipleSaveProcessor
|
||||
{
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(EntityManager $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, string $name, array $options): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$repository = $this->entityManager->getRDBRepository($entityType);
|
||||
|
||||
$idListAttribute = $name . 'Ids';
|
||||
$columnsAttribute = $name . 'Columns';
|
||||
|
||||
$defs = $this->entityManager->getDefs()->getEntity($entityType);
|
||||
|
||||
$skipCreate = $options['skipLinkMultipleCreate'] ?? false;
|
||||
$skipRemove = $options['skipLinkMultipleRemove'] ?? false;
|
||||
$skipUpdate = $options['skipLinkMultipleUpdate'] ?? false;
|
||||
|
||||
if ($entity->isNew()) {
|
||||
$skipRemove = true;
|
||||
$skipUpdate = true;
|
||||
}
|
||||
|
||||
if ($entity->has($idListAttribute)) {
|
||||
$specifiedIdList = $entity->get($idListAttribute);
|
||||
}
|
||||
else if ($entity->has($columnsAttribute)) {
|
||||
$skipRemove = true;
|
||||
|
||||
$specifiedIdList = array_keys(
|
||||
get_object_vars(
|
||||
$entity->get($columnsAttribute) ?? (object) []
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_array($specifiedIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$toRemoveIdList = [];
|
||||
$existingIdList = [];
|
||||
$toUpdateIdList = [];
|
||||
$toCreateIdList = [];
|
||||
|
||||
$existingColumnsData = (object) [];
|
||||
|
||||
$columns = null;
|
||||
|
||||
if ($defs->hasField($name)) {
|
||||
$columns = $defs->getField($name)->getParam('columns');
|
||||
}
|
||||
|
||||
$columnData = !empty($columns) ?
|
||||
$entity->get($columnsAttribute) :
|
||||
null;
|
||||
|
||||
if (!$skipRemove || !$skipUpdate) {
|
||||
$foreignEntityList = $repository->getRelation($entity, $name)->find();
|
||||
|
||||
foreach ($foreignEntityList as $foreignEntity) {
|
||||
$existingIdList[] = $foreignEntity->id;
|
||||
|
||||
if (empty($columns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = (object) [];
|
||||
|
||||
foreach ($columns as $columnName => $columnField) {
|
||||
$foreignId = $foreignEntity->id;
|
||||
|
||||
$data->$columnName = $foreignEntity->get($columnField);
|
||||
}
|
||||
|
||||
$existingColumnsData->$foreignId = $data;
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($columnsAttribute, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
if ($entity->has($idListAttribute) && !$entity->hasFetched($idListAttribute)) {
|
||||
$entity->setFetched($idListAttribute, $existingIdList);
|
||||
}
|
||||
|
||||
if ($entity->has($columnsAttribute) && !empty($columns)) {
|
||||
$entity->setFetched($columnsAttribute, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($existingIdList as $id) {
|
||||
if (!in_array($id, $specifiedIdList)) {
|
||||
if (!$skipRemove) {
|
||||
$toRemoveIdList[] = $id;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($skipUpdate || empty($columns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($columns as $columnName => $columnField) {
|
||||
if (!isset($columnData->$id) || !is_object($columnData->$id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
property_exists($columnData->$id, $columnName)
|
||||
&&
|
||||
(
|
||||
!property_exists($existingColumnsData->$id, $columnName)
|
||||
||
|
||||
$columnData->$id->$columnName !== $existingColumnsData->$id->$columnName
|
||||
)
|
||||
) {
|
||||
$toUpdateIdList[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$skipCreate) {
|
||||
foreach ($specifiedIdList as $id) {
|
||||
if (!in_array($id, $existingIdList)) {
|
||||
$toCreateIdList[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toCreateIdList as $id) {
|
||||
$data = null;
|
||||
|
||||
if (!empty($columns) && isset($columnData->$id)) {
|
||||
$data = (array) $columnData->$id;
|
||||
}
|
||||
|
||||
$repository->getRelation($entity, $name)->relateById($id, $data);
|
||||
}
|
||||
|
||||
foreach ($toRemoveIdList as $id) {
|
||||
$repository->getRelation($entity, $name)->unrelateById($id);
|
||||
}
|
||||
|
||||
foreach ($toUpdateIdList as $id) {
|
||||
$data = (array) $columnData->$id;
|
||||
|
||||
$repository->getRelation($entity, $name)->updateColumnsById($id, (array) $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
<?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\FieldProcessing\Relation;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\Entity,
|
||||
ORM\EntityManager,
|
||||
};
|
||||
|
||||
class SaveProcessor
|
||||
{
|
||||
private $entityManager;
|
||||
|
||||
private $linkMultipleSaveProcessor;
|
||||
|
||||
private $manyRelationListMapCache = [];
|
||||
|
||||
private $hasOneRelationListMapCache = [];
|
||||
|
||||
private $belongsToHasOneRelationListMapCache = [];
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
LinkMultipleSaveProcessor $linkMultipleSaveProcessor
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->linkMultipleSaveProcessor = $linkMultipleSaveProcessor;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, array $options): void
|
||||
{
|
||||
$this->processMany($entity, $options);
|
||||
$this->processHasOne($entity, $options);
|
||||
$this->processBelongsToHasOne($entity, $options);
|
||||
|
||||
return;
|
||||
|
||||
foreach ($entity->getRelationList() as $name) {
|
||||
$type = $entity->getRelationType($name);
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($name, 'entity');
|
||||
$foreignKey = $entity->getRelationParam($name, 'foreignKey');
|
||||
|
||||
/*if (in_array($type, $manyRelationTypeList)) {
|
||||
$idListAttribute = $name . 'Ids';
|
||||
$columnsAttribute = $name . 'Columns';
|
||||
|
||||
if ($entity->has($idListAttribute) || $entity->has($columnsAttribute)) {
|
||||
$this->processLinkMultipleFieldSave($entity, $name, $options);
|
||||
}
|
||||
|
||||
continue;
|
||||
}*/
|
||||
|
||||
if ($type === $entity::HAS_ONE) {
|
||||
/*if (!$foreignEntityType || !$foreignKey) {
|
||||
continue;
|
||||
}*/
|
||||
|
||||
/*$noSave = $this->metadata
|
||||
->get(['entityDefs', $entity->getEntityType(), 'fields', $name, 'noSave']);
|
||||
|
||||
if ($noSave) {
|
||||
continue;
|
||||
}*/
|
||||
|
||||
$idAttribute = $name . 'Id';
|
||||
|
||||
if (!$entity->has($idAttribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === $entity::BELONGS_TO) {
|
||||
if (!$entity->get($name . 'Id')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entity->isAttributeChanged($name . 'Id')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$foreignLink = $entity->getRelationParam($name, 'foreign');
|
||||
|
||||
if (
|
||||
$this->metadata->get(
|
||||
['entityDefs', $foreignEntityType, 'links', $foreignLink, 'type']
|
||||
) === $entity::HAS_ONE
|
||||
) {
|
||||
$anotherEntity = $this
|
||||
->select(['id'])
|
||||
->where([
|
||||
$name . 'Id' => $entity->get($name . 'Id'),
|
||||
'id!=' => $entity->id,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if ($anotherEntity) {
|
||||
$anotherEntity->set($name . 'Id', null);
|
||||
|
||||
$this->entityManager->saveEntity($anotherEntity, [
|
||||
'skipAll' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processMany(Entity $entity, array $options): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
foreach ($this->getManyRelationList($entityType) as $name) {
|
||||
$this->processManyItem($entity, $name, $options);
|
||||
}
|
||||
}
|
||||
|
||||
private function processManyItem(Entity $entity, string $name, array $options): void
|
||||
{
|
||||
$idListAttribute = $name . 'Ids';
|
||||
$columnsAttribute = $name . 'Columns';
|
||||
|
||||
if (!$entity->has($idListAttribute) && !$entity->has($columnsAttribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->linkMultipleSaveProcessor->process($entity, $name, $options);
|
||||
}
|
||||
|
||||
private function processHasOne(Entity $entity, array $options): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
foreach ($this->getHasOneRelationList($entityType) as $name) {
|
||||
$this->processHasOneItem($entity, $name);
|
||||
}
|
||||
}
|
||||
|
||||
private function processHasOneItem(Entity $entity, string $name): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$idAttribute = $name . 'Id';
|
||||
|
||||
if (!$entity->has($idAttribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = $entity->get($idAttribute);
|
||||
|
||||
$defs = $this->entityManager->getDefs()->getEntity($entityType);
|
||||
|
||||
$relationDefs = $defs->getRelation($name);
|
||||
|
||||
$foreignKey = $relationDefs->getForeignKey();
|
||||
$foreignEntityType = $relationDefs->getForeignEntityType();
|
||||
|
||||
$previousForeignEntity = $this->entityManager
|
||||
->getRDBRepository($foreignEntityType)
|
||||
->select(['id'])
|
||||
->where([
|
||||
$foreignKey => $entity->getId(),
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if ($previousForeignEntity) {
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($idAttribute, $previousForeignEntity->getId());
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
$previousForeignEntity->set($foreignKey, null);
|
||||
|
||||
$this->entityManager->saveEntity($previousForeignEntity, [
|
||||
'skipAll' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
else if (!$entity->isNew()) {
|
||||
$entity->setFetched($idAttribute, null);
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
$this->entityManager
|
||||
->getRDBRepository($entityType)
|
||||
->getRelation($entity, $name)
|
||||
->relateById($id);
|
||||
}
|
||||
}
|
||||
|
||||
private function processBelongsToHasOne(Entity $entity, array $options): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
foreach ($this->getBelongsToHasOneRelationList($entityType) as $name) {
|
||||
$this->processBelongsToHasOneItem($entity, $name);
|
||||
}
|
||||
}
|
||||
|
||||
private function processBelongsToHasOneItem(Entity $entity, string $name): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$idAttribute = $name . 'Id';
|
||||
|
||||
if (!$entity->get($idAttribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entity->isAttributeChanged($idAttribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$anotherEntity = $this->entityManager
|
||||
->getRDBRepository($entityType)
|
||||
->select(['id'])
|
||||
->where([
|
||||
$idAttribute => $entity->get($idAttribute),
|
||||
'id!=' => $entity->getId(),
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if (!$anotherEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
$anotherEntity->set($idAttribute, null);
|
||||
|
||||
$this->entityManager->saveEntity($anotherEntity, [
|
||||
'skipAll' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getManyRelationList(string $entityType): array
|
||||
{
|
||||
if (array_key_exists($entityType, $this->manyRelationListMapCache)) {
|
||||
return $this->manyRelationListMapCache[$entityType];
|
||||
}
|
||||
|
||||
$typeList = [
|
||||
Entity::HAS_MANY,
|
||||
Entity::MANY_MANY,
|
||||
Entity::HAS_CHILDREN,
|
||||
];
|
||||
|
||||
$defs = $this->entityManager->getDefs()->getEntity($entityType);
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($defs->getRelationNameList() as $name) {
|
||||
$type = $defs->getRelation($name)->getType();
|
||||
|
||||
if (!in_array($type, $typeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$defs->hasAttribute($name . 'Ids')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($defs->hasField($name) && $defs->getField($name)->getParam('noSave')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $name;
|
||||
}
|
||||
|
||||
$this->manyRelationListMapCache[$entityType] = $list;
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
private function getHasOneRelationList(string $entityType): array
|
||||
{
|
||||
if (array_key_exists($entityType, $this->hasOneRelationListMapCache)) {
|
||||
return $this->hasOneRelationListMapCache[$entityType];
|
||||
}
|
||||
|
||||
$ormDefs = $this->entityManager->getDefs();
|
||||
|
||||
$defs = $ormDefs->getEntity($entityType);
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($defs->getRelationNameList() as $name) {
|
||||
$relationDefs = $defs->getRelation($name);
|
||||
|
||||
$type = $relationDefs->getType();
|
||||
|
||||
if ($type !== Entity::HAS_ONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$relationDefs->hasForeignEntityType()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$relationDefs->hasForeignKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$defs->hasAttribute($name . 'Id')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($defs->hasField($name) && $defs->getField($name)->getParam('noSave')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $name;
|
||||
}
|
||||
|
||||
$this->hasOneRelationListMapCache[$entityType] = $list;
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
private function getBelongsToHasOneRelationList(string $entityType): array
|
||||
{
|
||||
if (array_key_exists($entityType, $this->belongsToHasOneRelationListMapCache)) {
|
||||
return $this->belongsToHasOneRelationListMapCache[$entityType];
|
||||
}
|
||||
|
||||
$ormDefs = $this->entityManager->getDefs();
|
||||
|
||||
$defs = $ormDefs->getEntity($entityType);
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($defs->getRelationNameList() as $name) {
|
||||
$relationDefs = $defs->getRelation($name);
|
||||
|
||||
$type = $relationDefs->getType();
|
||||
|
||||
if ($type !== Entity::BELONGS_TO) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$relationDefs->hasForeignRelationName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$relationDefs->hasForeignEntityType()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$defs->hasAttribute($name . 'Id')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$foreignEntityType = $relationDefs->getForeignEntityType();
|
||||
$foreignRelationName = $relationDefs->getForeignRelationName();
|
||||
|
||||
$foreignType = $ormDefs
|
||||
->getEntity($foreignEntityType)
|
||||
->getRelation($foreignRelationName)
|
||||
->getType();
|
||||
|
||||
if ($foreignType !== Entity::HAS_ONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $name;
|
||||
}
|
||||
|
||||
$this->belongsToHasOneRelationListMapCache[$entityType] = $list;
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ use Espo\Core\ORM\Entity;
|
||||
use Espo\Core\{
|
||||
FieldProcessing\EmailAddress\SaveProcessor as EmailAddressSaveProcessor,
|
||||
FieldProcessing\PhoneNumber\SaveProcessor as PhoneNumberSaveProcessor,
|
||||
FieldProcessing\Relation\SaveProcessor as RelationSaveProcessor,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -45,17 +46,22 @@ class SaveProcessor
|
||||
|
||||
private $phoneNumberSaveProcessor;
|
||||
|
||||
private $relationSaveProcessor;
|
||||
|
||||
public function __construct(
|
||||
EmailAddressSaveProcessor $emailAddressSaveProcessor,
|
||||
PhoneNumberSaveProcessor $phoneNumberSaveProcessor
|
||||
PhoneNumberSaveProcessor $phoneNumberSaveProcessor,
|
||||
RelationSaveProcessor $relationSaveProcessor
|
||||
) {
|
||||
$this->emailAddressSaveProcessor = $emailAddressSaveProcessor;
|
||||
$this->phoneNumberSaveProcessor = $phoneNumberSaveProcessor;
|
||||
$this->relationSaveProcessor = $relationSaveProcessor;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, array $options): void
|
||||
{
|
||||
$this->emailAddressSaveProcessor->process($entity, $options);
|
||||
$this->phoneNumberSaveProcessor->process($entity, $options);
|
||||
$this->relationSaveProcessor->process($entity, $options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,11 @@ class Database extends RDBRepository
|
||||
];
|
||||
|
||||
$this->hookManager->process(
|
||||
$this->entityType, 'afterMassRelate', $entity, $options, $hookData
|
||||
$this->entityType,
|
||||
'afterMassRelate',
|
||||
$entity,
|
||||
$options,
|
||||
$hookData
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -151,11 +155,14 @@ class Database extends RDBRepository
|
||||
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
|
||||
if (is_string($foreign)) {
|
||||
$foreignId = $foreign;
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($relationName, 'entity');
|
||||
|
||||
if ($foreignEntityType) {
|
||||
$foreign = $this->getEntityManager()->getEntity($foreignEntityType);
|
||||
$foreign->id = $foreignId;
|
||||
|
||||
$foreign->set('id', $foreignId);
|
||||
|
||||
$foreign->setAsFetched();
|
||||
}
|
||||
}
|
||||
@@ -213,7 +220,6 @@ class Database extends RDBRepository
|
||||
parent::afterSave($entity, $options);
|
||||
|
||||
if (!$this->processFieldsAfterSaveDisabled) {
|
||||
$this->processSpecifiedRelationsSave($entity, $options);
|
||||
$this->processFileFieldsSave($entity);
|
||||
$this->processArrayFieldsSave($entity);
|
||||
$this->processWysiwygFieldsSave($entity);
|
||||
@@ -227,6 +233,7 @@ class Database extends RDBRepository
|
||||
public function save(Entity $entity, array $options = []): void
|
||||
{
|
||||
$nowString = date('Y-m-d H:i:s', time());
|
||||
|
||||
$restoreData = [];
|
||||
|
||||
if (
|
||||
@@ -262,7 +269,8 @@ class Database extends RDBRepository
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (empty($options['silent']) && empty($options['skipModifiedBy'])) {
|
||||
if ($entity->hasAttribute('modifiedAt')) {
|
||||
$entity->set('modifiedAt', $nowString);
|
||||
@@ -427,275 +435,4 @@ class Database extends RDBRepository
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function processLinkMultipleFieldSave(Entity $entity, string $link, array $options = [])
|
||||
{
|
||||
$name = $link;
|
||||
|
||||
$idListAttribute = $link . 'Ids';
|
||||
$columnsAttribute = $link . 'Columns';
|
||||
|
||||
if ($this->getMetadata()->get("entityDefs." . $entity->getEntityType() . ".fields.{$name}.noSave")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$skipCreate = false;
|
||||
$skipRemove = false;
|
||||
$skipUpdate = false;
|
||||
|
||||
if (!empty($options['skipLinkMultipleCreate'])) {
|
||||
$skipCreate = true;
|
||||
}
|
||||
|
||||
if (!empty($options['skipLinkMultipleRemove'])) {
|
||||
$skipRemove = true;
|
||||
}
|
||||
|
||||
if (!empty($options['skipLinkMultipleUpdate'])) {
|
||||
$skipUpdate = true;
|
||||
}
|
||||
|
||||
if ($entity->isNew()) {
|
||||
$skipRemove = true;
|
||||
$skipUpdate = true;
|
||||
}
|
||||
|
||||
if ($entity->has($idListAttribute)) {
|
||||
$specifiedIdList = $entity->get($idListAttribute);
|
||||
}
|
||||
else if ($entity->has($columnsAttribute)) {
|
||||
$skipRemove = true;
|
||||
$specifiedIdList = [];
|
||||
|
||||
foreach ($entity->get($columnsAttribute) as $id => $d) {
|
||||
$specifiedIdList[] = $id;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_array($specifiedIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$toRemoveIdList = [];
|
||||
$existingIdList = [];
|
||||
$toUpdateIdList = [];
|
||||
$toCreateIdList = [];
|
||||
$existingColumnsData = (object) [];
|
||||
|
||||
$columns = $this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $name, 'columns']);
|
||||
|
||||
if (!empty($columns)) {
|
||||
$columnData = $entity->get($columnsAttribute);
|
||||
}
|
||||
|
||||
if (!$skipRemove || !$skipUpdate) {
|
||||
$foreignEntityList = $this->getRelation($entity, $name)->find();
|
||||
|
||||
if ($foreignEntityList) {
|
||||
foreach ($foreignEntityList as $foreignEntity) {
|
||||
$existingIdList[] = $foreignEntity->id;
|
||||
|
||||
if (!empty($columns)) {
|
||||
$data = (object) [];
|
||||
|
||||
foreach ($columns as $columnName => $columnField) {
|
||||
$foreignId = $foreignEntity->id;
|
||||
$data->$columnName = $foreignEntity->get($columnField);
|
||||
}
|
||||
|
||||
$existingColumnsData->$foreignId = $data;
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($columnsAttribute, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
if ($entity->has($idListAttribute) && !$entity->hasFetched($idListAttribute)) {
|
||||
$entity->setFetched($idListAttribute, $existingIdList);
|
||||
}
|
||||
|
||||
if ($entity->has($columnsAttribute) && !empty($columns)) {
|
||||
$entity->setFetched($columnsAttribute, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($existingIdList as $id) {
|
||||
if (!in_array($id, $specifiedIdList)) {
|
||||
if (!$skipRemove) {
|
||||
$toRemoveIdList[] = $id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!$skipUpdate && !empty($columns)) {
|
||||
foreach ($columns as $columnName => $columnField) {
|
||||
if (isset($columnData->$id) && is_object($columnData->$id)) {
|
||||
if (
|
||||
property_exists($columnData->$id, $columnName)
|
||||
&&
|
||||
(
|
||||
!property_exists($existingColumnsData->$id, $columnName)
|
||||
||
|
||||
$columnData->$id->$columnName !== $existingColumnsData->$id->$columnName
|
||||
)
|
||||
) {
|
||||
$toUpdateIdList[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$skipCreate) {
|
||||
foreach ($specifiedIdList as $id) {
|
||||
if (!in_array($id, $existingIdList)) {
|
||||
$toCreateIdList[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toCreateIdList as $id) {
|
||||
$data = null;
|
||||
|
||||
if (!empty($columns) && isset($columnData->$id)) {
|
||||
$data = (array) $columnData->$id;
|
||||
}
|
||||
|
||||
$this->getRelation($entity, $name)->relateById($id, $data);
|
||||
}
|
||||
|
||||
foreach ($toRemoveIdList as $id) {
|
||||
$this->getRelation($entity, $name)->unrelateById($id);
|
||||
}
|
||||
|
||||
foreach ($toUpdateIdList as $id) {
|
||||
$data = (array) $columnData->$id;
|
||||
|
||||
$this->getRelation($entity, $name)->updateColumnsById($id, (array) $data);
|
||||
}
|
||||
}
|
||||
|
||||
protected function processSpecifiedRelationsSave(Entity $entity, array $options = [])
|
||||
{
|
||||
$relationTypeList = [
|
||||
$entity::HAS_MANY,
|
||||
$entity::MANY_MANY,
|
||||
$entity::HAS_CHILDREN,
|
||||
];
|
||||
|
||||
foreach ($entity->getRelationList() as $name) {
|
||||
|
||||
$type = $entity->getRelationType($name);
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($name, 'entity');
|
||||
$foreignKey = $entity->getRelationParam($name, 'foreignKey');
|
||||
|
||||
if (in_array($type, $relationTypeList)) {
|
||||
$idListAttribute = $name . 'Ids';
|
||||
$columnsAttribute = $name . 'Columns';
|
||||
|
||||
if ($entity->has($idListAttribute) || $entity->has($columnsAttribute)) {
|
||||
$this->processLinkMultipleFieldSave($entity, $name, $options);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === $entity::HAS_ONE) {
|
||||
if (!$foreignEntityType || !$foreignKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$noSave = $this->getMetadata()->get(
|
||||
['entityDefs', $entity->getEntityType(), 'fields', $name, 'noSave']
|
||||
);
|
||||
|
||||
if ($noSave) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$idAttribute = $name . 'Id';
|
||||
|
||||
if (!$entity->has($idAttribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$where = [];
|
||||
|
||||
$where[$foreignKey] = $entity->id;
|
||||
|
||||
$previousForeignEntity = $this->getEntityManager()
|
||||
->getRepository($foreignEntityType)
|
||||
->select(['id'])
|
||||
->where($where)
|
||||
->findOne();
|
||||
|
||||
if ($previousForeignEntity) {
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($idAttribute, $previousForeignEntity->id);
|
||||
}
|
||||
|
||||
if (!$entity->get($idAttribute)) {
|
||||
$previousForeignEntity->set($foreignKey, null);
|
||||
|
||||
$this->getEntityManager()->saveEntity($previousForeignEntity, ['skipAll' => true]);
|
||||
}
|
||||
} else {
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($idAttribute, null);
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->get($idAttribute)) {
|
||||
$relateResult = $this->relate($entity, $name, $entity->get($idAttribute));
|
||||
|
||||
if (!$relateResult) {
|
||||
$entity->set($idAttribute, null);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === $entity::BELONGS_TO) {
|
||||
if (!$entity->get($name . 'Id')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entity->isAttributeChanged($name . 'Id')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$foreignLink = $entity->getRelationParam($name, 'foreign');
|
||||
|
||||
if (
|
||||
$this->getMetadata()->get(
|
||||
['entityDefs', $foreignEntityType, 'links', $foreignLink, 'type']
|
||||
) === $entity::HAS_ONE
|
||||
) {
|
||||
$anotherEntity = $this
|
||||
->select(['id'])
|
||||
->where([
|
||||
$name . 'Id' => $entity->get($name . 'Id'),
|
||||
'id!=' => $entity->id,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if ($anotherEntity) {
|
||||
$anotherEntity->set($name . 'Id', null);
|
||||
|
||||
$this->getEntityManager()->saveEntity($anotherEntity, ['skipAll' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
<?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 tests\integration\Espo\Core\FieldProcessing;
|
||||
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
|
||||
class RelationTest extends \tests\integration\Core\BaseTestCase
|
||||
{
|
||||
public function testLinkMultiple1(): void
|
||||
{
|
||||
/* @var $entityManager EntityManager */
|
||||
$entityManager = $this->getContainer()->get('entityManager');
|
||||
|
||||
$contact1 = $entityManager->createEntity('Contact', []);
|
||||
$contact2 = $entityManager->createEntity('Contact', []);
|
||||
$contact3 = $entityManager->createEntity('Contact', []);
|
||||
|
||||
$opportunity = $entityManager->createEntity('Opportunity', [
|
||||
'contactsIds' => [
|
||||
$contact1->getId(),
|
||||
$contact2->getId(),
|
||||
],
|
||||
'contactsColumns' => (object) [
|
||||
$contact1->getId() => (object) [
|
||||
'role' => 'Decision Maker',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$opportunity = $entityManager->getEntity('Opportunity', $opportunity->getId());
|
||||
|
||||
$this->assertEquals(
|
||||
self::sortArray([
|
||||
$contact1->getId(),
|
||||
$contact2->getId(),
|
||||
]),
|
||||
self::sortArray($opportunity->getLinkMultipleIdList('contacts'))
|
||||
);
|
||||
|
||||
$column1 = $entityManager
|
||||
->getRDBRepository('Opportunity')
|
||||
->getRelation($opportunity, 'contacts')
|
||||
->getColumn($contact1, 'role');
|
||||
|
||||
$this->assertEquals('Decision Maker', $column1);
|
||||
|
||||
$opportunity->set([
|
||||
'contactsIds' => [
|
||||
$contact2->getId(),
|
||||
$contact3->getId(),
|
||||
],
|
||||
'contactsColumns' => (object) [
|
||||
$contact2->getId() => (object) [
|
||||
'role' => null,
|
||||
],
|
||||
$contact3->getId() => (object) [
|
||||
'role' => 'Evaluator',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$entityManager->saveEntity($opportunity);
|
||||
|
||||
$opportunity = $entityManager->getEntity('Opportunity', $opportunity->getId());
|
||||
|
||||
$this->assertEquals(
|
||||
self::sortArray([
|
||||
$contact2->getId(),
|
||||
$contact3->getId(),
|
||||
]),
|
||||
self::sortArray($opportunity->getLinkMultipleIdList('contacts'))
|
||||
);
|
||||
|
||||
$column3 = $entityManager
|
||||
->getRDBRepository('Opportunity')
|
||||
->getRelation($opportunity, 'contacts')
|
||||
->getColumn($contact3, 'role');
|
||||
|
||||
$this->assertEquals('Evaluator', $column3);
|
||||
|
||||
$opportunity = $entityManager->getEntity('Opportunity', $opportunity->getId());
|
||||
|
||||
$opportunity->set([
|
||||
'contactsColumns' => (object) [
|
||||
$contact3->getId() => (object) [
|
||||
'role' => 'Decision Maker',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$entityManager->saveEntity($opportunity);
|
||||
|
||||
$column3 = $entityManager
|
||||
->getRDBRepository('Opportunity')
|
||||
->getRelation($opportunity, 'contacts')
|
||||
->getColumn($contact3, 'role');
|
||||
|
||||
$this->assertEquals('Decision Maker', $column3);
|
||||
}
|
||||
|
||||
public function testHasOne(): void
|
||||
{
|
||||
/* @var $entityManager EntityManager */
|
||||
$entityManager = $this->getContainer()->get('entityManager');
|
||||
|
||||
$lead1 = $entityManager->createEntity('Lead', []);
|
||||
$lead2 = $entityManager->createEntity('Lead', []);
|
||||
|
||||
$account = $entityManager->createEntity('Account', []);
|
||||
|
||||
$lead1->set('createdAccountId', $account->getId());
|
||||
|
||||
$entityManager->saveEntity($lead1);
|
||||
|
||||
$lead2->set('createdAccountId', $account->getId());
|
||||
|
||||
$entityManager->saveEntity($lead2);
|
||||
|
||||
$lead1 = $entityManager->getEntity('Lead', $lead1->getId());
|
||||
|
||||
$this->assertNull($lead1->get('createdAccountId'));
|
||||
|
||||
$lead2 = $entityManager->getEntity('Lead', $lead2->getId());
|
||||
|
||||
$this->assertEquals($account->getId(), $lead2->get('createdAccountId'));
|
||||
}
|
||||
|
||||
public function testBelongsToHasOne(): void
|
||||
{
|
||||
/* @var $entityManager EntityManager */
|
||||
$entityManager = $this->getContainer()->get('entityManager');
|
||||
|
||||
$lead1 = $entityManager->createEntity('Lead', []);
|
||||
$lead2 = $entityManager->createEntity('Lead', []);
|
||||
|
||||
$account = $entityManager->createEntity('Account', [
|
||||
'originalLeadId' => $lead1->getId(),
|
||||
]);
|
||||
|
||||
$entityManager->saveEntity($account);
|
||||
|
||||
$account = $entityManager->getEntity('Account', $account->getId());
|
||||
|
||||
$account->set([
|
||||
'originalLeadId' => $lead2->getId(),
|
||||
]);
|
||||
|
||||
$entityManager->saveEntity($account);
|
||||
|
||||
$lead2 = $entityManager->getEntity('Lead', $lead2->getId());
|
||||
|
||||
$this->assertEquals($account->getId(), $lead2->get('createdAccountId'));
|
||||
|
||||
$lead1 = $entityManager->getEntity('Lead', $lead1->getId());
|
||||
|
||||
$this->assertEquals(null, $lead1->get('createdAccountId'));
|
||||
}
|
||||
|
||||
private static function sortArray(array $array): array
|
||||
{
|
||||
sort($array);
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user