From 703875d8a8fe2e2e5a43e08f776381bcbf3d98c2 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 28 Nov 2016 14:09:20 -0800 Subject: [PATCH 1/3] CRM-19690 - Declare Mailing.template_type, Mailing.template_options, Hook::mailingTemplateTypes. This adds two new fields to the schema. Template types can be declared (along with preferred editor) using hook_civicrm_mailingTemplateTypes, e.g. ``` function mymod_civicrm_mailingTemplateTypes(&$types) { $types[] = array( 'name' => 'moasico', 'editorUrl' => '~/crmMosaico/EditMailingCtrl/mosaico.html', ); } ``` Note: This only stores data. Other commits will make use of that data in more meaningful ways. --- CRM/Core/DAO/AllCoreTables.data.php | 2 +- CRM/Mailing/BAO/Mailing.php | 47 +++++++++++++++++++++++ CRM/Mailing/DAO/Mailing.php | 33 +++++++++++++++- CRM/Mailing/Info.php | 1 + CRM/Upgrade/Incremental/php/FourSeven.php | 21 ++++++++++ CRM/Utils/Hook.php | 17 ++++++++ tests/phpunit/api/v3/MailingTest.php | 8 ++++ xml/schema/Mailing/Mailing.xml | 18 +++++++++ 8 files changed, 145 insertions(+), 2 deletions(-) diff --git a/CRM/Core/DAO/AllCoreTables.data.php b/CRM/Core/DAO/AllCoreTables.data.php index 89b41a453f81..4bcc9b1564f8 100644 --- a/CRM/Core/DAO/AllCoreTables.data.php +++ b/CRM/Core/DAO/AllCoreTables.data.php @@ -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', diff --git a/CRM/Mailing/BAO/Mailing.php b/CRM/Mailing/BAO/Mailing.php index ec91ca3acf5a..fd76fa85379b 100644 --- a/CRM/Mailing/BAO/Mailing.php +++ b/CRM/Mailing/BAO/Mailing.php @@ -3197,4 +3197,51 @@ public static function getPublicViewUrl($id, $absolute = TRUE) { } } + /** + * @return array + * A list of template-types, keyed by name. Each defines: + * - editorUrl: string, Angular template name + * + * Ex: $templateTypes['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']; + } + + /** + * @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; + } + } diff --git a/CRM/Mailing/DAO/Mailing.php b/CRM/Mailing/DAO/Mailing.php index b95f0eea059a..bedf306e38fc 100644 --- a/CRM/Mailing/DAO/Mailing.php +++ b/CRM/Mailing/DAO/Mailing.php @@ -30,7 +30,7 @@ * * Generated from xml/schema/CRM/Mailing/Mailing.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:2ced8fea80d92e36fe16baa7daa73c7f) + * (GenCodeChecksum:99efa41e4294197973111d4d5ced5972) */ require_once 'CRM/Core/DAO.php'; require_once 'CRM/Utils/Type.php'; @@ -124,6 +124,18 @@ class CRM_Mailing_DAO_Mailing extends CRM_Core_DAO { * @var string */ public $replyto_email; + /** + * The language/processing system used for email templates. + * + * @var string + */ + public $template_type; + /** + * Advanced options used by the email templating system. (JSON encoded) + * + * @var longtext + */ + public $template_options; /** * Subject of mailing * @@ -444,6 +456,25 @@ static function &fields() { 'type' => 'Text', ) , ) , + 'template_type' => array( + 'name' => 'template_type', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Template Type') , + 'description' => 'The language/processing system used for email templates.', + 'required' => true, + 'maxlength' => 64, + 'size' => CRM_Utils_Type::BIG, + 'default' => 'traditional', + 'pseudoconstant' => array( + 'callback' => 'CRM_Mailing_BAO_Mailing::getTemplateTypeNames', + ) + ) , + 'template_options' => array( + 'name' => 'template_options', + 'type' => CRM_Utils_Type::T_LONGTEXT, + 'title' => ts('Template Options (JSON)') , + 'description' => 'Advanced options used by the email templating system. (JSON encoded)', + ) , 'subject' => array( 'name' => 'subject', 'type' => CRM_Utils_Type::T_STRING, diff --git a/CRM/Mailing/Info.php b/CRM/Mailing/Info.php index 3444383c03a7..e946b527633b 100644 --- a/CRM/Mailing/Info.php +++ b/CRM/Mailing/Info.php @@ -166,6 +166,7 @@ public function getAngularModules() { CRM_Core_Resources::singleton() ->addSetting(array( 'crmMailing' => array( + 'templateTypes' => CRM_Mailing_BAO_Mailing::getTemplateTypes(), 'civiMails' => $civiMails['values'], 'campaignEnabled' => in_array('CiviCampaign', $config->enableComponents), 'groupNames' => $groupNames['values'], diff --git a/CRM/Upgrade/Incremental/php/FourSeven.php b/CRM/Upgrade/Incremental/php/FourSeven.php index 140eafc0e2e8..e51dee5950a5 100644 --- a/CRM/Upgrade/Incremental/php/FourSeven.php +++ b/CRM/Upgrade/Incremental/php/FourSeven.php @@ -287,6 +287,16 @@ public function upgrade_4_7_15($rev) { $this->addTask(ts('Upgrade DB to %1: SQL', array(1 => $rev)), 'runSql', $rev); } + /** + * Upgrade function. + * + * @param string $rev + */ + public function upgrade_4_7_16($rev) { + $this->addTask(ts('Upgrade DB to %1: SQL', array(1 => $rev)), 'runSql', $rev); + $this->addTask('Add new CiviMail fields', 'addMailingTemplateType'); + } + /* * Important! All upgrade functions MUST add a 'runSql' task. * Uncomment and use the following template for a new upgrade version @@ -879,6 +889,17 @@ public static function alterIndexAndTypeForImageURL() { return TRUE; } + public static function addMailingTemplateType() { + if (!CRM_Core_DAO::checkFieldExists('civicrm_mailing', 'template_type', FALSE)) { + CRM_Core_DAO::executeQuery(' + ALTER TABLE civicrm_mailing + ADD COLUMN `template_type` varchar(64) NOT NULL DEFAULT \'traditional\' COMMENT \'The language/processing system used for email templates.\', + ADD COLUMN `template_options` longtext COMMENT \'Advanced options used by the email templating system. (JSON encoded)\' + '); + } + return TRUE; + } + /** * CRM-18651 Add DataType column to Option Group Table * @return bool diff --git a/CRM/Utils/Hook.php b/CRM/Utils/Hook.php index b762478c61a1..30deaad3eb00 100644 --- a/CRM/Utils/Hook.php +++ b/CRM/Utils/Hook.php @@ -896,6 +896,23 @@ public static function mailingGroups(&$form, &$groups, &$mailings) { ); } + /** + * (Experimental) Modify the list of template-types used for CiviMail composition. + * + * @param array $types + * Sequentially indexed list of template types. Each type specifies: + * - name: string + * - editorUrl: string, Angular template URL + * - weight: int, priority when picking a default value for new mailings + * @return mixed + */ + public static function mailingTemplateTypes(&$types) { + return self::singleton()->invoke(1, $types, self::$_nullObject, self::$_nullObject, + self::$_nullObject, self::$_nullObject, self::$_nullObject, + 'civicrm_mailingTemplateTypes' + ); + } + /** * This hook is called when composing the array of membershipTypes and their cost during a membership registration * (new or renewal). diff --git a/tests/phpunit/api/v3/MailingTest.php b/tests/phpunit/api/v3/MailingTest.php index 7378b63acd25..d8c65c17d6e0 100644 --- a/tests/phpunit/api/v3/MailingTest.php +++ b/tests/phpunit/api/v3/MailingTest.php @@ -85,6 +85,14 @@ public function testMailerCreateSuccess() { $this->getAndCheck($this->_params, $result['id'], 'mailing'); } + /** + * + */ + public function testTemplateTypeOptions() { + $types = $this->callAPISuccess('Mailing', 'getoptions', array('field' => 'template_type')); + $this->assertTrue(isset($types['values']['traditional'])); + } + /** * The Mailing.create API supports magic properties "groups[include,enclude]" and "mailings[include,exclude]". * Make sure these work diff --git a/xml/schema/Mailing/Mailing.xml b/xml/schema/Mailing/Mailing.xml index d655f5535937..20a0fe6bf49e 100644 --- a/xml/schema/Mailing/Mailing.xml +++ b/xml/schema/Mailing/Mailing.xml @@ -159,6 +159,24 @@ Text + + template_type + Template Type + varchar + 64 + 'traditional' + true + The language/processing system used for email templates. + + CRM_Mailing_BAO_Mailing::getTemplateTypeNames + + + + template_options + Template Options (JSON) + longtext + Advanced options used by the email templating system. (JSON encoded) + subject varchar From 6bc3944aad39af1b16bb68c187b0e3879562604d Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 2 Dec 2016 10:24:02 -0800 Subject: [PATCH 2/3] CRM-19690 - Mailing API - Encode and decode `template_options` Present API consumers with a consistent, array-based interface for reading and writing properties in a Mailing. Suppose you're submitting a REST request to create a mailing. The REST request as a whole is encoded with JSON. With the default API behavior, you would need to double-encode the `template_options`, e.g. roughly ``` POST rest.php json_encode([ entity => Mailing action => create params => [ template_options => json_encode([...]) ] ]) ``` With this patch, you only need to encode the request once. This parallels the approach used in CaseType API (for the XML `definition` field). --- api/v3/Mailing.php | 28 ++++++++++++++++++-- tests/phpunit/api/v3/MailingTest.php | 38 ++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/api/v3/Mailing.php b/api/v3/Mailing.php index c456276c7581..35d3f2fb4cda 100644 --- a/api/v3/Mailing.php +++ b/api/v3/Mailing.php @@ -43,6 +43,9 @@ * @throws \Civi\API\Exception\UnauthorizedException */ function civicrm_api3_mailing_create($params) { + if (isset($params['template_options']) && is_array($params['template_options'])) { + $params['template_options'] = $params['template_options'] === array() ? '{}' : json_encode($params['template_options']); + } if (CRM_Mailing_Info::workflowEnabled()) { // Note: 'schedule mailings' and 'approve mailings' can update certain fields, but can't create. @@ -64,7 +67,8 @@ function civicrm_api3_mailing_create($params) { $safeParams = $params; } $safeParams['_evil_bao_validator_'] = 'CRM_Mailing_BAO_Mailing::checkSendable'; - return _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $safeParams); + $result = _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $safeParams); + return _civicrm_api3_mailing_get_formatResult($result); } @@ -238,7 +242,27 @@ function civicrm_api3_mailing_delete($params) { * @return array */ function civicrm_api3_mailing_get($params) { - return _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params); + $result = _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params); + return _civicrm_api3_mailing_get_formatResult($result); +} + +/** + * Format definition. + * + * @param array $result + * + * @return array + * @throws \CRM_Core_Exception + */ +function _civicrm_api3_mailing_get_formatResult($result) { + if (isset($result['values']) && is_array($result['values'])) { + foreach ($result['values'] as $key => $caseType) { + if (isset($result['values'][$key]['template_options']) && is_string($result['values'][$key]['template_options'])) { + $result['values'][$key]['template_options'] = json_decode($result['values'][$key]['template_options'], TRUE); + } + } + } + return $result; } /** diff --git a/tests/phpunit/api/v3/MailingTest.php b/tests/phpunit/api/v3/MailingTest.php index d8c65c17d6e0..eb60ca10855c 100644 --- a/tests/phpunit/api/v3/MailingTest.php +++ b/tests/phpunit/api/v3/MailingTest.php @@ -93,6 +93,44 @@ public function testTemplateTypeOptions() { $this->assertTrue(isset($types['values']['traditional'])); } + /** + * The `template_options` field should be treated a JSON object. + * + * This test will create, read, and update the field. + */ + public function testMailerCreateTemplateOptions() { + // 1. Create mailing with template_options. + $params = $this->_params; + $params['template_options'] = json_encode(array('foo' => 'bar_1')); + $createResult = $this->callAPISuccess('mailing', 'create', $params); + $id = $createResult['id']; + $this->assertDBQuery('{"foo":"bar_1"}', 'SELECT template_options FROM civicrm_mailing WHERE id = %1', array( + 1 => array($id, 'Int'), + )); + $this->assertEquals('bar_1', $createResult['values'][$id]['template_options']['foo']); + + // 2. Get mailing with template_options. + $getResult = $this->callAPISuccess('mailing', 'get', array( + 'id' => $id, + )); + $this->assertEquals('bar_1', $getResult['values'][$id]['template_options']['foo']); + $getValueResult = $this->callAPISuccess('mailing', 'getvalue', array( + 'id' => $id, + 'return' => 'template_options', + )); + $this->assertEquals('bar_1', $getValueResult['foo']); + + // 3. Update mailing with template_options. + $updateResult = $this->callAPISuccess('mailing', 'create', array( + 'id' => $id, + 'template_options' => array('foo' => 'bar_2'), + )); + $this->assertDBQuery('{"foo":"bar_2"}', 'SELECT template_options FROM civicrm_mailing WHERE id = %1', array( + 1 => array($id, 'Int'), + )); + $this->assertEquals('bar_2', $updateResult['values'][$id]['template_options']['foo']); + } + /** * The Mailing.create API supports magic properties "groups[include,enclude]" and "mailings[include,exclude]". * Make sure these work From fb84048288817bf6c9b4bd7260939bd983f2d397 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 19 Dec 2016 19:00:40 -0800 Subject: [PATCH 3/3] (NFC) CRM-19690 - Improve code-style and docblocks --- CRM/Mailing/BAO/Mailing.php | 9 +++++++-- api/v3/Mailing.php | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CRM/Mailing/BAO/Mailing.php b/CRM/Mailing/BAO/Mailing.php index fd76fa85379b..5915f6a63e36 100644 --- a/CRM/Mailing/BAO/Mailing.php +++ b/CRM/Mailing/BAO/Mailing.php @@ -3198,11 +3198,14 @@ 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 by name. Each defines: + * A list of template-types, keyed numerically. Each defines: + * - name: string, a short symbolic name * - editorUrl: string, Angular template name * - * Ex: $templateTypes['mosaico']['editorUrl'] = '~/crmMosaico/editor.html'. + * Ex: $templateTypes[0] === array('name' => 'mosaico', 'editorUrl' => '~/crmMosaico/editor.html'). */ public static function getTemplateTypes() { if (!isset(Civi::$statics[__CLASS__]['templateTypes'])) { @@ -3233,6 +3236,8 @@ public static function getTemplateTypes() { } /** + * Get a list of template types. + * * @return array * Array(string $name => string $label). */ diff --git a/api/v3/Mailing.php b/api/v3/Mailing.php index 35d3f2fb4cda..765fc275b085 100644 --- a/api/v3/Mailing.php +++ b/api/v3/Mailing.php @@ -44,7 +44,7 @@ */ function civicrm_api3_mailing_create($params) { if (isset($params['template_options']) && is_array($params['template_options'])) { - $params['template_options'] = $params['template_options'] === array() ? '{}' : json_encode($params['template_options']); + $params['template_options'] = ($params['template_options'] === array()) ? '{}' : json_encode($params['template_options']); } if (CRM_Mailing_Info::workflowEnabled()) { // Note: 'schedule mailings' and 'approve mailings' can update certain fields, but can't create.