summaryrefslogtreecommitdiff
path: root/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB
diff options
context:
space:
mode:
Diffstat (limited to 'www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB')
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockMailing.html196
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockMailing.js32
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockSetup.html67
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockSetup.js32
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl.js149
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/edit.html178
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/main.html10
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/report.html194
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ListCtrl.html63
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ListCtrl.js12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/NewCtrl.js11
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ReportCtrl.js56
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Slider.html25
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Slider.js60
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Stats.js280
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/WinnerDialogCtrl.html19
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/WinnerDialogCtrl.js43
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/services.js234
18 files changed, 1661 insertions, 0 deletions
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockMailing.html b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockMailing.html
new file mode 100644
index 00000000..92448156
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockMailing.html
@@ -0,0 +1,196 @@
+<!--
+Required vars: abtest, fields
+
+Note: Much of this file is duplicated in crmMailing and crmMailingAB with variations on placement/title/binding.
+It could perhaps be thinned by 30-60% by making more directives.
+
+This template follows a basic pattern. For each included field, there are three variants, as in this example:
+ - fromAddress: The default From: address shared by both mailings (representatively mapped to mailing A)
+ - fromAddressA: The From: address for mailing A
+ - fromAddressB: The From: address for mailing B
+Each variant is guarded with "ng-if='fields.fieldName'"; if true, the field will be displayed and
+processed by Angular; if false, the field will be hidden and completely ignored by Angular.
+-->
+<div class="crm-block" ng-form="subform" crm-ui-id-scope>
+ <div class="crm-group">
+
+
+ <div crm-ui-field="{name: 'subform.msg_template_id', title: ts('Template')}" ng-if="fields.msg_template_id">
+ <div ng-controller="MsgTemplateCtrl">
+ <select
+ crm-ui-id="subform.msg_template_id"
+ name="msg_template_id"
+ class="fa-clipboard"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Message Template')}"
+ ng-model="abtest.mailings.a.msg_template_id"
+ ng-change="loadTemplate(abtest.mailings.a, abtest.mailings.a.msg_template_id)"
+ >
+ <option value=""></option>
+ <option ng-repeat="frm in crmMsgTemplates.getAll() | orderBy:'msg_title'" ng-value="frm.id">{{frm.msg_title}}</option>
+ </select>
+ <a crm-icon="fa-floppy-o" ng-click="saveTemplate(abtest.mailings.a)" class="crm-hover-button" title="{{ts('Save As')}}"></a>
+ </div>
+ </div>
+ <div crm-ui-field="{name: 'subform.msg_template_idA', title: ts('Template (A)')}" ng-if="fields.msg_template_idA">
+ <div ng-controller="MsgTemplateCtrl">
+ <select
+ crm-ui-id="subform.msg_template_idA"
+ name="msg_template_idA"
+ class="fa-clipboard"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Message Template')}"
+ ng-model="abtest.mailings.a.msg_template_id"
+ ng-change="loadTemplate(abtest.mailings.a, abtest.mailings.a.msg_template_id)"
+ >
+ <option value=""></option>
+ <option ng-repeat="frm in crmMsgTemplates.getAll() | orderBy:'msg_title'" ng-value="frm.id">{{frm.msg_title}}</option>
+ </select>
+ <a crm-icon="fa-floppy-o" ng-click="saveTemplate(abtest.mailings.a)" class="crm-hover-button" title="{{ts('Save As')}}"></a>
+ </div>
+ </div>
+ <div crm-ui-field="{name: 'subform.msg_template_idB', title: ts('Template (B)')}" ng-if="fields.msg_template_idB">
+ <div ng-controller="MsgTemplateCtrl">
+ <select
+ crm-ui-id="subform.msg_template_idB"
+ name="msg_template_idB"
+ class="fa-clipboard"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Message Template')}"
+ ng-model="abtest.mailings.b.msg_template_id"
+ ng-change="loadTemplate(abtest.mailings.b, abtest.mailings.b.msg_template_id)"
+ >
+ <option value=""></option>
+ <option ng-repeat="frm in crmMsgTemplates.getAll() | orderBy:'msg_title'" ng-value="frm.id">{{frm.msg_title}}</option>
+ </select>
+ <a crm-icon="fa-floppy-o" ng-click="saveTemplate(abtest.mailings.b)" class="crm-hover-button" title="{{ts('Save As')}}"></a>
+ </div>
+ </div>
+
+
+ <div crm-ui-field="{name: 'subform.fromAddress', title: ts('From'), help: hs('from_email')}" ng-if="fields.fromAddress">
+ <span ng-controller="EmailAddrCtrl" crm-mailing-from-address="fromPlaceholder" crm-mailing="abtest.mailings.a">
+ <select
+ crm-ui-id="subform.fromAddress"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: false, placeholder: ts('Email address')}"
+ name="fromAddress"
+ ng-model="fromPlaceholder.label"
+ required>
+ <option value=""></option>
+ <option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
+ </select>
+ </span>
+ </div>
+ <div crm-ui-field="{name: 'subform.fromAddressA', title: ts('From (A)'), help: hs('from_email')}" ng-if="fields.fromAddressA">
+ <span ng-controller="EmailAddrCtrl" crm-mailing-from-address="fromPlaceholder" crm-mailing="abtest.mailings.a">
+ <select
+ crm-ui-id="subform.fromAddressA"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: false, placeholder: ts('Email address')}"
+ name="fromAddressA"
+ ng-model="fromPlaceholder.label"
+ required>
+ <option value=""></option>
+ <option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
+ </select>
+ </span>
+ </div>
+ <div crm-ui-field="{name: 'subform.fromAddressB', title: ts('From (B)'), help: hs('from_email')}" ng-if="fields.fromAddressB">
+ <span ng-controller="EmailAddrCtrl" crm-mailing-from-address="fromPlaceholder" crm-mailing="abtest.mailings.b">
+ <select
+ crm-ui-id="subform.fromAddressB"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: false, placeholder: ts('Email address')}"
+ name="fromAddressB"
+ ng-model="fromPlaceholder.label"
+ required>
+ <option value=""></option>
+ <option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
+ </select>
+ </span>
+ </div>
+
+
+ <div crm-ui-field="{name: 'subform.replyTo', title: ts('Reply-To')}" ng-show="crmMailingConst.enableReplyTo" ng-if="fields.replyTo">
+ <span ng-controller="EmailAddrCtrl">
+ <select
+ crm-ui-id="subform.replyTo"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Email address')}"
+ name="replyTo"
+ ng-change="checkReplyToChange(abtest.mailings.a)"
+ ng-model="abtest.mailings.a.replyto_email"
+ >
+ <option value=""></option>
+ <option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
+ </select>
+ </span>
+ </div>
+ <div crm-ui-field="{name: 'subform.replyToA', title: ts('Reply-To (A)')}" ng-show="crmMailingConst.enableReplyTo" ng-if="fields.replyToA">
+ <span ng-controller="EmailAddrCtrl">
+ <select
+ crm-ui-id="subform.replyToA"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Email address')}"
+ name="replyToA"
+ ng-change="checkReplyToChange(abtest.mailings.a)"
+ ng-model="abtest.mailings.a.replyto_email"
+ >
+ <option value=""></option>
+ <option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
+ </select>
+ </span>
+ </div>
+ <div crm-ui-field="{name: 'subform.replyToB', title: ts('Reply-To (B)')}" ng-show="crmMailingConst.enableReplyTo" ng-if="fields.replyToB">
+ <span ng-controller="EmailAddrCtrl">
+ <select
+ crm-ui-id="subform.replyToB"
+ crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Email address')}"
+ name="replyToB"
+ ng-change="checkReplyToChange(abtest.mailings.b)"
+ ng-model="abtest.mailings.b.replyto_email"
+ >
+ <option value=""></option>
+ <option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
+ </select>
+ </span>
+ </div>
+
+
+ <div crm-ui-field="{name: 'subform.subject', title: ts('Subject')}" ng-if="fields.subject">
+ <div style="float: right;">
+ <input crm-mailing-token on-select="$broadcast('insert:subject', token.name)" tabindex="-1">
+ </div>
+ <input
+ crm-ui-id="subform.subject"
+ crm-ui-insert-rx="insert:subject"
+ type="text"
+ class="crm-form-text"
+ ng-model="abtest.mailings.a.subject"
+ required
+ placeholder="Subject"
+ name="subject" >
+ </div>
+ <div crm-ui-field="{name: 'subform.subjectA', title: ts('Subject (A)')}" ng-if="fields.subjectA">
+ <div style="float: right;">
+ <input crm-mailing-token on-select="$broadcast('insert:subjectA', token.name)" tabindex="-1">
+ </div>
+ <input
+ crm-ui-id="subform.subjectA"
+ crm-ui-insert-rx="insert:subjectA"
+ type="text"
+ class="crm-form-text"
+ ng-model="abtest.mailings.a.subject"
+ required
+ placeholder="Subject"
+ name="subjectA" >
+ </div>
+ <div crm-ui-field="{name: 'subform.subjectB', title: ts('Subject (B)')}" ng-if="fields.subjectB">
+ <div style="float: right;">
+ <input crm-mailing-token on-select="$broadcast('insert:subjectB', token.name)" tabindex="-1">
+ </div>
+ <input
+ crm-ui-id="subform.subjectB"
+ crm-ui-insert-rx="insert:subjectB"
+ type="text"
+ class="crm-form-text"
+ ng-model="abtest.mailings.b.subject"
+ required
+ placeholder="Subject"
+ name="subjectB" >
+ </div>
+ </div>
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockMailing.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockMailing.js
new file mode 100644
index 00000000..d738ab6e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockMailing.js
@@ -0,0 +1,32 @@
+(function(angular, $, _) {
+
+ // example:
+ // scope.myAbtest = new CrmMailingAB();
+ // <crm-mailing-ab-block-mailing="{fromAddressA: 1, fromAddressB: 1}" crm-abtest="myAbtest" />
+ var simpleDirectives = {
+ crmMailingAbBlockMailing: '~/crmMailingAB/BlockMailing.html'
+ };
+ _.each(simpleDirectives, function(templateUrl, directiveName) {
+ angular.module('crmMailingAB').directive(directiveName, function($parse, crmMailingABCriteria, crmUiHelp) {
+ var scopeDesc = {crmAbtest: '@'};
+ scopeDesc[directiveName] = '@';
+
+ return {
+ scope: scopeDesc,
+ templateUrl: templateUrl,
+ link: function(scope, elm, attr) {
+ var model = $parse(attr.crmAbtest);
+ scope.abtest = model(scope.$parent);
+ scope.crmMailingConst = CRM.crmMailing;
+ scope.crmMailingABCriteria = crmMailingABCriteria;
+ scope.ts = CRM.ts(null);
+ scope.hs = crmUiHelp({file: 'CRM/Mailing/MailingUI'});
+
+ var fieldsModel = $parse(attr[directiveName]);
+ scope.fields = fieldsModel(scope.$parent);
+ }
+ };
+ });
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockSetup.html b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockSetup.html
new file mode 100644
index 00000000..7d124cc0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockSetup.html
@@ -0,0 +1,67 @@
+<div class="crm-block" ng-form="setupForm" crm-ui-id-scope>
+ <div class="crm-group">
+ <div class="help" ng-if="fields.help">
+ {{ts('A/B testing allows you to send two test mailings to a random subset of your recipients. After collecting and comparing metrics, the more successful mailing will be sent to the remaining recipients.')}}
+ </div>
+ <div crm-ui-field="{name: 'setupForm.abName', title: ts('Name'), help: hs('name')}" ng-if="fields.abName">
+ <input type="text"
+ crm-ui-id="setupForm.abName"
+ name="abName"
+ ng-model="abtest.ab.name"
+ class="crm-form-text"
+ placeholder="A/B Test Name"
+ required/>
+ </div>
+ <div crm-ui-field="{name: 'setupForm.campaign', title: ts('Campaign'), help: hs({id: 'id-campaign_id', file: 'CRM/Campaign/Form/addCampaignToComponent'})}" ng-show="crmMailingConst.campaignEnabled"
+ ng-if="fields.campaign">
+ <input
+ crm-entityref="{entity: 'Campaign', select: {allowClear: true, placeholder: ts('Select Campaign')}}"
+ crm-ui-id="setupForm.campaign"
+ name="campaign"
+ ng-model="abtest.mailings.a.campaign_id"
+ ng-change="abtest.mailings.b.campaign_id=abtest.mailings.a.campaign_id"
+ />
+ </div>
+ <div crm-ui-field="{title: ts('Test Type')}" ng-if="fields.testing_criteria">
+ <div ng-repeat="criteria in crmMailingABCriteria.getAll()">
+ <label>
+ <input name="testing_criteria" ng-model="abtest.ab.testing_criteria" type="radio"
+ value="{{criteria.value}}" required/>
+ {{criteria.label}}
+ </label>
+ </div>
+ </div>
+ <div crm-ui-field="{name: 'setupForm.recipients', title: ts('Recipients')}" ng-if="fields.recipients">
+ <div crm-mailing-block-recipients="{name: 'recipients', id: 'setupForm.recipients'}" crm-mailing="abtest.mailings.a"></div>
+ </div>
+ <div crm-ui-field="{title: ts('Distribution')}" ng-if="fields.group_percentage">
+ <div crm-mailing-ab-slider ng-model="abtest.ab.group_percentage"></div>
+ </div>
+ <div crm-ui-field="{title: ts('Send')}" ng-if="fields.scheduled_date">
+ <div crm-mailing-radio-date="schedule" ng-model="abtest.mailings.a.scheduled_date">
+ <div>
+ <input ng-model="schedule.mode" type="radio" name="send" value="now" id="schedule-send-now"/>
+ <label for="schedule-send-now">{{ts('Send A/B test immediately')}}</label>
+ </div>
+ <div>
+ <input ng-model="schedule.mode" type="radio" name="send" value="at" id="schedule-send-at"/>
+ <label for="schedule-send-at">{{ts('Send A/B test at:')}}</label>
+ <input crm-ui-datepicker ng-model="schedule.datetime"/>
+ </div>
+ </div>
+ </div>
+ <div crm-ui-field="{title: ts('Assess')}" ng-if="fields.declare_winning_time">
+ <div crm-mailing-radio-date="assessSched" ng-model="abtest.ab.declare_winning_time">
+ <div>
+ <input ng-model="assessSched.mode" type="radio" name="assess" value="now" id="schedule-assess-now"/>
+ <label for="schedule-assess-now">{{ts('Assess A/B results on an on-going basis')}}</label>
+ </div>
+ <div>
+ <input ng-model="assessSched.mode" type="radio" name="assess" value="at" id="schedule-assess-at"/>
+ <label for="schedule-assess-at">{{ts('Assess A/B test at:')}}</label>
+ <input crm-ui-datepicker ng-model="assessSched.datetime"/>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockSetup.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockSetup.js
new file mode 100644
index 00000000..5809cdd0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/BlockSetup.js
@@ -0,0 +1,32 @@
+(function(angular, $, _) {
+
+ // example:
+ // scope.myAbtest = new CrmMailingAB();
+ // <crm-mailing-ab-block-setup="{abName: 1, group_percentage: 1}" crm-abtest="myAbtest" />
+ var simpleDirectives = {
+ crmMailingAbBlockSetup: '~/crmMailingAB/BlockSetup.html'
+ };
+ _.each(simpleDirectives, function(templateUrl, directiveName) {
+ angular.module('crmMailingAB').directive(directiveName, function($parse, crmMailingABCriteria, crmUiHelp) {
+ var scopeDesc = {crmAbtest: '@'};
+ scopeDesc[directiveName] = '@';
+
+ return {
+ scope: scopeDesc,
+ templateUrl: templateUrl,
+ link: function(scope, elm, attr) {
+ var model = $parse(attr.crmAbtest);
+ scope.abtest = model(scope.$parent);
+ scope.crmMailingConst = CRM.crmMailing;
+ scope.crmMailingABCriteria = crmMailingABCriteria;
+ scope.ts = CRM.ts(null);
+ scope.hs = crmUiHelp({file: 'CRM/Mailing/MailingUI'});
+
+ var fieldsModel = $parse(attr[directiveName]);
+ scope.fields = fieldsModel(scope.$parent);
+ }
+ };
+ });
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl.js
new file mode 100644
index 00000000..b189bafc
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl.js
@@ -0,0 +1,149 @@
+(function(angular, $, _) {
+
+ angular.module('crmMailingAB').controller('CrmMailingABEditCtrl', function($scope, abtest, crmMailingABCriteria, crmMailingMgr, crmMailingPreviewMgr, crmStatus, $q, $location, crmBlocker, $interval, $timeout, CrmAutosaveCtrl, dialogService) {
+ $scope.abtest = abtest;
+ var ts = $scope.ts = CRM.ts(null);
+ var block = $scope.block = crmBlocker();
+ $scope.crmUrl = CRM.url;
+ var myAutosave = null;
+ $scope.crmMailingABCriteria = crmMailingABCriteria;
+ $scope.crmMailingConst = CRM.crmMailing;
+ $scope.checkPerm = CRM.checkPerm;
+
+ $scope.isSubmitted = function isSubmitted() {
+ return _.size(abtest.mailings.a.jobs) > 0 || _.size(abtest.mailings.b.jobs) > 0;
+ };
+
+ $scope.sync = function sync() {
+ abtest.mailings.a.name = ts('Test A (%1)', {1: abtest.ab.name});
+ abtest.mailings.b.name = ts('Test B (%1)', {1: abtest.ab.name});
+ abtest.mailings.c.name = ts('Final (%1)', {1: abtest.ab.name});
+
+ if (abtest.ab.testing_criteria) {
+ // TODO review fields exposed in UI and make sure the sync rules match
+ switch (abtest.ab.testing_criteria) {
+ case 'subject':
+ var exclude_subject = [
+ 'name',
+ 'recipients',
+ 'subject'
+ ];
+ crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, exclude_subject);
+ crmMailingMgr.mergeInto(abtest.mailings.c, abtest.mailings.a, exclude_subject);
+ break;
+ case 'from':
+ var exclude_from = [
+ 'name',
+ 'recipients',
+ 'from_name',
+ 'from_email'
+ ];
+ crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, exclude_from);
+ crmMailingMgr.mergeInto(abtest.mailings.c, abtest.mailings.a, exclude_from);
+ break;
+ case 'full_email':
+ var exclude_full_email = [
+ 'name',
+ 'recipients',
+ 'subject',
+ 'from_name',
+ 'from_email',
+ 'replyto_email',
+ 'override_verp', // keep override_verp and replyto_Email linked
+ 'body_html',
+ 'body_text'
+ ];
+ crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, exclude_full_email);
+ crmMailingMgr.mergeInto(abtest.mailings.c, abtest.mailings.a, exclude_full_email);
+ break;
+ default:
+ throw "Unrecognized testing_criteria";
+ }
+ }
+ return true;
+ };
+
+ // @return Promise
+ $scope.save = function save() {
+ return block(crmStatus({start: ts('Saving...'), success: ts('Saved')}, abtest.save()));
+ };
+
+ // @return Promise
+ $scope.previewMailing = function previewMailing(mailingName, mode) {
+ return crmMailingPreviewMgr.preview(abtest.mailings[mailingName], mode);
+ };
+
+ // @return Promise
+ $scope.sendTest = function sendTest(mailingName, recipient) {
+ return block(crmStatus({start: ts('Saving...'), success: ''}, abtest.save())
+ .then(function() {
+ crmMailingPreviewMgr.sendTest(abtest.mailings[mailingName], recipient);
+ }));
+ };
+
+ // @return Promise
+ $scope.delete = function() {
+ return block(crmStatus({start: ts('Deleting...'), success: ts('Deleted')}, abtest.delete().then($scope.leave)));
+ };
+
+ // @return Promise
+ $scope.submit = function submit() {
+ if (block.check() || $scope.crmMailingAB.$invalid) {
+ return;
+ }
+ return block(crmStatus({start: ts('Saving...'), success: ''}, abtest.save())
+ .then(function() {
+ return crmStatus({
+ start: ts('Submitting...'),
+ success: ts('Submitted')
+ }, myAutosave.suspend(abtest.submitTest()));
+ // Note: We're going to leave, so we don't care that submit() modifies several server-side records.
+ // If we stayed on this page, then we'd care about updating and call: abtest.submitTest().then(...abtest.load()...)
+ })
+ );
+ };
+
+ $scope.leave = function leave() {
+ $location.path('abtest');
+ $location.replace();
+ };
+
+ $scope.selectWinner = function selectWinner(mailingName) {
+ var model = {
+ abtest: $scope.abtest,
+ mailingName: mailingName
+ };
+ var options = CRM.utils.adjustDialogDefaults({
+ autoOpen: false,
+ height: 'auto',
+ width: '40%',
+ title: ts('Select Final Mailing (Test %1)', {
+ 1: mailingName.toUpperCase()
+ })
+ });
+ return myAutosave.suspend(dialogService.open('selectWinnerDialog', '~/crmMailingAB/WinnerDialogCtrl.html', model, options));
+ };
+
+ // initialize
+ var syncJob = $interval($scope.sync, 333);
+ $scope.$on('$destroy', function() {
+ $interval.cancel(syncJob);
+ });
+
+ myAutosave = new CrmAutosaveCtrl({
+ save: $scope.save,
+ saveIf: function() {
+ return abtest.ab.status == 'Draft' && $scope.sync();
+ },
+ model: function() {
+ return abtest.getAutosaveSignature();
+ },
+ form: function() {
+ return $scope.crmMailingAB;
+ }
+ });
+ $timeout(myAutosave.start);
+ $scope.$on('$destroy', myAutosave.stop);
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/edit.html b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/edit.html
new file mode 100644
index 00000000..825f3a57
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/edit.html
@@ -0,0 +1,178 @@
+<!--
+ Implicit Controller: CrmMailingABEditCtrl
+
+ An ABTest includes two mailings, but we don't require the user to enter two complete mailings. For
+ simplicity, the email composition UI generally displays A (unless we specifically decided to expose an
+ individual field from B). At the end of the composition process, the controller's "sync" operation will
+ merge shared settings from "A" into "B".
+-->
+<div ng-form="crmMailingABEdit">
+ <div class="crm-block crm-form-block crmMailing">
+ <div crm-ui-wizard>
+ <div crm-ui-wizard-step="10" crm-title="ts('Setup')" ng-form="setupForm">
+ <div
+ crm-mailing-ab-block-setup="{
+ help: 1,
+ abName: 1,
+ campaign: 1,
+ testing_criteria: 1
+ }"
+ crm-abtest="abtest"></div>
+ </div>
+ <div crm-ui-wizard-step="11" crm-title="ts('Target')" ng-form="targetForm">
+ <div
+ crm-mailing-ab-block-setup="{
+ recipients: 1,
+ group_percentage: 1
+ }"
+ crm-abtest="abtest"></div>
+ </div>
+ <div crm-ui-wizard-step="20" crm-title="ts('Compose')" ng-if="abtest.ab.testing_criteria != 'full_email'" ng-form="composeForm">
+ <div crm-ui-tab-set>
+ <div crm-ui-tab id="tab-mailing" crm-title="ts('Mailing')">
+ <div
+ ng-if="abtest.ab.testing_criteria == 'from'"
+ crm-mailing-ab-block-mailing="{
+ msg_template_id: 1,
+ fromAddressA: 1,
+ fromAddressB: 1,
+ subject: 1
+ }"
+ crm-abtest="abtest"></div>
+ <div
+ ng-if="abtest.ab.testing_criteria == 'subject'"
+ crm-mailing-ab-block-mailing="{
+ msg_template_id: 1,
+ fromAddress: 1,
+ replyTo: 1,
+ subjectA: 1,
+ subjectB: 1
+ }"
+ crm-abtest="abtest"></div>
+ <div crm-ui-accordion="{title: ts('HTML')}">
+ <div crm-mailing-body-html crm-mailing="abtest.mailings.a"></div>
+ </div>
+ <div crm-ui-accordion="{title: ts('Plain Text'), collapsed: !abtest.mailings.a.body_text}">
+ <div crm-mailing-body-text crm-mailing="abtest.mailings.a"></div>
+ </div>
+ </div>
+ <!--
+ FIXME: Attachment UI works, but we haven't implemented backend logic for copying/sharing
+ of attachments among mailings A/B/C.
+ <div crm-ui-tab id="tab-attachment" crm-title="ts('Attachments')">
+ <div crm-attachments="abtest.attachments.a"></div>
+ </div>
+ -->
+ <div crm-ui-tab id="tab-header" crm-title="ts('Header and Footer')">
+ <div crm-mailing-block-header-footer crm-mailing="abtest.mailings.a"></div>
+ </div>
+ <div crm-ui-tab id="tab-pub" crm-title="ts('Publication')">
+ <div crm-mailing-block-publication crm-mailing="abtest.mailings.a"></div>
+ </div>
+ <div crm-ui-tab id="tab-response" crm-title="ts('Responses')">
+ <div crm-mailing-block-responses crm-mailing="abtest.mailings.a"></div>
+ </div>
+ </div>
+ <div crm-ui-accordion="{title: ts('Preview (A)')}">
+ <div crm-mailing-block-preview crm-mailing="abtest.mailings.a" on-preview="previewMailing('a', preview.mode)" on-send="sendTest('a', preview.recipient)"></div>
+ </div>
+ <div crm-ui-accordion="{title: ts('Preview (B)')}">
+ <div crm-mailing-block-preview crm-mailing="abtest.mailings.b" on-preview="previewMailing('b', preview.mode)" on-send="sendTest('b', preview.recipient)"></div>
+ </div>
+ </div>
+ <div crm-ui-wizard-step="21" crm-title="ts('Compose (A)')" ng-if="abtest.ab.testing_criteria == 'full_email'" ng-form="composeAForm">
+ <div crm-ui-tab-set>
+ <div crm-ui-tab id="tab-mailingA" crm-title="ts('Mailing')">
+ <div
+ crm-mailing-ab-block-mailing="{
+ msg_template_idA: 1,
+ fromAddressA: 1,
+ replyToA: 1,
+ subjectA: 1
+ }"
+ crm-abtest="abtest"></div>
+ <div crm-ui-accordion="{title: ts('HTML')}">
+ <div crm-mailing-body-html crm-mailing="abtest.mailings.a"></div>
+ </div>
+ <div crm-ui-accordion="{title: ts('Plain Text'), collapsed: !abtest.mailings.a.body_text}">
+ <div crm-mailing-body-text crm-mailing="abtest.mailings.a"></div>
+ </div>
+ </div>
+ <div crm-ui-tab id="tab-attachmentA" crm-title="ts('Attachments')">
+ <div crm-attachments="abtest.attachments.a"></div>
+ </div>
+ <div crm-ui-tab id="tab-headerA" crm-title="ts('Header and Footer')">
+ <div crm-mailing-block-header-footer crm-mailing="abtest.mailings.a"></div>
+ </div>
+ <div crm-ui-tab id="tab-pubA" crm-title="ts('Publication')">
+ <div crm-mailing-block-publication crm-mailing="abtest.mailings.a"></div>
+ </div>
+ <div crm-ui-tab id="tab-responseA" crm-title="ts('Responses')">
+ <div crm-mailing-block-responses crm-mailing="abtest.mailings.a"></div>
+ </div>
+ </div>
+ <div crm-ui-accordion="{title: ts('Preview')}">
+ <div crm-mailing-block-preview crm-mailing="abtest.mailings.a" on-preview="previewMailing('a', preview.mode)" on-send="sendTest('a', preview.recipient)"></div>
+ </div>
+ </div>
+ <div crm-ui-wizard-step="22" crm-title="ts('Compose (B)')" ng-if="abtest.ab.testing_criteria == 'full_email'" ng-form="composeBForm">
+ <div crm-ui-tab-set>
+ <div crm-ui-tab id="tab-mailingB" crm-title="ts('Mailing')">
+ <div
+ crm-mailing-ab-block-mailing="{
+ msg_template_idB: 1,
+ fromAddressB: 1,
+ replyToB: 1,
+ subjectB: 1
+ }"
+ crm-abtest="abtest"></div>
+ <div crm-ui-accordion="{title: ts('HTML')}">
+ <div crm-mailing-body-html crm-mailing="abtest.mailings.b"></div>
+ </div>
+ <div crm-ui-accordion="{title: ts('Plain Text'), collapsed: !abtest.mailings.b.body_text}">
+ <div crm-mailing-body-text crm-mailing="abtest.mailings.b"></div>
+ </div>
+ </div>
+ <div crm-ui-tab id="tab-attachmentB" crm-title="ts('Attachments')">
+ <div crm-attachments="abtest.attachments.b"></div>
+ </div>
+ <div crm-ui-tab id="tab-headerB" crm-title="ts('Header and Footer')">
+ <div crm-mailing-block-header-footer crm-mailing="abtest.mailings.b"></div>
+ </div>
+ <div crm-ui-tab id="tab-pubB" crm-title="ts('Publication')">
+ <div crm-mailing-block-publication crm-mailing="abtest.mailings.b"></div>
+ </div>
+ <div crm-ui-tab id="tab-responseB" crm-title="ts('Responses')">
+ <div crm-mailing-block-responses crm-mailing="abtest.mailings.b"></div>
+ </div>
+ </div>
+ <div crm-ui-accordion="{title: ts('Preview')}">
+ <div crm-mailing-block-preview crm-mailing="abtest.mailings.b" on-preview="previewMailing('b', preview.mode)" on-send="sendTest('b', preview.recipient)"></div>
+ </div>
+ </div>
+ <div crm-ui-wizard-step="30" crm-title="ts('Schedule')" ng-form="schedForm">
+ <div
+ crm-mailing-ab-block-setup="{
+ scheduled_date: 1,
+ declare_winning_time: 1
+ }"
+ crm-abtest="abtest"></div>
+ <center>
+ <a class="button crmMailing-submit-button" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingAB.$invalid}">
+ <div>{{ts('Submit Mailing')}}</div>
+ </a>
+ </center>
+ </div>
+ <span crm-ui-wizard-buttons style="float:right;">
+ <button
+ crm-icon="fa-trash"
+ ng-show="checkPerm('delete in CiviMail')"
+ ng-disabled="block.check()"
+ crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
+ on-yes="delete()">{{ts('Delete Draft')}}
+ </button>
+ <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave) ">{{ts('Save Draft')}}</button>
+ </span>
+ </div>
+ </div>
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/main.html b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/main.html
new file mode 100644
index 00000000..15822f6e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/main.html
@@ -0,0 +1,10 @@
+<!--
+ Implicit Controller: CrmMailingABEditCtrl
+-->
+<div crm-ui-debug="abtest.ab"></div>
+<div crm-ui-debug="abtest.mailings"></div>
+
+<form name="crmMailingAB" novalidate>
+ <div ng-include="'~/crmMailingAB/EditCtrl/edit.html'" ng-if="!isSubmitted()"></div>
+ <div ng-include="'~/crmMailingAB/EditCtrl/report.html'" ng-if="isSubmitted()"></div>
+</form>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/report.html b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/report.html
new file mode 100644
index 00000000..68b79245
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/EditCtrl/report.html
@@ -0,0 +1,194 @@
+<!--
+ Implicit Controller: CrmMailingABEditCtrl
+-->
+<div class="messages help">
+ <div class="msg-title crm-title">{{ts('A/B Test Results')}}: {{abtest.ab.name}}</div>
+ {{ts('This report displays the current results for your A/B test. You can return to this page to view the latest statistics by navigating to "Manage A/B Tests" and clicking "Results".')}}
+</div>
+<div ng-controller="CrmMailingABReportCtrl">
+ <table class="crm-mailing-ab-table">
+ <thead>
+ <tr ng-show="abtest.ab.status == 'Testing'">
+ <td></td>
+ <td ng-repeat="am in getActiveMailings()">
+ <button crm-icon="fa-trophy" ng-click="selectWinner(am.name)">{{ts('Select as Final')}}</button>
+ </td>
+ <td></td>
+ </tr>
+ </thead>
+
+ <thead>
+ <tr>
+ <th>{{ts('Delivery')}}</th>
+ <th ng-repeat="am in getActiveMailings()" class="crm-mailing-ab-col">{{am.title}}</th>
+ <th ng-show="abtest.ab.status == 'Testing'">{{ts('Final')}}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td>{{ts('Status')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <span ng-repeat="job in am.mailing.jobs" ng-hide="job.is_test == 1 || job.parent_id != null">{{job.status}}</span>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'">{{ts('Not selected')}}</td>
+ </tr>
+ <tr>
+ <td>{{ts('Scheduled')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <div ng-repeat="job in am.mailing.jobs" ng-hide="job.is_test == 1 || job.parent_id != null">{{job.scheduled_date}}</div>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('Started at')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <div ng-repeat="job in am.mailing.jobs" ng-hide="job.is_test == 1 || job.parent_id != null">{{job.start_date || ts('Not started')}}</div>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('Completed at')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <div ng-repeat="job in am.mailing.jobs" ng-hide="job.is_test == 1 || job.parent_id != null">{{job.end_date || ts('Not completed')}}</div>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ </tbody>
+
+ <thead>
+ <tr>
+ <th>{{ts('Performance')}}</th>
+ <th ng-repeat="am in getActiveMailings()" class="crm-mailing-ab-col">{{am.title}}</th>
+ <th ng-show="abtest.ab.status == 'Testing'">{{ts('Final')}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="statType in statTypes">
+ <td>{{statType.title}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <a
+ class="crm-hover-button action-item"
+ ng-href="{{statUrl(am.mailing, statType, 'search')}}"
+ ng-if="checkPerm('view all contacts') || checkPerm('edit all contacts')"
+ title="{{ts('Search for contacts using \'%1\'', {1: statType.title})}}"
+ crm-icon="fa-search"
+ ></a>
+ <a
+ class="crm-hover-button action-item"
+ ng-href="{{statUrl(am.mailing, statType, 'events')}}"
+ title="{{ts('Browse events of type \'%1\'', {1: statType.title})}}"
+ >{{stats[am.name][statType.name] || ts('n/a')}} </a> {{stats[am.name][rateStats[statType.name]] || ' '}}
+ <a
+ class="crm-hover-button action-item"
+ ng-href="{{statUrl(am.mailing, statType, 'report')}}"
+ title="{{ts('Reports for \'%1\'', {1: statType.title})}}"
+ crm-icon="clipboard"
+ ></a>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ </tbody>
+
+ <thead>
+ <tr>
+ <th>{{ts('Details')}}</th>
+ <th ng-repeat="am in getActiveMailings()" class="crm-mailing-ab-col">{{am.title}}</th>
+ <th ng-show="abtest.ab.status == 'Testing'">{{ts('Final')}}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td>{{ts('Mailing Name')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ {{am.mailing.name}}
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('From')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ "{{am.mailing.from_name}}" &lt;{{am.mailing.from_email}}&gt;
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('Subject')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ {{am.mailing.subject}}
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr ng-controller="ViewRecipCtrl">
+ <td>{{ts('Recipients')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <div ng-show="getIncludesAsString(am.mailing)">
+ <strong>{{ts('Include:')}}</strong> {{getIncludesAsString(am.mailing)}}
+ </div>
+ <div ng-show="getExcludesAsString(am.mailing)">
+ <strong>{{ts('Exclude:')}}</strong> <s>{{getExcludesAsString(am.mailing)}}</s>
+ </div>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('Content')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <a crm-icon="fa-television" class="crm-hover-button action-item" ng-click="previewMailing(am.name,'html')" ng-show="am.mailing.body_html">{{ts('HTML')}}</a>
+ <a crm-icon="fa-file-text-o" class="crm-hover-button action-item" ng-click="previewMailing(am.name,'text')" ng-show="am.mailing.body_text">{{ts('Text')}}</a>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('Attachments')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <div ng-repeat="file in am.attachments.files"><a ng-href="{{file.url}}" target="_blank">{{file.name}}</a></div>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('Tracking')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <div crm-mailing-review-bool crm-on="am.mailing.url_tracking=='1'" crm-title="ts('Click-Throughs')"></div>
+ <div crm-mailing-review-bool crm-on="am.mailing.open_tracking=='1'" crm-title="ts('Opens')"></div>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('Responding')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ <div crm-mailing-review-bool crm-on="am.mailing.override_verp=='0'" crm-title="ts('Track Replies')"></div>
+ <div crm-mailing-review-bool crm-on="am.mailing.override_verp=='0' && mailing.forward_replies=='1'" crm-title="ts('Forward Replies')"></div>
+ <div ng-controller="PreviewComponentCtrl">
+ <div ng-show="am.mailing.override_verp == '0' && mailing.auto_responder"><a crm-icon="fa-envelope" class="crm-hover-button action-item" ng-click="previewComponent(ts('Auto-Respond'), am.mailing.reply_id)">{{ts('Auto-Respond')}}</a></div>
+ <div><a crm-icon="fa-envelope" class="crm-hover-button action-item" ng-click="previewComponent(ts('Opt-out'), am.mailing.optout_id)">{{ts('Opt-out')}}</a></div>
+ <div><a crm-icon="fa-envelope" class="crm-hover-button action-item" ng-click="previewComponent(ts('Resubscribe'), am.mailing.resubscribe_id)">{{ts('Resubscribe')}}</a></div>
+ <div><a crm-icon="fa-envelope" class="crm-hover-button action-item" ng-click="previewComponent(ts('Unsubscribe'), am.mailing.unsubscribe_id)">{{ts('Unsubscribe')}}</a></div>
+ </div>
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ <tr>
+ <td>{{ts('Publication')}}</td>
+ <td ng-repeat="am in getActiveMailings()">
+ {{am.mailing.visibility}}
+ </td>
+ <td ng-show="abtest.ab.status == 'Testing'"></td>
+ </tr>
+ </tbody>
+
+ </table>
+
+ <!--
+ <div crm-ui-tab-set>
+ <div crm-ui-tab id="tab-opens" crm-title="ts('Opens (WIP)')">
+ <div crm-mailing-ab-stats="{criteria: 'open', split_count: 5}" crm-abtest="abtest"></div>
+ </div>
+ <div crm-ui-tab id="tab-clicks" crm-title="ts('Total Clicks (WIP)')">
+ <div crm-mailing-ab-stats="{criteria: 'total unique clicks', split_count: 5}" crm-abtest="abtest"></div>
+ </div>
+ </div>
+ -->
+
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ListCtrl.html b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ListCtrl.html
new file mode 100644
index 00000000..5d2c0768
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ListCtrl.html
@@ -0,0 +1,63 @@
+<!--
+Controller: ABListingCtrl
+Required vars: mailingABList
+-->
+
+<span crm-ui-order="{var: 'myOrder', defaults: ['-created_date']}"></span>
+
+<div crm-ui-accordion="{title: ts('Filter'), collapsed: true}">
+ <form name="filterForm">
+ <span>
+ <input class="big crm-form-text" ng-model="filter.name" placeholder="{{ts('Name')}}"/>
+ </span>
+ <span>
+ <select crm-ui-select style="width: 10em;" ng-model="filter.status">
+ <option value="">{{ts('- Status -')}}</option>
+ <option ng-repeat="o in fields.status.options" ng-value="o.key">{{o.value}}</option>
+ </select>
+ </span>
+ <span>
+ <select crm-ui-select style="width: 20em;" ng-model="filter.testing_criteria">
+ <option value="">{{ts('- Test Type -')}}</option>
+ <option ng-repeat="o in fields.testing_criteria.options" ng-value="o.key">{{o.value}}</option>
+ </select>
+ </span>
+ </form>
+</div>
+
+<div ng-show="mailingABList.length">
+ <table class="display">
+ <thead>
+ <tr>
+ <th><a crm-ui-order-by="[myOrder, 'name']">{{ts('Name')}}</a></th>
+ <th><a crm-ui-order-by="[myOrder, 'status']">{{ts('Status')}}</a></th>
+ <th><a crm-ui-order-by="[myOrder, 'testing_criteria']">{{ts('Test Type')}}</a></th>
+ <th><a crm-ui-order-by="[myOrder, 'created_date']">{{ts('Created')}}</a></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="mailingAB in mailingABList | filter:filter | orderBy:myOrder.get()">
+ <td>{{mailingAB.name}}</td>
+ <td>{{crmMailingABStatus.getByName(mailingAB.status).label}}</td>
+ <td>{{crmMailingABCriteria.get(mailingAB.testing_criteria).label}}</td>
+ <td>{{mailingAB.created_date}}</td>
+ <td>
+ <a class="action-item crm-hover-button" ng-href="#/abtest/{{mailingAB.id}}" ng-show="mailingAB.status == 'Draft'">{{ts('Continue')}}</a>
+ <a class="action-item crm-hover-button" ng-href="#/abtest/{{mailingAB.id}}" ng-show="mailingAB.status != 'Draft'">{{ts('Results')}}</a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+<div ng-show="mailingABList.length === 0" class="messages status no-popup">
+ <i class="crm-i fa-info-circle"></i>
+ {{ts('You have no A/B mailings')}}
+</div>
+
+
+<div class="crm-submit-buttons">
+ <br>
+ <a ng-href="#/abtest/new" class="button"><span><i class="crm-i fa-bar-chart"></i> {{ts('New A/B Test')}}</span></a>
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ListCtrl.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ListCtrl.js
new file mode 100644
index 00000000..d0ced773
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ListCtrl.js
@@ -0,0 +1,12 @@
+(function(angular, $, _) {
+
+ angular.module('crmMailingAB').controller('CrmMailingABListCtrl', function($scope, mailingABList, crmMailingABCriteria, crmMailingABStatus, fields) {
+ var ts = $scope.ts = CRM.ts(null);
+ $scope.mailingABList = _.values(mailingABList.values);
+ $scope.crmMailingABCriteria = crmMailingABCriteria;
+ $scope.crmMailingABStatus = crmMailingABStatus;
+ $scope.fields = fields;
+ $scope.filter = {};
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/NewCtrl.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/NewCtrl.js
new file mode 100644
index 00000000..edcfa705
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/NewCtrl.js
@@ -0,0 +1,11 @@
+(function(angular, $, _) {
+
+ angular.module('crmMailingAB').controller('CrmMailingABNewCtrl', function($scope, abtest, $location) {
+ // Transition URL "/abtest/new/foo" => "/abtest/123/foo"
+ var parts = $location.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard"
+ parts[2] = abtest.id;
+ $location.path(parts.join('/'));
+ $location.replace();
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ReportCtrl.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ReportCtrl.js
new file mode 100644
index 00000000..8755945e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/ReportCtrl.js
@@ -0,0 +1,56 @@
+(function(angular, $, _) {
+
+ angular.module('crmMailingAB').controller('CrmMailingABReportCtrl', function($scope, crmApi, crmMailingStats) {
+ var ts = $scope.ts = CRM.ts(null);
+
+ var CrmMailingABReportCnt = 1, activeMailings = null;
+ $scope.getActiveMailings = function() {
+ if ($scope.abtest.$CrmMailingABReportCnt != CrmMailingABReportCnt) {
+ $scope.abtest.$CrmMailingABReportCnt = ++CrmMailingABReportCnt;
+ activeMailings = [
+ {
+ name: 'a',
+ title: ts('Mailing A'),
+ mailing: $scope.abtest.mailings.a,
+ attachments: $scope.abtest.attachments.a
+ },
+ {
+ name: 'b',
+ title: ts('Mailing B'),
+ mailing: $scope.abtest.mailings.b,
+ attachments: $scope.abtest.attachments.b
+ }
+ ];
+ if ($scope.abtest.ab.status == 'Final') {
+ activeMailings.push({
+ name: 'c',
+ title: ts('Final'),
+ mailing: $scope.abtest.mailings.c,
+ attachments: $scope.abtest.attachments.c
+ });
+ }
+ }
+ return activeMailings;
+ };
+
+ crmMailingStats.getStats({
+ a: $scope.abtest.ab.mailing_id_a,
+ b: $scope.abtest.ab.mailing_id_b,
+ c: $scope.abtest.ab.mailing_id_c
+ }).then(function(stats) {
+ $scope.stats = stats;
+ });
+ $scope.rateStats = {
+ 'Unique Clicks': 'clickthrough_rate',
+ 'Delivered': 'delivered_rate',
+ 'Opened': 'opened_rate',
+ };
+ $scope.statTypes = crmMailingStats.getStatTypes();
+ $scope.statUrl = function statUrl(mailing, statType, view) {
+ return crmMailingStats.getUrl(mailing, statType, view, 'abtest/' + $scope.abtest.ab.id);
+ };
+
+ $scope.checkPerm = CRM.checkPerm;
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Slider.html b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Slider.html
new file mode 100644
index 00000000..cdfcad3b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Slider.html
@@ -0,0 +1,25 @@
+<table class="crm-mailing-ab-slider">
+ <tbody>
+ <tr>
+ <td style="width: 10em;">{{ts('Test Mailing A')}}</td>
+ <td>
+ <div class="slider-test slider-a"></div>
+ </td>
+ <td style="width: 5em;">({{testValue}}%)</td>
+ </tr>
+ <tr>
+ <td>{{ts('Test Mailing B')}}</td>
+ <td>
+ <div class="slider-test slider-b"></div>
+ </td>
+ <td>({{testValue}}%)</td>
+ </tr>
+ </tbody>
+ <tr>
+ <td>{{ts('Final Mailing')}}</td>
+ <td>
+ <div class="slider-win slider-b"></div>
+ </td>
+ <td>({{winValue}}%)</td>
+ </tr>
+</table>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Slider.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Slider.js
new file mode 100644
index 00000000..d26e35b1
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Slider.js
@@ -0,0 +1,60 @@
+(function(angular, $, _) {
+
+ // example: <div crm-mailing-ab-slider ng-model="abtest.ab.group_percentage"></div>
+ angular.module('crmMailingAB').directive('crmMailingAbSlider', function() {
+ return {
+ require: '?ngModel',
+ scope: {},
+ templateUrl: '~/crmMailingAB/Slider.html',
+ link: function(scope, element, attrs, ngModel) {
+ var TEST_MIN = 1, TEST_MAX = 50;
+ var sliders = $('.slider-test,.slider-win', element);
+ var sliderTests = $('.slider-test', element);
+ var sliderWin = $('.slider-win', element);
+
+ scope.ts = CRM.ts(null);
+ scope.testValue = 0;
+ scope.winValue = 100;
+
+ // set the base value (following a GUI event)
+ function setValue(value) {
+ value = Math.min(TEST_MAX, Math.max(TEST_MIN, value));
+ scope.$apply(function() {
+ ngModel.$setViewValue(value);
+ scope.testValue = value;
+ scope.winValue = 100 - (2 * scope.testValue);
+ sliderTests.slider('value', scope.testValue);
+ sliderWin.slider('value', scope.winValue);
+ });
+ }
+
+ sliders.slider({
+ min: 0,
+ max: 100,
+ range: 'min',
+ step: 1
+ });
+ sliderTests.slider({
+ slide: function slideTest(event, ui) {
+ event.preventDefault();
+ setValue(ui.value);
+ }
+ });
+ sliderWin.slider({
+ slide: function slideWinner(event, ui) {
+ event.preventDefault();
+ setValue(Math.round((100 - ui.value) / 2));
+ }
+ });
+
+ ngModel.$render = function() {
+ scope.testValue = ngModel.$viewValue;
+ scope.winValue = 100 - (2 * scope.testValue);
+ sliderTests.slider('value', scope.testValue);
+ sliderWin.slider('value', scope.winValue);
+ };
+ }
+ };
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Stats.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Stats.js
new file mode 100644
index 00000000..da2ebb63
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/Stats.js
@@ -0,0 +1,280 @@
+(function (angular, $, _) {
+
+
+ // FIXME: This code is long and hasn't been fully working for me, but I've moved it into a spot
+ // where it at least fits in a bit better.
+
+ // example: <div crm-mailing-ab-stats="{split_count: 6, criteria:'Open'}" crm-abtest="myabtest" />
+ // options (see also: Mailing.graph_stats API)
+ // - split_count: int
+ // - criteria: string
+ // - target_date: string, date
+ // - target_url: string
+ angular.module('crmMailingAB').directive('crmMailingAbStats', function (crmApi, $parse) {
+ return {
+ scope: {
+ crmMailingAbStats: '@',
+ crmAbtest: '@'
+ },
+ template: '<div class="crm-mailing-ab-stats"></div>',
+ link: function (scope, element, attrs) {
+ var abtestModel = $parse(attrs.crmAbtest);
+ var optionModel = $parse(attrs.crmMailingAbStats);
+ var options = angular.extend({}, optionModel(scope.$parent), {
+ criteria: 'Open', // e.g. 'Open', 'Total Unique Clicks'
+ split_count: 5
+ });
+
+ scope.$watch(attrs.crmAbtest, refresh);
+ function refresh() {
+ var abtest = abtestModel(scope.$parent);
+ if (!abtest) {
+ console.log('failed to draw stats - missing abtest');
+ return;
+ }
+
+ scope.graph_data = [
+ {},
+ {},
+ {},
+ {},
+ {}
+ ];
+ var keep_cnt = 0;
+
+ for (var i = 1; i <= options.split_count; i++) {
+ var result = crmApi('MailingAB', 'graph_stats', {
+ id: abtest.ab.id,
+ target_date: abtest.ab.declare_winning_time ? abtest.ab.declare_winning_time : 'now',
+ target_url: null, // FIXME
+ criteria: options.criteria,
+ split_count: options.split_count,
+ split_count_select: i
+ });
+ /*jshint -W083 */
+ result.then(function (data) {
+ var temp = 0;
+ keep_cnt++;
+ for (var key in data.values.A) {
+ temp = key;
+ }
+ var t = data.values.A[temp].time.split(" ");
+ var m = t[0];
+ var year = t[2];
+ var day = t[1].substr(0, t[1].length - 3);
+ var t1, hur, hour, min;
+ if (_.isEmpty(t[3])) {
+ t1 = t[4].split(":");
+ hur = t1[0];
+ if (t[5] == "AM") {
+ hour = hur;
+ if (hour == 12) {
+ hour = 0;
+ }
+ }
+ if (t[5] == "PM") {
+ hour = parseInt(hur) + 12;
+ }
+ min = t1[1];
+ }
+ else {
+ t1 = t[3].split(":");
+ hur = t1[0];
+ if (t[4] == "AM") {
+ hour = hur;
+ if (hour == 12) {
+ hour = 0;
+ }
+ }
+ if (t[4] == "PM") {
+ hour = parseInt(hur) + 12;
+ }
+ min = t1[1];
+ }
+ var month = 0;
+ switch (m) {
+ case "January":
+ month = 0;
+ break;
+ case "February":
+ month = 1;
+ break;
+ case "March":
+ month = 2;
+ break;
+ case "April":
+ month = 3;
+ break;
+ case "May":
+ month = 4;
+ break;
+ case "June":
+ month = 5;
+ break;
+ case "July":
+ month = 6;
+ break;
+ case "August":
+ month = 7;
+ break;
+ case "September":
+ month = 8;
+ break;
+ case "October":
+ month = 9;
+ break;
+ case "November":
+ month = 10;
+ break;
+ case "December":
+ month = 11;
+ break;
+
+ }
+ var tp = new Date(year, month, day, hour, min, 0, 0);
+ scope.graph_data[temp - 1] = {
+ time: tp,
+ x: data.values.A[temp].count,
+ y: data.values.B[temp].count
+ };
+
+ if (keep_cnt == options.split_count) {
+ scope.graphload = true;
+ data = scope.graph_data;
+
+ // set up a colour variable
+ var color = d3.scale.category10();
+
+ // map one colour each to x, y and z
+ // keys grabs the key value or heading of each key value pair in the json
+ // but not time
+ color.domain(d3.keys(data[0]).filter(function (key) {
+ return key !== "time";
+ }));
+
+ // create a nested series for passing to the line generator
+ // it's best understood by console logging the data
+ var series = color.domain().map(function (name) {
+ return {
+ name: name,
+ values: data.map(function (d) {
+ return {
+ time: d.time,
+ score: +d[name]
+ };
+ })
+ };
+ });
+
+ // Set the dimensions of the canvas / graph
+ var margin = {
+ top: 30,
+ right: 20,
+ bottom: 40,
+ left: 75
+ },
+ width = 550 - margin.left - margin.right,
+ height = 350 - margin.top - margin.bottom;
+
+ // Set the ranges
+ //var x = d3.time.scale().range([0, width]).domain([0,10]);
+ var x = d3.time.scale().range([0, width]);
+ var y = d3.scale.linear().range([height, 0]);
+
+ // Define the axes
+ var xAxis = d3.svg.axis().scale(x)
+ .orient("bottom").ticks(10);
+
+ var yAxis = d3.svg.axis().scale(y)
+ .orient("left").ticks(5);
+
+ // Define the line
+ // Note you plot the time / score pair from each key you created earlier
+ var valueline = d3.svg.line()
+ .x(function (d) {
+ return x(d.time);
+ })
+ .y(function (d) {
+ return y(d.score);
+ });
+
+ // Adds the svg canvas
+ var svg = d3.select($('.crm-mailing-ab-stats', element)[0])
+ .append("svg")
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+ .append("g")
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+ // Scale the range of the data
+ x.domain(d3.extent(data, function (d) {
+ return d.time;
+ }));
+
+ // note the nested nature of this you need to dig an additional level
+ y.domain([
+ d3.min(series, function (c) {
+ return d3.min(c.values, function (v) {
+ return v.score;
+ });
+ }),
+ d3.max(series, function (c) {
+ return d3.max(c.values, function (v) {
+ return v.score;
+ });
+ })
+ ]);
+ svg.append("text") // text label for the x axis
+ .attr("x", width / 2)
+ .attr("y", height + margin.bottom)
+ .style("text-anchor", "middle")
+ .text("Time");
+
+ svg.append("text") // text label for the x axis
+ .style("text-anchor", "middle")
+ .text(scope.winnercriteria).attr("transform",function (d) {
+ return "rotate(-90)";
+ }).attr("x", -height / 2)
+ .attr("y", -30);
+
+ // create a variable called series and bind the date
+ // for each series append a g element and class it as series for css styling
+ series = svg.selectAll(".series")
+ .data(series)
+ .enter().append("g")
+ .attr("class", "series");
+
+ // create the path for each series in the variable series i.e. x, y and z
+ // pass each object called x, y nad z to the lne generator
+ series.append("path")
+ .attr("class", "line")
+ .attr("d", function (d) {
+ // console.log(d); // to see how d3 iterates through series
+ return valueline(d.values);
+ })
+ .style("stroke", function (d) {
+ return color(d.name);
+ });
+
+ // Add the X Axis
+ svg.append("g") // Add the X Axis
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + height + ")")
+ .call(xAxis)
+ .selectAll("text")
+ .attr("transform", function (d) {
+ return "rotate(-30)";
+ });
+
+ // Add the Y Axis
+ svg.append("g") // Add the Y Axis
+ .attr("class", "y axis")
+ .call(yAxis);
+ }
+ });
+ }
+ }
+ } // link()
+ };
+ });
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/WinnerDialogCtrl.html b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/WinnerDialogCtrl.html
new file mode 100644
index 00000000..0c2db4f6
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/WinnerDialogCtrl.html
@@ -0,0 +1,19 @@
+<div ng-controller="CrmMailingABWinnerDialogCtrl">
+ <form novalidate name="winnerForm">
+ <div class="help">
+ {{ts('After selecting %1 as the winner, one must schedule the delivery for the final mailing.', {1: mailingTitle})}}
+ </div>
+
+ <div crm-mailing-radio-date="schedule" ng-model="abtest.mailings.c.scheduled_date">
+ <div>
+ <input ng-model="schedule.mode" type="radio" name="send" value="now" id="schedule-send-now"/>
+ <label for="schedule-send-now">{{ts('Send final mailing immediately')}}</label>
+ </div>
+ <div>
+ <input ng-model="schedule.mode" type="radio" name="send" value="at" id="schedule-send-at"/>
+ <label for="schedule-send-at">{{ts('Send final mailing at:')}}</label>
+ <input crm-ui-datepicker ng-model="schedule.datetime"/>
+ </div>
+ </div>
+ </form>
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/WinnerDialogCtrl.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/WinnerDialogCtrl.js
new file mode 100644
index 00000000..f378f641
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/WinnerDialogCtrl.js
@@ -0,0 +1,43 @@
+(function(angular, $, _) {
+
+ angular.module('crmMailingAB').controller('CrmMailingABWinnerDialogCtrl', function($scope, $timeout, dialogService, crmMailingMgr, crmStatus) {
+ var ts = $scope.ts = CRM.ts(null);
+ var abtest = $scope.abtest = $scope.model.abtest;
+ var mailingName = $scope.model.mailingName;
+
+ var titles = {a: ts('Mailing A'), b: ts('Mailing B')};
+ $scope.mailingTitle = titles[mailingName];
+
+ function init() {
+ // When using dialogService with a button bar, the major button actions
+ // need to be registered with the dialog widget (and not embedded in
+ // the body of the dialog).
+ var buttons = [
+ {
+ text: ts('Submit final mailing'),
+ icons: {primary: 'fa-paper-plane'},
+ click: function() {
+ crmStatus({start: ts('Submitting...'), success: ts('Submitted')},
+ abtest.submitFinal(abtest.mailings[mailingName].id).then(function (r) {
+ delete abtest.$CrmMailingABReportCnt;
+ }))
+ .then(function () {
+ dialogService.close('selectWinnerDialog', abtest);
+ });
+ }
+ },
+ {
+ text: ts('Cancel'),
+ icons: {primary: 'fa-times'},
+ click: function() {
+ dialogService.cancel('selectWinnerDialog');
+ }
+ }
+ ];
+ dialogService.setButtons('selectWinnerDialog', buttons);
+ }
+
+ $timeout(init);
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/services.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/services.js
new file mode 100644
index 00000000..2e9fa926
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmMailingAB/services.js
@@ -0,0 +1,234 @@
+(function (angular, $, _) {
+
+ function OptionGroup(values) {
+ this.get = function get(value) {
+ var r = _.where(values, {value: '' + value});
+ return r.length > 0 ? r[0] : null;
+ };
+ this.getByName = function get(name) {
+ var r = _.where(values, {name: '' + name});
+ return r.length > 0 ? r[0] : null;
+ };
+ this.getAll = function getAll() {
+ return values;
+ };
+ }
+
+ angular.module('crmMailingAB').factory('crmMailingABCriteria', function () {
+ // TODO Get data from server
+ var values = {
+ '1': {value: 'subject', name: 'subject', label: ts('Test different "Subject" lines')},
+ '2': {value: 'from', name: 'from', label: ts('Test different "From" lines')},
+ '3': {value: 'full_email', name: 'full_email', label: ts('Test entirely different emails')}
+ };
+ return new OptionGroup(values);
+ });
+
+ angular.module('crmMailingAB').factory('crmMailingABStatus', function () {
+ // TODO Get data from server
+ var values = {
+ '1': {value: '1', name: 'Draft', label: ts('Draft')},
+ '2': {value: '2', name: 'Testing', label: ts('Testing')},
+ '3': {value: '3', name: 'Final', label: ts('Final')}
+ };
+ return new OptionGroup(values);
+ });
+
+ // CrmMailingAB is a data-model which combines an AB test (APIv3 "MailingAB"), three mailings (APIv3 "Mailing"),
+ // and three sets of attachments (APIv3 "Attachment").
+ //
+ // example:
+ // var abtest = new CrmMailingAB(123);
+ // abtest.load().then(function(){
+ // alert("Mailing A is named "+abtest.mailings.a.name);
+ // });
+ angular.module('crmMailingAB').factory('CrmMailingAB', function (crmApi, crmMailingMgr, $q, CrmAttachments) {
+ function CrmMailingAB(id) {
+ this.id = id;
+ this.mailings = {};
+ this.attachments = {};
+ }
+
+ angular.extend(CrmMailingAB.prototype, {
+ getAutosaveSignature: function() {
+ return [
+ this.ab,
+ this.mailings,
+ this.attachments.a.getAutosaveSignature(),
+ this.attachments.b.getAutosaveSignature(),
+ this.attachments.c.getAutosaveSignature()
+ ];
+ },
+ // @return Promise CrmMailingAB
+ load: function load() {
+ var crmMailingAB = this;
+ if (!crmMailingAB.id) {
+ crmMailingAB.ab = {
+ name: '',
+ status: 'Draft',
+ mailing_id_a: null,
+ mailing_id_b: null,
+ mailing_id_c: null,
+ domain_id: null,
+ testing_criteria: 'subject',
+ winner_criteria: null,
+ specific_url: '',
+ declare_winning_time: null,
+ group_percentage: 10
+ };
+ var mailingDefaults = {
+ // Most defaults provided by Mailing.create API, but we
+ // want to force-enable tracking.
+ open_tracking: "1",
+ url_tracking: "1",
+ mailing_type:"experiment"
+ };
+ crmMailingAB.mailings.a = crmMailingMgr.create(mailingDefaults);
+ crmMailingAB.mailings.b = crmMailingMgr.create(mailingDefaults);
+ mailingDefaults.mailing_type = 'winner';
+ crmMailingAB.mailings.c = crmMailingMgr.create(mailingDefaults);
+ crmMailingAB.attachments.a = new CrmAttachments(function () {
+ return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab.mailing_id_a};
+ });
+ crmMailingAB.attachments.b = new CrmAttachments(function () {
+ return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab.mailing_id_b};
+ });
+ crmMailingAB.attachments.c = new CrmAttachments(function () {
+ return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab.mailing_id_c};
+ });
+
+ var dfr = $q.defer();
+ dfr.resolve(crmMailingAB);
+ return dfr.promise;
+ }
+ else {
+ return crmApi('MailingAB', 'get', {id: crmMailingAB.id})
+ .then(function (abResult) {
+ if (abResult.count != 1) {
+ throw "Failed to load AB Test";
+ }
+ crmMailingAB.ab = abResult.values[abResult.id];
+ return crmMailingAB._loadMailings();
+ });
+ }
+ },
+ // @return Promise CrmMailingAB
+ save: function save() {
+ var crmMailingAB = this;
+ return crmMailingAB._saveMailings()
+ .then(function () {
+ return crmApi('MailingAB', 'create', crmMailingAB.ab)
+ .then(function (abResult) {
+ if (!crmMailingAB.id) {
+ crmMailingAB.id = crmMailingAB.ab.id = abResult.id;
+ }
+ });
+ })
+ .then(function () {
+ return crmMailingAB;
+ });
+ },
+ // Schedule the test
+ // @return Promise CrmMailingAB
+ // Note: Submission may cause the server state to change. Consider abtest.submit().then(...abtest.load()...)
+ submitTest: function submitTest() {
+ var crmMailingAB = this;
+ var params = {
+ id: this.ab.id,
+ status: 'Testing',
+ approval_date: 'now',
+ scheduled_date: this.mailings.a.scheduled_date ? this.mailings.a.scheduled_date : 'now'
+ };
+ return crmApi('MailingAB', 'submit', params)
+ .then(function () {
+ return crmMailingAB.load();
+ });
+ },
+ // Schedule the final mailing
+ // @return Promise CrmMailingAB
+ // Note: Submission may cause the server state to change. Consider abtest.submit().then(...abtest.load()...)
+ submitFinal: function submitFinal(winner_id) {
+ var crmMailingAB = this;
+ var params = {
+ id: this.ab.id,
+ status: 'Final',
+ winner_id: winner_id,
+ approval_date: 'now',
+ scheduled_date: this.mailings.c.scheduled_date ? this.mailings.c.scheduled_date : 'now'
+ };
+ return crmApi('MailingAB', 'submit', params)
+ .then(function () {
+ return crmMailingAB.load();
+ });
+ },
+ // @param mailing Object (per APIv3)
+ // @return Promise
+ 'delete': function () {
+ if (this.id) {
+ return crmApi('MailingAB', 'delete', {id: this.id});
+ }
+ else {
+ var d = $q.defer();
+ d.resolve();
+ return d.promise;
+ }
+ },
+ // Load mailings A, B, and C (if available)
+ // @return Promise CrmMailingAB
+ _loadMailings: function _loadMailings() {
+ var crmMailingAB = this;
+ var todos = {};
+ _.each(['a', 'b', 'c'], function (mkey) {
+ if (crmMailingAB.ab['mailing_id_' + mkey]) {
+ todos[mkey] = crmMailingMgr.get(crmMailingAB.ab['mailing_id_' + mkey])
+ .then(function (mailing) {
+ crmMailingAB.mailings[mkey] = mailing;
+ crmMailingAB.attachments[mkey] = new CrmAttachments(function () {
+ return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab['mailing_id_' + mkey]};
+ });
+ return crmMailingAB.attachments[mkey].load();
+ });
+ }
+ else {
+ crmMailingAB.mailings[mkey] = crmMailingMgr.create();
+ crmMailingAB.attachments[mkey] = new CrmAttachments(function () {
+ return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab['mailing_id_' + mkey]};
+ });
+ }
+ });
+ return $q.all(todos).then(function () {
+ return crmMailingAB;
+ });
+ },
+ // Save mailings A, B, and C (if available)
+ // @return Promise CrmMailingAB
+ _saveMailings: function _saveMailings() {
+ var crmMailingAB = this;
+ var todos = {};
+ var p = $q.when(true);
+ _.each(['a', 'b', 'c'], function (mkey) {
+ if (!crmMailingAB.mailings[mkey]) {
+ return;
+ }
+ if (crmMailingAB.ab['mailing_id_' + mkey]) {
+ // paranoia: in case caller forgot to manage id on mailing
+ crmMailingAB.mailings[mkey].id = crmMailingAB.ab['mailing_id_' + mkey];
+ }
+ p = p.then(function(){
+ return crmMailingMgr.save(crmMailingAB.mailings[mkey])
+ .then(function () {
+ crmMailingAB.ab['mailing_id_' + mkey] = crmMailingAB.mailings[mkey].id;
+ return crmMailingAB.attachments[mkey].save();
+ });
+ });
+ });
+ return p.then(function () {
+ return crmMailingAB;
+ });
+ }
+
+ });
+ return CrmMailingAB;
+ });
+
+})(angular, CRM.$, CRM._);