From 78ea0d7512a5a2b17abc15527921c7b0bf9eb0ab Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 4 Jul 2025 09:38:41 +0300 Subject: [PATCH] email template link mapping --- .../Resources/metadata/app/emailTemplate.json | 15 ++ .../Tools/EmailTemplate/EntityMapProvider.php | 131 ++++++++++++++++++ .../Espo/Tools/EmailTemplate/Processor.php | 12 +- schema/metadata/app/emailTemplate.json | 12 ++ 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 application/Espo/Modules/Crm/Resources/metadata/app/emailTemplate.json create mode 100644 application/Espo/Tools/EmailTemplate/EntityMapProvider.php diff --git a/application/Espo/Modules/Crm/Resources/metadata/app/emailTemplate.json b/application/Espo/Modules/Crm/Resources/metadata/app/emailTemplate.json new file mode 100644 index 0000000000..6e8eabdc98 --- /dev/null +++ b/application/Espo/Modules/Crm/Resources/metadata/app/emailTemplate.json @@ -0,0 +1,15 @@ +{ + "entityLinkMapping": { + "Contact": { + "Account": "account" + }, + "Opportunity": { + "Account": "account", + "Contact": "contact" + }, + "Case": { + "Account": "account", + "Contact": "contact" + } + } +} diff --git a/application/Espo/Tools/EmailTemplate/EntityMapProvider.php b/application/Espo/Tools/EmailTemplate/EntityMapProvider.php new file mode 100644 index 0000000000..2030e6b0e5 --- /dev/null +++ b/application/Espo/Tools/EmailTemplate/EntityMapProvider.php @@ -0,0 +1,131 @@ +. + * + * 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\Tools\EmailTemplate; + +use Espo\Core\Acl\GlobalRestriction; +use Espo\Core\AclManager; +use Espo\Core\Record\ServiceContainer; +use Espo\Core\Utils\Metadata; +use Espo\Entities\User; +use Espo\ORM\Entity; +use Espo\ORM\EntityManager; + +/** + * @since 9.2.0 + * @internal + */ +class EntityMapProvider +{ + public function __construct( + private EntityManager $entityManager, + private AclManager $aclManager, + private ServiceContainer $serviceContainer, + private Metadata $metadata, + ) {} + + /** + * @return array + */ + public function get(Entity $entity, User $user, bool $applyAcl): array + { + /** @var array $map */ + $map = $this->metadata->get("app.emailTemplate.entityLinkMapping.{$entity->getEntityType()}") ?? []; + + $output = []; + + foreach ($map as $entityType => $link) { + $related = $this->getRelated( + entity: $entity, + link: $link, + user: $user, + applyAcl: $applyAcl, + ); + + if ($related) { + $output[$entityType] = $related; + } + } + + return $output; + } + + private function getRelated( + Entity $entity, + string $link, + User $user, + bool $applyAcl, + ): ?Entity { + + $entityDefs = $this->entityManager->getDefs()->getEntity($entity->getEntityType()); + + $forbiddenLinkList = $this->aclManager->getScopeRestrictedLinkList( + $entity->getEntityType(), + [ + GlobalRestriction::TYPE_FORBIDDEN, + GlobalRestriction::TYPE_INTERNAL, + GlobalRestriction::TYPE_ONLY_ADMIN, + ] + ); + + if ($applyAcl) { + if ( + $entityDefs->hasField($link) && + !$this->aclManager->checkField($user, $entity->getEntityType(), $link) + ) { + return null; + } + + if (in_array($link, $forbiddenLinkList)) { + return null; + } + } + + $related = $this->entityManager + ->getRelation($entity, $link) + ->findOne(); + + if (!$related) { + return null; + } + + if ( + $applyAcl && + !$this->aclManager->checkEntityRead($user, $related) + ) { + return null; + } + + $this->serviceContainer + ->get($related->getEntityType()) + ->loadAdditionalFields($related); + + return $related; + } +} diff --git a/application/Espo/Tools/EmailTemplate/Processor.php b/application/Espo/Tools/EmailTemplate/Processor.php index 0c1590fe37..80bb80471b 100644 --- a/application/Espo/Tools/EmailTemplate/Processor.php +++ b/application/Espo/Tools/EmailTemplate/Processor.php @@ -64,7 +64,8 @@ class Processor private FileStorageManager $fileStorageManager, private User $user, private HtmlizerFactory $htmlizerFactory, - private PlaceholdersProvider $placeholdersProvider + private PlaceholdersProvider $placeholdersProvider, + private EntityMapProvider $entityMapProvider, ) {} public function process(EmailTemplate $template, Params $params, Data $data): Result @@ -417,6 +418,15 @@ class Processor } } + if ($data->getParent()) { + $entityHash = array_merge( + $entityHash, + $this->entityMapProvider->get($data->getParent(), $user, $params->applyAcl()) + ); + + $entityHash[$data->getParent()->getEntityType()] = $data->getParent(); + } + if ($data->getRelatedId() && $data->getRelatedType()) { $related = $this->entityManager->getEntityById($data->getRelatedType(), $data->getRelatedId()); diff --git a/schema/metadata/app/emailTemplate.json b/schema/metadata/app/emailTemplate.json index ccae40d440..585379d62e 100644 --- a/schema/metadata/app/emailTemplate.json +++ b/schema/metadata/app/emailTemplate.json @@ -21,6 +21,18 @@ } } } + }, + "entityLinkMapping": { + "description": "EntityType => link mapping. As of v9.2.0.", + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "An entity type name mapped to a link." + }, + "description": "A parent entity type name mapped to EntityType => link map.." + } } } }