3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * This class contains functions for managing Tag(tag) for a contact
32 * @copyright CiviCRM LLC (c) 2004-2019
34 class CRM_Core_BAO_EntityTag
extends CRM_Core_DAO_EntityTag
{
37 * Given a contact id, it returns an array of tag id's the contact belongs to.
39 * @param int $entityID
40 * Id of the entity usually the contactID.
41 * @param string $entityTable
42 * Name of the entity table usually 'civicrm_contact'.
45 * reference $tag array of category id's the contact belongs to.
47 public static function getTag($entityID, $entityTable = 'civicrm_contact') {
50 $entityTag = new CRM_Core_BAO_EntityTag();
51 $entityTag->entity_id
= $entityID;
52 $entityTag->entity_table
= $entityTable;
55 while ($entityTag->fetch()) {
56 $tags[$entityTag->tag_id
] = $entityTag->tag_id
;
62 * Takes an associative array and creates a entityTag object.
64 * the function extract all the params it needs to initialize the create a
65 * group object. the params array could contain additional unused name/value
68 * @param array $params
69 * (reference ) an assoc array of name/value pairs.
71 * @return CRM_Core_BAO_EntityTag
73 public static function add(&$params) {
74 $dataExists = self
::dataExists($params);
79 $entityTag = new CRM_Core_BAO_EntityTag();
80 $entityTag->copyValues($params);
82 // dont save the object if it already exists, CRM-1276
83 if (!$entityTag->find(TRUE)) {
85 CRM_Utils_Hook
::pre('create', 'EntityTag', $params['tag_id'], $params);
89 //invoke post hook on entityTag
90 // we are using this format to keep things consistent between the single and bulk operations
91 // so a bit different from other post hooks
92 $object = [0 => [0 => $params['entity_id']], 1 => $params['entity_table']];
93 CRM_Utils_Hook
::post('create', 'EntityTag', $params['tag_id'], $object);
99 * Check if there is data to create the object.
101 * @param array $params
102 * An assoc array of name/value pairs.
106 public static function dataExists($params) {
107 return !($params['tag_id'] == 0);
111 * Delete the tag for a contact.
113 * @param array $params
114 * (reference ) an assoc array of name/value pairs.
116 public static function del(&$params) {
118 if (!empty($params['tag_id'])) {
119 CRM_Utils_Hook
::pre('delete', 'EntityTag', $params['tag_id'], $params);
122 $entityTag = new CRM_Core_BAO_EntityTag();
123 $entityTag->copyValues($params);
124 $entityTag->delete();
126 //invoke post hook on entityTag
127 if (!empty($params['tag_id'])) {
128 $object = [0 => [0 => $params['entity_id']], 1 => $params['entity_table']];
129 CRM_Utils_Hook
::post('delete', 'EntityTag', $params['tag_id'], $object);
134 * Given an array of entity ids and entity table, add all the entity to the tags.
136 * @param array $entityIds
137 * (reference ) the array of entity ids to be added.
140 * @param string $entityTable
141 * Name of entity table default:civicrm_contact.
142 * @param bool $applyPermissions
143 * Should permissions be applied in this function.
146 * (total, added, notAdded) count of entities added to tag
148 public static function addEntitiesToTag(&$entityIds, $tagId, $entityTable, $applyPermissions) {
149 $numEntitiesAdded = 0;
150 $numEntitiesNotAdded = 0;
151 $entityIdsAdded = [];
153 //invoke pre hook for entityTag
154 $preObject = [$entityIds, $entityTable];
155 CRM_Utils_Hook
::pre('create', 'EntityTag', $tagId, $preObject);
157 foreach ($entityIds as $entityId) {
158 // CRM-17350 - check if we have permission to edit the contact
159 // that this tag belongs to.
160 if ($applyPermissions && !self
::checkPermissionOnEntityTag($entityId, $entityTable)) {
161 $numEntitiesNotAdded++
;
164 $tag = new CRM_Core_DAO_EntityTag();
166 $tag->entity_id
= $entityId;
167 $tag->tag_id
= $tagId;
168 $tag->entity_table
= $entityTable;
171 $entityIdsAdded[] = $entityId;
175 $numEntitiesNotAdded++
;
179 //invoke post hook on entityTag
180 $object = [$entityIdsAdded, $entityTable];
181 CRM_Utils_Hook
::post('create', 'EntityTag', $tagId, $object);
183 CRM_Contact_BAO_GroupContactCache
::opportunisticCacheFlush();
185 return [count($entityIds), $numEntitiesAdded, $numEntitiesNotAdded];
189 * Basic check for ACL permission on editing/creating/removing a tag.
191 * In the absence of something better contacts get a proper check and other entities
192 * default to 'edit all contacts'. This is currently only accessed from the api which previously
193 * applied edit all contacts to all - so while still too restrictive it represents a loosening.
195 * Current possible entities are attachments, activities, cases & contacts.
197 * @param int $entityID
198 * @param string $entityTable
202 public static function checkPermissionOnEntityTag($entityID, $entityTable) {
203 if ($entityTable == 'civicrm_contact') {
204 return CRM_Contact_BAO_Contact_Permission
::allow($entityID, CRM_Core_Permission
::EDIT
);
207 return CRM_Core_Permission
::check('edit all contacts');
212 * Given an array of entity ids and entity table, remove entity(s)tags.
214 * @param array $entityIds
215 * (reference ) the array of entity ids to be removed.
218 * @param string $entityTable
219 * Name of entity table default:civicrm_contact.
220 * @param bool $applyPermissions
221 * Should permissions be applied in this function.
224 * (total, removed, notRemoved) count of entities removed from tags
226 public static function removeEntitiesFromTag(&$entityIds, $tagId, $entityTable, $applyPermissions) {
227 $numEntitiesRemoved = 0;
228 $numEntitiesNotRemoved = 0;
229 $entityIdsRemoved = [];
231 //invoke pre hook for entityTag
232 $preObject = [$entityIds, $entityTable];
233 CRM_Utils_Hook
::pre('delete', 'EntityTag', $tagId, $preObject);
235 foreach ($entityIds as $entityId) {
236 // CRM-17350 - check if we have permission to edit the contact
237 // that this tag belongs to.
238 if ($applyPermissions && !self
::checkPermissionOnEntityTag($entityId, $entityTable)) {
239 $numEntitiesNotRemoved++
;
242 $tag = new CRM_Core_DAO_EntityTag();
244 $tag->entity_id
= $entityId;
245 $tag->tag_id
= $tagId;
246 $tag->entity_table
= $entityTable;
249 $entityIdsRemoved[] = $entityId;
250 $numEntitiesRemoved++
;
253 $numEntitiesNotRemoved++
;
257 //invoke post hook on entityTag
258 $object = [$entityIdsRemoved, $entityTable];
259 CRM_Utils_Hook
::post('delete', 'EntityTag', $tagId, $object);
261 CRM_Contact_BAO_GroupContactCache
::opportunisticCacheFlush();
263 return [count($entityIds), $numEntitiesRemoved, $numEntitiesNotRemoved];
267 * Takes an associative array and creates tag entity record for all tag entities.
269 * @param array $params
270 * (reference) an assoc array of name/value pairs.
271 * @param string $entityTable
272 * @param int $entityID
274 public static function create(&$params, $entityTable, $entityID) {
275 // get categories for the entity id
276 $entityTag = CRM_Core_BAO_EntityTag
::getTag($entityID, $entityTable);
278 // get the list of all the categories
279 $allTag = CRM_Core_BAO_Tag
::getTags($entityTable);
281 // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input
282 if (!is_array($params)) {
286 // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input
287 if (!is_array($entityTag)) {
291 // check which values has to be inserted/deleted for contact
292 foreach ($allTag as $key => $varValue) {
293 $tagParams['entity_table'] = $entityTable;
294 $tagParams['entity_id'] = $entityID;
295 $tagParams['tag_id'] = $key;
297 if (array_key_exists($key, $params) && !array_key_exists($key, $entityTag)) {
298 // insert a new record
299 CRM_Core_BAO_EntityTag
::add($tagParams);
301 elseif (!array_key_exists($key, $params) && array_key_exists($key, $entityTag)) {
302 // delete a record for existing contact
303 CRM_Core_BAO_EntityTag
::del($tagParams);
309 * This function returns all entities assigned to a specific tag.
312 * An object of a tag.
315 * array of entity ids
317 public function getEntitiesByTag($tag) {
319 $entityTagDAO = new CRM_Core_DAO_EntityTag();
320 $entityTagDAO->tag_id
= $tag->id
;
321 $entityTagDAO->find();
322 while ($entityTagDAO->fetch()) {
323 $entityIds[] = $entityTagDAO->entity_id
;
331 * @param int $contactID
336 public static function getContactTags($contactID, $count = FALSE) {
339 $select = "SELECT ct.id, ct.name ";
342 $select = "SELECT count(*) as cnt";
347 INNER JOIN civicrm_entity_tag et ON ( ct.id = et.tag_id AND
348 et.entity_id = {$contactID} AND
349 et.entity_table = 'civicrm_contact' AND
352 $dao = CRM_Core_DAO
::executeQuery($query);
359 while ($dao->fetch()) {
360 $contactTags[$dao->id
] = $dao->name
;
367 * Get child contact tags given parentId.
369 * @param int $parentId
370 * @param int $entityId
371 * @param string $entityTable
375 public static function getChildEntityTags($parentId, $entityId, $entityTable = 'civicrm_contact') {
377 $query = "SELECT ct.id as tag_id, name FROM civicrm_tag ct
378 INNER JOIN civicrm_entity_tag et ON ( et.entity_id = {$entityId} AND
379 et.entity_table = '{$entityTable}' AND et.tag_id = ct.id)
380 WHERE ct.parent_id = {$parentId}";
382 $dao = CRM_Core_DAO
::executeQuery($query);
384 while ($dao->fetch()) {
385 $entityTags[$dao->tag_id
] = [
386 'id' => $dao->tag_id
,
387 'name' => $dao->name
,
397 * Tag A will inherit all of tag B's properties.
398 * Tag B will be deleted.
405 public function mergeTags($tagAId, $tagBId) {
407 1 => [$tagAId, 'Integer'],
408 2 => [$tagBId, 'Integer'],
411 // re-compute used_for field
412 $query = "SELECT id, name, used_for FROM civicrm_tag WHERE id IN (%1, %2)";
413 $dao = CRM_Core_DAO
::executeQuery($query, $queryParams);
415 while ($dao->fetch()) {
416 $label = ($dao->id
== $tagAId) ?
'tagA' : 'tagB';
417 $tags[$label] = $dao->name
;
418 $tags["{$label}_used_for"] = $dao->used_for ?
explode(",", $dao->used_for
) : [];
420 $usedFor = array_merge($tags["tagA_used_for"], $tags["tagB_used_for"]);
421 $usedFor = implode(',', array_unique($usedFor));
422 $tags["used_for"] = explode(",", $usedFor);
424 // get all merge queries together
426 // 1. update entity tag entries
427 "UPDATE IGNORE civicrm_entity_tag SET tag_id = %1 WHERE tag_id = %2",
429 "UPDATE civicrm_tag SET parent_id = %1 WHERE parent_id = %2",
430 // 3. update used_for info for tag A & children
431 "UPDATE civicrm_tag SET used_for = '{$usedFor}' WHERE id = %1 OR parent_id = %1",
433 "DELETE FROM civicrm_tag WHERE id = %2",
434 // 5. remove duplicate entity tag records
435 "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",
436 // 6. remove orphaned entity_tags
437 "DELETE FROM civicrm_entity_tag WHERE tag_id = %2",
439 $tables = ['civicrm_entity_tag', 'civicrm_tag'];
441 // Allow hook_civicrm_merge() to add SQL statements for the merge operation AND / OR
442 // perform any other actions like logging
443 CRM_Utils_Hook
::merge('sqls', $sqls, $tagAId, $tagBId, $tables);
445 // call the SQL queries in one transaction
446 $transaction = new CRM_Core_Transaction();
447 foreach ($sqls as $sql) {
448 CRM_Core_DAO
::executeQuery($sql, $queryParams, TRUE, NULL, TRUE);
450 $transaction->commit();
452 $tags['status'] = TRUE;
457 * Get options for a given field.
459 * @see CRM_Core_DAO::buildOptions
460 * @see CRM_Core_DAO::buildOptionsContext
462 * @param string $fieldName
463 * @param string $context
464 * As per CRM_Core_DAO::buildOptionsContext.
465 * @param array $props
466 * whatever is known about this dao object.
470 public static function buildOptions($fieldName, $context = NULL, $props = []) {
473 if ($fieldName == 'tag' ||
$fieldName == 'tag_id') {
474 if (!empty($props['entity_table'])) {
475 $entity = CRM_Utils_Type
::escape($props['entity_table'], 'String');
476 $params[] = "used_for LIKE '%$entity%'";
479 // Output tag list as nested hierarchy
480 // TODO: This will only work when api.entity is "entity_tag". What about others?
481 if ($context == 'search' ||
$context == 'create') {
483 return CRM_Core_BAO_Tag
::getTags(CRM_Utils_Array
::value('entity_table', $props, 'civicrm_contact'), $dummyArray, CRM_Utils_Array
::value('parent_id', $params), '- ');
487 $options = CRM_Core_PseudoConstant
::get(__CLASS__
, $fieldName, $params, $context);
489 // Special formatting for validate/match context
490 if ($fieldName == 'entity_table' && in_array($context, ['validate', 'match'])) {
492 foreach (self
::buildOptions($fieldName) as $tableName => $label) {
493 $bao = CRM_Core_DAO_AllCoreTables
::getClassForTable($tableName);
494 $apiName = CRM_Core_DAO_AllCoreTables
::getBriefName($bao);
495 $options[$tableName] = $apiName;