Index: modules/aggregator/aggregator.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.admin.inc,v
retrieving revision 1.18
diff -u -r1.18 aggregator.admin.inc
--- modules/aggregator/aggregator.admin.inc	22 Oct 2008 18:29:28 -0000	1.18
+++ modules/aggregator/aggregator.admin.inc	6 Nov 2008 21:54:42 -0000
@@ -389,33 +389,36 @@
  * @see system_settings_form()
  */
 function aggregator_admin_settings() {
-  $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items');
-  $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
-
-  $form['aggregator_allowed_html_tags'] = array(
-    '#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#size' => 80, '#maxlength' => 255,
-    '#default_value' => variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'),
-    '#description' => t('A space-separated list of HTML tags allowed in the content of feed items. (Tags in this list are not removed by Drupal.)'),
-  );
-
-  $form['aggregator_summary_items'] = array(
-    '#type' => 'select', '#title' => t('Items shown in sources and categories pages') ,
-    '#default_value' => variable_get('aggregator_summary_items', 3), '#options' => $items,
-    '#description' => t('Number of feed items displayed in feed and category summary pages.'),
-  );
-
-  $form['aggregator_clear'] = array(
-    '#type' => 'select', '#title' => t('Discard items older than'),
-    '#default_value' => variable_get('aggregator_clear', 9676800), '#options' => $period,
-    '#description' => t('The length of time to retain feed items before discarding. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status'))),
-  );
-
-  $form['aggregator_category_selector'] = array(
-    '#type' => 'radios', '#title' => t('Category selection type'), '#default_value' => variable_get('aggregator_category_selector', 'checkboxes'),
-    '#options' => array('checkboxes' => t('checkboxes'), 'select' => t('multiple selector')),
-    '#description' => t('The type of category selection widget displayed on categorization pages. (For a small number of categories, checkboxes are easier to use, while a multiple selector works well with large numbers of categories.)'),
+  $period = array('-1' => t('none'));
+  $period += drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
+  $parsers = module_implements('aggregator_parse');
+  foreach ($parsers as $k => $v) {
+    $info = module_invoke($v, 'aggregator_parse', 'info');
+    unset($parsers[$k]);
+    $parsers[$v] = $info['title'] . ' <span class="description">' . $info['description'] .'</span>';
+  }
+  $processors = module_implements('aggregator_process');
+  foreach ($processors as $k => $v) {
+    $info = module_invoke($v, 'aggregator_process', 'info');
+    unset($processors[$k]);
+    $processors[$v] = $info['title'] . ' <span class="description">' . $info['description'] .'</span>';
+  }
+  $form['aggregator_parser'] = array(
+    '#type' => 'radios',
+    '#title' => t('Parser'),
+    '#description' => t('Parsers retrieve and parse feed data. Choose one suitable for the type of feeds you would like to aggregate.'),
+    '#options' => $parsers,
+    '#default_value' => variable_get('aggregator_parser', ''),
+  );
+  $form['aggregator_processors'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Processors'),
+    '#description' => t('Processors act on parsed feed data, for example they store feed items. Pick the processors suitable for your task.'),
+    '#options' => $processors,
+    '#default_value' => variable_get('aggregator_processors', array()),
   );
-
+  $form['modules'] = array();
+  
   return system_settings_form($form);
 }
 
@@ -507,3 +510,50 @@
     drupal_set_message(t('The category %category has been added.', array('%category' => $form_state['values']['title'])));
   }
 }
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function aggregator_form_alter(&$form, $form_state, $form_id) {
+  if ($form_id == 'aggregator_admin_settings') {
+    if (in_array('aggregator', aggregator_get_enabled_processors())) {
+      $types = node_get_types();
+      $types_select = array();
+      foreach ($types as $type) {
+        // Do not allow a content-type for both the items and the feeds
+        if (!variable_get('aggregator_feed_' . $type->type, FALSE)) {
+          $types_select[$type->type] = $type->name;
+        }
+      }
+      $info = module_invoke('aggregator', 'aggregator_process', 'info');
+      $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
+      $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items');
+      
+      $form['modules']['aggregator'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Advanced Aggregator Light settings'),
+        '#description' => $info['description'],
+        '#collapsible' => TRUE,
+        '#collapsed' => !in_array('aggregator', aggregator_get_enabled_processors()),
+      );
+      
+      $form['modules']['aggregator']['aggregator_summary_items'] = array(
+        '#type' => 'select', '#title' => t('Items shown in sources and categories pages') ,
+        '#default_value' => variable_get('aggregator_summary_items', 3), '#options' => $items,
+        '#description' => t('Number of feed items displayed in feed and category summary pages.'),
+      );
+    
+      $form['modules']['aggregator']['aggregator_clear'] = array(
+        '#type' => 'select', '#title' => t('Discard items older than'),
+        '#default_value' => variable_get('aggregator_clear', 9676800), '#options' => $period,
+        '#description' => t('The length of time to retain feed items before discarding. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status'))),
+      );
+    
+      $form['modules']['aggregator']['aggregator_category_selector'] = array(
+        '#type' => 'radios', '#title' => t('Category selection type'), '#default_value' => variable_get('aggregator_category_selector', 'checkboxes'),
+        '#options' => array('checkboxes' => t('checkboxes'), 'select' => t('multiple selector')),
+        '#description' => t('The type of category selection widget displayed on categorization pages. (For a small number of categories, checkboxes are easier to use, while a multiple selector works well with large numbers of categories.)'),
+      );
+    }
+  }
+}
Index: modules/aggregator/aggregator.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.install,v
retrieving revision 1.17
diff -u -r1.17 aggregator.install
--- modules/aggregator/aggregator.install	12 Aug 2008 07:00:48 -0000	1.17
+++ modules/aggregator/aggregator.install	6 Nov 2008 21:54:42 -0000
@@ -7,6 +7,9 @@
 function aggregator_install() {
   // Create tables.
   drupal_install_schema('aggregator');
+  // Enable default parser and processors.
+  variable_set('aggregator_parser', 'aggregator');
+  variable_set('aggregator_processors', array('aggregator'));
 }
 
 /**
Index: modules/aggregator/aggregator.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.module,v
retrieving revision 1.398
diff -u -r1.398 aggregator.module
--- modules/aggregator/aggregator.module	1 Nov 2008 19:51:06 -0000	1.398
+++ modules/aggregator/aggregator.module	6 Nov 2008 21:54:42 -0000
@@ -1,5 +1,5 @@
 <?php
-// $Id: aggregator.module,v 1.398 2008/11/01 19:51:06 dries Exp $
+// $Id: aggregator.module,v 1.396 2008/10/20 12:57:35 dries Exp $
 
 /**
  * @file
@@ -287,9 +287,22 @@
  * Checks news feeds for updates once their refresh interval has elapsed.
  */
 function aggregator_cron() {
-  $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < :time', array(':time' => REQUEST_TIME));
-  foreach ($result as $feed) {
-    aggregator_refresh((array)$feed);
+  $ready = FALSE;
+  if (drupal_function_exists('_aggregator_light_delete_expired')) {
+    _aggregator_light_delete_expired();
+  }
+  // Query the feeds which should be refreshed and do the refresh.
+  $start = REQUEST_TIME;
+  while(!$ready || _aggregator_cron_time()) {
+    $result = db_query_range('SELECT * FROM {aggregator_feed} WHERE checked < :start AND (:start - checked) > refresh ORDER BY checked', array(':start' => $start), 0, 2);
+    $feed_count = 0;
+    foreach ($result as $feed) {
+      aggregator_refresh((array)$feed);
+      ++$feed_count;
+    }
+    if ($feed_count == 0) {
+      $ready = TRUE;
+    }
   }
 }
 
@@ -368,6 +381,126 @@
 }
 
 /**
+ * Implementation of hook_aggregator_process().
+ * 
+ * @param $op
+ *   'unique' Determine which of the items in $feed['items'] are unique.
+ *   'save'   Save feed items in $feed['items'].
+ *   'info'   Metadata about the processor.
+ * @param $feed
+ *   Feed associative array. Structure: @see aggregator_parse()
+ */
+function aggregator_aggregator_process($op, $feed = NULL) {
+  switch ($op) {
+    case 'unique':
+      foreach ($feed['items'] as $k => $item) {
+        if (!empty($item['guid'])) {
+          $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND guid = :guid", array(':fid' => $feed['fid'], ':guid' => $item['guid']))->fetchObject();
+        }
+        elseif ($item['link'] && $item['link'] != $feed['link'] && $item['link'] != $feed['url']) {
+          $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND link = :link", array(':fid' => $feed['fid'], ':link' => $item['link']))->fetchObject();
+        }
+        elseif (isset($item['title'])) {
+          $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND title = :title", array(':fid' => $feed['fid'], ':title' => $item['title']))->fetchObject();
+        }
+        if (!isset($feed['items'][$k]['unique'])) {
+          $feed['items'][$k]['unique'] = array();
+        }
+        $feed['items'][$k]['unique']['aggregator'] = (!isset($entry->iid) ? TRUE : $entry->iid);
+      }
+      return $feed;
+    case 'save':
+      $new = FALSE;
+      foreach ($feed['items'] as $k => $item) {
+        $new = ($new || is_numeric($item['unique']['aggregator']) ? FALSE : TRUE);
+        if (isset($item['description']) && isset($item['title'])) {
+          aggregator_save_item(array('iid' => $item['unique']['aggregator'], 'fid' => $feed['fid'], 'timestamp' => $item['timestamp'], 'title' => $item['title'], 'link' => $item['link'], 'author' => $item['author'], 'description' => $item['description'], 'guid' => $item['guid']));
+        }
+      }
+      if ($new) {
+        watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed['title']));
+        drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title'])));
+      }
+      return $feed;
+    case 'info':
+      return array(
+        'title' => t('Aggregator Light'),
+        'description' => t('Creates lightweight records of feed items.'),
+      );
+  }
+}
+
+/**
+ * Implementation of hook_aggregator_parse().
+ * 
+ * @param $op
+ *   'parse' Parse the feed-nodes
+ *   'info' Metadata about the processor
+ * @param $feed
+ *   Feed associative array come from {aggregator} table
+ */
+function aggregator_aggregator_parse($op, $feed = NULL) {
+  switch ($op) {
+    case 'parse':
+      // Generate conditional GET headers.
+      $headers = array();
+      if ($feed['etag']) {
+        $headers['If-None-Match'] = $feed['etag'];
+      }
+      if ($feed['modified']) {
+        $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) . ' GMT';
+      }
+
+      // Request feed.
+      $result = drupal_http_request($feed['url'], $headers);
+
+      // Process HTTP response code.
+      switch ($result->code) {
+        case 304:
+          return TRUE;
+          break;
+        case 301:
+          $feed['url'] = $result->redirect_url;
+          
+          if (isset($result->redirect_url)) {
+            watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url']));
+          }
+          // Do not break here.
+        case 200:
+        case 302:
+        case 307:
+          // We store the md5 hash of feed data in the database. When refreshing a
+          // feed we compare stored hash and new hash calculated from downloaded
+          // data. If both are equal we say that feed is not updated.
+          $feed['md5'] = md5($result->data);
+          if ($feed['hash'] == $feed['md5']) {
+            return TRUE;
+          }
+          return array_merge(
+            array(
+              'md5' => $feed['md5'],
+              'modified' => empty($result->headers['Last-Modified']) ? 0 : strtotime($result->headers['Last-Modified']),
+              'etag' => empty($result->headers['ETag']) ? '' : $result->headers['ETag'],
+            ),
+            aggregator_parse_feed($result->data, $feed));
+          break;
+        default:
+          watchdog('aggregator', 'The feed from %site seems to be broken, due to "%error".', array('%site' => $feed['title'], '%error' => $result->code . ' ' . $result->error), WATCHDOG_WARNING);
+          drupal_set_message(t('The feed from %site seems to be broken, because of error "%error".', array('%site' => $feed['title'], '%error' => $result->code . ' ' . $result->error)));
+          module_invoke('system', 'check_http_request');
+          return FALSE;
+      }
+      break;
+    case 'info':
+      return array(
+        'title' => t('Built-in Parser'),
+        'description' => t('Default parser for RSS, Atom and RDF feeds.'),
+      );
+      
+  }
+}
+
+/**
  * Add/edit/delete aggregator categories.
  *
  * @param $edit
@@ -545,7 +678,7 @@
           $items[$item]['LINK'] = $attributes['HREF'];
         }
         else {
-          $channel['LINK'] = $attributes['HREF'];
+          $channel['link'] = $attributes['HREF'];
         }
       }
       break;
@@ -616,8 +749,8 @@
       break;
     case 'TAGLINE':
     case 'SUBTITLE':
-      $channel += array('DESCRIPTION' => '');
-      $channel['DESCRIPTION'] .= $data;
+      $channel += array('description' => '');
+      $channel['description'] .= $data;
       break;
     case 'INFO':
     case 'ID':
@@ -626,8 +759,8 @@
       // it or its contents will end up in the item array.
       break;
     default:
-      $channel += array($tag => '');
-      $channel[$tag] .= $data;
+      $channel += array(strtolower($tag) => '');
+      $channel[strtolower($tag)] .= $data;
   }
 }
 
@@ -638,100 +771,67 @@
  *   An associative array describing the feed to be refreshed.
  */
 function aggregator_refresh($feed) {
-  global $channel, $image;
 
-  // Generate conditional GET headers.
-  $headers = array();
-  if ($feed['etag']) {
-    $headers['If-None-Match'] = $feed['etag'];
-  }
-  if ($feed['modified']) {
-    $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) . ' GMT';
-  }
-
-  // Request feed.
-  $result = drupal_http_request($feed['url'], $headers);
-
-  // Process HTTP response code.
-  switch ($result->code) {
-    case 304:
-      db_update('aggregator_feed')
-        ->fields(array('checked' => REQUEST_TIME))
-        ->condition('fid', $feed['fid'])
-        ->execute();
-      drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
-      break;
-    case 301:
-      $feed['url'] = $result->redirect_url;
-      // Do not break here.
-    case 200:
-    case 302:
-    case 307:
-      // We store the md5 hash of feed data in the database. When refreshing a
-      // feed we compare stored hash and new hash calculated from downloaded
-      // data. If both are equal we say that feed is not updated.
-      $md5 = md5($result->data);
-      if ($feed['hash'] == $md5) {
-        db_update('aggregator_feed')
-          ->condition('fid', $feed['fid'])
-          ->fields(array('checked' => REQUEST_TIME))
-          ->execute();
-        drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
-        break;
+  $parser = variable_get('aggregator_parser', 'aggregator');
+  $channel = module_invoke($parser, 'aggregator_parse', 'parse', $feed);
+  if ($channel === TRUE) {
+    db_update('aggregator_feed')
+      ->fields(array('checked' => REQUEST_TIME))
+      ->condition('fid', $feed['fid'])
+      ->execute();
+    drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
+    return;
+  }
+  if ($channel === FALSE) {
+    return;
+  }
+  if (is_array($channel)) {
+    $channel['fid'] = $feed['fid'];
+    $channel['url'] = $feed['url'];
+    // Let implementing modules process feed items.
+    if (is_array($channel['items'])) {
+      $processors = aggregator_get_enabled_processors();
+      foreach ($processors as $processor) {
+        $channel = module_invoke($processor, 'aggregator_process', 'unique', $channel);
       }
+      foreach ($processors as $processor) {
+        $channel = module_invoke($processor, 'aggregator_process', 'save', $channel);
+      }
+    }
+    
+    $image = isset($channel['image']) ? $channel['image'] : FALSE;
 
-      // Filter the input data.
-      if (aggregator_parse_feed($result->data, $feed)) {
-        $modified = empty($result->headers['Last-Modified']) ? 0 : strtotime($result->headers['Last-Modified']);
-
-        // Prepare the channel data.
-        foreach ($channel as $key => $value) {
-          $channel[$key] = trim($value);
-        }
-
-        // Prepare the image data (if any).
-        foreach ($image as $key => $value) {
-          $image[$key] = trim($value);
-        }
-
-        if (!empty($image['LINK']) && !empty($image['URL']) && !empty($image['TITLE'])) {
-          $image = l(theme('image', $image['URL'], $image['TITLE']), $image['LINK'], array('html' => TRUE));
-        }
-        else {
-          $image = '';
-        }
-
-        $etag = empty($result->headers['ETag']) ? '' : $result->headers['ETag'];
-        // Update the feed data.
-        db_merge('aggregator_feed')
-          ->key(array('fid' => $feed['fid']))
-          ->fields(array(
-            'url' => $feed['url'],
-            'checked' => REQUEST_TIME,
-            'link' => $channel['LINK'],
-            'description' => $channel['DESCRIPTION'],
-            'image' => $image,
-            'hash' => $md5,
-            'etag' => $etag,
-            'modified' => $modified,
-          ))
-          ->execute();
+    // Prepare the image data (if any).
+    if (is_array($image)) {
+      foreach ($image as $key => $value) {
+        $image[$key] = trim($value);
+      }
+    }
 
-        // Clear the cache.
-        cache_clear_all();
+    if (!empty($image['LINK']) && !empty($image['URL']) && !empty($image['TITLE'])) {
+      $image = l(theme('image', $image['URL'], $image['TITLE']), $image['LINK'], array('html' => TRUE));
+    }
+    else {
+      $image = '';
+    }
 
-        if (isset($result->redirect_url)) {
-          watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url']));
-        }
+    // Update the feed data.
+    db_merge('aggregator_feed')
+      ->key(array('fid' => $feed['fid']))
+      ->fields(array(
+        'url' => $feed['url'],
+        'checked' => REQUEST_TIME,
+        'link' => $channel['link'],
+        'description' => $channel['description'],
+        'image' => $image,
+        'hash' => $channel['md5'],
+        'etag' => $channel['etag'],
+        'modified' => $channel['modified'],
+      ))
+      ->execute();
 
-        watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed['title']));
-        drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title'])));
-      }
-      break;
-    default:
-      watchdog('aggregator', 'The feed from %site seems to be broken, due to "%error".', array('%site' => $feed['title'], '%error' => $result->code . ' ' . $result->error), WATCHDOG_WARNING);
-      drupal_set_message(t('The feed from %site seems to be broken, because of error "%error".', array('%site' => $feed['title'], '%error' => $result->code . ' ' . $result->error)));
-      module_invoke('system', 'check_http_request');
+    // Clear the cache.
+    cache_clear_all();
   }
 }
 
@@ -783,9 +883,9 @@
  * @param $feed
  *   An associative array describing the feed to be parsed.
  * @return
- *   FALSE on error, TRUE otherwise.
+ *   FALSE on error, the parsed feed otherwise
  */
-function aggregator_parse_feed(&$data, $feed) {
+function aggregator_parse_feed($data, $feed) {
   global $items, $image, $channel;
 
   // Unset the global variables before we use them.
@@ -793,6 +893,7 @@
   $items = array();
   $image = array();
   $channel = array();
+  $channel['items'] = array();
 
   // Parse the data.
   $xml_parser = drupal_xml_parser_create($data);
@@ -810,97 +911,61 @@
   // item first. In the database, the newest item should be at the top.
   $items = array_reverse($items);
 
-  // Initialize variables.
-  $title = $link = $author = $description = $guid = NULL;
   foreach ($items as $item) {
-    unset($title, $link, $author, $description, $guid);
 
     // Prepare the item:
     foreach ($item as $key => $value) {
-      $item[$key] = trim($value);
+      $item[strtolower($key)] = trim($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 (!empty($item['TITLE'])) {
-      $title = $item['TITLE'];
-    }
-    elseif (!empty($item['DESCRIPTION'])) {
-      $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['DESCRIPTION'], 40));
-    }
-    else {
-      $title = '';
+    if (empty($item['title']) && !empty($item['description'])) {
+      $item['title'] = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['description'], 40));
     }
 
     // Resolve the items link.
-    if (!empty($item['LINK'])) {
-      $link = $item['LINK'];
+    if (empty($item['link'])) {
+      $item['link'] = $feed['link'];
     }
-    else {
-      $link = $feed['link'];
-    }
-    $guid = isset($item['GUID']) ? $item['GUID'] : '';
+    $item['guid'] = isset($item['guid']) ? $item['guid'] : '';
 
     // Atom feeds have a CONTENT and/or SUMMARY tag instead of a DESCRIPTION tag.
-    if (!empty($item['CONTENT:ENCODED'])) {
-      $item['DESCRIPTION'] = $item['CONTENT:ENCODED'];
+    if (!empty($item['content:encoded'])) {
+      $item['description'] = $item['content:encoded'];
     }
-    elseif (!empty($item['SUMMARY'])) {
-      $item['DESCRIPTION'] = $item['SUMMARY'];
+    elseif (!empty($item['summary'])) {
+      $item['description'] = $item['summary'];
     }
-    elseif (!empty($item['CONTENT'])) {
-      $item['DESCRIPTION'] = $item['CONTENT'];
+    elseif (!empty($item['content'])) {
+      $item['description'] = $item['content'];
     }
 
     // Try to resolve and parse the item's publication date.
     $date = '';
-    foreach (array('PUBDATE', 'DC:DATE', 'DCTERMS:ISSUED', 'DCTERMS:CREATED', 'DCTERMS:MODIFIED', 'ISSUED', 'CREATED', 'MODIFIED', 'PUBLISHED', 'UPDATED') as $key) {
+    foreach (array('pubdate', 'dc:date', 'dcterms:issued', 'dcterms:created', 'dcterms:modified', 'issued', 'created', 'modified', 'published', 'updated') as $key) {
       if (!empty($item[$key])) {
         $date = $item[$key];
         break;
       }
     }
 
-    $timestamp = strtotime($date);
-
-    if ($timestamp === FALSE) {
-      $timestamp = aggregator_parse_w3cdtf($date); // Aggregator_parse_w3cdtf() returns FALSE on failure.
-    }
+    $item['timestamp'] = strtotime($date);
 
-    // Save this item. Try to avoid duplicate entries as much as possible. If
-    // we find a duplicate entry, we resolve it and pass along its ID is such
-    // that we can update it if needed.
-    if (!empty($guid)) {
-      $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND guid = :guid", array(':fid' => $feed['fid'], ':guid' => $guid))->fetchObject();
-    }
-    elseif ($link && $link != $feed['link'] && $link != $feed['url']) {
-      $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND link = :link", array(':fid' => $feed['fid'], ':link' => $link))->fetchObject();
+    if ($item['timestamp'] === FALSE) {
+      $item['timestamp'] = aggregator_parse_w3cdtf($date); // Aggregator_parse_w3cdtf() returns FALSE on failure.
     }
     else {
-      $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND title = :title", array(':fid' => $feed['fid'], ':title' => $title))->fetchObject();
-    }
-
-    if (!$timestamp) {
-      $timestamp = isset($entry->timestamp) ? $entry->timestamp : REQUEST_TIME;
+      $item['timestamp'] = REQUEST_TIME;
     }
-    $item += array('AUTHOR' => '', 'DESCRIPTION' => '');
-    aggregator_save_item(array('iid' => (isset($entry->iid) ? $entry->iid : ''), 'fid' => $feed['fid'], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION'], 'guid' => $guid));
+    $item += array('author' => '');
+    $channel['items'][] = $item;
   }
-
-  // Remove all items that are older than flush item timer.
-  $age = REQUEST_TIME - variable_get('aggregator_clear', 9676800);
-  $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid AND timestamp < :timestamp', array(':fid' => $feed['fid'], ':timestamp' => $age))->fetchCol();
-  if ($iids) {
-    db_delete('aggregator_category_item')
-      ->condition('iid', $iids, 'IN')
-      ->execute();
-    db_delete('aggregator_item')
-      ->condition('iid', $iids, 'IN')
-      ->execute();
+  if (!empty($image)) {
+    $channel['image'] = $image;
   }
-
-  return TRUE;
+  return $channel;
 }
 
 /**
@@ -910,7 +975,7 @@
  *   An associative array describing the item to be added/edited/deleted.
  */
 function aggregator_save_item($edit) {
-  if ($edit['title'] && empty($edit['iid'])) {
+  if ($edit['title'] && $edit['iid'] === TRUE) {
     $edit['iid'] = db_insert('aggregator_item')
       ->fields(array(
         'title' => $edit['title'],
@@ -963,6 +1028,34 @@
 }
 
 /**
+ * Returns the enabled parser.
+ * 
+ * @return 
+ *   A string that is the name of the module that implements the currently 
+ *   enabled parser.
+ */
+function aggregator_get_enabled_parser() {
+  return variable_get('aggregator_parser', '');
+}
+
+/**
+ * Returns enabled processors.
+ * 
+ * @return 
+ *   An array of strings that are the names of the modules that implement the
+ *   currently enabled processors.
+ */
+function aggregator_get_enabled_processors() {
+  $processors = variable_get('aggregator_processors', array());
+  foreach ($processors as $k => $v) {
+    if ($v === 0) {
+      unset($processors[$k]);
+    }
+  }
+  return $processors;
+}
+
+/**
  * Load an aggregator category.
  *
  * @param $cid
@@ -991,7 +1084,7 @@
  * @ingroup themeable
  */
 function theme_aggregator_block_item($item, $feed = 0) {
-
+  
   // Display the external link to the item.
   $output .= '<a href="' . check_url($item->link) . '">' . check_plain($item->title) . "</a>\n";
 
@@ -1021,3 +1114,28 @@
 function _aggregator_items($count) {
   return format_plural($count, '1 item', '@count items');
 }
+
+/**
+ * Checks for time limits in cron processing.
+ */
+function _aggregator_cron_time() {
+  static $time_limit;
+  $execute_percentage = 0.5;
+  if (!$time_limit) {
+    $time_limit = REQUEST_TIME + ($execute_percentage / 100) * ini_get('max_execution_time');
+    // However, check for left time, maybe some other cron processing already occured.
+    $time_limit = min($time_limit, variable_get('cron_semaphore', 0) + ini_get('max_execution_time'));
+  }
+  return max($time_limit - time(), 0);
+}
+
+function _aggregator_light_delete_expired() {
+    // Remove all items that are older than flush item timer.
+  $age = REQUEST_TIME - variable_get('aggregator_clear', 9676800);
+  $iids = db_query('SELECT iid FROM {aggregator_item} WHERE timestamp < :timestamp', array(':timestamp' => $age))->fetchCol();
+  if ($iids) {
+    db_delete('aggregator_category_item')
+      ->condition('iid', $iids, 'IN')
+      ->execute();
+  }
+}
