/[drupal]/contributions/modules/planet/planet.module
ViewVC logotype

Contents of /contributions/modules/planet/planet.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.8 - (show annotations) (download) (as text)
Mon Jun 8 19:22:28 2009 UTC (5 months, 2 weeks ago) by rockyroad
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-6--1
Changes since 1.7: +631 -1019 lines
File MIME type: text/x-php
[support request] #[476042] by [swe3tdave, RockyRoad]: swe3dave development branch snapshot (2009-03-23)
See ChangeLog (new) file for details.
1 <?php
2 // $Id: planet.module Exp $
3
4 /**
5 * @file
6 * The planet module.
7 *
8 * Planet is an aggregator that allows you to aggregate the blogs for
9 * users in a given role (e.g. staff) and associate content with the
10 * users rather than as a detached feed. This provides the benefit of
11 * showing avatars with content, providing per-user aggregation of
12 * planet content in addition to blog content, etc.
13 *
14 */
15
16 /**
17 * Implementation of hook_node_info.
18 *
19 * @return An array of information on the module's node types.
20 *
21 * @ingroup planet_node
22 */
23 function planet_node_info() {
24 return array(
25 'planet' => array(
26 'name' => t('Planet Entry'),
27 'module' => 'planet',
28 'description' => t('Node to contain posts aggregated from various blogs.'),
29 )
30 );
31 }
32
33 /**
34 * Implementation of hook_perm - Defines user permissions.
35 * The suggested naming convention for permissions is "action_verb modulename".
36 *
37 * @return An array of permissions strings.
38 *
39 * @ingroup base
40 */
41 function planet_perm() {
42 return array('administer planet', 'administer own planet feeds', 'view all planet nodes');
43 }
44
45 /**
46 * Implementation of hook_help - Provides online user help.
47 *
48 * @param $path A Drupal menu router path the help is being requested for
49 * @param $arg
50 * @return A localized string containing the help text.
51 *
52 * @ingroup base
53 */
54 function planet_help($path, $arg) {
55 switch ($path) {
56 case 'admin/help#planet':
57 // The module's help text, displayed on the admin/help page and through the module's individual help link.
58 $output = '<p>Planet is an aggregator that allows you to aggregate your users blogs and to display them in a "planet" fashion. Associating content with the users rather than as a detached feed.</p>';
59 $output .= '<p>To use planet, go to admin/settings/planet and note the following sections:</p>';
60 $output .= '<ul>';
61 $output .= '<li><strong>General Settings</strong>. The filter format drop down will allow you to select the filter format that will be used to show planet entry nodes.</li>';
62 $output .= '<li><strong>Feeds</strong>. This section lets you add a new feed. Give it a title, select an author, provide the feed url, and you\'re off. You\'ll have to manually refresh it or wait for a cron run for items to be imported.</li>';
63 $output .= '<li><strong>Feeds</strong>. This section lists current feeds, when they were last updated, how many items they have, and it allows you to edit, refresh, or freeze them. Freezing is a quick way to temporarily suspend updates from the given feed.</li>';
64 // @DIFFGROUP output syntax
65 $output .= '</ul>';
66 return $output;
67 // @DIFFINFO in D6, admin/modules#description is moved to .info file
68 }
69 }
70
71 /**
72 * Implementation of hook_view() - Displays a planet node.
73 *
74 * @param $node The node to be displayed.
75 * @param $teaser Whether to generate only a summary ("teaser") of the node.
76 * @param $page Whether the node is being displayed as a standalone page.
77 * @return The modified $node parameter, properly presented for output.
78 *
79 * @ingroup planet_node
80 * @CRUD{variables,R}
81 * @CRUD{planet_items,R}
82 */
83 function planet_view($node, $teaser = FALSE, $page = FALSE) {
84 // @DIFFINFO removed $link param to match D6 hook definition
85 if ($page === true && variable_get('planet_redirect_page', 0) == 1) {
86 // @DIFFINFO only 'link' field is used, so I restricted the query
87 $obj = db_fetch_object(db_query('SELECT link FROM {planet_items} WHERE nid = %d', $node->nid));
88 // @DIFFINFO if the query succeeds, $obj->nid == $node->nid , otherwise, $obj==FALSE,
89 if ($obj && $obj->link != '') {
90 header('Location: '. $obj->link);
91 exit;
92 }
93 }
94 else {
95 return node_prepare($node, $teaser);
96 }
97 }
98
99 /**
100 * Implementation of hook_access() - Define access permissions for planet nodes (aka CRUD) .
101 * @param $op The operation to be performed: 'create', 'view', 'update', 'delete'
102 * @param $node The node on which the operation is to be performed, or, if it does not yet exist, the type of node to be created.
103 * @param $account A user object representing the user for whom the operation is to be performed.
104 * @return true if the user is allowed to perform operation, false otherwise.
105 *
106 * @ingroup planet_node
107 */
108 function planet_access($op, $node, $account) {
109 // @DIFFINFO using planet-dedicated permissions
110 // Planet administrator has all permissions related to planet module
111 if (user_access('administer planet', $account))
112 return TRUE;
113 // Users allowed to edit their own planet nodes, are also allowed to create them, others are not.
114 $author = user_access('edit own planet feeds', $account);
115 if ($op == 'create') {
116 return $author;
117 }
118
119 // Does current user own requested node ?
120 $own = $account->uid == $node->uid;
121 if ($op == 'view')
122 return $own || user_access('view all planet nodes');
123 if ($op == 'update' || $op == 'delete') {
124 return $author && $own; // ok if $author owns the node
125 }
126 trigger_error("Unknown operation requested: $op");
127 return FALSE;
128 }
129
130 /**
131 * Implementation of hook_menu() - Defines menu items and page callbacks.
132 *
133 * @return An array of menu items.
134 * Each menu item has a key corresponding to the Drupal path being registered.
135 *
136 * @ingroup base
137 *
138 * This diagram shows how requests are handled.
139 * Each arrow is labeled with the permission required to access link.
140 * The menu item colors reflect the item type.
141 * @dotfile menu-links.dot "Menus and Pages"
142 */
143 function planet_menu() {
144 $items['admin/settings/planet'] = array(
145 'title' => 'Planet Settings',
146 'description' => 'Configure settings for the planet module.',
147 'page callback' => '_planet_settings',
148 'access arguments' => array('administer nodes'),
149 'type' => MENU_NORMAL_ITEM);
150
151 $items['user/%user/planet'] = array(
152 'title' => 'Planet Feeds',
153 'page callback' => 'planet_user_feeds',
154 'page arguments' => array(1),
155 'access arguments' => array('administer own planet feeds'),
156 'type' => MENU_LOCAL_TASK,
157 );
158
159 // if (arg(0) == 'planet' && is_numeric(arg(1))) {
160 // FIXME access denied http://localhost/drupal/?q=planet/1
161 $items['planet/'. '%'] = array(
162 'title' => 'planet',
163 'page callback' => 'planet_page_user',
164 'page arguments' => array(arg(1))
165 );
166
167 // if (arg(3) == 'refresh' && is_numeric(arg(4))) {
168 $items['admin/settings/planet/refresh/%'] = array(
169 'title' => 'planet refresh',
170 'page callback' => 'planet_call_refresh',
171 'access arguments' => array('administer nodes'),
172 'type' => MENU_CALLBACK
173 );
174
175 // if (is_numeric(arg(4)) && (arg(3) == 'freeze' || arg(3) == 'unfreeze')) {
176 $items['admin/settings/planet/'. arg(3) .'/%'] = array(
177 'title' => 'planet freeze',
178 'page callback' => 'planet_toggle_frozen',
179 'access arguments' => array('administer nodes'),
180 'type' => MENU_CALLBACK
181 );
182
183 $items['planet'] = array(
184 'title' => 'Planet',
185 'description' => 'Planet Page',
186 'page callback' => 'planet_page_last',
187 'access arguments' => array('access content'),
188 'type' => MENU_NORMAL_ITEM
189 );
190
191 $items['planet/feed'] = array(
192 'title' => 'Planet',
193 'page callback' => 'planet_feed',
194 'access arguments' => array('access content'),
195 'type' => MENU_CALLBACK
196 );
197
198 return $items;
199 }
200
201
202 /**
203 * Page callback for 'admin/settings/planet/refresh/%' ("planet refresh")
204 *
205 * @ingroup page
206 */
207 function planet_call_refresh() {
208 $title = planet_refresh();
209 watchdog('planet', 'Feed "'. $title .'" refreshed.');
210 drupal_set_message('Feed "'. $title .'" refreshed.');
211 drupal_goto('admin/settings/planet');
212 }
213
214 /**
215 * Page callback for 'admin/settings/planet/(un|)freeze/%' ("planet freeze")
216 *
217 * @ingroup page
218 * @CRUD{planet_feeds,U}
219 */
220 function planet_toggle_frozen() {
221
222 $fid = intval(arg(4));
223 db_query('UPDATE {planet_feeds} SET frozen = %d WHERE fid = %d', arg(3) == 'unfreeze' ? 0 : 1, $fid);
224 drupal_set_message('Feed '. (arg(3) == 'unfreeze' ? 'un' : '') .'frozen.');
225 drupal_goto('admin/settings/planet');
226 }
227
228
229 /**
230 * Deletes a feed and related items
231 * @param $fid the feed identifier key (integer)
232 * @param $path the drupal path for redirection after user confirmed.
233 *
234 * @internal
235 * @ingroup planet_feed
236 * @CRUD{planet_items,R}
237 * @invoke{drupal_get_form,planet_multiple_delete_confirm}
238 */
239 function planet__drop_feed($fid, $path) {
240 // @DIFFINFO documented $path param, removed unused $edit param
241
242 $result = db_query('SELECT nid FROM {planet_items} WHERE fid = %d', $fid);
243 $nodes = array(); // @DIFFINFO needs to be defined even empty (@FIXME it shouldn't be !)
244 while ($node = db_fetch_object($result)) {
245 $nodes[$node->nid] = TRUE;
246 }
247 return drupal_get_form('planet_multiple_delete_confirm', $nodes,
248 $fid, $path);
249 }
250
251 /**
252 * Page callback for 'user/%user/planet' ("Planet Feeds")
253 *
254 * @ingroup page
255 * @CRUD{planet_feeds,R}
256 * @invoke{drupal_get_form,planet_multiple_delete_confirm_submit}
257 * @invoke{drupal_get_form,planet_feed_form}
258 */
259 function planet_user_feeds() {
260 global $user;
261 if ($_POST) {
262 return planet__update($_POST, 'user/'. $user->uid .'/planet');
263 }
264 else { // no $_POST
265 $fid = intval(arg(3)); // @deprecated: arg()
266 if ($fid > 0) {
267 $edit = db_fetch_array(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));
268 $output .= drupal_get_form('planet_feed_form', $edit, true, $user);
269 }
270 else {
271
272 $output .= drupal_get_form('planet_feed_form', $edit, false, $user);
273
274 $output .= '<h2>Feeds</h2>';
275 $output .= planet__build_user_feeds_table();
276 }
277 print theme('page', $output);
278 }
279 }
280
281 // Note: I use 'planet__' prefix to name internal functions
282 // TODO: apply the same naming convention (this one or another) in whole module
283 /**
284 * @todo merge with planet__build_admin_feeds_table()
285 *
286 * @ingroup planet_feed
287 * @internal
288 * @CRUD{planet_feeds,R}
289 * @CRUD{planet_items,R}
290 */
291 function planet__build_user_feeds_table() {
292 // @DIFFINFO renamed planet__build_feeds_table1
293 global $user;
294 $feeds = db_query('SELECT fid, title, checked FROM {planet_feeds} '
295 . ' WHERE uid = %d;', $user->uid);
296 $rows = array();
297 $headers = array('Feed', 'Items', 'Edit', 'Last checked');
298 $now = time();
299 $items_statement = 'SELECT count(*) FROM {planet_items}'
300 . ' WHERE fid=%d';
301 // TODO: change this to prepared statement when supported by drupal DB abstraction layer.
302 while ($feed = db_fetch_object($feeds)) {
303 $_checked = $now - $feed->checked;
304 $checked = intval($_checked / 60) .' minutes';
305 if ($_checked % 60 > 0) {
306 $checked .= ', '. $feed->_checked % 60 .' seconds';
307 }
308 $checked .= ' ago';
309 $items = db_query($items_statement, $feed->fid);
310 if (!$items) trigger_error('ERROR while counting feed items.');
311 $cnt = db_result($items);
312 array_push($rows, array(
313 $feed->title,
314 $cnt,
315 l('edit', 'user/'. $user->uid .'/planet/'. intval($feed->fid)),
316 $checked,
317 )
318 );
319 }
320 return theme('table', $headers, $rows);
321 }
322
323 /**
324 * Updates a feed
325 */
326 function planet__update($edit, $path) {
327 // @DIFFINFO Username can change, so we need to store the ID, not the username.
328 $edit['uid'] = db_result(db_query("SELECT uid from {users} WHERE name = '%s'", $edit['username']));
329 unset($edit['username']);
330 $fid = intval($edit['fid']);
331 if (($edit['op'] == 'Delete') && ($fid != 0)) {
332 return planet__drop_feed($fid, $path);
333 }
334 else if ($edit['op'] == 'Delete all' && $edit['confirm'] == 1) {
335 $edit['fid'] = intval(arg(3)); // @deprecated arg()
336 $edit['redirect'] = $path;
337 return drupal_get_form('planet_multiple_delete_confirm_submit', $edit);
338 }
339 else {
340 planet__submit_feed_data($edit);
341 }
342 drupal_goto($path);
343 }
344
345 /**
346 * Submit the feed's data to the appropriate update or add function
347 */
348 function planet__submit_feed_data($data) {
349 if (isset($data['fid'])) {
350 if (intval($data['fid']) == 0) {
351 planet__add_feed($data);
352 } else {
353 planet__update_feed($data);
354 }
355 } else {
356 // TODO if user needs to click separately for each fieldset,
357 // why not using different forms ?
358 // or conversely, allow setting everything at once ?
359 planet__update_vars($data);
360 }
361 }
362
363 /**
364 * Adds a new feed to planet_feeds table.
365 * @param $data associative array with keys
366 * 'uid' (optional), 'title', 'link', 'image' (optional)
367 * @return success status
368 *
369 * @ingroup planet_feed
370 * @internal
371 * @CRUD{planet_feeds,C}
372 */
373 function planet__add_feed($data) {
374 $data['checked'] = 0;
375 $data['frozen'] = 0;
376 // @DIFFINFO Removed debugging block. Internal function is supposed to receive clean arguments. Checking will be left to planet_feed class
377
378 $rslt = drupal_write_record('planet_feeds', $data);
379 if ($rslt==SAVED_NEW) {
380 $title = planet_refresh(intval($data['fid']));
381 drupal_set_message('Added new feed: ' . $title);
382 return TRUE;
383 }
384 trigger_error('Failed to add new feed');
385 return FALSE;
386 }
387
388 /**
389 * Updates an existing feed in planet_feeds table.
390 * @param $data associative array with keys
391 * 'fid' (mandatory, primary key),
392 * other fields as needed:
393 * 'uid', 'title', 'link', 'image', 'checked', 'frozen'
394 * @return success status
395 *
396 * @ingroup planet_feed
397 * @internal
398 * @CRUD{planet_feeds,U}
399 */
400 function planet__update_feed($data) {
401 $rslt = drupal_write_record('planet_feeds', $data, 'fid');
402
403 if ($rslt==SAVED_UPDATED) {
404 drupal_set_message('Edited "'. $data['title'] .'" feed.');
405 return TRUE;
406 }
407 if ($rslt==SAVED_NEW) // @DEBUGGING
408 trigger_error('Feed {$data->fid} didn\'t exist. Record added.', E_ERROR);
409 else
410 trigger_error('Failed to edit feed');
411 return FALSE;
412 }
413
414 /**
415 * Updates an planet settings persistent variables.
416 * @param $data associative array with all optional keys
417 * 'planet_filter_formats', 'planet_redirect_page'.
418 *
419 * @ingroup isettings
420 * @internal
421 * @CRUD{variables,CUD}
422 */
423 function planet__update_vars($data) {
424 if ($data['planet_filter_formats']) {
425 variable_set('planet_filter_formats', $data['planet_filter_formats']);
426 }
427 if ($data['planet_redirect_page'] == 1) {
428 variable_set('planet_redirect_page', $data['planet_redirect_page']);
429 } else {
430 variable_del('planet_redirect_page');
431 }
432 drupal_set_message('Edited general planet settings.');
433 }
434
435 /**
436 * Page callback for 'admin/settings/planet' ("Planet Settings")
437 *
438 * @ingroup page
439 * @CRUD{planet_items,R}
440 * @CRUD{planet_feeds,R}
441 * @invoke{drupal_get_form,planet_multiple_delete_confirm}
442 * @invoke{drupal_get_form,planet_multiple_delete_confirm_submit}
443 * @invoke{drupal_get_form,planet_feed_form}
444 * @invoke{drupal_get_form,planet_settings_form}
445 */
446 function _planet_settings() {
447 if ($_POST) {
448 return planet__update($_POST, 'admin/settings/planet');
449 } else {
450 $fid = intval(arg(3)); // @deprecated arg()
451 if ($fid > 0) {
452 $edit = db_fetch_array(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));
453 $output .= drupal_get_form('planet_feed_form', $edit, true);
454 }
455 else {
456
457 $output .= drupal_get_form('planet_settings_form');
458
459 $output .= drupal_get_form('planet_feed_form', $edit);
460
461 $output .= '<h2>Feeds</h2>';
462 $output .= planet__build_admin_feeds_table();
463 }
464 print theme('page', $output);
465 }
466 }
467
468 /**
469 * builds an HTML table of feeds for administrator interaction.
470 *
471 * @return
472 *
473 * @ingroup planet_feed
474 * @internal
475 * @CRUD{planet_feeds,R}
476 * @CRUD{planet_items,R}
477 */
478 function planet__build_admin_feeds_table() {
479 // @DIFFINFO renamed planet__build_feeds_table() {
480 // @DIFFINFO the admin section is supposed to let you administer everyone not just the admin.. ;P
481 $feeds = db_query('SELECT fid, title, checked, frozen FROM {planet_feeds}');
482 $rows = array();
483 $headers = array('Feed', 'Items', 'Edit', 'Last checked', 'Refresh', 'Freeze');
484 $now = time();
485 $items_statement = 'SELECT count(*) FROM {planet_items} WHERE fid=%d';
486 // TODO: change this to prepared statement when supported by drupal DB Abstraction Layer.
487 while ($feed = db_fetch_object($feeds)) {
488 $_checked = $now - $feed->checked;
489 $checked = intval($_checked / 60) .' minutes';
490 if ($_checked % 60 > 0) {
491 $checked .= ', '. $feed->_checked % 60 .' seconds';
492 }
493 $checked .= ' ago';
494 $items = db_query($items_statement, $feed->fid);
495 if (!$items) trigger_error('ERROR while counting feed items.');
496 $cnt = db_result($items);
497 $fid = strval($feed->fid);
498 array_push($rows, array(
499 $feed->title,
500 $cnt,
501 l('edit', 'admin/settings/planet/'. $fid),
502 $checked,
503 l('refresh', 'admin/settings/planet/refresh/'. $fid),
504 l($feed->frozen ? 'unfreeze' : 'freeze', 'admin/settings/planet/'. ($feed->frozen ? 'unfreeze/' : 'freeze/') . $fid)
505 )
506 );
507 }
508 return theme('table', $headers, $rows);
509 }
510
511 /**
512 * TODO.
513 *
514 * @param &$form_state
515 * @param $nodes
516 * @param $fid
517 * @param $redirect
518 * @return
519 *
520 * @ingroup iforms
521 * @CRUD{node,R}
522 */
523 function planet_multiple_delete_confirm(&$form_state, $nodes, $fid, $redirect) {
524 $form_state['values']['fid'] = $fid;
525 $form_state['values']['redirect'] = $redirect;
526 $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
527 // array_filter returns only elements with TRUE values
528 foreach ($nodes as $nid => $value) {
529 $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
530 $form['nodes'][$nid] = array(
531 '#type' => 'hidden',
532 '#value' => $nid,
533 '#prefix' => '<li>',
534 '#suffix' => check_plain($title) ."</li>\n",
535 );
536 }
537 $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
538 $form['#submit'][] = 'planet_multiple_delete_confirm_submit';
539 return confirm_form($form,
540 t('Are you sure you want to delete these items?'),
541 $redirect, t('This action cannot be undone.'),
542 t('Delete all'), t('Cancel'));
543 }
544
545 /**
546 * TODO.
547 *
548 * @param &$form_state
549 * @param $edit
550 * @return
551 *
552 * @ingroup iforms
553 * @CRUD{node,D}
554 * @CRUD{planet_items,D}
555 * @CRUD{planet_feeds,D}
556 */
557 function planet_multiple_delete_confirm_submit(&$form_state, $edit) {
558 $fid = $edit['fid'];
559 if ($edit['confirm']) {
560 foreach ($edit['nodes'] as $nid => $value) {
561 node_delete($nid);
562 }
563 // @DIFFINFO inverted drop order to respect foreign keys
564 db_query('DELETE FROM {planet_items} WHERE fid = %d', $fid);
565 db_query('DELETE FROM {planet_feeds} WHERE fid = %d', $fid);
566 drupal_set_message(t('The feed and items have been deleted.'));
567 }
568 drupal_goto($edit['redirect']);
569 }
570
571
572 /**
573 * TODO.
574 *
575 * @param &$form_state
576 * @return
577 * @ingroup iforms
578 * @CRUD{role,R}
579 * @CRUD{variables,R}
580 */
581 function planet_settings_form(&$form_state) {
582 $result = db_query('SELECT format, name FROM {filter_formats}');
583 while ($format = db_fetch_object($result)) {
584 $formats[$format->format] = $format->name;
585 }
586
587 $form = array();
588
589 $form['general'] = array(
590 '#type' => 'fieldset',
591 '#title' => t('General Settings')
592 );
593
594 $form['general']['planet_filter_formats'] = array(
595 '#type' => 'select',
596 '#title' => t('Filter format for planet entry nodes'),
597 '#options' => $formats,
598 '#default_value' => variable_get('planet_filter_formats', 1),
599 '#description' => t('Select the filter format that will be used to show planet entry nodes.')
600 );
601
602 $form['general']['planet_redirect_page'] = array(
603 '#type' => 'checkbox',
604 '#title' => t('Redirect node?'),
605 '#return_value' => 1,
606 '#value' => (variable_get('planet_redirect_page', 0) == 1) ? 1 : null,
607 '#description' => t('Check this if you want the node view to redirect to the original content link; this is useful if you want the feed to forward through instead of showing the planet node.')
608 );
609
610 $form['general']['submit'] = array(
611 '#type' => 'submit',
612 '#value' => 'Save Configuration'
613 );
614
615 return $form;
616 }
617
618 /**
619 * TODO.
620 *
621 * @param &$form_state
622 * @param $edit
623 * @param $individual
624 * @param $user
625 * @return
626 * @ingroup iforms
627 * @CRUD{users,R}
628 * @CRUD{role,R}
629 * @CRUD{users_roles,R}
630 * @CRUD{variables,R}
631 */
632 function planet_feed_form(&$form_state, $edit = array(), $individual = false, $user = NULL) {
633
634 if ($individual) {
635 $g_user = db_fetch_array(db_query('SELECT uid, name FROM {users} WHERE uid = %d', $edit['uid']));
636 }
637
638 $form = array();
639
640 $form['feeds'] = array(
641 '#type' => 'fieldset',
642 '#title' => 'Feeds'
643 );
644
645 $form['feeds']['fid'] = array(
646 '#type' => 'hidden',
647 '#value' => $edit['fid']
648 );
649
650 $form['feeds']['title'] = array(
651 '#type' => 'textfield',
652 '#title' => t('Title'),
653 '#value' => $edit['title'],
654 '#size' => 40,
655 '#maxlength' => 40
656 );
657
658 if ($user == NULL) {
659 $form['feeds']['username'] = array(
660 '#type' => 'textfield',
661 '#title' => t('Original author'),
662 '#size' => 40,
663 '#maxlength' => 60,
664 '#default_value' => $g_user['name'],
665 '#required' => TRUE,
666 '#autocomplete_path' => 'user/autocomplete',
667 '#description' => t('Select a user to associate this feed with')
668 );
669 }
670 else {
671 $form['feeds']['username'] = array(
672 '#type' => 'hidden',
673 '#value' => $user->name,
674 );
675 }
676 $form['feeds']['link'] = array(
677 '#type' => 'textfield',
678 '#title' => t('URL'),
679 '#value' => $edit['link'],
680 '#size' => 40,
681 '#maxlength' => 80
682 );
683
684 $form['feeds']['submit'] = array(
685 '#type' => 'submit',
686 '#value' => $edit['fid'] > 0 ? 'Save' : 'Add'.' Feed'
687 );
688
689 if ($individual) {
690 $form['feeds']['delete'] = array(
691 '#type' => 'submit',
692 '#value' => 'Delete',
693 );
694 }
695
696 return $form;
697 }
698
699 /**
700 * Implementation of hook_cron - Perform periodic actions.
701 *
702 * @return none
703 * @ingroup system
704 * @CRUD{planet_feeds,R}
705 */
706 function planet_cron() {
707 $result = db_query('SELECT fid FROM {planet_feeds} WHERE frozen = 0');
708 while ($feed = db_fetch_object($result)) {
709 $title = planet_refresh($feed->fid);
710 watchdog('planet', 'Cron updated feed "'. $title .'".');
711 }
712 }
713
714
715 /**
716 * Private function; Checks a news feed for new items.
717 *
718 * @param $fid feed identifier (defaults to arg(4))
719 * @return
720 *
721 * @private
722 * @CRUD{planet_feeds,U}
723 * @invoke{module_invoke,taxonomy_node_get_terms}
724 */
725 function planet_refresh($fid = null) {
726 if (!$fid) {
727 $fid = intval(arg(4));
728 }
729 // initialize simplepie
730 // we want to do this only once and not each time per feed, which would be slower
731 include_once './'. drupal_get_path('module', 'planet') .'/simplepie.inc';
732
733 $process_feed = db_fetch_object(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));
734
735 $feed = new SimplePie();
736 $feed->enable_cache(FALSE);
737 $feed->set_timeout(15);
738 // prevent SimplePie from using all of it's data santization since we use Drupal's input formats to handle this
739 $feed->set_stupidly_fast(TRUE);
740 $feed->set_feed_url($process_feed->link);
741 // FeedBurner requires this check otherwise it won't work well with SimplePie
742 // also performance improvement
743 header('If-Modified-Since:'. $process_feed->checked);
744 $success = $feed->init();
745
746 if ($success && $feed->data) {
747 // get a unique hash of the headers in the feed, fast and easy way to compare if this feed is updated or not
748 $hash = md5(serialize($feed->data));
749
750 // hashes don't match so likely the feed is updated
751 if ($process_feed->hash != $hash) {
752 // above we define hook_view() which then performs check_url() on the $url in the feed node
753 // the problem is check_url() calls filter_xss_bad_protocol() which does it thing to prevent XSS
754 // but it returns the string through check_plain() which calls htmlspecialchars()
755 // this converts & in a url to &amp; and then causes SimplePie not to be able to parse it
756 // because of this, we decode this URL since we are passing it directly to SimplePie
757 // it is still encoded everywhere else it is output to prevent XSS
758 $process_feed->link = htmlspecialchars_decode($process_feed->link, ENT_QUOTES);
759
760 // turn each feed item into a node
761 planet_item_feed_parse($process_feed, $feed);
762 }
763
764 // finished processing this feed so we can mark it checked
765 db_query("UPDATE {planet_feeds} SET checked = %d, hash = '%s', error = 0 WHERE fid = %d", time(), $hash, $process_feed->fid);
766 }
767 else if (isset($feed->error)) {
768 db_query("UPDATE {planet_feeds} SET error = 1 WHERE fid = %d", $process_feed->fid);
769 watchdog('planet', 'The feed %feed could not be processed due to the following error: %error', array('%feed' => $process_feed->title, '%error' => $feed->error), WATCHDOG_ERROR, l('view', $process_feed->link));
770 }
771 else {
772 watchdog('planet', 'You shouldn\'t be here. Something has gone terribly wrong.');
773 }
774
775 return $process_feed->title;
776 }
777
778 // @DIFFINFO removed orphaned doc-block
779
780 /**
781 * Private function; Convert relative URLs
782 * @ingroup rss
783 * @private
784 */
785 function planet_convert_relative_urls(&$data, $base_url) {
786 $src = '%( href| src)="(?!\w+://)/?([^"]*)"%';
787 $dst = '$1="'. trim($base_url, '/') .'/$2"';
788 return preg_replace($src, $dst, $data);
789 }
790
791 /**
792 * Page callback for 'planet' ("Planet").
793 * @ingroup page
794 * @CRUD{node,R}
795 * @CRUD{variables,R}
796 */
797 function planet_page_last() {
798 global $user;
799
800 $output = '<br />';
801
802 $result = pager_query(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'planet' AND n.status = 1 ORDER BY n.created DESC"), variable_get('default_nodes_main', 10));
803
804 while ($node = db_fetch_object($result)) {
805 $node = node_load($node->nid);
806 $node->format = variable_get('planet_filter_formats', 1);
807 $output .= node_view($node);
808 }
809 $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
810 $output .= theme('xml_icon', url('planet/feed'));
811
812 print theme('page', $output);
813 }
814
815 /**
816 * Page callback for 'planet/feed' ("Planet").
817 * @ingroup page
818 * @CRUD{node,R}
819 * @CRUD{menu_links,R}
820 */
821 function planet_feed() {
822 $result = db_query_range(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'planet' AND n.status = 1 ORDER BY n.created DESC"), 0, 15);
823 $title = db_fetch_array(db_query("SELECT link_title FROM {menu_links} WHERE link_path = 'planet'"));
824
825 $channel['title'] = $title['link_title'];
826 $channel['link'] = url('planet', array('absolute' => TRUE));
827 $channel['description'] = 'Planet feed';
828
829 $items = array();
830 while ($row = db_fetch_object($result)) {
831 $items[] = $row->nid;
832 }
833 // generate RSS feed from $items set of nodes.
834 node_feed($items, $channel);
835 }
836
837 /**
838 * Implementation of hook_user() - React when operations are performed on user accounts.
839 * @ingroup system
840 * @CRUD{planet_feeds,R}
841 */
842 function planet_user($type, &$edit, &$user) {
843 if ($type == 'view' && user_access('edit own blog', $user)) {
844 $items[] = array('title' => t('Blog'),
845 'value' => /* TODO
846 Please manually fix the parameters on the l() or url() function on the next line.
847 Typically, this was not changed because of a function call inside an array call like
848 array('title' => t('View user profile.')).*/
849 l(t('view recent blog entries'), "planet/$user->uid", array('title' => t("Read %username's latest blog entries.", array('%username' => $user->name)))),
850 'class' => 'blog',
851 );
852 return array(t('History') => $items);
853 }
854 if ($type == 'load') {
855 $obj = db_fetch_object(db_query('SELECT link FROM {planet_feeds} WHERE uid = %d', $user->uid));
856 $user->planet_feed = $obj->link;
857 }
858 }
859
860 /**
861 * Menu callback; displays a Drupal page containing recent planet entries.
862 *
863 * @todo remove orphan function, or reuse it
864 * @ingroup page
865 */
866 function planet_page($a = NULL, $b = NULL) {
867 // @DIFFINFO I keep this for now to keep showing, in API doc, historic relation between called functions
868 if (is_numeric($a)) { // $a is a user ID
869 if ($b == 'feed') {
870 return planet_feed_user($a);
871 }
872 else {
873 return planet_page_user($a);
874 }
875 }
876 else if ($a == 'feed') {
877 return planet_feed_last();
878 }
879 else {
880 return planet_page_last();
881 }
882 }
883
884 /**
885 * Page callback for 'planet/%' ("planet").
886 * @ingroup page
887 *
888 * @CRUD{node,R}
889 * @CRUD{variables,R}
890 */
891 function planet_page_user($uid) {
892 global $user;
893
894 $account = user_load(array((is_numeric($uid) ? 'uid' : 'name') => $uid, 'status' => 1));
895
896 if ($account->uid) {
897 drupal_set_title($title = t("%name's planet", array('%name' => $account->name)));
898
899 if ($output) {
900 $output = '<ul>'. $output .'</ul>';
901 }
902 else {
903 $output = '';
904 }
905 $result = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE type = 'planet' AND n.uid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10), 0, NULL, $account->uid);
906 while ($node = db_fetch_object($result)) {
907 $output .= node_view(node_load($node->nid), 1);
908 }
909 $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
910 $output .= theme('feed_icon', url("planet/$account->uid/feed"));
911
912 drupal_add_link(array('rel' => 'alternate',
913 'type' => 'application/rss+xml',
914 'title' => t('RSS - %title', array('%title' => $title)),
915 'href' => url("planet/$account->uid/feed")));
916 return $output;
917 }
918 else {
919 drupal_not_found();
920 }
921 }
922
923 /**
924 * Implementation of hook_load - Load node-type-specific information.
925 *
926 * @param $node The node being loaded.
927 * @return An object containing properties of the node being loaded.
928 * @ingroup system
929 * @CRUD{planet_items,R}
930 */
931 function planet_load($node) {
932 $additions = db_fetch_object(db_query('SELECT link, guid FROM {planet_items} WHERE nid = %d', $node->nid));
933 return $additions;
934 }
935
936 /**
937 * Implementation of hook_form - Display a node editing form.
938 *
939 * @param &$node The node being added or edited.
940 * @param $form_state The form state array (unused).
941 * @return An array containing the form elements to be displayed in the node edit form.
942 * @ingroup planet_node
943 */
944 function planet_form(&$node, &$form_state) {
945 $form = array();
946 $form['title'] = array('#type' => 'textfield', '#title' => 'Title', '#value' => $node->title, '#size' => 30, '#maxlength' => 80);
947 $form['body'] = array('#type' => 'textarea', '#title' => 'Body', '#value' => $node->body);
948 return $form;
949 }
950
951 /**
952 * Turn each feed item into a node.
953 *
954 * @param $process_feed
955 * Feed node object
956 * @param $feed
957 * SimplePie feed object instaniated.
958 */
959 function planet_item_feed_parse($process_feed, $feed) {
960 // loop through all of the items in the feed, faster than foreach
961 $max = $feed->get_item_quantity();
962 $count = 0;
963 module_load_include('inc', 'node', 'node.pages');
964 module_load_include('inc', 'node', 'content_types');
965 $node = node_get_types('type', 'feed_item');
966
967 for ($i = 0; $i < $max; $i++) {
968 $item = $feed->get_item($i);
969
970 // we don't use $item->get_id(true) from SimplePie because it is slightly buggy
971 // and requires a lot of overhead to compute each time (since it uses a gigantic array structure)
972 // instead we opt for a much lighter weight comparison of just the title and body, eliminating the
973 // possibility of any date changes or other tiny changes causing duplicate nodes that otherwise
974 // appear to be the same
975 // that is why the body and title processing appears out here, so we can check for duplicates
976 // it is fast enough to not make much of a difference otherwise
977 $body = $item->get_content();
978 // this strips out any tags that may appear as <b> in the title, and makes sure &quot; -> " for display
979 $title = strip_tags(decode_entities($item->get_title()));
980
981 // some feeds don't provide titles so we construct one with the first 72 characters of the body
982 if (!$title) {
983 // remove any HTML or line breaks so these don't appear in the title
984 $title = trim(str_replace(array("\n", "\r"), ' ', strip_tags($body)));
985 $title = trim(substr($title, 0, 72));
986 $lastchar = substr($title, -1, 1);
987 // check to see if the last character in the title is a non-alphanumeric character, except for ? or !
988 // if it is strip it off so you don't get strange looking titles
989 if (preg_match('/[^0-9A-Za-z\!\?]/', $lastchar)) {
990 $title = substr($title, 0, -1);
991 }
992 // ? and ! are ok to end a title with since they make sense
993 if ($lastchar != '!' and $lastchar != '?') {
994 $title .= '...';
995 }
996 }
997
998 // unique id for each feed item, try and use item permalink, otherwise use feed permalink
999 if (!$link = $item->get_permalink()) {
1000 $link = $feed->get_permalink();
1001 }
1002 // we don't need serialize() since we already have strings
1003 $iid = md5($title . $link);
1004 $guid = md5("$title - . " . $process_feed->fid);
1005 // make sure we don't already have this feed item
1006 $duplicate = db_result(db_query("SELECT COUNT(iid) FROM {planet_items} WHERE iid = '%s'", $iid));
1007
1008 if (!$duplicate) {
1009
1010
1011 $entry = NULL;
1012 if ($guid && strlen($guid) > 0) {
1013 $entry = db_fetch_object(db_query("SELECT nid FROM {planet_items} WHERE guid = '%s' AND fid = %d", $guid, $process_feed->fid));
1014 }
1015 else if ($link && $link != $feed->link && $link != $feed->url) {
1016 $entry = db_fetch_object(db_query("SELECT nid FROM {planet_items} WHERE guid = '%s' AND fid = %d", $link, $process_feed->fid));
1017 }
1018 else {
1019 $entry = db_fetch_object(db_query("SELECT ai.nid AS nid FROM {node} n, {planet_items} ai WHERE ai.fid = %d AND ai.nid = n.nid AND n.title = '%s'", $process_feed->fid, $title));
1020 }
1021
1022 $link = $item->get_permalink();
1023 // this is node created date format for Drupal
1024 $date = $item->get_date('Y-m-d H:i:s O');
1025
1026 $entry->changed = $date;
1027 $entry->title = $title;
1028 $entry->body = $body;
1029 $entry->body = planet_convert_relative_urls($body, $link);
1030 $entry->teaser = node_teaser($entry->body);
1031 $entry->revision = true;
1032
1033 if (!isset($entry->nid)) {
1034 //print "Planet item " . $entry->title . "<br />";
1035 $entry->type = 'planet';
1036
1037 $options = variable_get('node_options_planet', array());
1038
1039 $entry->uid = $process_feed->uid;
1040 $entry->status = 1;
1041 $entry->moderate = 0;
1042 $entry->promote = in_array('promote', $options) ? 1 : 0;
1043 $entry->sticky = in_array('sticky', $options) ? 1 : 0;
1044 $entry->comment = in_array('comment', $options) ? 2 : 0;
1045 $entry->format = variable_get('planet_filter_formats', 1);
1046 $entry->created = $date;
1047 $entry->revision = true;
1048
1049 }
1050
1051 node_save($entry);
1052 $item_record = array('fid' => $process_feed->fid,
1053 'nid' => $entry->nid,
1054 'iid' => $iid,
1055 'guid'=> $guid,
1056 'link' =>$link,
1057 'created' => time());
1058 drupal_write_record('planet_items', $item_record);
1059 watchdog('planet', 'Adding '. $title);
1060 drupal_set_message('Adding '. $title);
1061 }
1062
1063 // we unset $item each time to prevent any pass by reference memory leaks that PHP encounters with objects in foreach loops
1064 unset($item);
1065 }
1066
1067 }

  ViewVC Help
Powered by ViewVC 1.1.2