9745cb3778179578f53a1f1d190c0b4a99f3277d
[civicrm-core.git] / CRM / Core / BAO / EntityTag.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * This class contains functions for managing Tag(tag) for a contact
30 *
31 * @package CRM
32 * @copyright CiviCRM LLC (c) 2004-2015
33 */
34 class CRM_Core_BAO_EntityTag extends CRM_Core_DAO_EntityTag {
35
36 /**
37 * Given a contact id, it returns an array of tag id's the contact belongs to.
38 *
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'.
43 *
44 * @return array
45 * reference $tag array of category id's the contact belongs to.
46 */
47 public static function &getTag($entityID, $entityTable = 'civicrm_contact') {
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 /**
62 * Takes an associative array and creates a entityTag object.
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 *
68 * @param array $params
69 * (reference ) an assoc array of name/value pairs.
70 *
71 * @return CRM_Core_BAO_EntityTag
72 */
73 public static function add(&$params) {
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 /**
96 * Check if there is data to create the object.
97 *
98 * @param array $params
99 * An assoc array of name/value pairs.
100 *
101 * @return bool
102 */
103 public static function dataExists($params) {
104 return !($params['tag_id'] == 0);
105 }
106
107 /**
108 * Delete the tag for a contact.
109 *
110 * @param array $params
111 * (reference ) an assoc array of name/value pairs.
112 */
113 public static function del(&$params) {
114 $entityTag = new CRM_Core_BAO_EntityTag();
115 $entityTag->copyValues($params);
116 $entityTag->delete();
117
118 //invoke post hook on entityTag
119 $object = array(0 => array(0 => $params['entity_id']), 1 => $params['entity_table']);
120 CRM_Utils_Hook::post('delete', 'EntityTag', $params['tag_id'], $object);
121 }
122
123 /**
124 * Given an array of entity ids and entity table, add all the entity to the tags
125 *
126 * @param array $entityIds
127 * (reference ) the array of entity ids to be added.
128 * @param int $tagId
129 * The id of the tag.
130 * @param string $entityTable
131 * Name of entity table default:civicrm_contact.
132 *
133 * @return array
134 * (total, added, notAdded) count of enities added to tag
135 */
136 public static function addEntitiesToTag(&$entityIds, $tagId, $entityTable = 'civicrm_contact') {
137 $numEntitiesAdded = 0;
138 $numEntitiesNotAdded = 0;
139 $entityIdsAdded = array();
140
141 foreach ($entityIds as $entityId) {
142 $tag = new CRM_Core_DAO_EntityTag();
143
144 $tag->entity_id = $entityId;
145 $tag->tag_id = $tagId;
146 $tag->entity_table = $entityTable;
147 if (!$tag->find()) {
148 $tag->save();
149 $entityIdsAdded[] = $entityId;
150 $numEntitiesAdded++;
151 }
152 else {
153 $numEntitiesNotAdded++;
154 }
155 }
156
157 //invoke post hook on entityTag
158 $object = array($entityIdsAdded, $entityTable);
159 CRM_Utils_Hook::post('create', 'EntityTag', $tagId, $object);
160
161 // reset the group contact cache for all groups
162 // if tags are being used in a smart group
163 CRM_Contact_BAO_GroupContactCache::remove();
164
165 return array(count($entityIds), $numEntitiesAdded, $numEntitiesNotAdded);
166 }
167
168 /**
169 * Given an array of entity ids and entity table, remove entity(s) tags
170 *
171 * @param array $entityIds
172 * (reference ) the array of entity ids to be removed.
173 * @param int $tagId
174 * The id of the tag.
175 * @param string $entityTable
176 * Name of entity table default:civicrm_contact.
177 *
178 * @return array
179 * (total, removed, notRemoved) count of entities removed from tags
180 */
181 public static function removeEntitiesFromTag(&$entityIds, $tagId, $entityTable = 'civicrm_contact') {
182 $numEntitiesRemoved = 0;
183 $numEntitiesNotRemoved = 0;
184 $entityIdsRemoved = array();
185
186 foreach ($entityIds as $entityId) {
187 $tag = new CRM_Core_DAO_EntityTag();
188
189 $tag->entity_id = $entityId;
190 $tag->tag_id = $tagId;
191 $tag->entity_table = $entityTable;
192 if ($tag->find()) {
193 $tag->delete();
194 $entityIdsRemoved[] = $entityId;
195 $numEntitiesRemoved++;
196 }
197 else {
198 $numEntitiesNotRemoved++;
199 }
200 }
201
202 //invoke post hook on entityTag
203 $object = array($entityIdsRemoved, $entityTable);
204 CRM_Utils_Hook::post('delete', 'EntityTag', $tagId, $object);
205
206 // reset the group contact cache for all groups
207 // if tags are being used in a smart group
208 CRM_Contact_BAO_GroupContactCache::remove();
209
210 return array(count($entityIds), $numEntitiesRemoved, $numEntitiesNotRemoved);
211 }
212
213 /**
214 * Takes an associative array and creates tag entity record for all tag entities.
215 *
216 * @param array $params
217 * (reference) an assoc array of name/value pairs.
218 * @param string $entityTable
219 * @param int $entityID
220 */
221 public static function create(&$params, $entityTable, $entityID) {
222 // get categories for the entity id
223 $entityTag = CRM_Core_BAO_EntityTag::getTag($entityID, $entityTable);
224
225 // get the list of all the categories
226 $allTag = CRM_Core_BAO_Tag::getTags($entityTable);
227
228 // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input
229 if (!is_array($params)) {
230 $params = array();
231 }
232
233 // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input
234 if (!is_array($entityTag)) {
235 $entityTag = array();
236 }
237
238 // check which values has to be inserted/deleted for contact
239 foreach ($allTag as $key => $varValue) {
240 $tagParams['entity_table'] = $entityTable;
241 $tagParams['entity_id'] = $entityID;
242 $tagParams['tag_id'] = $key;
243
244 if (array_key_exists($key, $params) && !array_key_exists($key, $entityTag)) {
245 // insert a new record
246 CRM_Core_BAO_EntityTag::add($tagParams);
247 }
248 elseif (!array_key_exists($key, $params) && array_key_exists($key, $entityTag)) {
249 // delete a record for existing contact
250 CRM_Core_BAO_EntityTag::del($tagParams);
251 }
252 }
253 }
254
255 /**
256 * This function returns all entities assigned to a specific tag.
257 *
258 * @param object $tag
259 * An object of a tag.
260 *
261 * @return array
262 * array of entity ids
263 */
264 public function getEntitiesByTag($tag) {
265 $entityIds = array();
266 $entityTagDAO = new CRM_Core_DAO_EntityTag();
267 $entityTagDAO->tag_id = $tag->id;
268 $entityTagDAO->find();
269 while ($entityTagDAO->fetch()) {
270 $entityIds[] = $entityTagDAO->entity_id;
271 }
272 return $entityIds;
273 }
274
275 /**
276 * Get contact tags.
277 */
278 public static function getContactTags($contactID, $count = FALSE) {
279 $contactTags = array();
280 if (!$count) {
281 $select = "SELECT name ";
282 }
283 else {
284 $select = "SELECT count(*) as cnt";
285 }
286
287 $query = "{$select}
288 FROM civicrm_tag ct
289 INNER JOIN civicrm_entity_tag et ON ( ct.id = et.tag_id AND
290 et.entity_id = {$contactID} AND
291 et.entity_table = 'civicrm_contact' AND
292 ct.is_tagset = 0 )";
293
294 $dao = CRM_Core_DAO::executeQuery($query);
295
296 if ($count) {
297 $dao->fetch();
298 return $dao->cnt;
299 }
300
301 while ($dao->fetch()) {
302 $contactTags[] = $dao->name;
303 }
304
305 return $contactTags;
306 }
307
308 /**
309 * Get child contact tags given parentId.
310 */
311 public static function getChildEntityTags($parentId, $entityId, $entityTable = 'civicrm_contact') {
312 $entityTags = array();
313 $query = "SELECT ct.id as tag_id, name FROM civicrm_tag ct
314 INNER JOIN civicrm_entity_tag et ON ( et.entity_id = {$entityId} AND
315 et.entity_table = '{$entityTable}' AND et.tag_id = ct.id)
316 WHERE ct.parent_id = {$parentId}";
317
318 $dao = CRM_Core_DAO::executeQuery($query);
319
320 while ($dao->fetch()) {
321 $entityTags[$dao->tag_id] = array(
322 'id' => $dao->tag_id,
323 'name' => $dao->name,
324 );
325 }
326
327 return $entityTags;
328 }
329
330 /**
331 * Merge two tags: tag B into tag A.
332 */
333 public function mergeTags($tagAId, $tagBId) {
334 $queryParams = array(
335 1 => array($tagBId, 'Integer'),
336 2 => array($tagAId, 'Integer'),
337 );
338
339 // re-compute used_for field
340 $query = "SELECT id, name, used_for FROM civicrm_tag WHERE id IN (%1, %2)";
341 $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
342 $tags = array();
343 while ($dao->fetch()) {
344 $label = ($dao->id == $tagAId) ? 'tagA' : 'tagB';
345 $tags[$label] = $dao->name;
346 $tags["{$label}_used_for"] = $dao->used_for ? explode(",", $dao->used_for) : array();
347 }
348 $usedFor = array_merge($tags["tagA_used_for"], $tags["tagB_used_for"]);
349 $usedFor = implode(',', array_unique($usedFor));
350 $tags["tagB_used_for"] = explode(",", $usedFor);
351
352 // get all merge queries together
353 $sqls = array(
354 // 1. update entity tag entries
355 "UPDATE IGNORE civicrm_entity_tag SET tag_id = %1 WHERE tag_id = %2",
356 // 2. update used_for info for tag B
357 "UPDATE civicrm_tag SET used_for = '{$usedFor}' WHERE id = %1",
358 // 3. remove tag A, if tag A is getting merged into B
359 "DELETE FROM civicrm_tag WHERE id = %2",
360 // 4. remove duplicate entity tag records
361 "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",
362 // 5. remove orphaned entity_tags
363 "DELETE FROM civicrm_entity_tag WHERE tag_id = %2",
364 );
365 $tables = array('civicrm_entity_tag', 'civicrm_tag');
366
367 // Allow hook_civicrm_merge() to add SQL statements for the merge operation AND / OR
368 // perform any other actions like logging
369 CRM_Utils_Hook::merge('sqls', $sqls, $tagAId, $tagBId, $tables);
370
371 // call the SQL queries in one transaction
372 $transaction = new CRM_Core_Transaction();
373 foreach ($sqls as $sql) {
374 CRM_Core_DAO::executeQuery($sql, $queryParams, TRUE, NULL, TRUE);
375 }
376 $transaction->commit();
377
378 $tags['status'] = TRUE;
379 return $tags;
380 }
381
382 /**
383 * Get options for a given field.
384 *
385 * @see CRM_Core_DAO::buildOptions
386 * @see CRM_Core_DAO::buildOptionsContext
387 *
388 * @param string $fieldName
389 * @param string $context
390 * As per CRM_Core_DAO::buildOptionsContext.
391 * @param array $props
392 * whatever is known about this dao object.
393 *
394 * @return array|bool
395 */
396 public static function buildOptions($fieldName, $context = NULL, $props = array()) {
397 $params = array();
398
399 if ($fieldName == 'tag' || $fieldName == 'tag_id') {
400 if (!empty($props['entity_table'])) {
401 $entity = CRM_Utils_Type::escape($props['entity_table'], 'String');
402 $params[] = "used_for LIKE '%$entity%'";
403 }
404
405 // Output tag list as nested hierarchy
406 // TODO: This will only work when api.entity is "entity_tag". What about others?
407 if ($context == 'search' || $context == 'create') {
408 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), '- ');
409 }
410 }
411
412 $options = CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context);
413
414 return $options;
415 }
416
417 }