Skip to content

Commit

Permalink
CATL-1613: Add new field to the mail account form - Skip emails which…
Browse files Browse the repository at this point in the history
… do not have a Case ID or Case token
  • Loading branch information
i-grou committed Oct 7, 2020
1 parent 993a642 commit f945f0c
Show file tree
Hide file tree
Showing 11 changed files with 301 additions and 5 deletions.
5 changes: 4 additions & 1 deletion CRM/Admin/Form/MailSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public function buildQuickForm() {
$this->add('select', 'is_default', ts('Used For?'), $usedfor);
$this->addField('activity_status', ['placeholder' => FALSE]);

$this->add('checkbox', 'is_non_case_email_skipped', ts('Skip emails which do not have a Case ID or Case hash'));
$this->add('checkbox', 'is_contact_creation_disabled_if_no_match', ts('Do not create new contacts when filing emails'));
}

Expand Down Expand Up @@ -148,6 +149,7 @@ public function postProcess() {
'is_ssl',
'is_default',
'activity_status',
'is_non_case_email_skipped',
'is_contact_creation_disabled_if_no_match',
];

Expand All @@ -156,6 +158,7 @@ public function postProcess() {
if (in_array($f, [
'is_default',
'is_ssl',
'is_non_case_email_skipped',
'is_contact_creation_disabled_if_no_match',
])) {
$params[$f] = CRM_Utils_Array::value($f, $formValues, FALSE);
Expand All @@ -168,7 +171,7 @@ public function postProcess() {
$params['domain_id'] = CRM_Core_Config::domainID();

// assign id only in update mode
$status = ts('Your New Email Settings have been saved.');
$status = ts('Your New Email Settings have been saved.');
if ($this->_action & CRM_Core_Action::UPDATE) {
$params['id'] = $this->_id;
$status = ts('Your Email Settings have been updated.');
Expand Down
2 changes: 2 additions & 0 deletions CRM/Upgrade/Incremental/php/FiveThirtyOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public function upgrade_5_31_alpha1($rev) {
*/
public function upgrade_5_31_0($rev) {
$this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
$this->addTask('Add is_non_case_email_skipped column to civicrm_mail_settings', 'addColumn',
'civicrm_mail_settings', 'is_non_case_email_skipped', "TINYINT DEFAULT 0 NOT NULL COMMENT 'Skip emails which do not have a Case ID or Case hash'");
$this->addTask('Add is_contact_creation_disabled_if_no_match column to civicrm_mail_settings', 'addColumn',
'civicrm_mail_settings', 'is_contact_creation_disabled_if_no_match', "TINYINT DEFAULT 0 NOT NULL COMMENT 'If this option is enabled, CiviCRM will not create new contacts when filing emails'");
}
Expand Down
22 changes: 22 additions & 0 deletions CRM/Utils/Hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,28 @@ public static function caseTypes(&$caseTypes) {
->invoke(['caseTypes'], $caseTypes, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, 'civicrm_caseTypes');
}

/**
* This hook is called when getting case email subject patterns.
*
* All emails related to cases have case hash/id in the subject, e.g:
* [case #ab12efg] Magic moment
* [case #1234] Magic is here
*
* Using this hook you can replace/enrich default list with some other
* patterns, e.g. include case type categories (see CiviCase extension) like:
* [(case|project|policy initiative) #hash]
* [(case|project|policy initiative) #id]
*
* @param array $subjectPatterns
* Cases related email subject regexp patterns.
*
* @return mixed
*/
public static function caseEmailSubjectPatterns(&$subjectPatterns) {
return self::singleton()
->invoke(['caseEmailSubjectPatterns'], $subjectPatterns, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, 'civicrm_caseEmailSubjectPatterns');
}

/**
* This hook is called soon after the CRM_Core_Config object has ben initialized.
* You can use this hook to modify the config object and hence behavior of CiviCRM dynamically.
Expand Down
127 changes: 127 additions & 0 deletions CRM/Utils/Mail/CaseMail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/

/**
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*/

/**
* Class CRM_Utils_Mail_CaseMail.
*/
class CRM_Utils_Mail_CaseMail {

/**
* A word that is used for cases by default (in email subject).
*
* @var string
*/
private $caseLabel = 'case';

/**
* Default cases related email subject regexp patterns.
*
* All emails related to cases have case hash/id in the subject, e.g:
* [case #ab12efg] Magic moment
* [case #1234] Magic is here
* This variable is defined in constructor.
*
* @var array|string[]
*/
private $subjectPatterns = [];

/**
* Cases related email subject regexp patterns extended by hooks.
*
* @var array|string[]
*/
private $subjectPatternsHooked = [];

/**
* CRM_Utils_Mail_CaseMail constructor.
*/
public function __construct() {
$this->subjectPatterns = [
'/\[' . $this->caseLabel . ' #([0-9a-f]{7})\]/i',
'/\[' . $this->caseLabel . ' #(\d+)\]/i',
];
}

/**
* Checks if email is related to cases.
*
* @param string $subject
* Email subject.
*
* @return bool
* TRUE if email subject contains case ID or case hash, FALSE otherwise.
*/
public function isCaseEmail ($subject) {
$subject = trim($subject);
$patterns = $this->getSubjectPatterns();
$res = FALSE;

for ($i = 0; !$res && $i < count($patterns); $i++) {
$res = preg_match($patterns[$i], $subject) === 1;
}

return $res;
}

/**
* Returns cases related email subject patterns.
*
* These patterns could be used to check if email is related to cases.
*
* @return array|string[]
*/
public function getSubjectPatterns() {
// Allow others to change patterns using hook.
if (empty($this->subjectPatternsHooked)) {
$patterns = $this->subjectPatterns;
CRM_Utils_Hook::caseEmailSubjectPatterns($patterns);
$this->subjectPatternsHooked = $patterns;
}

return !empty($this->subjectPatternsHooked)
? $this->subjectPatternsHooked
: $this->subjectPatterns;
}

/**
* Returns value of some class property.
*
* @param string $name
* Property name.
*
* @return mixed|null
* Property value or null if property does not exist.
*/
public function get($name) {
return $this->{$name} ?? NULL;
}

/**
* Sets value of some class property.
*
* @param string $name
* Property name.
* @param mixed $value
* New property value.
*/
public function set($name, $value) {
if (isset($this->{$name})) {
$this->{$name} = $value;
}
}

}
9 changes: 9 additions & 0 deletions CRM/Utils/Mail/EmailProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@ public static function _process($civiMail, $dao, $is_create_activities) {

// preseve backward compatibility
if ($usedfor == 0 || $is_create_activities) {
// Mail account may have 'Skip emails which do not have a Case ID
// or Case hash' option, if its enabled and email is not related
// to cases - then we need to put email to ignored folder.
$caseMailUtils = new CRM_Utils_Mail_CaseMail();
if (!empty($dao->is_non_case_email_skipped) && !$caseMailUtils->isCaseEmail($mail->subject)) {
$store->markIgnored($key);
continue;
}

// if its the activities that needs to be processed ..
try {
$createContact = !($dao->is_contact_creation_disabled_if_no_match ?? FALSE);
Expand Down
6 changes: 4 additions & 2 deletions templates/CRM/Admin/Form/MailSettings.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
<tr class="crm-mail-settings-form-block-is_default"><td class="label">{$form.is_default.label}</td><td>{$form.is_default.html}</td></tr>
<tr><td class="label">&nbsp;</td><td class="description">{ts}How this mail account will be used. Only one box may be used for bounce processing. It will also be used as the envelope email when sending mass mailings.{/ts}</td></tr>

<tr class="crm-mail-settings-form-block-is_contact_creation_disabled_if_no_match"><td class="label">&nbsp;</td><td>{$form.is_contact_creation_disabled_if_no_match.html}{$form.is_contact_creation_disabled_if_no_match.label}</td></tr>
<tr class="crm-mail-settings-form-block-is_non_case_email_skipped"><td class="label">&nbsp;</td><td>{$form.is_non_case_email_skipped.html}{$form.is_non_case_email_skipped.label} {help id='is_non_case_email_skipped'}</td></tr>

<tr class="crm-mail-settings-form-block-is_contact_creation_disabled_if_no_match"><td class="label">&nbsp;</td><td>{$form.is_contact_creation_disabled_if_no_match.html}{$form.is_contact_creation_disabled_if_no_match.label} {help id='is_contact_creation_disabled_if_no_match'}</td></tr>

<tr class="crm-mail-settings-form-block-activity_status"><td class="label">&nbsp;</td><td>{$form.activity_status.label}<div>{$form.activity_status.html}</div></td></tr>
</table>
Expand All @@ -68,9 +70,9 @@
function showActivityFields() {
var fields = [
'.crm-mail-settings-form-block-activity_status',
'.crm-mail-settings-form-block-is_non_case_email_skipped',
'.crm-mail-settings-form-block-is_contact_creation_disabled_if_no_match',
];
$(fields.join(', '), $form).toggle($(this).val() === '0');
}
$('select[name="is_default"]').each(showActivityFields).change(showActivityFields);
Expand Down
30 changes: 30 additions & 0 deletions templates/CRM/Admin/Page/MailSettings.hlp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*}

{htxt id="is_non_case_email_skipped-title"}
{ts}Skip emails which do not have a Case ID or Case hash{/ts}
{/htxt}

{htxt id="is_non_case_email_skipped"}
<p>{ts}CiviCRM has functionality to file emails which contain the Case ID or Case Hash in the subject line in the format [case #1234] against a case record.{/ts}</p>
<p>{ts}Where the Case ID or Case Hash is not included CiviCRM will file the email against the contact record, by matching the email addresses on the email with any email addresses of Contact records in CiviCRM.{/ts}</p>
<p>{ts}Enabling this option will have CiviCRM skip any emails that do not have the Case ID or Case Hash so that the system will only process emails that can be placed on case records.{/ts}</p>
<p>{ts}Any emails that are not processed will be moved to the ignored folder.{/ts}</p>
<p>{ts}If email is skipped, no activities or contacts ("from"/"to"/"cc"/"bcc") will be created.{/ts}</p>
{/htxt}

{htxt id="is_contact_creation_disabled_if_no_match-title"}
{ts}Do not create new contacts when filing emails{/ts}
{/htxt}

{htxt id="is_contact_creation_disabled_if_no_match"}
<p>{ts}If this option is enabled, CiviCRM will not create new contacts ("from"/"to"/"cc"/"bcc") when filing emails.{/ts}</p>
<p>{ts}If the email subject contains a valid Case ID or Case hash, the email will be filed against the case.{/ts}</p>
{/htxt}
64 changes: 62 additions & 2 deletions tests/phpunit/CRM/Utils/Mail/EmailProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,19 @@ public function setUp() {
public function tearDown() {
CRM_Utils_File::cleanDir(__DIR__ . '/data/mail');
parent::tearDown();
$this->quickCleanup(['civicrm_group', 'civicrm_group_contact', 'civicrm_mailing', 'civicrm_mailing_job', 'civicrm_mailing_event_bounce', 'civicrm_mailing_event_queue', 'civicrm_mailing_group', 'civicrm_mailing_recipients', 'civicrm_contact', 'civicrm_email']);
$this->quickCleanup([
'civicrm_group',
'civicrm_group_contact',
'civicrm_mailing',
'civicrm_mailing_job',
'civicrm_mailing_event_bounce',
'civicrm_mailing_event_queue',
'civicrm_mailing_group',
'civicrm_mailing_recipients',
'civicrm_contact',
'civicrm_email',
'civicrm_activity',
]);
}

/**
Expand Down Expand Up @@ -139,7 +151,6 @@ public function testBounceProcessingDeletedEmail() {
}

/**
*
* Wrapper to check for mailing bounces.
*
* Normally we would call $this->callAPISuccessGetCount but there is not one & there is resistance to
Expand Down Expand Up @@ -169,6 +180,55 @@ public function setUpMailing() {
$this->eventQueue = $this->callAPISuccess('MailingEventQueue', 'get', ['api.MailingEventQueue.create' => ['hash' => 'aaaaaaaaaaaaaaaa']]);
}

/**
* Set up mail account with 'Skip emails which do not have a Case ID or
* Case hash' option enabled.
*/
public function setUpSkipNonCasesEmail() {
$this->callAPISuccess('MailSettings', 'get', [
'api.MailSettings.create' => [
'name' => 'mailbox',
'protocol' => 'Localdir',
'source' => __DIR__ . '/data/mail',
'domain' => 'example.com',
'is_default' => '0',
'is_non_case_email_skipped' => TRUE,
],
]);
}

/**
* Test case email processing when is_non_case_email_skipped is enabled.
*/
public function testInboundProcessingCaseEmail() {
$this->setUpSkipNonCasesEmail();
$mail = 'test_cases_email.eml';

copy(__DIR__ . '/data/inbound/' . $mail, __DIR__ . '/data/mail/' . $mail);
$this->callAPISuccess('job', 'fetch_activities', []);
$result = civicrm_api3('Activity', 'get', [
'sequential' => 1,
'subject' => ['LIKE' => "%[case #214bf6d]%"],
]);
$this->assertNotEmpty($result['values'][0]['id']);
}

/**
* Test non case email processing when is_non_case_email_skipped is enabled.
*/
public function testInboundProcessingNonCaseEmail() {
$this->setUpSkipNonCasesEmail();
$mail = 'test_non_cases_email.eml';

copy(__DIR__ . '/data/inbound/' . $mail, __DIR__ . '/data/mail/' . $mail);
$this->callAPISuccess('job', 'fetch_activities', []);
$result = civicrm_api3('Activity', 'get', [
'sequential' => 1,
'subject' => ['LIKE' => "%Love letter%"],
]);
$this->assertEmpty($result['values']);
}

/**
* Set up mail account with 'Do not create new contacts when filing emails'
* option enabled.
Expand Down
15 changes: 15 additions & 0 deletions tests/phpunit/CRM/Utils/Mail/data/inbound/test_cases_email.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Delivered-To: [email protected]
Received: by 10.2.13.84 with SMTP id 1234567890;
Wed, 19 Dec 2018 10:01:11 +0100 (CET)
Return-Path: <>
Message-ID: <[email protected]>
Date: Wed, 19 Dec 2018 10:01:07 +0100
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 8bit
Content-Disposition: inline
From: [email protected]
To: [email protected]
Subject: [case #214bf6d] Magic is here

This test case is full of fun.
15 changes: 15 additions & 0 deletions tests/phpunit/CRM/Utils/Mail/data/inbound/test_non_cases_email.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Delivered-To: [email protected]
Received: by 10.2.13.84 with SMTP id 1234567890;
Wed, 19 Dec 2018 10:01:11 +0100 (CET)
Return-Path: <>
Message-ID: <[email protected]>
Date: Wed, 19 Dec 2018 10:01:07 +0100
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 8bit
Content-Disposition: inline
From: [email protected]
To: [email protected]
Subject: Love letter

I love you unit test, because you are not related to cases.
Loading

0 comments on commit f945f0c

Please sign in to comment.