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

Benjamin Mako Hill || Want to submit a patch?