Merge pull request #12790 from alifrumin/core362
[civicrm-core.git] / tests / phpunit / CiviTest / CiviMailUtils.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
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 $dao->free();
169 }
170
171 switch ($type) {
172 case 'raw':
173 // nothing to do
174 break;
175
176 case 'ezc':
177 $msg = $this->convertToEzc($msg);
178 break;
179 }
180 return $msg;
181 }
182
183 /**
184 * @param string $type
185 * 'raw'|'ezc'.
186 *
187 * @throws Exception
188 * @return array(ezcMail)|array(string)
189 */
190 public function getAllMessages($type = 'raw') {
191 $msgs = array();
192
193 if ($this->_webtest) {
194 throw new Exception("Not implemented: getAllMessages for WebTest");
195 }
196 else {
197 $dao = CRM_Core_DAO::executeQuery('SELECT headers, body FROM civicrm_mailing_spool ORDER BY id');
198 while ($dao->fetch()) {
199 $msgs[] = $dao->headers . "\n\n" . $dao->body;
200 }
201 $dao->free();
202 }
203
204 switch ($type) {
205 case 'raw':
206 // nothing to do
207 break;
208
209 case 'ezc':
210 foreach ($msgs as $i => $msg) {
211 $msgs[$i] = $this->convertToEzc($msg);
212 }
213 break;
214 }
215
216 return $msgs;
217 }
218
219 /**
220 * @return int
221 */
222 public function getSelectedOutboundOption() {
223 $selectedOption = CRM_Mailing_Config::OUTBOUND_OPTION_MAIL;
224 // Is there a better way to do this? How do you get the currently selected value of a radio button in selenium?
225 for ($i = 0; $i <= 5; $i++) {
226 if ($i != CRM_Mailing_Config::OUTBOUND_OPTION_MOCK) {
227 if ($this->_ut->getValue('xpath=//input[@name="outBound_option" and @value="' . $i . '"]') == "on") {
228 $selectedOption = $i;
229 break;
230 }
231 }
232 }
233 return $selectedOption;
234 }
235
236 /*
237 * Utility functions (previously part of CiviUnitTestCase)
238 * Included for backward compatibility with existing tests.
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 */
252 public function checkMailLog($strings, $absentStrings = array(), $prefix = '') {
253 $mail = $this->getMostRecentEmail('raw');
254 return $this->checkMailForStrings($strings, $absentStrings, $prefix, $mail);
255 }
256
257 /**
258 * Check contents of mail log.
259 *
260 * @param array $strings
261 * Strings that should be included.
262 * @param array $absentStrings
263 * Strings that should not be included.
264 * @param string $prefix
265 *
266 * @return \ezcMail|string
267 */
268 public function checkAllMailLog($strings, $absentStrings = array(), $prefix = '') {
269 $mails = $this->getAllMessages('raw');
270 $mail = implode(',', $mails);
271 return $this->checkMailForStrings($strings, $absentStrings, $prefix, $mail);
272 }
273
274 /**
275 * Check that mail log is empty.
276 * @param string $prefix
277 */
278 public function assertMailLogEmpty($prefix = '') {
279 $mail = $this->getMostRecentEmail('raw');
280 $this->_ut->assertEmpty($mail, 'mail sent when it should not have been ' . $prefix);
281 }
282
283 /**
284 * Assert that $expectedRecipients (and no else) have received emails
285 *
286 * @param array $expectedRecipients
287 * Array($msgPos => array($recipPos => $emailAddr)).
288 */
289 public function assertRecipients($expectedRecipients) {
290 $recipients = array();
291 foreach ($this->getAllMessages('ezc') as $message) {
292 $recipients[] = CRM_Utils_Array::collect('email', $message->to);
293 }
294 $cmp = function($a, $b) {
295 if ($a[0] == $b[0]) {
296 return 0;
297 }
298 return ($a[0] < $b[0]) ? 1 : -1;
299 };
300 usort($recipients, $cmp);
301 usort($expectedRecipients, $cmp);
302 $this->_ut->assertEquals(
303 $expectedRecipients,
304 $recipients,
305 "Incorrect recipients: " . print_r(array('expected' => $expectedRecipients, 'actual' => $recipients), TRUE)
306 );
307 }
308
309 /**
310 * Assert that $expectedSubjects (and no other subjects) were sent.
311 *
312 * @param array $expectedSubjects
313 * Array(string $subj).
314 */
315 public function assertSubjects($expectedSubjects) {
316 $subjects = array();
317 foreach ($this->getAllMessages('ezc') as $message) {
318 /** @var ezcMail $message */
319 $subjects[] = $message->subject;
320 }
321 sort($subjects);
322 sort($expectedSubjects);
323 $this->_ut->assertEquals(
324 $expectedSubjects,
325 $subjects,
326 "Incorrect subjects: " . print_r(array('expected' => $expectedSubjects, 'actual' => $subjects), TRUE)
327 );
328 }
329
330 /**
331 * Remove any sent messages from the log.
332 *
333 * @param int $limit
334 * How many recent messages to remove, defaults to 0 (all).
335 *
336 * @throws \Exception
337 */
338 public function clearMessages($limit = 0) {
339 if ($this->_webtest) {
340 throw new Exception("Not implemented: clearMessages for WebTest");
341 }
342 else {
343 $sql = 'DELETE FROM civicrm_mailing_spool ORDER BY id DESC';
344 if ($limit) {
345 $sql .= ' LIMIT ' . $limit;
346 }
347 CRM_Core_DAO::executeQuery($sql);
348 }
349 }
350
351 /**
352 * @param string $msg
353 * Email header and body.
354 * @return ezcMail
355 */
356 private function convertToEzc($msg) {
357 $set = new ezcMailVariableSet($msg);
358 $parser = new ezcMailParser();
359 $mail = $parser->parseMail($set);
360 $this->_ut->assertNotEmpty($mail, 'Cannot parse mail');
361 return $mail[0];
362 }
363
364 /**
365 * @param $strings
366 * @param $absentStrings
367 * @param $prefix
368 * @param $mail
369 * @return mixed
370 */
371 public function checkMailForStrings($strings, $absentStrings, $prefix, $mail) {
372 foreach ($strings as $string) {
373 $this->_ut->assertContains($string, $mail, "$string . not found in $mail $prefix");
374 }
375 foreach ($absentStrings as $string) {
376 $this->_ut->assertEmpty(strstr($mail, $string), "$string incorrectly found in $mail $prefix");;
377 }
378 return $mail;
379 }
380
381 }