Merge pull request #10925 from twomice/CRM-21122_dashboard_allow_smart_groups
[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" .
130 "to unsubscribe: http.*civicrm/mailing/optout" . // Default footer
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 ";" .
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
163 ".*\n" .
164 "<img src=\".*extern/open.php.*\"" .
165 ";",
166 $htmlPart->text
167 );
168
169 $this->assertEquals('plain', $textPart->subType);
170 $this->assertRegExp(
171 ";" .
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
174 "\n" .
175 "Links:\n" .
176 "------\n" .
177 "\\[1\\] http://example.net/first\\?cs=[0-9a-f_]+\n" .
178 "\\[2\\] http.*civicrm/mailing/optout.*\n" .
179 "\n" .
180 "to unsubscribe: http.*civicrm/mailing/optout" . // Default footer
181 ";",
182 $textPart->text
183 );
184 }
185 }
186
187 /**
188 * Generate a fully-formatted mailing (with body_html content).
189 */
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,
195 'url_tracking' => 1,
196 ));
6f616e5c 197 foreach ($allMessages as $message) {
b4a332a9
TO
198 /** @var ezcMail $message */
199 /** @var ezcMailText $htmlPart */
200 /** @var ezcMailText $textPart */
201
202 $this->assertTrue($message->body instanceof ezcMailMultipartAlternative);
203
204 list($textPart, $htmlPart) = $message->body->getParts();
205
206 $this->assertEquals('html', $htmlPart->subType);
207 $this->assertRegExp(
208 ";" .
209 // body_html
210 "<p>You can go to <a href=['\"].*extern/url\.php\?u=\d+&amp\\;qid=\d+['\"]>Google</a>" .
211 " or <a href=\"http.*civicrm/mailing/optout.*\">opt out</a>.</p>\n" .
212 // Default footer
213 "Sample Footer for HTML formatted content" .
214 ".*\n" .
215 // Open-tracking code
216 "<img src=\".*extern/open.php.*\"" .
217 ";",
218 $htmlPart->text
219 );
220
221 $this->assertEquals('plain', $textPart->subType);
222 $this->assertRegExp(
223 ";" .
224 // body_html, filtered
225 "You can go to Google \\[1\\] or opt out \\[2\\]\\.\n" .
226 "\n" .
227 "Links:\n" .
228 "------\n" .
229 "\\[1\\] .*extern/url\.php\?u=\d+&qid=\d+\n" .
230 "\\[2\\] http.*civicrm/mailing/optout.*\n" .
231 "\n" .
232 // Default footer
233 "to unsubscribe: http.*civicrm/mailing/optout" .
234 ";",
235 $textPart->text
236 );
237 }
238 }
239
6f616e5c 240 /**
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
246 *
247 * @return array
248 */
b4a332a9
TO
249 public function urlTrackingExamples() {
250 $cases = array();
251
b4a332a9 252 // Tracking disabled
b4a332a9
TO
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
b4a332a9
TO
288 $cases[] = array(
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),
293 );
294 $cases[] = array(
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),
301 );
302 $cases[] = array(
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),
308 );
309 $cases[] = array(
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>;',
313 ';Look at \.;',
314 array('url_tracking' => 1),
315 );
316 $cases[] = array(
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),
323 );
324
325 return $cases;
326 }
327
328 /**
329 * Generate a fully-formatted mailing (with body_html content).
330 *
331 * @dataProvider urlTrackingExamples
332 */
333 public function testUrlTracking($inputHtml, $htmlUrlRegex, $textUrlRegex, $params) {
334 $caseName = print_r(array('inputHtml' => $inputHtml, 'params' => $params), 1);
335
336 $allMessages = $this->runMailingSuccess($params + array(
337 'subject' => 'Example Subject',
338 'body_html' => $inputHtml,
339 ));
6f616e5c 340 foreach ($allMessages as $message) {
b4a332a9
TO
341 /** @var ezcMail $message */
342 /** @var ezcMailText $htmlPart */
343 /** @var ezcMailText $textPart */
344
345 $this->assertTrue($message->body instanceof ezcMailMultipartAlternative);
346
347 list($textPart, $htmlPart) = $message->body->getParts();
348
349 if ($htmlUrlRegex) {
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");
352 }
353
354 if ($textUrlRegex) {
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");
357 }
358 }
359 }
360
361 /**
362 * Create contacts in group.
363 *
364 * @param int $count
365 * @param int $groupID
366 * @param string $domain
367 */
368 protected function createContactsInGroup(
369 $count,
370 $groupID,
371 $domain = 'nul.example.com'
372 ) {
373 for ($i = 1; $i <= $count; $i++) {
374 $contactID = $this->individualCreate(array(
375 'first_name' => "Foo{$i}",
376 'email' => 'mail' . $i . '@' . $domain,
377 ));
378 $this->callAPISuccess('group_contact', 'create', array(
379 'contact_id' => $contactID,
380 'group_id' => $groupID,
381 'status' => 'Added',
382 ));
383 }
384 }
385
386 /**
387 * Create and execute a mailing. Return the matching messages.
388 *
389 * @param array $params
390 * List of parameters to send to Mailing.create API.
391 * @return array<ezcMail>
392 */
393 protected function runMailingSuccess($params) {
394 $mailingParams = array_merge($this->defaultParams, $params);
395 $this->callAPISuccess('mailing', 'create', $mailingParams);
396 $this->_mut->assertRecipients(array());
03c5ceba 397 $this->callAPISuccess('job', 'process_mailing', array('runInNonProductionEnvironment' => TRUE));
b4a332a9
TO
398
399 $allMessages = $this->_mut->getAllMessages('ezc');
400 // There are exactly two contacts produced by setUp().
401 $this->assertEquals(2, count($allMessages));
402
403 return $allMessages;
404 }
405
406}