Merge pull request #17715 from civicrm/5.27
[civicrm-core.git] / tests / phpunit / CiviTest / CiviMailUtils.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7d61e75f 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 * Mail utils for use during unit testing to allow retrieval
14 * and examination of 'sent' emails.
15 *
16 * Basic usage:
17 *
18 * $mut = new CiviMailUtils( $this, true ); //true automatically starts spooling
19 * ... do stuff ...
20 * $msg = $mut->getMostRecentEmail( 'raw' ); // or 'ezc' to get an ezc mail object
21 * ... assert stuff about $msg ...
22 * $mut->stop();
23 *
24 *
25 * @package CiviCRM
26 */
27
4cbe18b8
EM
28/**
29 * Class CiviMailUtils
30 */
a6439b6a 31class CiviMailUtils extends PHPUnit\Framework\TestCase {
6a488035
TO
32
33 /**
1d3260ea
SL
34 * Current outbound email option
35 * @var mixed
6a488035
TO
36 */
37 protected $_outBound_option = NULL;
38
39 /**
1d3260ea
SL
40 * Is this a webtest
41 * @var bool
6a488035
TO
42 */
43 protected $_webtest = FALSE;
44
45 /**
eceb18cc 46 * Constructor.
6a488035 47 *
858d0bf8 48 * @param CiviSeleniumTestCase|CiviUnitTestCase $unit_test The currently running test
e16033b4
TO
49 * @param bool $startImmediately
50 * Start writing to db now or wait until start() is called.
6a488035 51 */
00be9182 52 public function __construct(&$unit_test, $startImmediately = TRUE) {
70520a0b
CW
53 $this->_ut = $unit_test;
54
6a488035
TO
55 // Check if running under webtests or not
56 if (is_subclass_of($unit_test, 'CiviSeleniumTestCase')) {
57 $this->_webtest = TRUE;
58 }
59
60 if ($startImmediately) {
61 $this->start();
62 }
63 }
64
65 /**
eceb18cc 66 * Start writing emails to db instead of current option.
6a488035 67 */
00be9182 68 public function start() {
6a488035
TO
69 if ($this->_webtest) {
70 // Change outbound mail setting
14d3f751 71 $this->_ut->openCiviPage('admin/setting/smtp', "reset=1", "_qf_Smtp_next");
6a488035
TO
72
73 // First remember the current setting
74 $this->_outBound_option = $this->getSelectedOutboundOption();
75
76 $this->_ut->click('xpath=//input[@name="outBound_option" and @value="' . CRM_Mailing_Config::OUTBOUND_OPTION_REDIRECT_TO_DB . '"]');
14d3f751 77 $this->_ut->clickLink("_qf_Smtp_next");
6a488035
TO
78
79 // Is there supposed to be a status message displayed when outbound email settings are changed?
80 // assert something?
81
82 }
83 else {
84
85 // save current setting for outbound option, then change it
86 $mailingBackend = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
87 'mailing_backend'
88 );
89
90 $this->_outBound_option = $mailingBackend['outBound_option'];
91 $mailingBackend['outBound_option'] = CRM_Mailing_Config::OUTBOUND_OPTION_REDIRECT_TO_DB;
92
08ef4ddd 93 Civi::settings()->set('mailing_backend', $mailingBackend);
6a488035
TO
94
95 $mailingBackend = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
96 'mailing_backend'
97 );
98 }
99 }
100
00be9182 101 public function stop() {
6a488035 102 if ($this->_webtest) {
054c3522
CW
103 if ($this->_outBound_option != CRM_Mailing_Config::OUTBOUND_OPTION_REDIRECT_TO_DB) {
104 // Change outbound mail setting
105 $this->_ut->openCiviPage('admin/setting/smtp', "reset=1", "_qf_Smtp_next");
106 $this->_ut->click('xpath=//input[@name="outBound_option" and @value="' . $this->_outBound_option . '"]');
3a35908d 107 // There will be a warning when switching from test to live mode
054c3522 108 if ($this->_outBound_option != CRM_Mailing_Config::OUTBOUND_OPTION_DISABLED) {
499508f0 109 $this->_ut->getAlert();
054c3522
CW
110 }
111 $this->_ut->clickLink("_qf_Smtp_next");
112 }
6a488035
TO
113 }
114 else {
115
116 $mailingBackend = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
117 'mailing_backend'
118 );
119
120 $mailingBackend['outBound_option'] = $this->_outBound_option;
121
08ef4ddd 122 Civi::settings()->set('mailing_backend', $mailingBackend);
6a488035
TO
123 }
124 }
125
4cbe18b8
EM
126 /**
127 * @param string $type
128 *
129 * @return ezcMail|string
130 */
00be9182 131 public function getMostRecentEmail($type = 'raw') {
6a488035
TO
132 $msg = '';
133
6a488035 134 if ($this->_webtest) {
6a488035 135 // I don't understand but for some reason we have to load the page twice for a recent mailing to appear.
14d3f751
CW
136 $this->_ut->openCiviPage('mailing/browse/archived', 'reset=1');
137 $this->_ut->openCiviPage('mailing/browse/archived', 'reset=1', 'css=td.crm-mailing-name');
138 }
139 // We can't fetch mailing headers from webtest so we'll only try if the format is raw
140 if ($this->_webtest && $type == 'raw') {
6a488035 141 // This should select the first "Report" link in the table, which is sorted by Completion Date descending, so in theory is the most recent email. Not sure of a more robust way at the moment.
b3d40287 142 $this->_ut->clickLink('xpath=//tr[contains(@id, "crm-mailing_")]//a[text()="Report"]');
6a488035
TO
143
144 // Also not sure how robust this is, but there isn't a good
145 // identifier for this link either.
146 $this->_ut->waitForElementPresent('xpath=//a[contains(text(), "View complete message")]');
a5d61f09 147 $this->_ut->clickAjaxLink('xpath=//a[contains(text(), "View complete message")]');
14d3f751 148 $msg = $this->_ut->getText('css=.ui-dialog-content.crm-ajax-container');
6a488035
TO
149 }
150 else {
151 $dao = CRM_Core_DAO::executeQuery('SELECT headers, body FROM civicrm_mailing_spool ORDER BY id DESC LIMIT 1');
152 if ($dao->fetch()) {
153 $msg = $dao->headers . "\n\n" . $dao->body;
154 }
6a488035
TO
155 }
156
157 switch ($type) {
158 case 'raw':
159 // nothing to do
160 break;
6c6e6187 161
6a488035
TO
162 case 'ezc':
163 $msg = $this->convertToEzc($msg);
164 break;
165 }
166 return $msg;
167 }
168
169 /**
e16033b4
TO
170 * @param string $type
171 * 'raw'|'ezc'.
cbdcc634 172 *
e88f4fee 173 * @throws CRM_Core_Exception
174 *
6a488035
TO
175 * @return array(ezcMail)|array(string)
176 */
00be9182 177 public function getAllMessages($type = 'raw') {
e88f4fee 178 $msgs = [];
6a488035
TO
179
180 if ($this->_webtest) {
e88f4fee 181 throw new CRM_Core_Exception('Not implemented: getAllMessages for WebTest');
6a488035 182 }
e88f4fee 183 $dao = CRM_Core_DAO::executeQuery('SELECT headers, body FROM civicrm_mailing_spool ORDER BY id');
184 while ($dao->fetch()) {
185 $msgs[] = $dao->headers . "\n\n" . $dao->body;
6a488035
TO
186 }
187
188 switch ($type) {
189 case 'raw':
190 // nothing to do
191 break;
6c6e6187 192
6a488035
TO
193 case 'ezc':
194 foreach ($msgs as $i => $msg) {
195 $msgs[$i] = $this->convertToEzc($msg);
196 }
197 break;
198 }
199
200 return $msgs;
201 }
202
4cbe18b8
EM
203 /**
204 * @return int
205 */
00be9182 206 public function getSelectedOutboundOption() {
6a488035
TO
207 $selectedOption = CRM_Mailing_Config::OUTBOUND_OPTION_MAIL;
208 // Is there a better way to do this? How do you get the currently selected value of a radio button in selenium?
209 for ($i = 0; $i <= 5; $i++) {
210 if ($i != CRM_Mailing_Config::OUTBOUND_OPTION_MOCK) {
211 if ($this->_ut->getValue('xpath=//input[@name="outBound_option" and @value="' . $i . '"]') == "on") {
212 $selectedOption = $i;
213 break;
214 }
215 }
216 }
217 return $selectedOption;
218 }
219
220 /*
221 * Utility functions (previously part of CiviUnitTestCase)
222 * Included for backward compatibility with existing tests.
223 */
224
225 /**
eceb18cc 226 * Check contents of mail log.
77b97be7 227 *
e16033b4
TO
228 * @param array $strings
229 * Strings that should be included.
230 * @param array $absentStrings
231 * Strings that should not be included.
77b97be7 232 * @param string $prefix
6a488035 233 *
77b97be7 234 * @return \ezcMail|string
6a488035 235 */
affcc9d2 236 public function checkMailLog($strings, $absentStrings = [], $prefix = '') {
6a488035 237 $mail = $this->getMostRecentEmail('raw');
481312d9 238 return $this->checkMailForStrings($strings, $absentStrings, $prefix, $mail);
239 }
240
241 /**
242 * Check contents of mail log.
243 *
244 * @param array $strings
245 * Strings that should be included.
246 * @param array $absentStrings
247 * Strings that should not be included.
248 * @param string $prefix
249 *
250 * @return \ezcMail|string
251 */
affcc9d2 252 public function checkAllMailLog($strings, $absentStrings = [], $prefix = '') {
481312d9 253 $mails = $this->getAllMessages('raw');
254 $mail = implode(',', $mails);
255 return $this->checkMailForStrings($strings, $absentStrings, $prefix, $mail);
6a488035
TO
256 }
257
258 /**
eceb18cc 259 * Check that mail log is empty.
1e1fdcf6 260 * @param string $prefix
6a488035 261 */
00be9182 262 public function assertMailLogEmpty($prefix = '') {
6a488035
TO
263 $mail = $this->getMostRecentEmail('raw');
264 $this->_ut->assertEmpty($mail, 'mail sent when it should not have been ' . $prefix);
265 }
266
267 /**
268 * Assert that $expectedRecipients (and no else) have received emails
269 *
e16033b4
TO
270 * @param array $expectedRecipients
271 * Array($msgPos => array($recipPos => $emailAddr)).
6a488035 272 */
00be9182 273 public function assertRecipients($expectedRecipients) {
affcc9d2 274 $recipients = [];
6a488035
TO
275 foreach ($this->getAllMessages('ezc') as $message) {
276 $recipients[] = CRM_Utils_Array::collect('email', $message->to);
277 }
11f535e4
TO
278 $cmp = function($a, $b) {
279 if ($a[0] == $b[0]) {
280 return 0;
281 }
282 return ($a[0] < $b[0]) ? 1 : -1;
283 };
284 usort($recipients, $cmp);
285 usort($expectedRecipients, $cmp);
6a488035
TO
286 $this->_ut->assertEquals(
287 $expectedRecipients,
288 $recipients,
289 "Incorrect recipients: " . print_r(array('expected' => $expectedRecipients, 'actual' => $recipients), TRUE)
290 );
291 }
292
f8c3594b
TO
293 /**
294 * Assert that $expectedSubjects (and no other subjects) were sent.
295 *
296 * @param array $expectedSubjects
297 * Array(string $subj).
298 */
299 public function assertSubjects($expectedSubjects) {
affcc9d2 300 $subjects = [];
f8c3594b
TO
301 foreach ($this->getAllMessages('ezc') as $message) {
302 /** @var ezcMail $message */
303 $subjects[] = $message->subject;
304 }
305 sort($subjects);
306 sort($expectedSubjects);
307 $this->_ut->assertEquals(
308 $expectedSubjects,
309 $subjects,
310 "Incorrect subjects: " . print_r(array('expected' => $expectedSubjects, 'actual' => $subjects), TRUE)
311 );
312 }
313
6a488035 314 /**
eceb18cc 315 * Remove any sent messages from the log.
aa1febb5 316 *
317 * @param int $limit
b13d4a61 318 * How many recent messages to remove, defaults to 0 (all).
aa1febb5 319 *
1e0f58c7 320 * @throws \CRM_Core_Exception
6a488035 321 */
b13d4a61 322 public function clearMessages($limit = 0) {
6a488035 323 if ($this->_webtest) {
1e0f58c7 324 throw new \CRM_Core_Exception("Not implemented: clearMessages for WebTest");
6a488035
TO
325 }
326 else {
b13d4a61
SL
327 $sql = 'DELETE FROM civicrm_mailing_spool ORDER BY id DESC';
328 if ($limit) {
329 $sql .= ' LIMIT ' . $limit;
330 }
331 CRM_Core_DAO::executeQuery($sql);
6a488035
TO
332 }
333 }
334
335 /**
e16033b4
TO
336 * @param string $msg
337 * Email header and body.
6a488035
TO
338 * @return ezcMail
339 */
340 private function convertToEzc($msg) {
14d3f751 341 $set = new ezcMailVariableSet($msg);
6a488035
TO
342 $parser = new ezcMailParser();
343 $mail = $parser->parseMail($set);
344 $this->_ut->assertNotEmpty($mail, 'Cannot parse mail');
345 return $mail[0];
346 }
96025800 347
481312d9 348 /**
349 * @param $strings
350 * @param $absentStrings
351 * @param $prefix
352 * @param $mail
353 * @return mixed
354 */
355 public function checkMailForStrings($strings, $absentStrings, $prefix, $mail) {
356 foreach ($strings as $string) {
357 $this->_ut->assertContains($string, $mail, "$string . not found in $mail $prefix");
358 }
359 foreach ($absentStrings as $string) {
fe7f4414 360 $this->_ut->assertEmpty(strstr($mail, $string), "$string incorrectly found in $mail $prefix");
481312d9 361 }
362 return $mail;
363 }
364
6a488035 365}