self::invalidateGroupContactCache($group->id);
}
- $locks = self::getLocksForRefreshableGroupsTo([$groupID]);
- foreach ($locks as $groupID => $lock) {
+ $lockedGroups = self::getLocksForRefreshableGroupsTo([$groupID]);
+ foreach ($lockedGroups as $groupID) {
$groupContactsTempTable = CRM_Utils_SQL_TempTable::build()
->setCategory('gccache')
->setMemory();
self::buildGroupContactTempTable([$groupID], $groupContactsTempTable);
self::updateCacheFromTempTable($groupContactsTempTable, [$groupID]);
- $lock->release();
+ self::releaseGroupLocks([$groupID]);
}
}
$locks = [];
$groupIDs = self::getGroupsNeedingRefreshing($groupIDs);
foreach ($groupIDs as $groupID) {
- $lock = Civi::lockManager()->acquire("data.core.group.{$groupID}");
- if ($lock->isAcquired()) {
- $locks[$groupID] = $lock;
+ if (self::getGroupLock($groupID)) {
+ $locks[] = $groupID;
}
}
return $locks;
$groupContactsTempTable = CRM_Utils_SQL_TempTable::build()
->setCategory('gccache')
->setMemory();
- $locks = self::getLocksForRefreshableGroupsTo($smartGroups);
- if (!empty($locks)) {
- self::buildGroupContactTempTable(array_keys($locks), $groupContactsTempTable);
+ $lockedGroups = self::getLocksForRefreshableGroupsTo($smartGroups);
+ if (!empty($lockedGroups)) {
+ self::buildGroupContactTempTable($lockedGroups, $groupContactsTempTable);
// Note in theory we could do this transfer from the temp
// table to the group_contact_cache table out-of-process - possibly by
// continuing on after the browser is released (which seems to be
// Also - if we switched to the 'triple union' approach described above
// we could throw a try-catch around this line since best-effort would
// be good enough & potentially improve user experience.
- self::updateCacheFromTempTable($groupContactsTempTable, array_keys($locks));
-
- foreach ($locks as $lock) {
- $lock->release();
- }
+ self::updateCacheFromTempTable($groupContactsTempTable, $lockedGroups);
+ self::releaseGroupLocks($lockedGroups);
}
$smartGroups = implode(',', $smartGroups);
}
}
+ /**
+ * Get a lock, if available, for the given group.
+ *
+ * @param int $groupID
+ *
+ * @return bool
+ * @throws \CRM_Core_Exception
+ */
+ protected static function getGroupLock(int $groupID): bool {
+ $cacheKey = "data.core.group.$groupID";
+ if (isset(Civi::$statics["data.core.group.$groupID"])) {
+ // Loop avoidance for a circular parent-child situation.
+ // This would occur where the parent is a criteria of the child
+ // but needs to resolve the child to resolve itself.
+ // This has a unit test - testGroupWithParentInCriteria
+ return FALSE;
+ }
+ $lock = Civi::lockManager()->acquire($cacheKey);
+ if ($lock->isAcquired()) {
+ Civi::$statics["data.core.group.$groupID"] = $lock;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Release locks on the groups.
+ *
+ * @param array $groupIDs
+ */
+ protected static function releaseGroupLocks(array $groupIDs): void {
+ foreach ($groupIDs as $groupID) {
+ $lock = Civi::$statics["data.core.group.$groupID"];
+ $lock->release();
+ unset(Civi::$statics["data.core.group.$groupID"]);
+ }
+ }
+
}
+--------------------------------------------------------------------+
*/
+use Civi\Api4\Group;
+use Civi\Api4\SavedSearch;
+
/**
* Test class for CRM_Contact_BAO_Group BAO
*
* Test case to ensure child group is present in the hierarchy
* if it has multiple parent groups and not all are disabled.
*/
- public function testGroupHirearchy() {
+ public function testGroupHirearchy(): void {
// Use-case :
// 1. Create two parent group A and B and disable B
// 2. Create a child group C
// 3. Ensure that Group C is present in the group hierarchy
$params = [
- 'name' => uniqid(),
+ 'name' => 'parent group a',
'title' => 'Parent Group A',
'description' => 'Parent Group One',
'visibility' => 'User and User Admin Only',
$group1 = CRM_Contact_BAO_Group::create($params);
$params = array_merge($params, [
- 'name' => uniqid(),
+ 'name' => 'parent group b',
'title' => 'Parent Group B',
'description' => 'Parent Group Two',
// disable
$group2 = CRM_Contact_BAO_Group::create($params);
$params = array_merge($params, [
- 'name' => uniqid(),
+ 'name' => 'parent group c',
'title' => 'Child Group C',
'description' => 'Child Group C',
'parents' => [
/**
* Test nestedGroup pseudoconstant
*/
- public function testNestedGroup() {
+ public function testNestedGroup(): void {
$params = [
- 'name' => 'groupa',
+ 'name' => 'group a',
'title' => 'Parent Group A',
'description' => 'Parent Group One',
'visibility' => 'User and User Admin Only',
$group1 = CRM_Contact_BAO_Group::create($params);
$params = [
- 'name' => 'groupb',
+ 'name' => 'group b',
'title' => 'Parent Group B',
'description' => 'Parent Group Two',
'visibility' => 'User and User Admin Only',
$group2 = CRM_Contact_BAO_Group::create($params);
$params = [
- 'name' => 'groupc',
+ 'name' => 'group c',
'title' => 'Child Group C',
'description' => 'Child Group C',
'visibility' => 'User and User Admin Only',
], $nestedGroup);
}
+ /**
+ * Test that parents as criteria don't cause loops.
+ *
+ * @throws \API_Exception
+ */
+ public function testGroupWithParentInCriteria(): void {
+ $parentGroupID = Group::create()->setValues([
+ 'name' => 'Parent',
+ 'title' => 'Parent',
+ ])->execute()->first()['id'];
+ $savedSearchID = SavedSearch::create()->setValues([
+ 'form_values' => [
+ ['group', '=', ['IN' => [$parentGroupID]], 0, 0],
+ ],
+ 'name' => 'child',
+ ])->execute()->first()['id'];
+ $childGroupID = Group::create()->setValues([
+ 'name' => 'Child',
+ 'title' => 'Child',
+ 'saved_search_id' => $savedSearchID,
+ 'parents' => [$parentGroupID],
+ ])->execute()->first()['id'];
+ $this->callAPISuccess('Contact', 'get', ['group' => $childGroupID]);
+ }
+
/**
* Test adding a smart group.
*/
- public function testAddSmart() {
+ public function testAddSmart(): void {
$checkParams = $params = [
'title' => 'Group Dos',
'search_custom_id' => NULL,
'search_context' => 'advanced',
];
- list($smartGroupID, $savedSearchID) = CRM_Contact_BAO_Group::createHiddenSmartGroup($hiddenSmartParams);
+ [$smartGroupID, $savedSearchID] = CRM_Contact_BAO_Group::createHiddenSmartGroup($hiddenSmartParams);
$mailingID = $this->callAPISuccess('Mailing', 'create', [])['id'];
$this->callAPISuccess('MailingGroup', 'create', [