Merge pull request #11439 from tunbola/CRM-21584
[civicrm-core.git] / tests / phpunit / CRM / Mailing / BaseMailingSystemTest.php
CommitLineData
b4a332a9
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
15a4309a 6 | Copyright CiviCRM LLC (c) 2004-2017 |
b4a332a9
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28/**
29 * Test that content produced by CiviMail looks the way it's expected.
30 *
31 * @package CiviCRM_APIv3
32 * @subpackage API_Job
33 *
15a4309a 34 * @copyright CiviCRM LLC (c) 2004-2017
b4a332a9
TO
35 */
36
37/**
38 * Class CRM_Mailing_MailingSystemTest
39 * @group headless
40 * @see \Civi\FlexMailer\FlexMailerSystemTest
41 * @see CRM_Mailing_MailingSystemTest
42 */
43abstract class CRM_Mailing_BaseMailingSystemTest extends CiviUnitTestCase {
44 protected $_apiversion = 3;
45
46 public $DBResetRequired = FALSE;
47 public $defaultParams = array();
48 private $_groupID;
49
50 /**
51 * @var CiviMailUtils
52 */
53 private $_mut;
54
55 public function setUp() {
56 $this->useTransaction();
57 parent::setUp();
6f616e5c 58 CRM_Mailing_BAO_MailingJob::$mailsProcessed = 0;
b4a332a9
TO
59
60 $this->_groupID = $this->groupCreate();
61 $this->createContactsInGroup(2, $this->_groupID);
62
63 $this->defaultParams = array(
64 'name' => 'mailing name',
65 'created_id' => 1,
66 'groups' => array('include' => array($this->_groupID)),
67 'scheduled_date' => 'now',
68 );
69 $this->_mut = new CiviMailUtils($this, TRUE);
70 $this->callAPISuccess('mail_settings', 'get',
71 array('api.mail_settings.create' => array('domain' => 'chaos.org')));
72 }
73
74 /**
75 */
76 public function tearDown() {
77 $this->_mut->stop();
78 CRM_Utils_Hook::singleton()->reset();
79 CRM_Mailing_BAO_MailingJob::$mailsProcessed = 0; // DGW
80 parent::tearDown();
81 }
82
83 /**
84 * Generate a fully-formatted mailing with standard email headers.
85 */
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}.',
90 ));
91 foreach ($allMessages as $k => $message) {
92 /** @var ezcMail $message */
93
94 $offset = $k + 1;
95
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);
100
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]);
106 }
107 }
108
109 /**
110 * Generate a fully-formatted mailing (with body_text content).
111 */
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
118 ));
6f616e5c 119 foreach ($allMessages as $message) {
b4a332a9
TO
120 /** @var ezcMail $message */
121 /** @var ezcMailText $textPart */
122
123 $this->assertTrue($message->body instanceof ezcMailText);
124
125 $this->assertEquals('plain', $message->body->subType);
126 $this->assertRegExp(
127 ";" .
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" .
360269e5 130 "Unsubscribe: http.*civicrm/mailing/optout" . // Default footer
b4a332a9
TO
131 ";",
132 $message->body->text
133 );
134 }
135 }
136
137 /**
138 * Generate a fully-formatted mailing (with body_html content).
139 */
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,
145 'url_tracking' => 0,
146 ));
6f616e5c 147 foreach ($allMessages as $message) {
b4a332a9
TO
148 /** @var ezcMail $message */
149 /** @var ezcMailText $htmlPart */
150 /** @var ezcMailText $textPart */
151
152 $this->assertTrue($message->body instanceof ezcMailMultipartAlternative);
153
154 list($textPart, $htmlPart) = $message->body->getParts();
155
156 $this->assertEquals('html', $htmlPart->subType);
157 $this->assertRegExp(
158 ";" .
360269e5 159 "<p>Sample Header for HTML formatted content\.</p>.*" . // Default header
b4a332a9 160 // FIXME: CiviMail puts double " after hyperlink!
360269e5
MM
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>.*" . // body_html
162 "<p>Sample Footer for HTML formatted content\.</p>.*" . // Default footer
b4a332a9 163 "<img src=\".*extern/open.php.*\"" .
360269e5 164 ";s",
b4a332a9
TO
165 $htmlPart->text
166 );
b4a332a9
TO
167 $this->assertEquals('plain', $textPart->subType);
168 $this->assertRegExp(
169 ";" .
360269e5
MM
170 "Sample Header for HTML formatted content\\..*" . // Default header (converted from HTML as it was not supplied)
171 "You can go to Google \\[1\\] or opt out \\[2\\]\\..*" . // Text body (converted from HTML as it was not supplied)
172 "Sample Footer for HTML formatted content\..*" . // Footer footer (converted from HTML as it was not supplied)
173 "Unsubscribe \\[2\\]\\..*" . // Text body (converted from HTML as it was not supplied)
174 "Links:.*" .
175 "------.*" .
176 "\\[1\\] http://example.net/first\\?cs=[0-9a-f_]+.*" .
177 "\\[2\\] http.*civicrm/mailing/optout.*" .
178 ";s",
b4a332a9
TO
179 $textPart->text
180 );
181 }
182 }
183
184 /**
185 * Generate a fully-formatted mailing (with body_html content).
186 */
187 public function testHtmlWithOpenAndUrlTracking() {
188 $allMessages = $this->runMailingSuccess(array(
189 'subject' => 'Example Subject',
190 'body_html' => '<p>You can go to <a href="http://example.net">Google</a> or <a href="{action.optOutUrl}">opt out</a>.</p>',
191 'open_tracking' => 1,
192 'url_tracking' => 1,
193 ));
6f616e5c 194 foreach ($allMessages as $message) {
b4a332a9
TO
195 /** @var ezcMail $message */
196 /** @var ezcMailText $htmlPart */
197 /** @var ezcMailText $textPart */
198
199 $this->assertTrue($message->body instanceof ezcMailMultipartAlternative);
200
201 list($textPart, $htmlPart) = $message->body->getParts();
202
203 $this->assertEquals('html', $htmlPart->subType);
204 $this->assertRegExp(
205 ";" .
206 // body_html
207 "<p>You can go to <a href=['\"].*extern/url\.php\?u=\d+&amp\\;qid=\d+['\"]>Google</a>" .
208 " or <a href=\"http.*civicrm/mailing/optout.*\">opt out</a>.</p>\n" .
209 // Default footer
360269e5 210 "<p>Sample Footer for HTML formatted content\.</p>" .
b4a332a9
TO
211 ".*\n" .
212 // Open-tracking code
213 "<img src=\".*extern/open.php.*\"" .
214 ";",
215 $htmlPart->text
216 );
b4a332a9
TO
217 $this->assertEquals('plain', $textPart->subType);
218 $this->assertRegExp(
219 ";" .
360269e5
MM
220 "You can go to Google \\[1\\] or opt out \\[2\\]\\..*" .
221 "Unsubscribe \\[2\\].*" .
b4a332a9
TO
222 "Links:\n" .
223 "------\n" .
360269e5
MM
224 "\\[1\\] .*extern/url\.php\?u=\d+&qid=\d+.*" .
225 "\\[2\\] http.*civicrm/mailing/optout.*" .
226 ";s",
b4a332a9
TO
227 $textPart->text
228 );
229 }
230 }
231
6f616e5c 232 /**
233 * Each case comes in four parts:
234 * 1. Mailing HTML (body_html)
235 * 2. Regex to run against final HTML
236 * 3. Regex to run against final text
237 * 4. Additional mailing options
238 *
239 * @return array
240 */
b4a332a9
TO
241 public function urlTrackingExamples() {
242 $cases = array();
243
b4a332a9 244 // Tracking disabled
b4a332a9
TO
245 $cases[] = array(
246 '<p><a href="http://example.net/">Foo</a></p>',
247 ';<p><a href="http://example\.net/">Foo</a></p>;',
248 ';\\[1\\] http://example\.net/;',
249 array('url_tracking' => 0),
250 );
251 $cases[] = array(
252 '<p><a href="http://example.net/?id={contact.contact_id}">Foo</a></p>',
253 // FIXME: Legacy tracker adds extra quote after URL
254 ';<p><a href="http://example\.net/\?id=\d+""?>Foo</a></p>;',
255 ';\\[1\\] http://example\.net/\?id=\d+;',
256 array('url_tracking' => 0),
257 );
258 $cases[] = array(
259 '<p><a href="{action.optOutUrl}">Foo</a></p>',
260 ';<p><a href="http.*civicrm/mailing/optout.*">Foo</a></p>;',
261 ';\\[1\\] http.*civicrm/mailing/optout.*;',
262 array('url_tracking' => 0),
263 );
264 $cases[] = array(
265 '<p>Look at <img src="http://example.net/foo.png">.</p>',
266 ';<p>Look at <img src="http://example\.net/foo\.png">\.</p>;',
267 ';Look at \.;',
268 array('url_tracking' => 0),
269 );
270 $cases[] = array(
271 // Plain-text URL's are tracked in plain-text emails...
272 // but not in HTML emails.
273 "<p>Please go to: http://example.net/</p>",
274 ";<p>Please go to: http://example\.net/</p>;",
275 ';Please go to: http://example\.net/;',
276 array('url_tracking' => 0),
277 );
278
279 // Tracking enabled
b4a332a9
TO
280 $cases[] = array(
281 '<p><a href="http://example.net/">Foo</a></p>',
282 ';<p><a href=[\'"].*extern/url\.php\?u=\d+.*[\'"]>Foo</a></p>;',
283 ';\\[1\\] .*extern/url\.php\?u=\d+.*;',
284 array('url_tracking' => 1),
285 );
286 $cases[] = array(
287 // FIXME: CiviMail URL tracking doesn't track tokenized links.
288 '<p><a href="http://example.net/?id={contact.contact_id}">Foo</a></p>',
289 // FIXME: Legacy tracker adds extra quote after URL
290 ';<p><a href="http://example\.net/\?id=\d+""?>Foo</a></p>;',
291 ';\\[1\\] http://example\.net/\?id=\d+;',
292 array('url_tracking' => 1),
293 );
294 $cases[] = array(
295 // It would be redundant/slow to track the action URLs?
296 '<p><a href="{action.optOutUrl}">Foo</a></p>',
297 ';<p><a href="http.*civicrm/mailing/optout.*">Foo</a></p>;',
298 ';\\[1\\] http.*civicrm/mailing/optout.*;',
299 array('url_tracking' => 1),
300 );
301 $cases[] = array(
302 // It would be excessive/slow to track every embedded image.
303 '<p>Look at <img src="http://example.net/foo.png">.</p>',
304 ';<p>Look at <img src="http://example\.net/foo\.png">\.</p>;',
305 ';Look at \.;',
306 array('url_tracking' => 1),
307 );
308 $cases[] = array(
360269e5 309 // Plain-text URLs are not tracked.
b4a332a9
TO
310 "<p>Please go to: http://example.net/</p>",
311 ";<p>Please go to: http://example\.net/</p>;",
360269e5 312 ';Please go to: http://example\.net/;',
b4a332a9
TO
313 array('url_tracking' => 1),
314 );
315
316 return $cases;
317 }
318
319 /**
320 * Generate a fully-formatted mailing (with body_html content).
321 *
322 * @dataProvider urlTrackingExamples
323 */
324 public function testUrlTracking($inputHtml, $htmlUrlRegex, $textUrlRegex, $params) {
325 $caseName = print_r(array('inputHtml' => $inputHtml, 'params' => $params), 1);
326
327 $allMessages = $this->runMailingSuccess($params + array(
328 'subject' => 'Example Subject',
329 'body_html' => $inputHtml,
330 ));
6f616e5c 331 foreach ($allMessages as $message) {
b4a332a9
TO
332 /** @var ezcMail $message */
333 /** @var ezcMailText $htmlPart */
334 /** @var ezcMailText $textPart */
335
336 $this->assertTrue($message->body instanceof ezcMailMultipartAlternative);
337
338 list($textPart, $htmlPart) = $message->body->getParts();
339
340 if ($htmlUrlRegex) {
341 $this->assertEquals('html', $htmlPart->subType, "Should have HTML part in case: $caseName");
342 $this->assertRegExp($htmlUrlRegex, $htmlPart->text, "Should have correct HTML in case: $caseName");
343 }
344
345 if ($textUrlRegex) {
346 $this->assertEquals('plain', $textPart->subType, "Should have text part in case: $caseName");
347 $this->assertRegExp($textUrlRegex, $textPart->text, "Should have correct text in case: $caseName");
348 }
349 }
350 }
351
352 /**
353 * Create contacts in group.
354 *
355 * @param int $count
356 * @param int $groupID
357 * @param string $domain
358 */
359 protected function createContactsInGroup(
360 $count,
361 $groupID,
362 $domain = 'nul.example.com'
363 ) {
364 for ($i = 1; $i <= $count; $i++) {
365 $contactID = $this->individualCreate(array(
366 'first_name' => "Foo{$i}",
367 'email' => 'mail' . $i . '@' . $domain,
368 ));
369 $this->callAPISuccess('group_contact', 'create', array(
370 'contact_id' => $contactID,
371 'group_id' => $groupID,
372 'status' => 'Added',
373 ));
374 }
375 }
376
377 /**
378 * Create and execute a mailing. Return the matching messages.
379 *
380 * @param array $params
381 * List of parameters to send to Mailing.create API.
382 * @return array<ezcMail>
383 */
384 protected function runMailingSuccess($params) {
385 $mailingParams = array_merge($this->defaultParams, $params);
386 $this->callAPISuccess('mailing', 'create', $mailingParams);
387 $this->_mut->assertRecipients(array());
03c5ceba 388 $this->callAPISuccess('job', 'process_mailing', array('runInNonProductionEnvironment' => TRUE));
b4a332a9
TO
389
390 $allMessages = $this->_mut->getAllMessages('ezc');
391 // There are exactly two contacts produced by setUp().
392 $this->assertEquals(2, count($allMessages));
393
394 return $allMessages;
395 }
396
397}