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