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 +--------------------------------------------------------------------+
13 * This class contains functions for managing Tag(tag) for a contact
16 * @copyright CiviCRM LLC https://civicrm.org/licensing
18 class CRM_Core_BAO_EntityTag
extends CRM_Core_DAO_EntityTag
{
21 * Given a contact id, it returns an array of tag id's the contact belongs to.
23 * @param int $entityID
24 * Id of the entity usually the contactID.
25 * @param string $entityTable
26 * Name of the entity table usually 'civicrm_contact'.
29 * reference $tag array of category id's the contact belongs to.
31 public static function getTag($entityID, $entityTable = 'civicrm_contact') {
34 $entityTag = new CRM_Core_BAO_EntityTag();
35 $entityTag->entity_id
= $entityID;
36 $entityTag->entity_table
= $entityTable;
39 while ($entityTag->fetch()) {
40 $tags[$entityTag->tag_id
] = $entityTag->tag_id
;
46 * Takes an associative array and creates a entityTag object.
48 * the function extract all the params it needs to initialize the create a
49 * group object. the params array could contain additional unused name/value
52 * @param array $params
53 * (reference ) an assoc array of name/value pairs.
55 * @return CRM_Core_BAO_EntityTag
57 public static function add(&$params) {
58 $dataExists = self
::dataExists($params);
63 $entityTag = new CRM_Core_BAO_EntityTag();
64 $entityTag->copyValues($params);
66 // dont save the object if it already exists, CRM-1276
67 if (!$entityTag->find(TRUE)) {
69 CRM_Utils_Hook
::pre('create', 'EntityTag', $params['tag_id'], $params);
73 //invoke post hook on entityTag
74 // we are using this format to keep things consistent between the single and bulk operations
75 // so a bit different from other post hooks
76 $object = [0 => [0 => $params['entity_id']], 1 => $params['entity_table']];
77 CRM_Utils_Hook
::post('create', 'EntityTag', $params['tag_id'], $object);
83 * Check if there is data to create the object.
85 * @param array $params
86 * An assoc array of name/value pairs.
90 public static function dataExists($params) {
91 return !($params['tag_id'] == 0);
95 * Delete the tag for a contact.
97 * @param array $params
98 * (reference ) an assoc array of name/value pairs.
100 public static function del(&$params) {
102 if (!empty($params['tag_id'])) {
103 CRM_Utils_Hook
::pre('delete', 'EntityTag', $params['tag_id'], $params);
106 $entityTag = new CRM_Core_BAO_EntityTag();
107 $entityTag->copyValues($params);
108 $entityTag->delete();
110 //invoke post hook on entityTag
111 if (!empty($params['tag_id'])) {
112 $object = [0 => [0 => $params['entity_id']], 1 => $params['entity_table']];
113 CRM_Utils_Hook
::post('delete', 'EntityTag', $params['tag_id'], $object);
118 * Given an array of entity ids and entity table, add all the entity to the tags.
120 * @param array $entityIds
121 * (reference ) the array of entity ids to be added.
124 * @param string $entityTable
125 * Name of entity table default:civicrm_contact.
126 * @param bool $applyPermissions
127 * Should permissions be applied in this function.
130 * (total, added, notAdded) count of entities added to tag
132 public static function addEntitiesToTag(&$entityIds, $tagId, $entityTable, $applyPermissions) {
133 $numEntitiesAdded = 0;
134 $numEntitiesNotAdded = 0;
135 $entityIdsAdded = [];
137 //invoke pre hook for entityTag
138 $preObject = [$entityIds, $entityTable];
139 CRM_Utils_Hook
::pre('create', 'EntityTag', $tagId, $preObject);
141 foreach ($entityIds as $entityId) {
142 // CRM-17350 - check if we have permission to edit the contact
143 // that this tag belongs to.
144 if ($applyPermissions && !self
::checkPermissionOnEntityTag($entityId, $entityTable)) {
145 $numEntitiesNotAdded++
;
148 $tag = new CRM_Core_DAO_EntityTag();
150 $tag->entity_id
= $entityId;
151 $tag->tag_id
= $tagId;
152 $tag->entity_table
= $entityTable;
155 $entityIdsAdded[] = $entityId;
159 $numEntitiesNotAdded++
;
163 //invoke post hook on entityTag
164 $object = [$entityIdsAdded, $entityTable];
165 CRM_Utils_Hook
::post('create', 'EntityTag', $tagId, $object);
167 CRM_Contact_BAO_GroupContactCache
::opportunisticCacheFlush();
169 return [count($entityIds), $numEntitiesAdded, $numEntitiesNotAdded];
173 * Basic check for ACL permission on editing/creating/removing a tag.
175 * In the absence of something better contacts get a proper check and other entities
176 * default to 'edit all contacts'. This is currently only accessed from the api which previously
177 * applied edit all contacts to all - so while still too restrictive it represents a loosening.
179 * Current possible entities are attachments, activities, cases & contacts.
181 * @param int $entityID
182 * @param string $entityTable
186 public static function checkPermissionOnEntityTag($entityID, $entityTable) {
187 if ($entityTable == 'civicrm_contact') {
188 return CRM_Contact_BAO_Contact_Permission
::allow($entityID, CRM_Core_Permission
::EDIT
);
191 return CRM_Core_Permission
::check('edit all contacts');
196 * Given an array of entity ids and entity table, remove entity(s)tags.
198 * @param array $entityIds
199 * (reference ) the array of entity ids to be removed.
202 * @param string $entityTable
203 * Name of entity table default:civicrm_contact.
204 * @param bool $applyPermissions
205 * Should permissions be applied in this function.
208 * (total, removed, notRemoved) count of entities removed from tags
210 public static function removeEntitiesFromTag(&$entityIds, $tagId, $entityTable, $applyPermissions) {
211 $numEntitiesRemoved = 0;
212 $numEntitiesNotRemoved = 0;
213 $entityIdsRemoved = [];
215 //invoke pre hook for entityTag
216 $preObject = [$entityIds, $entityTable];
217 CRM_Utils_Hook
::pre('delete', 'EntityTag', $tagId, $preObject);
219 foreach ($entityIds as $entityId) {
220 // CRM-17350 - check if we have permission to edit the contact
221 // that this tag belongs to.
222 if ($applyPermissions && !self
::checkPermissionOnEntityTag($entityId, $entityTable)) {
223 $numEntitiesNotRemoved++
;
226 $tag = new CRM_Core_DAO_EntityTag();
228 $tag->entity_id
= $entityId;
229 $tag->tag_id
= $tagId;
230 $tag->entity_table
= $entityTable;
233 $entityIdsRemoved[] = $entityId;
234 $numEntitiesRemoved++
;
237 $numEntitiesNotRemoved++
;
241 //invoke post hook on entityTag
242 $object = [$entityIdsRemoved, $entityTable];
243 CRM_Utils_Hook
::post('delete', 'EntityTag', $tagId, $object);
245 CRM_Contact_BAO_GroupContactCache
::opportunisticCacheFlush();
247 return [count($entityIds), $numEntitiesRemoved, $numEntitiesNotRemoved];
251 * Takes an associative array and creates tag entity record for all tag entities.
253 * @param array $params
254 * (reference) an assoc array of name/value pairs.
255 * @param string $entityTable
256 * @param int $entityID
258 public static function create(&$params, $entityTable, $entityID) {
259 // get categories for the entity id
260 $entityTag = CRM_Core_BAO_EntityTag
::getTag($entityID, $entityTable);
262 // get the list of all the categories
263 $allTag = CRM_Core_BAO_Tag
::getTags($entityTable);
265 // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input
266 if (!is_array($params)) {
270 // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input
271 if (!is_array($entityTag)) {
275 // check which values has to be inserted/deleted for contact
276 foreach ($allTag as $key => $varValue) {
277 $tagParams['entity_table'] = $entityTable;
278 $tagParams['entity_id'] = $entityID;
279 $tagParams['tag_id'] = $key;
281 if (array_key_exists($key, $params) && !array_key_exists($key, $entityTag)) {
282 // insert a new record
283 CRM_Core_BAO_EntityTag
::add($tagParams);
285 elseif (!array_key_exists($key, $params) && array_key_exists($key, $entityTag)) {
286 // delete a record for existing contact
287 CRM_Core_BAO_EntityTag
::del($tagParams);
293 * This function returns all entities assigned to a specific tag.
296 * An object of a tag.
299 * array of entity ids
301 public function getEntitiesByTag($tag) {
303 $entityTagDAO = new CRM_Core_DAO_EntityTag();
304 $entityTagDAO->tag_id
= $tag->id
;
305 $entityTagDAO->find();
306 while ($entityTagDAO->fetch()) {
307 $entityIds[] = $entityTagDAO->entity_id
;
315 * @param int $contactID
320 public static function getContactTags($contactID, $count = FALSE) {
323 $select = "SELECT ct.id, ct.name ";
326 $select = "SELECT count(*) as cnt";
331 INNER JOIN civicrm_entity_tag et ON ( ct.id = et.tag_id AND
332 et.entity_id = {$contactID} AND
333 et.entity_table = 'civicrm_contact' AND
336 $dao = CRM_Core_DAO
::executeQuery($query);
343 while ($dao->fetch()) {
344 $contactTags[$dao->id
] = $dao->name
;
351 * Get child contact tags given parentId.
353 * @param int $parentId
354 * @param int $entityId
355 * @param string $entityTable
359 public static function getChildEntityTags($parentId, $entityId, $entityTable = 'civicrm_contact') {
361 $query = "SELECT ct.id as tag_id, name FROM civicrm_tag ct
362 INNER JOIN civicrm_entity_tag et ON ( et.entity_id = {$entityId} AND
363 et.entity_table = '{$entityTable}' AND et.tag_id = ct.id)
364 WHERE ct.parent_id = {$parentId}";
366 $dao = CRM_Core_DAO
::executeQuery($query);
368 while ($dao->fetch()) {
369 $entityTags[$dao->tag_id
] = [
370 'id' => $dao->tag_id
,
371 'name' => $dao->name
,
381 * Tag A will inherit all of tag B's properties.
382 * Tag B will be deleted.
389 public function mergeTags($tagAId, $tagBId) {
391 1 => [$tagAId, 'Integer'],
392 2 => [$tagBId, 'Integer'],
395 // re-compute used_for field
396 $query = "SELECT id, name, used_for FROM civicrm_tag WHERE id IN (%1, %2)";
397 $dao = CRM_Core_DAO
::executeQuery($query, $queryParams);
399 while ($dao->fetch()) {
400 $label = ($dao->id
== $tagAId) ?
'tagA' : 'tagB';
401 $tags[$label] = $dao->name
;
402 $tags["{$label}_used_for"] = $dao->used_for ?
explode(",", $dao->used_for
) : [];
404 $usedFor = array_merge($tags["tagA_used_for"], $tags["tagB_used_for"]);
405 $usedFor = implode(',', array_unique($usedFor));
406 $tags["used_for"] = explode(",", $usedFor);
408 // get all merge queries together
410 // 1. update entity tag entries
411 "UPDATE IGNORE civicrm_entity_tag SET tag_id = %1 WHERE tag_id = %2",
413 "UPDATE civicrm_tag SET parent_id = %1 WHERE parent_id = %2",
414 // 3. update used_for info for tag A & children
415 "UPDATE civicrm_tag SET used_for = '{$usedFor}' WHERE id = %1 OR parent_id = %1",
417 "DELETE FROM civicrm_tag WHERE id = %2",
418 // 5. remove duplicate entity tag records
419 "DELETE et2.* from civicrm_entity_tag et1 INNER JOIN civicrm_entity_tag et2 ON et1.entity_table = et2.entity_table AND et1.entity_id = et2.entity_id AND et1.tag_id = et2.tag_id WHERE et1.id < et2.id",
420 // 6. remove orphaned entity_tags
421 "DELETE FROM civicrm_entity_tag WHERE tag_id = %2",
423 $tables = ['civicrm_entity_tag', 'civicrm_tag'];
425 // Allow hook_civicrm_merge() to add SQL statements for the merge operation AND / OR
426 // perform any other actions like logging
427 CRM_Utils_Hook
::merge('sqls', $sqls, $tagAId, $tagBId, $tables);
429 // call the SQL queries in one transaction
430 $transaction = new CRM_Core_Transaction();
431 foreach ($sqls as $sql) {
432 CRM_Core_DAO
::executeQuery($sql, $queryParams, TRUE, NULL, TRUE);
434 $transaction->commit();
436 $tags['status'] = TRUE;
441 * Get options for a given field.
443 * @see CRM_Core_DAO::buildOptions
444 * @see CRM_Core_DAO::buildOptionsContext
446 * @param string $fieldName
447 * @param string $context
448 * As per CRM_Core_DAO::buildOptionsContext.
449 * @param array $props
450 * whatever is known about this dao object.
454 public static function buildOptions($fieldName, $context = NULL, $props = []) {
457 if ($fieldName == 'tag' ||
$fieldName == 'tag_id') {
458 if (!empty($props['entity_table'])) {
459 $entity = CRM_Utils_Type
::escape($props['entity_table'], 'String');
460 $params[] = "used_for LIKE '%$entity%'";
463 // Output tag list as nested hierarchy
464 // TODO: This will only work when api.entity is "entity_tag". What about others?
465 if ($context == 'search' ||
$context == 'create') {
467 return CRM_Core_BAO_Tag
::getTags(CRM_Utils_Array
::value('entity_table', $props, 'civicrm_contact'), $dummyArray, CRM_Utils_Array
::value('parent_id', $params), '- ');
471 $options = CRM_Core_PseudoConstant
::get(__CLASS__
, $fieldName, $params, $context);
473 // Special formatting for validate/match context
474 if ($fieldName == 'entity_table' && in_array($context, ['validate', 'match'])) {
476 foreach (self
::buildOptions($fieldName) as $tableName => $label) {
477 $bao = CRM_Core_DAO_AllCoreTables
::getClassForTable($tableName);
478 $apiName = CRM_Core_DAO_AllCoreTables
::getBriefName($bao);
479 $options[$tableName] = $apiName;