Merge pull request #8347 from eileenmcnaughton/tidy-up
[civicrm-core.git] / CRM / Core / BAO / EntityTag.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
fa938177 6 | Copyright CiviCRM LLC (c) 2004-2016 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 * This class contains functions for managing Tag(tag) for a contact
30 *
31 * @package CRM
fa938177 32 * @copyright CiviCRM LLC (c) 2004-2016
6a488035
TO
33 */
34class CRM_Core_BAO_EntityTag extends CRM_Core_DAO_EntityTag {
35
36 /**
192d36c5 37 * Given a contact id, it returns an array of tag id's the contact belongs to.
6a488035 38 *
6a0b768e
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'.
6a488035 43 *
8d7a9d07
CB
44 * @return array
45 * reference $tag array of category id's the contact belongs to.
6a488035 46 */
24602943 47 public static function getTag($entityID, $entityTable = 'civicrm_contact') {
6a488035
TO
48 $tags = array();
49
50 $entityTag = new CRM_Core_BAO_EntityTag();
51 $entityTag->entity_id = $entityID;
52 $entityTag->entity_table = $entityTable;
53 $entityTag->find();
54
55 while ($entityTag->fetch()) {
56 $tags[$entityTag->tag_id] = $entityTag->tag_id;
57 }
58 return $tags;
59 }
60
61 /**
fe482240 62 * Takes an associative array and creates a entityTag object.
6a488035
TO
63 *
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
66 * pairs
67 *
6a0b768e
TO
68 * @param array $params
69 * (reference ) an assoc array of name/value pairs.
6a488035 70 *
16b10e64 71 * @return CRM_Core_BAO_EntityTag
6a488035 72 */
00be9182 73 public static function add(&$params) {
6a488035
TO
74 $dataExists = self::dataExists($params);
75 if (!$dataExists) {
76 return NULL;
77 }
78
79 $entityTag = new CRM_Core_BAO_EntityTag();
80 $entityTag->copyValues($params);
81
82 // dont save the object if it already exists, CRM-1276
83 if (!$entityTag->find(TRUE)) {
84 $entityTag->save();
85
86 //invoke post hook on entityTag
87 // we are using this format to keep things consistent between the single and bulk operations
88 // so a bit different from other post hooks
89 $object = array(0 => array(0 => $params['entity_id']), 1 => $params['entity_table']);
90 CRM_Utils_Hook::post('create', 'EntityTag', $params['tag_id'], $object);
91 }
92 return $entityTag;
93 }
94
95 /**
fe482240 96 * Check if there is data to create the object.
6a488035 97 *
6a0b768e
TO
98 * @param array $params
99 * An assoc array of name/value pairs.
dd244018 100 *
8d7a9d07 101 * @return bool
6a488035 102 */
00be9182 103 public static function dataExists($params) {
c490a46a 104 return !($params['tag_id'] == 0);
6a488035
TO
105 }
106
107 /**
fe482240 108 * Delete the tag for a contact.
6a488035 109 *
6a0b768e
TO
110 * @param array $params
111 * (reference ) an assoc array of name/value pairs.
6a488035 112 */
00be9182 113 public static function del(&$params) {
6a488035
TO
114 $entityTag = new CRM_Core_BAO_EntityTag();
115 $entityTag->copyValues($params);
355b20b1 116 $entityTag->delete();
6a488035 117
355b20b1 118 //invoke post hook on entityTag
5fb2e445 119 if (!empty($params['tag_id'])) {
120 $object = array(0 => array(0 => $params['entity_id']), 1 => $params['entity_table']);
121 CRM_Utils_Hook::post('delete', 'EntityTag', $params['tag_id'], $object);
122 }
6a488035
TO
123 }
124
125 /**
f836c635 126 * Given an array of entity ids and entity table, add all the entity to the tags.
6a488035 127 *
6a0b768e
TO
128 * @param array $entityIds
129 * (reference ) the array of entity ids to be added.
130 * @param int $tagId
131 * The id of the tag.
132 * @param string $entityTable
133 * Name of entity table default:civicrm_contact.
424616b8 134 * @param bool $applyPermissions
135 * Should permissions be applied in this function.
6a488035 136 *
a6c01b45 137 * @return array
424616b8 138 * (total, added, notAdded) count of entities added to tag
6a488035 139 */
424616b8 140 public static function addEntitiesToTag(&$entityIds, $tagId, $entityTable, $applyPermissions) {
353ffa53 141 $numEntitiesAdded = 0;
6a488035 142 $numEntitiesNotAdded = 0;
353ffa53 143 $entityIdsAdded = array();
6a488035
TO
144
145 foreach ($entityIds as $entityId) {
f836c635
J
146 // CRM-17350 - check if we have permission to edit the contact
147 // that this tag belongs to.
424616b8 148 if ($applyPermissions && !self::checkPermissionOnEntityTag($entityId, $entityTable)) {
f836c635
J
149 $numEntitiesNotAdded++;
150 continue;
151 }
6a488035
TO
152 $tag = new CRM_Core_DAO_EntityTag();
153
353ffa53
TO
154 $tag->entity_id = $entityId;
155 $tag->tag_id = $tagId;
6a488035
TO
156 $tag->entity_table = $entityTable;
157 if (!$tag->find()) {
158 $tag->save();
159 $entityIdsAdded[] = $entityId;
160 $numEntitiesAdded++;
161 }
162 else {
163 $numEntitiesNotAdded++;
164 }
165 }
166
167 //invoke post hook on entityTag
168 $object = array($entityIdsAdded, $entityTable);
169 CRM_Utils_Hook::post('create', 'EntityTag', $tagId, $object);
170
2b68a50c 171 CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush();
6a488035
TO
172
173 return array(count($entityIds), $numEntitiesAdded, $numEntitiesNotAdded);
174 }
175
176 /**
424616b8 177 * Basic check for ACL permission on editing/creating/removing a tag.
178 *
179 * In the absence of something better contacts get a proper check and other entities
180 * default to 'edit all contacts'. This is currently only accessed from the api which previously
181 * applied edit all contacts to all - so while still too restrictive it represents a loosening.
182 *
183 * Current possible entities are attachments, activities, cases & contacts.
184 *
185 * @param int $entityID
186 * @param string $entityTable
187 *
188 * @return bool
189 */
190 public static function checkPermissionOnEntityTag($entityID, $entityTable) {
191 if ($entityTable == 'civicrm_contact') {
192 return CRM_Contact_BAO_Contact_Permission::allow($entityID, CRM_Core_Permission::EDIT);
193 }
194 else {
195 return CRM_Core_Permission::check('edit all contacts');
196 }
197 }
198
199 /**
200 * Given an array of entity ids and entity table, remove entity(s)tags.
6a488035 201 *
6a0b768e
TO
202 * @param array $entityIds
203 * (reference ) the array of entity ids to be removed.
204 * @param int $tagId
205 * The id of the tag.
206 * @param string $entityTable
207 * Name of entity table default:civicrm_contact.
424616b8 208 * @param bool $applyPermissions
209 * Should permissions be applied in this function.
6a488035 210 *
a6c01b45
CW
211 * @return array
212 * (total, removed, notRemoved) count of entities removed from tags
6a488035 213 */
424616b8 214 public static function removeEntitiesFromTag(&$entityIds, $tagId, $entityTable, $applyPermissions) {
6a488035
TO
215 $numEntitiesRemoved = 0;
216 $numEntitiesNotRemoved = 0;
217 $entityIdsRemoved = array();
218
219 foreach ($entityIds as $entityId) {
f836c635
J
220 // CRM-17350 - check if we have permission to edit the contact
221 // that this tag belongs to.
424616b8 222 if ($applyPermissions && !self::checkPermissionOnEntityTag($entityId, $entityTable)) {
223 $numEntitiesNotRemoved++;
f836c635
J
224 continue;
225 }
6a488035
TO
226 $tag = new CRM_Core_DAO_EntityTag();
227
353ffa53
TO
228 $tag->entity_id = $entityId;
229 $tag->tag_id = $tagId;
6a488035
TO
230 $tag->entity_table = $entityTable;
231 if ($tag->find()) {
232 $tag->delete();
233 $entityIdsRemoved[] = $entityId;
234 $numEntitiesRemoved++;
235 }
236 else {
237 $numEntitiesNotRemoved++;
238 }
239 }
240
241 //invoke post hook on entityTag
242 $object = array($entityIdsRemoved, $entityTable);
243 CRM_Utils_Hook::post('delete', 'EntityTag', $tagId, $object);
244
2b68a50c 245 CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush();
6a488035
TO
246
247 return array(count($entityIds), $numEntitiesRemoved, $numEntitiesNotRemoved);
248 }
249
250 /**
fe482240 251 * Takes an associative array and creates tag entity record for all tag entities.
6a488035 252 *
6a0b768e
TO
253 * @param array $params
254 * (reference) an assoc array of name/value pairs.
192d36c5 255 * @param string $entityTable
100fef9d 256 * @param int $entityID
6a488035 257 */
00be9182 258 public static function create(&$params, $entityTable, $entityID) {
6a488035
TO
259 // get categories for the entity id
260 $entityTag = CRM_Core_BAO_EntityTag::getTag($entityID, $entityTable);
261
262 // get the list of all the categories
263 $allTag = CRM_Core_BAO_Tag::getTags($entityTable);
264
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)) {
267 $params = array();
268 }
269
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)) {
272 $entityTag = array();
273 }
274
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;
280
281 if (array_key_exists($key, $params) && !array_key_exists($key, $entityTag)) {
282 // insert a new record
283 CRM_Core_BAO_EntityTag::add($tagParams);
284 }
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);
288 }
289 }
290 }
291
292 /**
fe482240 293 * This function returns all entities assigned to a specific tag.
6a488035 294 *
6a0b768e
TO
295 * @param object $tag
296 * An object of a tag.
6a488035 297 *
a6c01b45
CW
298 * @return array
299 * array of entity ids
6a488035 300 */
00be9182 301 public function getEntitiesByTag($tag) {
07fc2fd3 302 $entityIds = array();
6a488035
TO
303 $entityTagDAO = new CRM_Core_DAO_EntityTag();
304 $entityTagDAO->tag_id = $tag->id;
305 $entityTagDAO->find();
306 while ($entityTagDAO->fetch()) {
07fc2fd3 307 $entityIds[] = $entityTagDAO->entity_id;
6a488035 308 }
07fc2fd3 309 return $entityIds;
6a488035
TO
310 }
311
312 /**
fe482240 313 * Get contact tags.
54957108 314 *
315 * @param int $contactID
316 * @param bool $count
317 *
318 * @return array
6a488035 319 */
00be9182 320 public static function getContactTags($contactID, $count = FALSE) {
6a488035
TO
321 $contactTags = array();
322 if (!$count) {
bff5783c 323 $select = "SELECT ct.id, ct.name ";
6a488035
TO
324 }
325 else {
326 $select = "SELECT count(*) as cnt";
327 }
328
8ef12e64 329 $query = "{$select}
330 FROM civicrm_tag ct
6a488035
TO
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
334 ct.is_tagset = 0 )";
335
336 $dao = CRM_Core_DAO::executeQuery($query);
337
338 if ($count) {
339 $dao->fetch();
340 return $dao->cnt;
341 }
342
343 while ($dao->fetch()) {
bff5783c 344 $contactTags[$dao->id] = $dao->name;
6a488035
TO
345 }
346
347 return $contactTags;
348 }
349
350 /**
fe482240 351 * Get child contact tags given parentId.
ea3ddccf 352 *
353 * @param int $parentId
354 * @param int $entityId
355 * @param string $entityTable
356 *
357 * @return array
6a488035 358 */
00be9182 359 public static function getChildEntityTags($parentId, $entityId, $entityTable = 'civicrm_contact') {
6a488035
TO
360 $entityTags = array();
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}";
365
366 $dao = CRM_Core_DAO::executeQuery($query);
367
368 while ($dao->fetch()) {
369 $entityTags[$dao->tag_id] = array(
370 'id' => $dao->tag_id,
371 'name' => $dao->name,
372 );
373 }
374
375 return $entityTags;
376 }
377
378 /**
100fef9d 379 * Merge two tags: tag B into tag A.
ea3ddccf 380 *
381 * @param int $tagAId
382 * @param int $tagBId
383 *
384 * @return array
6a488035 385 */
00be9182 386 public function mergeTags($tagAId, $tagBId) {
2aa397bc 387 $queryParams = array(
353ffa53 388 1 => array($tagBId, 'Integer'),
6a488035
TO
389 2 => array($tagAId, 'Integer'),
390 );
391
392 // re-compute used_for field
393 $query = "SELECT id, name, used_for FROM civicrm_tag WHERE id IN (%1, %2)";
353ffa53
TO
394 $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
395 $tags = array();
6a488035
TO
396 while ($dao->fetch()) {
397 $label = ($dao->id == $tagAId) ? 'tagA' : 'tagB';
398 $tags[$label] = $dao->name;
399 $tags["{$label}_used_for"] = $dao->used_for ? explode(",", $dao->used_for) : array();
400 }
401 $usedFor = array_merge($tags["tagA_used_for"], $tags["tagB_used_for"]);
402 $usedFor = implode(',', array_unique($usedFor));
403 $tags["tagB_used_for"] = explode(",", $usedFor);
404
405 // get all merge queries together
406 $sqls = array(
407 // 1. update entity tag entries
c2105be3 408 "UPDATE IGNORE civicrm_entity_tag SET tag_id = %1 WHERE tag_id = %2",
6a488035
TO
409 // 2. update used_for info for tag B
410 "UPDATE civicrm_tag SET used_for = '{$usedFor}' WHERE id = %1",
411 // 3. remove tag A, if tag A is getting merged into B
412 "DELETE FROM civicrm_tag WHERE id = %2",
413 // 4. remove duplicate entity tag records
414 "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",
c2105be3
BS
415 // 5. remove orphaned entity_tags
416 "DELETE FROM civicrm_entity_tag WHERE tag_id = %2",
6a488035
TO
417 );
418 $tables = array('civicrm_entity_tag', 'civicrm_tag');
419
420 // Allow hook_civicrm_merge() to add SQL statements for the merge operation AND / OR
421 // perform any other actions like logging
422 CRM_Utils_Hook::merge('sqls', $sqls, $tagAId, $tagBId, $tables);
423
424 // call the SQL queries in one transaction
425 $transaction = new CRM_Core_Transaction();
426 foreach ($sqls as $sql) {
427 CRM_Core_DAO::executeQuery($sql, $queryParams, TRUE, NULL, TRUE);
428 }
429 $transaction->commit();
430
431 $tags['status'] = TRUE;
432 return $tags;
433 }
76773c5a
CW
434
435 /**
436 * Get options for a given field.
8d7a9d07 437 *
76773c5a 438 * @see CRM_Core_DAO::buildOptions
8d7a9d07 439 * @see CRM_Core_DAO::buildOptionsContext
76773c5a 440 *
6a0b768e
TO
441 * @param string $fieldName
442 * @param string $context
8d7a9d07 443 * As per CRM_Core_DAO::buildOptionsContext.
6a0b768e 444 * @param array $props
16b10e64 445 * whatever is known about this dao object.
76773c5a 446 *
8d7a9d07 447 * @return array|bool
76773c5a
CW
448 */
449 public static function buildOptions($fieldName, $context = NULL, $props = array()) {
450 $params = array();
451
985f4890
CW
452 if ($fieldName == 'tag' || $fieldName == 'tag_id') {
453 if (!empty($props['entity_table'])) {
454 $entity = CRM_Utils_Type::escape($props['entity_table'], 'String');
455 $params[] = "used_for LIKE '%$entity%'";
456 }
76773c5a 457
985f4890
CW
458 // Output tag list as nested hierarchy
459 // TODO: This will only work when api.entity is "entity_tag". What about others?
460 if ($context == 'search' || $context == 'create') {
461 return CRM_Core_BAO_Tag::getTags(CRM_Utils_Array::value('entity_table', $props, 'civicrm_contact'), CRM_Core_DAO::$_nullArray, CRM_Utils_Array::value('parent_id', $params), '- ');
462 }
76773c5a
CW
463 }
464
985f4890
CW
465 $options = CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context);
466
76773c5a
CW
467 return $options;
468 }
96025800 469
6a488035 470}