3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * Test that content produced by CiviMail looks the way it's expected.
31 * @package CiviCRM_APIv3
34 * @copyright CiviCRM LLC (c) 2004-2017
38 * Class CRM_Mailing_MailingSystemTest
40 * @see \Civi\FlexMailer\FlexMailerSystemTest
41 * @see CRM_Mailing_MailingSystemTest
43 abstract class CRM_Mailing_BaseMailingSystemTest
extends CiviUnitTestCase
{
44 protected $_apiversion = 3;
46 public $DBResetRequired = FALSE;
47 public $defaultParams = array();
55 public function setUp() {
56 $this->useTransaction();
58 CRM_Mailing_BAO_MailingJob
::$mailsProcessed = 0;
60 $this->_groupID
= $this->groupCreate();
61 $this->createContactsInGroup(2, $this->_groupID
);
63 $this->defaultParams
= array(
64 'name' => 'mailing name',
66 'groups' => array('include' => array($this->_groupID
)),
67 'scheduled_date' => 'now',
69 $this->_mut
= new CiviMailUtils($this, TRUE);
70 $this->callAPISuccess('mail_settings', 'get',
71 array('api.mail_settings.create' => array('domain' => 'chaos.org')));
76 public function tearDown() {
78 CRM_Utils_Hook
::singleton()->reset();
79 CRM_Mailing_BAO_MailingJob
::$mailsProcessed = 0; // DGW
84 * Generate a fully-formatted mailing with standard email headers.
86 public function testBasicHeaders() {
87 $allMessages = $this->runMailingSuccess(array(
88 'subject' => 'Accidents in cars cause children for {contact.display_name}!',
89 'body_text' => 'BEWARE children need regular infusions of toys. Santa knows your {domain.address}. There is no {action.optOutUrl}.',
91 foreach ($allMessages as $k => $message) {
92 /** @var ezcMail $message */
96 $this->assertEquals("FIXME", $message->from
->name
);
97 $this->assertEquals("info@EXAMPLE.ORG", $message->from
->email
);
98 $this->assertEquals("Mr. Foo{$offset} Anderson II", $message->to
[0]->name
);
99 $this->assertEquals("mail{$offset}@nul.example.com", $message->to
[0]->email
);
101 $this->assertRegExp('#^text/plain; charset=utf-8#', $message->headers
['Content-Type']);
102 $this->assertRegExp(';^b\.[\d\.a-f]+@chaos.org$;', $message->headers
['Return-Path']);
103 $this->assertRegExp(';^b\.[\d\.a-f]+@chaos.org$;', $message->headers
['X-CiviMail-Bounce'][0]);
104 $this->assertRegExp(';^\<mailto:u\.[\d\.a-f]+@chaos.org\>$;', $message->headers
['List-Unsubscribe'][0]);
105 $this->assertEquals('bulk', $message->headers
['Precedence'][0]);
110 * Generate a fully-formatted mailing (with body_text content).
112 public function testText() {
113 $allMessages = $this->runMailingSuccess(array(
114 'subject' => 'Accidents in cars cause children for {contact.display_name}!',
115 'body_text' => 'BEWARE children need regular infusions of toys. Santa knows your {domain.address}. There is no {action.optOutUrl}.',
116 'open_tracking' => 1,
117 // Note: open_tracking does nothing with text, but we'll just verify that it does nothing
119 foreach ($allMessages as $message) {
120 /** @var ezcMail $message */
121 /** @var ezcMailText $textPart */
123 $this->assertTrue($message->body
instanceof ezcMailText
);
125 $this->assertEquals('plain', $message->body
->subType
);
128 "Sample Header for TEXT formatted content.\n" . // Default header
129 "BEWARE children need regular infusions of toys. Santa knows your .*\\. There is no http.*civicrm/mailing/optout.*\\.\n" .
130 "to unsubscribe: http.*civicrm/mailing/optout" . // Default footer
138 * Generate a fully-formatted mailing (with body_html content).
140 public function testHtmlWithOpenTracking() {
141 $allMessages = $this->runMailingSuccess(array(
142 'subject' => 'Example Subject',
143 'body_html' => '<p>You can go to <a href="http://example.net/first?{contact.checksum}">Google</a> or <a href="{action.optOutUrl}">opt out</a>.</p>',
144 'open_tracking' => 1,
147 foreach ($allMessages as $message) {
148 /** @var ezcMail $message */
149 /** @var ezcMailText $htmlPart */
150 /** @var ezcMailText $textPart */
152 $this->assertTrue($message->body
instanceof ezcMailMultipartAlternative
);
154 list($textPart, $htmlPart) = $message->body
->getParts();
156 $this->assertEquals('html', $htmlPart->subType
);
159 "Sample Header for HTML formatted content.\n" . // Default header
160 // FIXME: CiviMail puts double " after hyperlink!
161 "<p>You can go to <a href=\"http://example.net/first\\?cs=[0-9a-f_]+\"\"?>Google</a> or <a href=\"http.*civicrm/mailing/optout.*\">opt out</a>.</p>\n" . // body_html
162 "Sample Footer for HTML formatted content" . // Default footer
164 "<img src=\".*extern/open.php.*\"" .
169 $this->assertEquals('plain', $textPart->subType
);
172 "Sample Header for TEXT formatted content.\n" . // Default header
173 "You can go to Google \\[1\\] or opt out \\[2\\]\\.\n" . // body_html, filtered
177 "\\[1\\] http://example.net/first\\?cs=[0-9a-f_]+\n" .
178 "\\[2\\] http.*civicrm/mailing/optout.*\n" .
180 "to unsubscribe: http.*civicrm/mailing/optout" . // Default footer
188 * Generate a fully-formatted mailing (with body_html content).
190 public function testHtmlWithOpenAndUrlTracking() {
191 $allMessages = $this->runMailingSuccess(array(
192 'subject' => 'Example Subject',
193 'body_html' => '<p>You can go to <a href="http://example.net">Google</a> or <a href="{action.optOutUrl}">opt out</a>.</p>',
194 'open_tracking' => 1,
197 foreach ($allMessages as $message) {
198 /** @var ezcMail $message */
199 /** @var ezcMailText $htmlPart */
200 /** @var ezcMailText $textPart */
202 $this->assertTrue($message->body
instanceof ezcMailMultipartAlternative
);
204 list($textPart, $htmlPart) = $message->body
->getParts();
206 $this->assertEquals('html', $htmlPart->subType
);
210 "<p>You can go to <a href=['\"].*extern/url\.php\?u=\d+&\\;qid=\d+['\"]>Google</a>" .
211 " or <a href=\"http.*civicrm/mailing/optout.*\">opt out</a>.</p>\n" .
213 "Sample Footer for HTML formatted content" .
215 // Open-tracking code
216 "<img src=\".*extern/open.php.*\"" .
221 $this->assertEquals('plain', $textPart->subType
);
224 // body_html, filtered
225 "You can go to Google \\[1\\] or opt out \\[2\\]\\.\n" .
229 "\\[1\\] .*extern/url\.php\?u=\d+&qid=\d+\n" .
230 "\\[2\\] http.*civicrm/mailing/optout.*\n" .
233 "to unsubscribe: http.*civicrm/mailing/optout" .
241 * Each case comes in four parts:
242 * 1. Mailing HTML (body_html)
243 * 2. Regex to run against final HTML
244 * 3. Regex to run against final text
245 * 4. Additional mailing options
249 public function urlTrackingExamples() {
254 '<p><a href="http://example.net/">Foo</a></p>',
255 ';<p><a href="http://example\.net/">Foo</a></p>;',
256 ';\\[1\\] http://example\.net/;',
257 array('url_tracking' => 0),
260 '<p><a href="http://example.net/?id={contact.contact_id}">Foo</a></p>',
261 // FIXME: Legacy tracker adds extra quote after URL
262 ';<p><a href="http://example\.net/\?id=\d+""?>Foo</a></p>;',
263 ';\\[1\\] http://example\.net/\?id=\d+;',
264 array('url_tracking' => 0),
267 '<p><a href="{action.optOutUrl}">Foo</a></p>',
268 ';<p><a href="http.*civicrm/mailing/optout.*">Foo</a></p>;',
269 ';\\[1\\] http.*civicrm/mailing/optout.*;',
270 array('url_tracking' => 0),
273 '<p>Look at <img src="http://example.net/foo.png">.</p>',
274 ';<p>Look at <img src="http://example\.net/foo\.png">\.</p>;',
276 array('url_tracking' => 0),
279 // Plain-text URL's are tracked in plain-text emails...
280 // but not in HTML emails.
281 "<p>Please go to: http://example.net/</p>",
282 ";<p>Please go to: http://example\.net/</p>;",
283 ';Please go to: http://example\.net/;',
284 array('url_tracking' => 0),
289 '<p><a href="http://example.net/">Foo</a></p>',
290 ';<p><a href=[\'"].*extern/url\.php\?u=\d+.*[\'"]>Foo</a></p>;',
291 ';\\[1\\] .*extern/url\.php\?u=\d+.*;',
292 array('url_tracking' => 1),
295 // FIXME: CiviMail URL tracking doesn't track tokenized links.
296 '<p><a href="http://example.net/?id={contact.contact_id}">Foo</a></p>',
297 // FIXME: Legacy tracker adds extra quote after URL
298 ';<p><a href="http://example\.net/\?id=\d+""?>Foo</a></p>;',
299 ';\\[1\\] http://example\.net/\?id=\d+;',
300 array('url_tracking' => 1),
303 // It would be redundant/slow to track the action URLs?
304 '<p><a href="{action.optOutUrl}">Foo</a></p>',
305 ';<p><a href="http.*civicrm/mailing/optout.*">Foo</a></p>;',
306 ';\\[1\\] http.*civicrm/mailing/optout.*;',
307 array('url_tracking' => 1),
310 // It would be excessive/slow to track every embedded image.
311 '<p>Look at <img src="http://example.net/foo.png">.</p>',
312 ';<p>Look at <img src="http://example\.net/foo\.png">\.</p>;',
314 array('url_tracking' => 1),
317 // Plain-text URL's are tracked in plain-text emails...
318 // but not in HTML emails.
319 "<p>Please go to: http://example.net/</p>",
320 ";<p>Please go to: http://example\.net/</p>;",
321 ';Please go to: .*extern/url.php\?u=\d+&qid=\d+;',
322 array('url_tracking' => 1),
329 * Generate a fully-formatted mailing (with body_html content).
331 * @dataProvider urlTrackingExamples
333 public function testUrlTracking($inputHtml, $htmlUrlRegex, $textUrlRegex, $params) {
334 $caseName = print_r(array('inputHtml' => $inputHtml, 'params' => $params), 1);
336 $allMessages = $this->runMailingSuccess($params +
array(
337 'subject' => 'Example Subject',
338 'body_html' => $inputHtml,
340 foreach ($allMessages as $message) {
341 /** @var ezcMail $message */
342 /** @var ezcMailText $htmlPart */
343 /** @var ezcMailText $textPart */
345 $this->assertTrue($message->body
instanceof ezcMailMultipartAlternative
);
347 list($textPart, $htmlPart) = $message->body
->getParts();
350 $this->assertEquals('html', $htmlPart->subType
, "Should have HTML part in case: $caseName");
351 $this->assertRegExp($htmlUrlRegex, $htmlPart->text
, "Should have correct HTML in case: $caseName");
355 $this->assertEquals('plain', $textPart->subType
, "Should have text part in case: $caseName");
356 $this->assertRegExp($textUrlRegex, $textPart->text
, "Should have correct text in case: $caseName");
362 * Create contacts in group.
365 * @param int $groupID
366 * @param string $domain
368 protected function createContactsInGroup(
371 $domain = 'nul.example.com'
373 for ($i = 1; $i <= $count; $i++
) {
374 $contactID = $this->individualCreate(array(
375 'first_name' => "Foo{$i}",
376 'email' => 'mail' . $i . '@' . $domain,
378 $this->callAPISuccess('group_contact', 'create', array(
379 'contact_id' => $contactID,
380 'group_id' => $groupID,
387 * Create and execute a mailing. Return the matching messages.
389 * @param array $params
390 * List of parameters to send to Mailing.create API.
391 * @return array<ezcMail>
393 protected function runMailingSuccess($params) {
394 $mailingParams = array_merge($this->defaultParams
, $params);
395 $this->callAPISuccess('mailing', 'create', $mailingParams);
396 $this->_mut
->assertRecipients(array());
397 $this->callAPISuccess('job', 'process_mailing', array('runInNonProductionEnvironment' => TRUE));
399 $allMessages = $this->_mut
->getAllMessages('ezc');
400 // There are exactly two contacts produced by setUp().
401 $this->assertEquals(2, count($allMessages));