Commit | Line | Data |
---|---|---|
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 | 11 | |
6a488035 TO |
12 | /** |
13 | * Test class for CRM_Contact_BAO_GroupContact BAO | |
14 | * | |
6c6e6187 | 15 | * @package CiviCRM |
acb109b7 | 16 | * @group headless |
6a488035 TO |
17 | */ |
18 | class CRM_Contact_BAO_GroupContactCacheTest extends CiviUnitTestCase { | |
19 | ||
20 | /** | |
eceb18cc | 21 | * Manually add and remove contacts from a smart group. |
4f39b1e6 EM |
22 | * |
23 | * @throws \API_Exception | |
24 | * @throws \CRM_Core_Exception | |
25 | * @throws \CiviCRM_API3_Exception | |
6a488035 | 26 | */ |
4f39b1e6 EM |
27 | public function testManualAddRemove(): void { |
28 | [$group, $living, $deceased] = $this->setupSmartGroup(); | |
6a488035 TO |
29 | |
30 | // Add $n1 to $g | |
4f39b1e6 | 31 | $this->callAPISuccess('GroupContact', 'create', [ |
6a488035 TO |
32 | 'contact_id' => $living[0]->id, |
33 | 'group_id' => $group->id, | |
9099cab3 | 34 | ]); |
2828c36d | 35 | |
4f39b1e6 | 36 | CRM_Contact_BAO_GroupContactCache::load($group); |
6a488035 | 37 | $this->assertCacheMatches( |
9099cab3 | 38 | [$deceased[0]->id, $deceased[1]->id, $deceased[2]->id, $living[0]->id], |
6a488035 TO |
39 | $group->id |
40 | ); | |
41 | ||
42 | // Remove $y1 from $g | |
9099cab3 | 43 | $this->callAPISuccess('group_contact', 'create', [ |
6a488035 TO |
44 | 'contact_id' => $deceased[0]->id, |
45 | 'group_id' => $group->id, | |
46 | 'status' => 'Removed', | |
9099cab3 | 47 | ]); |
2828c36d | 48 | |
4f39b1e6 | 49 | CRM_Contact_BAO_GroupContactCache::load($group); |
6a488035 | 50 | $this->assertCacheMatches( |
9099cab3 | 51 | [ |
92915c55 TO |
52 | $deceased[1]->id, |
53 | $deceased[2]->id, | |
28a04ea9 | 54 | $living[0]->id, |
9099cab3 | 55 | ], |
6a488035 TO |
56 | $group->id |
57 | ); | |
58 | } | |
b6708aeb | 59 | |
6a488035 | 60 | /** |
4f39b1e6 EM |
61 | * Allow removing contact from a parent group even if contact is in a child |
62 | * group. (CRM-8858). | |
63 | * | |
64 | * @throws \CRM_Core_Exception | |
6a488035 | 65 | */ |
4f39b1e6 EM |
66 | public function testRemoveFromParentSmartGroup(): void { |
67 | // Create $c1, $c2, $c3 | |
68 | $deceased = $this->createTestObject('CRM_Contact_DAO_Contact', ['is_deceased' => 1], 3); | |
69 | ||
6a488035 | 70 | // Create smart group $parent |
9099cab3 | 71 | $params = [ |
6a488035 TO |
72 | 'name' => 'Deceased Contacts', |
73 | 'title' => 'Deceased Contacts', | |
74 | 'is_active' => 1, | |
9099cab3 CW |
75 | 'formValues' => ['is_deceased' => 1], |
76 | ]; | |
6a488035 | 77 | $parent = CRM_Contact_BAO_Group::createSmartGroup($params); |
9099cab3 | 78 | $this->registerTestObjects([$parent]); |
6a488035 TO |
79 | |
80 | // Create group $child in $parent | |
9099cab3 | 81 | $params = [ |
6a488035 TO |
82 | 'name' => 'Child Group', |
83 | 'title' => 'Child Group', | |
84 | 'is_active' => 1, | |
9099cab3 CW |
85 | 'parents' => [$parent->id => 1], |
86 | ]; | |
6a488035 | 87 | $child = CRM_Contact_BAO_Group::create($params); |
9099cab3 | 88 | $this->registerTestObjects([$child]); |
6a488035 | 89 | |
6a488035 TO |
90 | // Add $c1, $c2, $c3 to $child |
91 | foreach ($deceased as $contact) { | |
9099cab3 | 92 | $this->callAPISuccess('group_contact', 'create', [ |
6a488035 TO |
93 | 'contact_id' => $contact->id, |
94 | 'group_id' => $child->id, | |
9099cab3 | 95 | ]); |
6a488035 TO |
96 | } |
97 | ||
4f39b1e6 | 98 | CRM_Contact_BAO_GroupContactCache::load($parent); |
6a488035 | 99 | $this->assertCacheMatches( |
9099cab3 | 100 | [$deceased[0]->id, $deceased[1]->id, $deceased[2]->id], |
6a488035 TO |
101 | $parent->id |
102 | ); | |
b6708aeb | 103 | |
6a488035 | 104 | // Remove $c1 from $parent |
4f39b1e6 | 105 | $this->callAPISuccess('GroupContact', 'create', [ |
6a488035 TO |
106 | 'contact_id' => $deceased[0]->id, |
107 | 'group_id' => $parent->id, | |
108 | 'status' => 'Removed', | |
9099cab3 | 109 | ]); |
b6708aeb | 110 | |
6a488035 | 111 | // Assert $c1 not in $parent |
4f39b1e6 | 112 | CRM_Contact_BAO_GroupContactCache::load($parent); |
6a488035 | 113 | $this->assertCacheMatches( |
9099cab3 | 114 | [ |
92915c55 | 115 | $deceased[1]->id, |
28a04ea9 | 116 | $deceased[2]->id, |
9099cab3 | 117 | ], |
6a488035 TO |
118 | $parent->id |
119 | ); | |
b6708aeb | 120 | |
6a488035 | 121 | // Assert $c1 still in $child |
b6708aeb | 122 | $this->assertDBQuery(1, |
6a488035 | 123 | 'select count(*) from civicrm_group_contact where group_id=%1 and contact_id=%2 and status=%3', |
9099cab3 CW |
124 | [ |
125 | 1 => [$child->id, 'Integer'], | |
126 | 2 => [$deceased[0]->id, 'Integer'], | |
127 | 3 => ['Added', 'String'], | |
128 | ] | |
6a488035 TO |
129 | ); |
130 | } | |
131 | ||
132 | /** | |
eceb18cc | 133 | * Assert that the cache for a group contains exactly the listed contacts. |
6a488035 | 134 | * |
5a4f6742 | 135 | * @param array $expectedContactIds |
e16033b4 | 136 | * Array(int). |
5a4f6742 | 137 | * @param int $groupId |
6a488035 | 138 | */ |
4f39b1e6 | 139 | public function assertCacheMatches($expectedContactIds, $groupId): void { |
6a488035 | 140 | $sql = 'SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id = %1'; |
9099cab3 | 141 | $params = [1 => [$groupId, 'Integer']]; |
6a488035 | 142 | $dao = CRM_Core_DAO::executeQuery($sql, $params); |
9099cab3 | 143 | $actualContactIds = []; |
6a488035 TO |
144 | while ($dao->fetch()) { |
145 | $actualContactIds[] = $dao->contact_id; | |
146 | } | |
147 | ||
148 | sort($expectedContactIds); | |
149 | sort($actualContactIds); | |
150 | $this->assertEquals($expectedContactIds, $actualContactIds); | |
151 | } | |
152 | ||
0a0e1aab | 153 | /** |
154 | * Test the opportunistic refresh cache function does not touch non-expired entries. | |
155 | */ | |
156 | public function testOpportunisticRefreshCacheNoChangeIfNotExpired() { | |
4f39b1e6 | 157 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
9099cab3 | 158 | $this->callAPISuccess('Contact', 'create', ['id' => $deceased[0]->id, 'is_deceased' => 0]); |
0a0e1aab | 159 | $this->assertCacheMatches( |
9099cab3 | 160 | [$deceased[0]->id, $deceased[1]->id, $deceased[2]->id], |
0a0e1aab | 161 | $group->id |
162 | ); | |
2b68a50c | 163 | CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); |
0a0e1aab | 164 | |
2a4fb809 | 165 | $this->assertCacheNotRefreshed($deceased, $group); |
0a0e1aab | 166 | } |
167 | ||
168 | /** | |
2a4fb809 | 169 | * Test the opportunistic refresh cache function does refresh stale entries. |
0a0e1aab | 170 | */ |
171 | public function testOpportunisticRefreshChangeIfCacheDateFieldStale() { | |
4f39b1e6 | 172 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
9099cab3 | 173 | $this->callAPISuccess('Contact', 'create', ['id' => $deceased[0]->id, 'is_deceased' => 0]); |
0a0e1aab | 174 | CRM_Core_DAO::executeQuery('UPDATE civicrm_group SET cache_date = DATE_SUB(NOW(), INTERVAL 7 MINUTE) WHERE id = ' . $group->id); |
2a4fb809 | 175 | $group->find(TRUE); |
0a0e1aab | 176 | Civi::$statics['CRM_Contact_BAO_GroupContactCache']['is_refresh_init'] = FALSE; |
177 | sleep(1); | |
2b68a50c | 178 | CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); |
0a0e1aab | 179 | |
2a4fb809 | 180 | $this->assertCacheRefreshed($group); |
181 | } | |
0a0e1aab | 182 | |
2a4fb809 | 183 | /** |
184 | * Test the opportunistic refresh cache function does refresh expired entries if mode is deterministic. | |
185 | */ | |
186 | public function testOpportunisticRefreshNoChangeWithDeterministicSetting() { | |
4f39b1e6 | 187 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
9099cab3 CW |
188 | $this->callAPISuccess('Setting', 'create', ['smart_group_cache_refresh_mode' => 'deterministic']); |
189 | $this->callAPISuccess('Contact', 'create', ['id' => $deceased[0]->id, 'is_deceased' => 0]); | |
2a4fb809 | 190 | $this->makeCacheStale($group); |
2b68a50c | 191 | CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); |
2a4fb809 | 192 | $this->assertCacheNotRefreshed($deceased, $group); |
9099cab3 | 193 | $this->callAPISuccess('Setting', 'create', ['smart_group_cache_refresh_mode' => 'opportunistic']); |
2a4fb809 | 194 | } |
195 | ||
196 | /** | |
197 | * Test the deterministic cache function refreshes with the deterministic setting. | |
198 | */ | |
199 | public function testDeterministicRefreshChangeWithDeterministicSetting() { | |
4f39b1e6 | 200 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
9099cab3 CW |
201 | $this->callAPISuccess('Setting', 'create', ['smart_group_cache_refresh_mode' => 'deterministic']); |
202 | $this->callAPISuccess('Contact', 'create', ['id' => $deceased[0]->id, 'is_deceased' => 0]); | |
2a4fb809 | 203 | $this->makeCacheStale($group); |
2b68a50c | 204 | CRM_Contact_BAO_GroupContactCache::deterministicCacheFlush(); |
2a4fb809 | 205 | $this->assertCacheRefreshed($group); |
9099cab3 | 206 | $this->callAPISuccess('Setting', 'create', ['smart_group_cache_refresh_mode' => 'opportunistic']); |
2a4fb809 | 207 | } |
208 | ||
209 | /** | |
210 | * Test the deterministic cache function refresh doesn't mess up non-expired. | |
211 | */ | |
212 | public function testDeterministicRefreshChangeDoesNotTouchNonExpired() { | |
4f39b1e6 | 213 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
9099cab3 CW |
214 | $this->callAPISuccess('Setting', 'create', ['smart_group_cache_refresh_mode' => 'deterministic']); |
215 | $this->callAPISuccess('Contact', 'create', ['id' => $deceased[0]->id, 'is_deceased' => 0]); | |
2b68a50c | 216 | CRM_Contact_BAO_GroupContactCache::deterministicCacheFlush(); |
2a4fb809 | 217 | $this->assertCacheNotRefreshed($deceased, $group); |
9099cab3 | 218 | $this->callAPISuccess('Setting', 'create', ['smart_group_cache_refresh_mode' => 'opportunistic']); |
2a4fb809 | 219 | } |
220 | ||
221 | /** | |
222 | * Test the deterministic cache function refreshes with the opportunistic setting. | |
223 | * | |
224 | * (hey it's an opportunity!). | |
225 | */ | |
226 | public function testDeterministicRefreshChangeWithOpportunisticSetting() { | |
4f39b1e6 | 227 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
9099cab3 CW |
228 | $this->callAPISuccess('Setting', 'create', ['smart_group_cache_refresh_mode' => 'opportunistic']); |
229 | $this->callAPISuccess('Contact', 'create', ['id' => $deceased[0]->id, 'is_deceased' => 0]); | |
2a4fb809 | 230 | $this->makeCacheStale($group); |
2b68a50c | 231 | CRM_Contact_BAO_GroupContactCache::deterministicCacheFlush(); |
2a4fb809 | 232 | $this->assertCacheRefreshed($group); |
233 | } | |
234 | ||
235 | /** | |
236 | * Test the api job wrapper around the deterministic refresh works. | |
237 | */ | |
238 | public function testJobWrapper() { | |
4f39b1e6 | 239 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
9099cab3 CW |
240 | $this->callAPISuccess('Setting', 'create', ['smart_group_cache_refresh_mode' => 'opportunistic']); |
241 | $this->callAPISuccess('Contact', 'create', ['id' => $deceased[0]->id, 'is_deceased' => 0]); | |
2a4fb809 | 242 | $this->makeCacheStale($group); |
9099cab3 | 243 | $this->callAPISuccess('Job', 'group_cache_flush', []); |
2a4fb809 | 244 | $this->assertCacheRefreshed($group); |
0a0e1aab | 245 | } |
246 | ||
6a488035 TO |
247 | // *** Everything below this should be moved to parent class **** |
248 | ||
249 | /** | |
1d3260ea SL |
250 | * @var array |
251 | * (DAO_Name => array(int)) List of items to garbage-collect during tearDown | |
6a488035 TO |
252 | */ |
253 | private $_testObjects; | |
254 | ||
6a488035 TO |
255 | /** |
256 | * Tears down the fixture, for example, closes a network connection. | |
2828c36d | 257 | * |
6a488035 | 258 | * This method is called after a test is executed. |
6a488035 | 259 | */ |
594a9328 | 260 | protected function tearDown(): void { |
6a488035 | 261 | parent::tearDown(); |
eb68c129 | 262 | $this->deleteTestObjects(); |
6a488035 TO |
263 | } |
264 | ||
265 | /** | |
2828c36d | 266 | * This is a wrapper for CRM_Core_DAO::createTestObject which tracks created entities. |
6a488035 TO |
267 | * |
268 | * @see CRM_Core_DAO::createTestObject | |
2828c36d | 269 | * |
270 | * @param string $daoName | |
1e1fdcf6 EM |
271 | * @param array $params |
272 | * @param int $numObjects | |
273 | * @param bool $createOnly | |
2828c36d | 274 | * |
275 | * @return array|NULL|object | |
6a488035 | 276 | */ |
9099cab3 | 277 | public function createTestObject($daoName, $params = [], $numObjects = 1, $createOnly = FALSE) { |
6a488035 TO |
278 | $objects = CRM_Core_DAO::createTestObject($daoName, $params, $numObjects, $createOnly); |
279 | if (is_array($objects)) { | |
280 | $this->registerTestObjects($objects); | |
0db6c3e1 TO |
281 | } |
282 | else { | |
9099cab3 | 283 | $this->registerTestObjects([$objects]); |
6a488035 TO |
284 | } |
285 | return $objects; | |
286 | } | |
287 | ||
288 | /** | |
2828c36d | 289 | * Register test objects. |
290 | * | |
5a4f6742 CW |
291 | * @param array $objects |
292 | * DAO or BAO objects. | |
6a488035 | 293 | */ |
00be9182 | 294 | public function registerTestObjects($objects) { |
6a488035 TO |
295 | foreach ($objects as $object) { |
296 | $daoName = preg_replace('/_BAO_/', '_DAO_', get_class($object)); | |
297 | $this->_testObjects[$daoName][] = $object->id; | |
298 | } | |
299 | } | |
300 | ||
2828c36d | 301 | /** |
302 | * Delete test objects. | |
303 | * | |
304 | * Note: You might argue that the FK relations between test | |
305 | * objects could make this problematic; however, it should | |
306 | * behave intuitively as long as we mentally split our | |
307 | * test-objects between the "manual/primary records" | |
308 | * and the "automatic/secondary records" | |
309 | */ | |
00be9182 | 310 | public function deleteTestObjects() { |
6a488035 TO |
311 | foreach ($this->_testObjects as $daoName => $daoIds) { |
312 | foreach ($daoIds as $daoId) { | |
9099cab3 | 313 | CRM_Core_DAO::deleteTestObjects($daoName, ['id' => $daoId]); |
6a488035 TO |
314 | } |
315 | } | |
9099cab3 | 316 | $this->_testObjects = []; |
6a488035 TO |
317 | } |
318 | ||
0a0e1aab | 319 | /** |
320 | * Set up a smart group testing scenario. | |
321 | * | |
322 | * @return array | |
323 | */ | |
4f39b1e6 EM |
324 | protected function setupSmartGroup(): array { |
325 | // Create contacts $y1, $y2, $y3 which do match $g; create $n1, $n2, $n3 which do not match $g | |
326 | $living = $this->createTestObject('CRM_Contact_DAO_Contact', ['is_deceased' => 0], 3); | |
327 | $deceased = $this->createTestObject('CRM_Contact_DAO_Contact', ['is_deceased' => 1], 3); | |
328 | $this->assertCount(3, $deceased); | |
329 | $this->assertCount(3, $living); | |
330 | ||
9099cab3 | 331 | $params = [ |
0a0e1aab | 332 | 'name' => 'Deceased Contacts', |
333 | 'title' => 'Deceased Contacts', | |
334 | 'is_active' => 1, | |
9099cab3 CW |
335 | 'formValues' => ['is_deceased' => 1], |
336 | ]; | |
0a0e1aab | 337 | $group = CRM_Contact_BAO_Group::createSmartGroup($params); |
9099cab3 | 338 | $this->registerTestObjects([$group]); |
0a0e1aab | 339 | |
0a0e1aab | 340 | // Assert: $g cache has exactly $y1, $y2, $y3 |
4f39b1e6 | 341 | CRM_Contact_BAO_GroupContactCache::load($group); |
0a0e1aab | 342 | $group->find(TRUE); |
343 | $this->assertCacheMatches( | |
9099cab3 | 344 | [$deceased[0]->id, $deceased[1]->id, $deceased[2]->id], |
0a0e1aab | 345 | $group->id |
346 | ); | |
347 | // Reload the group so we have the cache_date & refresh_date. | |
9099cab3 | 348 | return [$group, $living, $deceased]; |
0a0e1aab | 349 | } |
350 | ||
2a4fb809 | 351 | /** |
352 | * @param $deceased | |
353 | * @param $group | |
354 | * | |
355 | * @throws \Exception | |
356 | */ | |
357 | protected function assertCacheNotRefreshed($deceased, $group) { | |
358 | $this->assertCacheMatches( | |
9099cab3 | 359 | [$deceased[0]->id, $deceased[1]->id, $deceased[2]->id], |
2a4fb809 | 360 | $group->id |
361 | ); | |
9099cab3 | 362 | $afterGroup = $this->callAPISuccessGetSingle('Group', ['id' => $group->id]); |
2a4fb809 | 363 | $this->assertEquals($group->cache_date, $afterGroup['cache_date']); |
364 | } | |
365 | ||
366 | /** | |
367 | * Make the cache for the group stale, resetting it to before the timeout period. | |
368 | * | |
369 | * @param CRM_Contact_BAO_Group $group | |
370 | */ | |
371 | protected function makeCacheStale(&$group) { | |
372 | CRM_Core_DAO::executeQuery('UPDATE civicrm_group SET cache_date = DATE_SUB(NOW(), INTERVAL 7 MINUTE) WHERE id = ' . $group->id); | |
373 | unset($group->cache_date); | |
374 | $group->find(TRUE); | |
375 | Civi::$statics['CRM_Contact_BAO_GroupContactCache']['is_refresh_init'] = FALSE; | |
376 | } | |
377 | ||
378 | /** | |
379 | * @param $group | |
380 | * | |
381 | * @throws \Exception | |
382 | */ | |
383 | protected function assertCacheRefreshed($group) { | |
384 | $this->assertCacheMatches( | |
9099cab3 | 385 | [], |
2a4fb809 | 386 | $group->id |
387 | ); | |
388 | ||
9099cab3 | 389 | $afterGroup = $this->callAPISuccessGetSingle('Group', ['id' => $group->id]); |
826096b0 | 390 | $this->assertTrue(empty($afterGroup['cache_date']), 'cache date should not be set as the cache is not built'); |
2a4fb809 | 391 | } |
392 | ||
6451118c JP |
393 | /** |
394 | * Test Smart group search | |
395 | */ | |
396 | public function testSmartGroupSearchBuilder() { | |
9099cab3 | 397 | $returnProperties = [ |
6451118c JP |
398 | 'contact_type' => 1, |
399 | 'contact_sub_type' => 1, | |
400 | 'sort_name' => 1, | |
401 | 'group' => 1, | |
9099cab3 | 402 | ]; |
4f39b1e6 | 403 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
6451118c | 404 | |
9099cab3 | 405 | $params = [ |
6451118c JP |
406 | 'name' => 'Living Contacts', |
407 | 'title' => 'Living Contacts', | |
408 | 'is_active' => 1, | |
9099cab3 CW |
409 | 'formValues' => ['is_deceased' => 0], |
410 | ]; | |
6451118c JP |
411 | $group2 = CRM_Contact_BAO_Group::createSmartGroup($params); |
412 | ||
413 | //Filter on smart group with =, !=, IN and NOT IN operator. | |
9099cab3 | 414 | $params = [['group', '=', $group2->id, 1, 0]]; |
6451118c JP |
415 | $query = new CRM_Contact_BAO_Query( |
416 | $params, $returnProperties, | |
417 | NULL, FALSE, FALSE, CRM_Contact_BAO_Query::MODE_CONTACTS, | |
418 | FALSE, | |
419 | FALSE, FALSE | |
420 | ); | |
9f6a1556 JP |
421 | $ids = $query->searchQuery(0, 0, NULL, |
422 | FALSE, FALSE, FALSE, | |
423 | TRUE, FALSE | |
424 | ); | |
e24f9388 | 425 | $key = $query->getGroupCacheTableKeys()[0]; |
ed17b5e2 | 426 | $expectedWhere = "civicrm_group_contact_cache_{$key}.group_id IN (\"{$group2->id}\")"; |
6451118c | 427 | $this->assertContains($expectedWhere, $query->_whereClause); |
9f6a1556 | 428 | $this->_assertContactIds($query, "group_id = {$group2->id}"); |
6451118c | 429 | |
9099cab3 | 430 | $params = [['group', '!=', $group->id, 1, 0]]; |
6451118c JP |
431 | $query = new CRM_Contact_BAO_Query( |
432 | $params, $returnProperties, | |
433 | NULL, FALSE, FALSE, CRM_Contact_BAO_Query::MODE_CONTACTS, | |
434 | FALSE, | |
435 | FALSE, FALSE | |
436 | ); | |
e24f9388 | 437 | $key = $query->getGroupCacheTableKeys()[0]; |
6451118c | 438 | //Assert if proper where clause is present. |
ed17b5e2 | 439 | $expectedWhere = "civicrm_group_contact_{$key}.group_id != {$group->id} AND civicrm_group_contact_cache_{$key}.group_id IS NULL OR ( civicrm_group_contact_cache_{$key}.contact_id NOT IN (SELECT contact_id FROM civicrm_group_contact_cache cgcc WHERE cgcc.group_id IN ( {$group->id} ) ) )"; |
6451118c | 440 | $this->assertContains($expectedWhere, $query->_whereClause); |
9f6a1556 | 441 | $this->_assertContactIds($query, "group_id != {$group->id}"); |
6451118c | 442 | |
9099cab3 | 443 | $params = [['group', 'IN', [$group->id, $group2->id], 1, 0]]; |
6451118c JP |
444 | $query = new CRM_Contact_BAO_Query( |
445 | $params, $returnProperties, | |
446 | NULL, FALSE, FALSE, CRM_Contact_BAO_Query::MODE_CONTACTS, | |
447 | FALSE, | |
448 | FALSE, FALSE | |
449 | ); | |
e24f9388 | 450 | $key = $query->getGroupCacheTableKeys()[0]; |
ed17b5e2 | 451 | $expectedWhere = "civicrm_group_contact_cache_{$key}.group_id IN (\"{$group->id}\", \"{$group2->id}\")"; |
6451118c | 452 | $this->assertContains($expectedWhere, $query->_whereClause); |
9f6a1556 | 453 | $this->_assertContactIds($query, "group_id IN ({$group->id}, {$group2->id})"); |
6451118c | 454 | |
9099cab3 | 455 | $params = [['group', 'NOT IN', [$group->id], 1, 0]]; |
6451118c JP |
456 | $query = new CRM_Contact_BAO_Query( |
457 | $params, $returnProperties, | |
458 | NULL, FALSE, FALSE, CRM_Contact_BAO_Query::MODE_CONTACTS, | |
459 | FALSE, | |
460 | FALSE, FALSE | |
461 | ); | |
e24f9388 | 462 | $key = $query->getGroupCacheTableKeys()[0]; |
ed17b5e2 | 463 | $expectedWhere = "civicrm_group_contact_{$key}.group_id NOT IN ( {$group->id} ) AND civicrm_group_contact_cache_{$key}.group_id IS NULL OR ( civicrm_group_contact_cache_{$key}.contact_id NOT IN (SELECT contact_id FROM civicrm_group_contact_cache cgcc WHERE cgcc.group_id IN ( {$group->id} ) ) )"; |
6451118c | 464 | $this->assertContains($expectedWhere, $query->_whereClause); |
9f6a1556 | 465 | $this->_assertContactIds($query, "group_id NOT IN ({$group->id})"); |
e24f9388 SL |
466 | $this->callAPISuccess('group', 'delete', ['id' => $group->id]); |
467 | $this->callAPISuccess('group', 'delete', ['id' => $group2->id]); | |
468 | } | |
469 | ||
e24f9388 | 470 | public function testMultipleGroupWhereClause() { |
9099cab3 | 471 | $returnProperties = [ |
e24f9388 SL |
472 | 'contact_type' => 1, |
473 | 'contact_sub_type' => 1, | |
474 | 'sort_name' => 1, | |
475 | 'group' => 1, | |
9099cab3 | 476 | ]; |
4f39b1e6 | 477 | [$group, $living, $deceased] = $this->setupSmartGroup(); |
e24f9388 | 478 | |
9099cab3 | 479 | $params = [ |
e24f9388 SL |
480 | 'name' => 'Living Contacts', |
481 | 'title' => 'Living Contacts', | |
482 | 'is_active' => 1, | |
9099cab3 CW |
483 | 'formValues' => ['is_deceased' => 0], |
484 | ]; | |
e24f9388 SL |
485 | $group2 = CRM_Contact_BAO_Group::createSmartGroup($params); |
486 | ||
487 | //Filter on smart group with =, !=, IN and NOT IN operator. | |
9099cab3 | 488 | $params = [['group', '=', $group2->id, 1, 0], ['group', '=', $group->id, 1, 0]]; |
e24f9388 SL |
489 | $query = new CRM_Contact_BAO_Query( |
490 | $params, $returnProperties, | |
491 | NULL, FALSE, FALSE, CRM_Contact_BAO_Query::MODE_CONTACTS, | |
492 | FALSE, | |
493 | FALSE, FALSE | |
494 | ); | |
495 | $ids = $query->searchQuery(0, 0, NULL, | |
496 | FALSE, FALSE, FALSE, | |
497 | TRUE, FALSE | |
498 | ); | |
499 | $key1 = $query->getGroupCacheTableKeys()[0]; | |
500 | $key2 = $query->getGroupCacheTableKeys()[1]; | |
501 | $expectedWhere = 'civicrm_group_contact_cache_' . $key1 . '.group_id IN ("' . $group2->id . '") ) ) AND ( ( civicrm_group_contact_cache_' . $key2 . '.group_id IN ("' . $group->id . '")'; | |
502 | $this->assertContains($expectedWhere, $query->_whereClause); | |
503 | // Check that we have 3 joins to the group contact cache 1 for each of the group where clauses and 1 for the fact we are returning groups in the select. | |
504 | $expectedFrom1 = 'LEFT JOIN civicrm_group_contact_cache civicrm_group_contact_cache_' . $key1 . ' ON contact_a.id = civicrm_group_contact_cache_' . $key1 . '.contact_id'; | |
505 | $this->assertContains($expectedFrom1, $query->_fromClause); | |
506 | $expectedFrom2 = 'LEFT JOIN civicrm_group_contact_cache civicrm_group_contact_cache_' . $key2 . ' ON contact_a.id = civicrm_group_contact_cache_' . $key2 . '.contact_id'; | |
507 | $this->assertContains($expectedFrom2, $query->_fromClause); | |
508 | $expectedFrom3 = 'LEFT JOIN civicrm_group_contact_cache ON contact_a.id = civicrm_group_contact_cache.contact_id'; | |
509 | $this->assertContains($expectedFrom3, $query->_fromClause); | |
9f6a1556 JP |
510 | } |
511 | ||
512 | /** | |
513 | * Check if contact ids are fetched correctly. | |
514 | * | |
515 | * @param object $query | |
516 | * @param string $groupWhereClause | |
517 | */ | |
518 | public function _assertContactIds($query, $groupWhereClause) { | |
519 | $contactIds = explode(',', $query->searchQuery(0, 0, NULL, | |
520 | FALSE, FALSE, FALSE, | |
521 | TRUE, FALSE | |
522 | )); | |
9099cab3 | 523 | $expectedContactIds = []; |
9f6a1556 JP |
524 | $groupDAO = CRM_Core_DAO::executeQuery("SELECT contact_id FROM civicrm_group_contact_cache WHERE {$groupWhereClause}"); |
525 | while ($groupDAO->fetch()) { | |
526 | $expectedContactIds[] = $groupDAO->contact_id; | |
527 | } | |
528 | $this->assertEquals(sort($expectedContactIds), sort($contactIds)); | |
6451118c JP |
529 | } |
530 | ||
6a488035 | 531 | } |