Initial commit

This commit is contained in:
root
2026-01-19 17:44:46 +01:00
commit 823af8b11d
8721 changed files with 1130846 additions and 0 deletions

View File

@@ -0,0 +1,214 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnFlowchart;
use Espo\Modules\Advanced\Entities\BpmnFlowchart;
use Espo\Modules\Advanced\Entities\Workflow;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class CreateWorkflows
{
public function __construct(private EntityManager $entityManager)
{}
/**
* @param BpmnFlowchart $entity
*/
public function afterSave(Entity $entity): void
{
$workflowList = $this->entityManager
->getRDBRepositoryByClass(Workflow::class)
->where([
'flowchartId' => $entity->getId(),
'isInternal' => true,
])
->find();
$workflowsToRecreate = false;
if (
!$entity->isNew() &&
json_encode($entity->get('data')) !== json_encode($entity->getFetched('data'))
) {
$workflowsToRecreate = true;
}
if ($entity->isNew() || $workflowsToRecreate) {
$this->removeWorkflows($entity);
$data = $entity->get('data');
if (isset($data->list) && is_array($data->list)) {
foreach ($data->list as $item) {
if (!is_object($item)) {
continue;
}
$itId = $item->id ?? null;
$itType = $item->type ?? null;
$itTriggerType = $item->triggerType ?? null;
$itSignal = $item->signal ?? null;
if (
$itType === 'eventStartConditional' &&
in_array($itTriggerType, [
'afterRecordCreated',
'afterRecordSaved',
'afterRecordUpdated',
])
) {
$workflow = $this->entityManager->getNewEntity(Workflow::ENTITY_TYPE);
$conditionsAll = [];
if (isset($item->conditionsAll)) {
$conditionsAll = $item->conditionsAll;
}
$conditionsAny = [];
if (isset($item->conditionsAny)) {
$conditionsAny = $item->conditionsAny;
}
$conditionsFormula = null;
if (isset($item->conditionsFormula)) {
$conditionsFormula = $item->conditionsFormula;
}
$workflow->set([
'type' => $itTriggerType,
'entityType' => $entity->get('targetType'),
'isInternal' => true,
'flowchartId' => $entity->getId(),
'isActive' => $entity->get('isActive'),
'conditionsAll' => $conditionsAll,
'conditionsAny' => $conditionsAny,
'conditionsFormula' => $conditionsFormula,
'actions' => [
(object) [
'type' => 'startBpmnProcess',
'flowchartId' => $entity->getId(),
'elementId' => $itId,
'cid' => 0,
]
],
'processOrder' => 100,
]);
$this->entityManager->saveEntity($workflow);
}
if (
$itType === 'eventStartSignal' &&
$itSignal
) {
$workflow = $this->entityManager->getNewEntity(Workflow::ENTITY_TYPE);
$workflow->set([
'type' => Workflow::TYPE_SIGNAL,
'signalName' => $itSignal,
'entityType' => $entity->get('targetType'),
'isInternal' => true,
'flowchartId' => $entity->get('id'),
'isActive' => $entity->get('isActive'),
'actions' => [
(object) [
'type' => 'startBpmnProcess',
'flowchartId' => $entity->getId(),
'elementId' => $itId,
'cid' => 0,
]
],
'processOrder' => 150,
]);
$this->entityManager->saveEntity($workflow);
}
if (
$itType === 'eventStartTimer' &&
!empty($item->targetReportId) &&
!empty($item->scheduling)
) {
$workflow = $this->entityManager->getNewEntity(Workflow::ENTITY_TYPE);
$workflow->set([
'type' => Workflow::TYPE_SCHEDULED,
'entityType' => $entity->get('targetType'),
'isInternal' => true,
'flowchartId' => $entity->getId(),
'isActive' => $entity->get('isActive'),
'scheduling' => $item->scheduling,
'schedulingApplyTimezone' => $item->schedulingApplyTimezone ?? false,
'targetReportId' => $item->targetReportId,
'targetReportName' => $item->targetReportId,
'actions' => [
(object) [
'type' => 'startBpmnProcess',
'flowchartId' => $entity->getId(),
'elementId' => $itId,
'cid' => 0,
]
],
'processOrder' => 100,
]);
$this->entityManager->saveEntity($workflow);
}
}
}
}
if (
$entity->isAttributeChanged('isActive') &&
!$entity->isNew() &&
!$workflowsToRecreate
) {
foreach ($workflowList as $workflow) {
if ($workflow->get('isActive') !== $entity->get('isActive')) {
$workflow->set('isActive', $entity->get('isActive'));
$this->entityManager->saveEntity($workflow);
}
}
}
}
private function removeWorkflows(Entity $entity): void
{
$workflowList = $this->entityManager
->getRDBRepository(Workflow::ENTITY_TYPE)
->where([
'flowchartId' => $entity->getId(),
'isInternal' => true,
])
->find();
foreach ($workflowList as $workflow) {
$this->entityManager->removeEntity($workflow);
$this->entityManager
->getRDBRepository(Workflow::ENTITY_TYPE)
->deleteFromDb($workflow->getId());
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnFlowchart;
use Espo\Modules\Advanced\Core\Bpmn\Utils\Helper;
use Espo\Modules\Advanced\Entities\BpmnFlowchart;
use Espo\ORM\Entity;
class Prepare
{
/**
* @param BpmnFlowchart $entity
*/
public function beforeSave(Entity $entity): void
{
$handleFlowchartData = false;
if (
$entity->isNew() ||
json_encode($entity->get('data')) !== json_encode($entity->getFetched('data'))
) {
$handleFlowchartData = true;
}
if (!$handleFlowchartData) {
return;
}
$data = $entity->get('data') ?? (object) [];
$eData = Helper::getElementsDataFromFlowchartData($data);
$elementsDataHash = $eData['elementsDataHash'];
$eventStartIdList = $eData['eventStartIdList'];
$eventStartAllIdList = $eData['eventStartAllIdList'];
$entity->set('elementsDataHash', $elementsDataHash);
$entity->set('hasNoneStartEvent', count($eventStartIdList) > 0);
$entity->set('eventStartIdList', $eventStartIdList);
$entity->set('eventStartAllIdList', $eventStartAllIdList);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnFlowchart;
use Espo\ORM\Entity;
use Espo\Modules\Advanced\Entities\WorkflowRoundRobin;
use Espo\ORM\EntityManager;
class RemoveRoundRobin
{
/** @var int */
public static $order = 9;
public function __construct(private EntityManager $entityManager)
{}
public function afterRemove(Entity $entity): void
{
$roundRobinList = $this->entityManager
->getRDBRepository(WorkflowRoundRobin::ENTITY_TYPE)
->where(['flowchartId' => $entity->getId()])
->find();
foreach ($roundRobinList as $item) {
$this->entityManager->removeEntity($item);
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnFlowchart;
use Espo\Modules\Advanced\Entities\Workflow;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class RemoveWorkflows
{
private EntityManager $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function afterRemove(Entity $entity): void
{
$workflowList = $this->entityManager
->getRDBRepository(Workflow::ENTITY_TYPE)
->where([
'flowchartId' => $entity->getId(),
'isInternal' => true,
])
->find();
foreach ($workflowList as $workflow) {
$this->entityManager->removeEntity($workflow);
$this->entityManager->getRDBRepository(Workflow::ENTITY_TYPE)
->deleteFromDb($workflow->getId());
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnProcess;
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class RejectFlowNodes
{
private EntityManager $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function afterRemove(Entity $entity): void
{
$flowNodeList = $this->entityManager
->getRDBRepositoryByClass(BpmnFlowNode::class)
->where([
'processId' => $entity->getId(),
'status!=' => [
BpmnFlowNode::STATUS_PROCESSED,
BpmnFlowNode::STATUS_REJECTED,
BpmnFlowNode::STATUS_FAILED,
],
])
->find();
foreach ($flowNodeList as $flowNode) {
$flowNode->setStatus(BpmnFlowNode::STATUS_REJECTED);
$this->entityManager->saveEntity($flowNode);
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnProcess;
use Espo\Modules\Advanced\Entities\BpmnProcess;
use Espo\ORM\Entity;
class RootProcess
{
/**
* @param BpmnProcess $entity
*/
public function beforeSave(Entity $entity): void
{
if (!$entity->isNew()) {
return;
}
if ($entity->getRootProcessId()) {
return;
}
$entity->set('rootProcessId', $entity->getId());
}
}

View File

@@ -0,0 +1,52 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnProcess;
use Espo\Core\Exceptions\Error;
use Espo\Core\InjectableFactory;
use Espo\Modules\Advanced\Core\Bpmn\BpmnManager;
use Espo\Modules\Advanced\Entities\BpmnProcess;
use Espo\ORM\Entity;
class StartProcess
{
public function __construct(
private InjectableFactory $injectableFactory
) {}
/**
* @param BpmnProcess $entity
* @param array<string, mixed> $options
* @throws Error
*/
public function afterSave(Entity $entity, array $options): void
{
if (!$entity->isNew()) {
return;
}
if (!empty($options['skipStartProcessFlow'])) {
return;
}
$manager = $this->injectableFactory->create(BpmnManager::class);
$manager->startCreatedProcess($entity);
}
}

View File

@@ -0,0 +1,69 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnProcess;
use Espo\Core\InjectableFactory;
use Espo\Modules\Advanced\Core\Bpmn\BpmnManager;
use Espo\Modules\Advanced\Entities\BpmnProcess;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class StopProcess
{
public function __construct(
private InjectableFactory $injectableFactory,
private EntityManager $entityManager
) {}
/**
* @param BpmnProcess $entity
* @param array<string, mixed> $options
*/
public function afterSave(Entity $entity, array $options): void
{
if (!empty($options['skipStopProcess'])) {
return;
}
if ($entity->isNew()) {
return;
}
if (!$entity->isAttributeChanged('status')) {
return;
}
if ($entity->getStatus() !== BpmnProcess::STATUS_STOPPED) {
return;
}
$manager = $this->injectableFactory->create(BpmnManager::class);
$manager->stopProcess($entity);
$subProcessList = $this->entityManager
->getRDBRepositoryByClass(BpmnProcess::class)
->where(['parentProcessId' => $entity->getId()])
->find();
foreach ($subProcessList as $e) {
$manager->stopProcess($e);
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnProcess;
use Espo\Modules\Advanced\Entities\BpmnProcess;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class SubProcesses
{
public function __construct(private EntityManager $entityManager)
{}
/**
* @param array<string, mixed> $options
*/
public function afterRemove(Entity $entity, array $options): void
{
$subProcessList = $this->entityManager
->getRDBRepository(BpmnProcess::ENTITY_TYPE)
->where(['parentProcessId' => $entity->getId()])
->find();
foreach ($subProcessList as $e) {
$this->entityManager->removeEntity($e, $options);
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnUserTask;
use Espo\Modules\Advanced\Entities\BpmnUserTask;
use Espo\ORM\Entity;
class Prepare
{
/**
* @param BpmnUserTask $entity
*/
public function beforeSave(Entity $entity): void
{
if (
!$entity->get('isResolved') &&
$entity->get('resolution') &&
$entity->isAttributeChanged('resolution')
) {
$entity->set('isResolved', true);
}
if (
!$entity->get('isResolved') &&
$entity->get('isCanceled') &&
$entity->isAttributeChanged('isCanceled')
) {
$entity->set('resolution', BpmnUserTask::RESOLUTION_CANCELED);
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\BpmnUserTask;
use Espo\Core\InjectableFactory;
use Espo\Modules\Advanced\Core\Bpmn\BpmnManager;
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
use Espo\Modules\Advanced\Entities\BpmnUserTask;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class Resolve
{
private EntityManager $entityManager;
private InjectableFactory $injectableFactory;
public function __construct(
EntityManager $entityManager,
InjectableFactory $injectableFactory
) {
$this->entityManager = $entityManager;
$this->injectableFactory = $injectableFactory;
}
/**
* @param BpmnUserTask $entity
*/
public function afterSave(Entity $entity): void
{
$flowNodeId = $entity->get('flowNodeId');
if (!$flowNodeId) {
return;
}
if (!$entity->getFetched('isResolved') && $entity->get('isResolved')) {
/** @var ?BpmnFlowNode $flowNode */
$flowNode = $this->entityManager->getEntityById(BpmnFlowNode::ENTITY_TYPE, $flowNodeId);
if (!$flowNode) {
return;
}
if ($flowNode->getStatus() !== BpmnFlowNode::STATUS_IN_PROCESS) {
return;
}
$manager = $this->injectableFactory->create(BpmnManager::class);
$manager->completeFlow($flowNode);
}
}
}

View File

@@ -0,0 +1,86 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\CampaignTrackingUrl;
use Espo\Modules\Advanced\Core\SignalManager;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @noinspection PhpUnused
*/
class Signal
{
/** @var int */
public static $order = 100;
public function __construct(
private EntityManager $entityManager,
private SignalManager $signalManager
) {}
/**
* @noinspection PhpUnused
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
*/
public function afterClick(Entity $entity, array $options, array $hookData): void
{
if (!empty($options['skipWorkflow'])) {
return;
}
if (!empty($options['skipSignal'])) {
return;
}
if (!empty($options['silent'])) {
return;
}
$uid = $hookData['uid'] ?? null;
if ($uid) {
$this->signalManager->trigger(
implode('.', ['clickUniqueUrl', $uid])
);
}
$targetType = $hookData['targetType'] ?? null;
$targetId = $hookData['targetId'] ?? null;
if (!$targetType || !$targetId) {
return;
}
$target = $this->entityManager->getEntityById($targetType, $targetId);
if (!$target) {
return;
}
$signalManager = $this->signalManager;
$signalManager->trigger(implode('.', ['@clickUrl', $entity->getId()]), $target);
$signalManager->trigger(implode('.', ['@clickUrl']), $target);
$signalManager->trigger(implode('.', ['clickUrl', $targetType, $targetId, $entity->getId()]));
$signalManager->trigger(implode('.', ['clickUrl', $targetType, $targetId]));
}
}

View File

@@ -0,0 +1,633 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\Common;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Entities\EmailAddress;
use Espo\Entities\LeadCapture;
use Espo\Entities\Note;
use Espo\Entities\Notification;
use Espo\Entities\PhoneNumber;
use Espo\Modules\Advanced\Core\SignalManager;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\ORM\Entity;
/**
* @noinspection PhpUnused
*/
class Signal
{
/** @var int */
public static $order = 100;
/** @var string[] */
private $ignoreEntityTypeList = [
Notification::ENTITY_TYPE,
EmailAddress::ENTITY_TYPE,
PhoneNumber::ENTITY_TYPE,
];
/** @var string[] */
private $ignoreRegularEntityTypeList = [
Note::ENTITY_TYPE,
];
public function __construct(
private Metadata $metadata,
private Config $config,
private SignalManager $signalManager
) {}
/**
* @param array<string, mixed> $options
*/
public function afterSave(Entity $entity, array $options): void
{
if ($this->toSkipSignal($options)) {
return;
}
if ($this->config->get('signalCrudHooksDisabled')) {
return;
}
if (in_array($entity->getEntityType(), $this->ignoreEntityTypeList)) {
return;
}
if (!$entity instanceof CoreEntity) {
return;
}
$ignoreRegular = in_array($entity->getEntityType(), $this->ignoreRegularEntityTypeList);
$signalManager = $this->signalManager;
if ($entity->isNew()) {
$signalManager->trigger('@create', $entity, $options);
if (!$ignoreRegular) {
$signalManager->trigger(['create', $entity->getEntityType()]);
}
if (
$entity->getEntityType() === Note::ENTITY_TYPE &&
$entity->get('type') === Note::TYPE_POST
) {
$parentId = $entity->get('parentId');
$parentType = $entity->get('parentType');
if ($parentType && $parentId) {
$signalManager->trigger([
'streamPost',
$parentType,
$parentId
]);
}
}
} else {
$signalManager->trigger('@update', $entity, $options);
if (!$ignoreRegular) {
$signalManager->trigger([
'update',
$entity->getEntityType(),
$entity->getId()
]);
}
}
if ($ignoreRegular) {
return;
}
foreach ($entity->getRelationList() as $relation) {
$type = $entity->getRelationType($relation);
if ($type === Entity::BELONGS_TO_PARENT && $entity->isNew()) {
$parentId = $entity->get($relation . 'Id');
$parentType = $entity->get($relation . 'Type');
if (!$parentType || !$parentId) {
continue;
}
if (!$this->metadata->get(['scopes', $parentType, 'object'])) {
continue;
}
$signalManager->trigger([
'createChild',
$parentType,
$parentId,
$entity->getEntityType()
]);
continue;
}
if ($type === Entity::BELONGS_TO) {
$idAttribute = $relation . 'Id';
$idValue = $entity->get($idAttribute);
if (!$entity->isNew()) {
if (!$entity->isAttributeChanged($idAttribute)) {
continue;
}
} else if (!$idValue) {
continue;
}
$foreignEntityType = $entity->getRelationParam($relation, 'entity');
$foreign = $entity->getRelationParam($relation, 'foreign');
if (!$foreignEntityType) {
continue;
}
if (!$foreign) {
continue;
}
if (in_array($foreignEntityType, ['User', 'Team'])) {
continue;
}
if (!$this->metadata->get(['scopes', $foreignEntityType, 'object'])) {
continue;
}
if ($entity->isNew()) {
$signalManager->trigger([
'createRelated',
$foreignEntityType,
$idValue,
$foreign
]);
}
}
}
}
/**
* @param array<string, mixed> $options
*/
public function afterRemove(Entity $entity, array $options): void
{
if ($this->toSkipSignal($options)) {
return;
}
if ($this->config->get('signalCrudHooksDisabled')) {
return;
}
if (in_array($entity->getEntityType(), $this->ignoreEntityTypeList)) {
return;
}
$ignoreRegular = in_array($entity->getEntityType(), $this->ignoreRegularEntityTypeList);
$signalManager = $this->signalManager;
$signalManager->trigger('@delete', $entity, $options);
if (!$ignoreRegular) {
$signalManager->trigger([
'delete',
$entity->getEntityType(),
$entity->getId()
]);
}
}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
*/
public function afterRelate(Entity $entity, array $options, array $hookData): void
{
if ($this->toSkipSignal($options)) {
return;
}
if ($this->config->get('signalCrudHooksDisabled')) {
return;
}
if (in_array($entity->getEntityType(), $this->ignoreEntityTypeList)) {
return;
}
if (!$entity instanceof CoreEntity) {
return;
}
$ignoreRegular = in_array($entity->getEntityType(), $this->ignoreRegularEntityTypeList);
if ($entity->isNew()) {
return;
}
$signalManager = $this->signalManager;
$foreign = $hookData['foreignEntity'] ?? null;
$link = $hookData['relationName'] ?? null;
if (!$foreign || !$link) {
return;
}
$foreignId = $foreign->getId();
$relationType = $entity->getRelationParam($link, 'type');
if ($relationType !== Entity::MANY_MANY) {
$ignoreRegular = true;
}
$signalManager->trigger(['@relate', $link, $foreignId], $entity, $options);
$signalManager->trigger(['@relate', $link], $entity, $options, ['id' => $foreignId]);
if (!$ignoreRegular) {
$signalManager->trigger([
'relate',
$entity->getEntityType(),
$entity->getId(),
$link,
$foreignId
]);
$signalManager->trigger([
'relate',
$entity->getEntityType(),
$entity->getId(),
$link
]);
}
$foreignLink = $entity->getRelationParam($link, 'foreign');
if (!$foreignLink) {
return;
}
$signalManager->trigger(['@relate', $foreignLink, $entity->getId()], $foreign);
$signalManager->trigger(['@relate', $foreignLink], $foreign, [], ['id' => $entity->getId()]);
if ($ignoreRegular) {
return;
}
$signalManager->trigger([
'relate',
$foreign->getEntityType(),
$foreign->getId(),
$foreignLink,
$entity->getId()
]);
$signalManager->trigger([
'relate',
$foreign->getEntityType(),
$foreign->getId(),
$foreignLink
]);
}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
*/
public function afterUnrelate(Entity $entity, array $options, array $hookData): void
{
if ($this->toSkipSignal($options)) {
return;
}
if ($this->config->get('signalCrudHooksDisabled')) {
return;
}
if (in_array($entity->getEntityType(), $this->ignoreEntityTypeList)) {
return;
}
if (!$entity instanceof CoreEntity) {
return;
}
$ignoreRegular = in_array($entity->getEntityType(), $this->ignoreRegularEntityTypeList);
if ($entity->isNew()) {
return;
}
$signalManager = $this->signalManager;
$foreign = $hookData['foreignEntity'] ?? null;
$link = $hookData['relationName'] ?? null;
if (!$foreign || !$link) {
return;
}
$foreignId = $foreign->getId();
$relationType = $entity->getRelationParam($link, 'type');
if ($relationType !== Entity::MANY_MANY) {
$ignoreRegular = true;
}
$signalManager->trigger(['@unrelate', $link, $foreignId], $entity, $options);
$signalManager->trigger(['@unrelate', $link], $entity, $options, ['id' => $foreignId]);
if (!$ignoreRegular) {
$signalManager->trigger([
'unrelate',
$entity->getEntityType(),
$entity->getId(),
$link,
$foreignId
]);
$signalManager->trigger([
'unrelate',
$entity->getEntityType(),
$entity->getId(),
$link
]);
}
$foreignLink = $entity->getRelationParam($link, 'foreign');
if (!$foreignLink) {
return;
}
$signalManager->trigger(['@unrelate', $foreignLink, $entity->getId()], $foreign);
$signalManager->trigger(['@unrelate', $foreignLink], $foreign, [], ['id' => $entity->getId()]);
$signalManager->trigger([
'unrelate',
$foreign->getEntityType(),
$foreign->getId(),
$foreignLink,
$entity->getId()
]);
$signalManager->trigger([
'unrelate',
$foreign->getEntityType(),
$foreign->getId(),
$foreignLink
]);
}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
* @noinspection PhpUnused
*/
public function afterMassRelate(Entity $entity, array $options, array $hookData): void
{
if ($this->toSkipSignal($options)) {
return;
}
if ($this->config->get('signalCrudHooksDisabled')) {
return;
}
if (in_array($entity->getEntityType(), $this->ignoreEntityTypeList)) {
return;
}
$link = $hookData['relationName'] ?? null;
if (!$link) {
return;
}
$signalManager = $this->signalManager;
$signalManager->trigger(['@relate', $link], $entity, $options);
$signalManager->trigger([
'relate',
$entity->getEntityType(),
$entity->getId(),
$link
]);
}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
* @noinspection PhpUnused
*/
public function afterLeadCapture(Entity $entity, array $options, array $hookData): void
{
if ($this->toSkipSignal($options)) {
return;
}
if ($entity->getEntityType() === LeadCapture::ENTITY_TYPE) {
return;
}
$id = $hookData['leadCaptureId'];
$signalManager = $this->signalManager;
$signalManager->trigger(['@leadCapture', $id], $entity);
$signalManager->trigger(['@leadCapture'], $entity);
$signalManager->trigger([
'leadCapture',
$entity->getEntityType(),
$entity->getId(),
$id
]);
$signalManager->trigger([
'leadCapture',
$entity->getEntityType(),
$entity->getId()
]);
}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
* @noinspection PhpUnused
*/
public function afterConfirmation(Entity $entity, array $options, array $hookData): void
{
if ($this->toSkipSignal($options)) {
return;
}
$eventEntityType = $entity->getEntityType();
$eventId = $entity->getId();
$status = $hookData['status'];
$entityType = $hookData['inviteeType'];
$id = $hookData['inviteeId'];
$signalManager = $this->signalManager;
if ($status === Meeting::ATTENDEE_STATUS_ACCEPTED) {
$signalManager->trigger(['@eventAccepted', $entityType], $entity, [], ['id' => $id]);
$signalManager->trigger([
'eventAccepted',
$entityType,
$id,
$eventEntityType,
$eventId
]);
$signalManager->trigger([
'eventAccepted',
$entityType,
$id,
$eventEntityType
]);
}
if ($status === Meeting::ATTENDEE_STATUS_TENTATIVE) {
$signalManager->trigger(['@eventTentative', $entityType], $entity, [], ['id' => $id]);
$signalManager->trigger([
'eventTentative',
$entityType,
$id,
$eventEntityType,
$eventId
]);
$signalManager->trigger([
'eventTentative',
$entityType,
$id,
$eventEntityType
]);
}
if ($status === Meeting::ATTENDEE_STATUS_DECLINED) {
$signalManager->trigger(['@eventDeclined', $entityType], $entity, [], ['id' => $id]);
$signalManager->trigger([
'eventDeclined',
$entityType,
$id,
$eventEntityType,
$eventId
]);
$signalManager->trigger([
'eventDeclined',
$entityType,
$id,
$eventEntityType
]);
}
if (
$status === Meeting::ATTENDEE_STATUS_ACCEPTED ||
$status === Meeting::ATTENDEE_STATUS_TENTATIVE
) {
$signalManager->trigger(['@eventAcceptedTentative', $entityType], $entity, [], ['id' => $id]);
$signalManager->trigger([
'eventAcceptedTentative',
$entityType,
$id,
$eventEntityType,
$eventId
]);
$signalManager->trigger([
'eventAcceptedTentative',
$entityType,
$id,
$eventEntityType
]);
}
}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
* @noinspection PhpUnused
* @noinspection PhpUnusedParameterInspection
*/
public function afterOptOut(Entity $entity, array $options, array $hookData): void
{
if ($this->toSkipSignal($options)) {
return;
}
$signalManager = $this->signalManager;
$signalManager->trigger(['@optOut'], $entity);
$signalManager->trigger([
'optOut',
$entity->getEntityType(),
$entity->getId()
]);
}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
* @noinspection PhpUnused
* @noinspection PhpUnusedParameterInspection
*/
public function afterCancelOptOut(Entity $entity, array $options, array $hookData): void
{
if ($this->toSkipSignal($options)) {
return;
}
$signalManager = $this->signalManager;
$signalManager->trigger(['@cancelOptOut'], $entity);
$signalManager->trigger([
'cancelOptOut',
$entity->getEntityType(),
$entity->getId()
]);
}
/**
* @param array<string, mixed> $options
*/
private function toSkipSignal(array $options): bool
{
return !empty($options['skipWorkflow']) ||
!empty($options['skipSignal']) ||
!empty($options['silent']);
}
}

View File

@@ -0,0 +1,83 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\Common;
use Espo\Core\Utils\Metadata;
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class UpdateDeferredFlowNodes
{
/** @var int */
private const LIMIT = 10;
public function __construct(
private EntityManager $entityManager,
private Metadata $metadata
) {}
/**
* @param array<string, mixed> $options
*/
public function afterSave(Entity $entity, array $options): void
{
// To skip if updated from a BPM process.
if (!empty($options['skipWorkflow'])) {
return;
}
if (!empty($options['workflowId'])) {
return;
}
if (!empty($options['silent'])) {
return;
}
$entityType = $entity->getEntityType();
if (!$this->metadata->get(['scopes', $entityType, 'object'])) {
return;
}
$nodeList = $this->entityManager
->getRDBRepository(BpmnFlowNode::ENTITY_TYPE)
->where([
'targetId' => $entity->getId(),
'targetType' => $entityType,
'status' => [
BpmnFlowNode::STATUS_PENDING,
BpmnFlowNode::STATUS_STANDBY,
],
'isDeferred' => true,
])
->limit(0, self::LIMIT)
->find();
foreach ($nodeList as $node) {
$node->set('isDeferred', false);
$this->entityManager->saveEntity($node, [
'silent' => true,
'skipAll' => true,
]);
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\Common;
use Espo\Core\Formula\Exceptions\Error;
use Espo\Modules\Advanced\Core\WorkflowManager;
use Espo\ORM\Entity;
class Workflow
{
/** @var int */
public static $order = 99;
public function __construct(private WorkflowManager $workflowManager) {}
/**
* @param array<string, mixed> $options
* @throws Error
* @throws \Espo\Core\Exceptions\Error
*/
public function afterSave(Entity $entity, array $options): void
{
if (!empty($options['skipWorkflow'])) {
return;
}
if (!empty($options['silent'])) {
return;
}
if ($entity->isNew()) {
$this->workflowManager->process($entity, WorkflowManager::AFTER_RECORD_CREATED, $options);
} else {
$this->workflowManager->process($entity, WorkflowManager::AFTER_RECORD_UPDATED, $options);
}
$this->workflowManager->process($entity, WorkflowManager::AFTER_RECORD_SAVED, $options);
}
}

View File

@@ -0,0 +1,110 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\Report;
use Espo\Modules\Advanced\Entities\Report;
use Espo\Modules\Advanced\Entities\Report as ReportEntity;
use Espo\ORM\Entity;
class Prepare
{
/**
* @param Report $entity
*/
public function beforeSave(Entity $entity): void
{
if (
$entity->isAttributeChanged('emailSendingInterval') ||
$entity->isAttributeChanged('emailSendingTime') ||
$entity->isAttributeChanged('emailSendingSettingWeekdays') ||
$entity->isAttributeChanged('emailSendingSettingDay')
) {
/** @noinspection PhpRedundantOptionalArgumentInspection */
$entity->set('emailSendingLastDateSent', null);
}
if (
$entity->get('type') === ReportEntity::TYPE_GRID &&
($entity->has('chartOneColumns') || $entity->has('chartOneY2Columns'))
) {
$this->handleChartDataList($entity);
}
}
private function handleChartDataList(Report $entity): void
{
$groupBy = $entity->getGroupBy();
if (count($groupBy) > 1) {
/** @noinspection PhpRedundantOptionalArgumentInspection */
$entity->set('chartDataList', null);
return;
}
$chartDataList = $entity->get('chartDataList');
$y = null;
$y2 = null;
if ($chartDataList && count($chartDataList) !== 0) {
$y = $chartDataList[0]->columnList ?? null;
$y2 = $chartDataList[0]->y2ColumnList ?? null;
}
$newY = $y ?? null;
$newY2 = $y2 ?? null;
if ($entity->has('chartOneColumns')) {
$newY = $entity->get('chartOneColumns') ?? [];
if ($newY && count($newY) === 0) {
$newY = null;
}
}
if ($entity->has('chartOneY2Columns')) {
$newY2 = $entity->get('chartOneY2Columns') ?? [];
if ($newY2 && count($newY2) === 0) {
$newY2 = null;
}
}
$chartType = $entity->get('chartType');
if (!in_array($chartType, ['BarVertical', 'BarHorizontal', 'Line'])) {
$newY2 = null;
}
if ($newY || $newY2) {
$newItem = (object) [
'columnList' => $newY,
'y2ColumnList' => $newY2,
];
$entity->set('chartDataList', [$newItem]);
return;
}
/** @noinspection PhpRedundantOptionalArgumentInspection */
$entity->set('chartDataList', null);
}
}

View File

@@ -0,0 +1,98 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\TargetList;
use Espo\Modules\Advanced\Core\SignalManager;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class Signal
{
/** @var int */
public static $order = 100;
public function __construct(
private SignalManager $signalManager,
private EntityManager $entityManager
) {}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
*/
public function afterOptOut(Entity $entity, array $options, array $hookData): void
{
if (!empty($options['skipWorkflow'])) {
return;
}
if (!empty($options['skipSignal'])) {
return;
}
if (!empty($options['silent'])) {
return;
}
$targetType = $hookData['targetType'];
$targetId = $hookData['targetId'];
$target = $this->entityManager->getEntityById($targetType, $targetId);
if (!$target) {
return;
}
$this->signalManager->trigger(implode('.', ['@optOut', $entity->getId()]), $target);
$this->signalManager->trigger(
implode('.', ['optOut', $target->getEntityType(), $target->getId(), $entity->getId()]));
}
/**
* @param array<string, mixed> $options
* @param array<string, mixed> $hookData
*/
public function afterCancelOptOut(Entity $entity, array $options, array $hookData): void
{
if (!empty($options['skipWorkflow'])) {
return;
}
if (!empty($options['skipSignal'])) {
return;
}
if (!empty($options['silent'])) {
return;
}
$targetType = $hookData['targetType'];
$targetId = $hookData['targetId'];
$target = $this->entityManager->getEntityById($targetType, $targetId);
if (!$target) {
return;
}
$this->signalManager->trigger(implode('.', ['@cancelOptOut', $entity->getId()]), $target);
$this->signalManager->trigger(
implode('.', ['cancelOptOut', $target->getEntityType(), $target->getId(), $entity->getId()]));
}
}

View File

@@ -0,0 +1,41 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\Workflow;
use Espo\Modules\Advanced\Core\WorkflowManager;
use Espo\ORM\Entity;
class ReloadWorkflows
{
/** @var int */
public static $order = 9;
public function __construct(private WorkflowManager $workflowManager)
{}
public function afterSave(Entity $entity): void
{
$this->workflowManager->loadWorkflows(true);
}
public function afterRemove(Entity $entity): void
{
$this->workflowManager->loadWorkflows(true);
}
}

View File

@@ -0,0 +1,65 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\Workflow;
use Espo\Entities\Job;
use Espo\Modules\Advanced\Entities\Workflow;
use Espo\Modules\Advanced\Tools\Workflow\Jobs\RunScheduledWorkflow as RunScheduledWorkflowJob;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class RemoveJobs
{
private EntityManager $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @param Workflow $entity
*/
public function afterSave(Entity $entity): void
{
$toRemove = $entity->getType() === Workflow::TYPE_SCHEDULED &&
(
$entity->isAttributeChanged('scheduling') ||
$entity->isAttributeChanged('schedulingApplyTimezone')
);
if (!$toRemove) {
return;
}
$pendingJobList = $this->entityManager
->getRDBRepository(Job::ENTITY_TYPE)
->where([
'className' => RunScheduledWorkflowJob::class,
'status' => 'Pending',
'targetType' => Workflow::ENTITY_TYPE,
'targetId' => $entity->getId(),
])
->find();
foreach ($pendingJobList as $pendingJob) {
$this->entityManager->removeEntity($pendingJob);
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
/***********************************************************************************
* The contents of this file are subject to the Extension License Agreement
* ("Agreement") which can be viewed at
* https://www.espocrm.com/extension-license-agreement/.
* By copying, installing downloading, or using this file, You have unconditionally
* agreed to the terms and conditions of the Agreement, and You may not use this
* file except in compliance with the Agreement. Under the terms of the Agreement,
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
* redistribute, market, publish, commercialize, or otherwise transfer rights or
* usage to the software or any modified version or derivative work of the software
* created by or for you.
*
* Copyright (C) 2015-2025 EspoCRM, Inc.
*
* License ID: 19bc86a68a7bb01f458cb391d43a9212
************************************************************************************/
namespace Espo\Modules\Advanced\Hooks\Workflow;
use Espo\Modules\Advanced\Entities\WorkflowRoundRobin;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
class RemoveRoundRobin
{
/** @var int */
public static $order = 9;
public function __construct(private EntityManager $entityManager)
{}
public function afterRemove(Entity $entity): void
{
$roundRobinList = $this->entityManager
->getRDBRepository(WorkflowRoundRobin::ENTITY_TYPE)
->where(['workflowId' => $entity->getId()])
->find();
foreach ($roundRobinList as $item) {
$this->entityManager->removeEntity($item);
}
}
}