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,57 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Call;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Record\ServiceContainer;
use Espo\Modules\Crm\Entities\Call;
use Espo\ORM\Entity;
/**
* @implements SaveHook<Call>
*/
class AfterUpdate implements SaveHook
{
public function __construct(
private ServiceContainer $serviceContainer
) {}
public function process(Entity $entity): void
{
if (
!$entity->isAttributeChanged('contactsIds') &&
!$entity->isAttributeChanged('leadsIds')
) {
return;
}
$this->serviceContainer->getByClass(Call::class)->loadAdditionalFields($entity);
}
}

View File

@@ -0,0 +1,77 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Campaign;
use Espo\Core\Exceptions\Error\Body;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Modules\Crm\Entities\Campaign;
use Espo\Modules\Crm\Entities\CampaignTrackingUrl;
use Espo\Modules\Crm\Entities\MassEmail;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements SaveHook<Campaign>
*/
class BeforeUpdate implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
) {}
public function process(Entity $entity): void
{
$this->checkType($entity);
}
/**
* @throws Forbidden
*/
private function checkType(Campaign $entity): void
{
if (!$entity->isAttributeChanged('type')) {
return;
}
$massEmail = $this->entityManager
->getRDBRepositoryByClass(MassEmail::class)
->where(['campaignId' => $entity->getId()])
->findOne();
if ($massEmail) {
throw Forbidden::createWithBody(
'Cannot change type.',
Body::create()->withMessageTranslation('cannotChangeType', Campaign::ENTITY_TYPE)
);
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\CampaignTrackingUrl;
use Espo\Core\Acl;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Modules\Crm\Entities\Campaign;
use Espo\Modules\Crm\Entities\CampaignTrackingUrl;
use Espo\ORM\Entity;
/**
* @implements SaveHook<CampaignTrackingUrl>
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private Acl $acl
) {}
public function process(Entity $entity): void
{
if (!$this->acl->check($entity, Acl\Table::ACTION_EDIT)) {
throw new Forbidden("No 'edit' access.");
}
$this->checkCampaign($entity);
}
/**
* @throws Forbidden
*/
private function checkCampaign(CampaignTrackingUrl $entity): void
{
if (
!$entity->getCampaign() || in_array($entity->getCampaign()->getType(), [
Campaign::TYPE_EMAIL,
Campaign::TYPE_NEWSLETTER,
])
) {
return;
}
throw new Forbidden("Cannot create tacking URL for non-email campaign.");
}
}

View File

@@ -0,0 +1,118 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Case;
use Espo\Core\Acl;
use Espo\Core\Name\Field;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Email;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\CaseObj;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Lead;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use RuntimeException;
/**
* @implements SaveHook<CaseObj>
* @noinspection PhpUnused
*/
class AfterCreate implements SaveHook
{
private const EMAIL_REPLY_LEVEL = 3;
private const EMAIL_REPLY_LIMIT = 2;
private const EMAIL_REPLY_LIMIT_SECOND = 1;
public function __construct(
private EntityManager $entityManager,
private Acl $acl,
) {}
public function process(Entity $entity): void
{
/** @var ?string $emailId */
$emailId = $entity->get('originalEmailId');
if (!$emailId) {
return;
}
$email = $this->entityManager->getRDBRepositoryByClass(Email::class)->getById($emailId);
if (!$email) {
return;
}
$this->changeEmailParent($email, $entity);
}
private function changeEmailParent(Email $email, CaseObj $entity, int $level = 0): void
{
if (!$this->acl->checkEntityRead($email)) {
return;
}
if (
$email->getParentId() &&
!in_array($email->getParentType(), [
Account::ENTITY_TYPE,
Contact::ENTITY_TYPE,
Lead::ENTITY_TYPE,
])
) {
return;
}
$email->setParent($entity);
$this->entityManager->saveEntity($email);
if ($level === self::EMAIL_REPLY_LEVEL) {
return;
}
$limit = $level === 0 ? self::EMAIL_REPLY_LIMIT : self::EMAIL_REPLY_LIMIT_SECOND;
$replies = $this->entityManager
->getRelation($email, Email::LINK_REPLIES)
->limit(0, $limit)
->order(Field::CREATED_AT)
->find();
foreach ($replies as $reply) {
if (!$reply instanceof Email) {
throw new RuntimeException();
}
$this->changeEmailParent($reply, $entity, $level + 1);
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Case;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\CaseObj;
use Espo\ORM\Entity;
/**
* @implements SaveHook<CaseObj>
* @noinspection PhpUnused
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private User $user
) {}
public function process(Entity $entity): void
{
if (!$this->user->isPortal()) {
return;
}
$userContact = $this->user->getContact();
if (!$userContact) {
return;
}
if (!$entity->getAccount() && $userContact->getAccount()) {
$entity->setAccount($userContact->getAccount());
}
if (!$entity->getContact()) {
$entity->setContact($userContact);
}
}
}

View File

@@ -0,0 +1,81 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Contact;
use Espo\Core\Acl;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Config;
use Espo\Entities\Email;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements SaveHook<Contact>
*/
class AfterCreate implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
private Config $config,
private Acl $acl
) {}
public function process(Entity $entity): void
{
$emailId = $entity->get('originalEmailId');
if (!$emailId) {
return;
}
/** @var ?Email $email */
$email = $this->entityManager->getEntityById(Email::ENTITY_TYPE, $emailId);
if (!$email || $email->getParentId() || !$this->acl->check($email)) {
return;
}
if ($this->config->get('b2cMode') || !$entity->getAccount()) {
$email->set([
'parentType' => Contact::ENTITY_TYPE,
'parentId' => $entity->getId(),
]);
} else if ($entity->getAccount()) {
$email->set([
'parentType' => Account::ENTITY_TYPE,
'parentId' => $entity->getAccount()->getId(),
]);
}
$this->entityManager->saveEntity($email);
}
}

View File

@@ -0,0 +1,101 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Lead;
use Espo\Core\Acl;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\DateTime;
use Espo\Entities\Email;
use Espo\Modules\Crm\Entities\Campaign as Campaign;
use Espo\Modules\Crm\Entities\CampaignLogRecord as CampaignLogRecord;
use Espo\Modules\Crm\Entities\Lead;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements SaveHook<Lead>
*/
class AfterCreate implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
private Acl $acl
) {}
public function process(Entity $entity): void
{
$this->processOriginalEmail($entity);
$this->processCampaignLog($entity);
}
private function processOriginalEmail(Lead $entity): void
{
$emailId = $entity->get('originalEmailId');
if (!$emailId) {
return;
}
/** @var ?Email $email */
$email = $this->entityManager->getEntityById(Email::ENTITY_TYPE, $emailId);
if (!$email || $email->getParentId() || !$this->acl->check($email)) {
return;
}
$email->set([
'parentType' => Lead::ENTITY_TYPE,
'parentId' => $entity->getId(),
]);
$this->entityManager->saveEntity($email);
}
private function processCampaignLog(Lead $entity): void
{
$campaign = $entity->getCampaign();
if (!$campaign) {
return;
}
$log = $this->entityManager->getNewEntity(CampaignLogRecord::ENTITY_TYPE);
$log->set([
'action' => CampaignLogRecord::ACTION_LEAD_CREATED,
'actionDate' => DateTime::getSystemNowString(),
'parentType' => Lead::ENTITY_TYPE,
'parentId' => $entity->getId(),
'campaignId' => $campaign->getId(),
]);
$this->entityManager->saveEntity($log);
}
}

View File

@@ -0,0 +1,75 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\MassEmail;
use Espo\Core\Acl;
use Espo\Core\Acl\Table;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Modules\Crm\Entities\Campaign;
use Espo\Modules\Crm\Entities\MassEmail;
use Espo\ORM\Entity;
/**
* @implements SaveHook<MassEmail>
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private Acl $acl
) {}
public function process(Entity $entity): void
{
if (!$this->acl->check($entity, Table::ACTION_EDIT)) {
throw new Forbidden("No 'edit' access.");
}
$this->checkCampaign($entity);
}
/**
* @throws Forbidden
*/
private function checkCampaign(MassEmail $entity): void
{
if (
!$entity->getCampaign() || in_array($entity->getCampaign()->getType(), [
Campaign::TYPE_EMAIL,
Campaign::TYPE_NEWSLETTER,
Campaign::TYPE_INFORMATIONAL_EMAIL,
])
) {
return;
}
throw new Forbidden("Cannot create mass email for non-email campaign.");
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Espo\Modules\Crm\Classes\RecordHooks\Meeting;
use Espo\Core\Acl;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\Hook\CreateHook;
use Espo\Entities\Email;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements CreateHook<Meeting>
*/
class BeforeCreateSourceEmailCheck implements CreateHook
{
public function __construct(
private EntityManager $entityManager,
private Acl $acl,
) {}
public function process(Entity $entity, CreateParams $params): void
{
$emailId = $entity->get('sourceEmailId');
if (!$emailId) {
return;
}
$email = $this->entityManager->getRDBRepositoryByClass(Email::class)->getById($emailId);
if (!$email) {
return;
}
if (!$this->acl->checkEntityRead($email)) {
throw new Forbidden("No access to source email.");
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Opportunity;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Metadata;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\ORM\Entity;
/**
* @implements SaveHook<Opportunity>
*/
class BeforeUpdate implements SaveHook
{
public function __construct(
private Metadata $metadata
) {}
public function process(Entity $entity): void
{
$this->setProbability($entity);
}
private function setProbability(Opportunity $entity): void
{
if ($entity->isAttributeWritten('probability') && $entity->getProbability() !== null) {
return;
}
$stage = $entity->getStage();
$probability = $this->metadata->get("entityDefs.Opportunity.fields.stage.probabilityMap.$stage");
if ($probability === null) {
return;
}
$entity->setProbability($probability);
}
}

View File

@@ -0,0 +1,184 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\TargetList;
use Espo\Core\Acl;
use Espo\Core\Acl\Table;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Modules\Crm\Entities\Campaign;
use Espo\Modules\Crm\Entities\CampaignLogRecord;
use Espo\Modules\Crm\Entities\TargetList;
use Espo\Modules\Crm\Tools\TargetList\MetadataProvider;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\ORM\Name\Attribute;
/**
* @implements SaveHook<TargetList>
*/
class AfterCreate implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
private Acl $acl,
private MetadataProvider $metadataProvider
) {}
/**
* @throws Forbidden
* @throws NotFound
*/
public function process(Entity $entity): void
{
if (
!$entity->get('sourceCampaignId') ||
!$entity->get('includingActionList')
) {
return;
}
/** @var string $campaignId */
$campaignId = $entity->get('sourceCampaignId');
/** @var string[] $includingActionList */
$includingActionList = $entity->get('includingActionList');
/** @var string[] $excludingActionList */
$excludingActionList = $entity->get('excludingActionList') ?? [];
$this->populateFromCampaignLog(
$entity,
$campaignId,
$includingActionList,
$excludingActionList
);
}
/**
* @param string[] $includingActionList
* @param string[] $excludingActionList
* @throws NotFound
* @throws Forbidden
*/
protected function populateFromCampaignLog(
TargetList $entity,
string $sourceCampaignId,
array $includingActionList,
array $excludingActionList
): void {
$campaign = $this->entityManager->getEntityById(Campaign::ENTITY_TYPE, $sourceCampaignId);
if (!$campaign) {
throw new NotFound("Campaign not found.");
}
if (!$this->acl->check($campaign, Table::ACTION_READ)) {
throw new Forbidden("No access to campaign.");
}
$queryBuilder = $this->entityManager
->getQueryBuilder()
->select()
->from(CampaignLogRecord::ENTITY_TYPE)
->select([Attribute::ID, 'parentId', 'parentType'])
->where([
'isTest' => false,
'campaignId' => $sourceCampaignId,
]);
$notQueryBuilder = clone $queryBuilder;
$queryBuilder->where([
'action=' => $includingActionList,
]);
$queryBuilder->group([
'parentId',
'parentType',
Attribute::ID,
]);
$notQueryBuilder->where(['action=' => $excludingActionList]);
$notQueryBuilder->select([Attribute::ID]);
/** @var iterable<CampaignLogRecord> $logRecords */
$logRecords = $this->entityManager
->getRDBRepository(CampaignLogRecord::ENTITY_TYPE)
->clone($queryBuilder->build())
->find();
$entityTypeLinkMap = $this->metadataProvider->getEntityTypeLinkMap();
foreach ($logRecords as $logRecord) {
if (!$logRecord->getParent()) {
continue;
}
$parentType = $logRecord->getParent()->getEntityType();
$parentId = $logRecord->getParent()->getId();
if (!$parentType) {
continue;
}
if (empty($entityTypeLinkMap[$parentType])) {
continue;
}
$existing = null;
if (!empty($excludingActionList)) {
$cloneQueryBuilder = clone $notQueryBuilder;
$cloneQueryBuilder->where([
'parentType' => $parentType,
'parentId' => $parentId,
]);
$existing = $this->entityManager
->getRDBRepository(CampaignLogRecord::ENTITY_TYPE)
->clone($cloneQueryBuilder->build())
->findOne();
}
if ($existing) {
continue;
}
$relation = $entityTypeLinkMap[$parentType];
$this->entityManager
->getRDBRepositoryByClass(TargetList::class)
->getRelation($entity, $relation)
->relateById($parentId);
}
}
}

View File

@@ -0,0 +1,89 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\TargetList;
use Espo\Core\Acl;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\Hook\CreateHook;
use Espo\Modules\Crm\Entities\TargetList;
use Espo\Modules\Crm\Tools\TargetList\MetadataProvider;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements CreateHook<TargetList>
*/
class AfterCreateDuplicate implements CreateHook
{
public function __construct(
private Acl $acl,
private EntityManager $entityManager,
private MetadataProvider $metadataProvider
) {}
public function process(Entity $entity, CreateParams $params): void
{
$id = $params->getDuplicateSourceId();
if (!$id) {
return;
}
$sourceEntity = $this->entityManager->getRDBRepositoryByClass(TargetList::class)->getById($id);
if (!$sourceEntity) {
return;
}
if (!$this->acl->check($sourceEntity, Acl\Table::ACTION_READ)) {
return;
}
$this->duplicateLinks($entity, $sourceEntity);
}
private function duplicateLinks(TargetList $entity, TargetList $sourceEntity): void
{
$repository = $this->entityManager->getRDBRepositoryByClass(TargetList::class);
foreach ($this->metadataProvider->getTargetLinkList() as $link) {
$collection = $repository
->getRelation($sourceEntity, $link)
->where(['@relation.optedOut' => false])
->find();
foreach ($collection as $relatedEntity) {
$repository
->getRelation($entity, $link)
->relate($relatedEntity, ['optedOut' => false]);
}
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Task;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Task;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements SaveHook<Task>
*/
class AfterSave implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
private User $user,
) {}
public function process(Entity $entity): void
{
/** @var ?string $emailId */
$emailId = $entity->get('emailId');
if (!$emailId || !$entity->getAssignedUser()) {
return;
}
if (!$entity->isNew() && !$entity->isAttributeChanged('assignedUserId')) {
return;
}
$email = $this->entityManager->getRDBRepositoryByClass(Email::class)->getById($emailId);
if (!$email) {
return;
}
$relation = $this->entityManager->getRelation($email, 'users');
if ($relation->isRelatedById($entity->getAssignedUser()->getId())) {
return;
}
$isRead = $entity->getAssignedUser()->getId() === $this->user->getId();
$relation->relateById($entity->getAssignedUser()->getId(), [Email::USERS_COLUMN_IS_READ => $isRead]);
}
}

View File

@@ -0,0 +1,72 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 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\Modules\Crm\Classes\RecordHooks\Task;
use Espo\Core\Acl;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Email;
use Espo\Modules\Crm\Entities\Task;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements SaveHook<Task>
*/
class BeforeCreate implements SaveHook
{
public function __construct(
private Acl $acl,
private EntityManager $entityManager,
) {}
public function process(Entity $entity): void
{
/** @var ?string $emailId */
$emailId = $entity->get('originalEmailId');
if ($emailId === null) {
return;
}
$email = $this->entityManager->getRDBRepositoryByClass(Email::class)->getById($emailId);
if (!$email) {
throw new BadRequest("Email record not found.");
}
if (!$this->acl->checkEntityRead($entity)) {
throw new Forbidden("No access to email.");
}
$entity->set('emailId', $emailId);
}
}