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

Diff of /contributions/modules/planet/planet.module

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

revision 1.7 by swe3tdave, Wed Mar 11 23:15:34 2009 UTC revision 1.8 by rockyroad, Mon Jun 8 19:22:28 2009 UTC
# Line 1  Line 1 
1  <?php  <?php
2  // $Id: planet.install Exp $  // $Id: planet.module Exp $
3    
4  /**   /**
5   * @file   * @file
6   * The planet module   * 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() {  function planet_node_info() {
24    return array(    return array(
25      'planet' => array(      'planet' => array(
26      'name' => t('Planet Entry'),        'name' => t('Planet Entry'),
27      'module' => 'planet',        'module' => 'planet',
28      'description' => t('Node to contain posts aggregated from various blogs.'),        'description' => t('Node to contain posts aggregated from various blogs.'),
29      )      )
30    );    );
31  }  }
32    
33  /**  /**
34   * Implementation of hook_perm.   * 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() {  function planet_perm() {
42    return array('administer planet', 'administer own planet feeds');    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) {  function planet_help($path, $arg) {
55    switch ($path) {    switch ($path) {
56      case 'admin/help/planet':      case 'admin/help#planet':
57        $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>';        // 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>';        $output .= '<p>To use planet, go to admin/settings/planet and note the following sections:</p>';
60        $output .= '<ul>';        $output .= '<ul>';
61        $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>';        $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>';        $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>';        $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;        return $output;
67      case 'admin/modules#description':        // @DIFFINFO in D6, admin/modules#description is moved to .info file
       return t('Aggregates RSS feeds and faciliates their association with site users who belong to a given role.');  
68    }    }
69  }  }
70    
71  function planet_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {  /**
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) {    if ($page === true && variable_get('planet_redirect_page', 0) == 1) {
86      $obj = db_fetch_object(db_query('SELECT * FROM {planet_items} WHERE nid = %d', $node->nid));      // @DIFFINFO only 'link' field is used, so I restricted the query
87      if ($obj->nid == $node->nid && $obj->link != '') {      $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);        header('Location: '. $obj->link);
91        exit;        exit;
92      }      }
# Line 52  function planet_view($node, $teaser = FA Line 96  function planet_view($node, $teaser = FA
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) {  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') {    if ($op == 'create') {
116      return user_access('edit own blog', $account) && $account->uid;      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') {    if ($op == 'update' || $op == 'delete') {
124      if (user_access('edit own blog', $account) && ($account->uid == $node->uid) || user_access('administer nodes', $account)) {      return $author && $own;    // ok if $author owns the node
       return TRUE;  
     }  
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() {  function planet_menu() {
   
144    $items['admin/settings/planet'] = array(    $items['admin/settings/planet'] = array(
145      'title' => 'Planet Settings',      'title' => 'Planet Settings',
146      'description' => 'Configure settings for the planet module.',      'description' => 'Configure settings for the planet module.',
# Line 84  function planet_menu() { Line 157  function planet_menu() {
157    );    );
158    
159    //  if (arg(0) == 'planet' && is_numeric(arg(1))) {    //  if (arg(0) == 'planet' && is_numeric(arg(1))) {
160      // FIXME access denied http://localhost/drupal/?q=planet/1
161    $items['planet/'. '%'] = array(    $items['planet/'. '%'] = array(
162      'title' => 'planet',      'title' => 'planet',
163      'page callback' => 'planet_page_user',      'page callback' => 'planet_page_user',
164      'page arguments' => array(arg(1))      'page arguments' => array(arg(1))
165    );    );
 //  }  
166    
167  //   if (arg(3) == 'refresh' && is_numeric(arg(4))) {    //   if (arg(3) == 'refresh' && is_numeric(arg(4))) {
   
168    $items['admin/settings/planet/refresh/%'] = array(    $items['admin/settings/planet/refresh/%'] = array(
169      'title' => 'planet refresh',      'title' => 'planet refresh',
170      'page callback' => 'planet_call_refresh',      'page callback' => 'planet_call_refresh',
171      'access arguments' => array('administer nodes'),      'access arguments' => array('administer nodes'),
172      'type' => MENU_CALLBACK      'type' => MENU_CALLBACK
173    );    );
 //   }  
174    
175  //  if (is_numeric(arg(4)) && (arg(3) == 'freeze' || arg(3) == 'unfreeze')) {    //  if (is_numeric(arg(4)) && (arg(3) == 'freeze' || arg(3) == 'unfreeze')) {
176    $items['admin/settings/planet/'. arg(3) .'/%'] = array(    $items['admin/settings/planet/'. arg(3) .'/%'] = array(
177      'title' => 'planet freeze',      'title' => 'planet freeze',
178      'page callback' => 'planet_toggle_frozen',      'page callback' => 'planet_toggle_frozen',
179      'access arguments' => array('administer nodes'),      'access arguments' => array('administer nodes'),
180      'type' => MENU_CALLBACK      'type' => MENU_CALLBACK
181    );    );
 //   }  
182    
183    $items['planet'] = array(    $items['planet'] = array(
184      'title' => 'Planet',      'title' => 'Planet',
# Line 128  function planet_menu() { Line 198  function planet_menu() {
198    return $items;    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() {  function planet_call_refresh() {
208    $title = planet_refresh();    $title = planet_refresh();
209    watchdog('planet', 'Feed "'. $title .'" refreshed.');    watchdog('planet', 'Feed "'. $title .'" refreshed.');
# Line 135  function planet_call_refresh() { Line 211  function planet_call_refresh() {
211    drupal_goto('admin/settings/planet');    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() {  function planet_toggle_frozen() {
221    
222    $fid = intval(arg(4));    $fid = intval(arg(4));
# Line 144  function planet_toggle_frozen() { Line 226  function planet_toggle_frozen() {
226  }  }
227    
228    
229  function planet_user_feeds() {  /**
230  global $user;   * Deletes a feed and related items
231  if ($_POST) {   * @param $fid the feed identifier key (integer)
232      $edit = $_POST;   * @param $path the drupal path for redirection after user confirmed.
233      if ($_POST['op'] == 'Delete' && intval($edit['fid']) > 0) {   *
234        $result = db_query('SELECT nid FROM {planet_items} WHERE fid = %d', intval($edit['fid']));   * @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)) {        while ($node = db_fetch_object($result)) {
245          $nodes[$node->nid] = TRUE;          $nodes[$node->nid] = TRUE;
246        }        }
247        return drupal_get_form('planet_multiple_delete_confirm', $nodes, intval($edit['fid']), 'user/'. $user->uid .'/planet');        return drupal_get_form('planet_multiple_delete_confirm', $nodes,
248      }                               $fid, $path);
249      else if ($_POST['op'] == 'Delete all' && $_POST['confirm'] == 1) {  }
250        $edit['fid'] = intval(arg(3));  
251        $edit['redirect'] = 'user/'. $user->uid .'/planet';  /**
252        return drupal_get_form('planet_multiple_delete_confirm_submit', $edit);   * Page callback for 'user/%user/planet' ("Planet Feeds")
253      }   *
254      else {   * @ingroup page
255        if (isset($edit['fid']) && intval($edit['fid']) == 0) {   * @CRUD{planet_feeds,R}
256          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']);   * @invoke{drupal_get_form,planet_multiple_delete_confirm_submit}
257          $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']));   * @invoke{drupal_get_form,planet_feed_form}
258          $title = planet_refresh(intval($edit_r['fid']));   */
259          drupal_set_message('Added new feed: ' . $title);  function planet_user_feeds() {
260        }    global $user;
261        else if ($edit['fid'] && intval($edit['fid']) > 0) {    if ($_POST) {
262          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']);      return planet__update($_POST, 'user/'. $user->uid .'/planet');
         drupal_set_message('Edited "'. $edit['title'] .'" feed.');  
       }  
       else {  
         if ($edit['planet_author_roles']) {  
           variable_set('planet_author_roles', $edit['planet_author_roles']);  
         }  
         if ($edit['planet_filter_formats']) {  
           variable_set('planet_filter_formats', $edit['planet_filter_formats']);  
         }  
         if ($edit['planet_redirect_page'] == 1) {  
           variable_set('planet_redirect_page', $edit['planet_redirect_page']);  
         }  
         else {  
           variable_del('planet_redirect_page');  
         }  
         drupal_set_message('Edited general planet settings.');  
       }  
     }  
     drupal_goto('user/'. $user->uid .'/planet');  
263    }    }
264    else {    else { // no $_POST
265      $fid = intval(arg(3));      $fid = intval(arg(3));      // @deprecated: arg()
266      if ($fid > 0) {      if ($fid > 0) {
267        $edit = db_fetch_array(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));        $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);        $output .= drupal_get_form('planet_feed_form', $edit, true, $user);
# Line 199  if ($_POST) { Line 271  if ($_POST) {
271    
272        $output .= drupal_get_form('planet_feed_form', $edit, false, $user);        $output .= drupal_get_form('planet_feed_form', $edit, false, $user);
273    
274       // $result = db_query('SELECT *, (UNIX_TIMESTAMP(NOW()) - checked) _checked FROM {planet_feeds}');        $output .= '<h2>Feeds</h2>';
275        $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);        $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();        $rows = array();
297        $headers = array('Feed', 'Items', 'Edit', 'Last checked');        $headers = array('Feed', 'Items', 'Edit', 'Last checked');
298        while ($feed = db_fetch_object($result)) {        $now = time();
299          $checked = intval($feed->_checked / 60) .' minutes';        $items_statement = 'SELECT count(*) FROM {planet_items}'
300          if ($feed->_checked % 60 > 0) {          . ' 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';            $checked .= ', '. $feed->_checked % 60 .' seconds';
307          }          }
308          $checked .= ' ago';          $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(          array_push($rows, array(
313            $feed->title,            $feed->title,
314            $feed->cnt,            $cnt,
315            l('edit', 'user/'. $user->uid .'/planet/'. intval($feed->fid)),            l('edit', 'user/'. $user->uid .'/planet/'. intval($feed->fid)),
316            $checked,            $checked,
317            )            )
318          );          );
319        }        }
320        $output .= '<h2>Feeds</h2>';        return theme('table', $headers, $rows);
       $output .= theme('table', $headers, $rows);  
     }  
     print theme('page', $output);  
   }  
321  }  }
322    
323    /**
324  function _planet_settings() {   * Updates a feed
325    if ($_POST) {   */
326      $edit = $_POST;  function planet__update($edit, $path) {
327        // @DIFFINFO Username can change, so we need to store the ID, not the username.
328      if ($_POST['op'] == 'Delete' && intval($edit['fid']) > 0) {      $edit['uid'] = db_result(db_query("SELECT uid from {users} WHERE name = '%s'", $edit['username']));
329        $result = db_query('SELECT nid FROM {planet_items} WHERE fid = %d', intval($edit['fid']));      unset($edit['username']);
330        while ($node = db_fetch_object($result)) {      $fid = intval($edit['fid']);
331          $nodes[$node->nid] = TRUE;      if (($edit['op'] == 'Delete') && ($fid != 0)) {
332        }        return planet__drop_feed($fid, $path);
333        return drupal_get_form('planet_multiple_delete_confirm', $nodes, intval($edit['fid']), 'admin/settings/planet');      }
334      }      else if ($edit['op'] == 'Delete all' && $edit['confirm'] == 1) {
335      else if ($_POST['op'] == 'Delete all' && $_POST['confirm'] == 1) {        $edit['fid'] = intval(arg(3)); // @deprecated arg()
336        $edit['fid'] = intval(arg(3));        $edit['redirect'] = $path;
       $edit['redirect'] = 'admin/settings/planet';  
337        return drupal_get_form('planet_multiple_delete_confirm_submit', $edit);        return drupal_get_form('planet_multiple_delete_confirm_submit', $edit);
338      }      }
339      else {      else {
340        if (isset($edit['fid']) && intval($edit['fid']) == 0) {        planet__submit_feed_data($edit);
         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']);  
         $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']));  
         $title = planet_refresh(intval($edit_r['fid']));  
         drupal_set_message('Added new feed: ' . $title);  
       }  
       else if ($edit['fid'] && intval($edit['fid']) > 0) {  
         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']);  
         drupal_set_message('Edited "'. $edit['title'] .'" feed.');  
       }  
       else {  
         if ($edit['planet_author_roles']) {  
           variable_set('planet_author_roles', $edit['planet_author_roles']);  
         }  
         if ($edit['planet_filter_formats']) {  
           variable_set('planet_filter_formats', $edit['planet_filter_formats']);  
         }  
         if ($edit['planet_redirect_page'] == 1) {  
           variable_set('planet_redirect_page', $edit['planet_redirect_page']);  
         }  
         else {  
           variable_del('planet_redirect_page');  
         }  
         drupal_set_message('Edited general planet settings.');  
       }  
341      }      }
342      drupal_goto('admin/settings/planet');      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    else {    drupal_set_message('Edited general planet settings.');
433      $fid = intval(arg(3));  }
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) {      if ($fid > 0) {
452        $edit = db_fetch_array(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));        $edit = db_fetch_array(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));
453        $output .= drupal_get_form('planet_feed_form', $edit, true);        $output .= drupal_get_form('planet_feed_form', $edit, true);
# Line 279  function _planet_settings() { Line 455  function _planet_settings() {
455      else {      else {
456    
457        $output .= drupal_get_form('planet_settings_form');        $output .= drupal_get_form('planet_settings_form');
       //$output .= drupal_get_form('settings', $form);  
       //$output .= $form;  
458    
459        $output .= drupal_get_form('planet_feed_form', $edit);        $output .= drupal_get_form('planet_feed_form', $edit);
460    
      // $result = db_query('SELECT *, (UNIX_TIMESTAMP(NOW()) - checked) _checked FROM {planet_feeds}');  
       $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;');  
       $rows = array();  
       $headers = array('Feed', 'Items', 'Edit', 'Last checked', 'Refresh', 'Freeze');  
       while ($feed = db_fetch_object($result)) {  
         $checked = intval($feed->_checked / 60) .' minutes';  
         if ($feed->_checked % 60 > 0) {  
           $checked .= ', '. $feed->_checked % 60 .' seconds';  
         }  
         $checked .= ' ago';  
         array_push($rows, array(  
           $feed->title,  
           $feed->cnt,  
           l('edit', 'admin/settings/planet/'. intval($feed->fid)),  
           $checked,  
           l('refresh', 'admin/settings/planet/refresh/'. intval($feed->fid)),  
           l($feed->frozen ? 'unfreeze' : 'freeze', 'admin/settings/planet/'. ($feed->frozen ? 'unfreeze/' : 'freeze/') . intval($feed->fid))  
           )  
         );  
       }  
461        $output .= '<h2>Feeds</h2>';        $output .= '<h2>Feeds</h2>';
462        $output .= theme('table', $headers, $rows);        $output .= planet__build_admin_feeds_table();
463      }      }
464      print theme('page', $output);      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) {  function planet_multiple_delete_confirm(&$form_state, $nodes, $fid, $redirect) {
524    $form_state['values']['fid'] = $fid;    $form_state['values']['fid'] = $fid;
525    $form_state['values']['redirect'] = $redirect;    $form_state['values']['redirect'] = $redirect;
# Line 333  function planet_multiple_delete_confirm( Line 542  function planet_multiple_delete_confirm(
542                        t('Delete all'), t('Cancel'));                        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) {  function planet_multiple_delete_confirm_submit(&$form_state, $edit) {
558    $fid = $edit['fid'];    $fid = $edit['fid'];
559    if ($edit['confirm']) {    if ($edit['confirm']) {
560      foreach ($edit['nodes'] as $nid => $value) {      foreach ($edit['nodes'] as $nid => $value) {
561        node_delete($nid);        node_delete($nid);
562      }      }
563      db_query('DELETE FROM {planet_feeds} WHERE fid = %d', $fid);      // @DIFFINFO inverted drop order to respect foreign keys
564      db_query('DELETE FROM {planet_items} WHERE fid = %d', $fid);      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.'));      drupal_set_message(t('The feed and items have been deleted.'));
567    }    }
568    drupal_goto($edit['redirect']);    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) {  function planet_settings_form(&$form_state) {
   $roles = array();  
   
   $result = db_query('SELECT rid, name FROM {role}');  
   while ($role = db_fetch_object($result)) {  
     $roles[$role->rid] = $role->name;  
   }  
   
   unset($result);  
582    $result = db_query('SELECT format, name FROM {filter_formats}');    $result = db_query('SELECT format, name FROM {filter_formats}');
583    while ($format = db_fetch_object($result)) {    while ($format = db_fetch_object($result)) {
584      $formats[$format->format] = $format->name;      $formats[$format->format] = $format->name;
# Line 369  function planet_settings_form(&$form_sta Line 591  function planet_settings_form(&$form_sta
591      '#title' => t('General Settings')      '#title' => t('General Settings')
592    );    );
593    
   $form['general']['planet_author_roles'] = array(  
     '#type' => 'select',  
     '#title' => t('Role to select authors from'),  
     '#options' => $roles,  
     '#default_value' => variable_get('planet_author_roles', 2),  
     '#description' => t('Select the role from which blog authors should be selected on the feed creation screen.')  
   );  
   
594    $form['general']['planet_filter_formats'] = array(    $form['general']['planet_filter_formats'] = array(
595      '#type' => 'select',      '#type' => 'select',
596      '#title' => t('Filter format for planet entry nodes'),      '#title' => t('Filter format for planet entry nodes'),
# Line 401  function planet_settings_form(&$form_sta Line 615  function planet_settings_form(&$form_sta
615    return $form;    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) {  function planet_feed_form(&$form_state, $edit = array(), $individual = false, $user = NULL) {
   $uids = array();  
   $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));  
   while ($f_user = db_fetch_object($result)) {  
     $uids[$f_user->uid] = $f_user->name;  
   }  
633    
634    if ($individual) {    if ($individual) {
635      if (!isset($uids[$edit['uid']])) {    $g_user = db_fetch_array(db_query('SELECT uid, name FROM {users} WHERE uid = %d', $edit['uid']));
       $g_user = db_fetch_array(db_query('SELECT uid, name FROM {users} WHERE uid = %d', $edit['uid']));  
       $uids[$edit['uid']] = $g_user['name'];  
     }  
636    }    }
637    
638    $form = array();    $form = array();
# Line 437  function planet_feed_form(&$form_state, Line 656  function planet_feed_form(&$form_state,
656    );    );
657    
658    if ($user == NULL) {    if ($user == NULL) {
659      $form['feeds']['uid'] = array(      $form['feeds']['username'] = array(
660        '#type' => 'select',        '#type' => 'textfield',
661        '#title' => t('Original author'),        '#title' => t('Original author'),
662        '#value' => $edit['uid'],        '#size' => 40,
663        '#options' => $uids,        '#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')        '#description' => t('Select a user to associate this feed with')
668      );      );
669    }    }
670    else {    else {
671      $form['feeds']['uid'] = array(      $form['feeds']['username'] = array(
672        '#type' => 'hidden',        '#type' => 'hidden',
673        '#value' => $user->uid,        '#value' => $user->name,
674      );      );
675    }    }
676    $form['feeds']['link'] = array(    $form['feeds']['link'] = array(
# Line 474  function planet_feed_form(&$form_state, Line 696  function planet_feed_form(&$form_state,
696    return $form;    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() {  function planet_cron() {
707    $result = db_query('SELECT fid FROM {planet_feeds} WHERE frozen = 0');    $result = db_query('SELECT fid FROM {planet_feeds} WHERE frozen = 0');
708    while ($feed = db_fetch_object($result)) {    while ($feed = db_fetch_object($result)) {
# Line 482  function planet_cron() { Line 711  function planet_cron() {
711    }    }
712  }  }
713    
714  function planet_refresh($fid = null) {  
   if (!$fid) {  
     $fid = intval(arg(4));  
   }  
   
   $feed = db_fetch_object(db_query('SELECT * FROM {planet_feeds} WHERE fid = %d', $fid));  
   
   $headers = array();  
   $result = planet_http_request($feed->link, $headers, 15);  
   
   switch ($result->code) {  
     case 304:  
       drupal_set_message(t('No new content syndicated from %site.', array('%site' => '<em>'. $feed->title .'</em>')));  
       break;  
   
     case 301:  
       if ($result->redirect_url) {  
         $feed->link = $result->redirect_url;  
         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));  
         db_query("UPDATE {planet_feeds} SET link = '%s' WHERE fid = %d", $feed->link, $feed->fid);  
       }  
       break;  
   
     case 200:  
     case 302:  
     case 307:  
       $xml_tree = planet_parse_xml($result->data);  
   
       if ($xml_tree['parser_error']) {  
         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);  
         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');  
         break;  
       }  
       else {  
         drupal_set_message('Parsing feed '. $feed->title .' took '. $xml_tree['parser_time'] .' seconds.');  
       }  
   
       if (planet_parse_items($xml_tree, $feed) !== false) {  
         if ($result->headers['Last-Modified']) {  
           $modified = strtotime($result->headers['Last-Modified']);  
         }  
   
         /*  
         ** Prepare data:  
         */  
         if ($xml_tree['RSS']) { // RSS 0.91, 0.92, 2.0  
           $root = &$xml_tree['RSS'][0];  
           $channel = &$root['CHANNEL'][0];  
           $image = &$channel['IMAGE'][0];  
           $description = &$channel['DESCRIPTION'][0]['VALUE'];  
           $link = &$channel['LINK'][0]['VALUE'];  
         }  
         else if ($xml_tree['RDF:RDF']) {  
           $root = &$xml_tree['RDF:RDF'][0];  
           $channel = &$root['CHANNEL'][0];  
           $image = &$root['IMAGE'][0];  
           $description = &$channel['DESCRIPTION'][0]['VALUE'];  
           $link = &$channel['LINK'][0]['VALUE'];  
         }  
         else if ($xml_tree['FEED']) { // Atom 0.3, 1.0  
           $root = &$xml_tree['FEED'][0];  
           $channel = &$root;  
           $image = &$channel['LOGO'][0]['VALUE'];  
           $description = ($channel['TAGLINE'][0]['VALUE'] ? $channel['TAGLINE'][0]['VALUE'] : '');  
           // TODO: remove this Atom hack when we have field mapping or at least specialized parsers in place  
           if (count($channel['LINK']) > 1) {  
             $link = $feed->link;  
             foreach ($channel['LINK'] as $l) {  
               if ($l['REL'] == 'alternate') {  
                 $link = $l['HREF'];  
               }  
             }  
           }  
           else {  
             $link = $channel['LINK'][0]['HREF'];  
           }  
         }  
         else if ($xml_tree['CHANNEL']) { // RSS 1.1  
           $root = &$xml_tree['CHANNEL'][0];  
           $channel = &$root;  
           $image = &$channel['IMAGE'][0];  
           $description = &$channel['DESCRIPTION'][0]['VALUE'];  
           $link = &$channel['LINK'][0]['VALUE'];  
         }  
         else if ($xml_tree['OPML']) {  
           $root = &$xml_tree['OPML'][0];  
           $channel = &$root;  
           $image = NULL;  
           $description = NULL;  
           $link = NULL;  
         }  
         else {  
           // unsupported format  
           break;  
         }  
   
         if (!$feed->uid) {  
           if ($channel['AUTHOR'][0]['VALUE']) {  
             $feed->uid = $channel['AUTHOR'][0]['VALUE'];  
           }  
           if ($channel['AUTHOR'][0]['NAME'][0]['VALUE']) {  
             $feed->uid = $channel['AUTHOR'][0]['NAME'][0]['VALUE'];  
           }  
           else if ($channel['DC:CREATOR']) {  
             $feed->uid = $channel['DC:CREATOR'][0]['VALUE'];  
           }  
           else {  
             $feed->uid = '';  
           }  
         }  
   
         /*  
         ** Generate image link  
         */  
         if (!$feed->image && $image['LINK'] && $image['URL'] && $image['TITLE']) {  
           if (strlen($image['TITLE'][0]['VALUE']) > 250) {  
             $image['TITLE'][0]['VALUE'] = trim(substr($image['TITLE'][0]['VALUE'], 0, 250)).'...';  
           }  
           $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>';  
         }  
   
         /*  
         ** Update the feed data:  
         */  
         $feed->checked = time();  
         $feed->link = $link;  
         $feed->etag = $result->headers['ETag'];  
         $feed->modified = $modified;  
         if ($feed->body == '' && $description/* && valid_input_data($description)*/) {  
           $feed->body = $feed->teaser = $description;  
         }  
         $feed->rss_data = &$xml_tree;  
   
         /*  
         ** Taxonomy module doesn't add taxonomy terms at load time... so we have to do it by hand :((  
         */  
         $terms = module_invoke('taxonomy', 'node_get_terms', $feed->nid, 'tid');  
         $feed->taxonomy = array();  
         foreach ($terms as $tid => $term) {  
           if ($term->tid) {  
             $feed->taxonomy[] = $term->tid;  
           }  
         }  
       }  
     default:  
   }  
   
   
   db_query('UPDATE {planet_feeds} SET checked = %d WHERE fid = %d', time(), $fid);  
   return $feed->title;  
   //print theme('page', 'refreshing '. $fid .'.');// and got '. print_r($feed, 1));  
 }  
   
 /**  
  * Private function; Parse HTTP headers from data retreived with cURL  
  * from: http://pl2.php.net/manual/en/function.curl-setopt.php#42009  
  */  
 function planet_parse_response($response) {  
   /*  
    ***original code extracted from examples at  
    ***http://www.webreference.com/programming  
                            /php/cookbook/chap11/1/3.html  
   
    ***returns an array in the following format which varies depending on headers returned  
   
        [0] => the HTTP error or response code such as 404  
        [1] => Array  
        (  
            [Server] => Microsoft-IIS/5.0  
            [Date] => Wed, 28 Apr 2004 23:29:20 GMT  
            [X-Powered-By] => ASP.NET  
            [Connection] => close  
            [Set-Cookie] => COOKIESTUFF  
            [Expires] => Thu, 01 Dec 1994 16:00:00 GMT  
            [Content-Type] => text/html  
            [Content-Length] => 4040  
        )  
        [2] => Response body (string)  
   */  
   
   do {  
     list($response_headers, $response) = explode("\r\n\r\n", $response, 2);  
     $response_header_lines = explode("\r\n", $response_headers);  
   
     // first line of headers is the HTTP response code  
     $http_response_line = array_shift($response_header_lines);  
     if (preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@', $http_response_line, $matches)) {  
       $response_code = $matches[1];  
     }  
     else {  
       $response_code = "Error";  
     }  
   }  
   while (substr($response_code, 0, 1) == "1");  
   
   $response_body = $response;  
   
   // put the rest of the headers in an array  
   $response_header_array = array();  
   foreach ($response_header_lines as $header_line) {  
     list($header, $value) = explode(':', $header_line, 2);  
     $response_header_array[$header] = trim($value);  
   }  
   
   return array($response_code, $response_header_array, $response_body, $http_response_line);  
 }  
   
 /**  
  * Private function; Gets data from given URL :)  
  */  
 function planet_http_request($url, $headers = array(), $timeout = 15, $method = 'GET', $data = NULL, $follow = 3) {  
   if (!function_exists('curl_init')) {  
     return drupal_http_request($url, $headers, $method, $data, $follow);  
   }  
   
   // convert headers array to format used by cURL  
   $temp = array();  
   foreach ($headers as $header => $value) {  
     $temp[] = $header .': '. $value;  
   }  
   $headers = $temp;  
   
   $result = new StdClass();  
   
   $ch = curl_init();  
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
   curl_setopt($ch, CURLOPT_URL, $url);  
   curl_setopt($ch, CURLOPT_HEADER, 1);  
   curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);  
   curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);  
   curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);  
   
   $data = curl_exec($ch);  
   $info = curl_getinfo($ch);  
   
   curl_close($ch);  
   unset($ch);  
   
   $response = planet_parse_response($data);  
   $result->code = $response[0];  
   $result->headers = $response[1];  
   $result->data = $response[2];  
   $error = $response[3];  
   switch ($code) {  
     case 200: // OK  
     case 304: // Not modified  
       break;  
     case 301: // Moved permanently  
     case 302: // Moved temporarily  
     case 307: // Moved temporarily  
       $location = $result->headers['Location'];  
   
       if ($follow) {  
         $result = planet_http_request($result->headers['Location'], $headers, $timeout, $method, $data, --$follow);  
         $result->redirect_code = $result->code;  
       }  
       $result->redirect_url = $location;  
       break;  
     default:  
       $result->error = $error;  
       break;  
   }  
   
   $result->code = $response[0];  
   return $result;  
 }  
   
715  /**  /**
716   * Private function; Checks a news feed for new items.   * 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));
  * Private function;  
  * Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing  
  * functions do not handle this format.  
  * See http://www.w3.org/TR/NOTE-datetime for more information.  
  * Origionally from MagpieRSS (http://magpierss.sourceforge.net/).  
  *  
  * @param $date_str A string with a potentially W3C DTF date.  
  * @return A timestamp if parsed successfully or -1 if not.  
  */  
 function planet_parse_w3cdtf($date_str) {  
   if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) {  
     list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);  
     // calc epoch for current date assuming GMT  
     $epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);  
     if ($match[10] != 'Z') { // Z is zulu time, aka GMT  
       list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);  
       // zero out the variables  
       if (!$tz_hour) {  
         $tz_hour = 0;  
       }  
       if (!$tz_min) {  
         $tz_min = 0;  
       }  
       $offset_secs = (($tz_hour * 60) + $tz_min) * 60;  
       // is timezone ahead of GMT?  then subtract offset  
       if ($tz_mod == '+') {  
         $offset_secs *= -1;  
       }  
       $epoch += $offset_secs;  
     }  
     return $epoch;  
   }  
   else {  
     return -1;  
   }  
 }  
   
 /**  
  * Private function;  
  * from: http://pl2.php.net/manual/en/function.html-entity-decode.php#51055  
  * Used as callback function for preg_replace_all() to decode numeric entities to UTF-8 chars  
  *  
  * @param $ord Number  
  * @return UTF-8 string  
  */  
 function planet_replace_num_entity($ord) {  
   $ord = $ord[1];  
   if (preg_match('/^x([0-9a-f]+)$/i', $ord, $match)) {  
     $ord = hexdec($match[1]);  
   }  
   else {  
     $ord = intval($ord);  
   }  
   
   $no_bytes = 0;  
   $byte = array();  
   
   if ($ord == 128) {  
     return chr(226) . chr(130) . chr(172);  
   }  
   else if ($ord == 129) {  
     return chr(239) . chr(191) . chr(189);  
   }  
   else if ($ord == 130) {  
     return chr(226) . chr(128) . chr(154);  
   }  
   else if ($ord == 131) {  
     return chr(198) . chr(146);  
   }  
   else if ($ord == 132) {  
     return chr(226) . chr(128) . chr(158);  
   }  
   else if ($ord == 133) {  
     return chr(226) . chr(128) . chr(166);  
   }  
   else if ($ord == 134) {  
     return chr(226) . chr(128) . chr(160);  
   }  
   else if ($ord == 135) {  
     return chr(226) . chr(128) . chr(161);  
   }  
   else if ($ord == 136) {  
     return chr(203) . chr(134);  
   }  
   else if ($ord == 137) {  
     return chr(226) . chr(128) . chr(176);  
   }  
   else if ($ord == 138) {  
     return chr(197) . chr(160);  
   }  
   else if ($ord == 139) {  
     return chr(226) . chr(128) . chr(185);  
   }  
   else if ($ord == 140) {  
     return chr(197) . chr(146);  
   }  
   else if ($ord == 141) {  
     return chr(239) . chr(191) . chr(189);  
   }  
   else if ($ord == 142) {  
     return chr(197) . chr(189);  
   }  
   else if ($ord == 143) {  
     return chr(239) . chr(191) . chr(189);  
   }  
   else if ($ord == 144) {  
     return chr(239) . chr(191) . chr(189);  
   }  
   else if ($ord == 145) {  
     return chr(226) . chr(128) . chr(152);  
   }  
   else if ($ord == 146) {  
     return chr(226) . chr(128) . chr(153);  
   }  
   else if ($ord == 147) {  
     return chr(226) . chr(128) . chr(156);  
   }  
   else if ($ord == 148) {  
     return chr(226) . chr(128) . chr(157);  
   }  
   else if ($ord == 149) {  
     return chr(226) . chr(128) . chr(162);  
   }  
   else if ($ord == 150) {  
     return chr(226) . chr(128) . chr(147);  
   }  
   else if ($ord == 151) {  
     return chr(226) . chr(128) . chr(148);  
   }  
   else if ($ord == 152) {  
     return chr(203) . chr(156);  
   }  
   else if ($ord == 153) {  
     return chr(226) . chr(132) . chr(162);  
   }  
   else if ($ord == 154) {  
     return chr(197) . chr(161);  
   }  
   else if ($ord == 155) {  
     return chr(226) . chr(128) . chr(186);  
   }  
   else if ($ord == 156) {  
     return chr(197) . chr(147);  
   }  
   else if ($ord == 157) {  
     return chr(239) . chr(191) . chr(189);  
   }  
   else if ($ord == 158) {  
     return chr(197) . chr(190);  
   }  
   else if ($ord == 159) {  
     return chr(197) . chr(184);  
   }  
   else if ($ord == 160) {  
     return chr(194) . chr(160);  
   }  
   
   if ($ord < 128) {  
     return chr($ord);  
   }  
   else if ($ord < 2048) {  
     $no_bytes = 2;  
   }  
   else if ($ord < 65536) {  
     $no_bytes = 3;  
728    }    }
729    else if ($ord < 1114112) {    // initialize simplepie
730      $no_bytes = 4;    // 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 {    else {
772      return;      watchdog('planet', 'You shouldn\'t be here. Something has gone terribly wrong.');
   }  
   
   switch ($no_bytes) {  
     case 2:  
       $prefix = array(31, 192);  
       break;  
   
     case 3:  
       $prefix = array(15, 224);  
       break;  
   
     case 4:  
       $prefix = array(7, 240);  
       break;  
   }  
   
   for ($i = 0; $i < $no_bytes; $i++) {  
     $byte[$no_bytes - $i - 1] = (($ord & (63 * pow(2, 6 * $i))) / pow(2, 6 * $i)) & 63 | 128;  
   }  
   
   $byte[0] = ($byte[0] & $prefix[0]) | $prefix[1];  
   
   $ret = '';  
   for ($i = 0; $i < $no_bytes; $i++) {  
     $ret .= chr($byte[$i]);  
   }  
   
   return $ret;  
 }  
   
 /**  
  * Private function; Convert named entities to UTF-8 characters  
  * from: http://pl2.php.net/manual/en/function.html-entity-decode.php#51722  
  */  
 function planet_replace_name_entities(&$text) {  
   static $ttr;  
   if (!$ttr) {  
     $trans_tbl = get_html_translation_table(HTML_ENTITIES);  
     foreach ($trans_tbl as $k => $v) {  
       $ttr[$v] = utf8_encode($k);  
     }  
     $ttr['&apos;'] = "'";  
773    }    }
774    return strtr($text, $ttr);  
775  }    return $process_feed->title;
   
 /**  
  * Private function; Convert all entities to UTF-8 characters  
  */  
 function planet_replace_entities(&$text) {  
   $result = planet_replace_name_entities($text);  
   return preg_replace_callback('/&#([0-9a-fx]+);/mi', 'planet_replace_num_entity', $result);  
776  }  }
777    
778  /**  // @DIFFINFO removed orphaned doc-block
  * Private function; Clone object function to stay compatible with both php4 and php5  
  * from: Drupal 4.7CVS  
  * TODO: remove after moving to Drupal 4.7  
  */  
 function planet_clone($object) {  
   return version_compare(phpversion(), '5.0') < 0 ? $object : clone($object);  
 }  
779    
780  /**  /**
781   * Private function; Convert relative URLs   * Private function; Convert relative URLs
782     * @ingroup rss
783     * @private
784   */   */
785  function planet_convert_relative_urls(&$data, $base_url) {  function planet_convert_relative_urls(&$data, $base_url) {
786    $src = '%( href| src)="(?!\w+://)/?([^"]*)"%';    $src = '%( href| src)="(?!\w+://)/?([^"]*)"%';
# Line 999  function planet_convert_relative_urls(&$ Line 789  function planet_convert_relative_urls(&$
789  }  }
790    
791  /**  /**
792   * Private function; Creates nodes from data found in given xml_tree   * Page callback for 'planet' ("Planet").
793     * @ingroup page
794     * @CRUD{node,R}
795     * @CRUD{variables,R}
796   */   */
 function planet_parse_items(&$xml_tree, &$feed) {  
   
   if ($xml_tree['RSS']) { // RSS 0.91, 0.92, 2.0  
     $items = &$xml_tree['RSS'][0]['CHANNEL'][0]['ITEM'];  
     $link_field = 'VALUE';  
   }  
   else if ($xml_tree['RDF:RDF']) {  
     $items = &$xml_tree['RDF:RDF'][0]['ITEM'];  
     $link_field = 'VALUE';  
   }  
   else if ($xml_tree['FEED']) { // Atom 0.3, 1.0  
     $items = &$xml_tree['FEED'][0]['ENTRY'];  
     $link_field = 'HREF';  
   }  
   else if ($xml_tree['CHANNEL']) { // RSS 1.1  
     $items = &$xml_tree['CHANNEL'][0]['ITEMS'][0]['ITEM'];  
     $link_field = 'VALUE';  
   }  
   else {  
     // unsupported format  
     $items = array();  
     return false;  
   }  
   
   /*  
   ** We reverse the array such that we store the first item last,  
   ** and the last item first. In the database, the newest item  
   ** should be at the top.  
   */  
   $items_added = 0;  
   
   
   for ($index = count($items) - 1; $index >= 0; $index--) {  
     $item = &$items[$index];  
   //print '<pre>'. print_r($item, 1) .'</pre>';  
     $teaser = NULL;  
     $body = NULL;  
   
     // Description field is needed early for case when no title is specified  
     if ($item['DESCRIPTION']) { // RSS 0.91, 0.92, 1.0, 1.1, 2.0  
       $body = &$item['DESCRIPTION'][0]['VALUE'];  
     }  
     else if ($item['SUMMARY']) { // Atom 0.3, 1.0  
       $body = &$item['SUMMARY'][0]['VALUE'];  
     }  
   
     if ($item['CONTENT']) { // Atom 0.3, 1.0  
       if (strlen($body) < strlen($item['CONTENT'][0]['VALUE'])) {  
         if ($body) {  
           $teaser = $body;  
         }  
         $body = &$item['CONTENT'][0]['VALUE'];  
       }  
     }  
     else if ($item['CONTENT:ENCODED']) { // Don't know where it came from but it can be found in RSS 2.0 feeds  
       if (strlen($body) < strlen($item['CONTENT:ENCODED'][0]['VALUE'])) {  
         if ($body) {  
           $teaser = $body;  
         }  
         $body = &$item['CONTENT:ENCODED'][0]['VALUE'];  
       }  
     }  
   
     /*  
     ** Resolve the item's title. If no title is found, we use  
     ** up to 40 characters of the description ending at a word  
     ** boundary but not splitting potential entities.  
     */  
     if (!($title = $item['TITLE'][0]['VALUE'])) {  
       $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($body, 40));  
     }  
   
     // If title was "escaped" then it may still contain entities, becuase each & from entity was also escabet to &amp; before  
     // TODO: the same for content?  
     if ($item['TITLE'][0]['MODE'] == 'escaped') {  
       $title = planet_replace_entities($title);  
     }  
     $title = strip_tags($title);  
   
     /*  
     ** Resolve the items link.  
     */  
     if ($item['LINK']) {  
       // TODO: remove this Atom hack when we have field mapping or at least specialized parsers in place  
       if (count($item['LINK']) > 1) {  
         $link = $feed->link;  
         foreach ($item['LINK'] as $temp) {  
           if ($temp['REL'] == 'alternate') {  
             $link = $temp[$link_field];  
           }  
         }  
       }  
       else {  
         $link = $item['LINK'][0][$link_field];  
       }  
     }  
     elseif ($item['GUID'] && (strncmp($item['GUID'][0][$link_field], 'http://', 7) == 0) && $item['GUID'][0]['ISPERMALINK'] != 'false') {  
       $link = $item['GUID'][0][$link_field];  
     }  
     else {  
       $link = $feed->link;  
     }  
   
     /*  
     ** Resolve the items source.  
     */  
     if ($item['SOURCE'][0]['VALUE'] && $item['SOURCE'][0]['URL']) { // RSS 2.0  
       $source_title = &$item['SOURCE'][0]['VALUE'];  
       $source_link = &$item['SOURCE'][0]['URL'];  
     }  
     else if ($item['SOURCE'] || $item['ATOM:SOURCE']) { // ATOM 1.0  
       if ($item['SOURCE'][0]['TITLE']) $source_title = &$item['SOURCE'][0]['TITLE'][0]['VALUE'];  
       else if ($item['SOURCE'][0]['ATOM:TITLE']) $source_title = &$item['SOURCE'][0]['ATOM:TITLE'][0]['VALUE'];  
       if ($item['SOURCE'][0]['LINK']) $source_link = &$item['SOURCE'][0]['LINK'][0]['VALUE'];  
       else if ($item['SOURCE'][0]['ATOM:LINK']) $source_link = &$item['SOURCE'][0]['ATOM:LINK'][0]['VALUE'];  
     }  
     else {  
       $source_title = '';  
       $source_link = '';  
     }  
   
     /*  
     ** Try to resolve and parse the item's publication date.  If no  
     ** date is found, we use the current date instead.  
     */  
     // TODO: find nicer way for handling namespaces ;)  
     if ($item['PUBDATE']) $date = $item['PUBDATE'][0]['VALUE'];                        // RSS 2.0  
     else if ($item['DC:DATE']) $date = $item['DC:DATE'][0]['VALUE'];                   // Dublin core  
     else if ($item['DATE']) $date = $item['DATE'][0]['VALUE'];                         // Dublin core  
     else if ($item['DCTERMS:ISSUED']) $date = $item['DCTERMS:ISSUED'][0]['VALUE'];     // Dublin core  
     else if ($item['ISSUED']) $date = $item['ISSUED'][0]['VALUE'];                     // Dublin core  
     else if ($item['DCTERMS:CREATED']) $date = $item['DCTERMS:CREATED'][0]['VALUE'];   // Dublin core  
     else if ($item['CREATED']) $date = $item['CREATED'][0]['VALUE'];                   // Dublin core  
     else if ($item['DCTERMS:MODIFIED']) $date = $item['DCTERMS:MODIFIED'][0]['VALUE']; // Dublin core  
     else if ($item['MODIFIED']) $date = $item['MODIFIED'][0]['VALUE'];                 // Dublin core  
     else if ($item['ATOM:UPDATED']) $date = $item['ATOM:UPDATED'][0]['VALUE'];         // Atom  
     else if ($item['UPDATED']) $date = $item['UPDATED'][0]['VALUE'];                   // Atom  
     else $date = 'now';  
   
     if ($feed->item_date_source == FEEDS_ITEM_DATE_SNIFFED && $date) {  
       $timestamp = strtotime($date); // strtotime() returns -1 on failure  
       if ($timestamp < 0) {  
         $timestamp = planet_parse_w3cdtf($date); // also returns -1 on failure  
         if ($timestamp < 0) {  
           $timestamp = time(); // better than nothing  
         }  
       }  
     }  
     else {  
       $timestamp = time();  
     }  
   
     // Ignore items older than allowed for feed  
     if ($timestamp < $time_horizont) {  
       continue;  
     }  
   
     /*  
     ** Save this item. Try to avoid duplicate entries as much as  
     ** possible. If we find a duplicate entry, we resolve it and  
     ** pass along it's ID such that we can update it if needed.  
     */  
     // Try to use RSS:GUID/ATOM:ID as unique identifier  
     $guid = '';  
     if ($item['GUID'][0]['VALUE']) { // RSS 2.0  
       $guid = $item['GUID'][0]['VALUE'];  
     }  
     else if ($item['ATOM:ID'][0]['VALUE']) { // ATOM 0.3, 1.0  
       $guid = $item['ATOM:ID'][0]['VALUE'];  
     }  
     else if ($item['ID'][0]['VALUE']) { // ATOM 0.3, 1.0  
       $guid = $item['ID'][0]['VALUE'];  
     }  
     else {  
       // feed may contain duplicated links for different items, so we try to generate unique ID for each item  
       $guid = md5("$title - . " . $feed->fid);  
     }  
     // TODO: is there anyway to check if DC:IDENTIFIER is unique?  
     //       http://dublincore.org/documents/usageguide/elements.shtml says it can be non-unique so useles for us :(  
   
     $entry = NULL;  
     if ($guid && strlen($guid) > 0) {  
       $entry = db_fetch_object(db_query("SELECT nid FROM {planet_items} WHERE guid = '%s' AND fid = %d", $guid, $feed->fid));  
     }  
     else if ($link && $link != $feed->link && $link != $feed->url) {  
       $entry = db_fetch_object(db_query("SELECT nid FROM {planet_items} WHERE guid = '%s' AND fid = %d", $link, $feed->fid));  
     }  
     else {  
       $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));  
     }  
   
     //print $guid .'<br />';  
     //print $entry->nid .'<br />';  
     // Ignore items already existing in database and not allowed to be updated  
   
     //Fields to update in either case  
     $entry->changed = strtotime($date);  
     $entry->title = $title;  
     $entry->body = $body;  
     $entry->body = planet_convert_relative_urls($body, $link);  
     $entry->teaser = node_teaser($entry->body);  
     $entry->revision = true;  
   
     //Fields to set if it's a new item.  
     if (!isset($entry->nid)) {  
       //print "Planet item " . $entry->title . "<br />";  
       $entry->type = 'planet';  
   
       $options = variable_get('node_options_planet', array());  
   
       $entry->uid = $feed->uid;  
       $entry->status = 1;  
       $entry->moderate = 0;  
       $entry->promote = in_array('promote', $options) ? 1 : 0;  
       $entry->sticky = in_array('sticky', $options) ? 1 : 0;  
       $entry->comment = in_array('comment', $options) ? 2 : 0;  
       $entry->format = variable_get('planet_filter_formats', 1);  
       $entry->created = strtotime($date);  
       $entry->revision = true;  
   
       $terms = module_invoke('taxonomy', 'node_get_terms', $edit->nid, 'tid');  
       foreach ($terms as $tid => $term) {  
         if ($term->tid) {  
           $edit->taxonomy[] = $term->tid;  
         }  
       }  
       //print '<pre>'. print_r($entry, 1) .'</pre>';  
       node_save($entry);  
       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);  
       watchdog('planet', 'Adding '. $title);  
       drupal_set_message('Adding '. $title);  
     }  
   }  
   
   return $items_added;  
 }  
   
   
 /**  
  * Private function; parses given XML data and returns array  
  */  
 function planet_parse_xml(&$data) {  
   global $xml_tree, $xml_paths, $xml_path_cur;  
   $xml_tree = array();  
   $xml_paths[] = &$xml_tree;  
   $xml_path_cur = 0;  
   
   $_start = microtime();  
   
   // 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>  
   $data = trim(str_replace(array('<![CDATA[', ']]>'), '', $data));  
   
   // Add CDATA around content which may contain (x)html data, and is not contained in CDATA yet  
   $src = array(  
     '%(<(link|content|content:encoded|description|title|summary|info|tagline|copyright|source|itunes:summary|media:text|text)(?>[^<]*(?<!/)>)(?!<!\[CDATA\[))(.*)(</\2>)%sUS',  
     '%24:(\d\d:\d\d)%' // workaround buggy hour format in feeds  
     /*'%(<(\w+)(?>[^<]*type=")(?:text/html|application/xhtml\+xml|html|xhtml")(?>[^<]*(?<!/)>)(?!<!\[CDATA\[))(.*)(</\2>)%sUS'*/  
     );  
   $dst = array(  
     '$1<![CDATA[$3]]>$4',  
     '00:$1'  
     );  
   $data = preg_replace($src, $dst, $data);  
   
   // parse the data:  
   $xml_parser = drupal_xml_parser_create($data);  
   if ($xml_parser == NULL) {  
     return $xml_tree;  
   }  
   
   xml_set_element_handler($xml_parser, 'planet_element_start', 'planet_element_end');  
   xml_set_character_data_handler($xml_parser, 'planet_element_data');  
   xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 1);  
   xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 1);  
   if (!xml_parse($xml_parser, $data, 1)) {  
     $xml_tree['parser_error'] = xml_error_string(xml_get_error_code($xml_parser));  
     $xml_tree['parser_line'] = xml_get_current_line_number($xml_parser);  
   }  
   else {  
        unset($xml_tree['parser_error']);  
        unset($xml_tree['parser_line']);  
   }  
   xml_parser_free($xml_parser);  
   
   $_end = microtime();  
   
   list($sec, $usec) = explode(' ', $_start);  
   $_start = $sec + $usec;  
   list($sec, $usec) = explode(' ', $_end);  
   $xml_tree['parser_time'] = ($sec + $usec) - $_start;  
   
   return $xml_tree;  
 }  
   
 /**  
  * Private call-back function used by the XML parser.  
  */  
 function planet_element_start($parser, $name, $attributes) {  
   global $xml_tree, $xml_paths, $xml_path_cur;  
   
   $temp = &$xml_paths[$xml_path_cur++];  
   $temp[$name][] = $attributes;  
   $xml_paths[$xml_path_cur] = &$temp[$name][count($temp[$name])-1];  
 }  
   
 /**  
  * Private call-back function used by the XML parser.  
  */  
 function planet_element_end($parser, $name) {  
   global $xml_tree, $xml_paths, $xml_path_cur;  
   
   $temp = &$xml_paths[$xml_path_cur];  
   array_pop($xml_paths);  
   $xml_path_cur--;  
   if (isset($temp['VALUE'])) {  
     $temp['VALUE'] = trim(planet_replace_entities($temp['VALUE']));  
   }  
 }  
   
 /**  
  * Private call-back function used by the XML parser.  
  */  
 function planet_element_data($parser, $data) {  
   global $xml_tree, $xml_paths, $xml_path_cur;  
   
   $temp = trim($data);  
   if (strlen($temp) > 0) {  
     $temp = &$xml_paths[$xml_path_cur];  
     $temp['VALUE'] .= $data;  
   }  
 }  
   
797  function planet_page_last() {  function planet_page_last() {
798    global $user;    global $user;
799    
# Line 1351  function planet_page_last() { Line 812  function planet_page_last() {
812    print theme('page', $output);    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() {  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);    $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);