trunk, branches/version-0.7:
[scuttle] / services / tagservice.php
1 <?php
2 class TagService {
3     var $db;
4     var $tablename;
5
6     function &getInstance(&$db) {
7         static $instance;
8         if (!isset($instance))
9             $instance =& new TagService($db);
10         return $instance;
11     }
12
13     function TagService(&$db) {
14         $this->db =& $db;
15         $this->tablename = $GLOBALS['tableprefix'] .'tags';
16     }
17
18     function isNotSystemTag($var) {
19         if (utf8_substr($var, 0, 7) == 'system:')
20             return false;
21         else
22             return true;
23     }
24
25     function attachTags($bookmarkid, $tags, $fromApi = false, $extension = NULL, $replace = true, $fromImport = false) {
26         // Make sure that categories is an array of trimmed strings, and that if the categories are
27         // coming in from an API call to add a bookmark, that underscores are converted into strings.
28         if (!is_array($tags)) {
29             $tags = trim($tags);
30             if ($tags != '') {
31                 if (substr($tags, -1) == ',') {
32                     $tags = substr($tags, 0, -1);
33                 }
34                 if ($fromApi) {
35                     $tags = explode(' ', $tags);
36                 } else {
37                     $tags = explode(',', $tags);
38                 }
39             } else {
40                 $tags = null;
41             }
42         }
43
44         $tags_count = count($tags);
45         for ($i = 0; $i < $tags_count; $i++) {
46             $tags[$i] = trim(strtolower($tags[$i]));
47             if ($fromApi) {
48                 include_once(dirname(__FILE__) .'/../functions.inc.php');
49                 $tags[$i] = convertTag($tags[$i], 'in');
50             }
51         }
52
53         if ($tags_count > 0) {
54             // Remove system tags
55             $tags = array_filter($tags, array($this, "isNotSystemTag"));
56
57             // Eliminate any duplicate categories
58             $temp = array_unique($tags);
59             $tags = array_values($temp);
60         } else {
61             // Unfiled
62             $tags[] = 'system:unfiled';
63         }
64
65         // Media and file types
66         if (!is_null($extension)) {
67             include_once(dirname(__FILE__) .'/../functions.inc.php');
68             if ($keys = multi_array_search($extension, $GLOBALS['filetypes'])) {
69                 $tags[] = 'system:filetype:'. $extension;
70                 $tags[] = 'system:media:'. array_shift($keys);
71             }
72         }
73
74         // Imported
75         if ($fromImport) {
76             $tags[] = 'system:imported';
77         }
78
79         $this->db->sql_transaction('begin');
80
81         if ($replace) {
82             if (!$this->deleteTagsForBookmark($bookmarkid)){
83                 $this->db->sql_transaction('rollback');
84                 message_die(GENERAL_ERROR, 'Could not attach tags (deleting old ones failed)', '', __LINE__, __FILE__, $sql, $this->db);
85                 return false;
86             }
87         }
88
89         // Add the categories to the DB.
90         for ($i = 0; $i < count($tags); $i++) {
91             if ($tags[$i] != '') {
92                 $values = array(
93                     'bId' => intval($bookmarkid),
94                     'tag' => $tags[$i]
95                 );
96
97                 if (!$this->hasTag($bookmarkid, $tags[$i])) {
98                     $sql = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values);
99                     if (!($dbresult =& $this->db->sql_query($sql))) {
100                         $this->db->sql_transaction('rollback');
101                         message_die(GENERAL_ERROR, 'Could not attach tags', '', __LINE__, __FILE__, $sql, $this->db);
102                         return false;
103                     }
104                 }
105             }
106         }
107         $this->db->sql_transaction('commit');
108         return true;    
109     } 
110     
111     function deleteTag($tag) {
112         $userservice =& ServiceFactory::getServiceInstance('UserService');
113         $logged_on_user = $userservice->getCurrentUserId();
114
115         $query = 'DELETE FROM '. $this->getTableName() .' USING '. $GLOBALS['tableprefix'] .'tags, '. $GLOBALS['tableprefix'] .'bookmarks WHERE '. $GLOBALS['tableprefix'] .'tags.bId = '. $GLOBALS['tableprefix'] .'bookmarks.bId AND '. $GLOBALS['tableprefix'] .'bookmarks.uId = '. $logged_on_user .' AND '. $GLOBALS['tableprefix'] .'tags.tag = "'. $this->db->sql_escape($tag) .'"';
116
117         if (!($dbresult =& $this->db->sql_query($query))) {
118             message_die(GENERAL_ERROR, 'Could not delete tags', '', __LINE__, __FILE__, $query, $this->db);
119             return false;
120         }
121
122         return true;
123     }
124     
125     function deleteTagsForBookmark($bookmarkid) {
126         if (!is_int($bookmarkid)) {
127             message_die(GENERAL_ERROR, 'Could not delete tags (invalid bookmarkid)', '', __LINE__, __FILE__, $query);
128             return false;
129         }
130
131         $query = 'DELETE FROM '. $this->getTableName() .' WHERE bId = '. intval($bookmarkid);
132
133         if (!($dbresult =& $this->db->sql_query($query))) {
134             message_die(GENERAL_ERROR, 'Could not delete tags', '', __LINE__, __FILE__, $query, $this->db);
135             return false;
136         }
137
138         return true;
139     }
140
141     function &getTagsForBookmark($bookmarkid) {
142         if (!is_int($bookmarkid)) {
143             message_die(GENERAL_ERROR, 'Could not get tags (invalid bookmarkid)', '', __LINE__, __FILE__, $query);
144             return false;
145         }
146
147         $query = 'SELECT tag FROM '. $this->getTableName() .' WHERE bId = '. intval($bookmarkid) .' AND LEFT(tag, 7) <> "system:" ORDER BY tag';
148
149         if (!($dbresult =& $this->db->sql_query($query))) {
150             message_die(GENERAL_ERROR, 'Could not get tags', '', __LINE__, __FILE__, $query, $this->db);
151             return false;
152         }
153
154         $tags = array();
155         while ($row =& $this->db->sql_fetchrow($dbresult)) {
156             $tags[] = $row['tag'];
157         }
158
159         return $tags;
160     }
161
162     function &getTags($userid = NULL) {
163         $userservice =& ServiceFactory::getServiceInstance('UserService');
164         $logged_on_user = $userservice->getCurrentUserId();
165
166         $query = 'SELECT T.tag, COUNT(B.bId) AS bCount FROM '. $GLOBALS['tableprefix'] .'bookmarks AS B INNER JOIN '. $userservice->getTableName() .' AS U ON B.uId = U.'. $userservice->getFieldName('primary') .' INNER JOIN '. $GLOBALS['tableprefix'] .'tags AS T ON B.bId = T.bId';
167
168         $conditions = array();
169         if (!is_null($userid)) {
170             $conditions['U.'. $userservice->getFieldName('primary')] = intval($userid);
171             if ($logged_on_user != $userid)
172                 $conditions['B.bStatus'] = 0;
173         } else {
174             $conditions['B.bStatus'] = 0;
175         }
176
177         $query .= ' WHERE '. $this->db->sql_build_array('SELECT', $conditions) .' AND LEFT(T.tag, 7) <> "system:" GROUP BY T.tag ORDER BY bCount DESC, tag';
178
179         if (!($dbresult =& $this->db->sql_query($query))) {
180             message_die(GENERAL_ERROR, 'Could not get tags', '', __LINE__, __FILE__, $query, $this->db);
181             return false;
182         }
183         return $this->db->sql_fetchrowset($dbresult);
184     }
185     
186   
187     // Returns the tags related to the specified tags; i.e. attached to the same bookmarks
188     function &getRelatedTags($tags, $for_user = NULL, $logged_on_user = NULL, $limit = 10) {
189         $conditions = array();
190         // Only count the tags that are visible to the current user.
191         if ($for_user != $logged_on_user || is_null($for_user))
192             $conditions['B.bStatus'] = 0;
193
194         if (!is_null($for_user))
195             $conditions['B.uId'] = $for_user;
196
197         // Set up the tags, if need be.
198         if (is_numeric($tags))
199             $tags = NULL;
200         if (!is_array($tags) and !is_null($tags))
201             $tags = explode('+', trim($tags));
202
203         $tagcount = count($tags);
204         for ($i = 0; $i < $tagcount; $i++) {
205             $tags[$i] = trim($tags[$i]);
206         }
207
208         // Set up the SQL query.
209         $query_1 = 'SELECT DISTINCTROW T0.tag, COUNT(B.bId) AS bCount FROM '. $GLOBALS['tableprefix'] .'bookmarks AS B, '. $this->getTableName() .' AS T0';
210         $query_2 = '';
211         $query_3 = ' WHERE B.bId = T0.bId ';
212         if (count($conditions) > 0)
213             $query_4 = ' AND '. $this->db->sql_build_array('SELECT', $conditions);
214         else
215             $query_4 = '';
216         // Handle the parts of the query that depend on any tags that are present.
217         for ($i = 1; $i <= $tagcount; $i++) {
218             $query_2 .= ', '. $this->getTableName() .' AS T'. $i;
219             $query_4 .= ' AND T'. $i .'.bId = B.bId AND T'. $i .'.tag = "'. $this->db->sql_escape($tags[$i - 1]) .'" AND T0.tag <> "'. $this->db->sql_escape($tags[$i - 1]) .'"';
220         }
221         $query_5 = ' AND LEFT(T0.tag, 7) <> "system:" GROUP BY T0.tag ORDER BY bCount DESC, T0.tag';
222         $query = $query_1 . $query_2 . $query_3 . $query_4 . $query_5;
223
224         if (! ($dbresult =& $this->db->sql_query_limit($query, $limit)) ){
225             message_die(GENERAL_ERROR, 'Could not get related tags', '', __LINE__, __FILE__, $query, $this->db);
226             return false;
227         }
228         return $this->db->sql_fetchrowset($dbresult);
229     }
230
231     // Returns the most popular tags used for a particular bookmark hash
232     function &getRelatedTagsByHash($hash, $limit = 20) {
233         $userservice = & ServiceFactory :: getServiceInstance('UserService');
234         $sId = $userservice->getCurrentUserId();
235         // Logged in
236         if ($userservice->isLoggedOn()) {
237             $arrWatch = $userservice->getWatchList($sId);
238             // From public bookmarks or user's own
239             $privacy = ' AND ((B.bStatus = 0) OR (B.uId = '. $sId .')';
240             // From shared bookmarks in watchlist
241             foreach ($arrWatch as $w) {
242                 $privacy .= ' OR (B.uId = '. $w .' AND B.bStatus = 1)';
243             }
244             $privacy .= ') ';
245         // Not logged in
246         } else {
247             $privacy = ' AND B.bStatus = 0 ';
248         }
249
250         $query = 'SELECT T.tag, COUNT(T.tag) AS bCount FROM sc_bookmarks AS B LEFT JOIN sc_tags AS T ON B.bId = T.bId WHERE B.bHash = "'. $hash .'" '. $privacy .'AND LEFT(T.tag, 7) <> "system:" GROUP BY T.tag ORDER BY bCount DESC';
251
252         if (!($dbresult =& $this->db->sql_query_limit($query, $limit))) {
253             message_die(GENERAL_ERROR, 'Could not get related tags for this hash', '', __LINE__, __FILE__, $query, $this->db);
254             return false;
255         }
256         return $this->db->sql_fetchrowset($dbresult);
257     }
258
259     function &getPopularTags($user = NULL, $limit = 30, $logged_on_user = NULL, $days = NULL) {
260         // Only count the tags that are visible to the current user.
261         if (($user != $logged_on_user) || is_null($user) || ($user === false))
262             $privacy = ' AND B.bStatus = 0';
263         else
264             $privacy = '';
265
266         if (is_null($days) || !is_int($days))
267             $span = '';
268         else
269             $span = ' AND B.bDatetime > "'. date('Y-m-d H:i:s', time() - (86400 * $days)) .'"';
270
271         $query = 'SELECT T.tag, COUNT(T.bId) AS bCount FROM '. $this->getTableName() .' AS T, '. $GLOBALS['tableprefix'] .'bookmarks AS B WHERE ';
272         if (is_null($user) || ($user === false)) {
273             $query .= 'B.bId = T.bId AND B.bStatus = 0';
274         } else {
275             $query .= 'B.uId = '. $this->db->sql_escape($user) .' AND B.bId = T.bId'. $privacy;
276         }
277         $query .= $span .' AND LEFT(T.tag, 7) <> "system:" GROUP BY T.tag ORDER BY bCount DESC, tag';
278
279         if (!($dbresult =& $this->db->sql_query_limit($query, $limit))) {
280             message_die(GENERAL_ERROR, 'Could not get popular tags', '', __LINE__, __FILE__, $query, $this->db);
281             return false;
282         }
283
284         return $this->db->sql_fetchrowset($dbresult);
285     }
286
287     function hasTag($bookmarkid, $tag) {
288         $query = 'SELECT COUNT(*) AS tCount FROM '. $this->getTableName() .' WHERE bId = '. intval($bookmarkid) .' AND tag ="'. $this->db->sql_escape($tag) .'"';
289
290         if (! ($dbresult =& $this->db->sql_query($query)) ) {
291             message_die(GENERAL_ERROR, 'Could not find tag', '', __LINE__, __FILE__, $query, $this->db);
292             return false;
293         }
294         
295         if ($row =& $this->db->sql_fetchrow($dbresult)) {
296             if ($row['tCount'] > 0) {
297                 return true;
298             }
299         }
300         return false;
301     }
302
303     function renameTag($userid, $old, $new, $fromApi = false) {
304         $bookmarkservice =& ServiceFactory::getServiceInstance('BookmarkService');
305
306         if (is_null($userid) || is_null($old) || is_null($new))
307             return false;
308
309         // Find bookmarks with old tag
310         $bookmarksInfo =& $bookmarkservice->getBookmarks(0, NULL, $userid, $old);
311         $bookmarks =& $bookmarksInfo['bookmarks'];
312
313         // Delete old tag
314         $this->deleteTag($old);
315
316         // Attach new tags
317         foreach(array_keys($bookmarks) as $key) {
318             $row =& $bookmarks[$key];
319             $this->attachTags($row['bId'], $new, $fromApi, NULL, false);
320         }
321
322         return true;
323     }
324
325     function &tagCloud($tags = NULL, $steps = 5, $sizemin = 90, $sizemax = 225, $sortOrder = NULL) {
326
327         if (is_null($tags) || count($tags) < 1) {
328             return false;
329         }
330
331         $min = $tags[count($tags) - 1]['bCount'];
332         $max = $tags[0]['bCount'];
333
334         for ($i = 1; $i <= $steps; $i++) {
335             $delta = ($max - $min) / (2 * $steps - $i);
336             $limit[$i] = $i * $delta + $min;
337         }
338         $sizestep = ($sizemax - $sizemin) / $steps;
339         foreach ($tags as $row) {
340             $next = false;
341             for ($i = 1; $i <= $steps; $i++) {
342                 if (!$next && $row['bCount'] <= $limit[$i]) {
343                     $size = $sizestep * ($i - 1) + $sizemin;
344                     $next = true;
345                 }
346             }
347             $tempArray = array('size' => $size .'%');
348             $row = array_merge($row, $tempArray); 
349             $output[] = $row;
350         }
351
352         if ($sortOrder == 'alphabet_asc') {
353             usort($output, create_function('$a,$b','return strcasecmp(utf8_deaccent($a["tag"]), utf8_deaccent($b["tag"]));'));
354         }
355
356         return $output;
357     }
358
359     // Properties
360     function getTableName()       { return $this->tablename; }
361     function setTableName($value) { $this->tablename = $value; }
362 }
363 ?>

Benjamin Mako Hill || Want to submit a patch?