1 |
<?php |
2 |
// vim: ts=4 foldcolumn=4 foldmethod=marker |
3 |
/** |
4 |
* RF_Controller class found here. |
5 |
* |
6 |
* This file is part of Reblog, |
7 |
* a derivative work of Feed On Feeds. |
8 |
* |
9 |
* Distributed under the Gnu Public License. |
10 |
* |
11 |
* @package Refeed |
12 |
* @license http://opensource.org/licenses/gpl-license.php GNU Public License |
13 |
* @author Michal Migurski <mike@stamen.com> |
14 |
* @author Michael Frumin <mfrumin@eyebeam.org> |
15 |
* @copyright ©2004 Michael Frumin, Michal Migurski |
16 |
* @link http://reblog.org Reblog |
17 |
* @link http://feedonfeeds.com Feed On Feeds |
18 |
* @version $Revision: 1.41 $ |
19 |
*/ |
20 |
|
21 |
/** |
22 |
* RF_Controller primarily handles the database connection and most |
23 |
* database interaction, and mediates connections between users and data. |
24 |
* |
25 |
* RF_Controller does most of the heavy-lifting, including: |
26 |
* - Keeping of database and output interfaces |
27 |
* - Linking users and user information, e.g. feed subscriptions |
28 |
* - Retrieving feed and item model objects |
29 |
* - Adding and saving feed and item model objects to the database |
30 |
* - Checking for feed or item existence when processing new information |
31 |
* - Modifying and retrieving per-user metadata for items and feeds |
32 |
* - Keeping and invoking plugins |
33 |
* |
34 |
* Other controller classes provide |
35 |
* {@link RF_Input_Controller RSS subscription code}, |
36 |
* {@link RF_Install_Controller installation procedures}, |
37 |
* and {@link RF_Userdata_Controller higher-level feed & item per-user metadata access}. |
38 |
*/ |
39 |
class RF_Controller |
40 |
{ |
41 |
/** |
42 |
* Database connection for reading |
43 |
* @var DB_common |
44 |
*/ |
45 |
var $dbhr; |
46 |
|
47 |
/** |
48 |
* Database connection for writing |
49 |
* @var DB_common |
50 |
*/ |
51 |
var $dbhw; |
52 |
|
53 |
/** |
54 |
* Flag determines whether transactions are supported in the current database or not. |
55 |
* @var boolean |
56 |
*/ |
57 |
var $db_transactions = false; |
58 |
|
59 |
/** |
60 |
* Array of plug-ins object instances. |
61 |
* @var array |
62 |
*/ |
63 |
var $plugins = array(); |
64 |
|
65 |
/** |
66 |
* Flag determines whether to attempt to close database instances on request completion. |
67 |
* @var boolean |
68 |
*/ |
69 |
var $no_dbh_disconnect = false; |
70 |
|
71 |
/** |
72 |
* @param DB_common $dbhr Database connection for reading |
73 |
* @param DB_common $dbhw Database connection for writing |
74 |
* @param array $args Optional arguments array |
75 |
* |
76 |
* @uses RF_Page::$controller Assigned after instantiation |
77 |
* @uses RF_Controller::$no_dbh_disconnect Assigned based on arguments, defaults to false |
78 |
* @uses RF_Controller::$dbhr Assigned on instantiation, from {@link DB_common $dbh}. |
79 |
* @uses RF_Controller::$dbhw Assigned on instantiation, from {@link DB_common $dbh}. |
80 |
* @uses RF_Controller::$db_transactions Assigned on instantiation, if database is tested to support START TRANSACTION. |
81 |
* @uses RF_Controller::finishRequest() Designated a shutdown function. |
82 |
*/ |
83 |
function RF_Controller(&$dbhr, &$dbhw, $args = array()) |
84 |
{ |
85 |
// database handler; assuming to behave like PEAR::DB_common |
86 |
$this->dbhr =& $dbhr; |
87 |
$this->dbhw =& $dbhw; |
88 |
|
89 |
$this->loadPlugins(); |
90 |
register_shutdown_function(array(&$this, 'finishRequest')); |
91 |
|
92 |
if(strtolower(get_class($this->dbhw)) == 'db_mysql_logged') { |
93 |
|
94 |
// don't use transactions when logging queries |
95 |
$this->db_transactions = false; |
96 |
|
97 |
} else { |
98 |
|
99 |
// determine whether transactions are aupported by current MySQL |
100 |
$result = $this->dbhw->query("START TRANSACTION"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
101 |
|
102 |
$this->db_transactions = (DB::isError($result) && $result->getCode() == DB_ERROR_SYNTAX) |
103 |
? false |
104 |
: true; |
105 |
|
106 |
// just in case... |
107 |
$this->dbhw->query("COMMIT"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
108 |
|
109 |
} |
110 |
|
111 |
$this->no_dbh_disconnect = empty($args['no_dbh_disconnect']) ? false : true; |
112 |
} |
113 |
|
114 |
/** |
115 |
* As a result of register_shutdown_function(), called at the end of a request. |
116 |
* |
117 |
* @uses RF_Controller::invokePlugin(), "finishedRequest" event. |
118 |
* @uses RF_Controller::$no_dbh_disconnect If not true, don't disconnect databas instances. |
119 |
* @uses RF_Controller::$dbhr Disconnected |
120 |
* @uses RF_Controller::$dbhw Disconnected after a commit of any open transactions |
121 |
*/ |
122 |
function finishRequest() |
123 |
{ |
124 |
$this->invokePlugin('finishedRequest'); |
125 |
|
126 |
// just in case... |
127 |
if($this->db_transactions) |
128 |
$this->dbhw->query("COMMIT"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
129 |
|
130 |
if(!$this->no_dbh_disconnect) { |
131 |
$this->dbhw->disconnect(); |
132 |
$this->dbhr->disconnect(); |
133 |
} |
134 |
|
135 |
} |
136 |
|
137 |
/** |
138 |
* Irrevokably deletes a feed from the database. |
139 |
* |
140 |
* @param RF_Feed $feed Feed to delete |
141 |
* |
142 |
* @uses RF_Controller::getReadHandle() |
143 |
* @uses RF_Controller::writeToDatabase() |
144 |
* @uses RF_Controller::invokePlugin() "feedDeleted" event, |
145 |
* parameters: {@link RF_Feed $feed}. |
146 |
*/ |
147 |
function deleteFeed(&$feed) |
148 |
{ |
149 |
$dbhr =& $this->getReadHandle(); |
150 |
|
151 |
$query = sprintf("DELETE FROM %s |
152 |
WHERE id = %d", |
153 |
$dbhr->quoteIdentifier($feed->tableName()), |
154 |
$feed->getID()); |
155 |
|
156 |
$this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
157 |
|
158 |
$query = sprintf("DELETE FROM %s |
159 |
WHERE feed_id = %d", |
160 |
$dbhr->quoteIdentifier(RF_Item::tableName()), |
161 |
$feed->getID()); |
162 |
|
163 |
$this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
164 |
|
165 |
$this->invokePlugin('feedDeleted', array(&$feed)); |
166 |
} |
167 |
|
168 |
/** |
169 |
* Unsubscribes user from a feed. |
170 |
* |
171 |
* Removes all ties between a user and a feed. Only deletes the |
172 |
* feed itself if there are no remaining subscribers to the feed. |
173 |
* |
174 |
* @param RF_User $user User to unsubscribe |
175 |
* @param RF_Feed $feed Feed to unsubscribe |
176 |
* |
177 |
* @uses RF_Controller::getReadHandle() |
178 |
* @uses RF_Feed::userdataTableName() |
179 |
* @uses RF_Controller::writeToDatabase() |
180 |
* @uses RF_Controller::getUserItems() |
181 |
* @uses RF_Item::userdataTableName() |
182 |
* @uses RF_Controller::getFeedUsers() |
183 |
* @uses RF_Controller::deleteFeed() |
184 |
* @uses RF_Controller::invokePlugin() "unsubscribedUserFromFeed" event, |
185 |
* parameters: {@link RF_User $user}, {@link RF_Feed $feed}. |
186 |
*/ |
187 |
function unsubscribeUserFromFeed(&$user, &$feed) |
188 |
{ |
189 |
$dbhr =& $this->getReadHandle(); |
190 |
|
191 |
$query = sprintf("DELETE FROM %s |
192 |
WHERE feed_id = %d |
193 |
AND user_id = %d", |
194 |
$dbhr->quoteIdentifier($feed->userdataTableName()), |
195 |
$feed->getID(), |
196 |
$user->getID()); |
197 |
|
198 |
$this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
199 |
|
200 |
$item_ids = array(); |
201 |
|
202 |
foreach($this->getUserItems($user, array('feed' => $feed->getID())) as $item) |
203 |
$item_ids[] = $item->getID(); |
204 |
|
205 |
if(count($item_ids) > 0) { |
206 |
|
207 |
$query = sprintf("DELETE FROM %s |
208 |
WHERE user_id = %d |
209 |
AND item_id IN (%s)", |
210 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
211 |
$user->getID(), |
212 |
join(', ', $item_ids)); |
213 |
|
214 |
$this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
215 |
} |
216 |
|
217 |
$this->invokePlugin('unsubscribedUserFromFeed', array(&$user, &$feed)); |
218 |
|
219 |
if(count($this->getFeedUsers($feed)) == 0) |
220 |
$this->deleteFeed($feed); |
221 |
} |
222 |
|
223 |
/** |
224 |
* Subscribe a user to a feed. |
225 |
* |
226 |
* @param RF_User $user User to subscribe |
227 |
* @param RF_Feed $feed Feed to subscribe |
228 |
* |
229 |
* @uses RF_Controller::setFeedUserdata() |
230 |
* @uses RF_Controller::invokePlugin() "subscribedUserToFeed" event, |
231 |
* parameters: {@link RF_User $user}, {@link RF_Feed $feed}. |
232 |
*/ |
233 |
function subscribeUserToFeed(&$user, &$feed) |
234 |
{ |
235 |
$result = $this->setFeedUserdata($feed, $user, 'subscribed', 1, 'numeric'); |
236 |
|
237 |
$this->invokePlugin('subscribedUserToFeed', array(&$user, &$feed)); |
238 |
return $result; |
239 |
} |
240 |
|
241 |
/** |
242 |
* Check whether a user is subscribed to a feed. |
243 |
* |
244 |
* @param RF_User $user User to check |
245 |
* @param RF_Feed $feed Feed to check |
246 |
* @return integer 1 if subscribed, 0 if not |
247 |
* |
248 |
* @uses RF_Controller::userSubscribedToFeed() |
249 |
*/ |
250 |
function userSubscribedToFeed(&$user, &$feed) |
251 |
{ |
252 |
return end($this->getFeedUserdata($feed, $user, 'subscribed', 'numeric')); |
253 |
} |
254 |
|
255 |
/** |
256 |
* Get a list of feed subscribers. |
257 |
* |
258 |
* @param RF_Feed $feed Feed to check |
259 |
* @return array Array of {@link RF_User users} |
260 |
* |
261 |
* @uses RF_Controller::getReadHandle() |
262 |
* @uses RF_Feed::userdataTableName() |
263 |
* @uses RF_Controller::readFromDatabase() |
264 |
* @uses RF_User::RF_User() |
265 |
* @uses RF_Controller::invokePlugin() "gotFeedUsers" event, |
266 |
* parameters: {@link RF_Feed $feed}, {@link RF_User $users}. |
267 |
*/ |
268 |
function getFeedUsers(&$feed) |
269 |
{ |
270 |
$dbhr =& $this->getReadHandle(); |
271 |
|
272 |
$query = sprintf("SELECT user_id |
273 |
FROM %s |
274 |
WHERE feed_id = %d |
275 |
AND label = 'subscribed' |
276 |
AND value_numeric = 1 |
277 |
AND user_id > 0", |
278 |
$dbhr->quoteIdentifier($feed->userdataTableName()), |
279 |
$feed->getID()); |
280 |
|
281 |
$result = $this->readFromDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
282 |
|
283 |
$users = array(); |
284 |
|
285 |
# Since this is super generic and just used for updating/cleaning |
286 |
# we use the generic use class |
287 |
|
288 |
while($subscriber = $result->fetchRow(DB_FETCHMODE_ASSOC)) |
289 |
{ |
290 |
$users[] = new RF_User(array('id' => $subscriber['user_id'])); |
291 |
} |
292 |
|
293 |
$this->invokePlugin('gotFeedUsers', array(&$feed, &$users)); |
294 |
|
295 |
return $users; |
296 |
} |
297 |
|
298 |
/** |
299 |
* Get a list of feed items. |
300 |
* |
301 |
* @param RF_Feed $feed Feed to check |
302 |
* @return array Array of {@link RF_Item items} |
303 |
* |
304 |
* @uses RF_Controller::getReadHandle() |
305 |
* @uses RF_Item::tableName() |
306 |
* @uses RF_Controller::readFromDatabase() |
307 |
* @uses RF_Item::RF_Item() |
308 |
* @uses RF_Controller::invokePlugin() "gotFeedItems" event, |
309 |
* parameters: {@link RF_Feed $feed}, array {@link RF_Item $items}. |
310 |
*/ |
311 |
function getFeedItems(&$feed) |
312 |
{ |
313 |
$dbhr =& $this->getReadHandle(); |
314 |
|
315 |
$query = sprintf("SELECT * FROM %s |
316 |
WHERE feed_id = %d", |
317 |
$dbhr->quoteIdentifier(RF_Item::tableName()), |
318 |
$feed->getID()); |
319 |
|
320 |
$result = $this->readFromDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
321 |
|
322 |
$items = array(); |
323 |
|
324 |
while($item = $result->fetchRow(DB_FETCHMODE_ASSOC)) { |
325 |
$item = new RF_Item($item); |
326 |
$item->feed =& $feed; |
327 |
$items[] = $item; |
328 |
} |
329 |
|
330 |
$this->invokePlugin('gotFeedItems', array(&$feed, &$items)); |
331 |
|
332 |
return $items; |
333 |
} |
334 |
|
335 |
/** |
336 |
* Get a list of feeds. |
337 |
* |
338 |
* @param mixed $order Order to return results: |
339 |
* - blank: database-determined order |
340 |
* - "random": random order |
341 |
* @return array Array of {@link RF_Feed feeds} |
342 |
* |
343 |
* @uses RF_Controller::getReadHandle() |
344 |
* @uses RF_Feed::tableName() |
345 |
* @uses RF_Controller::readFromDatabase() |
346 |
* @uses RF_Feed::RF_Feed() |
347 |
* @uses RF_Controller::invokePlugin() "gotFeeds" event, |
348 |
* parameters: array {@link RF_Feed $feeds}. |
349 |
*/ |
350 |
function getFeeds($order=false) |
351 |
{ |
352 |
$dbhr =& $this->getReadHandle(); |
353 |
|
354 |
switch($order) { |
355 |
case 'random': |
356 |
$order_clause = 'ORDER BY RAND()'; |
357 |
break; |
358 |
default: |
359 |
$order_clause = ''; |
360 |
break; |
361 |
} |
362 |
|
363 |
$query = sprintf("SELECT * FROM %s %s", |
364 |
$dbhr->quoteIdentifier(RF_Feed::tableName()), |
365 |
$order_clause); |
366 |
|
367 |
$result = $this->readFromDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
368 |
|
369 |
$feeds = array(); |
370 |
|
371 |
while($feed = $result->fetchRow(DB_FETCHMODE_ASSOC)) |
372 |
$feeds[] = new RF_Feed($feed); |
373 |
|
374 |
$this->invokePlugin('gotFeeds', array(&$feeds)); |
375 |
|
376 |
return $feeds; |
377 |
} |
378 |
|
379 |
/** |
380 |
* Pre-process arguments used to retrieve user's items. |
381 |
* |
382 |
* @param RF_User $user User whose items to retrieve. |
383 |
* @param array $args Name/value argument pairs, |
384 |
* often resulting from HTTP client input. |
385 |
* @return array Name/value argument pairs, cleaned up a bit. |
386 |
* |
387 |
* @see RF_Controller::getUserItems() |
388 |
* @uses RF_User::itemPageCount() |
389 |
* @uses RF_Controller::invokePlugin() "gotUserItemsQueryArguments" event, |
390 |
* parameters: array $args. |
391 |
*/ |
392 |
function getUserItemsQueryArguments(&$user, $args) |
393 |
{ |
394 |
$default_args = array( |
395 |
'item' => null, |
396 |
'how' => null, |
397 |
'offset' => 0, |
398 |
'howmany' => $user->itemPageCount(), |
399 |
'order' => 'out', |
400 |
'feed' => null, |
401 |
'what' => null, |
402 |
'when' => null, |
403 |
'itemtag' => null, |
404 |
'feedtag' => null, |
405 |
'search' => /*isset($_SESSION['search']) ? $_SESSION['search'] :*/ null |
406 |
); |
407 |
|
408 |
foreach($default_args as $key => $value) |
409 |
$args[$key] = isset($args[$key]) ? $args[$key] : $default_args[$key]; |
410 |
|
411 |
$this->invokePlugin('gotUserItemsQueryArguments', array(&$args)); |
412 |
|
413 |
return $args; |
414 |
} |
415 |
|
416 |
/** |
417 |
* generate SQL query conditions used to retrieve user's items. |
418 |
* |
419 |
* @param RF_User $user User whose items to retrieve. |
420 |
* @param array $args Name/value argument pairs, |
421 |
* often resulting from HTTP client input. |
422 |
* @return array Name/value pairs of SQL conditions and statements. |
423 |
* |
424 |
* @see RF_Controller::getUserItems() |
425 |
* @uses RF_Controller::getReadHandle() |
426 |
* @uses RF_Controller::getUserItemsQueryArguments() |
427 |
* @uses RF_Controller::invokePlugin() "gotUserItemsQueryConditions" event, |
428 |
* parameters: {@link RF_User $user}, array $conditions, array $args. |
429 |
*/ |
430 |
function getUserItemsQueryConditions(&$user, &$args) |
431 |
{ |
432 |
$dbhr =& $this->getReadHandle(); |
433 |
|
434 |
$args = $this->getUserItemsQueryArguments($user, $args); |
435 |
|
436 |
/* if "how" == "paged", limit returned results: |
437 |
if "offset" exists, start there. |
438 |
if "howmany" exists, use that */ |
439 |
$limit_clause = $args['how'] == 'paged' |
440 |
? sprintf("LIMIT %d, %d", $args['offset'], $args['howmany']) |
441 |
: ''; |
442 |
|
443 |
// if "order" == "out", order by timestamp, otherwise by modified |
444 |
$order_clause = $args['order'] == 'out' |
445 |
? 'ORDER BY `timestamp` DESC, i.modified DESC' |
446 |
: 'ORDER BY i.modified DESC, `timestamp` DESC'; |
447 |
|
448 |
// If a search is being performed it's probably good to order by relevance |
449 |
$order_clause = empty($args['search']) |
450 |
? $order_clause |
451 |
: sprintf('ORDER BY (MATCH(i.link, i.title, i.content, i.author, i.category) AGAINST(%s)) DESC', $dbhr->quoteSmart($args['search'])); |
452 |
|
453 |
// if "item" is numeric, set the item_id |
454 |
$item_test = is_numeric($args['item']) && $args['item'] >= 0 |
455 |
? sprintf("id.item_id = %d", $args['item']) |
456 |
: '1'; // always true if no item specified |
457 |
|
458 |
// if "feed" is numeric, set the feed_id |
459 |
$feed_test = is_numeric($args['feed']) && $args['feed'] >= 0 |
460 |
? sprintf("(f.id = %d)", $args['feed']) |
461 |
: false; // don't check the feeds table first! |
462 |
|
463 |
// if "what" == "new", check for "read" == 0 |
464 |
$unread_test = $args['what'] == 'new' |
465 |
? '(id.value_numeric = 0)' |
466 |
: '1'; // always true if no unread state specified |
467 |
|
468 |
// if "what" == "published", check for "published" == 1 |
469 |
$published_test = preg_match('/\bpublished\b/', $args['what']) |
470 |
? '(id_published.value_numeric = 1)' |
471 |
: '1'; // always true if no published state specified |
472 |
|
473 |
// if "what" == "self", check for "self" == 1 |
474 |
$self_test = preg_match('/\bself\b/', $args['what']) |
475 |
? '(id_self.value_numeric = 1)' |
476 |
: '1'; // always true if no self state specified |
477 |
|
478 |
/* if "when" exists: |
479 |
if "when" == "today" |
480 |
if not, use "when" as a date */ |
481 |
if(empty($args['when'])) { |
482 |
$when_test = '1'; // always true if no date limit specified |
483 |
|
484 |
} else { |
485 |
|
486 |
switch($args['when']) { |
487 |
case 'today': |
488 |
$args['when_start'] = date("Y-m-d"); |
489 |
break; |
490 |
default: |
491 |
$args['when_start'] = $args['when']; |
492 |
break; |
493 |
} |
494 |
|
495 |
$args['when_start'] = strtotime($args['when_start']); |
496 |
$args['when_end'] = $args['when_start'] + (24 * 60 * 60); |
497 |
|
498 |
$when_test = sprintf("((UNIX_TIMESTAMP(id.timestamp) >= %d AND UNIX_TIMESTAMP(id.timestamp) < %d) |
499 |
OR (UNIX_TIMESTAMP(id_published.timestamp) >= %d AND UNIX_TIMESTAMP(id_published.timestamp) < %d))", |
500 |
$args['when_start'], $args['when_end'], |
501 |
$args['when_start'], $args['when_end']); |
502 |
} |
503 |
|
504 |
// if "itemtag" exists, check for is encoded version |
505 |
$itemtag_test = empty($args['itemtag']) |
506 |
? '1' |
507 |
: sprintf('(id_tag.value_short = %s)', $dbhr->quoteSmart($args['itemtag'])); |
508 |
|
509 |
if(!empty($args['feedtag'])) { |
510 |
|
511 |
$feed_ids = array(); |
512 |
|
513 |
foreach($this->getUserFeeds($user, array('feedtag' => $args['feedtag']), true) as $feed) |
514 |
$feed_ids[] = $feed->getID(); |
515 |
|
516 |
$feed_tagtest = '(f.id IN ('.join(',', $feed_ids).'))'; |
517 |
|
518 |
$feed_test = $feed_test |
519 |
? "({$feed_test} && {$feed_tagtest})" |
520 |
: $feed_tagtest; |
521 |
} |
522 |
|
523 |
// TODO: if "search" exists, match titles, links, content, author and category against it |
524 |
$search_test = empty($args['search']) |
525 |
? '1' |
526 |
: sprintf('(MATCH(i.link, i.title, i.content, i.author, i.category) AGAINST(%s))', $dbhr->quoteSmart($args['search'])); |
527 |
|
528 |
$conditions = compact('limit_clause', 'feed_test', 'item_test', 'unread_test', 'published_test', 'self_test', 'when_test', 'order_clause', 'itemtag_test', 'search_test'); |
529 |
|
530 |
$this->invokePlugin('gotUserItemsQueryConditions', array(&$user, &$conditions, &$args)); |
531 |
|
532 |
return $conditions; |
533 |
} |
534 |
|
535 |
/** |
536 |
* generate a complete SQL query used to retrieve user's items. |
537 |
* |
538 |
* @param RF_User $user User whose items to retrieve. |
539 |
* @param array $args Name/value argument pairs, |
540 |
* often resulting from HTTP client input. |
541 |
* @return string SQL SELECT query. |
542 |
* |
543 |
* @see RF_Controller::getUserItems() |
544 |
* @uses RF_Controller::getReadHandle() |
545 |
* @uses RF_Controller::getUserItemsQueryConditions() |
546 |
* @uses RF_Item::userdataTableName() |
547 |
* @uses RF_Item::tableName() |
548 |
* @uses RF_Feed::tableName() |
549 |
* @uses RF_Controller::invokePlugin() "gotUserItemsQuery" event, |
550 |
* parameters: {@link RF_User $user}, string $query. |
551 |
*/ |
552 |
function getUserItemsQuery(&$user, $args) |
553 |
{ |
554 |
$dbhr =& $this->getReadHandle(); |
555 |
|
556 |
// expect back an array with these keys defined: |
557 |
// 'limit_clause', 'feed_test', 'item_test', 'unread_test', 'published_test', 'when_test', 'order_clause' |
558 |
$conditions = $this->getUserItemsQueryConditions($user, $args); |
559 |
|
560 |
// extra joins for extra metadata on each item |
561 |
$meta = array('join_clauses' => array(), 'column_clauses' => array(), 'where_clauses' => array(1)); |
562 |
|
563 |
// $args[metadata] like [{'format': s, 'label': s}, ..., {'format': s, 'label': s}] |
564 |
// if a match is required, may look like {'format': s, 'label': s, 'value': s} |
565 |
if(is_array($args['metadata'])) |
566 |
foreach($args['metadata'] as $m => $metadatum) |
567 |
if(is_string($metadatum['format']) && is_string($metadatum['label'])) { |
568 |
|
569 |
$meta['join_clauses'][] = sprintf(" |
570 |
LEFT JOIN %s AS id{$m} |
571 |
ON id{$m}.item_id = i.id |
572 |
AND id{$m}.user_id = %d |
573 |
AND id{$m}.label = %s |
574 |
", |
575 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
576 |
$user->getID(), |
577 |
$dbhr->quoteSmart($metadatum['label'])); |
578 |
|
579 |
$meta['column_clauses'][] = sprintf( |
580 |
"id{$m}.value_%s AS %s,", |
581 |
$dbhr->escapeSimple($metadatum['format']), |
582 |
$dbhr->quoteIdentifier("metadatum_{$metadatum['label']}")); |
583 |
|
584 |
if(isset($metadatum['value'])) |
585 |
if(is_string($metadatum['value']) || is_numeric($metadatum['value'])) |
586 |
$meta['where_clauses'][] = sprintf( |
587 |
"(id{$m}.value_%s = %s)", |
588 |
$dbhr->escapeSimple($metadatum['format']), |
589 |
$dbhr->quoteSmart($metadatum['value'])); |
590 |
} |
591 |
|
592 |
$core_join_clauses = sprintf(" |
593 |
LEFT JOIN %s AS id_published |
594 |
ON id_published.item_id = i.id |
595 |
AND id_published.user_id = %d |
596 |
AND id_published.label = 'published' |
597 |
|
598 |
LEFT JOIN %s AS id_self |
599 |
ON id_self.item_id = i.id |
600 |
AND id_self.user_id = %d |
601 |
AND id_self.label = 'self' |
602 |
|
603 |
LEFT JOIN %s AS id_title |
604 |
ON id_title.item_id = i.id |
605 |
AND id_title.user_id = %d |
606 |
AND id_title.label = 'title' |
607 |
|
608 |
LEFT JOIN %s AS id_content |
609 |
ON id_content.item_id = i.id |
610 |
AND id_content.user_id = %d |
611 |
AND id_content.label = 'content' |
612 |
|
613 |
LEFT JOIN %s AS id_link |
614 |
ON id_link.item_id = i.id |
615 |
AND id_link.user_id = %d |
616 |
AND id_link.label = 'link' |
617 |
|
618 |
LEFT JOIN %s AS fd |
619 |
ON fd.feed_id = f.id |
620 |
AND fd.user_id = %d |
621 |
AND fd.label = 'tags' |
622 |
", |
623 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
624 |
$user->getID(), |
625 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
626 |
$user->getID(), |
627 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
628 |
$user->getID(), |
629 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
630 |
$user->getID(), |
631 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
632 |
$user->getID(), |
633 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
634 |
$user->getID()); |
635 |
|
636 |
if($conditions['itemtag_test'] != '1') |
637 |
$core_join_clauses .= sprintf(" |
638 |
LEFT JOIN %s AS id_tag |
639 |
ON id_tag.item_id = i.id |
640 |
AND id_tag.user_id = %d |
641 |
AND id_tag.label = 'tag' |
642 |
", |
643 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
644 |
$user->getID()); |
645 |
|
646 |
$meta['join_clauses'] = join("\n\n", $meta['join_clauses']); |
647 |
$meta['where_clauses'] = join(' AND ', $meta['where_clauses']); |
648 |
|
649 |
// each of these queries will work fine, but the former is |
650 |
// optimized for searches on particular metadata values, |
651 |
// and the latter is optimized for typical per-feed viewing |
652 |
if($conditions['feed_test'] === false) { |
653 |
// this query optimized for metadata searches |
654 |
$table_clauses = sprintf(" |
655 |
FROM %s AS id |
656 |
|
657 |
LEFT JOIN %s AS i |
658 |
ON i.id = id.item_id |
659 |
|
660 |
LEFT JOIN %s AS f |
661 |
ON f.id = i.feed_id |
662 |
|
663 |
{$core_join_clauses} |
664 |
{$meta['join_clauses']} |
665 |
|
666 |
WHERE id.user_id = %d |
667 |
AND id.label = 'read' |
668 |
AND {$conditions['item_test']} |
669 |
AND {$conditions['unread_test']} |
670 |
AND {$conditions['published_test']} |
671 |
AND {$conditions['self_test']} |
672 |
AND {$conditions['when_test']} |
673 |
AND {$conditions['itemtag_test']} |
674 |
AND {$conditions['search_test']} |
675 |
AND {$meta['where_clauses']} |
676 |
AND i.id IS NOT NULL |
677 |
", |
678 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
679 |
$dbhr->quoteIdentifier(RF_Item::tableName()), |
680 |
$dbhr->quoteIdentifier(RF_Feed::tableName()), |
681 |
$user->getID()); |
682 |
|
683 |
} else { |
684 |
// this query optimized for in-feed searches |
685 |
$table_clauses = sprintf(" |
686 |
FROM %s AS f |
687 |
|
688 |
LEFT JOIN %s AS i |
689 |
ON i.feed_id = f.id |
690 |
|
691 |
LEFT JOIN %s AS id |
692 |
ON id.item_id = i.id |
693 |
AND id.user_id = %d |
694 |
AND id.label = 'read' |
695 |
|
696 |
{$core_join_clauses} |
697 |
{$meta['join_clauses']} |
698 |
|
699 |
WHERE {$conditions['feed_test']} |
700 |
AND {$conditions['item_test']} |
701 |
AND {$conditions['unread_test']} |
702 |
AND {$conditions['published_test']} |
703 |
AND {$conditions['self_test']} |
704 |
AND {$conditions['when_test']} |
705 |
AND {$conditions['itemtag_test']} |
706 |
AND {$conditions['search_test']} |
707 |
AND i.id IS NOT NULL |
708 |
", |
709 |
$dbhr->quoteIdentifier(RF_Feed::tableName()), |
710 |
$dbhr->quoteIdentifier(RF_Item::tableName()), |
711 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
712 |
$user->getID()); |
713 |
|
714 |
} |
715 |
|
716 |
$meta['column_clauses'] = join(' ', $meta['column_clauses']); |
717 |
|
718 |
$q = sprintf(" |
719 |
SELECT |
720 |
|
721 |
### Core item properties |
722 |
i.id, |
723 |
i.guid, |
724 |
IFNULL(id_link.value_long, i.link) AS link, |
725 |
IFNULL(id_title.value_long, i.title) AS title, |
726 |
IFNULL(id_content.value_long, i.content) AS content, |
727 |
i.author, |
728 |
i.category, |
729 |
i.modified, |
730 |
|
731 |
# use the timestamp of the most recent read or publish action |
732 |
IF(UNIX_TIMESTAMP(id.timestamp) < UNIX_TIMESTAMP(id_published.timestamp), |
733 |
UNIX_TIMESTAMP(id_published.timestamp), |
734 |
UNIX_TIMESTAMP(id.timestamp)) AS `timestamp`, |
735 |
|
736 |
### Original item properties |
737 |
i.link AS original_link, |
738 |
i.title AS original_title, |
739 |
i.content AS original_content, |
740 |
|
741 |
### Core feed properties |
742 |
f.id AS feed_id, |
743 |
f.url AS feed_url, |
744 |
f.title AS feed_title, |
745 |
f.link AS feed_link, |
746 |
f.description AS feed_description, |
747 |
fd.value_long AS feed_metadatum_tags, |
748 |
|
749 |
### Extra item properties |
750 |
{$meta['column_clauses']} |
751 |
id_published.value_numeric AS published, |
752 |
id.value_numeric AS `read` |
753 |
|
754 |
{$table_clauses} |
755 |
|
756 |
GROUP BY id.item_id |
757 |
|
758 |
{$conditions['order_clause']} |
759 |
{$conditions['limit_clause']} |
760 |
"); |
761 |
|
762 |
$this->invokePlugin('gotUserItemsQuery', array(&$user, &$q)); |
763 |
|
764 |
return $q; |
765 |
} |
766 |
|
767 |
/** |
768 |
* retrieve a user's items based on variable criteria. |
769 |
* |
770 |
* @param RF_User $user User whose items to retrieve. |
771 |
* @param array $args Name/value argument pairs, |
772 |
* often resulting from HTTP client input. |
773 |
* @return array Array of {@link RF_Item items} |
774 |
* |
775 |
* @uses RF_Controller::getUserItemsQuery() |
776 |
* @uses RF_Controller::readFromDatabase() |
777 |
* @uses RF_Item::RF_Item() |
778 |
* @uses RF_Feed::RF_Feed() |
779 |
* @uses RF_Controller::invokePlugin() "gotUserItems" event, |
780 |
* parameters: {@link RF_User $user}, array {@link RF_Item $items}. |
781 |
*/ |
782 |
function getUserItems(&$user, $args) |
783 |
{ |
784 |
$result = $this->readFromDatabase($this->getUserItemsQuery($user, $args)."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
785 |
|
786 |
$items = array(); |
787 |
|
788 |
while($item = $result->fetchRow(DB_FETCHMODE_ASSOC)) { |
789 |
|
790 |
// `modified' is returned in "YYYY-MM-DD HH:MM:SS" format |
791 |
$item['modified'] = strtotime($item['modified']); |
792 |
|
793 |
$item['feed'] = array(); |
794 |
$item['feed']['metadata'] = array(); |
795 |
$item['metadata'] = array(); |
796 |
$item['original'] = array(); |
797 |
|
798 |
foreach(array_keys($item) as $property) { |
799 |
|
800 |
if(substr($property, 0, 15) == 'feed_metadatum_') { |
801 |
$item['feed']['metadata'][substr($property, 15)] = $item[$property]; |
802 |
unset($item[$property]); |
803 |
|
804 |
} elseif(substr($property, 0, 5) == 'feed_') { |
805 |
$item['feed'][substr($property, 5)] = $item[$property]; |
806 |
unset($item[$property]); |
807 |
|
808 |
} elseif(substr($property, 0, 10) == 'metadatum_') { |
809 |
$item['metadata'][substr($property, 10)] = $item[$property]; |
810 |
unset($item[$property]); |
811 |
|
812 |
} elseif(substr($property, 0, 9) == 'original_') { |
813 |
$item['original'][substr($property, 9)] = $item[$property]; |
814 |
unset($item[$property]); |
815 |
|
816 |
} |
817 |
|
818 |
} |
819 |
|
820 |
$item['feed'] = new RF_Feed($item['feed']); |
821 |
|
822 |
$items[] = new RF_Item($item); |
823 |
} |
824 |
|
825 |
$this->invokePlugin('gotUserItems', array(&$user, &$items)); |
826 |
|
827 |
return $items; |
828 |
} |
829 |
|
830 |
/** |
831 |
* retrieve a user's item |
832 |
* |
833 |
* @param RF_User $user User whose items to retrieve. |
834 |
* @param mixed $item Instance of RF_Item, or numeric item ID |
835 |
* @return RF_Item Single {@link RF_Item item} |
836 |
* |
837 |
* @uses RF_Item::RF_Item() |
838 |
* @uses RF_Controller::getUserItems() |
839 |
*/ |
840 |
function getUserItem(&$user, $item) |
841 |
{ |
842 |
if(is_numeric($item)) |
843 |
$item = new RF_Item(array('id' => $item)); |
844 |
|
845 |
$items = $this->getUserItems($user, array('item' => $item->getID())); |
846 |
|
847 |
if(count($items) == 1) |
848 |
return $items[0]; |
849 |
} |
850 |
|
851 |
/** |
852 |
* generate SQL query conditions used to retrieve user's feeds. |
853 |
* |
854 |
* @param RF_User $user User whose feeds to retrieve. |
855 |
* @param array $args Name/value argument pairs, |
856 |
* often resulting from HTTP client input. |
857 |
* @param boolean $tag_only Optional flag: if 'feedtag' is passed |
858 |
* in $args, get /only/ those feeds with |
859 |
* the tag, or all of them? Default no. |
860 |
* @return array Name/value pairs of SQL conditions and statements. |
861 |
* |
862 |
* @see RF_Controller::getUserFeeds() |
863 |
* @uses RF_Controller::getReadHandle() |
864 |
* @uses RF_Controller::invokePlugin() "gotUserFeedsQueryArguments" event, |
865 |
* parameters: array $args. |
866 |
* @uses RF_Controller::invokePlugin() "gotUserFeedsQueryConditions" event, |
867 |
* parameters: {@link RF_User $user}, array $conditions, array $args. |
868 |
*/ |
869 |
function getUserFeedsQueryConditions(&$user, &$args, $tag_only=false) |
870 |
{ |
871 |
$dbhr =& $this->getReadHandle(); |
872 |
|
873 |
$this->invokePlugin('gotUserFeedsQueryArguments', array(&$args)); |
874 |
|
875 |
switch($_SESSION['feed_sort']) { |
876 |
case 'age': |
877 |
$order_clause = 'ORDER BY usage_last_update DESC, f.title ASC'; |
878 |
break; |
879 |
|
880 |
case 'items': |
881 |
$order_clause = 'ORDER BY usage_unread DESC, f.title ASC'; |
882 |
break; |
883 |
|
884 |
case 'random': |
885 |
$order_clause = 'ORDER BY RAND()'; |
886 |
break; |
887 |
|
888 |
default: |
889 |
$order_clause = 'ORDER BY f.title ASC'; |
890 |
break; |
891 |
} |
892 |
|
893 |
// if "feed" is numeric, set the feed_id |
894 |
$feed_test = is_numeric($args['feed']) && $args['feed'] >= 0 |
895 |
? sprintf("(f.id = %d)", $args['feed']) |
896 |
: '1'; // always true if no feed specified |
897 |
|
898 |
/*$args['search'] = isset($args['search']) |
899 |
? $args['search'] |
900 |
: $_SESSION['search'];*/ |
901 |
|
902 |
// TODO: if "search" exists, match titles, links, content, author and category against it |
903 |
$search_test = empty($args['search']) |
904 |
? '1' |
905 |
: sprintf('(MATCH(f.url, f.title, f.link, f.description) AGAINST(%s))', $dbhr->quoteSmart($args['search'])); |
906 |
|
907 |
$feedtag_jointest = empty($args['feedtag']) |
908 |
? '1' |
909 |
: sprintf('(fd_tag.value_short = %s)', $dbhr->quoteSmart($args['feedtag'])); |
910 |
|
911 |
$feedtag_test = $tag_only |
912 |
? $feedtag_jointest |
913 |
: '1'; |
914 |
|
915 |
$conditions = compact('order_clause', 'feed_test', 'search_test', 'feedtag_test', 'feedtag_jointest'); |
916 |
|
917 |
$this->invokePlugin('gotUserFeedsQueryConditions', array(&$user, &$conditions, &$args)); |
918 |
|
919 |
return $conditions; |
920 |
} |
921 |
|
922 |
/** |
923 |
* generate a complete SQL query used to retrieve user's feeds. |
924 |
* |
925 |
* @param RF_User $user User whose feeds to retrieve. |
926 |
* @param array $args Name/value argument pairs, |
927 |
* often resulting from HTTP client input. |
928 |
* @param boolean $tag_only Optional flag: if 'feedtag' is passed |
929 |
* in $args, get /only/ those feeds with |
930 |
* the tag, or all of them? Default no. |
931 |
* @return string SQL SELECT query. |
932 |
* |
933 |
* @see RF_Controller::getUserFeeds() |
934 |
* @uses RF_Controller::getReadHandle() |
935 |
* @uses RF_Controller::getUserFeedsQueryConditions() |
936 |
* @uses RF_Feed::userdataTableName() |
937 |
* @uses RF_Item::userdataTableName() |
938 |
* @uses RF_Feed::tableName() |
939 |
* @uses RF_Item::tableName() |
940 |
* @uses RF_Controller::invokePlugin() "gotUserFeedsQuery" event, |
941 |
* parameters: {@link RF_User $user}, string $query. |
942 |
*/ |
943 |
function getUserFeedsQuery(&$user, $args, $tag_only=false) |
944 |
{ |
945 |
$dbhr =& $this->getReadHandle(); |
946 |
$conditions = $this->getUserFeedsQueryConditions($user, $args, $tag_only); |
947 |
|
948 |
// extra joins for extra metadata on each item |
949 |
$meta = array('join_clauses' => array(), 'column_clauses' => array(), 'where_clauses' => array(1)); |
950 |
|
951 |
// $args[metadata] like [{'format': s, 'label': s}, ..., {'format': s, 'label': s}] |
952 |
// if a match is required, may look like {'format': s, 'label': s, 'value': s} |
953 |
if(isset($args['metadata']) && is_array($args['metadata'])) |
954 |
foreach($args['metadata'] as $m => $metadatum) |
955 |
if(is_string($metadatum['format']) && is_string($metadatum['label'])) { |
956 |
|
957 |
$meta['join_clauses'][] = sprintf(" |
958 |
LEFT JOIN %s AS fd{$m} |
959 |
ON fd{$m}.feed_id = f.id |
960 |
AND fd{$m}.user_id = %d |
961 |
AND fd{$m}.label = %s |
962 |
", |
963 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
964 |
$user->getID(), |
965 |
$dbhr->quoteSmart($metadatum['label'])); |
966 |
|
967 |
$meta['column_clauses'][] = sprintf( |
968 |
"fd{$m}.value_%s AS %s,", |
969 |
$dbhr->escapeSimple($metadatum['format']), |
970 |
$dbhr->quoteIdentifier("metadatum_{$metadatum['label']}")); |
971 |
|
972 |
if(is_string($metadatum['value']) || is_numeric($metadatum['value'])) |
973 |
$meta['where_clauses'][] = sprintf( |
974 |
"(fd{$m}.value_%s = %s)", |
975 |
$dbhr->escapeSimple($metadatum['format']), |
976 |
$dbhr->quoteSmart($metadatum['value'])); |
977 |
} |
978 |
|
979 |
$meta['join_clauses'] = join("\n\n", $meta['join_clauses']); |
980 |
$meta['where_clauses'] = join(' AND ', $meta['where_clauses']); |
981 |
$meta['column_clauses'] = join(' ', $meta['column_clauses']); |
982 |
|
983 |
$q = sprintf("SELECT |
984 |
|
985 |
### Core feed properties |
986 |
f.id, |
987 |
f.url, |
988 |
f.title, |
989 |
f.link, |
990 |
f.description, |
991 |
|
992 |
### Feed usage properties |
993 |
fd_usage_last_update.value_numeric AS usage_last_update, |
994 |
fd_usage_total.value_numeric AS usage_items, |
995 |
fd_usage_unread.value_numeric AS usage_unread, |
996 |
fd_usage_published.value_numeric AS usage_published, |
997 |
IF(fd_tag.value_short IS NULL, 0, 1) AS usage_tagged, # flag - tagged or no? |
998 |
|
999 |
### Extra feed properties |
1000 |
{$meta['column_clauses']} |
1001 |
fd_published.value_numeric AS published |
1002 |
|
1003 |
FROM %s AS fd |
1004 |
|
1005 |
LEFT JOIN %s AS f |
1006 |
ON f.id = fd.feed_id |
1007 |
|
1008 |
LEFT JOIN %s AS fd_tag |
1009 |
ON fd_tag.feed_id = f.id |
1010 |
AND fd_tag.user_id = %d |
1011 |
AND fd_tag.label = 'tag' |
1012 |
AND {$conditions['feedtag_jointest']} |
1013 |
|
1014 |
LEFT JOIN %s AS fd_published |
1015 |
ON fd_published.feed_id = f.id |
1016 |
AND fd_published.user_id = %d |
1017 |
AND fd_published.label = 'published' |
1018 |
|
1019 |
LEFT JOIN %s AS fd_usage_last_update |
1020 |
ON fd_usage_last_update.feed_id = f.id |
1021 |
AND fd_usage_last_update.user_id = %d |
1022 |
AND fd_usage_last_update.label = 'usage_last_update' |
1023 |
|
1024 |
LEFT JOIN %s AS fd_usage_total |
1025 |
ON fd_usage_total.feed_id = f.id |
1026 |
AND fd_usage_total.user_id = %d |
1027 |
AND fd_usage_total.label = 'usage_unread' |
1028 |
|
1029 |
LEFT JOIN %s AS fd_usage_unread |
1030 |
ON fd_usage_unread.feed_id = f.id |
1031 |
AND fd_usage_unread.user_id = %d |
1032 |
AND fd_usage_unread.label = 'usage_unread' |
1033 |
|
1034 |
LEFT JOIN %s AS fd_usage_published |
1035 |
ON fd_usage_published.feed_id = f.id |
1036 |
AND fd_usage_published.user_id = %d |
1037 |
AND fd_usage_published.label = 'usage_published' |
1038 |
|
1039 |
{$meta['join_clauses']} |
1040 |
|
1041 |
WHERE fd.label = 'subscribed' |
1042 |
AND fd.user_id = %d |
1043 |
AND {$conditions['feed_test']} |
1044 |
AND {$conditions['search_test']} |
1045 |
AND {$meta['where_clauses']} |
1046 |
AND {$conditions['feedtag_test']} |
1047 |
|
1048 |
GROUP BY fd.feed_id |
1049 |
|
1050 |
{$conditions['order_clause']} |
1051 |
", |
1052 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
1053 |
$dbhr->quoteIdentifier(RF_Feed::tableName()), |
1054 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
1055 |
$user->getID(), |
1056 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
1057 |
$user->getID(), |
1058 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
1059 |
$user->getID(), |
1060 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
1061 |
$user->getID(), |
1062 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
1063 |
$user->getID(), |
1064 |
$dbhr->quoteIdentifier(RF_Feed::userdataTableName()), |
1065 |
$user->getID(), |
1066 |
$user->getID()); |
1067 |
|
1068 |
$this->invokePlugin('gotUserFeedsQuery', array(&$user, &$q)); |
1069 |
|
1070 |
return $q; |
1071 |
} |
1072 |
|
1073 |
/** |
1074 |
* retrieve a user's feeds based on variable criteria. |
1075 |
* |
1076 |
* @param RF_User $user User whose feeds to retrieve. |
1077 |
* @param array $args Name/value argument pairs, |
1078 |
* often resulting from HTTP client input. |
1079 |
* @param boolean $tag_only Optional flag: if 'feedtag' is passed |
1080 |
* in $args, get /only/ those feeds with |
1081 |
* the tag, or all of them? Default no. |
1082 |
* @return array Array of {@link RF_Feed feeds} |
1083 |
* |
1084 |
* @uses RF_Controller::getUserFeedsQuery() |
1085 |
* @uses RF_Controller::readFromDatabase() |
1086 |
* @uses RF_Feed::RF_Feed() |
1087 |
* @uses RF_Controller::invokePlugin() "gotUserFeeds" event, |
1088 |
* parameters: {@link RF_User $user}, array {@link RF_Feed $feeds}. |
1089 |
*/ |
1090 |
function getUserFeeds(&$user, $args, $tag_only=false) |
1091 |
{ |
1092 |
$result = $this->readFromDatabase($this->getUserFeedsQuery($user, $args, $tag_only)."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1093 |
|
1094 |
$feeds = array(); |
1095 |
|
1096 |
while($feed = $result->fetchRow(DB_FETCHMODE_ASSOC)) { |
1097 |
|
1098 |
$feed['usage'] = array(); |
1099 |
$feed['metadata'] = array(); |
1100 |
|
1101 |
foreach(array_keys($feed) as $property) { |
1102 |
|
1103 |
$short_property = substr($property, 6); |
1104 |
|
1105 |
if(substr($property, 0, 6) == 'usage_') { |
1106 |
$feed['usage'][$short_property] = $feed[$property]; |
1107 |
unset($feed[$property]); |
1108 |
|
1109 |
} elseif(substr($property, 0, 10) == 'metadatum_') { |
1110 |
$feed['metadata'][substr($property, 10)] = $feed[$property]; |
1111 |
unset($feed[$property]); |
1112 |
|
1113 |
} |
1114 |
|
1115 |
} |
1116 |
|
1117 |
$feeds[] = new RF_Feed($feed); |
1118 |
} |
1119 |
|
1120 |
$this->invokePlugin('gotUserFeeds', array(&$user, &$feeds)); |
1121 |
|
1122 |
return $feeds; |
1123 |
} |
1124 |
|
1125 |
/** |
1126 |
* retrieve a user's feed |
1127 |
* |
1128 |
* @param RF_User $user User whose feeds to retrieve. |
1129 |
* @param mixed $feed Instance of RF_Feed, or numeric feed ID |
1130 |
* @return RF_Feed Single {@link RF_Feed feed} |
1131 |
* |
1132 |
* @uses RF_Feed::RF_Feed() |
1133 |
* @uses RF_Controller::getUserFeeds() |
1134 |
*/ |
1135 |
function getUserFeed(&$user, $feed) |
1136 |
{ |
1137 |
if(is_numeric($feed)) |
1138 |
$feed = new RF_Feed(array('id' => $feed)); |
1139 |
|
1140 |
$feeds = $this->getUserFeeds($user, array('feed' => $feed->getID())); |
1141 |
|
1142 |
if(count($feeds) == 1) |
1143 |
return $feeds[0]; |
1144 |
} |
1145 |
|
1146 |
/** |
1147 |
* @uses RF_Controller::getReadHandle() |
1148 |
*/ |
1149 |
function getUserSelfFeedQuery(&$user, $args) |
1150 |
{ |
1151 |
$dbhr =& $this->getReadHandle(); |
1152 |
$conditions = $this->getUserFeedsQueryConditions($user, $args); |
1153 |
|
1154 |
$q = sprintf("SELECT |
1155 |
|
1156 |
### Self-feed usage properties |
1157 |
|
1158 |
UNIX_TIMESTAMP( |
1159 |
MAX(IF(id_read.value_numeric = 0, |
1160 |
id_read.timestamp, 0))) AS usage_last_update, # timestamp of newest unread item |
1161 |
|
1162 |
COUNT(i.id) AS usage_items, # number of items total |
1163 |
|
1164 |
SUM(id_published.value_numeric) AS usage_published # number of published items |
1165 |
|
1166 |
FROM %s AS id |
1167 |
|
1168 |
LEFT JOIN %s AS i |
1169 |
ON i.id = id.item_id |
1170 |
|
1171 |
LEFT JOIN %s AS id_read |
1172 |
ON id_read.item_id = i.id |
1173 |
AND id_read.user_id = %d |
1174 |
AND id_read.label = 'read' |
1175 |
|
1176 |
LEFT JOIN %s AS id_published |
1177 |
ON id_published.item_id = i.id |
1178 |
AND id_published.user_id = %d |
1179 |
AND id_published.label = 'published' |
1180 |
|
1181 |
WHERE id.label = 'self' |
1182 |
AND id.user_id = %d |
1183 |
AND id.value_numeric = 1 |
1184 |
AND {$conditions['feed_test']} |
1185 |
|
1186 |
GROUP BY i.feed_id |
1187 |
", |
1188 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
1189 |
$dbhr->quoteIdentifier(RF_Item::tableName()), |
1190 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
1191 |
$user->getID(), |
1192 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
1193 |
$user->getID(), |
1194 |
$user->getID()); |
1195 |
|
1196 |
$this->invokePlugin('gotUserSelfFeedQuery', array(&$user, &$q)); |
1197 |
|
1198 |
return $q; |
1199 |
} |
1200 |
|
1201 |
/** |
1202 |
* |
1203 |
*/ |
1204 |
function getUserSelfFeed(&$user, $args) |
1205 |
{ |
1206 |
$result = $this->readFromDatabase($this->getUserSelfFeedQuery($user, $args)."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1207 |
|
1208 |
if($feed = $result->fetchRow(DB_FETCHMODE_ASSOC)) { |
1209 |
|
1210 |
$feed['usage'] = array(); |
1211 |
|
1212 |
foreach(array_keys($feed) as $property) { |
1213 |
|
1214 |
$short_property = substr($property, 6); |
1215 |
|
1216 |
if(substr($property, 0, 6) == 'usage_') { |
1217 |
$feed['usage'][$short_property] = $feed[$property]; |
1218 |
|
1219 |
switch($short_property) { |
1220 |
case 'last_update': |
1221 |
$total_usage[$short_property] = max($total_usage[$short_property], $feed[$property]); |
1222 |
break; |
1223 |
default: |
1224 |
$total_usage[$short_property] = $total_usage[$short_property] + $feed[$property]; |
1225 |
break; |
1226 |
} |
1227 |
|
1228 |
unset($feed[$property]); |
1229 |
} |
1230 |
|
1231 |
} |
1232 |
|
1233 |
$feed = new RF_Feed($feed); |
1234 |
|
1235 |
$this->invokePlugin('gotUserSelfFeed', array(&$user, &$feed)); |
1236 |
} |
1237 |
|
1238 |
return $feed; |
1239 |
} |
1240 |
|
1241 |
/** |
1242 |
* Retrive a list of all user's tags |
1243 |
* |
1244 |
* @param RF_User $user - |
1245 |
* @param string $referent Entity referenced by the tag |
1246 |
* - "item" (default) |
1247 |
* - "feed" |
1248 |
* @param string $order Tag return order: |
1249 |
* - "alphabet": Alphabetically, A - Z (default) |
1250 |
* - "usage": By usage frequency, most to least |
1251 |
* @return array Array of strings |
1252 |
* |
1253 |
* @uses RF_Controller::getReadHandle() |
1254 |
* @uses RF_Controller::readFromDatabase() |
1255 |
* @uses RF_Controller::invokePlugin() "gotUserTags" event, |
1256 |
* parameters: {@link RF_User $user}, string $referent, array $tags. |
1257 |
*/ |
1258 |
function getUserTags(&$user, $referent='item', $order='alphabet') |
1259 |
{ |
1260 |
$dbhr =& $this->getReadHandle(); |
1261 |
|
1262 |
$order_clauses = array('alphabet' => 'tag ASC', |
1263 |
'usage' => 'items DESC'); |
1264 |
|
1265 |
$order_clause = $order_clauses[$order]; |
1266 |
|
1267 |
$tables = array('item' => RF_Item::userdataTableName(), |
1268 |
'feed' => RF_Feed::userdataTableName()); |
1269 |
|
1270 |
$query = sprintf("SELECT value_short AS tag, |
1271 |
COUNT(%s) AS referents |
1272 |
FROM %s |
1273 |
WHERE label = 'tag' |
1274 |
AND user_id = %d |
1275 |
GROUP BY tag |
1276 |
ORDER BY {$order_clause} |
1277 |
", |
1278 |
$dbhr->quoteIdentifier("{$referent}_id"), |
1279 |
$dbhr->quoteIdentifier($tables[$referent]), |
1280 |
$user->getID()); |
1281 |
|
1282 |
$tags = array(); |
1283 |
$result = $this->readFromDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1284 |
|
1285 |
while($tag = $result->fetchRow(DB_FETCHMODE_ASSOC)) |
1286 |
$tags[] = $tag['tag']; |
1287 |
|
1288 |
$this->invokePlugin('gotUserTags', array(&$user, $referent, &$tags)); |
1289 |
|
1290 |
return $tags; |
1291 |
} |
1292 |
|
1293 |
/** |
1294 |
* Return a usage summary for tags on a given set of items. |
1295 |
* |
1296 |
* @param array $objects Instances of {@link RF_Item RF_Item} or {@link RF_Feed RF_Feed} |
1297 |
* @return array Associative array - keys are tags, values are usage counts. |
1298 |
* Sorted in descending order of usage. |
1299 |
*/ |
1300 |
function tagUsage($objects) |
1301 |
{ |
1302 |
$usage = array(); |
1303 |
|
1304 |
foreach($objects as $object) |
1305 |
if(isset($object->metadata['tags'])) { |
1306 |
$tags = preg_split('#\s+#', $object->metadata['tags']); |
1307 |
|
1308 |
foreach($tags as $tag) |
1309 |
if($tag) |
1310 |
$usage[$tag] = $usage[$tag] |
1311 |
? $usage[$tag] + 1 |
1312 |
: 1; |
1313 |
} |
1314 |
|
1315 |
arsort($usage); |
1316 |
return $usage; |
1317 |
} |
1318 |
|
1319 |
/** |
1320 |
* Mark new or changed items in a feed as unread and perhaps published. |
1321 |
* Try to set tags in accordance with feed tags as well |
1322 |
* |
1323 |
* @param RF_Feed $feed Feed to which items belong |
1324 |
* @param array $items Instances of {@link RF_Item RF_Item} |
1325 |
* |
1326 |
* @uses RF_Controller::getFeedUsers() |
1327 |
* @uses RF_Userdata_Controller::RF_Userdata_Controller() |
1328 |
* @uses RF_Userdata_Controller::markItemsUnread() |
1329 |
* @uses RF_Userdata_Controller::markItemsPublished() |
1330 |
* @uses RF_Controller::freshenUserFeedUsage() |
1331 |
* @uses RF_Controller::invokePlugin() "freshenedUserFeedItems" event, |
1332 |
* parameters: {@link RF_User $user}, {@link RF_Feed $feed}, array {@link RF_Item $items}. |
1333 |
*/ |
1334 |
function freshenFeedItems(&$feed, &$items) |
1335 |
{ |
1336 |
foreach($this->getFeedUsers($feed) as $user) { |
1337 |
|
1338 |
$userdata_controller = new RF_Userdata_Controller($this, $user); |
1339 |
|
1340 |
if(count($items)) { |
1341 |
$userdata_controller->markItemsUnread($items); |
1342 |
|
1343 |
$published = isset($feed->published) |
1344 |
? $feed->published |
1345 |
: reset($this->getFeedUserdata($feed, $user, 'published', 'numeric')); |
1346 |
|
1347 |
if($published) |
1348 |
$userdata_controller->markItemsPublished($items); |
1349 |
|
1350 |
$this->invokePlugin('freshenedUserFeedItems', array(&$user, &$feed, &$items)); |
1351 |
} |
1352 |
|
1353 |
$this->freshenUserFeedUsage($user, $feed); |
1354 |
} |
1355 |
} |
1356 |
|
1357 |
/** |
1358 |
* Delete feed items that are are too old. |
1359 |
* |
1360 |
* Criteria are based on a user's specified deletion behavior: |
1361 |
* - {@link RF_User::itemKeepDays() Number of days} that items should be kept around |
1362 |
* - {@link RF_User::itemKeepUnread() Whether to consider unread items} for deletion |
1363 |
* |
1364 |
* @param RF_Feed $feed Feed to which items belong |
1365 |
* @param array $except_items Instances of {@link RF_Item RF_Item} to keep |
1366 |
* |
1367 |
* @uses RF_Controller::getWriteHandle() |
1368 |
* @uses RF_Controller::getFeedUsers() |
1369 |
* @uses RF_User::itemKeepDays() |
1370 |
* @uses RF_User::itemKeepUnread() |
1371 |
* @uses RF_Controller::readFromDatabase() |
1372 |
* @uses RF_Controller::writeToDatabase() |
1373 |
* @uses RF_Controller::freshenUserFeedUsage() |
1374 |
* @uses RF_Controller::$db_transactions |
1375 |
* @uses RF_Controller::invokePlugin() "flushedObsoleteUserItems" event, |
1376 |
* parameters: {@link RF_User $user}, {@link RF_Feed $feed}, array {@link RF_Item $items}. |
1377 |
* @uses RF_Controller::invokePlugin() "flushedObsoleteItems" event, |
1378 |
* parameters: {@link RF_Feed $feed}, array {@link RF_Item $items}. |
1379 |
*/ |
1380 |
function flushObsoleteFeedItems(&$feed, &$except_items) |
1381 |
{ |
1382 |
//error_log('flushing old items from feed #'.$feed->getID().' (except '.count($except_items).' items)...'); |
1383 |
|
1384 |
$dbhw =& $this->getWriteHandle(); |
1385 |
|
1386 |
$flushed_item_ids = array(); |
1387 |
|
1388 |
foreach($this->getFeedUsers($feed) as $user) { |
1389 |
|
1390 |
if($user->itemKeepDays() === false) |
1391 |
continue; |
1392 |
|
1393 |
$except_item_ids = array(); |
1394 |
|
1395 |
foreach($except_items as $except_item) |
1396 |
$except_item_ids[] = intval($except_item->getID()); |
1397 |
|
1398 |
$except_test = count($except_item_ids) |
1399 |
? sprintf('i.id NOT IN (%s)', join(',', $except_item_ids)) |
1400 |
: '1'; |
1401 |
|
1402 |
$read_test = $user->itemKeepUnread() |
1403 |
? '`read` = 1' |
1404 |
: '`read` IN (1, 0)'; |
1405 |
|
1406 |
// look for items that are old and unloved |
1407 |
$q = sprintf("SELECT i.id AS id, |
1408 |
id.value_numeric AS `read`, |
1409 |
IF(id_pub.value_numeric, 1, 0) AS published |
1410 |
|
1411 |
FROM %s AS i |
1412 |
|
1413 |
LEFT JOIN %s AS id |
1414 |
ON id.item_id = i.id |
1415 |
AND id.user_id = %d |
1416 |
AND id.label = 'read' |
1417 |
|
1418 |
LEFT JOIN %s AS id_pub |
1419 |
ON id_pub.item_id = i.id |
1420 |
AND id_pub.user_id = %d |
1421 |
AND id_pub.label = 'published' |
1422 |
|
1423 |
WHERE i.feed_id = %d |
1424 |
AND i.timestamp < NOW() - INTERVAL %d DAY |
1425 |
AND {$except_test} |
1426 |
|
1427 |
HAVING {$read_test} |
1428 |
AND published = 0", |
1429 |
$dbhw->quoteIdentifier(RF_Item::tableName()), |
1430 |
$dbhw->quoteIdentifier(RF_Item::userdataTableName()), |
1431 |
$user->getID(), |
1432 |
$dbhw->quoteIdentifier(RF_Item::userdataTableName()), |
1433 |
$user->getID(), |
1434 |
$feed->getID(), |
1435 |
$user->itemKeepDays()); |
1436 |
|
1437 |
$result = $this->readFromDatabase($q."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); //error_log($q); |
1438 |
|
1439 |
$items = array(); |
1440 |
$item_ids = array(); |
1441 |
|
1442 |
while($item = $result->fetchRow(DB_FETCHMODE_ASSOC)) { |
1443 |
$items[] = new RF_Item($item); |
1444 |
$item_ids[] = intval($item['id']); |
1445 |
} |
1446 |
|
1447 |
$flushed_item_ids = array_merge($flushed_item_ids, $item_ids); |
1448 |
|
1449 |
//error_log('Going to remove '.count($item_ids).' items from feed #'.$feed->getID().' for user #'.$user->getID()); |
1450 |
|
1451 |
if(count($item_ids)) { |
1452 |
|
1453 |
$q = sprintf("DELETE FROM %s |
1454 |
WHERE item_id IN (%s) |
1455 |
AND user_id = %d", |
1456 |
$dbhw->quoteIdentifier(RF_Item::userdataTableName()), |
1457 |
join(',', $item_ids), |
1458 |
$user->getID()); |
1459 |
|
1460 |
$this->writeToDatabase($q."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); //error_log($q); |
1461 |
|
1462 |
$this->invokePlugin('flushedObsoleteUserItems', array(&$user, &$feed, &$items)); |
1463 |
$this->freshenUserFeedUsage($user, $feed); |
1464 |
} |
1465 |
} |
1466 |
|
1467 |
// So far, only users' item data has been deleted - the items table remains untouched. |
1468 |
// If any items no longer have associated user data, delete them completely. |
1469 |
|
1470 |
if(count($flushed_item_ids)) { |
1471 |
|
1472 |
if($this->db_transactions) |
1473 |
$this->writeToDatabase("START TRANSACTION"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1474 |
|
1475 |
$flushed_item_ids = array_unique($flushed_item_ids); |
1476 |
|
1477 |
//error_log('Going to consider '.count($flushed_item_ids).' items from feed #'.$feed->getID().' for total removal'); |
1478 |
|
1479 |
// look for items that lack associated user data |
1480 |
$q = sprintf("SELECT i.id AS id |
1481 |
FROM %s AS i |
1482 |
LEFT JOIN %s AS id |
1483 |
ON id.item_id = i.id |
1484 |
WHERE i.id IN (%s) |
1485 |
AND id.item_id IS NULL", |
1486 |
$dbhw->quoteIdentifier(RF_Item::tableName()), |
1487 |
$dbhw->quoteIdentifier(RF_Item::userdataTableName()), |
1488 |
join(',', $flushed_item_ids)); |
1489 |
|
1490 |
// technically, we're reading here - just want to make |
1491 |
// sure to use the right database connection for the |
1492 |
// transaction we're wrapped up in |
1493 |
$result = $this->writeToDatabase($q."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); //error_log($q); |
1494 |
|
1495 |
$items = array(); |
1496 |
$obliterated_item_ids = array(); |
1497 |
|
1498 |
while($item = $result->fetchRow(DB_FETCHMODE_ASSOC)) { |
1499 |
$items[] = new RF_Item($item); |
1500 |
$obliterated_item_ids[] = intval($item['id']); |
1501 |
} |
1502 |
|
1503 |
if(count($obliterated_item_ids)) { |
1504 |
|
1505 |
//error_log('Going to obliterate '.count($obliterated_item_ids).' items from feed #'.$feed->getID()); |
1506 |
|
1507 |
$q = sprintf("DELETE FROM %s |
1508 |
WHERE id IN (%s)", |
1509 |
$dbhw->quoteIdentifier(RF_Item::tableName()), |
1510 |
join(',', $obliterated_item_ids)); |
1511 |
|
1512 |
$this->writeToDatabase($q."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); //error_log($q); |
1513 |
|
1514 |
$this->invokePlugin('flushedObsoleteItems', array(&$feed, &$items)); |
1515 |
} |
1516 |
} |
1517 |
|
1518 |
if($this->db_transactions) |
1519 |
$this->writeToDatabase("COMMIT"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1520 |
} |
1521 |
|
1522 |
/** |
1523 |
* Update usage statistics regarding item counts (unread, published, etc.) |
1524 |
* and update times in feed userdata table. |
1525 |
* |
1526 |
* @param RF_User $user - |
1527 |
* @param RF_Feed $feed Feed to which items belong |
1528 |
* |
1529 |
* @uses RF_Controller::getReadHandle() |
1530 |
* @uses RF_Controller::setFeedUserdata() |
1531 |
*/ |
1532 |
function freshenUserFeedUsage(&$user, &$feed) |
1533 |
{ |
1534 |
// Feed might not exist for self-published items |
1535 |
if(!$feed->getID()) |
1536 |
return; |
1537 |
|
1538 |
$dbhr =& $this->getReadHandle(); |
1539 |
|
1540 |
$query = sprintf("SELECT # NULL's don't count toward the total & unread counts |
1541 |
SUM(IF(id.value_numeric IS NOT NULL, |
1542 |
1, 0)) AS total, |
1543 |
SUM(IF(id.value_numeric = 0, 1, 0)) AS unread, |
1544 |
|
1545 |
SUM(IF(id_pub.value_numeric, 1, 0)) AS published, |
1546 |
|
1547 |
UNIX_TIMESTAMP( |
1548 |
MAX(IF(id.value_numeric = 0, |
1549 |
id.timestamp, 0))) AS last_update |
1550 |
|
1551 |
FROM %s AS i |
1552 |
|
1553 |
LEFT JOIN %s AS id |
1554 |
ON id.item_id = i.id |
1555 |
AND id.user_id = %d |
1556 |
AND id.label = 'read' |
1557 |
|
1558 |
LEFT JOIN %s AS id_pub |
1559 |
ON id_pub.item_id = i.id |
1560 |
AND id_pub.user_id = %d |
1561 |
AND id_pub.label = 'published' |
1562 |
|
1563 |
WHERE i.feed_id = %d", |
1564 |
|
1565 |
$dbhr->quoteIdentifier(RF_Item::tableName()), |
1566 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
1567 |
$user->getID(), |
1568 |
$dbhr->quoteIdentifier(RF_Item::userdataTableName()), |
1569 |
$user->getID(), |
1570 |
$feed->getID()); |
1571 |
|
1572 |
$result = $dbhr->getRow($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__, DB_FETCHMODE_ASSOC); |
1573 |
|
1574 |
if(DB::isError($result)) |
1575 |
die(sprintf('<p><b>%s.</b></p><pre>%s</pre>', htmlspecialchars($result->getMessage()), htmlspecialchars($result->getDebugInfo()))); |
1576 |
|
1577 |
foreach($result as $metric => $value) |
1578 |
$this->setFeedUserdata($feed, $user, "usage_{$metric}", intval($value), 'numeric'); |
1579 |
} |
1580 |
|
1581 |
/** |
1582 |
* Check whether a feed exists in the database, based on its URL. |
1583 |
* |
1584 |
* @param string $url Address of feed to be checked. |
1585 |
* |
1586 |
* @return mixed Requested {@link RF_Feed feed}, or false if none found. |
1587 |
* |
1588 |
* @uses RF_Controller::getReadHandle() |
1589 |
*/ |
1590 |
function feedExistsWithURL($url) |
1591 |
{ |
1592 |
$dbhr =& $this->getReadHandle(); |
1593 |
|
1594 |
$query = sprintf("SELECT id, url, title, link, description |
1595 |
FROM %s |
1596 |
WHERE url = %s", |
1597 |
$dbhr->quoteIdentifier(RF_Feed::tableName()), |
1598 |
$dbhr->quoteSmart($url)); |
1599 |
|
1600 |
$result = $dbhr->getRow($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__, DB_FETCHMODE_ASSOC); |
1601 |
|
1602 |
return $result |
1603 |
? new RF_Feed($result) |
1604 |
: false; |
1605 |
} |
1606 |
|
1607 |
/** |
1608 |
* Check whether an item exists in the database, based on its GUID |
1609 |
* |
1610 |
* @param RF_Feed $feed Feed to be checked for the item. |
1611 |
* @param string $guid GUID of the item in question. |
1612 |
* |
1613 |
* @return mixed Requested {@link RF_Item item}, or false if none found. |
1614 |
* |
1615 |
* @uses RF_Controller::getReadHandle() |
1616 |
*/ |
1617 |
function itemExistsWithGUID($feed, $guid) |
1618 |
{ |
1619 |
$dbhr =& $this->getReadHandle(); |
1620 |
|
1621 |
$query = sprintf("SELECT i.id, i.feed_id, i.guid, i.title, i.link, i.content, i.modified, i.author, i.category |
1622 |
FROM %s AS i |
1623 |
WHERE i.feed_id = %d |
1624 |
AND i.guid = %s", |
1625 |
$dbhr->quoteIdentifier(RF_Item::tableName()), |
1626 |
$feed->getID(), |
1627 |
$dbhr->quoteSmart($guid)); |
1628 |
|
1629 |
$result = $dbhr->getRow($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__, DB_FETCHMODE_ASSOC); |
1630 |
|
1631 |
// `modified' is returned in "YYYY-MM-DD HH:MM:SS" format |
1632 |
if(isset($result['modified'])) |
1633 |
$result['modified'] = strtotime($result['modified']); |
1634 |
|
1635 |
return $result |
1636 |
? new RF_Item($result) |
1637 |
: false; |
1638 |
} |
1639 |
|
1640 |
/** |
1641 |
* Save a feed into the database. |
1642 |
* @param RF_Feed $feed - |
1643 |
* @return boolean True |
1644 |
* @uses RF_Controller::getWriteHandle() |
1645 |
* @uses RF_Controller::getReadHandle() |
1646 |
*/ |
1647 |
function saveFeed(&$feed) |
1648 |
{ |
1649 |
$dbhw =& $this->getWriteHandle(); |
1650 |
$data = $feed->columnNamesValues(); |
1651 |
|
1652 |
// the insert timestamp only gets set the first time. |
1653 |
if(!$feed->getID()) |
1654 |
$data['insert_timestamp'] = date('YmdHis'); |
1655 |
|
1656 |
$query = $feed->getID() |
1657 |
? $dbhw->autoPrepare($feed->tableName(), array_keys($data), DB_AUTOQUERY_UPDATE, 'id = '.$dbhw->quoteSmart($feed->getID())) |
1658 |
: $dbhw->autoPrepare($feed->tableName(), array_keys($data), DB_AUTOQUERY_INSERT); |
1659 |
|
1660 |
$result = $dbhw->execute($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__, array_values($data)); |
1661 |
|
1662 |
if(DB::isError($result)) |
1663 |
die(join(":", array($result->getMessage(), $result->getDebugInfo()))); |
1664 |
|
1665 |
$feed->setID($feed->getID() |
1666 |
? $feed->getID() |
1667 |
: $dbhw->getOne("SELECT LAST_INSERT_ID()"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__)); |
1668 |
|
1669 |
return true; |
1670 |
} |
1671 |
|
1672 |
/** |
1673 |
* Save an item into the database. |
1674 |
* @param RF_Item $item - |
1675 |
* @return boolean True |
1676 |
* @uses RF_Controller::getWriteHandle() |
1677 |
* @uses RF_Controller::getReadHandle() |
1678 |
*/ |
1679 |
function saveItem(&$item) |
1680 |
{ |
1681 |
$dbhw =& $this->getWriteHandle(); |
1682 |
$data = $item->columnNamesValues(); |
1683 |
|
1684 |
// sanitize all links being saved with single quote |
1685 |
// Thanks to TDavid: http://www.php-scripts.com/20060617/86/ |
1686 |
$data['link'] = str_replace("'","%27",$data['link']); |
1687 |
|
1688 |
// the insert timestamp only gets set the first time. |
1689 |
if(!$item->getID()) |
1690 |
$data['insert_timestamp'] = date('YmdHis'); |
1691 |
|
1692 |
$query = $item->getID() |
1693 |
? $dbhw->autoPrepare($item->tableName(), array_keys($data), DB_AUTOQUERY_UPDATE, 'id = '.$dbhw->quoteSmart($item->getID())) |
1694 |
: $dbhw->autoPrepare($item->tableName(), array_keys($data), DB_AUTOQUERY_INSERT); |
1695 |
|
1696 |
$result = $dbhw->execute($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__, array_values($data)); |
1697 |
|
1698 |
if(DB::isError($result)) |
1699 |
die(join(":", array($result->getMessage(), $result->getDebugInfo()))); |
1700 |
|
1701 |
$item->setID($item->getID() |
1702 |
? $item->getID() |
1703 |
: $dbhw->getOne("SELECT LAST_INSERT_ID()"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__)); |
1704 |
|
1705 |
return true; |
1706 |
} |
1707 |
|
1708 |
/** |
1709 |
* Retrieve labeled metadata for a given user's RSS item. |
1710 |
* |
1711 |
* @param RF_Item $item Item to match |
1712 |
* @param RF_User $user User to match |
1713 |
* @param string $label Label to match |
1714 |
* @param string $format Format to match, one of: |
1715 |
* - "numeric" for integer value |
1716 |
* - "short" for short string |
1717 |
* - "long" for long string |
1718 |
* |
1719 |
* @return array Array of retrieved values. |
1720 |
* |
1721 |
* @uses RF_Controller::getReadHandle() |
1722 |
* @uses RF_Controller::readFromDatabase() |
1723 |
* @uses RF_Item::userdataTableName() |
1724 |
* @see RF_Controller::setItemUserdata() |
1725 |
* @see RF_Controller::setItemsUserdata() |
1726 |
* @see RF_Controller::removeItemUserdata() |
1727 |
*/ |
1728 |
function getItemUserdata($item, $user, $label, $format) |
1729 |
{ |
1730 |
$dbhr =& $this->getReadHandle(); |
1731 |
|
1732 |
$query = sprintf("SELECT value_%s AS value |
1733 |
FROM %s |
1734 |
WHERE user_id = %d |
1735 |
AND item_id = %d |
1736 |
AND label = %s", |
1737 |
$dbhr->escapeSimple($format), |
1738 |
$dbhr->quoteIdentifier($item->userdataTableName()), |
1739 |
(is_null($user) ? 0 : $user->getID()), |
1740 |
$item->getID(), |
1741 |
$dbhr->quoteSmart($label)); |
1742 |
|
1743 |
$values = array(); |
1744 |
$result = $this->readFromDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1745 |
|
1746 |
while($value = $result->fetchRow(DB_FETCHMODE_ASSOC)) |
1747 |
$values[] = $value['value']; |
1748 |
|
1749 |
return $values; |
1750 |
} |
1751 |
|
1752 |
/** |
1753 |
* Retrieve labeled metadata for a given user's RSS feed. |
1754 |
* |
1755 |
* @param RF_Feed $feed Feed to match |
1756 |
* @param RF_User $user User to match |
1757 |
* @param string $label Label to match |
1758 |
* @param string $format Format to match, one of: |
1759 |
* - "numeric" for integer value |
1760 |
* - "short" for short string |
1761 |
* - "long" for long string |
1762 |
* |
1763 |
* @return array Array of retrieved values. |
1764 |
* |
1765 |
* @uses RF_Controller::getReadHandle() |
1766 |
* @uses RF_Controller::readFromDatabase() |
1767 |
* @uses RF_Feed::userdataTableName() |
1768 |
* @see RF_Controller::setFeedUserdata() |
1769 |
* @see RF_Controller::setFeedsUserdata() |
1770 |
* @see RF_Controller::removeFeedUserdata() |
1771 |
*/ |
1772 |
function getFeedUserdata($feed, $user, $label, $format) |
1773 |
{ |
1774 |
$dbhr =& $this->getReadHandle(); |
1775 |
|
1776 |
$query = sprintf("SELECT value_%s AS value |
1777 |
FROM %s |
1778 |
WHERE user_id = %d |
1779 |
AND feed_id = %d |
1780 |
AND label = %s", |
1781 |
$dbhr->escapeSimple($format), |
1782 |
$dbhr->quoteIdentifier($feed->userdataTableName()), |
1783 |
(is_null($user) ? 0 : $user->getID()), |
1784 |
$feed->getID(), |
1785 |
$dbhr->quoteSmart($label)); |
1786 |
|
1787 |
$values = array(); |
1788 |
$result = $this->readFromDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1789 |
|
1790 |
while($value = $result->fetchRow(DB_FETCHMODE_ASSOC)) |
1791 |
$values[] = $value['value']; |
1792 |
|
1793 |
return $values; |
1794 |
} |
1795 |
|
1796 |
/** |
1797 |
* Assign labeled metadata to a given user's RSS item. |
1798 |
* |
1799 |
* @param RF_Item $item Item to assign to |
1800 |
* @param RF_User $user User to assign to |
1801 |
* @param string $label Label to assign to |
1802 |
* @param mixed $values Value or array of values to assign to - note format! |
1803 |
* @param string $format Format to assign to, one of: |
1804 |
* - "numeric" for integer value |
1805 |
* - "short" for short string |
1806 |
* - "long" for long string |
1807 |
* |
1808 |
* @return boolean Return value of {@link RF_Controller::setItemsUserdata() RF_Controller::setItemsUserdata()} |
1809 |
* |
1810 |
* @uses RF_Controller::setItemsUserdata() |
1811 |
* @see RF_Controller::getItemUserdata() |
1812 |
* @see RF_Controller::removeItemUserdata() |
1813 |
*/ |
1814 |
function setItemUserdata(&$item, &$user, $label, $values, $format) |
1815 |
{ |
1816 |
return $this->setItemsUserdata(array(&$item), $user, $label, $values, $format); |
1817 |
} |
1818 |
|
1819 |
/** |
1820 |
* Assign labeled metadata to a given user's RSS feed. |
1821 |
* |
1822 |
* @param RF_Feed $feed Feed to assign to |
1823 |
* @param RF_User $user User to assign to |
1824 |
* @param string $label Label to assign to |
1825 |
* @param mixed $values Value or array of values to assign to - note format! |
1826 |
* @param string $format Format to assign to, one of: |
1827 |
* - "numeric" for integer value |
1828 |
* - "short" for short string |
1829 |
* - "long" for long string |
1830 |
* |
1831 |
* @return boolean Return value of {@link RF_Controller::setFeedsUserdata() RF_Controller::setFeedsUserdata()} |
1832 |
* |
1833 |
* @uses RF_Controller::setFeedsUserdata() |
1834 |
* @see RF_Controller::getFeedUserdata() |
1835 |
* @see RF_Controller::removeFeedUserdata() |
1836 |
*/ |
1837 |
function setFeedUserdata(&$feed, &$user, $label, $values, $format) |
1838 |
{ |
1839 |
return $this->setFeedsUserdata(array(&$feed), $user, $label, $values, $format); |
1840 |
} |
1841 |
|
1842 |
/** |
1843 |
* Remove labeled metadata from a given user's RSS feed. |
1844 |
* |
1845 |
* @param RF_Feed $feed Feed to assign to |
1846 |
* @param RF_User $user User to assign to |
1847 |
* @param string $label Label to assign to |
1848 |
* |
1849 |
* @return boolean Return value of {@link RF_Controller::writeToDatabase() RF_Controller::writeToDatabase()} |
1850 |
* |
1851 |
* @uses RF_Controller::getReadHandle() |
1852 |
* @uses RF_Controller::writeToDatabase() |
1853 |
* @see RF_Controller::getFeedUserdata() |
1854 |
* @see RF_Controller::setFeedUserdata() |
1855 |
* @see RF_Controller::setFeedsUserdata() |
1856 |
*/ |
1857 |
function removeFeedUserdata(&$feed, &$user, $label) |
1858 |
{ |
1859 |
$dbhr =& $this->getReadHandle(); |
1860 |
|
1861 |
$query = sprintf("DELETE FROM %s |
1862 |
WHERE user_id = %d |
1863 |
AND feed_id = %d |
1864 |
AND label = %s", |
1865 |
$dbhr->quoteIdentifier($feed->userdataTableName()), |
1866 |
(is_null($user) ? 0 : $user->getID()), |
1867 |
$feed->getID(), |
1868 |
$dbhr->quoteSmart($label)); |
1869 |
|
1870 |
return $this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1871 |
} |
1872 |
|
1873 |
/** |
1874 |
* Remove labeled metadata from a given user's RSS item. |
1875 |
* |
1876 |
* @param RF_Item $item Item to assign to |
1877 |
* @param RF_User $user User to assign to |
1878 |
* @param string $label Label to assign to |
1879 |
* |
1880 |
* @return boolean Return value of {@link RF_Controller::writeToDatabase() RF_Controller::writeToDatabase()} |
1881 |
* |
1882 |
* @uses RF_Controller::getReadHandle() |
1883 |
* @uses RF_Item::userdataTableName() |
1884 |
* @uses RF_Controller::writeToDatabase() |
1885 |
* @see RF_Controller::getItemUserdata() |
1886 |
* @see RF_Controller::setItemUserdata() |
1887 |
* @see RF_Controller::setItemsUserdata() |
1888 |
*/ |
1889 |
function removeItemUserdata(&$item, &$user, $label) |
1890 |
{ |
1891 |
$dbhr =& $this->getReadHandle(); |
1892 |
|
1893 |
$query = sprintf("DELETE FROM %s |
1894 |
WHERE user_id = %d |
1895 |
AND item_id = %d |
1896 |
AND label = %s", |
1897 |
$dbhr->quoteIdentifier($item->userdataTableName()), |
1898 |
(is_null($user) ? 0 : $user->getID()), |
1899 |
$item->getID(), |
1900 |
$dbhr->quoteSmart($label)); |
1901 |
|
1902 |
return $this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1903 |
} |
1904 |
|
1905 |
/** |
1906 |
* Assign labeled metadata to a given user's RSS items. |
1907 |
* |
1908 |
* @param array $items Items to assign to - instances of RF_Item |
1909 |
* @param RF_User $user User to assign to |
1910 |
* @param string $label Label to assign to |
1911 |
* @param mixed $values Value or array of values to assign to - note format! |
1912 |
* @param string $format Format to assign to, one of: |
1913 |
* - "numeric" for integer value |
1914 |
* - "short" for short string |
1915 |
* - "long" for long string |
1916 |
* |
1917 |
* @return boolean True |
1918 |
* |
1919 |
* @uses RF_Controller::getReadHandle() |
1920 |
* @uses RF_Item::userdataTableName() |
1921 |
* @uses RF_Controller::$db_transactions |
1922 |
* @uses RF_Controller::writeToDatabase() |
1923 |
* @see RF_Controller::getItemUserdata() |
1924 |
* @see RF_Controller::setItemUserdata() |
1925 |
* @see RF_Controller::removeItemUserdata() |
1926 |
*/ |
1927 |
function setItemsUserdata($items, $user, $label, $values, $format) |
1928 |
{ |
1929 |
$dbhr =& $this->getReadHandle(); |
1930 |
|
1931 |
if(!count($items)) |
1932 |
return true; |
1933 |
|
1934 |
if(!is_array($values)) |
1935 |
$values = array($values); |
1936 |
|
1937 |
$item_ids = array(); |
1938 |
|
1939 |
foreach($items as $item) |
1940 |
$item_ids[] = $item->getID(); |
1941 |
|
1942 |
if($this->db_transactions) |
1943 |
$this->writeToDatabase("START TRANSACTION"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1944 |
|
1945 |
// delete existing userdata |
1946 |
$query = sprintf("DELETE FROM %s |
1947 |
WHERE user_id = %d |
1948 |
AND item_id IN (%s) |
1949 |
AND label = %s", |
1950 |
$dbhr->quoteIdentifier($item->userdataTableName()), |
1951 |
(is_null($user) ? 0 : $user->getID()), |
1952 |
join(', ', $item_ids), |
1953 |
$dbhr->quoteSmart($label)); |
1954 |
|
1955 |
$this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1956 |
|
1957 |
if(count($values) > 0) { |
1958 |
|
1959 |
// generate SQL value tuples |
1960 |
$value_tuples = array(); |
1961 |
|
1962 |
foreach($items as $item) |
1963 |
foreach($values as $value) |
1964 |
$value_tuples[] = sprintf("(%d, %d, %s, %s)", |
1965 |
(is_null($user) ? 0 : $user->getID()), |
1966 |
$item->getID(), |
1967 |
$dbhr->quoteSmart($label), |
1968 |
$dbhr->quoteSmart($value)); |
1969 |
|
1970 |
// add new user data |
1971 |
$query = sprintf("INSERT INTO %s |
1972 |
(user_id, item_id, label, value_%s) |
1973 |
VALUES %s", |
1974 |
$dbhr->quoteIdentifier($items[0]->userdataTableName()), |
1975 |
$dbhr->escapeSimple($format), |
1976 |
join(', ', $value_tuples)); |
1977 |
|
1978 |
$this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1979 |
} |
1980 |
|
1981 |
if($this->db_transactions) |
1982 |
$this->writeToDatabase("COMMIT"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
1983 |
|
1984 |
return true; |
1985 |
} |
1986 |
|
1987 |
/** |
1988 |
* Assign labeled metadata to a given user's RSS feeds. |
1989 |
* |
1990 |
* @param array $feeds Feeds to assign to - instances of RF_Feed |
1991 |
* @param RF_User $user User to assign to |
1992 |
* @param string $label Label to assign to |
1993 |
* @param mixed $values Value or array of values to assign to - note format! |
1994 |
* @param string $format Format to assign to, one of: |
1995 |
* - "numeric" for integer value |
1996 |
* - "short" for short string |
1997 |
* - "long" for long string |
1998 |
* |
1999 |
* @return boolean True |
2000 |
* |
2001 |
* @uses RF_Controller::getReadHandle() |
2002 |
* @uses RF_Feed::userdataTableName() |
2003 |
* @uses RF_Controller::$db_transactions |
2004 |
* @uses RF_Controller::writeToDatabase() |
2005 |
* @see RF_Controller::getFeedUserdata() |
2006 |
* @see RF_Controller::setFeedUserdata() |
2007 |
* @see RF_Controller::removeFeedUserdata() |
2008 |
*/ |
2009 |
function setFeedsUserdata($feeds, $user, $label, $values, $format) |
2010 |
{ |
2011 |
$dbhr =& $this->getReadHandle(); |
2012 |
|
2013 |
if(!count($feeds)) |
2014 |
return true; |
2015 |
|
2016 |
if(!is_array($values)) |
2017 |
$values = array($values); |
2018 |
|
2019 |
$feed_ids = array(); |
2020 |
|
2021 |
foreach($feeds as $feed) |
2022 |
$feed_ids[] = $feed->getID(); |
2023 |
|
2024 |
if($this->db_transactions) |
2025 |
$this->writeToDatabase("START TRANSACTION"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
2026 |
|
2027 |
// delete existing userdata |
2028 |
$query = sprintf("DELETE FROM %s |
2029 |
WHERE user_id = %d |
2030 |
AND feed_id IN (%s) |
2031 |
AND label = %s", |
2032 |
$dbhr->quoteIdentifier($feed->userdataTableName()), |
2033 |
(is_null($user) ? 0 : $user->getID()), |
2034 |
join(', ', $feed_ids), |
2035 |
$dbhr->quoteSmart($label)); |
2036 |
|
2037 |
$this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
2038 |
|
2039 |
if(count($values) > 0) { |
2040 |
|
2041 |
// generate SQL value tuples |
2042 |
$value_tuples = array(); |
2043 |
|
2044 |
foreach($feeds as $feed) |
2045 |
foreach($values as $value) |
2046 |
$value_tuples[] = sprintf("(%d, %d, %s, %s)", |
2047 |
(is_null($user) ? 0 : $user->getID()), |
2048 |
$feed->getID(), |
2049 |
$dbhr->quoteSmart($label), |
2050 |
$dbhr->quoteSmart($value)); |
2051 |
|
2052 |
// add new user data |
2053 |
$query = sprintf("INSERT INTO %s |
2054 |
(user_id, feed_id, label, value_%s) |
2055 |
VALUES %s", |
2056 |
$dbhr->quoteIdentifier($feeds[0]->userdataTableName()), |
2057 |
$dbhr->escapeSimple($format), |
2058 |
join(', ', $value_tuples)); |
2059 |
|
2060 |
$this->writeToDatabase($query."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
2061 |
} |
2062 |
|
2063 |
if($this->db_transactions) |
2064 |
$this->writeToDatabase("COMMIT"."\n# Context: ".__CLASS__.":".__FUNCTION__."() ".__FILE__.":".__LINE__); |
2065 |
|
2066 |
return true; |
2067 |
} |
2068 |
|
2069 |
/** |
2070 |
* Prepare arguments for additional userdata to be |
2071 |
* passed to getUserItems or getUserFeeds. |
2072 |
* |
2073 |
* @param array source Array of metadata information: |
2074 |
* {label: {format: value}, label: {format: value}, ...} |
2075 |
* @return array Array of metadata information: |
2076 |
* [{"format": format, "label": label, "value": value}, ...] |
2077 |
* |
2078 |
* @see RF_Controller::getUserItems() |
2079 |
* @see RF_Controller::getUserFeeds() |
2080 |
*/ |
2081 |
function prepareUserdataArguments($source) |
2082 |
{ |
2083 |
// these seem to get used all the time |
2084 |
$destination = array(array('format' => 'long', 'label' => 'comment'), |
2085 |
array('format' => 'long', 'label' => 'tags')); |
2086 |
|
2087 |
/* |
2088 |
source[metadata] should look like: |
2089 |
[{label: {format: value}}, {label: {format: value}}, ...] |
2090 |
*/ |
2091 |
if(is_array($source)) |
2092 |
foreach($source as $label => $metadatum) |
2093 |
if(is_array($metadatum)) |
2094 |
foreach($metadatum as $format => $value) |
2095 |
$destination[] = compact('format', 'label', 'value'); |
2096 |
|
2097 |
/* |
2098 |
return an array composed of triplets: |
2099 |
{'label': label, 'format': format, 'value': value} |
2100 |
'value' is optional, it's used for matches. |
2101 |
this is passed as an element of the $args array, |
2102 |
the second parameter to RF_Controller::getUserItems() |
2103 |
*/ |
2104 |
return $destination; |
2105 |
} |
2106 |
|
2107 |
/** |
2108 |
* Find plugin files. |
2109 |
* |
2110 |
* Looks in plugins directory, and notes every encountered file |
2111 |
* named "*.plugin.php". Looks in subdirectories recursively. |
2112 |
* |
2113 |
* @param string $dir Optional starting directory - omit on initial call |
2114 |
* @return array Array of file names |
2115 |
*/ |
2116 |
function findPlugins($dir=null) |
2117 |
{ |
2118 |
$plugin_files = array(); |
2119 |
|
2120 |
$plugins_dir = is_null($dir) |
2121 |
? realpath(join(DIRECTORY_SEPARATOR, array(dirname(__FILE__), '..', '..', 'plugins'))) |
2122 |
: $dir; |
2123 |
|
2124 |
// Find and remember all plug-in files |
2125 |
if(is_dir($plugins_dir) && $d = opendir($plugins_dir)) |
2126 |
while($f = readdir($d)) { |
2127 |
|
2128 |
$p = $plugins_dir.DIRECTORY_SEPARATOR.$f; |
2129 |
|
2130 |
if(is_file($p) && preg_match('/\.plugin\.php$/i', $f)) |
2131 |
$plugin_files[] = $p; |
2132 |
|
2133 |
if(is_dir($p) && !in_array($f, array('.', '..'))) |
2134 |
$plugin_files = array_merge($plugin_files, $this->findPlugins($p)); |
2135 |
} |
2136 |
|
2137 |
if($d) |
2138 |
closedir($d); |
2139 |
|
2140 |
return $plugin_files; |
2141 |
} |
2142 |
|
2143 |
/** |
2144 |
* Load plugin classes. |
2145 |
* |
2146 |
* Finds all plugin files and includes them. Then, looks through all |
2147 |
* declared classes and instantiates those with $activePlugin === true |
2148 |
* into {@link RF_Controller::$plugins $plugins} array. |
2149 |
* |
2150 |
* @uses RF_Controller::findPlugins() |
2151 |
* @uses RF_Controller::$plugins Assigned after finding active plugins. |
2152 |
* @uses RF_Controller::invokePlugin() "loadedPlugins" event. |
2153 |
*/ |
2154 |
function loadPlugins() |
2155 |
{ |
2156 |
// first include each plug-in file - hope they're all valid php! |
2157 |
foreach($this->findPlugins() as $plugin_file) |
2158 |
include_once($plugin_file); |
2159 |
|
2160 |
// loop through all declared classes, and instantiate them |
2161 |
// into the plugins array if they have $activePlugin === true |
2162 |
foreach(get_declared_classes() as $c) |
2163 |
if(is_array(get_class_vars($c))) |
2164 |
foreach(get_class_vars($c) as $var => $val) |
2165 |
if($var == 'activePlugin' && $val === true) |
2166 |
$this->plugins[$c] = new $c($this); |
2167 |
|
2168 |
// while we're at it, mash all the members of remoteMethods |
2169 |
// to lowercase so invokePlugin can figure out what to do. |
2170 |
foreach($this->plugins as $p => $plugin) |
2171 |
if(isset($plugin->remoteMethods) && is_array($plugin->remoteMethods)) |
2172 |
foreach($plugin->remoteMethods as $m => $method) |
2173 |
$this->plugins[$p]->remoteMethods[$m] = strtolower($method); |
2174 |
|
2175 |
$this->invokePlugin('loadedPlugins'); |
2176 |
} |
2177 |
|
2178 |
/** |
2179 |
* Invoke plugins. |
2180 |
* |
2181 |
* Invokes methods matching $event on each plugin in the plugins array. |
2182 |
* If a given plugin lacks the named method, it is skipped. |
2183 |
* Alternatively, a specific plug=in may be invoked by using dot-syntax |
2184 |
* method names, e.g. "plugin_name.event", where the plug-in's name |
2185 |
* is its class. |
2186 |
* |
2187 |
* @param string $event Method to invoke on each plugin. |
2188 |
* @param array $params Arguments to pass to invoked method. |
2189 |
* @param boolean $remote Was this invokation the result of a remote API call? |
2190 |
* |
2191 |
* @return mixed Array of return values from each responsive plug-in, |
2192 |
* or just one return value if a specific lpug-in is named. |
2193 |
* |
2194 |
* @uses RF_Controller::$plugins Searched for plugins with method names matching $event. |
2195 |
*/ |
2196 |
function invokePlugin($event, $params=array(), $remote=false) |
2197 |
{ |
2198 |
if(strpos($event, '.')) { |
2199 |
list($plugin, $event) = split('\.', strtolower($event)); |
2200 |
|
2201 |
if($plugin =& $this->plugins[$plugin]) |
2202 |
if(method_exists($plugin, $event) && (!$remote || is_array($plugin->remoteMethods) && in_array(strtolower($event), $plugin->remoteMethods))) |
2203 |
return call_user_func_array(array(&$plugin, $event), $params); |
2204 |
|
2205 |
} else { |
2206 |
$results = array(); |
2207 |
|
2208 |
foreach($this->plugins as $p => $plugin) |
2209 |
if(method_exists($plugin, $event) && (!$remote || is_array($plugin->remoteMethods) && in_array(strtolower($event), $plugin->remoteMethods))) |
2210 |
$results[$p] = call_user_func_array(array(&$this->plugins[$p], $event), $params); |
2211 |
|
2212 |
return $results; |
2213 |
} |
2214 |
} |
2215 |
|
2216 |
/** |
2217 |
* Grab a reference to the read DB handle |
2218 |
* |
2219 |
* @return DB instance of PEAR DB handle suitable for reading from the database |
2220 |
* |
2221 |
* @uses RF_Controller::$dbhr Returned upon request |
2222 |
*/ |
2223 |
function &getReadHandle() |
2224 |
{ |
2225 |
return $this->dbhr; |
2226 |
} |
2227 |
|
2228 |
/** |
2229 |
* Grab a reference to the write DB handle |
2230 |
* |
2231 |
* @return DB instance of PEAR DB handle suitable for writing to the database |
2232 |
* |
2233 |
* @uses RF_Controller::$dbhw Returned upon request |
2234 |
*/ |
2235 |
function &getWriteHandle() |
2236 |
{ |
2237 |
return $this->dbhw; |
2238 |
} |
2239 |
|
2240 |
/** |
2241 |
* Pass a read query to {@link RF_Controller::$dbhr the database}, and get the results back. |
2242 |
* |
2243 |
* @param string $query SQL query to execute. |
2244 |
* @param mixed $params Array, string or numeric data to be used in execution |
2245 |
* of the statement. Quantity of items passed must match |
2246 |
* quantity of placeholders in query: meaning 1 placeholder |
2247 |
* for non-array parameters or 1 placeholder per array element. |
2248 |
* @param array $acceptable_errors Normally any error would cause the script to die here, |
2249 |
* but this array can be populated with errors (constants |
2250 |
* from {@link DB.php DB.php}) that are quietly ignored. |
2251 |
* @return DB_result Results of query execution. |
2252 |
* |
2253 |
* @uses RF_Controller::getReadHandle() |
2254 |
*/ |
2255 |
function readFromDatabase($query, $params=array(), $acceptable_errors=array()) |
2256 |
{ |
2257 |
$dbhr =& $this->getReadHandle(); |
2258 |
|
2259 |
$result = $dbhr->query($query, $params); |
2260 |
|
2261 |
if(DB::isError($result) && !in_array($result->getCode(), $acceptable_errors)) |
2262 |
if($result->getCode() == DB_ERROR_NOSUCHTABLE || $result->getCode() == DB_ERROR_NOSUCHDB) { |
2263 |
die(sprintf('<p><b>%s.</b> Have you tried <a href="install.php">installing Refeed</a>?</p>', htmlspecialchars($result->getMessage()))); |
2264 |
|
2265 |
} else { |
2266 |
die(sprintf('<p><b>%s.</b></p><pre>%s</pre>', htmlspecialchars($result->getMessage()), htmlspecialchars($result->getDebugInfo()))); |
2267 |
|
2268 |
} |
2269 |
|
2270 |
return $result; |
2271 |
} |
2272 |
|
2273 |
/** |
2274 |
* Pass a write query to {@link RF_Controller::$dbhw the database}, and get the results back. |
2275 |
* |
2276 |
* @param string $query SQL query to execute. |
2277 |
* @param mixed $params Array, string or numeric data to be used in execution |
2278 |
* of the statement. Quantity of items passed must match |
2279 |
* quantity of placeholders in query: meaning 1 placeholder |
2280 |
* for non-array parameters or 1 placeholder per array element. |
2281 |
* @param array $acceptable_errors Normally any error would cause the script to die here, |
2282 |
* but this array can be populated with errors (constants |
2283 |
* from {@link DB.php DB.php}) that are quietly ignored. |
2284 |
* @return DB_result Results of query execution. |
2285 |
*/ |
2286 |
function writeToDatabase($query, $params=array(), $acceptable_errors=array()) |
2287 |
{ |
2288 |
$dbhr =& $this->getWriteHandle(); |
2289 |
|
2290 |
$result = $dbhr->query($query, $params); |
2291 |
|
2292 |
if(DB::isError($result) && !in_array($result->getCode(), $acceptable_errors)) |
2293 |
if($result->getCode() == DB_ERROR_NOSUCHTABLE || $result->getCode() == DB_ERROR_NOSUCHDB) { |
2294 |
die(sprintf('<p><b>%s.</b> Have you tried <a href="install.php">installing Refeed</a>?</p>', htmlspecialchars($result->getMessage()))); |
2295 |
|
2296 |
} else { |
2297 |
die(sprintf('<p><b>%s.</b></p><pre>%s</pre>', htmlspecialchars($result->getMessage()), htmlspecialchars($result->getDebugInfo()))); |
2298 |
|
2299 |
} |
2300 |
|
2301 |
return $result; |
2302 |
} |
2303 |
} |
2304 |
|
2305 |
?> |