3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
12 use Civi\Core\Event\PostEvent
;
17 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 class CRM_Contact_BAO_GroupContact
extends CRM_Contact_DAO_GroupContact
implements \Civi\Test\HookInterface
{
22 * Deprecated add function
24 * @param array $params
26 * @return CRM_Contact_DAO_GroupContact
27 * @throws \CRM_Core_Exception
31 public static function add(array $params): CRM_Contact_DAO_GroupContact
{
32 return self
::writeRecord($params);
36 * Callback for hook_civicrm_post().
38 * @param \Civi\Core\Event\PostEvent $event
40 * @noinspection PhpUnused
41 * @noinspection UnknownInspectionInspection
43 public static function self_hook_civicrm_post(PostEvent
$event): void
{
44 if (is_object($event->object) && in_array($event->action
, ['create', 'edit'], TRUE)) {
45 // Lookup existing info for the sake of subscription history
46 if ($event->action
=== 'edit') {
47 $event->object->find(TRUE);
49 $params = $event->object->toArray();
50 CRM_Contact_BAO_SubscriptionHistory
::create($params);
55 * Given the list of params in the params array, fetch the object
56 * and store the values in the values array
58 * @param array $params
59 * Input parameters to find object.
60 * @param array $values
61 * Output values of the object.
64 * (reference) the values that could be potentially assigned to smarty
66 public static function getValues($params, &$values) {
70 $values['group']['data'] = CRM_Contact_BAO_GroupContact
::getContactGroup($params['contact_id'],
75 // get the total count of groups
76 $values['group']['totalCount'] = CRM_Contact_BAO_GroupContact
::getContactGroup($params['contact_id'],
86 * Given an array of contact ids, add all the contacts to the group
88 * @param array $contactIds
89 * The array of contact ids to be added.
91 * The id of the group.
92 * @param string $method
93 * @param string $status
94 * @param int $tracking
97 * (total, added, notAdded) count of contacts added to group
99 public static function addContactsToGroup(
106 if (empty($contactIds) ||
empty($groupId)) {
110 CRM_Utils_Hook
::pre('create', 'GroupContact', $groupId, $contactIds);
112 $result = self
::bulkAddContactsToGroup($contactIds, $groupId, $method, $status, $tracking);
113 CRM_Contact_BAO_GroupContactCache
::invalidateGroupContactCache($groupId);
114 CRM_Contact_BAO_Contact_Utils
::clearContactCaches();
116 CRM_Utils_Hook
::post('create', 'GroupContact', $groupId, $contactIds);
118 return [count($contactIds), $result['count_added'], $result['count_not_added']];
122 * Given an array of contact ids, remove all the contacts from the group
124 * @param array $contactIds
125 * (reference ) the array of contact ids to be removed.
126 * @param int $groupId
127 * The id of the group.
129 * @param string $method
130 * @param string $status
131 * @param string $tracking
134 * (total, removed, notRemoved) count of contacts removed to group
136 public static function removeContactsFromGroup(
143 if (!is_array($contactIds)) {
146 if ($status == 'Removed' ||
$status == 'Deleted') {
153 CRM_Utils_Hook
::pre($op, 'GroupContact', $groupId, $contactIds);
155 $date = date('YmdHis');
156 $numContactsRemoved = 0;
157 $numContactsNotRemoved = 0;
159 $group = new CRM_Contact_DAO_Group();
160 $group->id
= $groupId;
163 foreach ($contactIds as $contactId) {
164 if ($status == 'Deleted') {
165 $query = "DELETE FROM civicrm_group_contact WHERE contact_id = %1 AND group_id = %2";
166 $dao = CRM_Core_DAO
::executeQuery($query, [
167 1 => [$contactId, 'Positive'],
168 2 => [$groupId, 'Positive'],
171 'group_id' => $groupId,
172 'contact_id' => $contactId,
176 'tracking' => $tracking,
178 CRM_Contact_BAO_SubscriptionHistory
::create($historyParams);
179 // Removing a row from civicrm_group_contact for a smart group may mean a contact
180 // Is now back in a group based on criteria so we will invalidate the cache if it is there
181 // So that accurate group cache is created next time it is needed.
182 CRM_Contact_BAO_GroupContactCache
::invalidateGroupContactCache($groupId);
185 $groupContact = new CRM_Contact_DAO_GroupContact();
186 $groupContact->group_id
= $groupId;
187 $groupContact->contact_id
= $contactId;
188 // check if the selected contact id already a member, or if this is
189 // an opt-out of a smart group.
190 // if not a member remove to groupContact else keep the count of contacts that are not removed
191 if ($groupContact->find(TRUE) ||
$group->saved_search_id
) {
192 // remove the contact from the group
193 $numContactsRemoved++
;
196 $numContactsNotRemoved++
;
199 //now we grant the negative membership to contact if not member. CRM-3711
201 'group_id' => $groupId,
202 'contact_id' => $contactId,
206 'tracking' => $tracking,
208 CRM_Contact_BAO_SubscriptionHistory
::create($historyParams);
209 $groupContact->status
= $status;
210 $groupContact->save();
211 // Remove any rows from the group contact cache so it disappears straight away from smart groups.
212 CRM_Contact_BAO_GroupContactCache
::removeContact($contactId, $groupId);
216 CRM_Contact_BAO_Contact_Utils
::clearContactCaches();
218 CRM_Utils_Hook
::post($op, 'GroupContact', $groupId, $contactIds);
220 return [count($contactIds), $numContactsRemoved, $numContactsNotRemoved];
224 * Get list of all the groups and groups for a contact.
226 * @param int $contactId
229 * @param bool $visibility
233 * this array has key-> group id and value group title
235 public static function getGroupList($contactId = 0, $visibility = FALSE) {
236 $select = 'SELECT civicrm_group.id, civicrm_group.title ';
237 $from = ' FROM civicrm_group ';
238 $where = " WHERE civicrm_group.is_active = 1 ";
240 $from .= ' , civicrm_group_contact ';
241 $where .= " AND civicrm_group.id = civicrm_group_contact.group_id
242 AND civicrm_group_contact.contact_id = " . CRM_Utils_Type
::escape($contactId, 'Integer');
246 $where .= " AND civicrm_group.visibility != 'User and User Admin Only'";
248 $groupBy = " GROUP BY civicrm_group.id";
250 $orderby = " ORDER BY civicrm_group.name";
251 $sql = $select . $from . $where . $groupBy . $orderby;
253 $group = CRM_Core_DAO
::executeQuery($sql);
256 while ($group->fetch()) {
257 $values[$group->id
] = $group->title
;
264 * Get the list of groups for contact based on status of group membership.
266 * @param int $contactId
268 * @param string $status
269 * State of membership.
270 * @param int $numGroupContact
271 * Number of groups for a contact that should be shown.
273 * True if we are interested only in the count.
274 * @param bool $ignorePermission
275 * True if we should ignore permissions for the current user.
276 * useful in profile where permissions are limited for the user. If left
277 * at false only groups viewable by the current user are returned
278 * @param bool $onlyPublicGroups
279 * True if we want to hide system groups.
281 * @param bool $excludeHidden
283 * @param int $groupId
285 * @param bool $includeSmartGroups
286 * Include or Exclude Smart Group(s)
287 * @param bool $public
288 * Are we returning groups for use on a public page.
291 * the relevant data object values for the contact or the total count when $count is TRUE
293 public static function getContactGroup(
296 $numGroupContact = NULL,
298 $ignorePermission = FALSE,
299 $onlyPublicGroups = FALSE,
300 $excludeHidden = TRUE,
302 $includeSmartGroups = FALSE,
306 $select = 'SELECT count(DISTINCT civicrm_group_contact.id)';
310 civicrm_group_contact.id as civicrm_group_contact_id,
311 civicrm_group.title as group_title,
312 civicrm_group.frontend_title as group_public_title,
313 civicrm_group.visibility as visibility,
314 civicrm_group_contact.status as status,
315 civicrm_group.id as group_id,
316 civicrm_group.is_hidden as is_hidden,
317 civicrm_subscription_history.date as date,
318 civicrm_subscription_history.method as method';
321 $where = " WHERE contact_a.id = %1 AND civicrm_group.is_active = 1";
322 if (!$includeSmartGroups) {
323 $where .= " AND saved_search_id IS NULL";
325 if ($excludeHidden) {
326 $where .= " AND civicrm_group.is_hidden = 0 ";
328 $params = [1 => [$contactId, 'Integer']];
329 if (!empty($status)) {
330 $where .= ' AND civicrm_group_contact.status = %2';
331 $params[2] = [$status, 'String'];
333 if (!empty($groupId)) {
334 $where .= " AND civicrm_group.id = %3 ";
335 $params[3] = [$groupId, 'Integer'];
338 'civicrm_group_contact' => 1,
339 'civicrm_group' => 1,
340 'civicrm_subscription_history' => 1,
343 if ($ignorePermission) {
344 $permission = ' ( 1 ) ';
347 $permission = CRM_Core_Permission
::getPermissionedStaticGroupClause(CRM_Core_Permission
::VIEW
, $tables, $whereTables);
350 $from = CRM_Contact_BAO_Query
::fromClause($tables);
352 $where .= " AND $permission ";
354 if ($onlyPublicGroups) {
355 $where .= " AND civicrm_group.visibility != 'User and User Admin Only' ";
358 $order = $limit = '';
360 $order = ' ORDER BY civicrm_group.title, civicrm_subscription_history.date ASC';
362 if ($numGroupContact) {
363 $limit = " LIMIT 0, $numGroupContact";
367 $sql = $select . $from . $where . $order . $limit;
370 $result = CRM_Core_DAO
::singleValueQuery($sql, $params);
374 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
376 while ($dao->fetch()) {
377 $id = $dao->civicrm_group_contact_id
;
378 $values[$id]['id'] = $id;
379 $values[$id]['group_id'] = $dao->group_id
;
380 $values[$id]['title'] = ($public && !empty($group->group_public_title
) ?
$group->group_public_title
: $dao->group_title
);
381 $values[$id]['visibility'] = $dao->visibility
;
382 $values[$id]['is_hidden'] = $dao->is_hidden
;
383 switch ($dao->status
) {
393 $prefix = 'pending_';
395 $values[$id][$prefix . 'date'] = $dao->date
;
396 $values[$id][$prefix . 'method'] = $dao->method
;
397 if ($status == 'Removed') {
398 $query = "SELECT `date` as `date_added` FROM civicrm_subscription_history WHERE id = (SELECT max(id) FROM civicrm_subscription_history WHERE contact_id = %1 AND status = \"Added\" AND group_id = $dao->group_id )";
399 $dateDAO = CRM_Core_DAO
::executeQuery($query, $params);
400 if ($dateDAO->fetch()) {
401 $values[$id]['date_added'] = $dateDAO->date_added
;
410 * Returns membership details of a contact for a group.
412 * @param int $contactId
414 * @param int $groupID
415 * Id of a particular group.
416 * @param string $method
417 * If we want the subscription history details for a specific method.
422 public static function getMembershipDetail($contactId, $groupID, $method = 'Email') {
423 $leftJoin = $where = $orderBy = NULL;
426 //CRM-13341 add group_id clause
428 LEFT JOIN civicrm_subscription_history
429 ON ( civicrm_group_contact.contact_id = civicrm_subscription_history.contact_id
430 AND civicrm_subscription_history.group_id = {$groupID} )";
431 $where = "AND civicrm_subscription_history.method ='Email'";
432 $orderBy = "ORDER BY civicrm_subscription_history.id DESC";
436 FROM civicrm_group_contact
438 WHERE civicrm_group_contact.contact_id = %1
439 AND civicrm_group_contact.group_id = %2
445 1 => [$contactId, 'Integer'],
446 2 => [$groupID, 'Integer'],
448 $dao = CRM_Core_DAO
::executeQuery($query, $params);
454 * Method to get Group Id.
456 * @param int $groupContactID
457 * Id of a particular group.
460 * @return int groupID
462 public static function getGroupId($groupContactID) {
463 $dao = new CRM_Contact_DAO_GroupContact();
464 $dao->id
= $groupContactID;
466 return $dao->group_id
;
470 * Deprecated create function.
474 * @param array $params
476 * @return CRM_Contact_DAO_GroupContact
478 public static function create(array $params) {
479 // @fixme create was only called from CRM_Contact_BAO_Contact::createProfileContact
480 // As of Aug 2020 it's not called from anywhere so we can remove the below code after some time
482 CRM_Core_Error
::deprecatedFunctionWarning('Use the GroupContact API');
483 return self
::add($params);
487 * @param int $contactID
488 * @param int $groupID
492 public static function isContactInGroup($contactID, $groupID) {
493 if (!CRM_Utils_Rule
::positiveInteger($contactID) ||
494 !CRM_Utils_Rule
::positiveInteger($groupID)
500 ['group', 'IN', [$groupID], 0, 0],
501 ['contact_id', '=', $contactID, 0, 0],
503 [$contacts] = CRM_Contact_BAO_Query
::apiQuery($params, ['contact_id']);
505 if (!empty($contacts)) {
512 * Function merges the groups from otherContactID to mainContactID.
513 * along with subscription history
515 * @param int $mainContactId
516 * Contact id of main contact record.
517 * @param int $otherContactId
518 * Contact id of record which is going to merge.
520 * @see CRM_Dedupe_Merger::cpTables()
522 * TODO: use the 3rd $sqls param to append sql statements rather than executing them here
524 public static function mergeGroupContact($mainContactId, $otherContactId) {
526 1 => [$mainContactId, 'Integer'],
527 2 => [$otherContactId, 'Integer'],
530 // find all groups that are in otherContactID but not in mainContactID, copy them over
532 SELECT cOther.group_id
533 FROM civicrm_group_contact cOther
534 LEFT JOIN civicrm_group_contact cMain ON cOther.group_id = cMain.group_id AND cMain.contact_id = %1
535 WHERE cOther.contact_id = %2
536 AND cMain.contact_id IS NULL
538 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
541 while ($dao->fetch()) {
542 $otherGroupIDs[] = $dao->group_id
;
545 if (!empty($otherGroupIDs)) {
546 $otherGroupIDString = implode(',', $otherGroupIDs);
549 UPDATE civicrm_group_contact
551 WHERE contact_id = %2
552 AND group_id IN ( $otherGroupIDString )
554 CRM_Core_DAO
::executeQuery($sql, $params);
557 UPDATE civicrm_subscription_history
559 WHERE contact_id = %2
560 AND group_id IN ( $otherGroupIDString )
562 CRM_Core_DAO
::executeQuery($sql, $params);
566 SELECT cOther.group_id as group_id,
567 cOther.status as group_status
568 FROM civicrm_group_contact cMain
569 INNER JOIN civicrm_group_contact cOther ON cMain.group_id = cOther.group_id
570 WHERE cMain.contact_id = %1
571 AND cOther.contact_id = %2
573 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
576 while ($dao->fetch()) {
577 // only copy it over if it has added status and migrate the history
578 if ($dao->group_status
== 'Added') {
579 $groupIDs[] = $dao->group_id
;
583 if (!empty($groupIDs)) {
584 $groupIDString = implode(',', $groupIDs);
587 UPDATE civicrm_group_contact
589 WHERE contact_id = %1
590 AND group_id IN ( $groupIDString )
592 CRM_Core_DAO
::executeQuery($sql, $params);
595 UPDATE civicrm_subscription_history
597 WHERE contact_id = %2
598 AND group_id IN ( $groupIDString )
600 CRM_Core_DAO
::executeQuery($sql, $params);
603 // delete all the other group contacts
606 FROM civicrm_group_contact
607 WHERE contact_id = %2
609 CRM_Core_DAO
::executeQuery($sql, $params);
613 FROM civicrm_subscription_history
614 WHERE contact_id = %2
616 CRM_Core_DAO
::executeQuery($sql, $params);
620 * Given an array of contact ids, add all the contacts to the group
622 * @param array $contactIDs
623 * The array of contact ids to be added.
624 * @param int $groupID
625 * The id of the group.
626 * @param string $method
627 * @param string $status
628 * @param string $tracking
631 * (total, added, notAdded) count of contacts added to group
633 public static function bulkAddContactsToGroup(
641 $numContactsAdded = 0;
642 $numContactsNotAdded = 0;
645 REPLACE INTO civicrm_group_contact ( group_id, contact_id, status )
648 $subscriptioHistorySQL = "
649 INSERT INTO civicrm_subscription_history( group_id, contact_id, date, method, status, tracking )
653 $date = date('YmdHis');
655 // to avoid long strings, lets do BULK_INSERT_HIGH_COUNT values at a time
656 while (!empty($contactIDs)) {
657 $input = array_splice($contactIDs, 0, CRM_Core_DAO
::BULK_INSERT_HIGH_COUNT
);
658 $contactStr = implode(',', $input);
660 // lets check their current status
662 SELECT GROUP_CONCAT(contact_id) as contactStr
663 FROM civicrm_group_contact
666 AND contact_id IN ( $contactStr )
669 1 => [$groupID, 'Integer'],
670 2 => [$status, 'String'],
674 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
676 $presentIDs = explode(',', $dao->contactStr
);
677 $presentIDs = array_flip($presentIDs);
680 $gcValues = $shValues = [];
681 foreach ($input as $cid) {
682 if (isset($presentIDs[$cid])) {
683 $numContactsNotAdded++
;
687 $gcValues[] = "( $groupID, $cid, '$status' )";
688 $shValues[] = "( $groupID, $cid, '$date', '$method', '$status', '$tracking' )";
692 if (!empty($gcValues)) {
693 $cgSQL = $contactGroupSQL . implode(",\n", $gcValues);
694 CRM_Core_DAO
::executeQuery($cgSQL);
696 $shSQL = $subscriptioHistorySQL . implode(",\n", $shValues);
697 CRM_Core_DAO
::executeQuery($shSQL);
701 return ['count_added' => $numContactsAdded, 'count_not_added' => $numContactsNotAdded];
705 * Get options for a given field.
706 * @see CRM_Core_DAO::buildOptions
708 * @param string $fieldName
709 * @param string $context
710 * @see CRM_Core_DAO::buildOptionsContext
711 * @param array $props
712 * whatever is known about this dao object.
716 public static function buildOptions($fieldName, $context = NULL, $props = []) {
717 $options = CRM_Core_PseudoConstant
::get(__CLASS__
, $fieldName, [], $context);
719 // Sort group list by hierarchy
720 // TODO: This will only work when api.entity is "group_contact". What about others?
721 if (($fieldName == 'group' ||
$fieldName == 'group_id') && ($context == 'search' ||
$context == 'create')) {
722 $options = CRM_Contact_BAO_Group
::getGroupsHierarchy($options, NULL, '- ', TRUE);