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