Commit | Line | Data |
---|---|---|
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 | */ | |
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(); | |
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+&\\;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 | } |