Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRM-19690 - CiviMail/FlexMailer - Allow alternative email templating systems #9497

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
725da87
CRM-19690 - Declare Mailing.template_type, Mailing.template_options, …
totten Nov 28, 2016
5a25fd7
CRM-19690 - Mailing API - Encode and decode `template_options`
totten Dec 2, 2016
45a555b
CRM-19690 - crmMailing - Pick editor layout using template_type
totten Dec 19, 2016
88797e4
CRM-19690 - crmMailingBlockRecipients - Provide selector for more dow…
totten Dec 13, 2016
f3958c9
CRM-19690 - MailingJob - Extract findPendingTasks().
totten Dec 7, 2016
913d8aa
CRM-19690 - CRM_Utils_Token::getAnonymousTokenDetails() - Add missing…
totten Nov 29, 2016
d2fc467
CRM-19690 - Mailing - Make getTemplates and getVerpAndUrlsAndHeaders …
totten Nov 29, 2016
671bd6c
CRM-19690 - AbstractTokenSubscriber - Only evaluate tokens that are used
totten Nov 30, 2016
22d558d
CRM-19690 - CRM_Mailing_Tokens - Add TokenProcessor support
totten Nov 30, 2016
decc37b
CRM-19690 - CRM_Mailing_MailingSystemTest - Improve CiviMail test cov…
totten Dec 16, 2016
afd56cc
CRM-19690 - Enable FlexMailer (if present)
totten Dec 3, 2016
0ba21be
CRM-19690 - Implement FlexMailer. Add tests.
totten Dec 1, 2016
4da0e13
(NFC) CRM-19690 - Improve code-style and docblocks
totten Dec 20, 2016
86680ca
CRM_Extension_System - Fix extra slash in `vendor` URLs
totten Dec 20, 2016
274a594
CRM-19690 - CRM_Mailing_Tokens - Remove unused `getTrackOpenUrl`
totten Dec 22, 2016
6c225c2
CRM-19690 - CRM_Mailing_TokensTest - Define negative test scenario
totten Dec 22, 2016
591183b
CRM-19690 - CRM_Mailing_{Action,}Tokens - Docblock tweak
totten Dec 22, 2016
bddb863
CRM-19690 - MailingJob::deliver() - Remove disabled debug code
totten Dec 22, 2016
c5e041b
CRM-19690 - MailingJob::findPendingTasks() - Tweak style
totten Dec 22, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CRM/Core/DAO/AllCoreTables.data.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
// (GenCodeChecksum:bd14c54d35d01e466eec41f1605ba862)
// (GenCodeChecksum:1f9e47fc8d0661ec0b31d4cbbba6783c)
return array(
'CRM_Core_DAO_AddressFormat' => array(
'name' => 'AddressFormat',
Expand Down
2 changes: 1 addition & 1 deletion CRM/Extension/System.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public function getFullContainer() {
if (is_dir($vendorPath)) {
$containers['cmsvendor'] = new CRM_Extension_Container_Basic(
$vendorPath,
$this->parameters['userFrameworkBaseURL'] . DIRECTORY_SEPARATOR . 'vendor',
CRM_Utils_File::addTrailingSlash($this->parameters['userFrameworkBaseURL'], '/') . 'vendor',
$this->getCache(),
'cmsvendor'
);
Expand Down
105 changes: 105 additions & 0 deletions CRM/Mailing/ActionTokens.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2016 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM 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 and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/

/**
* Class CRM_Mailing_ActionTokens
*
* Generate "action.*" tokens for mailings.
*
* To activate these tokens, the TokenProcessor context must specify:
* "mailingJobId" (int)
* "mailingActionTarget" (array) with keys:
* 'id' => int, event queue ID
* 'hash' => string, event queue hash code
* 'contact_id' => int, contact_id,
* 'email' => string, email
* 'phone' => string, phone
*/
class CRM_Mailing_ActionTokens extends \Civi\Token\AbstractTokenSubscriber {

/**
* Class constructor.
*/
public function __construct() {
// TODO: Think about supporting dynamic tokens like "{action.subscribe.\d+}"
parent::__construct('action', array(
'subscribeUrl' => ts('Subscribe URL (Action)'),
'forward' => ts('Forward URL (Action)'),
'optOut' => ts('Opt-Out (Action)'),
'optOutUrl' => ts('Opt-Out URL (Action)'),
'reply' => ts('Reply (Action)'),
'unsubscribe' => ts('Unsubscribe (Action)'),
'unsubscribeUrl' => ts('Unsubscribe URL (Action)'),
'resubscribe' => ts('Resubscribe (Action)'),
'resubscribeUrl' => ts('Resubscribe URL (Action)'),
'eventQueueId' => ts('Event Queue ID'),
));
}

/**
* @inheritDoc
*/
public function evaluateToken(
\Civi\Token\TokenRow $row,
$entity,
$field,
$prefetch = NULL
) {
// Most CiviMail action tokens were implemented via getActionTokenReplacement().
// However, {action.subscribeUrl} has a second implementation via
// replaceSubscribeInviteTokens(). The two appear mostly the same.
// We use getActionTokenReplacement() since it's more consistent. However,
// this doesn't provide the dynamic/parameterized tokens of
// replaceSubscribeInviteTokens().

if (empty($row->context['mailingJobId']) || empty($row->context['mailingActionTarget']['hash'])) {
throw new \CRM_Core_Exception("Error: Cannot use action tokens unless context defines mailingJobId and mailingActionTarget.");
}

if ($field === 'eventQueueId') {
$row->format('text/plain')->tokens($entity, $field, $row->context['mailingActionTarget']['id']);
return;
}

list($verp, $urls) = CRM_Mailing_BAO_Mailing::getVerpAndUrls(
$row->context['mailingJobId'],
$row->context['mailingActionTarget']['id'],
$row->context['mailingActionTarget']['hash'],
// Note: Behavior is already undefined for SMS/'phone' mailings...
$row->context['mailingActionTarget']['email']
);

$row->format('text/plain')->tokens($entity, $field,
CRM_Utils_Token::getActionTokenReplacement(
$field, $verp, $urls, FALSE));
$row->format('text/html')->tokens($entity, $field,
CRM_Utils_Token::getActionTokenReplacement(
$field, $verp, $urls, TRUE));
}

}
56 changes: 54 additions & 2 deletions CRM/Mailing/BAO/Mailing.php
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ private function getPreparedTemplates() {
* @return array
* reference to an assoc array
*/
private function &getTemplates() {
public function getTemplates() {
if (!$this->templates) {
$this->getHeaderFooter();
$this->templates = array();
Expand Down Expand Up @@ -1071,7 +1071,7 @@ public static function getVerpAndUrls($job_id, $event_queue_id, $hash, $email) {
* @return array
* array ref that hold array refs to the verp info, urls, and headers
*/
private function getVerpAndUrlsAndHeaders($job_id, $event_queue_id, $hash, $email, $isForward = FALSE) {
public function getVerpAndUrlsAndHeaders($job_id, $event_queue_id, $hash, $email, $isForward = FALSE) {
$config = CRM_Core_Config::singleton();

/**
Expand Down Expand Up @@ -3197,4 +3197,56 @@ public static function getPublicViewUrl($id, $absolute = TRUE) {
}
}

/**
* Get a list of template types which can be used as `civicrm_mailing.template_type`.
*
* @return array
* A list of template-types, keyed numerically. Each defines:
* - name: string, a short symbolic name
* - editorUrl: string, Angular template name
*
* Ex: $templateTypes[0] === array('name' => 'mosaico', 'editorUrl' => '~/crmMosaico/editor.html').
*/
public static function getTemplateTypes() {
if (!isset(Civi::$statics[__CLASS__]['templateTypes'])) {
$types = array();
$types[] = array(
'name' => 'traditional',
'editorUrl' => CRM_Mailing_Info::workflowEnabled() ? '~/crmMailing/EditMailingCtrl/workflow.html' : '~/crmMailing/EditMailingCtrl/2step.html',
'weight' => 0,
);

CRM_Utils_Hook::mailingTemplateTypes($types);

$defaults = array('weight' => 0);
foreach (array_keys($types) as $typeName) {
$types[$typeName] = array_merge($defaults, $types[$typeName]);
}
usort($types, function ($a, $b) {
if ($a['weight'] === $b['weight']) {
return 0;
}
return $a['weight'] < $b['weight'] ? -1 : 1;
});

Civi::$statics[__CLASS__]['templateTypes'] = $types;
}

return Civi::$statics[__CLASS__]['templateTypes'];
}

/**
* Get a list of template types.
*
* @return array
* Array(string $name => string $label).
*/
public static function getTemplateTypeNames() {
$r = array();
foreach (self::getTemplateTypes() as $type) {
$r[$type['name']] = $type['name'];
}
return $r;
}

}
132 changes: 76 additions & 56 deletions CRM/Mailing/BAO/MailingJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,13 @@ public static function runJobs($testParams = NULL, $mode = NULL) {
}

// Compose and deliver each child job
$isComplete = $job->deliver($mailer, $testParams);
if (Civi::container()->hasParameter('civi_flexmailer_callback')) {
$cb = Civi::container()->getParameter('civi_flexmailer_callback');
$isComplete = Civi\Core\Resolver::singleton()->call($cb, array($job, $mailer, $testParams));
}
else {
$isComplete = $job->deliver($mailer, $testParams);
}

CRM_Utils_Hook::post('create', 'CRM_Mailing_DAO_Spool', $job->id, $isComplete);

Expand Down Expand Up @@ -492,62 +498,15 @@ public function queue($testParams = NULL) {
* @param array $testParams
*/
public function deliver(&$mailer, $testParams = NULL) {
if (\Civi::settings()->get('experimentalFlexMailerEngine')) {
throw new \RuntimeException("Cannot use legacy deliver() when experimentalFlexMailerEngine is enabled");
}

$mailing = new CRM_Mailing_BAO_Mailing();
$mailing->id = $this->mailing_id;
$mailing->find(TRUE);
$mailing->free();

$eq = new CRM_Mailing_Event_BAO_Queue();
$eqTable = CRM_Mailing_Event_BAO_Queue::getTableName();
$emailTable = CRM_Core_BAO_Email::getTableName();
$phoneTable = CRM_Core_DAO_Phone::getTableName();
$contactTable = CRM_Contact_BAO_Contact::getTableName();
$edTable = CRM_Mailing_Event_BAO_Delivered::getTableName();
$ebTable = CRM_Mailing_Event_BAO_Bounce::getTableName();

$query = " SELECT $eqTable.id,
$emailTable.email as email,
$eqTable.contact_id,
$eqTable.hash,
NULL as phone
FROM $eqTable
INNER JOIN $emailTable
ON $eqTable.email_id = $emailTable.id
INNER JOIN $contactTable
ON $contactTable.id = $emailTable.contact_id
LEFT JOIN $edTable
ON $eqTable.id = $edTable.event_queue_id
LEFT JOIN $ebTable
ON $eqTable.id = $ebTable.event_queue_id
WHERE $eqTable.job_id = " . $this->id . "
AND $edTable.id IS null
AND $ebTable.id IS null
AND $contactTable.is_opt_out = 0";

if ($mailing->sms_provider_id) {
$query = "
SELECT $eqTable.id,
$phoneTable.phone as phone,
$eqTable.contact_id,
$eqTable.hash,
NULL as email
FROM $eqTable
INNER JOIN $phoneTable
ON $eqTable.phone_id = $phoneTable.id
INNER JOIN $contactTable
ON $contactTable.id = $phoneTable.contact_id
LEFT JOIN $edTable
ON $eqTable.id = $edTable.event_queue_id
LEFT JOIN $ebTable
ON $eqTable.id = $ebTable.event_queue_id
WHERE $eqTable.job_id = " . $this->id . "
AND $edTable.id IS null
AND $ebTable.id IS null
AND ( $contactTable.is_opt_out = 0
OR $contactTable.do_not_sms = 0 )";
}
$eq->query($query);

$config = NULL;

if ($config == NULL) {
Expand Down Expand Up @@ -581,11 +540,8 @@ public function deliver(&$mailer, $testParams = NULL) {

// make sure that there's no more than $mailerBatchLimit mails processed in a run
$mailerBatchLimit = Civi::settings()->get('mailerBatchLimit');
$eq = self::findPendingTasks($this->id, $mailing->sms_provider_id ? 'sms' : 'email');
while ($eq->fetch()) {
// if ( ( $mailsProcessed % 100 ) == 0 ) {
// CRM_Utils_System::xMemory( "$mailsProcessed: " );
// }

if ($mailerBatchLimit > 0 && self::$mailsProcessed >= $mailerBatchLimit) {
if (!empty($fields)) {
$this->deliverGroup($fields, $mailing, $mailer, $job_date, $attachments);
Expand Down Expand Up @@ -1037,4 +993,68 @@ public function writeToDB(
return $result;
}

/**
* Search the mailing-event queue for a list of pending delivery tasks.
*
* @param int $jobId
* @param string $medium
* Ex: 'email' or 'sms'.
*
* @return \CRM_Mailing_Event_BAO_Queue
* A query object whose rows provide ('id', 'contact_id', 'hash') and ('email' or 'phone').
*/
public static function findPendingTasks($jobId, $medium) {
$eq = new CRM_Mailing_Event_BAO_Queue();
$queueTable = CRM_Mailing_Event_BAO_Queue::getTableName();
$emailTable = CRM_Core_BAO_Email::getTableName();
$phoneTable = CRM_Core_BAO_Phone::getTableName();
$contactTable = CRM_Contact_BAO_Contact::getTableName();
$deliveredTable = CRM_Mailing_Event_BAO_Delivered::getTableName();
$bounceTable = CRM_Mailing_Event_BAO_Bounce::getTableName();

$query = " SELECT $queueTable.id,
$emailTable.email as email,
$queueTable.contact_id,
$queueTable.hash,
NULL as phone
FROM $queueTable
INNER JOIN $emailTable
ON $queueTable.email_id = $emailTable.id
INNER JOIN $contactTable
ON $contactTable.id = $emailTable.contact_id
LEFT JOIN $deliveredTable
ON $queueTable.id = $deliveredTable.event_queue_id
LEFT JOIN $bounceTable
ON $queueTable.id = $bounceTable.event_queue_id
WHERE $queueTable.job_id = " . $jobId . "
AND $deliveredTable.id IS null
AND $bounceTable.id IS null
AND $contactTable.is_opt_out = 0";

if ($medium === 'sms') {
$query = "
SELECT $queueTable.id,
$phoneTable.phone as phone,
$queueTable.contact_id,
$queueTable.hash,
NULL as email
FROM $queueTable
INNER JOIN $phoneTable
ON $queueTable.phone_id = $phoneTable.id
INNER JOIN $contactTable
ON $contactTable.id = $phoneTable.contact_id
LEFT JOIN $deliveredTable
ON $queueTable.id = $deliveredTable.event_queue_id
LEFT JOIN $bounceTable
ON $queueTable.id = $bounceTable.event_queue_id
WHERE $queueTable.job_id = " . $jobId . "
AND $deliveredTable.id IS null
AND $bounceTable.id IS null
AND ( $contactTable.is_opt_out = 0
OR $contactTable.do_not_sms = 0 )";
}
$eq->query($query);
return $eq;
}

}
Loading