diff --git a/application/Espo/Core/Record/Hook/Provider.php b/application/Espo/Core/Record/Hook/Provider.php index 47aa50816f..8b4d113f15 100644 --- a/application/Espo/Core/Record/Hook/Provider.php +++ b/application/Espo/Core/Record/Hook/Provider.php @@ -42,10 +42,13 @@ class Provider /** @var array */ private $typeInterfaceListMap = [ - Type::BEFORE_CREATE => [CreateHook::class, SaveHook::class], Type::BEFORE_READ => [ReadHook::class], + Type::BEFORE_CREATE => [CreateHook::class, SaveHook::class], + Type::AFTER_CREATE => [CreateHook::class, SaveHook::class], Type::BEFORE_UPDATE => [UpdateHook::class, SaveHook::class], + Type::AFTER_UPDATE => [UpdateHook::class, SaveHook::class], Type::BEFORE_DELETE => [DeleteHook::class], + Type::AFTER_DELETE => [DeleteHook::class], Type::BEFORE_LINK => [LinkHook::class], Type::BEFORE_UNLINK => [UnlinkHook::class], ]; diff --git a/application/Espo/Core/Record/Hook/Type.php b/application/Espo/Core/Record/Hook/Type.php index 25ae3af11c..ec2e77714b 100644 --- a/application/Espo/Core/Record/Hook/Type.php +++ b/application/Espo/Core/Record/Hook/Type.php @@ -31,10 +31,16 @@ namespace Espo\Core\Record\Hook; class Type { - public const BEFORE_CREATE = 'beforeCreate'; public const BEFORE_READ = 'beforeRead'; + + public const BEFORE_CREATE = 'beforeCreate'; public const BEFORE_UPDATE = 'beforeUpdate'; public const BEFORE_DELETE = 'beforeDelete'; + + public const AFTER_CREATE = 'afterCreate'; + public const AFTER_UPDATE = 'afterUpdate'; + public const AFTER_DELETE = 'afterDelete'; + public const BEFORE_LINK = 'beforeLink'; public const BEFORE_UNLINK = 'beforeUnlink'; } diff --git a/application/Espo/Core/Record/HookManager.php b/application/Espo/Core/Record/HookManager.php index 6a5e6a45ee..53c5e3a5f9 100644 --- a/application/Espo/Core/Record/HookManager.php +++ b/application/Espo/Core/Record/HookManager.php @@ -66,6 +66,24 @@ class HookManager } } + /** + * @throws BadRequest + * @throws Forbidden + * @throws Conflict + */ + public function processAfterCreate(Entity $entity, CreateParams $params): void + { + foreach ($this->getAfterCreateHookList($entity->getEntityType()) as $hook) { + if ($hook instanceof SaveHook) { + $hook->process($entity); + + continue; + } + + $hook->process($entity, $params); + } + } + public function processBeforeRead(Entity $entity, ReadParams $params): void { foreach ($this->getBeforeReadHookList($entity->getEntityType()) as $hook) { @@ -91,6 +109,24 @@ class HookManager } } + /** + * @throws BadRequest + * @throws Forbidden + * @throws Conflict + */ + public function processAfterUpdate(Entity $entity, UpdateParams $params): void + { + foreach ($this->getAfterUpdateHookList($entity->getEntityType()) as $hook) { + if ($hook instanceof SaveHook) { + $hook->process($entity); + + continue; + } + + $hook->process($entity, $params); + } + } + /** * @throws BadRequest * @throws Forbidden @@ -103,6 +139,18 @@ class HookManager } } + /** + * @throws BadRequest + * @throws Forbidden + * @throws Conflict + */ + public function processAfterDelete(Entity $entity, DeleteParams $params): void + { + foreach ($this->getAfterDeleteHookList($entity->getEntityType()) as $hook) { + $hook->process($entity, $params); + } + } + public function processBeforeLink(Entity $entity, string $link, Entity $foreignEntity): void { foreach ($this->getBeforeLinkHookList($entity->getEntityType()) as $hook) { @@ -131,19 +179,37 @@ class HookManager */ private function getBeforeCreateHookList(string $entityType): array { - /** @var CreateHook[] */ + /** @var (CreateHook|SaveHook)[] */ return $this->provider->getList($entityType, Type::BEFORE_CREATE); } + /** + * @return (CreateHook|SaveHook)[] + */ + private function getAfterCreateHookList(string $entityType): array + { + /** @var (CreateHook|SaveHook)[] */ + return $this->provider->getList($entityType, Type::AFTER_CREATE); + } + /** * @return (UpdateHook|SaveHook)[] */ private function getBeforeUpdateHookList(string $entityType): array { - /** @var UpdateHook[] */ + /** @var (UpdateHook|SaveHook)[] */ return $this->provider->getList($entityType, Type::BEFORE_UPDATE); } + /** + * @return (UpdateHook|SaveHook)[] + */ + private function getAfterUpdateHookList(string $entityType): array + { + /** @var (UpdateHook|SaveHook)[] */ + return $this->provider->getList($entityType, Type::AFTER_UPDATE); + } + /** * @return DeleteHook[] */ @@ -153,6 +219,15 @@ class HookManager return $this->provider->getList($entityType, Type::BEFORE_DELETE); } + /** + * @return DeleteHook[] + */ + private function getAfterDeleteHookList(string $entityType): array + { + /** @var DeleteHook[] */ + return $this->provider->getList($entityType, Type::AFTER_DELETE); + } + /** * @return LinkHook[] */ diff --git a/application/Espo/Core/Record/Service.php b/application/Espo/Core/Record/Service.php index a138023507..10a5a7073a 100644 --- a/application/Espo/Core/Record/Service.php +++ b/application/Espo/Core/Record/Service.php @@ -763,7 +763,6 @@ class Service implements Crud, $this->processApiBeforeCreateApiScript($entity, $params); $this->recordHookManager->processBeforeCreate($entity, $params); - $this->beforeCreateEntity($entity, $data); $this->entityManager->saveEntity($entity, [SaveOption::API => true]); @@ -771,6 +770,7 @@ class Service implements Crud, $this->afterCreateEntity($entity, $data); $this->afterCreateProcessDuplicating($entity, $params); $this->loadAdditionalFields($entity); + $this->recordHookManager->processAfterCreate($entity, $params); $this->prepareEntityForOutput($entity); $this->processActionHistoryRecord(Action::CREATE, $entity); @@ -846,6 +846,8 @@ class Service implements Crud, $this->loadAdditionalFields($entity); } + $this->recordHookManager->processAfterUpdate($entity, $params); + $this->prepareEntityForOutput($entity); $this->processActionHistoryRecord(Action::UPDATE, $entity); @@ -882,8 +884,11 @@ class Service implements Crud, $this->recordHookManager->processBeforeDelete($entity, $params); $this->beforeDeleteEntity($entity); + $this->getRepository()->remove($entity); + $this->afterDeleteEntity($entity); + $this->recordHookManager->processAfterDelete($entity, $params); $this->processActionHistoryRecord(Action::DELETE, $entity); } diff --git a/schema/metadata/recordDefs.json b/schema/metadata/recordDefs.json index a32a912215..7c07dd13e6 100644 --- a/schema/metadata/recordDefs.json +++ b/schema/metadata/recordDefs.json @@ -144,6 +144,13 @@ "type": "string" } }, + "afterCreateHookClassNameList": { + "description": "After-create hooks. Should implement the Espo\\Core\\Record\\Hook\\CreateHook or Espo\\Core\\Record\\Hook\\SaveHook interface. As of v8.2.", + "type": "array", + "items": { + "type": "string" + } + }, "beforeUpdateHookClassNameList": { "description": "Before-update hooks. Should implement the Espo\\Core\\Record\\Hook\\UpdateHook or Espo\\Core\\Record\\Hook\\SaveHook interface.", "type": "array", @@ -151,6 +158,13 @@ "type": "string" } }, + "afterUpdateHookClassNameList": { + "description": "Before-update hooks. Should implement the Espo\\Core\\Record\\Hook\\UpdateHook or Espo\\Core\\Record\\Hook\\SaveHook interface. As of v8.2.", + "type": "array", + "items": { + "type": "string" + } + }, "beforeDeleteHookClassNameList": { "description": "Before-delete hooks. Should implement the Espo\\Core\\Record\\Hook\\DeleteHook interface.", "type": "array", @@ -158,6 +172,13 @@ "type": "string" } }, + "afterDeleteHookClassNameList": { + "description": "Before-delete hooks. Should implement the Espo\\Core\\Record\\Hook\\DeleteHook interface. As of v8.2.", + "type": "array", + "items": { + "type": "string" + } + }, "beforeLinkHookClassNameList": { "description": "Before-link hooks. Should implement the Espo\\Core\\Record\\Hook\\LinkHook interface.", "type": "array",