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