From 338e0bb9d4d807fe8e22e520c169dae0fc9c8cec Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 26 Jun 2024 13:27:20 +0300 Subject: [PATCH] deleteId param --- application/Espo/Core/Record/Service.php | 17 +++++- .../AdditionalBuilder/DeleteIdField.php | 61 +++++++++++++++++++ .../Espo/Hooks/{User => Common}/DeleteId.php | 43 ++++++++++--- .../Espo/Resources/metadata/app/metadata.json | 3 +- .../Resources/metadata/entityDefs/User.json | 12 +--- application/Espo/Services/User.php | 8 --- .../Espo/Tools/FieldManager/FieldManager.php | 1 + schema/metadata/entityDefs.json | 6 +- 8 files changed, 119 insertions(+), 32 deletions(-) create mode 100644 application/Espo/Core/Utils/Metadata/AdditionalBuilder/DeleteIdField.php rename application/Espo/Hooks/{User => Common}/DeleteId.php (64%) diff --git a/application/Espo/Core/Record/Service.php b/application/Espo/Core/Record/Service.php index 56fa1d9c99..7be6444ce4 100644 --- a/application/Espo/Core/Record/Service.php +++ b/application/Espo/Core/Record/Service.php @@ -1115,10 +1115,23 @@ class Service implements Crud, } if (!$entity->get('deleted')) { - throw new Forbidden(); + throw new Forbidden("No 'deleted' attribute."); } - $this->getRepository()->restoreDeleted($entity->getId()); + $this->entityManager->getTransactionManager() + ->run(function () use ($entity) { + $this->getRepository()->restoreDeleted($entity->getId()); + + if ( + $entity->hasAttribute('deleteId') && + $this->metadata->get("entityDefs.$this->entityType.deleteId") + ) { + $this->entityManager->refreshEntity($entity); + + $entity->set('deleteId', '0'); + $this->getRepository()->save($entity, [SaveOption::SILENT => true]); + } + }); } public function getMaxSelectTextAttributeLength(): ?int diff --git a/application/Espo/Core/Utils/Metadata/AdditionalBuilder/DeleteIdField.php b/application/Espo/Core/Utils/Metadata/AdditionalBuilder/DeleteIdField.php new file mode 100644 index 0000000000..81f51fbc0a --- /dev/null +++ b/application/Espo/Core/Utils/Metadata/AdditionalBuilder/DeleteIdField.php @@ -0,0 +1,61 @@ +. + * + * 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 Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Utils\Metadata\AdditionalBuilder; + +use Espo\Core\Utils\Metadata\AdditionalBuilder; +use stdClass; + +class DeleteIdField implements AdditionalBuilder +{ + public function build(stdClass $data): void + { + if (!isset($data->entityDefs)) { + return; + } + + foreach (get_object_vars($data->entityDefs) as $entityType => $entityDefsItem) { + if (!($entityDefsItem->deleteId ?? false)) { + continue; + } + + $entityDefsItem->fields ??= (object) []; + + $data->entityDefs->$entityType->fields->deleteId = (object) [ + "type" => "varchar", + "maxLength" => 17, + "readOnly" => true, + "notNull" => true, + "default" => "0", + "utility" => true, + "customizationDisabled" => true + ]; + } + } +} diff --git a/application/Espo/Hooks/User/DeleteId.php b/application/Espo/Hooks/Common/DeleteId.php similarity index 64% rename from application/Espo/Hooks/User/DeleteId.php rename to application/Espo/Hooks/Common/DeleteId.php index d187419d75..7e81a24157 100644 --- a/application/Espo/Hooks/User/DeleteId.php +++ b/application/Espo/Hooks/Common/DeleteId.php @@ -27,35 +27,58 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Hooks\User; +namespace Espo\Hooks\Common; use Espo\Core\Hook\Hook\BeforeRemove; -use Espo\Core\Hook\Hook\BeforeSave; +use Espo\Core\Utils\Metadata; use Espo\Core\Utils\Util; -use Espo\Entities\User; use Espo\ORM\Entity; use Espo\ORM\Repository\Option\RemoveOptions; use Espo\ORM\Repository\Option\SaveOptions; +use Espo\Core\Hook\Hook\BeforeSave; /** - * @implements BeforeRemove - * @implements BeforeSave + * Handles 'deleteId' on soft-deletes. + * + * @implements BeforeSave + * @implements BeforeRemove */ -class DeleteId implements BeforeRemove, BeforeSave +class DeleteId implements BeforeSave, BeforeRemove { + private const ID_ATTR = 'deleteId'; + private const DELETED_ATTR = 'deleted'; + + public function __construct( + private Metadata $metadata, + ) {} + public function beforeRemove(Entity $entity, RemoveOptions $options): void { - $entity->set('deleteId', Util::generateId()); + if (!$this->hasDeleteId($entity)) { + return; + } + + $entity->set(self::ID_ATTR, Util::generateId()); } public function beforeSave(Entity $entity, SaveOptions $options): void { - if (!$entity->isAttributeChanged('deleted')) { + if (!$this->hasDeleteId($entity)) { return; } - $deleteId = $entity->get('deleted') ? Util::generateId() : '0'; + if (!$entity->isAttributeChanged(self::DELETED_ATTR)) { + return; + } - $entity->set('deleteId', $deleteId); + $deleteId = $entity->get(self::DELETED_ATTR) ? Util::generateId() : '0'; + + $entity->set(self::ID_ATTR, $deleteId); + } + + private function hasDeleteId(Entity $entity): bool + { + return $entity->hasAttribute(self::DELETED_ATTR) && + $this->metadata->get("entityDefs.{$entity->getEntityType()}.deleteId"); } } diff --git a/application/Espo/Resources/metadata/app/metadata.json b/application/Espo/Resources/metadata/app/metadata.json index 51873c7f9b..36f0941df6 100644 --- a/application/Espo/Resources/metadata/app/metadata.json +++ b/application/Espo/Resources/metadata/app/metadata.json @@ -44,6 +44,7 @@ ["recordDefs"] ], "additionalBuilderClassNameList": [ - "Espo\\Core\\Utils\\Metadata\\AdditionalBuilder\\Fields" + "Espo\\Core\\Utils\\Metadata\\AdditionalBuilder\\Fields", + "Espo\\Core\\Utils\\Metadata\\AdditionalBuilder\\DeleteIdField" ] } diff --git a/application/Espo/Resources/metadata/entityDefs/User.json b/application/Espo/Resources/metadata/entityDefs/User.json index 9f0687ffcd..517181e4fc 100644 --- a/application/Espo/Resources/metadata/entityDefs/User.json +++ b/application/Espo/Resources/metadata/entityDefs/User.json @@ -484,15 +484,6 @@ "directAccessDisabled": true, "exportDisabled": true }, - "deleteId": { - "type": "varchar", - "maxLength": 17, - "readOnly": true, - "notNull": true, - "default": "0", - "utility": true, - "customizationDisabled": true - }, "emailAddressList": { "type": "array", "utility": true, @@ -636,5 +627,6 @@ "deleteId" ] } - } + }, + "deleteId": true } diff --git a/application/Espo/Services/User.php b/application/Espo/Services/User.php index d9433d79d0..0d99b9c9b8 100644 --- a/application/Espo/Services/User.php +++ b/application/Espo/Services/User.php @@ -257,14 +257,6 @@ class User extends Record } parent::restoreDeleted($id); - - $entity = $this->getRepository()->getById($id); - - if ($entity) { - $entity->set('deleteId', '0'); - - $this->getRepository()->save($entity); - } } private function createPasswordChecker(): PasswordChecker diff --git a/application/Espo/Tools/FieldManager/FieldManager.php b/application/Espo/Tools/FieldManager/FieldManager.php index 9955ef8edf..9a642ff59f 100644 --- a/application/Espo/Tools/FieldManager/FieldManager.php +++ b/application/Espo/Tools/FieldManager/FieldManager.php @@ -55,6 +55,7 @@ class FieldManager private $forbiddenFieldNameList = [ 'id', 'deleted', + 'deleteId', 'skipDuplicateCheck', 'isFollowed', 'isStarred', diff --git a/schema/metadata/entityDefs.json b/schema/metadata/entityDefs.json index 9cf117e133..5212c2805d 100644 --- a/schema/metadata/entityDefs.json +++ b/schema/metadata/entityDefs.json @@ -82,7 +82,11 @@ }, "noDeletedAttribute": { "type": "boolean", - "description": "Do not add the `deleted` attribute." + "description": "Do not add the `deleted` attribute. Soft-deletes will be impossible." + }, + "deleteId": { + "type": "boolean", + "description": "Adds `deletedId` attribute. An random ID is written there on soft-deletes. As of v8.4." }, "hooksDisabled": { "type": "boolean",