mirror of
https://github.com/espocrm/espocrm.git
synced 2026-06-28 06:56:05 +00:00
email auto reply field
This commit is contained in:
@@ -317,6 +317,11 @@ class Binding implements BindingProcessor
|
||||
'Espo\\Core\\Mail\\Importer',
|
||||
'Espo\\Core\\Mail\\Importer\\DefaultImporter'
|
||||
);
|
||||
|
||||
$binder->bindImplementation(
|
||||
'Espo\\Core\\Mail\\Importer\\AutoReplyDetector',
|
||||
'Espo\\Core\\Mail\\Importer\\DefaultAutoReplyDetector'
|
||||
);
|
||||
}
|
||||
|
||||
private function bindAcl(Binder $binder): void
|
||||
|
||||
@@ -244,26 +244,22 @@ class AfterFetch implements AfterFetchInterface
|
||||
|
||||
$subject = $replyData->getSubject();
|
||||
|
||||
if ($case) {
|
||||
$subject = '[#' . $case->get('number'). '] ' . $subject;
|
||||
if ($case && $case->getNumber() !== null) {
|
||||
$subject = "[#{$case->getNumber()}] $subject";
|
||||
}
|
||||
|
||||
/** @var Email $reply */
|
||||
$reply = $this->entityManager->getRDBRepositoryByClass(Email::class)->getNew();
|
||||
|
||||
$reply
|
||||
->addToAddress($fromAddress)
|
||||
->setSubject($subject)
|
||||
->setBody($replyData->getBody())
|
||||
->setIsHtml($replyData->isHtml());
|
||||
|
||||
if ($email->has('teamsIds')) {
|
||||
$reply->set('teamsIds', $email->get('teamsIds'));
|
||||
}
|
||||
->setIsHtml($replyData->isHtml())
|
||||
->setIsAutoReply()
|
||||
->setTeams($email->getTeams());
|
||||
|
||||
if ($email->getParentId() && $email->getParentType()) {
|
||||
$reply->set('parentId', $email->getParentId());
|
||||
$reply->set('parentType', $email->getParentType());
|
||||
$reply->setParent($email->getParent());
|
||||
}
|
||||
|
||||
$this->entityManager->saveEntity($reply);
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Espo\Core\Mail\Account\GroupAccount\Hooks;
|
||||
use Espo\Core\Mail\Account\Hook\BeforeFetch as BeforeFetchInterface;
|
||||
use Espo\Core\Mail\Account\Hook\BeforeFetchResult;
|
||||
use Espo\Core\Mail\Account\Account;
|
||||
use Espo\Core\Mail\Importer\AutoReplyDetector;
|
||||
use Espo\Core\Mail\Message;
|
||||
use Espo\Core\Mail\Account\GroupAccount\BouncedRecognizer;
|
||||
use Espo\Core\Utils\Log;
|
||||
@@ -50,6 +51,7 @@ class BeforeFetch implements BeforeFetchInterface
|
||||
private EntityManager $entityManager,
|
||||
private BouncedRecognizer $bouncedRecognizer,
|
||||
private CampaignService $campaignService,
|
||||
private AutoReplyDetector $autoReplyDetector,
|
||||
) {}
|
||||
|
||||
public function process(Account $account, Message $message): BeforeFetchResult
|
||||
@@ -125,22 +127,7 @@ class BeforeFetch implements BeforeFetchInterface
|
||||
|
||||
private function checkMessageIsAutoReply(Message $message): bool
|
||||
{
|
||||
if ($message->getHeader('X-Autoreply')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($message->getHeader('X-Autorespond')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
$message->getHeader('Auto-submitted') &&
|
||||
strtolower($message->getHeader('Auto-submitted')) !== 'no'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->autoReplyDetector->detect($message);
|
||||
}
|
||||
|
||||
private function checkMessageCannotBeAutoReplied(Message $message): bool
|
||||
|
||||
42
application/Espo/Core/Mail/Importer/AutoReplyDetector.php
Normal file
42
application/Espo/Core/Mail/Importer/AutoReplyDetector.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* 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\Core\Mail\Importer;
|
||||
|
||||
use Espo\Core\Mail\Message;
|
||||
|
||||
/**
|
||||
* Detects if an email is auto-response.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*/
|
||||
interface AutoReplyDetector
|
||||
{
|
||||
public function detect(Message $message): bool;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* 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\Core\Mail\Importer;
|
||||
|
||||
use Espo\Core\Mail\Message;
|
||||
|
||||
class DefaultAutoReplyDetector implements AutoReplyDetector
|
||||
{
|
||||
public function detect(Message $message): bool
|
||||
{
|
||||
if ($message->getHeader('X-Autoreply')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($message->getHeader('X-Autorespond')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
$message->getHeader('Auto-submitted') &&
|
||||
strtolower($message->getHeader('Auto-submitted')) !== 'no'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,8 @@ class DefaultImporter implements Importer
|
||||
private LinkMultipleSaver $linkMultipleSaver,
|
||||
private DuplicateFinder $duplicateFinder,
|
||||
private JobSchedulerFactory $jobSchedulerFactory,
|
||||
private ParentFinder $parentFinder
|
||||
private ParentFinder $parentFinder,
|
||||
private AutoReplyDetector $autoReplyDetector,
|
||||
) {
|
||||
$this->notificator = $notificatorFactory->createByClass(Email::class);
|
||||
$this->filtersMatcher = new FiltersMatcher();
|
||||
@@ -146,6 +147,8 @@ class DefaultImporter implements Importer
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
$email->setIsAutoReply($this->autoReplyDetector->detect($message));
|
||||
|
||||
$this->processDeliveryDate($parser, $message, $email);
|
||||
|
||||
if (!$email->getDateSent()) {
|
||||
|
||||
@@ -893,4 +893,14 @@ class Email extends Entity
|
||||
{
|
||||
return $this->set('isReplied', $isReplied);
|
||||
}
|
||||
|
||||
public function isAutoReply(): bool
|
||||
{
|
||||
return $this->get('isAutoReply');
|
||||
}
|
||||
|
||||
public function setIsAutoReply(bool $isAutoReply = true): self
|
||||
{
|
||||
return $this->set('isAutoReply', $isAutoReply);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ class CaseObj extends Entity
|
||||
|
||||
protected $entityType = 'Case';
|
||||
|
||||
public function getNumber(): ?int
|
||||
{
|
||||
return $this->get('number');
|
||||
}
|
||||
|
||||
public function setName(?string $name): self
|
||||
{
|
||||
return $this->set(Field::NAME, $name);
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
"icsEventDateStart": "ICS Event Date Start",
|
||||
"groupFolder": "Group Folder",
|
||||
"groupStatusFolder": "Group Status Folder",
|
||||
"sendAt": "Send At"
|
||||
"sendAt": "Send At",
|
||||
"isAutoReply": "Is Auto-Reply"
|
||||
},
|
||||
"links": {
|
||||
"replied": "Replied",
|
||||
|
||||
@@ -443,6 +443,11 @@
|
||||
"Espo\\Classes\\FieldValidators\\Email\\SendAt\\Future"
|
||||
]
|
||||
},
|
||||
"isAutoReply": {
|
||||
"type": "bool",
|
||||
"readOnly": true,
|
||||
"fieldManagerParamList": []
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true,
|
||||
|
||||
@@ -31,7 +31,11 @@
|
||||
"textFilterClassName": "Espo\\Classes\\Select\\Email\\TextFilter",
|
||||
"textFilterUseContainsAttributeList": ["name"],
|
||||
"selectAttributesDependencyMap": {
|
||||
"subject": ["name"],
|
||||
"subject": [
|
||||
"name",
|
||||
"isAutoReply",
|
||||
"hasAttachment"
|
||||
],
|
||||
"personStringData": ["fromString", "fromEmailAddressId"],
|
||||
"replyToName": ["replyToString"]
|
||||
}
|
||||
|
||||
@@ -7,17 +7,25 @@
|
||||
title="{{value}}"
|
||||
>{{value}}</a>
|
||||
</span>
|
||||
{{#if hasAttachment}}
|
||||
<span class="list-icon-container">
|
||||
<a
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-action="showAttachments"
|
||||
class="text-muted"
|
||||
><span
|
||||
class="fas fa-paperclip small"
|
||||
title="{{translate 'hasAttachment' category='fields' scope='Email'}}"
|
||||
></span></a>
|
||||
</span>
|
||||
{{#if hasIcon}}
|
||||
<span class="list-icon-container" data-icon-count="{{iconCount}}">
|
||||
{{#if hasAttachment}}
|
||||
<a
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-action="showAttachments"
|
||||
class="text-muted"
|
||||
><span
|
||||
class="fas fa-paperclip small"
|
||||
title="{{translate 'hasAttachment' category='fields' scope='Email'}}"
|
||||
></span></a>
|
||||
{{/if}}
|
||||
{{#if isAutoReply}}
|
||||
<span
|
||||
class="fas fas fa-robot small text-muted"
|
||||
title="{{translate 'isAutoReply' category='fields' scope='Email'}}"
|
||||
></span>
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
|
||||
@@ -39,6 +39,17 @@ class EmailSubjectFieldView extends VarcharFieldView {
|
||||
data.isImportant = this.model.has('isImportant') && this.model.get('isImportant');
|
||||
data.hasAttachment = this.model.has('hasAttachment') && this.model.get('hasAttachment');
|
||||
data.isReplied = this.model.has('isReplied') && this.model.get('isReplied');
|
||||
data.isAutoReply = this.model.has('isAutoReply') && this.model.attributes.isAutoReply;
|
||||
|
||||
data.hasIcon = data.hasAttachment || data.isAutoReply;
|
||||
|
||||
if (data.hasIcon) {
|
||||
data.iconCount = 1;
|
||||
|
||||
if (data.hasAttachment && data.isAutoReply) {
|
||||
data.iconCount = 2;
|
||||
}
|
||||
}
|
||||
|
||||
data.inTrash = this.model.attributes.groupFolderId ?
|
||||
this.model.attributes.groupStatusFolder === 'Trash' :
|
||||
@@ -88,6 +99,7 @@ class EmailSubjectFieldView extends VarcharFieldView {
|
||||
'hasAttachment',
|
||||
'inTrash',
|
||||
'groupStatusFolder',
|
||||
'isAutoReply',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -374,6 +374,18 @@ div.list-expanded > ul > li > div.expanded-row {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.list-icon-container[data-icon-count="2"]) {
|
||||
> span {
|
||||
> span:first-child {
|
||||
width: calc(100% - var(--32px));
|
||||
}
|
||||
|
||||
> span.list-icon-container {
|
||||
width: var(--32px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user