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