Merge lp://qastaging/~swe3tdave-deactivatedaccount/ubuntu-drupal-planet/6.x into lp://qastaging/~m-baert/ubuntu-drupal-planet/6.x

Proposed by David Giard
Status: Merged
Merged at revision: 20
Proposed branch: lp://qastaging/~swe3tdave-deactivatedaccount/ubuntu-drupal-planet/6.x
Merge into: lp://qastaging/~m-baert/ubuntu-drupal-planet/6.x
Diff against target: None lines
To merge this branch: bzr merge lp://qastaging/~swe3tdave-deactivatedaccount/ubuntu-drupal-planet/6.x
Reviewer Review Type Date Requested Status
Rocky Road Pending
Review via email: mp+4744@code.qastaging.launchpad.net
To post a comment you must log in.
Revision history for this message
David Giard (swe3tdave-deactivatedaccount) wrote :

there are some change i think you forgot to merge, in planet_user_feeds.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.txt'
2--- README.txt 2009-03-01 02:52:51 +0000
3+++ README.txt 2009-03-20 04:37:38 +0000
4@@ -1,19 +1,17 @@
5-This module allows site administrators to associate a feed with users who belong
6-to a given role and to display it in a "planet" fashion. Feed items can be
7-auto-converted to blogs if categories or the post itself contains a snippet
8-specified in the planet settings, or posts can be converted manually.
9-
10-The idea is that it's nice in some cases to aggregate a group's personal blogs,
11-promoting to prime time only those posts which are relevant to the site's purpose.
12-
13+Planet is an aggregator that allows you to aggregate the blogs for users in a given
14+role (e.g. staff) and to display them in a "planet" fashion. Associating content
15+with the users rather than as a detached feed.
16
17 Installation
18 ------------
19
20 1. Copy the planet directory into your Drupal modules directory.
21
22-2. Go to admin/modules and enable the planet module.
23-
24-3. To perform further configuration (and to add feeds), go to admin/settings/planet.
25+2. Download the SimplePie 1.1 library: http://simplepie.org/
26+ Place simplepie.inc in your Planet module directory.
27+
28+3. Go to admin/modules and enable the planet module.
29+
30+4. To perform further configuration (and to add feeds), go to admin/settings/planet.
31
32
33
34=== added directory 'doc'
35=== added file 'doc/drupal-planet.dox'
36--- doc/drupal-planet.dox 1970-01-01 00:00:00 +0000
37+++ doc/drupal-planet.dox 2009-03-21 16:38:04 +0000
38@@ -0,0 +1,120 @@
39+/**
40+@mainpage Planet Module API
41+
42+Source repository: https://code.edge.launchpad.net/~m-baert .
43+
44+You may download it directly from command line:
45+@verbatim
46+bzr branch lp:~m-baert/drupal-planet/6.x
47+@endverbatim
48+
49+@section planet_info Module Definition
50+Here's how planet is defined in @c planet.info :
51+
52+@li @c name Planet
53+@li @c description Aggregates RSS feeds and faciliates their association with site users who belong to a given role.
54+@li @c package Community - optional
55+@li @c version 6.x
56+@li @c core 6.x
57+
58+@section menu_links Menus and Pages
59+
60+Menu items and page callbacks are defined in the hook_menu()
61+ implementation, planet_menu.
62+
63+This <a href="../../menu-links.svg">diagram</a> shows how requests are handled.
64+
65+@dotfile menu-links.dot "Menus and Page Requests"
66+
67+Each arrow is labeled with the permission required to access link.
68+
69+The menu item colors reflect the item type:
70+
71+@li <span class="menu_normal_item">MENU_NORMAL_ITEM</span> (default) Normal menu items show up in the menu tree and can be moved/hidden by the administrator.
72+@li <span class="menu_local_task">MENU_LOCAL_TASK</span> Local tasks are rendered as tabs by default. For menu items that describe actions to be performed on their parent item.
73+@li <span class="menu_callback">MENU_CALLBACK</span> Callbacks simply register a path so that the correct function is fired when the URL is accessed. They are not shown in the menu.
74+
75+@section funcalls Global calls graph
76+
77+Here is a map of internal function calls.
78+
79+It is quite large for now, and may not be easy to read as displayed here,
80+but you may <a href="../../funcalls.svg">download the svg version here</a>.
81+
82+Dashed lines show indirect invocations (
83+<span class="hook">hooks</span>,
84+<span class="page">callbacks</span>,
85+<span class="form">forms</span>,
86+other <span class="handler">handlers</span>
87+).
88+
89+<span class="db_access">Database access</span> is also shown with access type indicators:
90+@li @b C : Create table rows
91+@li @b R : Read table rows
92+@li @b U : Update table rows
93+@li @b D : Delete table rows
94+
95+ @dotfile funcalls.dot "Internal Function Calls"
96+
97+
98+@section planet_groups Function Groups
99+
100+Each planet function may belong to one or more categories:
101+
102+@li @ref base
103+@li @ref system
104+@li @ref page
105+@li @ref iforms
106+@li @ref planet_node
107+@li @ref ihooks
108+@li @ref planet_feed
109+@li @ref planet_item
110+@li @ref rss
111+
112+
113+*/
114+#@li @ref db_access
115+#@li @ref internal
116+
117+// @DIFFINFO Group definitions for doxygen documentation
118+/**
119+ @defgroup ihooks All Implemented Hooks
120+ @{
121+ @defgroup base Basic Definitions for Planet Module
122+Main user interface.
123+
124+ @defgroup system System Interaction
125+Various callbacks for reacting to drupal some system events.
126+
127+ @defgroup planet_node Planet Node Type support
128+This group may not be easy to identify because our node type
129+has the same name as the module: "planet".
130+
131+@defgroup empty .
132+@}
133+*/
134+# above "empty" group is a dirty trick to prevent doxygen from ejecting
135+# the last subgroup .
136+
137+# Note:
138+Renaming "Modules" output section probably implies recompiling doxygen.
139+ http://www.nabble.com/Modules-book-title:-possible-to-rename---td15608879.html
140+
141+/**
142+ @defgroup page Page callbacks (Menu System)
143+Functions registered in planet_menu()
144+
145+ @defgroup iforms Forms
146+User forms, invoked with drupal_get_form() .
147+
148+ @defgroup isettings General Planet Settings
149+
150+ @defgroup planet_feed Managing Planet feeds
151+
152+ @defgroup planet_item Managing Planet feed items
153+
154+ @defgroup rss RSS Feed Handling
155+
156+ */
157+ #* @defgroup internal Internal Functions
158+ #* @defgroup db_access Functions accessing database (directly through Drupal API)
159
160=== modified file 'planet.info'
161--- planet.info 2009-03-01 02:54:23 +0000
162+++ planet.info 2009-03-21 01:30:45 +0000
163@@ -1,6 +1,7 @@
164 ; $Id: planet.info,v 1.2 2007/05/30 03:22:01 daryl Exp $
165-name = Planet
166-description = Planet blog aggregator
167+; Planet blog aggregator
168+name = Planet
169+description = Aggregates RSS feeds and faciliates their association with site users who belong to a given role.
170 package = Community - optional
171 version = 6.x
172 core = 6.x
173
174=== modified file 'planet.install'
175--- planet.install 2009-03-01 03:00:14 +0000
176+++ planet.install 2009-03-21 01:41:56 +0000
177@@ -10,49 +10,70 @@
178 'description' => t('The base table for planet.'),
179 'fields' => array(
180 'fid' => array(
181- 'description' => t('The primary identifier for a planet_feeds table.'),
182+ 'description' => t('Primary Key: Unique identifier for a planet RSS feed.'),
183 'type' => 'serial',
184 'unsigned' => TRUE,
185 'not null' => TRUE,
186 ),
187- 'uid' => array(
188+ 'uid' => array(
189+ 'description' => t('Foreign key to {users}.uid . Identifies user who choose the feed.'),
190 'type' => 'int',
191 'unsigned' => 1,
192 'not null' => FALSE,
193 ),
194 'title' => array(
195 'type' => 'varchar',
196+ 'description' => t('Title of the feed.'),
197 'length' => 50,
198 'not null' => TRUE,
199 ),
200 'link' => array(
201 'type' => 'varchar',
202+ 'description' => t('URL to the feed'),
203 'length' => 80,
204 'not null' => TRUE,
205 ),
206 'image' => array(
207+ 'description' => t('An image representing the feed'),
208 'type' => 'varchar',
209 'length' => 120,
210 'not null' => FALSE,
211 ),
212- 'checked' => array(
213+ 'checked' => array(
214+ 'description' => t('Last time feed was checked for new items, as Unix timestamp'),
215 'type' => 'int',
216 'not null' => FALSE,
217 ),
218 'frozen' => array(
219- 'type' => 'int',
220- 'not null' => TRUE,
221- 'default' => 0,
222- ),
223+ // TODO: add field description 'description' => t(''),
224+ 'type' => 'int', // TODO change to boolean when supported
225+ 'not null' => TRUE,
226+ 'default' => 0,
227+ ),
228+ 'hash' => array(
229+ 'type' => 'varchar',
230+ 'length' => 32,
231+ 'description' => t("A hash of the feed's headers."),
232+ ),
233+ 'error' => array(
234+ 'type' => 'int',
235+ 'not null' => TRUE,
236+ 'default' => 0,
237+ 'length' => 1,
238+ 'description' => t("Whether the feed is throwing errors or not."),
239+ ),
240 ),
241 'primary key' => array('fid'),
242+ 'indexes' => array(
243+ 'error' => array('error'),
244+ ),
245 );
246
247 $schema['planet_items'] = array(
248 'description' => t('contain feed id and its corresponding nid'),
249 'fields' => array(
250 'id' => array(
251- 'description' => t('The primary identifier for a planet_items table'),
252+ 'description' => t('Primary key: Unique identifier for a planet feed item'),
253 'type' => 'serial',
254 'unsigned' => 1,
255 'not null' => TRUE,
256@@ -67,6 +88,12 @@
257 'unsigned' => 1,
258 'not null' => FALSE,
259 ),
260+ 'iid' => array(
261+ 'type' => 'varchar',
262+ 'length' => 32,
263+ 'not null' => TRUE,
264+ 'description' => t("md5 of the feed item's title and body."),
265+ ),
266 'guid' => array(
267 'type' => 'varchar',
268 'length' => 120,
269@@ -91,11 +118,9 @@
270 /**
271 * Implementation of hook_install()
272 *
273- * This will automatically install the database tables for the planet module for MySQL.
274- *
275- * If you are using another database, you will have to install the tables by hand, using the queries below as a reference.
276- *
277- *
278+ * This will automatically install the database tables for the planet
279+ * module, using Drupal's data abstraction layer.
280+ *
281 */
282
283 function planet_install() {
284@@ -106,9 +131,9 @@
285 /**
286 * Implementation of hook_uninstall()
287 *
288- * This will automatically uninstall the database tables for the planet module for MySQL.
289+ * Uninstalls planet module by removing its own database tables, nodes
290+ * and persistent variables.
291 *
292- *
293 */
294
295 function planet_uninstall() {
296@@ -120,3 +145,42 @@
297 db_query("DELETE FROM {node} WHERE type = '%s'", planet);
298
299 }
300+
301+function planet_update_1() {
302+ $ret = array();
303+ db_add_field($ret, 'planet_feeds', 'hash', array(
304+ 'type' => 'varchar',
305+ 'length' => 32,
306+ 'description' => t("A hash of the feed's headers."),
307+ )
308+ );
309+ return $ret;
310+}
311+
312+function planet_update_2() {
313+ $ret = array();
314+ db_add_field($ret, 'planet_items', 'iid', array(
315+ 'type' => 'varchar',
316+ 'length' => 32,
317+ 'not null' => TRUE,
318+ 'default' => '',
319+ 'description' => t("md5 of the feed item's title and body."),
320+ )
321+ );
322+ return $ret;
323+}
324+
325+function planet_update_3() {
326+ $ret = array();
327+ db_add_field($ret, 'planet_feeds', 'error', array(
328+ 'type' => 'int',
329+ 'not null' => TRUE,
330+ 'default' => 0,
331+ 'size' => 'tiny',
332+ 'description' => t("Whether the feed is throwing errors or not."),
333+ )
334+ );
335+ db_add_index($ret, 'planet_feeds', 'error', array('error'));
336+
337+ return $ret;
338+}
339
340=== modified file 'planet.module'
341--- planet.module 2009-03-08 19:00:05 +0000
342+++ planet.module 2009-03-21 16:38:04 +0000
343@@ -1,48 +1,92 @@
344 <?php
345-// $Id: planet.install Exp $
346+// $Id: planet.module Exp $
347
348-/**
349+ /**
350 * @file
351- * The planet module
352+ * The planet module.
353+ *
354+ * Planet is an aggregator that allows you to aggregate the blogs for
355+ * users in a given role (e.g. staff) and associate content with the
356+ * users rather than as a detached feed. This provides the benefit of
357+ * showing avatars with content, providing per-user aggregation of
358+ * planet content in addition to blog content, etc.
359 *
360 */
361
362+/**
363+ * Implementation of hook_node_info.
364+ *
365+ * @return An array of information on the module's node types.
366+ *
367+ * @ingroup planet_node
368+ */
369 function planet_node_info() {
370 return array(
371 'planet' => array(
372- 'name' => t('Planet Entry'),
373- 'module' => 'planet',
374- 'description' => t('Node to contain posts aggregated from various blogs.'),
375+ 'name' => t('Planet Entry'),
376+ 'module' => 'planet',
377+ 'description' => t('Node to contain posts aggregated from various blogs.'),
378 )
379 );
380 }
381
382 /**
383- * Implementation of hook_perm.
384+ * Implementation of hook_perm - Defines user permissions.
385+ * The suggested naming convention for permissions is "action_verb modulename".
386+ *
387+ * @return An array of permissions strings.
388+ *
389+ * @ingroup base
390 */
391 function planet_perm() {
392- return array('administer planet', 'administer own planet feeds');
393+ return array('administer planet', 'administer own planet feeds', 'view all planet nodes');
394 }
395
396+/**
397+ * Implementation of hook_help - Provides online user help.
398+ *
399+ * @param $path A Drupal menu router path the help is being requested for
400+ * @param $arg
401+ * @return A localized string containing the help text.
402+ *
403+ * @ingroup base
404+ */
405 function planet_help($path, $arg) {
406 switch ($path) {
407- case 'admin/help/planet':
408+ case 'admin/help#planet':
409+ // The module's help text, displayed on the admin/help page and through the module's individual help link.
410 $output = '<p>Planet is an aggregator that allows you to aggregate the blogs for users in a given role (e.g. staff) and associate content with the users rather than as a detached feed. This provides the benefit of showing avatars with content, providing per-user aggregation of planet content in addition to blog content, etc.</p>';
411 $output .= '<p>To use planet, go to admin/settings/planet and note the following sections:</p>';
412 $output .= '<ul>';
413 $output .= '<li><strong>General Settings</strong>. The role to select bloggers from lets you narrow the user list for when you\'re adding a feed and associating it with a user. A common setting will be to create a staff role and use this for planet.</li>';
414 $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>';
415 $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>';
416+ // @DIFFGROUP output syntax
417+ $output .= '</ul>';
418 return $output;
419- case 'admin/modules#description':
420- return t('Aggregates RSS feeds and faciliates their association with site users who belong to a given role.');
421+ // @DIFFINFO in D6, admin/modules#description is moved to .info file
422 }
423 }
424
425-function planet_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
426+/**
427+ * Implementation of hook_view() - Displays a planet node.
428+ *
429+ * @param $node The node to be displayed.
430+ * @param $teaser Whether to generate only a summary ("teaser") of the node.
431+ * @param $page Whether the node is being displayed as a standalone page.
432+ * @return The modified $node parameter, properly presented for output.
433+ *
434+ * @ingroup planet_node
435+ * @CRUD{variables,R}
436+ * @CRUD{planet_items,R}
437+ */
438+function planet_view($node, $teaser = FALSE, $page = FALSE) {
439+ // @DIFFINFO removed $link param to match D6 hook definition
440 if ($page === true && variable_get('planet_redirect_page', 0) == 1) {
441- $obj = db_fetch_object(db_query('SELECT * FROM {planet_items} WHERE nid = %d', $node->nid));
442- if ($obj->nid == $node->nid && $obj->link != '') {
443+ // @DIFFINFO only 'link' field is used, so I restricted the query
444+ $obj = db_fetch_object(db_query('SELECT link FROM {planet_items} WHERE nid = %d', $node->nid));
445+ // @DIFFINFO if the query succeeds, $obj->nid == $node->nid , otherwise, $obj==FALSE,
446+ if ($obj && $obj->link != '') {
447 header('Location: '. $obj->link);
448 exit;
449 }
450@@ -52,22 +96,51 @@
451 }
452 }
453
454+/**
455+ * Implementation of hook_access() - Define access permissions for planet nodes (aka CRUD) .
456+ * @param $op The operation to be performed: 'create', 'view', 'update', 'delete'
457+ * @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.
458+ * @param $account A user object representing the user for whom the operation is to be performed.
459+ * @return true if the user is allowed to perform operation, false otherwise.
460+ *
461+ * @ingroup planet_node
462+ */
463 function planet_access($op, $node, $account) {
464-
465-
466+ // @DIFFINFO using planet-dedicated permissions
467+ // Planet administrator has all permissions related to planet module
468+ if (user_access('administer planet', $account))
469+ return TRUE;
470+ // Users allowed to edit their own planet nodes, are also allowed to create them, others are not.
471+ $author = user_access('edit own planet feeds', $account);
472 if ($op == 'create') {
473- return user_access('edit own blog', $account) && $account->uid;
474+ return $author;
475 }
476-
477+
478+ // Does current user own requested node ?
479+ $own = $account->uid == $node->uid;
480+ if ($op == 'view')
481+ return $own || user_access('view all planet nodes');
482 if ($op == 'update' || $op == 'delete') {
483- if (user_access('edit own blog', $account) && ($account->uid == $node->uid) || user_access('administer nodes', $account)) {
484- return TRUE;
485- }
486+ return $author && $own; // ok if $author owns the node
487 }
488+ trigger_error("Unknown operation requested: $op");
489+ return FALSE;
490 }
491
492+/**
493+ * Implementation of hook_menu() - Defines menu items and page callbacks.
494+ *
495+ * @return An array of menu items.
496+ * Each menu item has a key corresponding to the Drupal path being registered.
497+ *
498+ * @ingroup base
499+ *
500+ * This diagram shows how requests are handled.
501+ * Each arrow is labeled with the permission required to access link.
502+ * The menu item colors reflect the item type.
503+ * @dotfile menu-links.dot "Menus and Pages"
504+ */
505 function planet_menu() {
506-
507 $items['admin/settings/planet'] = array(
508 'title' => 'Planet Settings',
509 'description' => 'Configure settings for the planet module.',
510@@ -84,31 +157,28 @@
511 );
512
513 // if (arg(0) == 'planet' && is_numeric(arg(1))) {
514+ // FIXME access denied http://localhost/drupal/?q=planet/1
515 $items['planet/'. '%'] = array(
516 'title' => 'planet',
517 'page callback' => 'planet_page_user',
518 'page arguments' => array(arg(1))
519 );
520-// }
521
522-// if (arg(3) == 'refresh' && is_numeric(arg(4))) {
523-
524+ // if (arg(3) == 'refresh' && is_numeric(arg(4))) {
525 $items['admin/settings/planet/refresh/%'] = array(
526 'title' => 'planet refresh',
527 'page callback' => 'planet_call_refresh',
528 'access arguments' => array('administer nodes'),
529 'type' => MENU_CALLBACK
530 );
531-// }
532
533-// if (is_numeric(arg(4)) && (arg(3) == 'freeze' || arg(3) == 'unfreeze')) {
534+ // if (is_numeric(arg(4)) && (arg(3) == 'freeze' || arg(3) == 'unfreeze')) {
535 $items['admin/settings/planet/'. arg(3) .'/%'] = array(
536 'title' => 'planet freeze',
537 'page callback' => 'planet_toggle_frozen',
538 'access arguments' => array('administer nodes'),
539 'type' => MENU_CALLBACK
540 );
541-// }
542
543 $items['planet'] = array(
544 'title' => 'Planet',
545@@ -128,6 +198,12 @@
546 return $items;
547 }
548
549+
550+/**
551+ * Page callback for 'admin/settings/planet/refresh/%' ("planet refresh")
552+ *
553+ * @ingroup page
554+ */
555 function planet_call_refresh() {
556 $title = planet_refresh();
557 watchdog('planet', 'Feed "'. $title .'" refreshed.');
558@@ -135,6 +211,12 @@
559 drupal_goto('admin/settings/planet');
560 }
561
562+/**
563+ * Page callback for 'admin/settings/planet/(un|)freeze/%' ("planet freeze")
564+ *
565+ * @ingroup page
566+ * @CRUD{planet_feeds,U}
567+ */
568 function planet_toggle_frozen() {
569
570 $fid = intval(arg(4));
571@@ -144,53 +226,42 @@
572 }
573
574
575+/**
576+ * Deletes a feed and related items
577+ * @param $fid the feed identifier key (integer)
578+ * @param $path the drupal path for redirection after user confirmed.
579+ *
580+ * @internal
581+ * @ingroup planet_feed
582+ * @CRUD{planet_items,R}
583+ * @invoke{drupal_get_form,planet_multiple_delete_confirm}
584+ */
585+function planet__drop_feed($fid, $path) {
586+ // @DIFFINFO documented $path param, removed unused $edit param
587+ $result = db_query('SELECT nid FROM {planet_items} WHERE fid = %d', $fid);
588+ $nodes = array(); // @DIFFINFO needs to be defined even empty
589+ while ($node = db_fetch_object($result)) {
590+ $nodes[$node->nid] = TRUE;
591+ }
592+ return drupal_get_form('planet_multiple_delete_confirm', $nodes,
593+ $fid, $path);
594+}
595+
596+/**
597+ * Page callback for 'user/%user/planet' ("Planet Feeds")
598+ *
599+ * @ingroup page
600+ * @CRUD{planet_feeds,R}
601+ * @invoke{drupal_get_form,planet_multiple_delete_confirm_submit}
602+ * @invoke{drupal_get_form,planet_feed_form}
603+ */
604 function planet_user_feeds() {
605-global $user;
606-if ($_POST) {
607- $edit = $_POST;
608- if ($_POST['op'] == 'Delete' && intval($edit['fid']) > 0) {
609- $result = db_query('SELECT nid FROM {planet_items} WHERE fid = %d', intval($edit['fid']));
610- while ($node = db_fetch_object($result)) {
611- $nodes[$node->nid] = TRUE;
612- }
613- return drupal_get_form('planet_multiple_delete_confirm', $nodes, intval($edit['fid']), 'user/'. $user->uid .'/planet');
614- }
615- else if ($_POST['op'] == 'Delete all' && $_POST['confirm'] == 1) {
616- $edit['fid'] = intval(arg(3));
617- $edit['redirect'] = 'user/'. $user->uid .'/planet';
618- return drupal_get_form('planet_multiple_delete_confirm_submit', $edit);
619- }
620- else {
621- if (isset($edit['fid']) && intval($edit['fid']) == 0) {
622- db_query('INSERT INTO {planet_feeds} (uid, title, link, image, checked, frozen) VALUES(%d, "%s", "%s", "%s", 0, 0)', $user->uid, $edit['title'], $edit['link'], $edit['image']);
623- $edit_r = db_fetch_array(db_query('SELECT fid FROM {planet_feeds} WHERE uid = %d AND title = "%s" AND link = "%s"', $user->uid, $edit['title'], $edit['link']));
624- $title = planet_refresh(intval($edit_r['fid']));
625- drupal_set_message('Added new feed: ' . $title);
626- }
627- else if ($edit['fid'] && intval($edit['fid']) > 0) {
628- db_query('UPDATE {planet_feeds} SET uid = %d, title="%s", link = "%s", image="%s" WHERE fid=%d', $user->uid, $edit['title'], $edit['link'], $edit['image'], $edit['fid']);
629- drupal_set_message('Edited "'. $edit['title'] .'" feed.');
630- }
631- else {
632- if ($edit['planet_author_roles']) {
633- variable_set('planet_author_roles', $edit['planet_author_roles']);
634- }
635- if ($edit['planet_filter_formats']) {
636- variable_set('planet_filter_formats', $edit['planet_filter_formats']);
637- }
638- if ($edit['planet_redirect_page'] == 1) {
639- variable_set('planet_redirect_page', $edit['planet_redirect_page']);
640- }
641- else {
642- variable_del('planet_redirect_page');
643- }
644- drupal_set_message('Edited general planet settings.');
645- }
646- }
647- drupal_goto('user/'. $user->uid .'/planet');
648+ global $user;
649+ if ($_POST) {
650+ planet__update($_POST, 'user/'. $user->uid .'/planet');
651 }
652- else {
653- $fid = intval(arg(3));
654+ else { // no $_POST
655+ $fid = intval(arg(3)); // @deprecated: arg()
656 if ($fid > 0) {
657 $edit = db_fetch_array(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));
658 $output .= drupal_get_form('planet_feed_form', $edit, true, $user);
659@@ -199,79 +270,183 @@
660
661 $output .= drupal_get_form('planet_feed_form', $edit, false, $user);
662
663- // $result = db_query('SELECT *, (UNIX_TIMESTAMP(NOW()) - checked) _checked FROM {planet_feeds}');
664- $result = db_query('SELECT COUNT(f.fid) cnt, f.*, (UNIX_TIMESTAMP(NOW()) - checked) _checked FROM {planet_feeds} f LEFT OUTER JOIN {planet_items} i ON i.fid = f.fid WHERE f.uid = %d GROUP BY f.fid;', $user->uid);
665+ $output .= '<h2>Feeds</h2>';
666+ $output .= planet__build_user_feeds_table();
667+ }
668+ print theme('page', $output);
669+ }
670+}
671+
672+// Note: I use 'planet__' prefix to name internal functions
673+// TODO: apply the same naming convention (this one or another) in whole module
674+/**
675+ * @todo merge with planet__build_admin_feeds_table()
676+ *
677+ * @ingroup planet_feed
678+ * @internal
679+ * @CRUD{planet_feeds,R}
680+ * @CRUD{planet_items,R}
681+ */
682+function planet__build_user_feeds_table() {
683+ // @DIFFINFO renamed planet__build_feeds_table1
684+ global $user;
685+ $feeds = db_query('SELECT fid, title, checked FROM {planet_feeds} '
686+ . ' WHERE uid = %d;', $user->uid);
687 $rows = array();
688 $headers = array('Feed', 'Items', 'Edit', 'Last checked');
689- while ($feed = db_fetch_object($result)) {
690- $checked = intval($feed->_checked / 60) .' minutes';
691- if ($feed->_checked % 60 > 0) {
692+ $now = time();
693+ $items_statement = 'SELECT count(*) FROM {planet_items}'
694+ . ' WHERE fid=%d';
695+ // TODO: change this to prepared statement when supported by drupal DB abstraction layer.
696+ while ($feed = db_fetch_object($feeds)) {
697+ $_checked = $now - $feed->checked;
698+ $checked = intval($_checked / 60) .' minutes';
699+ if ($_checked % 60 > 0) {
700 $checked .= ', '. $feed->_checked % 60 .' seconds';
701 }
702 $checked .= ' ago';
703+ $items = db_query($items_statement, $feed->fid);
704+ if (!$items) trigger_error('ERROR while counting feed items.');
705+ $cnt = db_result($items);
706 array_push($rows, array(
707 $feed->title,
708- $feed->cnt,
709+ $cnt,
710 l('edit', 'user/'. $user->uid .'/planet/'. intval($feed->fid)),
711 $checked,
712 )
713 );
714 }
715- $output .= '<h2>Feeds</h2>';
716- $output .= theme('table', $headers, $rows);
717- }
718- print theme('page', $output);
719- }
720-}
721-
722-
723+ return theme('table', $headers, $rows);
724+}
725+
726+/**
727+ * Updates a feed
728+ */
729+function planet__update($edit, $path) {
730+ $fid = intval($edit['fid']);
731+ if (($edit['op'] == 'Delete') && ($fid != 0)) {
732+ return planet__drop_feed($fid, $edit, $path);
733+ }
734+ else if ($edit['op'] == 'Delete all' && $edit['confirm'] == 1) {
735+ $edit['fid'] = intval(arg(3)); // @deprecated arg()
736+ $edit['redirect'] = $path;
737+ return drupal_get_form('planet_multiple_delete_confirm_submit', $edit);
738+ }
739+ else {
740+ planet__submit_feed_data($edit);
741+ }
742+ drupal_goto($path);
743+}
744+
745+/**
746+ * Submit the feed's data to the appropriate update or add function
747+ */
748+function planet__submit_feed_data($data) {
749+ if (isset($data['fid'])) {
750+ if (intval($data['fid']) == 0) {
751+ planet__add_feed($data);
752+ } else {
753+ planet__update_feed($data);
754+ }
755+ } else {
756+ // TODO if user needs to click separately for each fieldset,
757+ // why not using different forms ?
758+ // or conversely, allow setting everything at once ?
759+ planet__update_vars($data);
760+ }
761+}
762+
763+/**
764+ * Adds a new feed to planet_feeds table.
765+ * @param $data associative array with keys
766+ * 'uid' (optional), 'title', 'link', 'image' (optional)
767+ * @return success status
768+ *
769+ * @ingroup planet_feed
770+ * @internal
771+ * @CRUD{planet_feeds,C}
772+ */
773+function planet__add_feed($data) {
774+ $data['checked'] = 0;
775+ $data['frozen'] = 0;
776+ // @DIFFINFO Removed debugging block. Internal function is supposed to receive clean arguments. Checking will be left to planet_feed class
777+ $rslt = drupal_write_record('planet_feeds', $data);
778+ if ($rslt==SAVED_NEW) {
779+ $title = planet_refresh(intval($data['fid']));
780+ drupal_set_message('Added new feed: ' . $title);
781+ return TRUE;
782+ }
783+ trigger_error('Failed to add new feed');
784+ return FALSE;
785+}
786+
787+/**
788+ * Updates an existing feed in planet_feeds table.
789+ * @param $data associative array with keys
790+ * 'fid' (mandatory, primary key),
791+ * other fields as needed:
792+ * 'uid', 'title', 'link', 'image', 'checked', 'frozen'
793+ * @return success status
794+ *
795+ * @ingroup planet_feed
796+ * @internal
797+ * @CRUD{planet_feeds,U}
798+*/
799+function planet__update_feed($data) {
800+ $rslt = drupal_write_record('planet_feeds', $data, 'fid');
801+
802+ if ($rslt==SAVED_UPDATED) {
803+ drupal_set_message('Edited "'. $data['title'] .'" feed.');
804+ return TRUE;
805+ }
806+ if ($rslt==SAVED_NEW) // @DEBUGGING
807+ trigger_error('Feed {$data->fid} didn\'t exist. Record added.', E_ERROR);
808+ else
809+ trigger_error('Failed to edit feed');
810+ return FALSE;
811+}
812+
813+/**
814+ * Updates an planet settings persistent variables.
815+ * @param $data associative array with all optional keys
816+ * 'planet_author_roles', 'planet_filter_formats',
817+ * 'planet_redirect_page'.
818+ *
819+ * @ingroup isettings
820+ * @internal
821+ * @CRUD{variables,CUD}
822+*/
823+function planet__update_vars($data) {
824+ if ($data['planet_author_roles']) {
825+ variable_set('planet_author_roles', $data['planet_author_roles']);
826+ }
827+ if ($data['planet_filter_formats']) {
828+ variable_set('planet_filter_formats', $data['planet_filter_formats']);
829+ }
830+ if ($data['planet_redirect_page'] == 1) {
831+ variable_set('planet_redirect_page', $data['planet_redirect_page']);
832+ } else {
833+ variable_del('planet_redirect_page');
834+ }
835+ drupal_set_message('Edited general planet settings.');
836+}
837+
838+/**
839+ * Page callback for 'admin/settings/planet' ("Planet Settings")
840+ *
841+ * @ingroup page
842+ * @CRUD{planet_items,R}
843+ * @CRUD{planet_feeds,R}
844+ * @invoke{drupal_get_form,planet_multiple_delete_confirm}
845+ * @invoke{drupal_get_form,planet_multiple_delete_confirm_submit}
846+ * @invoke{drupal_get_form,planet_feed_form}
847+ * @invoke{drupal_get_form,planet_settings_form}
848+ */
849 function _planet_settings() {
850 if ($_POST) {
851- $edit = $_POST;
852-
853- if ($_POST['op'] == 'Delete' && intval($edit['fid']) > 0) {
854- $result = db_query('SELECT nid FROM {planet_items} WHERE fid = %d', intval($edit['fid']));
855- while ($node = db_fetch_object($result)) {
856- $nodes[$node->nid] = TRUE;
857- }
858- return drupal_get_form('planet_multiple_delete_confirm', $nodes, intval($edit['fid']), 'admin/settings/planet');
859- }
860- else if ($_POST['op'] == 'Delete all' && $_POST['confirm'] == 1) {
861- $edit['fid'] = intval(arg(3));
862- $edit['redirect'] = 'admin/settings/planet';
863- return drupal_get_form('planet_multiple_delete_confirm_submit', $edit);
864- }
865- else {
866- if (isset($edit['fid']) && intval($edit['fid']) == 0) {
867- db_query('INSERT INTO {planet_feeds} (uid, title, link, image, checked, frozen) VALUES(%d, "%s", "%s", "%s", 0, 0)', $edit['uid'], $edit['title'], $edit['link'], $edit['image']);
868- $edit_r = db_fetch_array(db_query('SELECT fid FROM {planet_feeds} WHERE uid = %d AND title = "%s" AND link = "%s"', $edit['uid'], $edit['title'], $edit['link']));
869- $title = planet_refresh(intval($edit_r['fid']));
870- drupal_set_message('Added new feed: ' . $title);
871- }
872- else if ($edit['fid'] && intval($edit['fid']) > 0) {
873- db_query('UPDATE {planet_feeds} SET uid = %d, title="%s", link = "%s", image="%s" WHERE fid=%d', $edit['uid'], $edit['title'], $edit['link'], $edit['image'], $edit['fid']);
874- drupal_set_message('Edited "'. $edit['title'] .'" feed.');
875- }
876- else {
877- if ($edit['planet_author_roles']) {
878- variable_set('planet_author_roles', $edit['planet_author_roles']);
879- }
880- if ($edit['planet_filter_formats']) {
881- variable_set('planet_filter_formats', $edit['planet_filter_formats']);
882- }
883- if ($edit['planet_redirect_page'] == 1) {
884- variable_set('planet_redirect_page', $edit['planet_redirect_page']);
885- }
886- else {
887- variable_del('planet_redirect_page');
888- }
889- drupal_set_message('Edited general planet settings.');
890- }
891- }
892- drupal_goto('admin/settings/planet');
893- }
894- else {
895- $fid = intval(arg(3));
896+ planet__update($_POST, 'admin/settings/planet');
897+ } else {
898+ $fid = intval(arg(3)); // @deprecated arg()
899 if ($fid > 0) {
900 $edit = db_fetch_array(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));
901 $output .= drupal_get_form('planet_feed_form', $edit, true);
902@@ -279,38 +454,72 @@
903 else {
904
905 $output .= drupal_get_form('planet_settings_form');
906- //$output .= drupal_get_form('settings', $form);
907- //$output .= $form;
908
909 $output .= drupal_get_form('planet_feed_form', $edit);
910
911- // $result = db_query('SELECT *, (UNIX_TIMESTAMP(NOW()) - checked) _checked FROM {planet_feeds}');
912- $result = db_query('SELECT COUNT(f.fid) cnt, f.*, (UNIX_TIMESTAMP(NOW()) - checked) _checked FROM {planet_feeds} f LEFT OUTER JOIN {planet_items} i ON i.fid = f.fid GROUP BY f.fid;');
913- $rows = array();
914- $headers = array('Feed', 'Items', 'Edit', 'Last checked', 'Refresh', 'Freeze');
915- while ($feed = db_fetch_object($result)) {
916- $checked = intval($feed->_checked / 60) .' minutes';
917- if ($feed->_checked % 60 > 0) {
918- $checked .= ', '. $feed->_checked % 60 .' seconds';
919- }
920- $checked .= ' ago';
921- array_push($rows, array(
922- $feed->title,
923- $feed->cnt,
924- l('edit', 'admin/settings/planet/'. intval($feed->fid)),
925- $checked,
926- l('refresh', 'admin/settings/planet/refresh/'. intval($feed->fid)),
927- l($feed->frozen ? 'unfreeze' : 'freeze', 'admin/settings/planet/'. ($feed->frozen ? 'unfreeze/' : 'freeze/') . intval($feed->fid))
928- )
929- );
930- }
931 $output .= '<h2>Feeds</h2>';
932- $output .= theme('table', $headers, $rows);
933+ $output .= planet__build_admin_feeds_table();
934 }
935 print theme('page', $output);
936 }
937 }
938
939+/**
940+ * builds an HTML table of feeds for administrator interaction.
941+ *
942+ * @return
943+ *
944+ * @ingroup planet_feed
945+ * @internal
946+ * @CRUD{planet_feeds,R}
947+ * @CRUD{planet_items,R}
948+ */
949+function planet__build_admin_feeds_table() {
950+ // @DIFFINFO renamed planet__build_feeds_table() {
951+ global $user;
952+ $feeds = db_query('SELECT fid, title, checked, frozen FROM {planet_feeds} '
953+ . ' WHERE uid = %d;', $user->uid);
954+ $rows = array();
955+ $headers = array('Feed', 'Items', 'Edit', 'Last checked', 'Refresh', 'Freeze');
956+ $now = time();
957+ $items_statement = 'SELECT count(*) FROM {planet_items} WHERE fid=%d';
958+ // TODO: change this to prepared statement when supported by drupal DB Abstraction Layer.
959+ while ($feed = db_fetch_object($feeds)) {
960+ $_checked = $now - $feed->checked;
961+ $checked = intval($_checked / 60) .' minutes';
962+ if ($_checked % 60 > 0) {
963+ $checked .= ', '. $feed->_checked % 60 .' seconds';
964+ }
965+ $checked .= ' ago';
966+ $items = db_query($items_statement, $feed->fid);
967+ if (!$items) trigger_error('ERROR while counting feed items.');
968+ $cnt = db_result($items);
969+ $fid = strval($feed->fid);
970+ array_push($rows, array(
971+ $feed->title,
972+ $cnt,
973+ l('edit', 'admin/settings/planet/'. $fid),
974+ $checked,
975+ l('refresh', 'admin/settings/planet/refresh/'. $fid),
976+ l($feed->frozen ? 'unfreeze' : 'freeze', 'admin/settings/planet/'. ($feed->frozen ? 'unfreeze/' : 'freeze/') . $fid)
977+ )
978+ );
979+ }
980+ return theme('table', $headers, $rows);
981+}
982+
983+/**
984+ * TODO.
985+ *
986+ * @param &$form_state
987+ * @param $nodes
988+ * @param $fid
989+ * @param $redirect
990+ * @return
991+ *
992+ * @ingroup iforms
993+ * @CRUD{node,R}
994+ */
995 function planet_multiple_delete_confirm(&$form_state, $nodes, $fid, $redirect) {
996 $form_state['values']['fid'] = $fid;
997 $form_state['values']['redirect'] = $redirect;
998@@ -333,21 +542,42 @@
999 t('Delete all'), t('Cancel'));
1000 }
1001
1002+/**
1003+ * TODO.
1004+ *
1005+ * @param &$form_state
1006+ * @param $edit
1007+ * @return
1008+ *
1009+ * @ingroup iforms
1010+ * @CRUD{node,D}
1011+ * @CRUD{planet_items,D}
1012+ * @CRUD{planet_feeds,D}
1013+ */
1014 function planet_multiple_delete_confirm_submit(&$form_state, $edit) {
1015 $fid = $edit['fid'];
1016 if ($edit['confirm']) {
1017 foreach ($edit['nodes'] as $nid => $value) {
1018 node_delete($nid);
1019 }
1020+ // @DIFFINFO inverted drop order to respect foreign keys
1021+ db_query('DELETE FROM {planet_items} WHERE fid = %d', $fid);
1022 db_query('DELETE FROM {planet_feeds} WHERE fid = %d', $fid);
1023- db_query('DELETE FROM {planet_items} WHERE fid = %d', $fid);
1024 drupal_set_message(t('The feed and items have been deleted.'));
1025 }
1026 drupal_goto($edit['redirect']);
1027 }
1028
1029
1030-
1031+/**
1032+ * TODO.
1033+ *
1034+ * @param &$form_state
1035+ * @return
1036+ * @ingroup iforms
1037+ * @CRUD{role,R}
1038+ * @CRUD{variables,R}
1039+ */
1040 function planet_settings_form(&$form_state) {
1041 $roles = array();
1042
1043@@ -401,7 +631,20 @@
1044 return $form;
1045 }
1046
1047-
1048+/**
1049+ * TODO.
1050+ *
1051+ * @param &$form_state
1052+ * @param $edit
1053+ * @param $individual
1054+ * @param $user
1055+ * @return
1056+ * @ingroup iforms
1057+ * @CRUD{users,R}
1058+ * @CRUD{role,R}
1059+ * @CRUD{users_roles,R}
1060+ * @CRUD{variables,R}
1061+ */
1062 function planet_feed_form(&$form_state, $edit = array(), $individual = false, $user = NULL) {
1063 $uids = array();
1064 $result = db_query('SELECT u.uid, u.name FROM {users} u, {role} r, {users_roles} ur WHERE u.uid = ur.uid AND ur.rid = r.rid AND r.rid = %d ORDER BY u.name ASC', variable_get('planet_author_roles', 2));
1065@@ -474,6 +717,13 @@
1066 return $form;
1067 }
1068
1069+/**
1070+ * Implementation of hook_cron - Perform periodic actions.
1071+ *
1072+ * @return none
1073+ * @ingroup system
1074+ * @CRUD{planet_feeds,R}
1075+ */
1076 function planet_cron() {
1077 $result = db_query('SELECT fid FROM {planet_feeds} WHERE frozen = 0');
1078 while ($feed = db_fetch_object($result)) {
1079@@ -482,515 +732,76 @@
1080 }
1081 }
1082
1083+
1084+/**
1085+ * Private function; Checks a news feed for new items.
1086+ *
1087+ * @param $fid feed identifier (defaults to arg(4))
1088+ * @return
1089+ *
1090+ * @private
1091+ * @CRUD{planet_feeds,U}
1092+ * @invoke{module_invoke,taxonomy_node_get_terms}
1093+ */
1094 function planet_refresh($fid = null) {
1095 if (!$fid) {
1096 $fid = intval(arg(4));
1097 }
1098-
1099- $feed = db_fetch_object(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));
1100-
1101- $headers = array();
1102- $result = planet_http_request($feed->link, $headers, 15);
1103-
1104- switch ($result->code) {
1105- case 304:
1106- drupal_set_message(t('No new content syndicated from %site.', array('%site' => '<em>'. $feed->title .'</em>')));
1107- break;
1108-
1109- case 301:
1110- if ($result->redirect_url) {
1111- $feed->link = $result->redirect_url;
1112- watchdog('planet', 'Updated URL for feed %title to %url.', array('%title' => '<em>'. $feed->title .'</em>', '%url' => '<em>'. $feed->url .'</em>'), WATCHDOG_NOTICE, l(t('view'), 'planet/'.$feed->fid));
1113- db_query("UPDATE {planet_feeds} SET link = '%s' WHERE fid = %d", $feed->link, $feed->fid);
1114- }
1115- break;
1116-
1117- case 200:
1118- case 302:
1119- case 307:
1120- $xml_tree = planet_parse_xml($result->data);
1121-
1122- if ($xml_tree['parser_error']) {
1123- watchdog('planet', 'Failed to parse RSS feed %site: %error at line %line.', array('%site' => '<em>'. $feed->title .'</em>', '%error' => $xml_tree['parser_error'], '%line' => $xml_tree['parser_line']), WATCHDOG_ERROR);
1124- drupal_set_message(t('Failed to parse RSS feed %site: %error at line %line.', array('%site' => '<em>'. $feed->title .'</em>', '%error' => $xml_tree['parser_error'], '%line' => $xml_tree['parser_line'])), 'error');
1125- break;
1126- }
1127- else {
1128- drupal_set_message('Parsing feed '. $feed->title .' took '. $xml_tree['parser_time'] .' seconds.');
1129- }
1130-
1131- if (planet_parse_items($xml_tree, $feed) !== false) {
1132- if ($result->headers['Last-Modified']) {
1133- $modified = strtotime($result->headers['Last-Modified']);
1134- }
1135-
1136- /*
1137- ** Prepare data:
1138- */
1139- if ($xml_tree['RSS']) { // RSS 0.91, 0.92, 2.0
1140- $root = &$xml_tree['RSS'][0];
1141- $channel = &$root['CHANNEL'][0];
1142- $image = &$channel['IMAGE'][0];
1143- $description = &$channel['DESCRIPTION'][0]['VALUE'];
1144- $link = &$channel['LINK'][0]['VALUE'];
1145- }
1146- else if ($xml_tree['RDF:RDF']) {
1147- $root = &$xml_tree['RDF:RDF'][0];
1148- $channel = &$root['CHANNEL'][0];
1149- $image = &$root['IMAGE'][0];
1150- $description = &$channel['DESCRIPTION'][0]['VALUE'];
1151- $link = &$channel['LINK'][0]['VALUE'];
1152- }
1153- else if ($xml_tree['FEED']) { // Atom 0.3, 1.0
1154- $root = &$xml_tree['FEED'][0];
1155- $channel = &$root;
1156- $image = &$channel['LOGO'][0]['VALUE'];
1157- $description = ($channel['TAGLINE'][0]['VALUE'] ? $channel['TAGLINE'][0]['VALUE'] : '');
1158- // TODO: remove this Atom hack when we have field mapping or at least specialized parsers in place
1159- if (count($channel['LINK']) > 1) {
1160- $link = $feed->link;
1161- foreach ($channel['LINK'] as $l) {
1162- if ($l['REL'] == 'alternate') {
1163- $link = $l['HREF'];
1164- }
1165- }
1166- }
1167- else {
1168- $link = $channel['LINK'][0]['HREF'];
1169- }
1170- }
1171- else if ($xml_tree['CHANNEL']) { // RSS 1.1
1172- $root = &$xml_tree['CHANNEL'][0];
1173- $channel = &$root;
1174- $image = &$channel['IMAGE'][0];
1175- $description = &$channel['DESCRIPTION'][0]['VALUE'];
1176- $link = &$channel['LINK'][0]['VALUE'];
1177- }
1178- else if ($xml_tree['OPML']) {
1179- $root = &$xml_tree['OPML'][0];
1180- $channel = &$root;
1181- $image = NULL;
1182- $description = NULL;
1183- $link = NULL;
1184- }
1185- else {
1186- // unsupported format
1187- break;
1188- }
1189-
1190- if (!$feed->uid) {
1191- if ($channel['AUTHOR'][0]['VALUE']) {
1192- $feed->uid = $channel['AUTHOR'][0]['VALUE'];
1193- }
1194- if ($channel['AUTHOR'][0]['NAME'][0]['VALUE']) {
1195- $feed->uid = $channel['AUTHOR'][0]['NAME'][0]['VALUE'];
1196- }
1197- else if ($channel['DC:CREATOR']) {
1198- $feed->uid = $channel['DC:CREATOR'][0]['VALUE'];
1199- }
1200- else {
1201- $feed->uid = '';
1202- }
1203- }
1204-
1205- /*
1206- ** Generate image link
1207- */
1208- if (!$feed->image && $image['LINK'] && $image['URL'] && $image['TITLE']) {
1209- if (strlen($image['TITLE'][0]['VALUE']) > 250) {
1210- $image['TITLE'][0]['VALUE'] = trim(substr($image['TITLE'][0]['VALUE'], 0, 250)).'...';
1211- }
1212- $feed->image = '<a href="'. $image['LINK'][0]['VALUE'] .'" class="planet_logo_link"><img src="'. $image['URL'][0]['VALUE'] .'" class="planet_logo" alt="'. $image['TITLE'][0]['VALUE'] .'" /></a>';
1213- }
1214-
1215- /*
1216- ** Update the feed data:
1217- */
1218- $feed->checked = time();
1219- $feed->link = $link;
1220- $feed->etag = $result->headers['ETag'];
1221- $feed->modified = $modified;
1222- if ($feed->body == '' && $description/* && valid_input_data($description)*/) {
1223- $feed->body = $feed->teaser = $description;
1224- }
1225- $feed->rss_data = &$xml_tree;
1226-
1227- /*
1228- ** Taxonomy module doesn't add taxonomy terms at load time... so we have to do it by hand :((
1229- */
1230- $terms = module_invoke('taxonomy', 'node_get_terms', $feed->nid, 'tid');
1231- $feed->taxonomy = array();
1232- foreach ($terms as $tid => $term) {
1233- if ($term->tid) {
1234- $feed->taxonomy[] = $term->tid;
1235- }
1236- }
1237- }
1238- default:
1239- }
1240-
1241-
1242- db_query('UPDATE {planet_feeds} SET checked = %d WHERE fid = %d', time(), $fid);
1243- return $feed->title;
1244- //print theme('page', 'refreshing '. $fid .'.');// and got '. print_r($feed, 1));
1245-}
1246-
1247-/**
1248- * Private function; Parse HTTP headers from data retreived with cURL
1249- * from: http://pl2.php.net/manual/en/function.curl-setopt.php#42009
1250- */
1251-function planet_parse_response($response) {
1252- /*
1253- ***original code extracted from examples at
1254- ***http://www.webreference.com/programming
1255- /php/cookbook/chap11/1/3.html
1256-
1257- ***returns an array in the following format which varies depending on headers returned
1258-
1259- [0] => the HTTP error or response code such as 404
1260- [1] => Array
1261- (
1262- [Server] => Microsoft-IIS/5.0
1263- [Date] => Wed, 28 Apr 2004 23:29:20 GMT
1264- [X-Powered-By] => ASP.NET
1265- [Connection] => close
1266- [Set-Cookie] => COOKIESTUFF
1267- [Expires] => Thu, 01 Dec 1994 16:00:00 GMT
1268- [Content-Type] => text/html
1269- [Content-Length] => 4040
1270- )
1271- [2] => Response body (string)
1272- */
1273-
1274- do {
1275- list($response_headers, $response) = explode("\r\n\r\n", $response, 2);
1276- $response_header_lines = explode("\r\n", $response_headers);
1277-
1278- // first line of headers is the HTTP response code
1279- $http_response_line = array_shift($response_header_lines);
1280- if (preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@', $http_response_line, $matches)) {
1281- $response_code = $matches[1];
1282- }
1283- else {
1284- $response_code = "Error";
1285- }
1286- }
1287- while (substr($response_code, 0, 1) == "1");
1288-
1289- $response_body = $response;
1290-
1291- // put the rest of the headers in an array
1292- $response_header_array = array();
1293- foreach ($response_header_lines as $header_line) {
1294- list($header, $value) = explode(':', $header_line, 2);
1295- $response_header_array[$header] = trim($value);
1296- }
1297-
1298- return array($response_code, $response_header_array, $response_body, $http_response_line);
1299-}
1300-
1301-/**
1302- * Private function; Gets data from given URL :)
1303- */
1304-function planet_http_request($url, $headers = array(), $timeout = 15, $method = 'GET', $data = NULL, $follow = 3) {
1305- if (!function_exists('curl_init')) {
1306- return drupal_http_request($url, $headers, $method, $data, $follow);
1307- }
1308-
1309- // convert headers array to format used by cURL
1310- $temp = array();
1311- foreach ($headers as $header => $value) {
1312- $temp[] = $header .': '. $value;
1313- }
1314- $headers = $temp;
1315-
1316- $result = new StdClass();
1317-
1318- $ch = curl_init();
1319- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1320- curl_setopt($ch, CURLOPT_URL, $url);
1321- curl_setopt($ch, CURLOPT_HEADER, 1);
1322- curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1323- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
1324- curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1325-
1326- $data = curl_exec($ch);
1327- $info = curl_getinfo($ch);
1328-
1329- curl_close($ch);
1330- unset($ch);
1331-
1332- $response = planet_parse_response($data);
1333- $result->code = $response[0];
1334- $result->headers = $response[1];
1335- $result->data = $response[2];
1336- $error = $response[3];
1337- switch ($code) {
1338- case 200: // OK
1339- case 304: // Not modified
1340- break;
1341- case 301: // Moved permanently
1342- case 302: // Moved temporarily
1343- case 307: // Moved temporarily
1344- $location = $result->headers['Location'];
1345-
1346- if ($follow) {
1347- $result = planet_http_request($result->headers['Location'], $headers, $timeout, $method, $data, --$follow);
1348- $result->redirect_code = $result->code;
1349- }
1350- $result->redirect_url = $location;
1351- break;
1352- default:
1353- $result->error = $error;
1354- break;
1355- }
1356-
1357- $result->code = $response[0];
1358- return $result;
1359-}
1360-
1361-/**
1362- * Private function; Checks a news feed for new items.
1363- */
1364-
1365-
1366-/**
1367- * Private function;
1368- * Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing
1369- * functions do not handle this format.
1370- * See http://www.w3.org/TR/NOTE-datetime for more information.
1371- * Origionally from MagpieRSS (http://magpierss.sourceforge.net/).
1372- *
1373- * @param $date_str A string with a potentially W3C DTF date.
1374- * @return A timestamp if parsed successfully or -1 if not.
1375- */
1376-function planet_parse_w3cdtf($date_str) {
1377- if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) {
1378- list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
1379- // calc epoch for current date assuming GMT
1380- $epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);
1381- if ($match[10] != 'Z') { // Z is zulu time, aka GMT
1382- list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);
1383- // zero out the variables
1384- if (!$tz_hour) {
1385- $tz_hour = 0;
1386- }
1387- if (!$tz_min) {
1388- $tz_min = 0;
1389- }
1390- $offset_secs = (($tz_hour * 60) + $tz_min) * 60;
1391- // is timezone ahead of GMT? then subtract offset
1392- if ($tz_mod == '+') {
1393- $offset_secs *= -1;
1394- }
1395- $epoch += $offset_secs;
1396- }
1397- return $epoch;
1398- }
1399- else {
1400- return -1;
1401- }
1402-}
1403-
1404-/**
1405- * Private function;
1406- * from: http://pl2.php.net/manual/en/function.html-entity-decode.php#51055
1407- * Used as callback function for preg_replace_all() to decode numeric entities to UTF-8 chars
1408- *
1409- * @param $ord Number
1410- * @return UTF-8 string
1411- */
1412-function planet_replace_num_entity($ord) {
1413- $ord = $ord[1];
1414- if (preg_match('/^x([0-9a-f]+)$/i', $ord, $match)) {
1415- $ord = hexdec($match[1]);
1416- }
1417- else {
1418- $ord = intval($ord);
1419- }
1420-
1421- $no_bytes = 0;
1422- $byte = array();
1423-
1424- if ($ord == 128) {
1425- return chr(226) . chr(130) . chr(172);
1426- }
1427- else if ($ord == 129) {
1428- return chr(239) . chr(191) . chr(189);
1429- }
1430- else if ($ord == 130) {
1431- return chr(226) . chr(128) . chr(154);
1432- }
1433- else if ($ord == 131) {
1434- return chr(198) . chr(146);
1435- }
1436- else if ($ord == 132) {
1437- return chr(226) . chr(128) . chr(158);
1438- }
1439- else if ($ord == 133) {
1440- return chr(226) . chr(128) . chr(166);
1441- }
1442- else if ($ord == 134) {
1443- return chr(226) . chr(128) . chr(160);
1444- }
1445- else if ($ord == 135) {
1446- return chr(226) . chr(128) . chr(161);
1447- }
1448- else if ($ord == 136) {
1449- return chr(203) . chr(134);
1450- }
1451- else if ($ord == 137) {
1452- return chr(226) . chr(128) . chr(176);
1453- }
1454- else if ($ord == 138) {
1455- return chr(197) . chr(160);
1456- }
1457- else if ($ord == 139) {
1458- return chr(226) . chr(128) . chr(185);
1459- }
1460- else if ($ord == 140) {
1461- return chr(197) . chr(146);
1462- }
1463- else if ($ord == 141) {
1464- return chr(239) . chr(191) . chr(189);
1465- }
1466- else if ($ord == 142) {
1467- return chr(197) . chr(189);
1468- }
1469- else if ($ord == 143) {
1470- return chr(239) . chr(191) . chr(189);
1471- }
1472- else if ($ord == 144) {
1473- return chr(239) . chr(191) . chr(189);
1474- }
1475- else if ($ord == 145) {
1476- return chr(226) . chr(128) . chr(152);
1477- }
1478- else if ($ord == 146) {
1479- return chr(226) . chr(128) . chr(153);
1480- }
1481- else if ($ord == 147) {
1482- return chr(226) . chr(128) . chr(156);
1483- }
1484- else if ($ord == 148) {
1485- return chr(226) . chr(128) . chr(157);
1486- }
1487- else if ($ord == 149) {
1488- return chr(226) . chr(128) . chr(162);
1489- }
1490- else if ($ord == 150) {
1491- return chr(226) . chr(128) . chr(147);
1492- }
1493- else if ($ord == 151) {
1494- return chr(226) . chr(128) . chr(148);
1495- }
1496- else if ($ord == 152) {
1497- return chr(203) . chr(156);
1498- }
1499- else if ($ord == 153) {
1500- return chr(226) . chr(132) . chr(162);
1501- }
1502- else if ($ord == 154) {
1503- return chr(197) . chr(161);
1504- }
1505- else if ($ord == 155) {
1506- return chr(226) . chr(128) . chr(186);
1507- }
1508- else if ($ord == 156) {
1509- return chr(197) . chr(147);
1510- }
1511- else if ($ord == 157) {
1512- return chr(239) . chr(191) . chr(189);
1513- }
1514- else if ($ord == 158) {
1515- return chr(197) . chr(190);
1516- }
1517- else if ($ord == 159) {
1518- return chr(197) . chr(184);
1519- }
1520- else if ($ord == 160) {
1521- return chr(194) . chr(160);
1522- }
1523-
1524- if ($ord < 128) {
1525- return chr($ord);
1526- }
1527- else if ($ord < 2048) {
1528- $no_bytes = 2;
1529- }
1530- else if ($ord < 65536) {
1531- $no_bytes = 3;
1532- }
1533- else if ($ord < 1114112) {
1534- $no_bytes = 4;
1535- }
1536- else {
1537- return;
1538- }
1539-
1540- switch ($no_bytes) {
1541- case 2:
1542- $prefix = array(31, 192);
1543- break;
1544-
1545- case 3:
1546- $prefix = array(15, 224);
1547- break;
1548-
1549- case 4:
1550- $prefix = array(7, 240);
1551- break;
1552- }
1553-
1554- for ($i = 0; $i < $no_bytes; $i++) {
1555- $byte[$no_bytes - $i - 1] = (($ord & (63 * pow(2, 6 * $i))) / pow(2, 6 * $i)) & 63 | 128;
1556- }
1557-
1558- $byte[0] = ($byte[0] & $prefix[0]) | $prefix[1];
1559-
1560- $ret = '';
1561- for ($i = 0; $i < $no_bytes; $i++) {
1562- $ret .= chr($byte[$i]);
1563- }
1564-
1565- return $ret;
1566-}
1567-
1568-/**
1569- * Private function; Convert named entities to UTF-8 characters
1570- * from: http://pl2.php.net/manual/en/function.html-entity-decode.php#51722
1571- */
1572-function planet_replace_name_entities(&$text) {
1573- static $ttr;
1574- if (!$ttr) {
1575- $trans_tbl = get_html_translation_table(HTML_ENTITIES);
1576- foreach ($trans_tbl as $k => $v) {
1577- $ttr[$v] = utf8_encode($k);
1578- }
1579- $ttr['&apos;'] = "'";
1580- }
1581- return strtr($text, $ttr);
1582-}
1583-
1584-/**
1585- * Private function; Convert all entities to UTF-8 characters
1586- */
1587-function planet_replace_entities(&$text) {
1588- $result = planet_replace_name_entities($text);
1589- return preg_replace_callback('/&#([0-9a-fx]+);/mi', 'planet_replace_num_entity', $result);
1590-}
1591-
1592-/**
1593- * Private function; Clone object function to stay compatible with both php4 and php5
1594- * from: Drupal 4.7CVS
1595- * TODO: remove after moving to Drupal 4.7
1596- */
1597-function planet_clone($object) {
1598- return version_compare(phpversion(), '5.0') < 0 ? $object : clone($object);
1599-}
1600+ // initialize simplepie
1601+ // we want to do this only once and not each time per feed, which would be slower
1602+ include_once './'. drupal_get_path('module', 'planet') .'/simplepie.inc';
1603+
1604+ $process_feed = db_fetch_object(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));
1605+
1606+ $feed = new SimplePie();
1607+ $feed->enable_cache(FALSE);
1608+ $feed->set_timeout(15);
1609+ // prevent SimplePie from using all of it's data santization since we use Drupal's input formats to handle this
1610+ $feed->set_stupidly_fast(TRUE);
1611+ $feed->set_feed_url($process_feed->link);
1612+ // FeedBurner requires this check otherwise it won't work well with SimplePie
1613+ // also performance improvement
1614+ header('If-Modified-Since:'. $process_feed->checked);
1615+ $success = $feed->init();
1616+
1617+ if ($success && $feed->data) {
1618+ // get a unique hash of the headers in the feed, fast and easy way to compare if this feed is updated or not
1619+ $hash = md5(serialize($feed->data));
1620+
1621+ // hashes don't match so likely the feed is updated
1622+ if ($process_feed->hash != $hash) {
1623+ // above we define hook_view() which then performs check_url() on the $url in the feed node
1624+ // the problem is check_url() calls filter_xss_bad_protocol() which does it thing to prevent XSS
1625+ // but it returns the string through check_plain() which calls htmlspecialchars()
1626+ // this converts & in a url to &amp; and then causes SimplePie not to be able to parse it
1627+ // because of this, we decode this URL since we are passing it directly to SimplePie
1628+ // it is still encoded everywhere else it is output to prevent XSS
1629+ $process_feed->link = htmlspecialchars_decode($process_feed->link, ENT_QUOTES);
1630+
1631+ // turn each feed item into a node
1632+ planet_item_feed_parse($process_feed, $feed);
1633+ }
1634+
1635+ // finished processing this feed so we can mark it checked
1636+ db_query("UPDATE {planet_feeds} SET checked = %d, hash = '%s', error = 0 WHERE fid = %d", time(), $hash, $process_feed->fid);
1637+ }
1638+ else if (isset($feed->error)) {
1639+ db_query("UPDATE {planet_feeds} SET error = 1 WHERE fid = %d", $process_feed->fid);
1640+ 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));
1641+ }
1642+ else {
1643+ watchdog('planet', 'You shouldn\'t be here. Something has gone terribly wrong.');
1644+ }
1645+
1646+ return $process_feed->title;
1647+}
1648+
1649+// @DIFFINFO removed orphaned doc-block
1650
1651 /**
1652 * Private function; Convert relative URLs
1653+ * @ingroup rss
1654+ * @private
1655 */
1656 function planet_convert_relative_urls(&$data, $base_url) {
1657 $src = '%( href| src)="(?!\w+://)/?([^"]*)"%';
1658@@ -999,340 +810,11 @@
1659 }
1660
1661 /**
1662- * Private function; Creates nodes from data found in given xml_tree
1663- */
1664-function planet_parse_items(&$xml_tree, &$feed) {
1665-
1666- if ($xml_tree['RSS']) { // RSS 0.91, 0.92, 2.0
1667- $items = &$xml_tree['RSS'][0]['CHANNEL'][0]['ITEM'];
1668- $link_field = 'VALUE';
1669- }
1670- else if ($xml_tree['RDF:RDF']) {
1671- $items = &$xml_tree['RDF:RDF'][0]['ITEM'];
1672- $link_field = 'VALUE';
1673- }
1674- else if ($xml_tree['FEED']) { // Atom 0.3, 1.0
1675- $items = &$xml_tree['FEED'][0]['ENTRY'];
1676- $link_field = 'HREF';
1677- }
1678- else if ($xml_tree['CHANNEL']) { // RSS 1.1
1679- $items = &$xml_tree['CHANNEL'][0]['ITEMS'][0]['ITEM'];
1680- $link_field = 'VALUE';
1681- }
1682- else {
1683- // unsupported format
1684- $items = array();
1685- return false;
1686- }
1687-
1688- /*
1689- ** We reverse the array such that we store the first item last,
1690- ** and the last item first. In the database, the newest item
1691- ** should be at the top.
1692- */
1693- $items_added = 0;
1694-
1695-
1696- for ($index = count($items) - 1; $index >= 0; $index--) {
1697- $item = &$items[$index];
1698- //print '<pre>'. print_r($item, 1) .'</pre>';
1699- $teaser = NULL;
1700- $body = NULL;
1701-
1702- // Description field is needed early for case when no title is specified
1703- if ($item['DESCRIPTION']) { // RSS 0.91, 0.92, 1.0, 1.1, 2.0
1704- $body = &$item['DESCRIPTION'][0]['VALUE'];
1705- }
1706- else if ($item['SUMMARY']) { // Atom 0.3, 1.0
1707- $body = &$item['SUMMARY'][0]['VALUE'];
1708- }
1709-
1710- if ($item['CONTENT']) { // Atom 0.3, 1.0
1711- if (strlen($body) < strlen($item['CONTENT'][0]['VALUE'])) {
1712- if ($body) {
1713- $teaser = $body;
1714- }
1715- $body = &$item['CONTENT'][0]['VALUE'];
1716- }
1717- }
1718- else if ($item['CONTENT:ENCODED']) { // Don't know where it came from but it can be found in RSS 2.0 feeds
1719- if (strlen($body) < strlen($item['CONTENT:ENCODED'][0]['VALUE'])) {
1720- if ($body) {
1721- $teaser = $body;
1722- }
1723- $body = &$item['CONTENT:ENCODED'][0]['VALUE'];
1724- }
1725- }
1726-
1727- /*
1728- ** Resolve the item's title. If no title is found, we use
1729- ** up to 40 characters of the description ending at a word
1730- ** boundary but not splitting potential entities.
1731- */
1732- if (!($title = $item['TITLE'][0]['VALUE'])) {
1733- $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($body, 40));
1734- }
1735-
1736- // If title was "escaped" then it may still contain entities, becuase each & from entity was also escabet to &amp; before
1737- // TODO: the same for content?
1738- if ($item['TITLE'][0]['MODE'] == 'escaped') {
1739- $title = planet_replace_entities($title);
1740- }
1741- $title = strip_tags($title);
1742-
1743- /*
1744- ** Resolve the items link.
1745- */
1746- if ($item['LINK']) {
1747- // TODO: remove this Atom hack when we have field mapping or at least specialized parsers in place
1748- if (count($item['LINK']) > 1) {
1749- $link = $feed->link;
1750- foreach ($item['LINK'] as $temp) {
1751- if ($temp['REL'] == 'alternate') {
1752- $link = $temp[$link_field];
1753- }
1754- }
1755- }
1756- else {
1757- $link = $item['LINK'][0][$link_field];
1758- }
1759- }
1760- elseif ($item['GUID'] && (strncmp($item['GUID'][0][$link_field], 'http://', 7) == 0) && $item['GUID'][0]['ISPERMALINK'] != 'false') {
1761- $link = $item['GUID'][0][$link_field];
1762- }
1763- else {
1764- $link = $feed->link;
1765- }
1766-
1767- /*
1768- ** Resolve the items source.
1769- */
1770- if ($item['SOURCE'][0]['VALUE'] && $item['SOURCE'][0]['URL']) { // RSS 2.0
1771- $source_title = &$item['SOURCE'][0]['VALUE'];
1772- $source_link = &$item['SOURCE'][0]['URL'];
1773- }
1774- else if ($item['SOURCE'] || $item['ATOM:SOURCE']) { // ATOM 1.0
1775- if ($item['SOURCE'][0]['TITLE']) $source_title = &$item['SOURCE'][0]['TITLE'][0]['VALUE'];
1776- else if ($item['SOURCE'][0]['ATOM:TITLE']) $source_title = &$item['SOURCE'][0]['ATOM:TITLE'][0]['VALUE'];
1777- if ($item['SOURCE'][0]['LINK']) $source_link = &$item['SOURCE'][0]['LINK'][0]['VALUE'];
1778- else if ($item['SOURCE'][0]['ATOM:LINK']) $source_link = &$item['SOURCE'][0]['ATOM:LINK'][0]['VALUE'];
1779- }
1780- else {
1781- $source_title = '';
1782- $source_link = '';
1783- }
1784-
1785- /*
1786- ** Try to resolve and parse the item's publication date. If no
1787- ** date is found, we use the current date instead.
1788- */
1789- // TODO: find nicer way for handling namespaces ;)
1790- if ($item['PUBDATE']) $date = $item['PUBDATE'][0]['VALUE']; // RSS 2.0
1791- else if ($item['DC:DATE']) $date = $item['DC:DATE'][0]['VALUE']; // Dublin core
1792- else if ($item['DATE']) $date = $item['DATE'][0]['VALUE']; // Dublin core
1793- else if ($item['DCTERMS:ISSUED']) $date = $item['DCTERMS:ISSUED'][0]['VALUE']; // Dublin core
1794- else if ($item['ISSUED']) $date = $item['ISSUED'][0]['VALUE']; // Dublin core
1795- else if ($item['DCTERMS:CREATED']) $date = $item['DCTERMS:CREATED'][0]['VALUE']; // Dublin core
1796- else if ($item['CREATED']) $date = $item['CREATED'][0]['VALUE']; // Dublin core
1797- else if ($item['DCTERMS:MODIFIED']) $date = $item['DCTERMS:MODIFIED'][0]['VALUE']; // Dublin core
1798- else if ($item['MODIFIED']) $date = $item['MODIFIED'][0]['VALUE']; // Dublin core
1799- else if ($item['ATOM:UPDATED']) $date = $item['ATOM:UPDATED'][0]['VALUE']; // Atom
1800- else if ($item['UPDATED']) $date = $item['UPDATED'][0]['VALUE']; // Atom
1801- else $date = 'now';
1802-
1803- if ($feed->item_date_source == FEEDS_ITEM_DATE_SNIFFED && $date) {
1804- $timestamp = strtotime($date); // strtotime() returns -1 on failure
1805- if ($timestamp < 0) {
1806- $timestamp = planet_parse_w3cdtf($date); // also returns -1 on failure
1807- if ($timestamp < 0) {
1808- $timestamp = time(); // better than nothing
1809- }
1810- }
1811- }
1812- else {
1813- $timestamp = time();
1814- }
1815-
1816- // Ignore items older than allowed for feed
1817- if ($timestamp < $time_horizont) {
1818- continue;
1819- }
1820-
1821- /*
1822- ** Save this item. Try to avoid duplicate entries as much as
1823- ** possible. If we find a duplicate entry, we resolve it and
1824- ** pass along it's ID such that we can update it if needed.
1825- */
1826- // Try to use RSS:GUID/ATOM:ID as unique identifier
1827- $guid = '';
1828- if ($item['GUID'][0]['VALUE']) { // RSS 2.0
1829- $guid = $item['GUID'][0]['VALUE'];
1830- }
1831- else if ($item['ATOM:ID'][0]['VALUE']) { // ATOM 0.3, 1.0
1832- $guid = $item['ATOM:ID'][0]['VALUE'];
1833- }
1834- else if ($item['ID'][0]['VALUE']) { // ATOM 0.3, 1.0
1835- $guid = $item['ID'][0]['VALUE'];
1836- }
1837- else {
1838- // feed may contain duplicated links for different items, so we try to generate unique ID for each item
1839- $guid = md5("$title - . " . $feed->fid);
1840- }
1841- // TODO: is there anyway to check if DC:IDENTIFIER is unique?
1842- // http://dublincore.org/documents/usageguide/elements.shtml says it can be non-unique so useles for us :(
1843-
1844- $entry = NULL;
1845- if ($guid && strlen($guid) > 0) {
1846- $entry = db_fetch_object(db_query("SELECT nid FROM {planet_items} WHERE guid = '%s' AND fid = %d", $guid, $feed->fid));
1847- }
1848- else if ($link && $link != $feed->link && $link != $feed->url) {
1849- $entry = db_fetch_object(db_query("SELECT nid FROM {planet_items} WHERE guid = '%s' AND fid = %d", $link, $feed->fid));
1850- }
1851- else {
1852- $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'", $feed->fid, $title));
1853- }
1854-
1855- //print $guid .'<br />';
1856- //print $entry->nid .'<br />';
1857- // Ignore items already existing in database and not allowed to be updated
1858-
1859- //Fields to update in either case
1860- $entry->changed = strtotime($date);
1861- $entry->title = $title;
1862- $entry->body = $body;
1863- $entry->body = planet_convert_relative_urls($body, $link);
1864- $entry->teaser = node_teaser($entry->body);
1865- $entry->revision = true;
1866-
1867- //Fields to set if it's a new item.
1868- if (!isset($entry->nid)) {
1869- //print "Planet item " . $entry->title . "<br />";
1870- $entry->type = 'planet';
1871-
1872- $options = variable_get('node_options_planet', array());
1873-
1874- $entry->uid = $feed->uid;
1875- $entry->status = 1;
1876- $entry->moderate = 0;
1877- $entry->promote = in_array('promote', $options) ? 1 : 0;
1878- $entry->sticky = in_array('sticky', $options) ? 1 : 0;
1879- $entry->comment = in_array('comment', $options) ? 2 : 0;
1880- $entry->format = variable_get('planet_filter_formats', 1);
1881- $entry->created = strtotime($date);
1882- $entry->revision = true;
1883-
1884- $terms = module_invoke('taxonomy', 'node_get_terms', $edit->nid, 'tid');
1885- foreach ($terms as $tid => $term) {
1886- if ($term->tid) {
1887- $edit->taxonomy[] = $term->tid;
1888- }
1889- }
1890- //print '<pre>'. print_r($entry, 1) .'</pre>';
1891- node_save($entry);
1892- db_query('INSERT INTO {planet_items} (fid, nid, guid, link, created) VALUES(%d, %d, "%s", "%s", UNIX_TIMESTAMP(NOW()))', $feed->fid, $entry->nid, $guid, $link);
1893- watchdog('planet', 'Adding '. $title);
1894- drupal_set_message('Adding '. $title);
1895- }
1896- }
1897-
1898- return $items_added;
1899-}
1900-
1901-
1902-/**
1903- * Private function; parses given XML data and returns array
1904- */
1905-function planet_parse_xml(&$data) {
1906- global $xml_tree, $xml_paths, $xml_path_cur;
1907- $xml_tree = array();
1908- $xml_paths[] = &$xml_tree;
1909- $xml_path_cur = 0;
1910-
1911- $_start = microtime();
1912-
1913- // Some feeds already use CDATA but in "wrong way": http://www.rocketboom.com/vlog/quicktime_daily_enclosures.xml (ie. <description> something <CDATA soemthing else></description>
1914- $data = trim(str_replace(array('<![CDATA[', ']]>'), '', $data));
1915-
1916- // Add CDATA around content which may contain (x)html data, and is not contained in CDATA yet
1917- $src = array(
1918- '%(<(link|content|content:encoded|description|title|summary|info|tagline|copyright|source|itunes:summary|media:text|text)(?>[^<]*(?<!/)>)(?!<!\[CDATA\[))(.*)(</\2>)%sUS',
1919- '%24:(\d\d:\d\d)%' // workaround buggy hour format in feeds
1920- /*'%(<(\w+)(?>[^<]*type=")(?:text/html|application/xhtml\+xml|html|xhtml")(?>[^<]*(?<!/)>)(?!<!\[CDATA\[))(.*)(</\2>)%sUS'*/
1921- );
1922- $dst = array(
1923- '$1<![CDATA[$3]]>$4',
1924- '00:$1'
1925- );
1926- $data = preg_replace($src, $dst, $data);
1927-
1928- // parse the data:
1929- $xml_parser = drupal_xml_parser_create($data);
1930- if ($xml_parser == NULL) {
1931- return $xml_tree;
1932- }
1933-
1934- xml_set_element_handler($xml_parser, 'planet_element_start', 'planet_element_end');
1935- xml_set_character_data_handler($xml_parser, 'planet_element_data');
1936- xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 1);
1937- xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 1);
1938- if (!xml_parse($xml_parser, $data, 1)) {
1939- $xml_tree['parser_error'] = xml_error_string(xml_get_error_code($xml_parser));
1940- $xml_tree['parser_line'] = xml_get_current_line_number($xml_parser);
1941- }
1942- else {
1943- unset($xml_tree['parser_error']);
1944- unset($xml_tree['parser_line']);
1945- }
1946- xml_parser_free($xml_parser);
1947-
1948- $_end = microtime();
1949-
1950- list($sec, $usec) = explode(' ', $_start);
1951- $_start = $sec + $usec;
1952- list($sec, $usec) = explode(' ', $_end);
1953- $xml_tree['parser_time'] = ($sec + $usec) - $_start;
1954-
1955- return $xml_tree;
1956-}
1957-
1958-/**
1959- * Private call-back function used by the XML parser.
1960- */
1961-function planet_element_start($parser, $name, $attributes) {
1962- global $xml_tree, $xml_paths, $xml_path_cur;
1963-
1964- $temp = &$xml_paths[$xml_path_cur++];
1965- $temp[$name][] = $attributes;
1966- $xml_paths[$xml_path_cur] = &$temp[$name][count($temp[$name])-1];
1967-}
1968-
1969-/**
1970- * Private call-back function used by the XML parser.
1971- */
1972-function planet_element_end($parser, $name) {
1973- global $xml_tree, $xml_paths, $xml_path_cur;
1974-
1975- $temp = &$xml_paths[$xml_path_cur];
1976- array_pop($xml_paths);
1977- $xml_path_cur--;
1978- if (isset($temp['VALUE'])) {
1979- $temp['VALUE'] = trim(planet_replace_entities($temp['VALUE']));
1980- }
1981-}
1982-
1983-/**
1984- * Private call-back function used by the XML parser.
1985- */
1986-function planet_element_data($parser, $data) {
1987- global $xml_tree, $xml_paths, $xml_path_cur;
1988-
1989- $temp = trim($data);
1990- if (strlen($temp) > 0) {
1991- $temp = &$xml_paths[$xml_path_cur];
1992- $temp['VALUE'] .= $data;
1993- }
1994-}
1995-
1996+ * Page callback for 'planet' ("Planet").
1997+ * @ingroup page
1998+ * @CRUD{node,R}
1999+ * @CRUD{variables,R}
2000+ */
2001 function planet_page_last() {
2002 global $user;
2003
2004@@ -1351,6 +833,12 @@
2005 print theme('page', $output);
2006 }
2007
2008+/**
2009+ * Page callback for 'planet/feed' ("Planet").
2010+ * @ingroup page
2011+ * @CRUD{node,R}
2012+ * @CRUD{menu_links,R}
2013+ */
2014 function planet_feed() {
2015 $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);
2016 $title = db_fetch_array(db_query("SELECT link_title FROM {menu_links} WHERE link_path = 'planet'"));
2017@@ -1363,12 +851,14 @@
2018 while ($row = db_fetch_object($result)) {
2019 $items[] = $row->nid;
2020 }
2021-
2022+ // generate RSS feed from $items set of nodes.
2023 node_feed($items, $channel);
2024 }
2025
2026 /**
2027- * Implementation of hook_user().
2028+ * Implementation of hook_user() - React when operations are performed on user accounts.
2029+ * @ingroup system
2030+ * @CRUD{planet_feeds,R}
2031 */
2032 function planet_user($type, &$edit, &$user) {
2033 if ($type == 'view' && user_access('edit own blog', $user)) {
2034@@ -1390,9 +880,12 @@
2035
2036 /**
2037 * Menu callback; displays a Drupal page containing recent planet entries.
2038+ *
2039+ * @todo remove orphan function, or reuse it
2040+ * @ingroup page
2041 */
2042 function planet_page($a = NULL, $b = NULL) {
2043-
2044+ // @DIFFINFO I keep this for now to keep showing, in API doc, historic relation between called functions
2045 if (is_numeric($a)) { // $a is a user ID
2046 if ($b == 'feed') {
2047 return planet_feed_user($a);
2048@@ -1409,6 +902,13 @@
2049 }
2050 }
2051
2052+/**
2053+ * Page callback for 'planet/%' ("planet").
2054+ * @ingroup page
2055+ *
2056+ * @CRUD{node,R}
2057+ * @CRUD{variables,R}
2058+ */
2059 function planet_page_user($uid) {
2060 global $user;
2061
2062@@ -1441,15 +941,148 @@
2063 }
2064 }
2065
2066+/**
2067+ * Implementation of hook_load - Load node-type-specific information.
2068+ *
2069+ * @param $node The node being loaded.
2070+ * @return An object containing properties of the node being loaded.
2071+ * @ingroup system
2072+ * @CRUD{planet_items,R}
2073+ */
2074 function planet_load($node) {
2075 $additions = db_fetch_object(db_query('SELECT link, guid FROM {planet_items} WHERE nid = %d', $node->nid));
2076 return $additions;
2077 }
2078
2079-function planet_form(&$node, &$param) {
2080+/**
2081+ * Implementation of hook_form - Display a node editing form.
2082+ *
2083+ * @param &$node The node being added or edited.
2084+ * @param $form_state The form state array (unused).
2085+ * @return An array containing the form elements to be displayed in the node edit form.
2086+ * @ingroup planet_node
2087+ */
2088+function planet_form(&$node, &$form_state) {
2089 $form = array();
2090 $form['title'] = array('#type' => 'textfield', '#title' => 'Title', '#value' => $node->title, '#size' => 30, '#maxlength' => 80);
2091 $form['body'] = array('#type' => 'textarea', '#title' => 'Body', '#value' => $node->body);
2092 return $form;
2093 }
2094
2095+/**
2096+ * Turn each feed item into a node.
2097+ *
2098+ * @param $process_feed
2099+ * Feed node object
2100+ * @param $feed
2101+ * SimplePie feed object instaniated.
2102+ */
2103+function planet_item_feed_parse($process_feed, $feed) {
2104+ // loop through all of the items in the feed, faster than foreach
2105+ $max = $feed->get_item_quantity();
2106+ $count = 0;
2107+ module_load_include('inc', 'node', 'node.pages');
2108+ module_load_include('inc', 'node', 'content_types');
2109+ $node = node_get_types('type', 'feed_item');
2110+
2111+ for ($i = 0; $i < $max; $i++) {
2112+ $item = $feed->get_item($i);
2113+
2114+ // we don't use $item->get_id(true) from SimplePie because it is slightly buggy
2115+ // and requires a lot of overhead to compute each time (since it uses a gigantic array structure)
2116+ // instead we opt for a much lighter weight comparison of just the title and body, eliminating the
2117+ // possibility of any date changes or other tiny changes causing duplicate nodes that otherwise
2118+ // appear to be the same
2119+ // that is why the body and title processing appears out here, so we can check for duplicates
2120+ // it is fast enough to not make much of a difference otherwise
2121+ $body = $item->get_content();
2122+ // this strips out any tags that may appear as <b> in the title, and makes sure &quot; -> " for display
2123+ $title = strip_tags(decode_entities($item->get_title()));
2124+
2125+ // some feeds don't provide titles so we construct one with the first 72 characters of the body
2126+ if (!$title) {
2127+ // remove any HTML or line breaks so these don't appear in the title
2128+ $title = trim(str_replace(array("\n", "\r"), ' ', strip_tags($body)));
2129+ $title = trim(substr($title, 0, 72));
2130+ $lastchar = substr($title, -1, 1);
2131+ // check to see if the last character in the title is a non-alphanumeric character, except for ? or !
2132+ // if it is strip it off so you don't get strange looking titles
2133+ if (preg_match('/[^0-9A-Za-z\!\?]/', $lastchar)) {
2134+ $title = substr($title, 0, -1);
2135+ }
2136+ // ? and ! are ok to end a title with since they make sense
2137+ if ($lastchar != '!' and $lastchar != '?') {
2138+ $title .= '...';
2139+ }
2140+ }
2141+
2142+ // unique id for each feed item, try and use item permalink, otherwise use feed permalink
2143+ if (!$link = $item->get_permalink()) {
2144+ $link = $feed->get_permalink();
2145+ }
2146+ // we don't need serialize() since we already have strings
2147+ $iid = md5($title . $link);
2148+ $guid = md5("$title - . " . $process_feed->fid);
2149+ // make sure we don't already have this feed item
2150+ $duplicate = db_result(db_query("SELECT COUNT(iid) FROM {planet_items} WHERE iid = '%s'", $iid));
2151+
2152+ if (!$duplicate) {
2153+
2154+
2155+ $entry = NULL;
2156+ if ($guid && strlen($guid) > 0) {
2157+ $entry = db_fetch_object(db_query("SELECT nid FROM {planet_items} WHERE guid = '%s' AND fid = %d", $guid, $process_feed->fid));
2158+ }
2159+ else if ($link && $link != $feed->link && $link != $feed->url) {
2160+ $entry = db_fetch_object(db_query("SELECT nid FROM {planet_items} WHERE guid = '%s' AND fid = %d", $link, $process_feed->fid));
2161+ }
2162+ else {
2163+ $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));
2164+ }
2165+
2166+ $link = $item->get_permalink();
2167+ // this is node created date format for Drupal
2168+ $date = $item->get_date('Y-m-d H:i:s O');
2169+
2170+ $entry->changed = $date;
2171+ $entry->title = $title;
2172+ $entry->body = $body;
2173+ $entry->body = planet_convert_relative_urls($body, $link);
2174+ $entry->teaser = node_teaser($entry->body);
2175+ $entry->revision = true;
2176+
2177+ if (!isset($entry->nid)) {
2178+ //print "Planet item " . $entry->title . "<br />";
2179+ $entry->type = 'planet';
2180+
2181+ $options = variable_get('node_options_planet', array());
2182+
2183+ $entry->uid = $process_feed->uid;
2184+ $entry->status = 1;
2185+ $entry->moderate = 0;
2186+ $entry->promote = in_array('promote', $options) ? 1 : 0;
2187+ $entry->sticky = in_array('sticky', $options) ? 1 : 0;
2188+ $entry->comment = in_array('comment', $options) ? 2 : 0;
2189+ $entry->format = variable_get('planet_filter_formats', 1);
2190+ $entry->created = $date;
2191+ $entry->revision = true;
2192+
2193+ }
2194+
2195+ node_save($entry);
2196+ $item_record = array('fid' => $process_feed->fid,
2197+ 'nid' => $entry->nid,
2198+ 'iid' => $iid,
2199+ 'guid'=> $guid,
2200+ 'link' =>$link,
2201+ 'created' => time());
2202+ drupal_write_record('planet_items', $item_record);
2203+ watchdog('planet', 'Adding '. $title);
2204+ drupal_set_message('Adding '. $title);
2205+ }
2206+
2207+ // we unset $item each time to prevent any pass by reference memory leaks that PHP encounters with objects in foreach loops
2208+ unset($item);
2209+ }
2210+
2211+}

Subscribers

People subscribed via source and target branches

to all changes: