Update Unit test styling to cover the future coder version
[civicrm-core.git] / tests / phpunit / CRM / Mailing / BaseMailingSystemTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
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 *
34 * @copyright CiviCRM LLC (c) 2004-2019
35 */
36
37 /**
38 * Class CRM_Mailing_MailingSystemTest
39 * @group headless
40 * @see \Civi\FlexMailer\FlexMailerSystemTest
41 * @see CRM_Mailing_MailingSystemTest
42 */
43 abstract 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();
58 CRM_Mailing_BAO_MailingJob::$mailsProcessed = 0;
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 // DGW
80 CRM_Mailing_BAO_MailingJob::$mailsProcessed = 0;
81 parent::tearDown();
82 }
83
84 /**
85 * Generate a fully-formatted mailing with standard email headers.
86 */
87 public function testBasicHeaders() {
88 $allMessages = $this->runMailingSuccess(array(
89 'subject' => 'Accidents in cars cause children for {contact.display_name}!',
90 'body_text' => 'BEWARE children need regular infusions of toys. Santa knows your {domain.address}. There is no {action.optOutUrl}.',
91 ));
92 foreach ($allMessages as $k => $message) {
93 /** @var ezcMail $message */
94
95 $offset = $k + 1;
96
97 $this->assertEquals("FIXME", $message->from->name);
98 $this->assertEquals("info@EXAMPLE.ORG", $message->from->email);
99 $this->assertEquals("Mr. Foo{$offset} Anderson II", $message->to[0]->name);
100 $this->assertEquals("mail{$offset}@nul.example.com", $message->to[0]->email);
101
102 $this->assertRegExp('#^text/plain; charset=utf-8#', $message->headers['Content-Type']);
103 $this->assertRegExp(';^b\.[\d\.a-f]+@chaos.org$;', $message->headers['Return-Path']);
104 $this->assertRegExp(';^b\.[\d\.a-f]+@chaos.org$;', $message->headers['X-CiviMail-Bounce'][0]);
105 $this->assertRegExp(';^\<mailto:u\.[\d\.a-f]+@chaos.org\>$;', $message->headers['List-Unsubscribe'][0]);
106 $this->assertEquals('bulk', $message->headers['Precedence'][0]);
107 }
108 }
109
110 /**
111 * Generate a fully-formatted mailing (with body_text content).
112 */
113 public function testText() {
114 $allMessages = $this->runMailingSuccess(array(
115 'subject' => 'Accidents in cars cause children for {contact.display_name}!',
116 'body_text' => 'BEWARE children need regular infusions of toys. Santa knows your {domain.address}. There is no {action.optOutUrl}.',
117 'open_tracking' => 1,
118 // Note: open_tracking does nothing with text, but we'll just verify that it does nothing
119 ));
120 foreach ($allMessages as $message) {
121 /** @var ezcMail $message */
122 /** @var ezcMailText $textPart */
123
124 $this->assertTrue($message->body instanceof ezcMailText);
125
126 $this->assertEquals('plain', $message->body->subType);
127 $this->assertRegExp(
128 ";" .
129 // Default header
130 "Sample Header for TEXT formatted content.\n" .
131 "BEWARE children need regular infusions of toys. Santa knows your .*\\. There is no http.*civicrm/mailing/optout.*\\.\n" .
132 // Default footer
133 "to unsubscribe: http.*civicrm/mailing/optout" .
134 ";",
135 $message->body->text
136 );
137 }
138 }
139
140 /**
141 * Generate a fully-formatted mailing (with body_html content).
142 */
143 public function testHtmlWithOpenTracking() {
144 $allMessages = $this->runMailingSuccess(array(
145 'subject' => 'Example Subject',
146 '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>',
147 'open_tracking' => 1,
148 'url_tracking' => 0,
149 ));
150 foreach ($allMessages as $message) {
151 /** @var ezcMail $message */
152 /** @var ezcMailText $htmlPart */
153 /** @var ezcMailText $textPart */
154
155 $this->assertTrue($message->body instanceof ezcMailMultipartAlternative);
156
157 list($textPart, $htmlPart) = $message->body->getParts();
158
159 $this->assertEquals('html', $htmlPart->subType);
160 $this->assertRegExp(
161 ";" .
162 // Default header
163 "Sample Header for HTML formatted content.\n" .
164 // FIXME: CiviMail puts double " after hyperlink!
165 // body_html
166 "<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" .
167 // Default footer
168 "Sample Footer for HTML formatted content" .
169 ".*\n" .
170 "<img src=\".*extern/open.php.*\"" .
171 ";",
172 $htmlPart->text
173 );
174
175 $this->assertEquals('plain', $textPart->subType);
176 $this->assertRegExp(
177 ";" .
178 // Default header
179 "Sample Header for TEXT formatted content.\n" .
180 // body_html, filtered
181 "You can go to Google \\[1\\] or opt out \\[2\\]\\.\n" .
182 "\n" .
183 "Links:\n" .
184 "------\n" .
185 "\\[1\\] http://example.net/first\\?cs=[0-9a-f_]+\n" .
186 "\\[2\\] http.*civicrm/mailing/optout.*\n" .
187 "\n" .
188 // Default footer
189 "to unsubscribe: http.*civicrm/mailing/optout" .
190 ";",
191 $textPart->text
192 );
193 }
194 }
195
196 /**
197 * Generate a fully-formatted mailing (with body_html content).
198 */
199 public function testHtmlWithOpenAndUrlTracking() {
200 $allMessages = $this->runMailingSuccess(array(
201 'subject' => 'Example Subject',
202 'body_html' => '<p>You can go to <a href="http://example.net">Google</a> or <a href="{action.optOutUrl}">opt out</a>.</p>',
203 'open_tracking' => 1,
204 'url_tracking' => 1,
205 ));
206 foreach ($allMessages as $message) {
207 /** @var ezcMail $message */
208 /** @var ezcMailText $htmlPart */
209 /** @var ezcMailText $textPart */
210
211 $this->assertTrue($message->body instanceof ezcMailMultipartAlternative);
212
213 list($textPart, $htmlPart) = $message->body->getParts();
214
215 $this->assertEquals('html', $htmlPart->subType);
216 $this->assertRegExp(
217 ";" .
218 // body_html
219 "<p>You can go to <a href=['\"].*extern/url\.php\?u=\d+&amp\\;qid=\d+['\"] rel='nofollow'>Google</a>" .
220 " or <a href=\"http.*civicrm/mailing/optout.*\">opt out</a>.</p>\n" .
221 // Default footer
222 "Sample Footer for HTML formatted content" .
223 ".*\n" .
224 // Open-tracking code
225 "<img src=\".*extern/open.php.*\"" .
226 ";",
227 $htmlPart->text
228 );
229
230 $this->assertEquals('plain', $textPart->subType);
231 $this->assertRegExp(
232 ";" .
233 // body_html, filtered
234 "You can go to Google \\[1\\] or opt out \\[2\\]\\.\n" .
235 "\n" .
236 "Links:\n" .
237 "------\n" .
238 "\\[1\\] .*extern/url\.php\?u=\d+&qid=\d+\n" .
239 "\\[2\\] http.*civicrm/mailing/optout.*\n" .
240 "\n" .
241 // Default footer
242 "to unsubscribe: http.*civicrm/mailing/optout" .
243 ";",
244 $textPart->text
245 );
246 }
247 }
248
249 /**
250 * Each case comes in four parts:
251 * 1. Mailing HTML (body_html)
252 * 2. Regex to run against final HTML
253 * 3. Regex to run against final text
254 * 4. Additional mailing options
255 *
256 * @return array
257 */
258 public function urlTrackingExamples() {
259 $cases = array();
260
261 // Tracking disabled
262 $cases[] = array(
263 '<p><a href="http://example.net/">Foo</a></p>',
264 ';<p><a href="http://example\.net/">Foo</a></p>;',
265 ';\\[1\\] http://example\.net/;',
266 array('url_tracking' => 0),
267 );
268 $cases[] = array(
269 '<p><a href="http://example.net/?id={contact.contact_id}">Foo</a></p>',
270 // FIXME: Legacy tracker adds extra quote after URL
271 ';<p><a href="http://example\.net/\?id=\d+""?>Foo</a></p>;',
272 ';\\[1\\] http://example\.net/\?id=\d+;',
273 array('url_tracking' => 0),
274 );
275 $cases[] = array(
276 '<p><a href="{action.optOutUrl}">Foo</a></p>',
277 ';<p><a href="http.*civicrm/mailing/optout.*">Foo</a></p>;',
278 ';\\[1\\] http.*civicrm/mailing/optout.*;',
279 array('url_tracking' => 0),
280 );
281 $cases[] = array(
282 '<p>Look at <img src="http://example.net/foo.png">.</p>',
283 ';<p>Look at <img src="http://example\.net/foo\.png">\.</p>;',
284 ';Look at \.;',
285 array('url_tracking' => 0),
286 );
287 $cases[] = array(
288 // Plain-text URL's are tracked in plain-text emails...
289 // but not in HTML emails.
290 "<p>Please go to: http://example.net/</p>",
291 ";<p>Please go to: http://example\.net/</p>;",
292 ';Please go to: http://example\.net/;',
293 array('url_tracking' => 0),
294 );
295
296 // Tracking enabled
297 $cases[] = array(
298 '<p><a href="http://example.net/">Foo</a></p>',
299 ';<p><a href=[\'"].*extern/url\.php\?u=\d+.*[\'"]>Foo</a></p>;',
300 ';\\[1\\] .*extern/url\.php\?u=\d+.*;',
301 array('url_tracking' => 1),
302 );
303 $cases[] = array(
304 // FIXME: CiviMail URL tracking doesn't track tokenized links.
305 '<p><a href="http://example.net/?id={contact.contact_id}">Foo</a></p>',
306 // FIXME: Legacy tracker adds extra quote after URL
307 ';<p><a href="http://example\.net/\?id=\d+""?>Foo</a></p>;',
308 ';\\[1\\] http://example\.net/\?id=\d+;',
309 array('url_tracking' => 1),
310 );
311 $cases[] = array(
312 // It would be redundant/slow to track the action URLs?
313 '<p><a href="{action.optOutUrl}">Foo</a></p>',
314 ';<p><a href="http.*civicrm/mailing/optout.*">Foo</a></p>;',
315 ';\\[1\\] http.*civicrm/mailing/optout.*;',
316 array('url_tracking' => 1),
317 );
318 $cases[] = array(
319 // It would be excessive/slow to track every embedded image.
320 '<p>Look at <img src="http://example.net/foo.png">.</p>',
321 ';<p>Look at <img src="http://example\.net/foo\.png">\.</p>;',
322 ';Look at \.;',
323 array('url_tracking' => 1),
324 );
325 $cases[] = array(
326 // Plain-text URL's are tracked in plain-text emails...
327 // but not in HTML emails.
328 "<p>Please go to: http://example.net/</p>",
329 ";<p>Please go to: http://example\.net/</p>;",
330 ';Please go to: .*extern/url.php\?u=\d+&qid=\d+;',
331 array('url_tracking' => 1),
332 );
333
334 return $cases;
335 }
336
337 /**
338 * Generate a fully-formatted mailing (with body_html content).
339 *
340 * @dataProvider urlTrackingExamples
341 */
342 public function testUrlTracking($inputHtml, $htmlUrlRegex, $textUrlRegex, $params) {
343 $caseName = print_r(array('inputHtml' => $inputHtml, 'params' => $params), 1);
344
345 $allMessages = $this->runMailingSuccess($params + array(
346 'subject' => 'Example Subject',
347 'body_html' => $inputHtml,
348 ));
349 foreach ($allMessages as $message) {
350 /** @var ezcMail $message */
351 /** @var ezcMailText $htmlPart */
352 /** @var ezcMailText $textPart */
353
354 $this->assertTrue($message->body instanceof ezcMailMultipartAlternative);
355
356 list($textPart, $htmlPart) = $message->body->getParts();
357
358 if ($htmlUrlRegex) {
359 $this->assertEquals('html', $htmlPart->subType, "Should have HTML part in case: $caseName");
360 $this->assertRegExp($htmlUrlRegex, $htmlPart->text, "Should have correct HTML in case: $caseName");
361 }
362
363 if ($textUrlRegex) {
364 $this->assertEquals('plain', $textPart->subType, "Should have text part in case: $caseName");
365 $this->assertRegExp($textUrlRegex, $textPart->text, "Should have correct text in case: $caseName");
366 }
367 }
368 }
369
370 /**
371 * Create contacts in group.
372 *
373 * @param int $count
374 * @param int $groupID
375 * @param string $domain
376 */
377 protected function createContactsInGroup(
378 $count,
379 $groupID,
380 $domain = 'nul.example.com'
381 ) {
382 for ($i = 1; $i <= $count; $i++) {
383 $contactID = $this->individualCreate(array(
384 'first_name' => "Foo{$i}",
385 'email' => 'mail' . $i . '@' . $domain,
386 ));
387 $this->callAPISuccess('group_contact', 'create', array(
388 'contact_id' => $contactID,
389 'group_id' => $groupID,
390 'status' => 'Added',
391 ));
392 }
393 }
394
395 /**
396 * Create and execute a mailing. Return the matching messages.
397 *
398 * @param array $params
399 * List of parameters to send to Mailing.create API.
400 * @return array<ezcMail>
401 */
402 protected function runMailingSuccess($params) {
403 $mailingParams = array_merge($this->defaultParams, $params);
404 $this->callAPISuccess('mailing', 'create', $mailingParams);
405 $this->_mut->assertRecipients(array());
406 $this->callAPISuccess('job', 'process_mailing', array('runInNonProductionEnvironment' => TRUE));
407
408 $allMessages = $this->_mut->getAllMessages('ezc');
409 // There are exactly two contacts produced by setUp().
410 $this->assertEquals(2, count($allMessages));
411
412 return $allMessages;
413 }
414
415 }