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