[PHPUNIT 8 ] Add void to more teardowns
[civicrm-core.git] / tests / phpunit / CRM / Mailing / MailingSystemTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Test that content produced by CiviMail looks the way it's expected.
14 *
15 * @package CiviCRM_APIv3
16 * @subpackage API_Job
17 *
18 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * @version $Id: Job.php 30879 2010-11-22 15:45:55Z shot $
20 *
21 */
22
23 /**
24 * Class CRM_Mailing_MailingSystemTest
25 *
26 * MailingSystemTest checks that overall composition and delivery of
27 * CiviMail blasts works. It extends CRM_Mailing_BaseMailingSystemTest
28 * which provides the general test scenarios -- but this variation
29 * checks that certain internal events/hooks fire.
30 *
31 * MailingSystemTest is the counterpart to FlexMailerSystemTest.
32 *
33 * @group headless
34 * @group civimail
35 * @see \Civi\FlexMailer\FlexMailerSystemTest
36 */
37 class CRM_Mailing_MailingSystemTest extends CRM_Mailing_BaseMailingSystemTest {
38
39 private $counts;
40
41 public function setUp() {
42 parent::setUp();
43 Civi::settings()->add(['experimentalFlexMailerEngine' => FALSE]);
44
45 $hooks = \CRM_Utils_Hook::singleton();
46 $hooks->setHook('civicrm_alterMailParams',
47 [$this, 'hook_alterMailParams']);
48 }
49
50 /**
51 * @see CRM_Utils_Hook::alterMailParams
52 */
53 public function hook_alterMailParams(&$params, $context = NULL) {
54 $this->counts['hook_alterMailParams'] = 1;
55 $this->assertEquals('civimail', $context);
56 }
57
58 public function tearDown(): void {
59 global $dbLocale;
60 if ($dbLocale) {
61 CRM_Core_I18n_Schema::makeSinglelingual('en_US');
62 }
63 parent::tearDown();
64 $this->assertNotEmpty($this->counts['hook_alterMailParams']);
65 }
66
67 // ---- Boilerplate ----
68
69 // The remainder of this class contains dummy stubs which make it easier to
70 // work with the tests in an IDE.
71
72 /**
73 * Generate a fully-formatted mailing (with body_html content).
74 *
75 * @dataProvider urlTrackingExamples
76 */
77 public function testUrlTracking(
78 $inputHtml,
79 $htmlUrlRegex,
80 $textUrlRegex,
81 $params
82 ) {
83 parent::testUrlTracking($inputHtml, $htmlUrlRegex, $textUrlRegex, $params);
84 }
85
86 public function testBasicHeaders() {
87 parent::testBasicHeaders();
88 }
89
90 public function testText() {
91 parent::testText();
92 }
93
94 public function testHtmlWithOpenTracking() {
95 parent::testHtmlWithOpenTracking();
96 }
97
98 public function testHtmlWithOpenAndUrlTracking() {
99 parent::testHtmlWithOpenAndUrlTracking();
100 }
101
102 /**
103 * Test to check Activity being created on mailing Job.
104 *
105 */
106 public function testMailingActivityCreate() {
107 $subject = uniqid('testMailingActivityCreate');
108 $this->runMailingSuccess([
109 'subject' => $subject,
110 'body_html' => 'Test Mailing Activity Create',
111 'scheduled_id' => $this->individualCreate(),
112 ]);
113
114 $this->callAPISuccessGetCount('activity', [
115 'activity_type_id' => 'Bulk Email',
116 'status_id' => 'Completed',
117 'subject' => $subject,
118 ], 1);
119 }
120
121 /**
122 * Data provider for testGitLabIssue1108
123 *
124 * First we run it without multiLingual mode, then with.
125 *
126 * This is because we test table names, which may have been translated in a
127 * multiLingual context.
128 *
129 */
130 public function multiLingual() {
131 return [[0], [1]];
132 }
133
134 /**
135 * - unsubscribe used dodgy SQL that only checked half of the polymorphic
136 * relationship in mailing_group, meaning it could match 'mailing 123'
137 * against _group_ 123.
138 *
139 * - also, an INNER JOIN on the group table hid the mailing-based
140 * mailing_group records.
141 *
142 * - in turn this inner join meant the query returned nothing, which then
143 * caused the code that is supposed to find the contact within those groups
144 * to basically find all the groups that the contact in or were smart groups.
145 *
146 * - in certain situations (which I have not been able to replicate in this
147 * test) it caused the unsubscribe to fail to find *any* groups to unsubscribe
148 * people from, thereby breaking the unsubscribe.
149 *
150 * @dataProvider multiLingual
151 *
152 */
153 public function testGitLabIssue1108($isMultiLingual) {
154
155 // We need to make sure the mailing IDs are higher than the groupIDs.
156 // We do this by adding mailings until the mailing.id value is at least 10
157 // higher than the highest group.id
158 // Note that creating a row in a transaction then rolling back the
159 // transaction still increments the AUTO_INCREMENT counter for the table.
160 // (If this behaviour ever changes we throw an exception.)
161 if ($isMultiLingual) {
162 $this->enableMultilingual();
163 CRM_Core_I18n_Schema::addLocale('fr_FR', 'en_US');
164 }
165 $max_group_id = CRM_Core_DAO::singleValueQuery("SELECT MAX(id) FROM civicrm_group");
166 $max_mailing_id = 0;
167 while ($max_mailing_id < $max_group_id + 10) {
168 CRM_Core_Transaction::create()->run(function($tx) use (&$max_mailing_id) {
169 CRM_Core_DAO::executeQuery("INSERT INTO civicrm_mailing (name) VALUES ('dummy');");
170 $_ = (int) CRM_Core_DAO::singleValueQuery("SELECT MAX(id) FROM civicrm_mailing");
171 if ($_ === $max_mailing_id) {
172 throw new RuntimeException("Expected that creating a new row would increment ID, but it did not. This could be a change in MySQL's implementation of rollback");
173 }
174 $max_mailing_id = $_;
175 $tx->rollback();
176 });
177 }
178
179 // Because our parent class marks the _groupID as private, we can't use that :-(
180 $group_1 = $this->groupCreate([
181 'name' => 'Test Group 1108.1',
182 'title' => 'Test Group 1108.1',
183 ]);
184 $this->createContactsInGroup(2, $group_1);
185
186 // Also _mut is private to the parent, so we have to make our own:
187 $mut = new CiviMailUtils($this, TRUE);
188
189 // Create initial mailing to the group.
190 $mailingParams = [
191 'name' => 'Issue 1108: mailing 1',
192 'subject' => 'Issue 1108: mailing 1',
193 'created_id' => 1,
194 'groups' => ['include' => [$group_1]],
195 'scheduled_date' => 'now',
196 'body_text' => 'Please just {action.unsubscribe}',
197 ];
198
199 // The following code is exactly the same as runMailingSuccess() except that we store the ID of the mailing.
200 $mailing_1 = $this->callAPISuccess('mailing', 'create', $mailingParams);
201 $mut->assertRecipients(array());
202 $this->callAPISuccess('job', 'process_mailing', array('runInNonProductionEnvironment' => TRUE));
203
204 $allMessages = $mut->getAllMessages('ezc');
205 // There are exactly two contacts produced by setUp().
206 $this->assertEquals(2, count($allMessages));
207
208 // We need a new group
209 $group_2 = $this->groupCreate([
210 'name' => 'Test Group 1108.2',
211 'title' => 'Test Group 1108.2',
212 ]);
213
214 // Now create the 2nd mailing to the recipients of the first,
215 // excluding our new albeit empty group.
216 $mailingParams = [
217 'name' => 'Issue 1108: mailing 2',
218 'subject' => 'Issue 1108: mailing 2',
219 'created_id' => 1,
220 'mailings' => ['include' => [$mailing_1['id']]],
221 'groups' => ['exclude' => [$group_2]],
222 'scheduled_date' => 'now',
223 'body_text' => 'Please just {action.unsubscribeUrl}',
224 ];
225 $this->callAPISuccess('mailing', 'create', $mailingParams);
226 $_ = $this->callAPISuccess('job', 'process_mailing', array('runInNonProductionEnvironment' => TRUE));
227
228 $allMessages = $mut->getAllMessages('ezc');
229 // We should have 2+2 messages sent by the mail system now.
230 $this->assertEquals(4, count($allMessages));
231
232 // So far so good.
233 // Now extract the unsubscribe details.
234 $message = end($allMessages);
235 $this->assertTrue($message->body instanceof ezcMailText);
236 $this->assertEquals('plain', $message->body->subType);
237 $this->assertEquals(1, preg_match(
238 '@mailing/unsubscribe.*jid=(\d+)&qid=(\d+)&h=([0-9a-z]+)@',
239 $message->body->text,
240 $matches
241 ));
242
243 // Create a group that has nothing to do with this mailing.
244 $group_3 = $this->groupCreate([
245 'name' => 'Test Group 1108.3',
246 'title' => 'Test Group 1108.3',
247 ]);
248 // Add contacts from group 1 to group 3.
249 $gcQuery = new CRM_Contact_BAO_GroupContact();
250 $gcQuery->group_id = $group_1;
251 $gcQuery->status = 'Added';
252 $gcQuery->find();
253 while ($gcQuery->fetch()) {
254 $this->callAPISuccess('group_contact', 'create',
255 ['group_id' => $group_3, 'contact_id' => $gcQuery->contact_id, 'status' => 'Added']);
256 }
257
258 // Part of the issue is caused by the fact that (at time of writing) the
259 // SQL joined the mailing_group table on just the entity_id, assuming it to
260 // be a group, but actually it could be a mailing.
261 // The difficulty in testing this is that because all our IDs are very low
262 // and contiguous the SQL looking for a match for 'mailing 1' does match a
263 // group ID of '1', which is created in this class's parent's setUp().
264 // Strictly speaking we don't know that it has ID 1, but as we can't access _groupID
265 // we'll have to assume that.
266 //
267 // So by deleting that group the SQL then matches nothing which is what we
268 // need for this case.
269 $_ = new CRM_Contact_BAO_Group();
270 $_->id = 1;
271 $_->delete();
272
273 $hooks = \CRM_Utils_Hook::singleton();
274 $found = [];
275 $hooks->setHook('civicrm_unsubscribeGroups',
276 function ($op, $mailingId, $contactId, &$groups, &$baseGroups) use (&$found) {
277 $found['groups'] = $groups;
278 $found['baseGroups'] = $baseGroups;
279 });
280
281 // Now test unsubscribe groups.
282 $groups = CRM_Mailing_Event_BAO_Unsubscribe::unsub_from_mailing(
283 $matches[1],
284 $matches[2],
285 $matches[3],
286 TRUE
287 );
288
289 // We expect that our group_1 was found.
290 $this->assertEquals(['groups' => [$group_1], 'baseGroups' => []], $found);
291
292 // We *should* get an array with just our $group_1 since this is the only group
293 // that we have included.
294 // $group_2 was only used to exclude people.
295 // $group_3 has nothing to do with this mailing and should not be there.
296 $this->assertNotEmpty($groups, "We should have received an array.");
297 $this->assertEquals([$group_1], array_keys($groups),
298 "We should have received an array with our group 1 in it.");
299
300 if ($isMultiLingual) {
301 global $dbLocale;
302 $dbLocale = '_fr_FR';
303 // Now test unsubscribe groups.
304 $groups = CRM_Mailing_Event_BAO_Unsubscribe::unsub_from_mailing(
305 $matches[1],
306 $matches[2],
307 $matches[3],
308 TRUE
309 );
310
311 // We expect that our group_1 was found.
312 $this->assertEquals(['groups' => [$group_1], 'baseGroups' => []], $found);
313
314 // We *should* get an array with just our $group_1 since this is the only group
315 // that we have included.
316 // $group_2 was only used to exclude people.
317 // $group_3 has nothing to do with this mailing and should not be there.
318 $this->assertNotEmpty($groups, "We should have received an array.");
319 $this->assertEquals([$group_1], array_keys($groups),
320 "We should have received an array with our group 1 in it.");
321 global $dbLocale;
322 $dbLocale = '_en_US';
323 }
324 }
325
326 }