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