Index: feedapi.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedapi/feedapi.module,v
retrieving revision 1.23.2.100
diff -u -r1.23.2.100 feedapi.module
--- feedapi.module	11 Dec 2007 21:59:10 -0000	1.23.2.100
+++ feedapi.module	13 Dec 2007 21:30:48 -0000
@@ -49,24 +49,22 @@
      $items[] = array(
       'path' => 'admin/content/feed/import_opml',
       'title' => t('Import OPML'),
-      'type' => MENU_NORMAL_ITEM,
       'access' => user_access('administer feedapi'),
       'callback' => 'drupal_get_form',
       'callback arguments' => array('feedapi_import_feeds_form'),
      );
      $items[] = array(
       'path' => 'admin/content/feed/export_opml',
-      'title' => t('Import OPML'),
-      'type' => MENU_CALLBACK,
+      'title' => t('Export all feeds as OPML'),
       'access' => user_access('administer feedapi'),
       'callback' => 'drupal_get_form',
       'callback arguments' => array('_feedapi_export_opml'),
      );
-     $items[] = array('path' => 'admin/settings/feedapi',
+     $items[] = array(
+       'path' => 'admin/settings/feedapi',
        'title' => t('FeedAPI settings'),
        'callback' => 'drupal_get_form',
        'callback arguments' => array('feedapi_admin_settings'),
-       'type' => MENU_NORMAL_ITEM,
        'access' => user_access('administer feedapi'),
      );
   }
@@ -75,14 +73,16 @@
     if (isset($node->feed)) {
       global $user;
       $own_feed = $node->uid == $user->uid && user_access('edit own '. $node->type . ' content') ? TRUE : FALSE;
-      $items[] = array('path' => 'node/'. $node->nid. '/refresh',
+      $items[] = array(
+        'path' => 'node/'. $node->nid. '/refresh',
         'title' => t('Refresh'),
         'callback' => 'feedapi_refresh',
         'callback arguments' => array($node, 'node/'. $node->nid),
         'type' => MENU_LOCAL_TASK,
         'access' => (user_access('administer feedapi') || $own_feed),
       );
-      $items[] = array('path' => 'node/'. $node->nid. '/purge',
+      $items[] = array(
+        'path' => 'node/'. $node->nid. '/purge',
         'title' => t('Remove items'),
         'callback' => 'feedapi_invoke_feedapi',
         'callback arguments' => array("purge", $node->feed, 'items'),
@@ -115,7 +115,6 @@
           $node_type_settings = _feedapi_get_settings(array('node_type' => $node->type));
           $node->feed->parsers = _feedapi_format_settings($node_type_settings, 'parsers');
           $node->feed->processors = _feedapi_format_settings($node_type_settings, 'processors');
-          $node->feed->nid = $node->nid;
         }
         break;
       case 'delete':
@@ -132,12 +131,7 @@
       case 'submit':
         // Todo: the first case essentially copies the entire feed array from feedapi to feed.
         // Figure out a way how to save this step.
-        if ($node->feedapi_feed_object) {
-          $node->feed = $node->feedapi_feed_object;
-        }
-        else {
-          $node->feed = _feedapi_build_feed_object($node->type, $node->feedapi['feedapi_url']);
-        }
+        $node->feed = isset($node->feedapi_feed_object) ? $node->feedapi_feed_object : _feedapi_build_feed_object($node->type, $node->feedapi['feedapi_url']);
         break;
     }
   }
@@ -147,10 +141,9 @@
  * Implementation of hook_node_type().
  */
 function feedapi_node_type($op, $info) {
-  switch ($op){
+  switch ($op) {
     case 'delete':
       variable_del('feedapi_settings_'. $info->type);
-      // Todo: find out, why and where there is still feedapi_$info->type being stored.
       variable_del('feedapi_'. $info->type);
       break;
     case 'update':
@@ -178,14 +171,13 @@
       foreach ($names as $type => $name) {
         $blocks[$type]['info'] = t('FeedAPI: Quick create !preset', array('!preset' => $name));
       }
-      return $blocks;
     case 'view':
       if (node_access('create', $delta)) {
-        $block['subject'] = t('Create !preset', array('!preset' => $names[$delta]));
-        $block['content'] = drupal_get_form('feedapi_simplified_form', $delta);
+        $blocks['subject'] = t('Create !preset', array('!preset' => $names[$delta]));
+        $blocks['content'] = drupal_get_form('feedapi_simplified_form', $delta);
       }
-      return $block;
   }
+  return $blocks;
 }
 
 /**
@@ -214,19 +206,16 @@
  * Implementation of hook_simpletest().
  */
 function feedapi_simpletest() {
-  $tests = file_scan_directory(drupal_get_path('module', 'feedapi'), '\.test');
-  return array_keys($tests);
+  return array_keys(file_scan_directory(drupal_get_path('module', 'feedapi'), '\.test'));
 }
 
 /**
- * Do various things with feed. Handle the core data and call the
- * underyling modules (parsers/processors) too.
- * TODO: Clean up this function, break it in smaller pieces that are 
- * easier to handle.
+ * Call the proper private function to handle feed operations
  *
  * @param $op
  *  "load"    Load the feed items basic data into the $feed->items[]
  * "refresh"  Re-download the feed and process newly arrived item
+ * "purge"    Delete all the feed items
  * 
  * @param $feed
  *  A feed object. If only the ID is known, you should pass something like this: $feed->nid = X 
@@ -245,122 +234,8 @@
     $feed = $node->feed;
   }
   _feedapi_sanitize_processors($feed);
-  switch ($op) {
-    case 'load':
-      $feed->items = array();
-      foreach ($feed->processors as $processor) {
-        $items = module_invoke($processor, 'feedapi_item', 'fetch', $feed);
-        if (is_array($items)) {
-          foreach ($items as $item) {
-            $feed->items[] = $item;
-          }
-        }
-      }
-      break;
-
-    case 'refresh':
-      timer_start('feedapi_'. $feed->nid);
-      $cron = $param;
-      if (!is_array($feed->processors) || count($feed->processors) == 0) {
-        if (!$cron) {
-          drupal_set_message(t("No processors specified for URL %url. Could not refresh.", array('%url' => $feed->url)), "error");
-          drupal_goto('node/'. $feed->nid);
-        }
-        return;
-      }
-      // Force the processors to delete old items and determine the max. create elements
-      $refresh = feedapi_expire($feed,  $cron === TRUE ? FALSE : TRUE);
-      if ($refresh == FALSE) {
-        return;
-      }
-      $nid = $feed->nid;
-      // Force non-caching mode if the feed is only processed half-done.
-      $feed = _feedapi_call_parsers($feed, $feed->parsers, $feed->half_done ? FALSE : TRUE);
-      if (($feed === FALSE) || (!is_array($feed->items))) {
-        // Refresh the Last refresh field at all the cases
-        db_query("UPDATE {feedapi} SET checked = %d, half_done = %d WHERE nid = %d", time(), FALSE, $nid);
-        if (!$cron) {
-          drupal_set_message(t('Could not refresh feed.'));
-        }
-        return;
-      }
-      
-      $items = array_reverse($feed->items);
-      $updated = 0;
-      $new = 0;
-      $half_done = FALSE;
-      $settings = _feedapi_get_settings(array('nid' => $feed->nid));
-      // Walk through the items
-      foreach ($items as $item) {
-        // Call each item parser
-        $is_updated = FALSE;
-        $is_new = FALSE;
-        foreach ($feed->processors as $processor) {
-          if (!module_invoke($processor, 'feedapi_item', 'unique', $item, $feed->nid, $feed->settings['processors'][$processor])) {
-            if ($settings['update_existing'] == TRUE) {
-              module_invoke($processor, 'feedapi_item', 'update', $item, $feed->nid, $feed->settings['processors'][$processor]);
-              $is_updated = TRUE;
-            }
-          }
-          else {
-
-            // if the item is already expired then do nothing
-            $diff = abs(time() - (isset($item->options->timestamp) ? $item->options->timestamp : time()));
-            $items_delete = $settings['items_delete'];
-            if ($diff > $items_delete && ($items_delete > FEEDAPI_NEVER_DELETE_OLD)) {
-              break;
-            }
-
-            module_invoke($processor, 'feedapi_item', 'save', $item, $feed->nid, $feed->settings['processors'][$processor]);
-            $is_new = TRUE;
-          }
-        }
-        $new = $is_new ? $new + 1 : $new;
-        $updated = ($is_updated && !$is_new) ? $updated + 1 : $updated;
-        // decision on time. If the exec time is greather than the user-set percentage of php max execution time
-        if ($cron && ((timer_read('feedapi_cron') / 1000) > ((variable_get('feedapi_cron_percentage', 15) / 100) * ini_get('max_execution_time')))) {
-          $timeout = FEEDAPI_TIMEOUT;
-          $half_done = ($new + $updated) == count($items) ? FALSE : TRUE;
-          break;
-        }
-      }
-      foreach (module_implements('feedapi_after_refresh') as $module) {
-        $func = $module. '_feedapi_after_refresh';
-        $func($feed);
-      }
-      if ($new > 0) {
-        $feed->statistics['update_times'] = _feedapi_queue($feed->statistics['update_times'], time());
-        $feed->statistics['new'] = _feedapi_queue($feed->statistics['new'], $new);
-        $feed->statistics['download_num'] = _feedapi_queue($feed->statistics['download_num'], count($items));
-        $feed->statistics['process_time'] = _feedapi_queue($feed->statistics['process_time'], timer_read('feedapi_'. $feed->nid));
-      }
-      db_query("UPDATE {feedapi} SET checked = %d, statistics = '%s', half_done = %d WHERE nid = %d", time(), serialize($feed->statistics), $half_done, $feed->nid);
-      if (!$cron) {
-        drupal_set_message(t("%new new item(s) were saved. %updated existing item(s) were updated.", array("%new" => $new, "%updated" => $updated)));
-      }
-      if ($timeout) {
-        return $timeout;
-      }
-      break;
-
-    case 'purge':
-      $node = node_load($feed->nid);
-      if ($param == 'items') {
-        return drupal_get_form('feedapi_purge_confirm', $node);
-      }
-      feedapi_invoke_feedapi('load', $feed);
-		    
-      // Delete items from the processors
-      foreach ($feed->items as $item) {
-        foreach($feed->processors as $processor) {
-          // FIXME: it's possible now to accidentally delete an item from another processor
-          module_invoke($processor, 'feedapi_item', 'delete', $item, $feed->settings['processors'][$processor]);
-        }
-      }
-      if ($param == 'items_confirmed') {
-        drupal_set_message(t('!count feed items have been deleted successfully from the feed.', array('!count' => count($feed->items))));
-      }
-      break;
+  if (in_array($op, array('load', 'refresh', 'purge'))) {
+    return call_user_func('_feedapi_'. $op, $feed, $param);
   }
 }
 
@@ -392,8 +267,6 @@
  * OPML Feed import form, also allows setting defaults to be applied to each feed
  */
 function feedapi_import_feeds_form() {
-  global $user;
-  $form['#attributes'] = array('enctype' => 'multipart/form-data');
   $form['opml'] = array(
     '#type' => 'file',
     '#title' => t('OPML File'),
@@ -411,17 +284,10 @@
     '#title' => t('Feed Type'),
     '#description' => t("The type of feed you would like to associate this import with."),
     '#options' => $feed_types,
-    '#default_value' => '',
-    '#size' => 1,
     '#required' => TRUE,
   );
-  $node = new stdClass();
-  $node->uid = $user->uid;
-  $form['#node'] = $node;
-  $form['#method'] = 'post';
   $form['#attributes']['enctype'] = 'multipart/form-data';
   $form['submit'] = array('#type' => 'submit', '#value' => 'Submit');
-  
   if (module_exists('og')) {
     og_form_add_og_audience('feedapi_import_feeds_form', $form);
   }
@@ -442,7 +308,6 @@
     else {
       drupal_set_message(t('Feed list could not be imported. Please check that this is a valid OPML file.'), 'error');
     }
-
   }
   else {
     drupal_set_message(t('Data could not be retrieved, invalid or empty file.'), 'error');
@@ -462,13 +327,12 @@
  */
 function feedapi_expire($feed, $force) {
   $settings = _feedapi_get_settings(array('nid' => $feed->nid));
-  $feed->items_delete = $settings['items_delete'];
   $needs_refresh = time() - $feed->checked > $feed->refresh;
-  if (is_array($feed->items) && ($needs_refresh || $force) && $feed->items_delete > FEEDAPI_NEVER_DELETE_OLD) {
+  if (is_array($feed->items) && ($needs_refresh || $force) && $settings['items_delete'] > FEEDAPI_NEVER_DELETE_OLD) {
     foreach ($feed->items as $item) {
       if (isset($item->arrived) || isset($item->timestamp)) {
         $diff = abs(time() - (isset($item->timestamp) ? $item->timestamp : $item->timestamp));
-        if ($diff > $feed->items_delete) {
+        if ($diff > $settings['items_delete']) {
           foreach($feed->processors as $processor) {
             module_invoke($processor, 'feedapi_item', 'delete', $item, $feed->nid);
           }
@@ -476,10 +340,7 @@
       }
     }
   }
-  if ($needs_refresh == FALSE && $force == FALSE) {
-    return FALSE;
-  }
-  return TRUE;
+  return ($needs_refresh == FALSE && $force == FALSE) ? FALSE : TRUE;
 }
 
 /**
@@ -661,53 +522,32 @@
       // Which parsers / processors are enabled is a per content-type setting.
       $node_type_settings = _feedapi_get_settings(array('node_type' => $form['type']['#value']));
       // retrieve forms.
-      $suitable_parsers = module_implements('feedapi_feed', TRUE);
-      foreach ($suitable_parsers as $module) {
-        if ($node_type_settings['parsers'][$module]['enabled']) {
-          $result = array();  
-          $result = module_invoke($module, 'feedapi_settings_form', 'parsers');
-          if (is_array($result)) {
-            $result['#weight'] = $node_type_settings['parsers'][$module]['weight'];
-            $form['feedapi']['parsers'][$module] = $result;
-            $form['feedapi']['parsers'][$module]['#type'] = 'fieldset';
-            $form['feedapi']['parsers'][$module]['#title'] = feedapi_get_natural_name($module);
-            $form['feedapi']['parsers'][$module]['#collapsible'] = TRUE;
-            $form['feedapi']['parsers'][$module]['#collapsed'] = FALSE;
-            $form['feedapi']['parsers'][$module]['#tree'] = TRUE;
+
+      foreach (array("parsers" => "feedapi_feed", "processors" => "feedapi_item") as $type => $requirement) {
+        $suitable_handlers = module_implements($requirement, TRUE);
+        foreach ($suitable_handlers as $module) {
+          if ($node_type_settings[$type][$module]['enabled']) {
+            $result = array();
+            $result = module_invoke($module, 'feedapi_settings_form', $type);
+            if (is_array($result)) {
+              $result['#weight'] = $node_type_settings[$type][$module]['weight'];
+              $form['feedapi'][$type][$module] = $result;
+              $form['feedapi'][$type][$module]['#type'] = 'fieldset';
+              $form['feedapi'][$type][$module]['#title'] = feedapi_get_natural_name($module);
+              $form['feedapi'][$type][$module]['#collapsible'] = TRUE;
+              $form['feedapi'][$type][$module]['#collapsed'] = FALSE;
+              $form['feedapi'][$type][$module]['#tree'] = TRUE;
+            }
           }
         }
-      }
-      if ($form['feedapi']['parsers']) {
-        $form['feedapi']['parsers']['#type'] = 'fieldset';
-        $form['feedapi']['parsers']['#title'] = t('Parsers');
-        $form['feedapi']['parsers']['#collapsible'] = TRUE;
-        $form['feedapi']['parsers']['#collapsed'] = TRUE;
-        $form['feedapi']['parsers']['#tree'] = TRUE;
-      }
-      
-      $suitable_processors = module_implements('feedapi_item', TRUE);
-      foreach ($suitable_processors as $module) {
-        if ($node_type_settings['processors'][$module]['enabled']) {
-          $result = array();
-          $result = module_invoke($module, 'feedapi_settings_form', 'processors');
-          if (is_array($result)) {
-            $result['#weight'] = $node_type_settings['processors'][$module]['weight'];
-            $form['feedapi']['processors'][$module] = $result;
-            $form['feedapi']['processors'][$module]['#type'] = 'fieldset';
-            $form['feedapi']['processors'][$module]['#title'] = feedapi_get_natural_name($module);
-            $form['feedapi']['processors'][$module]['#collapsible'] = TRUE;
-            $form['feedapi']['processors'][$module]['#collapsed'] = FALSE;
-            $form['feedapi']['processors'][$module]['#tree'] = TRUE;
-          }
+        if (isset($form['feedapi'][$type])) {
+          $form['feedapi'][$type]['#type'] = 'fieldset';
+          $form['feedapi'][$type]['#title'] = t('Parsers');
+          $form['feedapi'][$type]['#collapsible'] = TRUE;
+          $form['feedapi'][$type]['#collapsed'] = TRUE;
+          $form['feedapi'][$type]['#tree'] = TRUE;
         }
       }
-      if ($form['feedapi']['processors']) {
-        $form['feedapi']['processors']['#type'] = 'fieldset';
-        $form['feedapi']['processors']['#title'] = t('Processors');
-        $form['feedapi']['processors']['#collapsible'] = TRUE;
-        $form['feedapi']['processors']['#collapsed'] = TRUE;
-        $form['feedapi']['processors']['#tree'] = TRUE;
-      }
     }
     // If we are on a node form, get per node settings and populate form.
     if (!$settings = _feedapi_get_settings(array('nid' => $form['#node']->nid, 'node_type' => $form['type']['#value']))) {
@@ -719,7 +559,6 @@
 
 /**
  * Implementation of hook_feedapi_settings_form().
- * FeedAPI specific settings.
  */
 function feedapi_feedapi_settings_form($type) {
   if ($type == 'general') {
@@ -753,10 +592,9 @@
 function feedapi_cron() {
   $result = db_query("SELECT nid FROM {feedapi} ORDER BY checked ASC");
   timer_start('feedapi_cron');
-  while ($feed = db_fetch_object($result, $row++)) {
+  while ($feed = db_fetch_object($result)) {
     feedapi_invoke_feedapi('load', $feed);
-    $timeout = feedapi_invoke_feedapi('refresh', $feed, TRUE);
-    if ($timeout == FEEDAPI_TIMEOUT) {
+    if (FEEDAPI_TIMEOUT == feedapi_invoke_feedapi('refresh', $feed, TRUE)) {
       break;
     }
   }
@@ -815,7 +653,6 @@
     "Averages over the last @count refreshes."
   );
   $output .= theme('table', $header, $rows);
-  $output .= l(t('Export site feeds as OPML'), 'admin/content/feed/export_opml');
   $output .= theme('pager', 0, 50);
   return $output;
 }
@@ -838,7 +675,7 @@
   );
   $form['submit'] = array(
     '#type' => 'submit',
-    '#value' => t('Submit'),
+    '#value' => t('Add'),
   );
   return $form;
 }
@@ -877,8 +714,7 @@
 }
 
 /**
- * Settings:
- *  Allowed HTML tags, number of feeds refreshed in one round
+ * Settings: allowed HTML tags, number of feeds refreshed in one round
  */
 function feedapi_admin_settings() {
   $form['feedapi_allowed_html_tags'] = array(
@@ -958,26 +794,6 @@
 }
 
 /**
- * Update a feedapi node URL.
- * This function could be expanded to allow more updates than the URL.
- * @param $nid 
- * node-id of node to be updated.
- * @param $url 
- * string - new URL.
- * @param $title
- * string - optional new title.
- * @return 
- * Updated node.
- */
-function feedapi_update_node($nid, $url, $title = NULL) {
-  $node = node_load($nid);
-  $node->feed->url = $url;
-  $node->title = $title ? $title : $node->title;
-  node_save($node);
-  return $node;
-}
-
-/**
  * Load node by URL.
  * @param $args
  * Currently only supported $args['url] - URL string.
@@ -994,13 +810,11 @@
 /**
  * Refresh a feed node (= run enabled processors on it).
  * @param $node
- * A node object with a $node->feed object.
+ *  A node object with a $node->feed object.
  * @param $destination_path
- * If a destination path is given, function redirects to this destination.
- * @param $cron
- * If TRUE, function will be silent (no screen messages).
+ *  If a destination path is given, function redirects to this destination.
  */
-function feedapi_refresh($node, $destination_path = NULL, $cron = FALSE) {
+function feedapi_refresh($node, $destination_path = NULL) {
   feedapi_invoke_feedapi('refresh', $node->feed, $cron);
   if ($destination_path) {
     drupal_goto($destination_path);
@@ -1061,12 +875,12 @@
     db_query("UPDATE {feedapi} SET
             url = '%s', feed_type = '%s', processors = '%s',
             parsers = '%s', link = '%s' WHERE nid = %d", 
-    $node->feed->url,
-    $node->feed->feed_type,
-    serialize($node->feed->processors),
-    serialize($node->feed->parsers),
-    $node->feed->options->link,
-    $node->nid
+      $node->feed->url,
+      $node->feed->feed_type,
+      serialize($node->feed->processors),
+      serialize($node->feed->parsers),
+      $node->feed->options->link,
+      $node->nid
     );
     // Store add on module's settings if user has permission to do so.
     if (user_access('advanced feedapi options')) {
@@ -1095,6 +909,7 @@
   $parser_primary = array_shift($parsers);
   $parsers_secondary = $parsers;
   if (module_exists($parser_primary)) {
+    $feed->feed_type =  module_invoke($parser_primary, 'feedapi_feed', 'compatible', $feed);
     $parser_output =  module_invoke($parser_primary, 'feedapi_feed', 'parse', $feed, $cache);
     if ($parser_output === FALSE && $cache) {
       return $parser_output;
@@ -1163,9 +978,9 @@
  * If parser or processor is passed in, this function determines wether given
  * parser or processor is enabled for given node type.
  * @param $node_type
- * A Drupal node type.
+ *  A Drupal node type.
  * @param $parser_or_processor
- * A parser or processor - pass in by module name.
+ *  A parser or processor - pass in by module name.
  * @return TRUE if enabled, FALSE if not.
  */
 function feedapi_enabled($node_type, $parser_or_processor = '') {
@@ -1182,6 +997,131 @@
 }
 
 /**
+ * Put a list of feed items into the feed object from the processors
+ */
+function _feedapi_load(&$feed, $param) {
+  $feed->items = array();
+  foreach ($feed->processors as $processor) {
+    $items = module_invoke($processor, 'feedapi_item', 'fetch', $feed);
+    if (is_array($items)) {
+      foreach ($items as $item) {
+        $feed->items[] = $item;
+      }
+    }
+  }
+}
+
+/**
+ * Refresh the feed, call the proper parsers and processors' hooks
+ */
+function _feedapi_refresh(&$feed, $param) {
+  timer_start('feedapi_'. $feed->nid);
+  $cron = $param;
+  if (!is_array($feed->processors) || count($feed->processors) == 0) {
+    if (!$cron) {
+      drupal_set_message(t("No processors specified for URL %url. Could not refresh.", array('%url' => $feed->url)), "error");
+      drupal_goto('node/'. $feed->nid);
+    }
+    return;
+  }
+  // Force the processors to delete old items and determine the max. create elements
+  $refresh = feedapi_expire($feed,  $cron === TRUE ? FALSE : TRUE);
+  if ($refresh == FALSE) {
+    return;
+  }
+  $nid = $feed->nid;
+  // Force non-caching mode if the feed is only processed half-done.
+  $feed = _feedapi_call_parsers($feed, $feed->parsers, $feed->half_done ? FALSE : TRUE);
+  if (($feed === FALSE) || (!is_array($feed->items))) {
+    // Refresh the Last refresh field at all the cases
+    db_query("UPDATE {feedapi} SET checked = %d, half_done = %d WHERE nid = %d", time(), FALSE, $nid);
+    if (!$cron) {
+      drupal_set_message(t('Could not refresh feed.'));
+    }
+    return;
+  }
+
+  $items = array_reverse($feed->items);
+  $updated = 0;
+  $new = 0;
+  $half_done = FALSE;
+  $settings = _feedapi_get_settings(array('nid' => $feed->nid));
+  // Walk through the items
+  foreach ($items as $item) {
+    // Call each item parser
+    $is_updated = FALSE;
+    $is_new = FALSE;
+    foreach ($feed->processors as $processor) {
+      if (!module_invoke($processor, 'feedapi_item', 'unique', $item, $feed->nid, $feed->settings['processors'][$processor])) {
+        if ($settings['update_existing'] == TRUE) {
+          module_invoke($processor, 'feedapi_item', 'update', $item, $feed->nid, $feed->settings['processors'][$processor]);
+          $is_updated = TRUE;
+        }
+      }
+      else {
+
+        // if the item is already expired then do nothing
+        $diff = abs(time() - (isset($item->options->timestamp) ? $item->options->timestamp : time()));
+        $items_delete = $settings['items_delete'];
+        if ($diff > $items_delete && ($items_delete > FEEDAPI_NEVER_DELETE_OLD)) {
+          break;
+        }
+
+        module_invoke($processor, 'feedapi_item', 'save', $item, $feed->nid, $feed->settings['processors'][$processor]);
+        $is_new = TRUE;
+      }
+    }
+    $new = $is_new ? $new + 1 : $new;
+    $updated = ($is_updated && !$is_new) ? $updated + 1 : $updated;
+    // decision on time. If the exec time is greather than the user-set percentage of php max execution time
+    if ($cron && ((timer_read('feedapi_cron') / 1000) > ((variable_get('feedapi_cron_percentage', 15) / 100) * ini_get('max_execution_time')))) {
+      $timeout = FEEDAPI_TIMEOUT;
+      $half_done = ($new + $updated) == count($items) ? FALSE : TRUE;
+      break;
+    }
+  }
+  foreach (module_implements('feedapi_after_refresh') as $module) {
+    $func = $module. '_feedapi_after_refresh';
+    $func($feed);
+  }
+  if ($new > 0) {
+    $feed->statistics['update_times'] = _feedapi_queue($feed->statistics['update_times'], time());
+    $feed->statistics['new'] = _feedapi_queue($feed->statistics['new'], $new);
+    $feed->statistics['download_num'] = _feedapi_queue($feed->statistics['download_num'], count($items));
+    $feed->statistics['process_time'] = _feedapi_queue($feed->statistics['process_time'], timer_read('feedapi_'. $feed->nid));
+  }
+  db_query("UPDATE {feedapi} SET checked = %d, statistics = '%s', half_done = %d WHERE nid = %d", time(), serialize($feed->statistics), $half_done, $feed->nid);
+  if (!$cron) {
+    drupal_set_message(t("%new new item(s) were saved. %updated existing item(s) were updated.", array("%new" => $new, "%updated" => $updated)));
+  }
+  if ($timeout) {
+    return $timeout;
+  }
+}
+
+/**
+ * Delete all feed items of a feed.
+ */
+function _feedapi_purge(&$feed, $param) {
+  $node = node_load($feed->nid);
+  if ($param == 'items') {
+    return drupal_get_form('feedapi_purge_confirm', $node);
+  }
+  feedapi_invoke_feedapi('load', $feed);
+
+  // Delete items from the processors
+  foreach ($feed->items as $item) {
+    foreach($feed->processors as $processor) {
+      // FIXME: it's possible now to accidentally delete an item from another processor
+      module_invoke($processor, 'feedapi_item', 'delete', $item, $feed->settings['processors'][$processor]);
+    }
+  }
+  if ($param == 'items_confirmed') {
+    drupal_set_message(t('!count feed items have been deleted successfully from the feed.', array('!count' => count($feed->items))));
+  }
+}
+
+/**
  * Builds feed object ready to be sticked onto node.
  */
 function _feedapi_build_feed_object($node_type, $url) {
@@ -1190,14 +1130,6 @@
   $node_type_settings = _feedapi_get_settings(array('node_type' => $node_type));
   $feed->processors = _feedapi_format_settings($node_type_settings, 'processors');
   $feed->parsers = _feedapi_format_settings($node_type_settings, 'parsers');
-  $feed->feed_type = "XML feed";
-  /*
-  Todo - move assign procedure to 'refresh'. Decide what to do with $feed->feed_type = "XML feed"
-  $assigned = _feedapi_assign_parser_processor($feed->url, $node_type_settings);
-  $feed->processors = $assigned['processors'];
-  $feed->parsers = $assigned['parsers'];
-  $feed->feed_type = $assigned['type'];
-  */
   if ($feed->url) {
     $feed = _feedapi_call_parsers($feed, $feed->parsers, FALSE);
   }
@@ -1240,7 +1172,7 @@
  *       parsers/processors, use per node settings to override their 
  *       configuration, this allows us a more predictable 
  *       presets/settings behaviour. See d. o. #191692
- * Todo: add caching. 
+ * Todo: add caching.
  * Watch out: cache permutations of node_type or node_type+nid or nid.
  * Watch out: changes within page load likely.
  */
@@ -1291,67 +1223,8 @@
 }
 
 /**
- * Auto-detect one possible parser and one feed and item processor to the given URL
- * It follows the order of the modules from the module's settings page.
- */
-function _feedapi_assign_parser_processor($url, $node_type_settings) {
-  $parsers = module_implements('feedapi_feed', TRUE);
-  // Get the parsers' weight
-  $sorted_parsers = array();
-  foreach ($parsers as $parser) {
-    if ($node_type_settings['parsers'][$parser]['enabled']) {
-      $sorted_parsers[$node_type_settings['parsers'][$parser]['weight']][] = $parser;
-    }
-  }
-  $success = FALSE;
-  $keys = array_keys($sorted_parsers);
-  if (count($keys) > 0) {
-    $low = min($keys);
-    $max = max($keys);
-    // Call the ordered parsers to find all the compatible
-    $parsers = array();
-    $parsers['secondary'] = array();
-    for ($i = $low; $i <= $max; $i++) {
-      if (isset($sorted_parsers[$i])) {
-        foreach ($sorted_parsers[$i] as $parser) {
-          $type = module_invoke($parser, 'feedapi_feed', 'compatible', $url);
-          if (!isset($parsers['primary']) && is_string($type)) {
-            $success = TRUE;
-            $parsers['primary'] = $parser;
-          }
-          else if (is_string($type)) {
-            $parsers['secondary'][] = $parser;
-          }
-        }
-      }
-    }
-  }
-  if (!$success) {
-    // We can't find a suitable parser at all
-    return array('type' => '', 'parser' => '', 'processor_item' => '');
-  }
-  // Get the feed item processor's weight
-  $all_processors = module_implements('feedapi_item');
-  foreach ($all_processors as $processor) {
-    if (in_array($type, module_invoke($processor, 'feedapi_item', 'type'))) {
-      $processors[] = $processor;
-    }
-  }
-  $processors = array_intersect($processors, array_keys($node_type_settings['processors']));
-  foreach ($processors as $processor) {
-    if ($node_type_settings['processors'][$processor]['enabled']) {
-      $sorted_processors[$node_type_settings['processors'][$processor]['weight']] = $processor;
-    }
-  }
-  return array(
-    'type' => $type,
-    'parsers' => $parsers,
-    'processors' => $sorted_processors
-  );
-}
-
-/**
  * Add the element of the queue. This queue is fixed-length, the first element is discarded if full.
+ * Helper function for statistics handling
  */
 function _feedapi_queue($queue, $new_elem) {
   if (empty($queue) || !isset($queue)) {
@@ -1405,6 +1278,7 @@
   $output .= "</body>\n";
   $output .= "</opml>\n";
   drupal_set_header('Content-Type: text/xml; charset=utf-8');
+  drupal_set_header('Content-Disposition: attachment; filename="'. variable_get('site_name', 'drupal') .'.opml"');
   print $output;
   exit(0);
 }
Index: feedapi_aggregator/feedapi_aggregator.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedapi/feedapi_aggregator/feedapi_aggregator.module,v
retrieving revision 1.1.2.21
diff -u -r1.1.2.21 feedapi_aggregator.module
--- feedapi_aggregator/feedapi_aggregator.module	30 Oct 2007 22:11:21 -0000	1.1.2.21
+++ feedapi_aggregator/feedapi_aggregator.module	13 Dec 2007 21:30:50 -0000
@@ -41,50 +41,62 @@
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => -15,
      );
-    $items[] = array('path' => 'admin/content/feed/category/add',
+    $items[] = array(
+      'path' => 'admin/content/feed/category/add',
       'title' => t('Add category'),
       'callback' => 'drupal_get_form',
       'callback arguments' => array('feedapi_aggregator_form_category'),
       'access' => $edit,
-      'type' => MENU_LOCAL_TASK);
-      
-    $items[] = array('path' => 'aggregator',
+      'type' => MENU_LOCAL_TASK
+    );
+    $items[] = array(
+      'path' => 'aggregator',
       'title' => t('News aggregator'),
       'callback' => 'feedapi_aggregator_page_last',
       'access' => $view,
-      'weight' => 5);
-    $items[] = array('path' => 'aggregator/sources',
+      'weight' => 5
+    );
+    $items[] = array(
+      'path' => 'aggregator/sources',
       'title' => t('Sources'),
       'callback' => 'feedapi_aggregator_page_sources',
-      'access' => $view);
-    $items[] = array('path' => 'aggregator/categories',
+      'access' => $view
+    );
+    $items[] = array(
+      'path' => 'aggregator/categories',
       'title' => t('Categories'),
       'callback' => 'feedapi_aggregator_page_categories',
       'access' => $view,
-      'type' => MENU_ITEM_GROUPING);
-    $items[] = array('path' => 'aggregator/rss',
+      'type' => MENU_ITEM_GROUPING
+    );
+    $items[] = array(
+      'path' => 'aggregator/rss',
       'title' => t('RSS feed'),
       'callback' => 'feedapi_aggregator_page_rss',
       'access' => $view,
-      'type' => MENU_CALLBACK);
-    $items[] = array('path' => 'aggregator/opml',
+      'type' => MENU_CALLBACK
+    );
+    $items[] = array(
+      'path' => 'aggregator/opml',
       'title' => t('OPML feed'),
       'callback' => 'feedapi_aggregator_page_opml',
       'access' => $view,
-      'type' => MENU_CALLBACK);
+      'type' => MENU_CALLBACK
+    );
 
     $result = db_query('SELECT title, cid FROM {feedapi_aggregator_category} ORDER BY title');
     while ($category = db_fetch_array($result)) {
-      $items[] = array('path' => 'aggregator/categories/'. $category['cid'],
+      $items[] = array(
+        'path' => 'aggregator/categories/'. $category['cid'],
         'title' => $category['title'],
         'callback' => 'feedapi_aggregator_page_category',
         'access' => $view);
     }
-    $items[] = array('path' => 'admin/settings/feedapi_aggregator',
+    $items[] = array(
+      'path' => 'admin/settings/feedapi_aggregator',
       'title' => t('FeedAPI Aggregator'),
       'callback' => 'drupal_get_form',
       'callback arguments' => array('feedapi_aggregator_admin_settings'),
-      'type' => MENU_NORMAL_ITEM,
       'access' => user_access('administer feedapi'),
     );
   }
@@ -185,8 +197,7 @@
     '#default_value' => 3,
     '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)),
   );
-  $categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {feedapi_aggregator_category} c LEFT JOIN {feedapi_aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = %d', $item->iid);
-  $selected = array();
+  $categories_result = db_query('SELECT cid, title FROM {feedapi_aggregator_category}');
   while ($category = db_fetch_object($categories_result)) {
     $categories[$category->cid] = check_plain($category->title);
   }
Index: parser_common_syndication/parser_common_syndication.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedapi/parser_common_syndication/parser_common_syndication.module,v
retrieving revision 1.6.2.10
diff -u -r1.6.2.10 parser_common_syndication.module
--- parser_common_syndication/parser_common_syndication.module	27 Oct 2007 20:06:44 -0000	1.6.2.10
+++ parser_common_syndication/parser_common_syndication.module	13 Dec 2007 21:30:50 -0000
@@ -34,7 +34,7 @@
       if (!function_exists('simplexml_load_string')) {
         return FALSE;
       }
-      $url = is_string($args[1]) ? $args[1] : FALSE;
+      $url = $args[1]->url;
       $downloaded_string = _parser_common_syndication_download($url, FALSE);
       if (!defined('LIBXML_VERSION') || (version_compare(phpversion(), '5.1.0', '<'))) {
         @ $xml = simplexml_load_string($downloaded_string, NULL);
Index: parser_simplepie/parser_simplepie.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedapi/parser_simplepie/parser_simplepie.module,v
retrieving revision 1.6.2.14
diff -u -r1.6.2.14 parser_simplepie.module
--- parser_simplepie/parser_simplepie.module	13 Dec 2007 16:39:14 -0000	1.6.2.14
+++ parser_simplepie/parser_simplepie.module	13 Dec 2007 21:30:51 -0000
@@ -67,7 +67,7 @@
       return array("XML feed");
       break;
     case 'compatible':
-      $url = is_string($args[1]) ? $args[1] : FALSE;
+      $url = $args[1]->url;
       $parser = _parser_simplepie_get_parser($url);
       if ($parser->error) {
         return FALSE;
