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