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,59 @@
<?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\Hooks\Common;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\ORM\Entity;
use Espo\Tools\EmailNotification\HookProcessor;
class AssignmentEmailNotification
{
private HookProcessor $processor;
public function __construct(HookProcessor $processor)
{
$this->processor = $processor;
}
/**
* @param array<string, mixed> $options
*/
public function afterSave(Entity $entity, array $options): void
{
if (
!empty($options[SaveOption::SILENT]) ||
!empty($options[SaveOption::NO_NOTIFICATIONS])
) {
return;
}
$this->processor->afterSave($entity);
}
}

View File

@@ -0,0 +1,54 @@
<?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\Hooks\Common;
use Espo\Core\Hook\Hook\AfterSave;
use Espo\Core\ORM\Repository\Option\SaveContext;
use Espo\ORM\Entity;
use Espo\ORM\Repository\Option\SaveOptions;
/**
* @implements AfterSave<Entity>
*/
class CallDeferredActions implements AfterSave
{
public static int $order = 101;
public function afterSave(Entity $entity, SaveOptions $options): void
{
$saveContext = SaveContext::obtainFromOptions($options);
if (!$saveContext) {
return;
}
$saveContext->callDeferredActions();
}
}

View File

@@ -0,0 +1,143 @@
<?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\Hooks\Common;
use Espo\Core\Field\Link;
use Espo\Core\Field\LinkMultiple;
use Espo\Core\Field\LinkMultipleItem;
use Espo\Core\Hook\Hook\BeforeSave;
use Espo\Core\Name\Field;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity;
use Espo\ORM\Repository\Option\SaveOptions;
/**
* @implements BeforeSave<Entity>
*/
class Collaborators implements BeforeSave
{
public static int $order = 7;
private const FIELD_COLLABORATORS = Field::COLLABORATORS;
private const FIELD_ASSIGNED_USERS = Field::ASSIGNED_USERS;
private const FIELD_ASSIGNED_USER = Field::ASSIGNED_USER;
public function __construct(
private Metadata $metadata,
) {}
public function beforeSave(Entity $entity, SaveOptions $options): void
{
if (!$entity instanceof CoreEntity) {
return;
}
if (!$this->hasCollaborators($entity)) {
return;
}
if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
$this->processAssignedUsers($entity);
return;
}
$this->processAssignedUser($entity);
}
private function hasCollaborators(CoreEntity $entity): bool
{
if (!$this->metadata->get("scopes.{$entity->getEntityType()}.collaborators")) {
return false;
}
if (!$entity->hasLinkMultipleField(self::FIELD_COLLABORATORS)) {
return false;
}
return true;
}
private function processAssignedUsers(CoreEntity $entity): void
{
if (!$entity->has(self::FIELD_COLLABORATORS . 'Ids')) {
return;
}
$assignedUsers = $entity->getValueObject(self::FIELD_ASSIGNED_USERS);
$collaborators = $entity->getValueObject(self::FIELD_COLLABORATORS);
if (
!$assignedUsers instanceof LinkMultiple ||
!$collaborators instanceof LinkMultiple
) {
return;
}
$countBefore = $collaborators->getCount();
foreach ($assignedUsers->getList() as $assignedUser) {
$collaborators = $collaborators->withAdded($assignedUser);
}
if ($countBefore === $collaborators->getCount()) {
return;
}
$entity->setValueObject(self::FIELD_COLLABORATORS, $collaborators);
}
private function processAssignedUser(CoreEntity $entity): void
{
$idAttr = self::FIELD_ASSIGNED_USER . 'Id';
if (!$entity->hasAttribute($idAttr) || !$entity->isAttributeChanged($idAttr)) {
return;
}
$assignedUser = $entity->getValueObject(self::FIELD_ASSIGNED_USER);
if (!$assignedUser instanceof Link) {
return;
}
$collaborators = $entity->getValueObject(self::FIELD_COLLABORATORS);
if (!$collaborators instanceof LinkMultiple) {
return;
}
$collaborators = $collaborators
->withAdded(LinkMultipleItem::create($assignedUser->getId(), $assignedUser->getName()));
$entity->setValueObject(self::FIELD_COLLABORATORS, $collaborators);
}
}

View File

@@ -0,0 +1,97 @@
<?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\Hooks\Common;
use Espo\Core\ORM\Type\FieldType;
use Espo\ORM\Entity;
use Espo\Core\Di;
class CurrencyConverted implements Di\MetadataAware, Di\ConfigAware
{
use Di\MetadataSetter;
use Di\ConfigSetter;
public static int $order = 1;
public function beforeSave(Entity $entity): void
{
$fieldDefs = $this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields'], []);
foreach ($fieldDefs as $fieldName => $defs) {
if (empty($defs['type']) || $defs['type'] !== FieldType::CURRENCY_CONVERTED) {
continue;
}
$currencyFieldName = substr($fieldName, 0, -9);
$currencyCurrencyFieldName = $currencyFieldName . 'Currency';
if (
!$entity->isAttributeChanged($currencyFieldName) &&
!$entity->isAttributeChanged($currencyCurrencyFieldName)
) {
continue;
}
if (empty($fieldDefs[$currencyFieldName])) {
continue;
}
if ($entity->get($currencyFieldName) === null) {
$entity->set($fieldName, null);
continue;
}
$currency = $entity->get($currencyCurrencyFieldName);
$value = $entity->get($currencyFieldName);
if (!$currency) {
continue;
}
$rates = $this->config->get('currencyRates', []);
$baseCurrency = $this->config->get('baseCurrency');
$defaultCurrency = $this->config->get('defaultCurrency');
if ($defaultCurrency === $currency) {
$targetValue = $value;
} else {
$targetValue = $value;
$targetValue = $targetValue / ($rates[$baseCurrency] ?? 1.0);
$targetValue = $targetValue * ($rates[$currency] ?? 1.0);
$targetValue = round($targetValue, 2);
}
$entity->set($fieldName, $targetValue);
}
}
}

View File

@@ -0,0 +1,69 @@
<?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\Hooks\Common;
use Espo\Core\ORM\Type\FieldType;
use Espo\ORM\Entity;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\FieldUtil;
class CurrencyDefault
{
public static int $order = 200;
public function __construct(private Config $config, private FieldUtil $fieldUtil)
{}
public function beforeSave(Entity $entity): void
{
$fieldList = $this->fieldUtil->getFieldByTypeList($entity->getEntityType(), FieldType::CURRENCY);
$defaultCurrency = $this->config->get('defaultCurrency');
foreach ($fieldList as $field) {
$currencyAttribute = $field . 'Currency';
if ($entity->isNew()) {
if ($entity->get($field) && !$entity->get($currencyAttribute)) {
$entity->set($currencyAttribute, $defaultCurrency);
}
continue;
}
if (
$entity->isAttributeChanged($field) && $entity->has($currencyAttribute) &&
!$entity->get($currencyAttribute)
) {
$entity->set($currencyAttribute, $defaultCurrency);
}
}
}
}

View File

@@ -0,0 +1,85 @@
<?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\Hooks\Common;
use Espo\Core\Hook\Hook\BeforeRemove;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Util;
use Espo\ORM\Entity;
use Espo\ORM\Name\Attribute;
use Espo\ORM\Repository\Option\RemoveOptions;
use Espo\ORM\Repository\Option\SaveOptions;
use Espo\Core\Hook\Hook\BeforeSave;
/**
* Handles 'deleteId' on soft-deletes.
*
* @implements BeforeSave<Entity>
* @implements BeforeRemove<Entity>
*/
class DeleteId implements BeforeSave, BeforeRemove
{
private const ID_ATTR = 'deleteId';
private const DELETED_ATTR = Attribute::DELETED;
public function __construct(
private Metadata $metadata,
) {}
public function beforeRemove(Entity $entity, RemoveOptions $options): void
{
if (!$this->hasDeleteId($entity)) {
return;
}
$entity->set(self::ID_ATTR, Util::generateId());
}
public function beforeSave(Entity $entity, SaveOptions $options): void
{
if (!$this->hasDeleteId($entity)) {
return;
}
if (!$entity->isAttributeChanged(self::DELETED_ATTR)) {
return;
}
$deleteId = $entity->get(self::DELETED_ATTR) ? Util::generateId() : '0';
$entity->set(self::ID_ATTR, $deleteId);
}
private function hasDeleteId(Entity $entity): bool
{
return $entity->hasAttribute(self::DELETED_ATTR) &&
$this->metadata->get("entityDefs.{$entity->getEntityType()}.deleteId");
}
}

View File

@@ -0,0 +1,62 @@
<?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\Hooks\Common;
use Espo\ORM\Entity;
use Espo\Core\FieldProcessing\SaveProcessor;
use Espo\Core\ORM\Entity as CoreEntity;
class FieldProcessing
{
public static int $order = -11;
private SaveProcessor $saveProcessor;
public function __construct(SaveProcessor $saveProcessor)
{
$this->saveProcessor = $saveProcessor;
}
/**
* @param array<string, mixed> $options
*/
public function afterSave(Entity $entity, array $options): void
{
if (!empty($options['skipFieldProcessing'])) {
return;
}
if (!$entity instanceof CoreEntity) {
return;
}
$this->saveProcessor->process($entity, $options);
}
}

View File

@@ -0,0 +1,85 @@
<?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\Hooks\Common;
use Espo\Core\Hook\Hook\AfterSave;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\ORM\Defs;
use Espo\ORM\Defs\Params\AttributeParam;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\ORM\Repository\Option\SaveOptions;
use Espo\ORM\Type\AttributeType;
/**
* @implements AfterSave<Entity>
* @noinspection PhpUnused
*/
class ForeignFields implements AfterSave
{
public static int $order = 8;
public function __construct(
private Defs $defs,
private EntityManager $entityManager
) {}
public function afterSave(Entity $entity, SaveOptions $options): void
{
if (!$options->get(SaveOption::API) || $entity->isNew()) {
return;
}
$defs = $this->defs->getEntity($entity->getEntityType());
$foreignList = array_filter(
$entity->getAttributeList(), fn ($it) => $entity->getAttributeType($it) === AttributeType::FOREIGN);
$relationList = array_map(
fn ($it) => $defs->getAttribute($it)->getParam(AttributeParam::RELATION), $foreignList);
$relationList = array_filter($relationList, fn ($it) => $entity->isAttributeChanged($it . 'Id'));
$relationList = array_values($relationList);
if ($relationList === []) {
return;
}
$copy = $this->entityManager->getEntityById($entity->getEntityType(), $entity->getId());
if (!$copy) {
return;
}
foreach ($foreignList as $attribute) {
$entity->set($attribute, $copy->get($attribute));
}
}
}

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\Hooks\Common;
use Espo\ORM\Entity;
use Espo\ORM\Repository\Option\SaveOptions;
use Espo\Core\Hook\Hook\BeforeSave;
use Espo\Core\Formula\Manager as FormulaManager;
use Espo\Core\Utils\Log;
use Espo\Core\Utils\Metadata;
use Exception;
use stdClass;
/**
* @implements BeforeSave<Entity>
*/
class Formula implements BeforeSave
{
public static int $order = 11;
public function __construct(
private Metadata $metadata,
private FormulaManager $formulaManager,
private Log $log
) {}
public function beforeSave(Entity $entity, SaveOptions $options): void
{
if ($options->get('skipFormula')) {
return;
}
$scriptList = $this->metadata->get(['formula', $entity->getEntityType(), 'beforeSaveScriptList'], []);
$variables = (object) [];
foreach ($scriptList as $script) {
$this->runScript($script, $entity, $variables);
}
$customScript = $this->metadata->get(['formula', $entity->getEntityType(), 'beforeSaveCustomScript']);
if (!$customScript) {
return;
}
$this->runScript($customScript, $entity, $variables);
}
private function runScript(string $script, Entity $entity, stdClass $variables): void
{
try {
$this->formulaManager->run($script, $entity, $variables);
} catch (Exception $e) {
$this->log->critical('Before-save formula script failed. {message}', [
'message' => $e->getMessage(),
'exception' => $e,
]);
}
}
}

View File

@@ -0,0 +1,52 @@
<?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\Hooks\Common;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\ORM\Entity;
use Espo\Core\FieldProcessing\NextNumber\BeforeSaveProcessor as Processor;
class NextNumber
{
public function __construct(private Processor $processor)
{}
/**
* @param array<string, mixed> $options
*/
public function beforeSave(Entity $entity, array $options): void
{
if (!$entity instanceof CoreEntity) {
return;
}
$this->processor->process($entity, $options);
}
}

View File

@@ -0,0 +1,82 @@
<?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\Hooks\Common;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\Tools\Notification\HookProcessor;
use Espo\ORM\Entity;
class Notifications
{
public static int $order = 10;
private HookProcessor $processor;
public function __construct(HookProcessor $processor)
{
$this->processor = $processor;
}
/**
* @param array<string, mixed> $options
*/
public function afterSave(Entity $entity, array $options): void
{
if (!empty($options[SaveOption::SILENT]) || !empty($options[SaveOption::NO_NOTIFICATIONS])) {
return;
}
$this->processor->afterSave($entity, $options);
}
/**
* @param array<string, mixed> $options
*/
public function beforeRemove(Entity $entity, array $options): void
{
if (!empty($options[SaveOption::SILENT]) || !empty($options[SaveOption::NO_NOTIFICATIONS])) {
return;
}
$this->processor->beforeRemove($entity, $options);
}
/**
* @param array<string, mixed> $options
*/
public function afterRemove(Entity $entity, array $options): void
{
if (!empty($options[SaveOption::SILENT])) {
return;
}
$this->processor->afterRemove($entity);
}
}

View File

@@ -0,0 +1,107 @@
<?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\Hooks\Common;
use Espo\Core\Hook\Hook\AfterRelate;
use Espo\Core\Hook\Hook\AfterRemove;
use Espo\Core\Hook\Hook\AfterSave;
use Espo\Core\Hook\Hook\AfterUnrelate;
use Espo\Core\Hook\Hook\BeforeSave;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\ORM\Entity;
use Espo\ORM\Repository\Option\RelateOptions;
use Espo\ORM\Repository\Option\RemoveOptions;
use Espo\ORM\Repository\Option\SaveOptions;
use Espo\ORM\Repository\Option\UnrelateOptions;
use Espo\Tools\Stream\HookProcessor;
class Stream implements BeforeSave, AfterSave, AfterRemove, AfterRelate, AfterUnrelate
{
public static int $order = 9;
public function __construct(private HookProcessor $processor)
{}
public function beforeSave(Entity $entity, SaveOptions $options): void
{
if ($options->get(SaveOption::SILENT)) {
return;
}
$this->processor->beforeSave($entity, $options);
}
public function afterSave(Entity $entity, SaveOptions $options): void
{
if ($options->get(SaveOption::SILENT)) {
return;
}
$this->processor->afterSave($entity, $options->toAssoc());
}
public function afterRemove(Entity $entity, RemoveOptions $options): void
{
if ($options->get(SaveOption::SILENT)) {
return;
}
$this->processor->afterRemove($entity, $options);
}
public function afterRelate(
Entity $entity,
string $relationName,
Entity $relatedEntity,
array $columnData,
RelateOptions $options
): void {
if ($options->get(SaveOption::SILENT)) {
return;
}
$this->processor->afterRelate($entity, $relatedEntity, $relationName, $options->toAssoc());
}
public function afterUnrelate(
Entity $entity,
string $relationName,
Entity $relatedEntity,
UnrelateOptions $options
): void {
if ($options->get(SaveOption::SILENT)) {
return;
}
$this->processor->afterUnrelate($entity, $relatedEntity, $relationName, $options->toAssoc());
}
}

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\Hooks\Common;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\ORM\Entity;
use Espo\Tools\Stream\NoteAcl\AccessModifier;
/**
* Notes having `related` or `superParent` are subjects to access control
* through `users` and `teams` fields.
*
* When users or teams of `related` or `parent` record are changed
* the note record will be changed too.
*
* @noinspection PhpUnused
*/
class StreamNotesAcl
{
public static int $order = 10;
public function __construct(private AccessModifier $processor)
{}
/**
* @param array<string, mixed> $options
*/
public function afterSave(Entity $entity, array $options): void
{
if (!empty($options[SaveOption::NO_STREAM])) {
return;
}
if (!empty($options[SaveOption::SILENT])) {
return;
}
if (!empty($options['skipStreamNotesAcl'])) {
return;
}
if ($entity->isNew()) {
return;
}
$this->processor->process($entity);
}
}

View File

@@ -0,0 +1,49 @@
<?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\Hooks\Common;
use Espo\ORM\Entity;
use Espo\Core\FieldProcessing\VersionNumber\BeforeSaveProcessor as Processor;
class VersionNumber
{
private Processor $processor;
public function __construct(Processor $processor)
{
$this->processor = $processor;
}
public function beforeSave(Entity $entity): void
{
$this->processor->process($entity);
}
}

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\Hooks\Common;
use Espo\Core\Hook\Hook\AfterSave;
use Espo\Core\WebSocket\ConfigDataProvider;
use Espo\ORM\Entity;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\Core\Utils\Metadata;
use Espo\Core\WebSocket\Submission as WebSocketSubmission;
use Espo\ORM\Repository\Option\SaveOptions;
/**
* @implements AfterSave<Entity>
*/
class WebSocketSubmit implements AfterSave
{
public static int $order = 20;
public function __construct(
private Metadata $metadata,
private WebSocketSubmission $webSocketSubmission,
) {}
public function afterSave(Entity $entity, SaveOptions $options): void
{
if ($options->get(SaveOption::SILENT)) {
return;
}
if ($entity->isNew()) {
return;
}
$scope = $entity->getEntityType();
$id = $entity->getId();
if (!$this->metadata->get("scopes.$scope.object")) {
return;
}
$topic = "recordUpdate.$scope.$id";
$this->webSocketSubmission->submit($topic);
}
}

View File

@@ -0,0 +1,104 @@
<?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\Hooks\Common;
use Espo\Core\Hook\Hook\AfterRemove;
use Espo\Core\Hook\Hook\AfterSave;
use Espo\Core\Webhook\Options;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\Core\Utils\Metadata;
use Espo\Core\Webhook\Manager as WebhookManager;
use Espo\ORM\Repository\Option\RemoveOptions;
use Espo\ORM\Repository\Option\SaveOptions;
/**
* @implements AfterSave<Entity>
* @implements AfterRemove<Entity>
*/
class Webhook implements AfterSave, AfterRemove
{
public static int $order = 101;
public function __construct(
private Metadata $metadata,
private WebhookManager $webhookManager,
private User $user,
) {}
public function afterSave(Entity $entity, SaveOptions $options): void
{
if (
$this->toSkip($options, $entity) ||
!$entity instanceof CoreEntity
) {
return;
}
$userIdParam = $entity->isNew() ? SaveOption::CREATED_BY_ID : SaveOption::MODIFIED_BY_ID;
$eventOptions = new Options(
userId: $options->get($userIdParam) ?? $this->user->getId(),
);
if ($entity->isNew()) {
$this->webhookManager->processCreate($entity, $eventOptions);
return;
}
$this->webhookManager->processUpdate($entity, $eventOptions);
}
public function afterRemove(Entity $entity, RemoveOptions $options): void
{
if (
$this->toSkip($options, $entity) ||
!$entity instanceof CoreEntity
) {
return;
}
$eventOptions = new Options(
userId: $options->get(SaveOption::MODIFIED_BY_ID) ?? $this->user->getId(),
);
$this->webhookManager->processDelete($entity, $eventOptions);
}
private function toSkip(SaveOptions|RemoveOptions $options, Entity $entity): bool
{
return $options->get(SaveOption::SILENT) ||
!$this->metadata->get("scopes.{$entity->getEntityType()}.object");
}
}