Index: modules/aggregator/aggregator-feed-source.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator-feed-source.tpl.php,v
retrieving revision 1.2
diff -u -p -r1.2 aggregator-feed-source.tpl.php
--- modules/aggregator/aggregator-feed-source.tpl.php	15 May 2008 21:27:32 -0000	1.2
+++ modules/aggregator/aggregator-feed-source.tpl.php	15 Jul 2008 20:59:30 -0000
@@ -21,8 +21,8 @@
  */
 ?>
 <div class="feed-source">
-  <?php print $source_icon; ?>
-  <?php print $source_image; ?>
+  <?php // @todo print $source_icon; ?>
+  <?php // @todo print $source_image; ?>
   <div class="feed-description">
     <?php print $source_description; ?>
   </div>
Index: modules/aggregator/aggregator-item.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator-item.tpl.php,v
retrieving revision 1.2
diff -u -p -r1.2 aggregator-item.tpl.php
--- modules/aggregator/aggregator-item.tpl.php	15 May 2008 21:27:32 -0000	1.2
+++ modules/aggregator/aggregator-item.tpl.php	15 Jul 2008 20:59:30 -0000
@@ -37,9 +37,9 @@
   </div>
 <?php endif; ?>
 
-<?php if ($categories) : ?>
-  <div class="feed-item-categories">
-    <?php print t('Categories'); ?>: <?php print implode(', ', $categories); ?>
+<?php if ($terms) : ?>
+  <div class="feed-item-terms">
+    <?php print t('Terms'); ?>: <?php print implode(', ', $terms); ?>
   </div>
 <?php endif ;?>
 
Index: modules/aggregator/aggregator.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.admin.inc,v
retrieving revision 1.10
diff -u -p -r1.10 aggregator.admin.inc
--- modules/aggregator/aggregator.admin.inc	15 May 2008 21:27:32 -0000	1.10
+++ modules/aggregator/aggregator.admin.inc	15 Jul 2008 20:59:30 -0000
@@ -3,349 +3,74 @@
 
 /**
  * @file
- * Admin page callbacks for the aggregator module.
+ *   Administrative form for aggregator.
  */
 
 /**
- * Menu callback; displays the aggregator administration page.
+ * Provides a content-type tab form.
+ * Processors and parsers should do form_alter() to provide their settings here
  */
-function aggregator_admin_overview() {
-  return aggregator_view();
-}
-
-/**
- * Displays the aggregator administration page.
- *
- * @return
- *   The page HTML.
- */
-function aggregator_view() {
-  $result = db_query('SELECT f.*, COUNT(i.iid) AS items FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.etag, f.modified, f.image, f.block ORDER BY f.title');
-
-  $output = '<h3>' . t('Feed overview') . '</h3>';
-
-  $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3'));
-  $rows = array();
-  while ($feed = db_fetch_object($result)) {
-    $rows[] = array(l($feed->title, "aggregator/sources/$feed->fid"), format_plural($feed->items, '1 item', '@count items'), ($feed->checked ? t('@time ago', array('@time' => format_interval(time() - $feed->checked))) : t('never')), ($feed->checked ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - time()))) : t('never')), l(t('edit'), "admin/content/aggregator/edit/feed/$feed->fid"), l(t('remove items'), "admin/content/aggregator/remove/$feed->fid"), l(t('update items'), "admin/content/aggregator/update/$feed->fid"));
-  }
-  $output .= theme('table', $header, $rows);
-
-  $result = db_query('SELECT c.cid, c.title, count(ci.iid) as items FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid GROUP BY c.cid, c.title ORDER BY title');
-
-  $output .= '<h3>' . t('Category overview') . '</h3>';
-
-  $header = array(t('Title'), t('Items'), t('Operations'));
-  $rows = array();
-  while ($category = db_fetch_object($result)) {
-    $rows[] = array(l($category->title, "aggregator/categories/$category->cid"), format_plural($category->items, '1 item', '@count items'), l(t('edit'), "admin/content/aggregator/edit/category/$category->cid"));
-  }
-  $output .= theme('table', $header, $rows);
-
-  return $output;
-}
-
-/**
- * Form builder; Generate a form to add/edit feed sources.
- *
- * @ingroup forms
- * @see aggregator_form_feed_validate()
- * @see aggregator_form_feed_submit()
- */
-function aggregator_form_feed(&$form_state, $edit = array('refresh' => 900, 'title' => '', 'url' => '', 'fid' => NULL)) {
-  $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
-
-  if ($edit['refresh'] == '') {
-    $edit['refresh'] = 3600;
-  }
-
-  $form['title'] = array('#type' => 'textfield',
-    '#title' => t('Title'),
-    '#default_value' => $edit['title'],
-    '#maxlength' => 255,
-    '#description' => t('The name of the feed (or the name of the website providing the feed).'),
-    '#required' => TRUE,
-  );
-  $form['url'] = array('#type' => 'textfield',
-    '#title' => t('URL'),
-    '#default_value' => $edit['url'],
-    '#maxlength' => 255,
-    '#description' => t('The fully-qualified URL of the feed.'),
-    '#required' => TRUE,
-  );
-  $form['refresh'] = array('#type' => 'select',
-    '#title' => t('Update interval'),
-    '#default_value' => $edit['refresh'],
-    '#options' => $period,
-    '#description' => t('The length of time between feed updates. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status'))),
+function aggregator_settings_form($form_state) {
+  $type = arg(3);
+  $form = array();
+  $parsers = module_implements('aggregator_parse');
+  foreach ($parsers as $k => $v) {
+    $info = module_invoke($v, 'aggregator_parse', 'info');
+    unset($parsers[$k]);
+    $parsers[$v] = $info['title'];
+  }
+  $processors = module_implements('aggregator_process');
+  foreach ($processors as $k => $v) {
+    $info = module_invoke($v, 'aggregator_process', 'info');
+    unset($processors[$k]);
+    $processors[$v] = $info['title'];
+  }
+  $form['type'] = array('#type' => 'value', '#value' => $type);  // For other modules' form_alter().
+  $form['aggregator_feed_' . $type] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Is a feed content type'),
+    '#description' => t('Check if you would like to use this content type for downloading feeds to your site.'),
+    '#default_value' => variable_get('aggregator_feed_' . $type, FALSE),
+    '#weight' => -15,
+  );
+  // @todo How to assign description for each radio button?
+  $form['aggregator_parser_' . $type] = array(
+    '#type' => 'radios',
+    '#title' => t('Parser'),
+    '#description' => t('Parsers are responsible for retrieving and parsing feed data.'),
+    '#options' => $parsers,
+    '#default_value' => variable_get('aggregator_parser_' . $type, array_pop($parsers)),
+  );
+  // @todo: How to assign description for each checkbox?
+  $form['aggregator_processor_' . $type] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Processors'),
+    '#description' => t('Processors act on parsed feed data. Pick the processors suitable for your task.'),
+    '#options' => $processors,
+    '#default_value' => variable_get('aggregator_processor_' . $type, array_slice($processors, 0, 1)),
+  );
+  $form['processor'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Advanced configuration of processors'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['parser'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Advanced configuration of parsers'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
-
-  // Handling of categories.
-  $options = array();
-  $values = array();
-  $categories = db_query('SELECT c.cid, c.title, f.fid FROM {aggregator_category} c LEFT JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = %d ORDER BY title', $edit['fid']);
-  while ($category = db_fetch_object($categories)) {
-    $options[$category->cid] = check_plain($category->title);
-    if ($category->fid) $values[] = $category->cid;
-  }
-  if ($options) {
-    $form['category'] = array(
-      '#type' => 'checkboxes',
-      '#title' => t('Categorize news items'),
-      '#default_value' => $values,
-      '#options' => $options,
-      '#description' => t('New feed items are automatically filed in the checked categories.'),
-    );
-  }
   $form['submit'] = array(
     '#type' => 'submit',
-    '#value' => t('Save'),
+    '#value' => t('Save configuration'),
   );
-
-  if ($edit['fid']) {
-    $form['delete'] = array(
-      '#type' => 'submit',
-      '#value' => t('Delete'),
-    );
-    $form['fid'] = array(
-      '#type' => 'hidden',
-      '#value' => $edit['fid'],
-    );
-  }
-
   return $form;
 }
 
 /**
- * Validate aggregator_form_feed() form submissions.
- */
-function aggregator_form_feed_validate($form, &$form_state) {
-  if ($form_state['values']['op'] == t('Save')) {
-    // Ensure URL is valid.
-    if (!valid_url($form_state['values']['url'], TRUE)) {
-      form_set_error('url', t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $form_state['values']['url'])));
-    }
-    // Check for duplicate titles.
-    if (isset($form_state['values']['fid'])) {
-      $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE (title = '%s' OR url = '%s') AND fid <> %d", $form_state['values']['title'], $form_state['values']['url'], $form_state['values']['fid']);
-    }
-    else {
-      $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = '%s' OR url = '%s'", $form_state['values']['title'], $form_state['values']['url']);
-    }
-    while ($feed = db_fetch_object($result)) {
-      if (strcasecmp($feed->title, $form_state['values']['title']) == 0) {
-        form_set_error('title', t('A feed named %feed already exists. Please enter a unique title.', array('%feed' => $form_state['values']['title'])));
-      }
-      if (strcasecmp($feed->url, $form_state['values']['url']) == 0) {
-        form_set_error('url', t('A feed with this URL %url already exists. Please enter a unique URL.', array('%url' => $form_state['values']['url'])));
-      }
-    }
-  }
-}
-
-/**
- * Process aggregator_form_feed() form submissions.
- *
- * @todo Add delete confirmation dialog.
+ * Stores the values in the {variable} table.
  */
-function aggregator_form_feed_submit($form, &$form_state) {
-  if ($form_state['values']['op'] == t('Delete')) {
-    $title = $form_state['values']['title'];
-    // Unset the title.
-    unset($form_state['values']['title']);
-  }
-  aggregator_save_feed($form_state['values']);
-  if (isset($form_state['values']['fid'])) {
-    if (isset($form_state['values']['title'])) {
-      drupal_set_message(t('The feed %feed has been updated.', array('%feed' => $form_state['values']['title'])));
-      if (arg(0) == 'admin') {
-        $form_state['redirect'] = 'admin/content/aggregator/';
-        return;
-      }
-      else {
-        $form_state['redirect'] = 'aggregator/sources/' . $form_state['values']['fid'];
-        return;
-      }
-    }
-    else {
-      watchdog('aggregator', 'Feed %feed deleted.', array('%feed' => $title));
-      drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => $title)));
-      if (arg(0) == 'admin') {
-        $form_state['redirect'] = 'admin/content/aggregator/';
-        return;
-      }
-      else {
-        $form_state['redirect'] = 'aggregator/sources/';
-        return;
-      }
-    }
-  }
-  else {
-    watchdog('aggregator', 'Feed %feed added.', array('%feed' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator'));
-    drupal_set_message(t('The feed %feed has been added.', array('%feed' => $form_state['values']['title'])));
-  }
-}
-
-function aggregator_admin_remove_feed($form_state, $feed) {
-  return confirm_form(
-    array(
-      'feed' => array(
-        '#type' => 'value',
-        '#value' => $feed,
-      ),
-    ),
-    t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed['title'])),
-    'admin/content/aggregator',
-    t('This action cannot be undone.'),
-    t('Remove items'),
-    t('Cancel')
-  );
-}
-
-/**
- * Remove all items from a feed and redirect to the overview page.
- *
- * @param $feed
- *   An associative array describing the feed to be cleared.
- */
-function aggregator_admin_remove_feed_submit($form, &$form_state) {
-  aggregator_remove($form_state['values']['feed']);
-  $form_state['redirect'] = 'admin/content/aggregator';
-}
-
-/**
- * Menu callback; refreshes a feed, then redirects to the overview page.
- *
- * @param $feed
- *   An associative array describing the feed to be refreshed.
- */
-function aggregator_admin_refresh_feed($feed) {
-  aggregator_refresh($feed);
-  drupal_goto('admin/content/aggregator');
-}
-
-/**
- * Form builder; Configure the aggregator system.
- *
- * @ingroup forms
- * @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 work well with large numbers of categories.)'),
-  );
-
-  return system_settings_form($form);
-}
-
-/**
- * Form builder; Generate a form to add/edit/delete aggregator categories.
- *
- * @ingroup forms
- * @see aggregator_form_category_validate()
- * @see aggregator_form_category_submit()
- */
-function aggregator_form_category(&$form_state, $edit = array('title' => '', 'description' => '', 'cid' => NULL)) {
-  $form['title'] = array('#type' => 'textfield',
-    '#title' => t('Title'),
-    '#default_value' => $edit['title'],
-    '#maxlength' => 64,
-    '#required' => TRUE,
-  );
-  $form['description'] = array('#type' => 'textarea',
-    '#title' => t('Description'),
-    '#default_value' => $edit['description'],
-  );
-  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
-
-  if ($edit['cid']) {
-    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
-    $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
-  }
-
-  return $form;
-}
-
-/**
- * Validate aggregator_form_feed form submissions.
- */
-function aggregator_form_category_validate($form, &$form_state) {
-  if ($form_state['values']['op'] == t('Save')) {
-    // Check for duplicate titles
-    if (isset($form_state['values']['cid'])) {
-      $category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s' AND cid <> %d", $form_state['values']['title'], $form_state['values']['cid']));
-    }
-    else {
-      $category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s'", $form_state['values']['title']));
-    }
-    if ($category) {
-      form_set_error('title', t('A category named %category already exists. Please enter a unique title.', array('%category' => $form_state['values']['title'])));
-    }
-  }
-}
-
-/**
- * Process aggregator_form_category form submissions.
- *
- * @todo Add delete confirmation dialog.
- */
-function aggregator_form_category_submit($form, &$form_state) {
-  if ($form_state['values']['op'] == t('Delete')) {
-    $title = $form_state['values']['title'];
-    // Unset the title.
-    unset($form_state['values']['title']);
-  }
-  aggregator_save_category($form_state['values']);
-  if (isset($form_state['values']['cid'])) {
-    if (isset($form_state['values']['title'])) {
-      drupal_set_message(t('The category %category has been updated.', array('%category' => $form_state['values']['title'])));
-      if (arg(0) == 'admin') {
-        $form_state['redirect'] = 'admin/content/aggregator/';
-        return;
-      }
-      else {
-        $form_state['redirect'] = 'aggregator/categories/' . $form_state['values']['cid'];
-        return;
-      }
-    }
-    else {
-      watchdog('aggregator', 'Category %category deleted.', array('%category' => $title));
-      drupal_set_message(t('The category %category has been deleted.', array('%category' => $title)));
-      if (arg(0) == 'admin') {
-        $form_state['redirect'] = 'admin/content/aggregator/';
-        return;
-      }
-      else {
-        $form_state['redirect'] = 'aggregator/categories/';
-        return;
-      }
-    }
-  }
-  else {
-    watchdog('aggregator', 'Category %category added.', array('%category' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator'));
-    drupal_set_message(t('The category %category has been added.', array('%category' => $form_state['values']['title'])));
-  }
+function aggregator_settings_form_submit($form, &$form_state) {
+  system_settings_form_submit($form, $form_state);
 }
Index: modules/aggregator/aggregator.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.install,v
retrieving revision 1.16
diff -u -p -r1.16 aggregator.install
--- modules/aggregator/aggregator.install	15 May 2008 21:27:32 -0000	1.16
+++ modules/aggregator/aggregator.install	15 Jul 2008 20:59:30 -0000
@@ -15,123 +15,61 @@ function aggregator_install() {
 function aggregator_uninstall() {
   // Remove tables.
   drupal_uninstall_schema('aggregator');
+  
+}
 
-  variable_del('aggregator_allowed_html_tags');
-  variable_del('aggregator_summary_items');
-  variable_del('aggregator_clear');
-  variable_del('aggregator_category_selector');
+/**
+ * Implementation of hook_enable().
+ */
+function aggregator_enable() {
+  $modules = module_implements('aggregator_parse');
+  if (!count($modules)) {
+    drupal_set_message(t('Enable at least one parser module (e. g. Syndication Parser) for using aggregator.'), 'error');
+  }
+  // Creates the content-type for the aggregator feeds.
+  if (!in_array('feed', array_keys(node_get_types()))) {
+    $type_name = 'feed';
+    $type = array(
+      'type' => $type_name,
+      'name' => st('Feed'),
+      'module' => 'node',
+      'description' => st("A feed is for aggregating syndicated content from other sites."),
+      'custom' => TRUE,
+      'modified' => TRUE,
+      'locked' => FALSE,
+      'help' => '',
+      'min_word_count' => '',
+    );
+    $type = (object) _node_type_set_defaults($type);
+    node_type_save($type);
+    variable_set('aggregator_parser_' . $type_name, 'syndication_parser');
+    variable_set('aggregator_processor_' . $type_name, unserialize('a:1:{s:10:"aggregator";s:10:"aggregator";}'));
+    variable_set('aggregator_feed_' . $type_name, 1);
+  }
 }
 
 /**
  * Implementation of hook_schema().
  */
 function aggregator_schema() {
-  $schema['aggregator_category'] = array(
-    'description' => t('Stores categories for aggregator feeds and feed items.'),
-    'fields' => array(
-      'cid'  => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => t('Primary Key: Unique aggregator category ID.'),
-      ),
-      'title' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => t('Title of the category.'),
-      ),
-      'description' => array(
-        'type' => 'text',
-        'not null' => TRUE,
-        'size' => 'big',
-        'description' => t('Description of the category'),
-      ),
-      'block' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'tiny',
-        'description' => t('The number of recent items to show within the category block.'),
-      )
-    ),
-    'primary key' => array('cid'),
-    'unique keys' => array(
-      'title' => array('title'),
-    ),
-  );
-
-  $schema['aggregator_category_feed'] = array(
-    'description' => t('Bridge table; maps feeds to categories.'),
-    'fields' => array(
-      'fid' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => t("The feed's {aggregator_feed}.fid."),
-      ),
-      'cid' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => t('The {aggregator_category}.cid to which the feed is being assigned.'),
-      )
-    ),
-    'primary key' => array('cid', 'fid'),
-    'indexes' => array(
-      'fid' => array('fid'),
-    ),
-  );
-
-  $schema['aggregator_category_item'] = array(
-    'description' => t('Bridge table; maps feed items to categories.'),
-    'fields' => array(
-      'iid' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => t("The feed item's {aggregator_item}.iid."),
-      ),
-      'cid' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => t('The {aggregator_category}.cid to which the feed item is being assigned.'),
-      )
-    ),
-    'primary key' => array('cid', 'iid'),
-    'indexes' => array(
-      'iid' => array('iid'),
-    ),
-  );
-
   $schema['aggregator_feed'] = array(
     'description' => t('Stores feeds to be parsed by the aggregator.'),
     'fields' => array(
-      'fid' => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => t('Primary Key: Unique feed ID.'),
-      ),
-      'title' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => t('Title of the feed.'),
+      'nid' => array(
+        'description' => t('The {node}.nid to which this feed belongs.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE
       ),
       'url' => array(
-        'type' => 'varchar',
-        'length' => 255,
+        'type' => 'text',
         'not null' => TRUE,
-        'default' => '',
-        'description' => t('URL to the feed.'),
+        'description' => t('URL to the feed.')
       ),
       'refresh' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
         'description' => t('How often to check for new feed items, in seconds.'),
+        'type' => 'int',
+        'default' => 1800,
       ),
       'checked' => array(
         'type' => 'int',
@@ -140,17 +78,8 @@ function aggregator_schema() {
         'description' => t('Last time feed was checked for new items, as Unix timestamp.'),
       ),
       'link' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => t('The parent website of the feed; comes from the <link> element in the feed.'),
-      ),
-      'description' => array(
         'type' => 'text',
-        'not null' => TRUE,
-        'size' => 'big',
-        'description' => t("The parent website's description; comes from the <description> element in the feed."),
+        'description' => t('The parent website of the feed.'),
       ),
       'image' => array(
         'type' => 'text',
@@ -171,21 +100,16 @@ function aggregator_schema() {
         'default' => 0,
         'description' => t('When the feed was last modified, as a Unix timestamp.'),
       ),
-      'block' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'tiny',
-        'description' => t("Number of items to display in the feed's block."),
-      )
+      'hash' => array(
+        'description' => t('Stores the hash of the parsed feed structure.'),
+        'type' => 'varchar',
+        'length' => '32'),
     ),
-    'primary key' => array('fid'),
+    'primary key' => array('nid'),
     'unique keys' => array(
-      'url'  => array('url'),
-      'title' => array('title'),
+      'url'  => array(array('url', 100)),
     ),
   );
-
   $schema['aggregator_item'] = array(
     'description' => t('Stores the individual items imported from feeds.'),
     'fields' => array(
@@ -194,11 +118,11 @@ function aggregator_schema() {
         'not null' => TRUE,
         'description' => t('Primary Key: Unique ID for feed item.'),
       ),
-      'fid' => array(
+      'nid' => array(
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
-        'description' => t('The {aggregator_feed}.fid to which this item belongs.'),
+        'description' => t('The {node}.nid to which this item belongs.'),
       ),
       'title' => array(
         'type' => 'varchar',
@@ -230,18 +154,18 @@ function aggregator_schema() {
       'timestamp' => array(
         'type' => 'int',
         'not null' => FALSE,
-        'description' => t('Posted date of the feed item, as a Unix timestamp.'),
+        'description' => t('Post date of feed item, as a Unix timestamp.'),
       ),
       'guid' => array(
         'type' => 'varchar',
         'length' => 255,
         'not null' => FALSE,
         'description' => t('Unique identifier for the feed item.'),
-      )
+      ),
     ),
     'primary key' => array('iid'),
     'indexes' => array(
-      'fid' => array('fid'),
+      'nid' => array('nid')
     ),
   );
 
Index: modules/aggregator/aggregator.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.module,v
retrieving revision 1.382
diff -u -p -r1.382 aggregator.module
--- modules/aggregator/aggregator.module	5 Jul 2008 05:57:00 -0000	1.382
+++ modules/aggregator/aggregator.module	15 Jul 2008 20:59:31 -0000
@@ -16,14 +16,6 @@ function aggregator_help($path, $arg) {
       $output .= '<p>' . t('Feeds contain feed items, or individual posts published by the site providing the feed. Feeds may be grouped in categories, generally by topic. Users view feed items in the <a href="@aggregator">main aggregator display</a> or by <a href="@aggregator-sources">their source</a>. Administrators can <a href="@feededit">add, edit and delete feeds</a> and choose how often to check each feed for newly updated items. The most recent items in either a feed or category can be displayed as a block through the <a href="@admin-block">blocks administration page</a>. A <a href="@aggregator-opml">machine-readable OPML file</a> of all feeds is available. A correctly configured <a href="@cron">cron maintenance task</a> is required to update feeds automatically.', array('@aggregator' => url('aggregator'), '@aggregator-sources' => url('aggregator/sources'), '@feededit' => url('admin/content/aggregator'), '@admin-block' => url('admin/build/block'), '@aggregator-opml' => url('aggregator/opml'), '@cron' => url('admin/reports/status'))) . '</p>';
       $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@aggregator">Aggregator module</a>.', array('@aggregator' => 'http://drupal.org/handbook/modules/aggregator/')) . '</p>';
       return $output;
-    case 'admin/content/aggregator':
-      $output = '<p>' . t('Thousands of sites (particularly news sites and blogs) publish their latest headlines and posts in feeds, using a number of standardized XML-based formats. Formats supported by the aggregator include <a href="@rss">RSS</a>, <a href="@rdf">RDF</a>, and <a href="@atom">Atom</a>.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'http://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) . '</p>';
-      $output .= '<p>' . t('Current feeds are listed below, and <a href="@addfeed">new feeds may be added</a>. For each feed or feed category, the <em>latest items</em> block may be enabled at the <a href="@block">blocks administration page</a>.', array('@addfeed' => url('admin/content/aggregator/add/feed'), '@block' => url('admin/build/block'))) . '</p>';
-      return $output;
-    case 'admin/content/aggregator/add/feed':
-      return '<p>' . t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') . '</p>';
-    case 'admin/content/aggregator/add/category':
-      return '<p>' . t('Categories allow feed items from different feeds to be grouped together. For example, several sport-related feeds may belong to a category named <em>Sports</em>. Feed items may be grouped automatically (by selecting a category when creating or editing a feed) or manually (via the <em>Categorize</em> page available from feed item listings). Each category provides its own feed page and block.') . '</p>';
   }
 }
 
@@ -79,163 +71,106 @@ function aggregator_theme() {
  * Implementation of hook_menu().
  */
 function aggregator_menu() {
-  $items['admin/content/aggregator'] = array(
-    'title' => 'Feed aggregator',
-    'description' => "Configure which content your site aggregates from other sites, how often it polls them, and how they're categorized.",
-    'page callback' => 'aggregator_admin_overview',
-    'access arguments' => array('administer news feeds'),
-  );
-  $items['admin/content/aggregator/add/feed'] = array(
-    'title' => 'Add feed',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_form_feed'),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_LOCAL_TASK,
-    'parent' => 'admin/content/aggregator',
-  );
-  $items['admin/content/aggregator/add/category'] = array(
-    'title' => 'Add category',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_form_category'),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_LOCAL_TASK,
-    'parent' => 'admin/content/aggregator',
-  );
-  $items['admin/content/aggregator/remove/%aggregator_feed'] = array(
-    'title' => 'Remove items',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_admin_remove_feed', 4),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_CALLBACK,
-  );
-  $items['admin/content/aggregator/update/%aggregator_feed'] = array(
-    'title' => 'Update items',
-    'page callback' => 'aggregator_admin_refresh_feed',
-    'page arguments' => array(4),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_CALLBACK,
-  );
-  $items['admin/content/aggregator/list'] = array(
-    'title' => 'List',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -10,
-  );
-  $items['admin/content/aggregator/settings'] = array(
-    'title' => 'Settings',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_admin_settings'),
+  $types = node_get_types('types', NULL, TRUE);
+  foreach ($types as $type) {
+    $items["admin/build/node-type/$type->type/aggregator"] = array(
+      'title' => 'Feed aggregator',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('aggregator_settings_form', $type->type),
+      'access arguments' => array('administer news feeds'),
+      'type' => MENU_LOCAL_TASK,
+      'parent' => "admin/build/node-type/$type->type",
+    );
+  }
+  $items['node/%node/refresh'] = array(
+    'title' => 'Refresh',
+    'page callback' => 'aggregator_refresh_page',
+    'page arguments' => array(1),
     'type' => MENU_LOCAL_TASK,
-    'weight' => 10,
-    'access arguments' => array('administer news feeds'),
+    'access callback' => '_aggregator_perm_refresh',
+    'access arguments' => array(1),
   );
   $items['aggregator'] = array(
     'title' => 'Feed aggregator',
     'page callback' => 'aggregator_page_last',
-    'access arguments' => array('access news feeds'),
+    'access arguments' => array('access content'),
     'weight' => 5,
   );
   $items['aggregator/sources'] = array(
     'title' => 'Sources',
     'page callback' => 'aggregator_page_sources',
-    'access arguments' => array('access news feeds'),
+    'access arguments' => array('access content'),
   );
-  $items['aggregator/categories'] = array(
-    'title' => 'Categories',
-    'page callback' => 'aggregator_page_categories',
-    'access callback' => '_aggregator_has_categories',
+    $items['aggregator/sources/%node'] = array(
+    'page callback' => 'aggregator_page_source',
+    'page arguments' => array(2),
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
   );
+  $items['aggregator/terms'] = array(
+    'title' => 'Terms',
+    'page callback' => 'aggregator_page_terms',
+    'access arguments' => array('access content'),
+  );
+  $items['aggregator/terms/%'] = array(
+    'title' => 'Terms',
+    'page callback' => 'aggregator_page_terms',
+    'page arguments' => array(2),
+    'access arguments' => array('access content'),
+  );
+  // @todo: implement
   $items['aggregator/rss'] = array(
     'title' => 'RSS feed',
     'page callback' => 'aggregator_page_rss',
     'access arguments' => array('access news feeds'),
     'type' => MENU_CALLBACK,
   );
+  // @todo: implement
   $items['aggregator/opml'] = array(
     'title' => 'OPML feed',
     'page callback' => 'aggregator_page_opml',
     'access arguments' => array('access news feeds'),
     'type' => MENU_CALLBACK,
   );
-  $items['aggregator/categories/%aggregator_category'] = array(
-    'title callback' => '_aggregator_category_title',
-    'title arguments' => array(2),
-    'page callback' => 'aggregator_page_category',
-    'page arguments' => array(2),
-    'access callback' => 'user_access',
-    'access arguments' => array('access news feeds'),
-  );
-  $items['aggregator/categories/%aggregator_category/view'] = array(
-    'title' => 'View',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -10,
-  );
-  $items['aggregator/categories/%aggregator_category/categorize'] = array(
-    'title' => 'Categorize',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_page_category', 2),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_LOCAL_TASK,
-  );
-  $items['aggregator/categories/%aggregator_category/configure'] = array(
-    'title' => 'Configure',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_form_category', 2),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_LOCAL_TASK,
-    'weight' => 1,
-  );
-  $items['aggregator/sources/%aggregator_feed'] = array(
-    'page callback' => 'aggregator_page_source',
-    'page arguments' => array(2),
-    'access arguments' => array('access news feeds'),
-    'type' => MENU_CALLBACK,
-  );
-  $items['aggregator/sources/%aggregator_feed/view'] = array(
-    'title' => 'View',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -10,
-  );
-  $items['aggregator/sources/%aggregator_feed/categorize'] = array(
-    'title' => 'Categorize',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_page_source', 2),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_LOCAL_TASK,
-  );
-  $items['aggregator/sources/%aggregator_feed/configure'] = array(
-    'title' => 'Configure',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_form_feed', 2),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_LOCAL_TASK,
-    'weight' => 1,
-  );
-  $items['admin/content/aggregator/edit/feed/%aggregator_feed'] = array(
-    'title' => 'Edit feed',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_form_feed', 5),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_CALLBACK,
-  );
-  $items['admin/content/aggregator/edit/category/%aggregator_category'] = array(
-    'title' => 'Edit category',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('aggregator_form_category', 5),
-    'access arguments' => array('administer news feeds'),
-    'type' => MENU_CALLBACK,
-  );
 
   return $items;
 }
 
 /**
- * Menu callback.
- *
- * @return
- *   An aggregator category title.
+ * Implementation of hook_nodeapi().
  */
-function _aggregator_category_title($category) {
-  return $category['title'];
+function aggregator_nodeapi(&$node, $op, $teaser, $page) {
+  if (isset($node->feed) || variable_get('aggregator_feed_' . $node->type, FALSE)) {
+    switch ($op) {
+      case 'presave':
+        $node = aggregator_feed_create($node->url, $node->type, $node);
+        $nodes_list = array($node);
+        aggregator_feed_retrieve($nodes_list);
+        $node = $nodes_list[0];
+        if (empty($node->title)) {
+          $node->title = $node->feed->title;
+        }
+        if (empty($node->body)) {
+          $node->body = $node->feed->description;
+        }
+        break;
+      case 'insert':
+        aggregator_feed_save($node);
+        break;
+      case 'load':
+        $node->feed = db_fetch_object(db_query("SELECT * FROM {aggregator_feed} WHERE nid = %d", $node->nid));
+        break;
+      case 'delete':
+        db_query("DELETE FROM {aggregator_feed} WHERE nid = %d", $node->nid);
+        break;
+    }
+  }
+  if (aggregator_is_enabled('aggregator', $node->type)) {
+    switch ($op) {
+      case 'delete':
+        db_query('DELETE FROM {aggregator_item} WHERE nid = %d', $node->nid);
+    }
+  }
 }
 
 /**
@@ -246,34 +181,40 @@ function aggregator_init() {
 }
 
 /**
- * Find out whether there are any aggregator categories.
- *
- * @return
- *   TRUE if there is at least one category and the user has access to them, FALSE otherwise.
- */
-function _aggregator_has_categories() {
-  return user_access('access news feeds') && db_result(db_query('SELECT COUNT(*) FROM {aggregator_category}'));
-}
-
-/**
- * Implementation of hook_perm().
- */
-function aggregator_perm() {
-  return array(
-    'administer news feeds' => t('Add, edit or delete news feeds that are aggregated to your site.'),
-    'access news feeds' => t('View aggregated news feed items.'),
-  );
-}
-
-/**
  * Implementation of hook_cron().
  *
  * Checks news feeds for updates once their refresh interval has elapsed.
+ * Deletes expired items.
  */
 function aggregator_cron() {
-  $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < %d', time());
-  while ($feed = db_fetch_array($result)) {
-    aggregator_refresh($feed);
+  $ready = FALSE;
+  $types = node_get_types();
+  foreach ($types as $type) {
+    if (variable_get('aggregator_feed_' . $type->type, FALSE) && aggregator_is_enabled('aggregator', $type->type)) {
+      $result = db_query("SELECT l.iid FROM {aggregator_item} l LEFT JOIN {node} n ON l.nid = n.nid WHERE n.type = '%s'", $type->type);
+      $iids = array();
+      while ($item = db_fetch_array($result)) {
+        $iids[] = $item['iid'];
+      }
+      if (count($iids) > 0) {
+        $age = variable_get('aggregator_clear_' . $type->type, 9676800);
+        db_query('DELETE FROM {aggregator_item} WHERE iid IN (' . implode(', ', $iids) . ') AND %d - timestamp > %d', time(), $age);
+      }
+    }
+  }
+  $start = time();
+  while(_aggregator_cron_time() || !$ready) {
+    $result = db_query_range("SELECT nid FROM {aggregator_feed} WHERE checked <= %d ORDER BY checked", $start, 0, 2);
+    $nids = array();
+    while ($nid = db_fetch_array($result)) {
+      $nids[] = $nid['nid'];
+    }
+    if (count($nids) == 0) {
+      $ready = TRUE;
+    }
+    else {
+      aggregator_feed_refresh($nids);
+    }
   }
 }
 
@@ -283,389 +224,274 @@ function aggregator_cron() {
  * Generates blocks for the latest news items in each category and feed.
  */
 function aggregator_block($op = 'list', $delta = '', $edit = array()) {
-  if (user_access('access news feeds')) {
-    if ($op == 'list') {
-      $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title');
-      while ($category = db_fetch_object($result)) {
-        $block['category-' . $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title));
-      }
-      $result = db_query('SELECT fid, title FROM {aggregator_feed} ORDER BY fid');
-      while ($feed = db_fetch_object($result)) {
-        $block['feed-' . $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title));
-      }
-    }
-    elseif ($op == 'configure') {
-      list($type, $id) = explode('-', $delta);
-      if ($type == 'category') {
-        $value = db_result(db_query('SELECT block FROM {aggregator_category} WHERE cid = %d', $id));
-      }
-      else {
-        $value = db_result(db_query('SELECT block FROM {aggregator_feed} WHERE fid = %d', $id));
-      }
-      $form['block'] = array('#type' => 'select', '#title' => t('Number of news items in block'), '#default_value' => $value, '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)));
-      return $form;
-    }
-    elseif ($op == 'save') {
-      list($type, $id) = explode('-', $delta);
-      if ($type == 'category') {
-        $value = db_query('UPDATE {aggregator_category} SET block = %d WHERE cid = %d', $edit['block'], $id);
-      }
-      else {
-        $value = db_query('UPDATE {aggregator_feed} SET block = %d WHERE fid = %d', $edit['block'], $id);
-      }
-    }
-    elseif ($op == 'view') {
-      list($type, $id) = explode('-', $delta);
-      switch ($type) {
-        case 'feed':
-          if ($feed = db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE fid = %d', $id))) {
-            $block['subject'] = check_plain($feed->title);
-            $result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = %d ORDER BY timestamp DESC, iid DESC', $feed->fid, 0, $feed->block);
-            $read_more = theme('more_link', url('aggregator/sources/' . $feed->fid), t("View this feed's recent news."));
-          }
-          break;
-
-        case 'category':
-          if ($category = db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = %d', $id))) {
-            $block['subject'] = check_plain($category->title);
-            $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = %d ORDER BY i.timestamp DESC, i.iid DESC', $category->cid, 0, $category->block);
-            $read_more = theme('more_link', url('aggregator/categories/' . $category->cid), t("View this category's recent news."));
-          }
-          break;
+  if ($op == 'list') {
+    $block = array();
+    $block['aggregator-latest']['info'] = t('Latest aggregated light feed items');
+    $terms = _aggregator_collect_terms();
+    foreach ($terms as $tid => $name) {
+      $block['aggregator-' . $tid]['info'] = t('Latest items with !term  term', array('!term' => $name));
+    }
+    return $block;
+  }
+  else if ($op == 'configure') {
+    $value = variable_get($delta . '-block', 5);
+    $form['block'] = array('#type' => 'select', '#title' => t('Number of news items in block'), '#default_value' => $value, '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)));
+    return $form;
+  }
+  else if ($op == 'save') {
+    variable_set($delta . '-block', $edit['block']);
+  }
+  else if ($op == 'view') {
+    if (user_access('access content')) {
+      $tid = array_pop(explode('-', $delta));
+      $query_args = array();
+      if ($tid == 'latest') {
+        $title = t('Latest items');
+        $query = "SELECT title, link FROM {aggregator_item} ORDER BY timestamp DESC";
+      }
+      else if(is_numeric($tid)) {
+        $term = taxonomy_get_term($tid);
+        $title = t('Latest items with !term term', array('!term' => $term->name));
+        $query = "SELECT i.title, i.link FROM {aggregator_item} i LEFT JOIN {term_node} t ON t.nid = i.nid WHERE t.tid = %d ORDER BY timestamp DESC";
+        $query_args = array($tid);
       }
+      $result = db_query_range($query, $query_args, 0, variable_get($delta . '-block', 5));
       $items = array();
       while ($item = db_fetch_object($result)) {
         $items[] = theme('aggregator_block_item', $item);
       }
-
-      // Only display the block if there are items to show.
-      if (count($items) > 0) {
-        $block['content'] = theme('item_list', $items) . $read_more;
-      }
-    }
-    if (isset($block)) {
+      $block['subject'] = check_markup($title, 4);
+      $block['content'] = theme('item_list', $items);
       return $block;
     }
   }
 }
 
 /**
- * Add/edit/delete aggregator categories.
- *
- * @param $edit
- *   An associative array describing the category to be added/edited/deleted.
+ * Implementation of hook_form_alter().
  */
-function aggregator_save_category($edit) {
-  $link_path = 'aggregator/categories/';
-  if (!empty($edit['cid'])) {
-    $link_path .= $edit['cid'];
-    if (!empty($edit['title'])) {
-      db_query("UPDATE {aggregator_category} SET title = '%s', description = '%s' WHERE cid = %d", $edit['title'], $edit['description'], $edit['cid']);
-      $op = 'update';
-    }
-    else {
-      db_query('DELETE FROM {aggregator_category} WHERE cid = %d', $edit['cid']);
-      $edit['title'] = '';
-      $op = 'delete';
-    }
+function aggregator_form_alter(&$form, $form_state, $form_id) {
+  // Alters node form in the case of aggregator-enabled content-types
+  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id && variable_get('aggregator_feed_' . $form['type']['#value'], FALSE)) {
+    $form['title']['#required'] = FALSE;
+    $url = '';
+    if (isset($form_state['values']) && $form_state['values']['url']) {
+      $url = $form_state['values']['url'];
+    }
+    else if (isset($form['#node']->feed->url)) {
+      $url = $form['#node']->feed->url;
+    }
+    $form['url'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Feed URL'),
+      '#description' => t('Enter the URL of the feed or the URL of the website where the feed can be found.'),
+      '#default_value' => $url,
+      '#maxlength' => 2048,
+      '#weight' => -10,
+      '#required' => TRUE,
+    );
+  }
+  // Content-type tab form
+  if ($form_id == 'aggregator_settings_form') {
+    $type = $form['type']['#value'];
+    $form['processor']['aggregator'] = array(
+      "#type" => 'fieldset',
+      '#title' => t('Aggregator Light processor settings'),
+      '#collapsible' => TRUE,
+      '#collapsed' => !aggregator_is_enabled('aggregator', $type),
+    );
+    $vocabs = taxonomy_get_vocabularies();
+    $vids = array();
+    $vids[0] = t('None');
+    foreach ($vocabs as $vid => $vocab) {
+      $vids[$vid] = $vocab->name;
+    }
+    $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
+    $form['processor']['aggregator']['aggregator_vid_' . $type] = array(
+      '#type' => 'select',
+      '#title' => t('Vocabulary'),
+      '#multiple' => FALSE,
+      '#options' => $vids,
+      '#description' => t('Select a vocabulary for categorizing feed items.'),
+      '#default_value' => variable_get('aggregator_vid_' . $type, 0),
+    );
+    $form['processor']['aggregator']['aggregator_clear_' . $type] = array(
+      '#type' => 'select',
+      '#title' => t('Discard items older than'),
+      '#default_value' => variable_get('aggregator_clear_' . $type, 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'))),
+    );
+    $formats = filter_formats();
+    foreach ($formats as $k => $format) {
+      $formats[$k] = $format->name;
+    }
+    $form['processor']['aggregator']['aggregator_input_filter_' . $type] = array(
+      '#type' => 'select',
+      '#title' => t('Input format for item description'),
+      '#default_value' => variable_get('aggregator_input_filter_' . $type, FILTER_FORMAT_DEFAULT),
+      '#options' => $formats,
+      '#description' => t('You can specify here the allowed elements in the feed items.'),
+    );
+    $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items');
+    $form['processor']['aggregator']['aggregator_summary_items_' . $type] = array(
+      '#type' => 'select',
+      '#title' => t('Items shown in sources and categories pages') ,
+      '#default_value' => variable_get('aggregator_summary_items_' . $type, 3),
+      '#options' => $items,
+      '#description' => t('Number of feed items displayed in feed and category summary pages.'),
+    );
+    return $form;
+  }
+}
+
+/**
+ * Implementation of hook_requirements().
+ */
+function aggregator_requirements($phase) {
+  $t = get_t();
+  $requirements['aggregagor'] = array(
+    'title' => t('Aggregator'),
+    );
+  $modules = module_implements('aggregator_parse');
+  if (count($modules)) {
+    $requirements['aggregagor']['value'] = $t('Parser module(s) installed.');
+    $requirements['aggregagor']['severity'] = REQUIREMENT_OK;
   }
-  elseif (!empty($edit['title'])) {
-    // A single unique id for bundles and feeds, to use in blocks.
-    db_query("INSERT INTO {aggregator_category} (title, description, block) VALUES ('%s', '%s', 5)", $edit['title'], $edit['description']);
-    $link_path .= db_last_insert_id('aggregator', 'cid');
-    $op = 'insert';
-  }
-  if (isset($op)) {
-    menu_link_maintain('aggregator', $op, $link_path, $edit['title']);
+  else {
+    $requirements['aggregagor']['value'] = $t('!enable_link (e. g. Syndication Parser) for using aggregator.', array('!enable_link' => l($t('Enable at least one parser module'), 'admin/build/modules')));
+    $requirements['aggregagor']['severity'] = REQUIREMENT_ERROR;
   }
+  return $requirements;
 }
 
 /**
- * Add/edit/delete an aggregator feed.
- *
- * @param $edit
- *   An associative array describing the feed to be added/edited/deleted.
- */
-function aggregator_save_feed($edit) {
-  if (!empty($edit['fid'])) {
-    // An existing feed is being modified, delete the category listings.
-    db_query('DELETE FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
-  }
-  if (!empty($edit['fid']) && !empty($edit['title'])) {
-    db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['fid']);
-  }
-  elseif (!empty($edit['fid'])) {
-    $items = array();
-    $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
-    while ($item = db_fetch_object($result)) {
-      $items[] = "iid = $item->iid";
-    }
-    if (!empty($items)) {
-      db_query('DELETE FROM {aggregator_category_item} WHERE ' . implode(' OR ', $items));
-    }
-    db_query('DELETE FROM {aggregator_feed} WHERE fid = %d', $edit['fid']);
-    db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
-  }
-  elseif (!empty($edit['title'])) {
-    db_query("INSERT INTO {aggregator_feed} (title, url, refresh, block, description, image) VALUES ('%s', '%s', %d, 5, '', '')", $edit['title'], $edit['url'], $edit['refresh']);
-    // A single unique ID for bundles and feeds, to use in blocks.
-    $edit['fid'] = db_last_insert_id('aggregator_feed', 'fid');
-  }
-  if (!empty($edit['title'])) {
-    // The feed is being saved, save the categories as well.
-    if (!empty($edit['category'])) {
-      foreach ($edit['category'] as $cid => $value) {
-        if ($value) {
-          db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $edit['fid'], $cid);
+ * Implementation of hook_aggregator_process().
+ * 
+ * @param $op
+ *   'save' The feed items should be updated or saved.
+ *   'info' Metadata about the processor
+ * @param $node
+ *   The feed-node object.
+ */
+function aggregator_aggregator_process($op, $node = NULL) {
+  switch ($op) {
+    case 'save':
+      if (is_array($node->feed->items)) {
+        $age = variable_get('aggregator_clear_' . $node->type, 9676800);
+        foreach ($node->feed->items as $item) {
+          // Avoid to create already expired items
+          if (_aggregator_unique($node->nid, $item) && time() - $item->timestamp < $age) {
+            db_query("INSERT INTO {aggregator_item} (nid, title, link, author, description, timestamp) VALUES (%d, '%s', '%s', '%s', '%s', %d)", $node->nid, $item->title, $item->link, '', $item->description, $item->timestamp);
+          }
+          else {
+            db_query("UPDATE {aggregator_item} SET title = '%s', author = '%s', description = '%s', timestamp = %d WHERE link = '%s'", $item->title, '', $item->description, $item->timestamp, $item->link);
+          }
         }
       }
-    }
+      break;
+    case 'info':
+      return array(
+      'title' => t('Aggregator Light'),
+      'description' => t('Provides lightweight item processor for Aggregator.'),
+      );
   }
 }
 
+
 /**
- * Removes all items from a feed.
+ * Downloads the given feeds, if changed, call the parsers, call the processors.
  *
- * @param $feed
- *   An associative array describing the feed to be cleared.
+ * @param $feeds
+ *   Array of nid's of the feeds or array of node objects.
  */
-function aggregator_remove($feed) {
-  $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $feed['fid']);
-  while ($item = db_fetch_object($result)) {
-    $items[] = "iid = $item->iid";
+function aggregator_feed_refresh($feeds) {
+  $feed_nodes = array();
+  foreach ($feeds as $nid) {
+    if (!is_object($nid)) {
+      $feed = node_load(array('nid' => $nid));
+    }
+    else {
+      $feed = $nid;
+    }
+    $feed_nodes[] = $feed;
   }
-  if (!empty($items)) {
-    db_query('DELETE FROM {aggregator_category_item} WHERE ' . implode(' OR ', $items));
+  aggregator_feed_retrieve($feed_nodes);
+  foreach ($feed_nodes as $node) {
+    aggregator_feed_save($node);
   }
-  db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $feed['fid']);
-  db_query("UPDATE {aggregator_feed} SET checked = 0, etag = '', modified = 0 WHERE fid = %d", $feed['fid']);
-  drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed['title'])));
 }
 
 /**
- * Callback function used by the XML parser.
+ * Builds and initializes a feed object.
+ * It does not download the feed. it just creates the basic structure
+ * 
+ * @param $url
+ *   The URL of the feed.
+ * @param $type
+ *   The content-type of the feed node, this determines the feed configuration.
+ * @return
+ *   The node-feed object 
  */
-function aggregator_element_start($parser, $name, $attributes) {
-  global $item, $element, $tag, $items, $channel;
-
-  switch ($name) {
-    case 'IMAGE':
-    case 'TEXTINPUT':
-    case 'CONTENT':
-    case 'SUMMARY':
-    case 'TAGLINE':
-    case 'SUBTITLE':
-    case 'LOGO':
-    case 'INFO':
-      $element = $name;
-      break;
-    case 'ID':
-      if ($element != 'ITEM') {
-        $element = $name;
-      }
-    case 'LINK':
-      if (!empty($attributes['REL']) && $attributes['REL'] == 'alternate') {
-        if ($element == 'ITEM') {
-          $items[$item]['LINK'] = $attributes['HREF'];
-        }
-        else {
-          $channel['LINK'] = $attributes['HREF'];
-        }
-      }
-      break;
-    case 'ITEM':
-      $element = $name;
-      $item += 1;
-      break;
-    case 'ENTRY':
-      $element = 'ITEM';
-      $item += 1;
-      break;
-  }
-
-  $tag = $name;
+function aggregator_feed_create($url, $type, $node_skeleton = NULL) {
+  $node = $node_skeleton instanceof stdClass ? $node_skeleton : new stdClass();
+  $node->feed = new stdClass();
+  $node->type = $type;
+  $node->feed->url = $url;
+  return $node;
 }
 
 /**
- * Call-back function used by the XML parser.
+ * Downloads and parses the feed. populates $feed object and add the items to it.
+ * It does not save the feed nor the items. 
+ *
+ * @param $feed_nodes
+ *   The node-feed object (come from aggregator_feed_create).
  */
-function aggregator_element_end($parser, $name) {
-  global $element;
-
-  switch ($name) {
-    case 'IMAGE':
-    case 'TEXTINPUT':
-    case 'ITEM':
-    case 'ENTRY':
-    case 'CONTENT':
-    case 'INFO':
-      $element = '';
-      break;
-    case 'ID':
-      if ($element == 'ID') {
-        $element = '';
-      }
+function aggregator_feed_retrieve(&$feed_nodes) {
+  // Collect the nodes into groups by content-type
+  $node_group = array();
+  foreach ($feed_nodes as $k => $node) {
+    if (!isset($node_group[$node->type])) {
+      $node_group[$node->type] = array();
+    }
+    $node_group[$node->type][$k] = $node;
   }
-}
-
-/**
- * Callback function used by the XML parser.
- */
-function aggregator_element_data($parser, $data) {
-  global $channel, $element, $items, $item, $image, $tag;
-  $items += array($item => array());
-  switch ($element) {
-    case 'ITEM':
-      $items[$item] += array($tag => '');
-      $items[$item][$tag] .= $data;
-      break;
-    case 'IMAGE':
-    case 'LOGO':
-      $image += array($tag => '');
-      $image[$tag] .= $data;
-      break;
-    case 'LINK':
-      if ($data) {
-        $items[$item] += array($tag => '');
-        $items[$item][$tag] .= $data;
+  foreach ($node_group as $type => $nodes) {
+    $parser = variable_get('aggregator_parser_' . $type, FALSE);
+    $parser_in = $nodes;
+    $parser_out = module_invoke($parser, 'aggregator_parse', 'parse', $parser_in);
+    foreach ($parser_out as $k => $feed) {
+      $feed_nodes[$k]->feed = $feed;
+      $feed_nodes[$k]->feed->hash = md5(serialize($feed));
+      if (isset($feed_nodes[$k]->url) && !isset($feed_nodes[$k]->feed->url)) {
+        $feed_nodes[$k]->feed->url = $feed_nodes[$k]->url;
       }
-      break;
-    case 'CONTENT':
-      $items[$item] += array('CONTENT' => '');
-      $items[$item]['CONTENT'] .= $data;
-      break;
-    case 'SUMMARY':
-      $items[$item] += array('SUMMARY' => '');
-      $items[$item]['SUMMARY'] .= $data;
-      break;
-    case 'TAGLINE':
-    case 'SUBTITLE':
-      $channel += array('DESCRIPTION' => '');
-      $channel['DESCRIPTION'] .= $data;
-      break;
-    case 'INFO':
-    case 'ID':
-    case 'TEXTINPUT':
-      // The sub-element is not supported. However, we must recognize
-      // it or its contents will end up in the item array.
-      break;
-    default:
-      $channel += array($tag => '');
-      $channel[$tag] .= $data;
+    }
   }
 }
 
 /**
- * Checks a news feed for new items.
+ * Save or update feed
+ * It becomes a node and if there are items in the object, the processors are also called
  *
- * @param $feed
- *   An associative array describing the feed to be refreshed.
+ * @param $node
+ *   The node-feed object
  */
-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';
+function aggregator_feed_save($node) {
+  $hash_array = db_fetch_array(db_query("SELECT hash FROM {aggregator_feed} WHERE nid = %d", $node->nid));
+  $hash = is_array($hash_array) ? array_pop($hash_array) : NULL;
+  if (empty($hash)) {
+    db_query("INSERT INTO {aggregator_feed} (nid, url, link, checked, hash, refresh) VALUES (%d, '%s', '%s', %d, '%s', %d)", $node->nid, $node->feed->url, $node->feed->link, time(), $node->feed->hash, time());
   }
-
-  // Request feed.
-  $result = drupal_http_request($feed['url'], $headers);
-
-  // Process HTTP response code.
-  switch ($result->code) {
-    case 304:
-      db_query('UPDATE {aggregator_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']);
-      drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
-      break;
-    case 301:
-      $feed['url'] = $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:
-      // 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'])) {
-          // TODO: we should really use theme_image() here, but that only works with
-          // local images. It won't work with images fetched with a URL unless PHP version > 5.
-          $image = '<a href="' . check_url($image['LINK']) . '" class="feed-image"><img src="' . check_url($image['URL']) . '" alt="' . check_plain($image['TITLE']) . '" /></a>';
-        }
-        else {
-          $image = NULL;
-        }
-
-        $etag = empty($result->headers['ETag']) ? '' : $result->headers['ETag'];
-        // Update the feed data.
-        db_query("UPDATE {aggregator_feed} SET url = '%s', checked = %d, link = '%s', description = '%s', image = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['url'], time(), $channel['LINK'], $channel['DESCRIPTION'], $image, $etag, $modified, $feed['fid']);
-
-        // Clear the cache.
-        cache_clear_all();
-
-        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;
-      }
-      $result->error = t('feed not parseable');
-      // Do not break here..
-    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');
+  else {
+    db_query("UPDATE {aggregator_feed} SET url = '%s', link = '%s', checked = '%s', hash = '%s', refresh = '%s' WHERE nid = %d", $node->feed->url, $node->feed->link, time(), $node->feed->hash, 0, $node->nid);
   }
-}
-
-/**
- * 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.
- * Originally from MagpieRSS (http://magpierss.sourceforge.net/).
- *
- * @param $date_str
- *   A string with a potentially W3C DTF date.
- * @return
- *   A timestamp if parsed successfully or FALSE if not.
- */
-function aggregator_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]);
-    // Calculate the 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?  If yes, subtract offset.
-      if ($tz_mod == '+') {
-        $offset_secs *= -1;
-      }
-      $epoch += $offset_secs;
+  if ($hash != $node->feed->hash) {
+    $processors = variable_get('aggregator_processor_' . $node->type, array());
+    foreach ($processors as $processor) {
+      module_invoke($processor, 'aggregator_process', 'save', $node);
     }
-    return $epoch;
   }
   else {
     return FALSE;
@@ -673,233 +499,142 @@ function aggregator_parse_w3cdtf($date_s
 }
 
 /**
- * Parse a feed and store its items.
+ * Format an individual feed item for display in the block.
  *
- * @param $data
- *   The feed data.
+ * @param $item
+ *   The item to be displayed.
  * @param $feed
- *   An associative array describing the feed to be parsed.
+ *   Not used.
  * @return
- *   FALSE on error, TRUE otherwise.
+ *   The item HTML.
+ * @ingroup themeable
  */
-function aggregator_parse_feed(&$data, $feed) {
-  global $items, $image, $channel;
-
-  // Unset the global variables before we use them.
-  unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']);
-  $items = array();
-  $image = array();
-  $channel = array();
-
-  // Parse the data.
-  $xml_parser = drupal_xml_parser_create($data);
-  xml_set_element_handler($xml_parser, 'aggregator_element_start', 'aggregator_element_end');
-  xml_set_character_data_handler($xml_parser, 'aggregator_element_data');
-
-  if (!xml_parse($xml_parser, $data, 1)) {
-    watchdog('aggregator', 'The feed from %site seems to be broken, due to an error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING);
-    drupal_set_message(t('The feed from %site seems to be broken, because of error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error');
-    return FALSE;
-  }
-  xml_parser_free($xml_parser);
-
-  // 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 = 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);
-    }
-
-    // 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 = '';
-    }
-
-    // Resolve the items link.
-    if (!empty($item['LINK'])) {
-      $link = $item['LINK'];
-    }
-    else {
-      $link = $feed['link'];
-    }
-    $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'];
-    }
-    elseif (!empty($item['SUMMARY'])) {
-      $item['DESCRIPTION'] = $item['SUMMARY'];
-    }
-    elseif (!empty($item['CONTENT'])) {
-      $item['DESCRIPTION'] = $item['CONTENT'];
-    }
-
-    // Try to resolve and parse the item's publication date. If no date is
-    // found, use the current date instead.
-    $date = 'now';
-    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); // As of PHP 5.1.0, strtotime returns FALSE on failure instead of -1.
-
-    if ($timestamp <= 0) {
-      $timestamp = aggregator_parse_w3cdtf($date); // Aggregator_parse_w3cdtf() returns FALSE on failure.
-      if (!$timestamp) {
-        // Better than nothing.
-        $timestamp = time();
-      }
-    }
+function theme_aggregator_block_item($item, $feed = 0) {
+  global $user;
 
-    // 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_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND guid = '%s'", $feed['fid'], $guid));
-    }
-    else if ($link && $link != $feed['link'] && $link != $feed['url']) {
-      $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND link = '%s'", $feed['fid'], $link));
-    }
-    else {
-      $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND title = '%s'", $feed['fid'], $title));
+  $output = '';
+  if ($user->uid && module_exists('blog') && user_access('create blog entries')) {
+    if ($image = theme('image', 'misc/blog.png', t('blog it'), t('blog it'))) {
+      $output .= '<div class="icon">' . l($image, 'node/add/blog', array('attributes' => array('title' => t('Comment on this news item in your personal blog.'), 'class' => 'blog-it'), 'query' => "iid=$item->iid", 'html' => TRUE)) . '</div>';
     }
-    $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));
   }
 
-  // Remove all items that are older than flush item timer.
-  $age = time() - variable_get('aggregator_clear', 9676800);
-  $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
-
-  $items = array();
-  $num_rows = FALSE;
-  while ($item = db_fetch_object($result)) {
-    $items[] = $item->iid;
-    $num_rows = TRUE;
-  }
-  if ($num_rows) {
-    db_query('DELETE FROM {aggregator_category_item} WHERE iid IN (' . implode(', ', $items) . ')');
-    db_query('DELETE FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
-  }
+  // Display the external link to the item.
+  $output .= '<a href="' . check_url($item->link) . '">' . check_plain($item->title) . "</a>\n";
 
-  return TRUE;
+  return $output;
 }
 
 /**
- * Add/edit/delete an aggregator item.
+ * Page callbacks for refreshing a feed.
  *
- * @param $edit
- *   An associative array describing the item to be added/edited/deleted.
- */
-function aggregator_save_item($edit) {
-  if ($edit['iid'] && $edit['title']) {
-    db_query("UPDATE {aggregator_item} SET title = '%s', link = '%s', author = '%s', description = '%s', guid = '%s', timestamp = %d WHERE iid = %d", $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['guid'], $edit['timestamp'], $edit['iid']);
+ * @param $node
+ *   Feed node or array of feed nodes.
+ * @param $destination_path
+ *   Jumps here after the refresh.
+ */
+function aggregator_refresh_page($node, $destination_path = NULL) {
+  $feed_nodes = array($node);
+  aggregator_feed_refresh($feed_nodes);
+  if ($destination_path) {
+    drupal_goto($destination_path);
   }
-  elseif ($edit['iid']) {
-    db_query('DELETE FROM {aggregator_item} WHERE iid = %d', $edit['iid']);
-    db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $edit['iid']);
-  }
-  elseif ($edit['title'] && $edit['link']) {
-    db_query("INSERT INTO {aggregator_item} (fid, title, link, author, description, timestamp, guid) VALUES (%d, '%s', '%s', '%s', '%s', %d, '%s')", $edit['fid'], $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['timestamp'], $edit['guid']);
-    $edit['iid'] = db_last_insert_id('aggregator_item', 'iid');
-    // file the items in the categories indicated by the feed
-    $categories = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
-    while ($category = db_fetch_object($categories)) {
-      db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $category->cid, $edit['iid']);
-    }
+  else {
+    drupal_goto('node/'. $node->nid);
   }
 }
 
 /**
- * Load an aggregator feed.
- *
- * @param $fid
- *   The feed id.
+ * Tells if the given module is enabled for that content-type (as parser or as processor).
+ * 
+ * @param $module
+ *   The name of the module.
+ * @param $type
+ *   The name of the content-type.
  * @return
- *   An associative array describing the feed.
+ *   TRUE if enabled, FALSE if disabled.
  */
-function aggregator_feed_load($fid) {
-  static $feeds;
-  if (!isset($feeds[$fid])) {
-    $feeds[$fid] = db_fetch_array(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', $fid));
+function aggregator_is_enabled($module, $type) {
+  $types = node_get_types();
+  if ($module == variable_get('aggregator_parser_' . $types[$type]->type, '')) {
+    return TRUE;
   }
-
-  return $feeds[$fid];
+  $processors = array_values(variable_get('aggregator_processor_' . $types[$type]->type, array()));
+  if (in_array($module, $processors, TRUE)) {
+    return TRUE;
+  }
+  return FALSE;
 }
 
+
 /**
- * Load an aggregator category.
- *
- * @param $cid
- *   The category id.
- * @return
- *   An associative array describing the category.
+ * Determines if the current user can refresh the feed or not.
  */
-function aggregator_category_load($cid) {
-  static $categories;
-  if (!isset($categories[$cid])) {
-    $categories[$cid] = db_fetch_array(db_query('SELECT * FROM {aggregator_category} WHERE cid = %d', $cid));
+function _aggregator_perm_refresh($node) {
+  global $user;
+  if (variable_get('aggregator_feed_' . $node->type, FALSE) && isset($node->feed) && (user_access("edit any $node->type content") || (user_access("edit own $node->type content") && $node->uid == $user->uid))) {
+    return TRUE;
   }
+  return FALSE;
+}
 
-  return $categories[$cid];
+/**
+ * Checks for time limits in cron processing.
+ */
+function _aggregator_cron_time() {
+  static $time_limit;
+  $execute_percentage = 0.5;
+  if (!$time_limit) {
+    $time_limit = 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);
 }
 
 /**
- * Format an individual feed item for display in the block.
- *
+ * Decides if the item is new or not.
+ * The decision is based on the original URL of the article.
+ * This is the only simple and somewhat reliable way to do.
+ * @todo: I'm not sure if I agree with that :) it can actually get more complex
+ *   and depending on what you're trying to aggregate you can use many things in your feed item to
+ *   determine duplicity - hence:
+ * @todo:
+ *   We might want to go back to a hook_aggregator_process operation 'unique' that
+ *   allows any enabled processor to do additional duplicate checks. 
+ *   Another option, but less attractive, could be to make it the processor's responsibility to
+ *   provide an API for deduping to other modules.
+ * Lots of feed use the guid totally wrong.
+ * 
+ * @param $nid
+ *   Feed-node
  * @param $item
- *   The item to be displayed.
- * @param $feed
- *   Not used.
+ *   Feed item
  * @return
- *   The item HTML.
- * @ingroup themeable
+ *   TRUE if it is a new item, FALSE if it is already in the database.
+ *   
  */
-function theme_aggregator_block_item($item, $feed = 0) {
-  global $user;
-
-  $output = '';
-  if ($user->uid && module_exists('blog') && user_access('create blog entries')) {
-    if ($image = theme('image', 'misc/blog.png', t('blog it'), t('blog it'))) {
-      $output .= '<div class="icon">' . l($image, 'node/add/blog', array('attributes' => array('title' => t('Comment on this news item in your personal blog.'), 'class' => 'blog-it'), 'query' => "iid=$item->iid", 'html' => TRUE)) . '</div>';
-    }
-  }
-
-  // Display the external link to the item.
-  $output .= '<a href="' . check_url($item->link) . '">' . check_plain($item->title) . "</a>\n";
-
-  return $output;
+function _aggregator_unique($nid, $item) {
+  $result = db_fetch_array(db_query("SELECT COUNT(*) as same FROM {aggregator_item} WHERE link = '%s' AND nid = %d", $item->link, $nid));
+  return $result['same'] == 0 ? TRUE : FALSE;
 }
 
 /**
- * Safely render HTML content, as allowed.
- *
- * @param $value
- *   The content to be filtered.
- * @return
- *   The filtered content.
+ * Collect the terms from all the aggregator_light-enabled vocabularies
  */
-function aggregator_filter_xss($value) {
-  return filter_xss($value, preg_split('/\s+|<|>/', variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), -1, PREG_SPLIT_NO_EMPTY));
+function _aggregator_collect_terms() {
+  $types = node_get_types();
+  $terms = array();
+  foreach ($types as $type) {
+    $vid = variable_get('aggregator_vid_' . $type->type, 0);
+    if (variable_get('aggregator_feed_' . $type->type, FALSE) && $vid != 0) {
+      $result = db_query("SELECT tid, name FROM {term_data} WHERE vid = %d", $vid);
+      while ($vocab = db_fetch_array($result)) {
+        $terms[$vocab['tid']] = $vocab['name'];
+      }
+    }
+  }
+  return $terms;
 }
 
 /**
Index: modules/aggregator/aggregator.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.pages.inc,v
retrieving revision 1.14
diff -u -p -r1.14 aggregator.pages.inc
--- modules/aggregator/aggregator.pages.inc	15 May 2008 21:27:32 -0000	1.14
+++ modules/aggregator/aggregator.pages.inc	15 Jul 2008 20:59:32 -0000
@@ -13,9 +13,8 @@
  *   The items HTML.
  */
 function aggregator_page_last() {
-  drupal_add_feed(url('aggregator/rss'), variable_get('site_name', 'Drupal') . ' ' . t('aggregator'));
 
-  $items = aggregator_feed_items_load('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC');
+  $items = aggregator_feed_items_load('SELECT i.*, n.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.nid = f.nid INNER JOIN {node} n ON f.nid = n.nid  ORDER BY i.timestamp DESC, i.iid DESC');
 
   return _aggregator_page_list($items, arg(1));
 }
@@ -35,46 +34,19 @@ function aggregator_page_last() {
 function aggregator_page_source($arg1, $arg2 = NULL) {
   // If there are two arguments then this function is the categorize form, and
   // $arg1 is $form_state and $arg2 is $feed. Otherwise, $arg1 is $feed.
-  $feed = is_array($arg2) ? $arg2 : $arg1;
-  $feed = (object)$feed;
-  drupal_set_title(check_plain($feed->title));
-  $feed_source = theme('aggregator_feed_source', $feed);
+  $node = is_array($arg2) || is_object($arg2) ? $arg2 : $arg1;
+  $node = (object) $node;
+  drupal_set_title(check_markup($node->title, 4));
+  $feed_source = theme('aggregator_feed_source', $node);
 
   // It is safe to include the fid in the query because it's loaded from the
   // database by aggregator_feed_load.
-  $items = aggregator_feed_items_load('SELECT * FROM {aggregator_item} WHERE fid = ' . $feed->fid . ' ORDER BY timestamp DESC, iid DESC');
+  $items = aggregator_feed_items_load('SELECT * FROM {aggregator_item} WHERE nid = ' . $node->nid . ' ORDER BY timestamp DESC, iid DESC');
 
   return _aggregator_page_list($items, arg(3), $feed_source);
 }
 
 /**
- * Menu callback; displays all the items aggregated in a particular category.
- *
- * If there are two arguments then this function is called as a form.
- *
- * @param $arg1
- *   If there are two arguments then $arg1 is $form_state. Otherwise, $arg1 is $category.
- * @param $arg2
- *   If there are two arguments then $arg2 is $category.
- * @return
- *   The items HTML.
- */
-function aggregator_page_category($arg1, $arg2 = NULL) {
-  drupal_set_breadcrumb(array_merge(drupal_get_breadcrumb(), array(l(t('Categories'), 'aggregator/categories'))));
-  // If there are two arguments then we are called as a form, $arg1 is
-  // $form_state and $arg2 is $category. Otherwise, $arg1 is $category.
-  $category = is_array($arg2) ? $arg2 : $arg1;
-
-  drupal_add_feed(url('aggregator/rss/' . $category['cid']), variable_get('site_name', 'Drupal') . ' ' . t('aggregator - @title', array('@title' => $category['title'])));
-
-  // It is safe to include the cid in the query because it's loaded from the
-  // database by aggregator_category_load.
-  $items = aggregator_feed_items_load('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = ' . $category['cid'] . ' ORDER BY timestamp DESC, i.iid DESC');
-
-  return _aggregator_page_list($items, arg(3));
-}
-
-/**
  * Load feed items by passing a SQL query.
  *
  * @param $sql
@@ -85,12 +57,18 @@ function aggregator_page_category($arg1,
 function aggregator_feed_items_load($sql) {
   $items = array();
   if (isset($sql)) {
-    $result = pager_query($sql, 20);
+    $rest_of_args = array_slice(func_get_args(), 1);
+    $result = pager_query($sql, 20, 0, NULL, $rest_of_args);
     while ($item = db_fetch_object($result)) {
-      $result_category = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = %d ORDER BY c.title', $item->iid);
-      $item->categories = array();
-      while ($item_categories = db_fetch_object($result_category)) {
-        $item->categories[] = $item_categories;
+      $result_terms = db_query('SELECT d.name, t.tid FROM {aggregator_item} i LEFT JOIN {term_node} t ON t.nid = i.nid LEFT JOIN {term_data} d ON d.tid = t.tid WHERE t.nid = %d', $item->nid);
+      $result_type = db_query('SELECT n.type FROM {node} n WHERE nid = %d', $item->nid);
+      $parent_type = db_fetch_array($result_type);
+      if (is_array($parent_type)) {
+        $item->parent_type = $parent_type['type'];
+      }
+      $item->terms = array();
+      while ($item_terms = db_fetch_object($result_terms)) {
+        $item->terms[] = $item_terms;
       }
       $items[$item->iid] = $item;
     }
@@ -114,128 +92,15 @@ function aggregator_feed_items_load($sql
  *   The items HTML.
  */
 function _aggregator_page_list($items, $op, $feed_source = '') {
-  if (user_access('administer news feeds') && ($op == 'categorize')) {
-    // Get form data.
-    $output = aggregator_categorize_items($items, $feed_source);
-  }
-  else {
-    // Assemble themed output.
-    $output = $feed_source;
-    foreach ($items as $item) {
-      $output .= theme('aggregator_item', $item);
-    }
-    $output = theme('aggregator_wrapper', $output);
-  }
-
-  return $output;
-}
-
-/**
- * Form builder; build the page list form.
- *
- * @param $items
- *   An array of the feed items.
- * @param $feed_source
- *   The feed source URL.
- * @return
- *   The form structure.
- * @ingroup forms
- * @see aggregator_categorize_items_validate()
- * @see aggregator_categorize_items_submit()
- */
-function aggregator_categorize_items($items, $feed_source = '') {
-  $form['#submit'][] = 'aggregator_categorize_items_submit';
-  $form['#validate'][] = 'aggregator_categorize_items_validate';
-  $form['#theme'] = 'aggregator_categorize_items';
-  $form['feed_source'] = array(
-    '#value' => $feed_source,
-  );
-  $categories = array();
-  $done = FALSE;
-  $form['items'] = array();
-  $form['categories'] = array(
-    '#tree' => TRUE,
-  );
+  // Assemble themed output.
+  $output = $feed_source;
   foreach ($items as $item) {
-    $form['items'][$item->iid] = array('#value' => theme('aggregator_item', $item));
-    $form['categories'][$item->iid] = array();
-    $categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = %d', $item->iid);
-    $selected = array();
-    while ($category = db_fetch_object($categories_result)) {
-      if (!$done) {
-        $categories[$category->cid] = check_plain($category->title);
-      }
-      if ($category->iid) {
-        $selected[] = $category->cid;
-      }
-    }
-    $done = TRUE;
-    $form['categories'][$item->iid] = array(
-      '#type' => variable_get('aggregator_category_selector', 'checkboxes'),
-      '#default_value' => $selected,
-      '#options' => $categories,
-      '#size' => 10,
-      '#multiple' => TRUE
-    );
+    $output .= theme('aggregator_item', $item);
   }
-  $form['submit'] = array('#type' => 'submit', '#value' => t('Save categories'));
-
-  return $form;
-}
+  $output = theme('aggregator_wrapper', $output);
 
-/**
- * Validate aggregator_categorize_items() form submissions.
- */
-function aggregator_categorize_items_validate($form, &$form_state) {
-  if (!user_access('administer news feeds')) {
-    form_error($form, t('You are not allowed to categorize this feed item.'));
-  }
-}
 
-/**
- * Process aggregator_categorize_items() form submissions.
- */
-function aggregator_categorize_items_submit($form, &$form_state) {
-  if (!empty($form_state['values']['categories'])) {
-    foreach ($form_state['values']['categories'] as $iid => $selection) {
-      db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $iid);
-      foreach ($selection as $cid) {
-        if ($cid) {
-          db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $cid, $iid);
-        }
-      }
-    }
-  }
-  drupal_set_message(t('The categories have been saved.'));
-}
-
-/**
- * Theme the page list form for assigning categories.
- *
- * @param $form
- *   An associative array containing the structure of the form.
- * @return
- *   The output HTML.
- * @ingroup themeable
- */
-function theme_aggregator_categorize_items($form) {
-  $output = drupal_render($form['feed_source']);
-  $rows = array();
-  if ($form['items']) {
-    foreach (element_children($form['items']) as $key) {
-      if (is_array($form['items'][$key])) {
-        $rows[] = array(
-          drupal_render($form['items'][$key]),
-          array('data' => drupal_render($form['categories'][$key]), 'class' => 'categorize-item'),
-        );
-      }
-    }
-  }
-  $output .= theme('table', array('', t('Categorize')), $rows);
-  $output .= drupal_render($form['submit']);
-  $output .= drupal_render($form);
-
-  return theme('aggregator_wrapper', $output);
+  return $output;
 }
 
 /**
@@ -257,7 +122,7 @@ function template_preprocess_aggregator_
 
   $variables['feed_url'] = check_url($item->link);
   $variables['feed_title'] = check_plain($item->title);
-  $variables['content'] = aggregator_filter_xss($item->description);
+  $variables['content'] = check_markup($item->description, variable_get('aggregator_input_filter_' . $item->parent_type, FILTER_FORMAT_DEFAULT));
 
   $variables['source_url'] = '';
   $variables['source_title'] = '';
@@ -272,9 +137,9 @@ function template_preprocess_aggregator_
     $variables['source_date'] = format_date($item->timestamp, 'custom', variable_get('date_format_medium', 'D, m/d/Y - H:i'));
   }
 
-  $variables['categories'] = array();
-  foreach ($item->categories as $category) {
-    $variables['categories'][$category->cid] = l($category->title, 'aggregator/categories/' . $category->cid);
+  $variables['terms'] = array();
+  foreach ($item->terms as $term) {
+    $variables['terms'][$term->tid] = l($term->name, 'aggregator/terms/' . $term->tid);
   }
 }
 
@@ -282,164 +147,64 @@ function template_preprocess_aggregator_
  * Menu callback; displays all the feeds used by the aggregator.
  */
 function aggregator_page_sources() {
-  $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title');
-
-  $output = '';
-  while ($feed = db_fetch_object($result)) {
-    // Most recent items:
-    $summary_items = array();
-    if (variable_get('aggregator_summary_items', 3)) {
-      $items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = %d ORDER BY i.timestamp DESC', $feed->fid, 0, variable_get('aggregator_summary_items', 3));
-      while ($item = db_fetch_object($items)) {
-        $summary_items[] = theme('aggregator_summary_item', $item);
-      }
-    }
-    $feed->url = url('aggregator/sources/' . $feed->fid);
-    $output .= theme('aggregator_summary_items', $summary_items, $feed);
+  // Collect node types that light feeds can be.
+  $light_types = array();
+  $types = node_get_types();
+  foreach ($types as $type) {
+  	if (aggregator_is_enabled('aggregator', $type->type)) {
+  	  $light_types[] = "'" . $type->type . "'";
+  	}
   }
-  $output .= theme('feed_icon', url('aggregator/opml'), t('OPML feed'));
-
-  return theme('aggregator_wrapper', $output);
-}
-
-/**
- * Menu callback; displays all the categories used by the aggregator.
- */
-function aggregator_page_categories() {
-  $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description');
-
   $output = '';
-  while ($category = db_fetch_object($result)) {
-    if (variable_get('aggregator_summary_items', 3)) {
+  if ($light_types) {
+    // Query all the feeds inside this content-type.
+    $result = db_query('SELECT f.nid, n.title, n.type, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.nid = i.nid LEFT JOIN {node} n ON f.nid = n.nid WHERE n.type IN (' . implode(', ', $light_types) . ')GROUP BY f.nid, n.title ORDER BY last DESC, n.title');
+    while ($feed = db_fetch_object($result)) {
+      // Most recent items:
       $summary_items = array();
-      $items = db_query_range('SELECT i.title, i.timestamp, i.link, f.title as feed_title, f.link as feed_link FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON i.iid = ci.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE ci.cid = %d ORDER BY i.timestamp DESC', $category->cid, 0, variable_get('aggregator_summary_items', 3));
-      while ($item = db_fetch_object($items)) {
-        $summary_items[] = theme('aggregator_summary_item', $item);
+      if (variable_get('aggregator_summary_items_' . $feed->type, 3)) {
+        $items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.nid = %d ORDER BY i.timestamp DESC', $feed->nid, 0, variable_get('aggregator_summary_items_' . $feed->type, 3));
+        while ($item = db_fetch_object($items)) {
+          $summary_items[] = theme('aggregator_summary_item', $item);
+        }
       }
+      $feed->url = url('aggregator/sources/' . $feed->nid);
+      $output .= theme('aggregator_summary_items', $summary_items, $feed);
     }
-    $category->url = url('aggregator/categories/' . $category->cid);
-    $output .= theme('aggregator_summary_items', $summary_items, $category);
   }
-
   return theme('aggregator_wrapper', $output);
 }
 
 /**
- * Menu callback; generate an RSS 0.92 feed of aggregator items or categories.
+ * Menu callback; handle term-pages.
  */
-function aggregator_page_rss() {
-  $result = NULL;
-  // arg(2) is the passed cid, only select for that category.
-  if (arg(2)) {
-    $category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2)));
-    $sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = %d ORDER BY timestamp DESC, i.iid DESC';
-    $result = db_query_range($sql, $category->cid, 0, variable_get('feed_default_items', 10));
+function aggregator_page_terms($tid = NULL) {
+  // List all the items for a given term
+  if (is_numeric($tid)) {
+    $term = taxonomy_get_term($tid);
+    drupal_set_title(check_markup($term->name, 4));
+    $items = aggregator_feed_items_load('SELECT i.* FROM {aggregator_item} i LEFT JOIN {term_node} t ON i.nid = t.nid WHERE t.tid = %d ORDER BY i.timestamp DESC', $term->tid);
+    return _aggregator_page_list($items, arg(3));
   }
-  // Or, get the default aggregator items.
+  // List an overview of the terms
   else {
-    $category = NULL;
-    $sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC';
-    $result = db_query_range($sql, 0, variable_get('feed_default_items', 10));
-  }
-
-  $feeds = array();
-  while ($item = db_fetch_object($result)) {
-    $feeds[] = $item;
-  }
-
-  return theme('aggregator_page_rss', $feeds, $category);
-}
-
-/**
- * Theme the RSS output.
- *
- * @param $feeds
- *   An array of the feeds to theme.
- * @param $category
- *   A common category, if any, for all the feeds.
- * @ingroup themeable
- */
-function theme_aggregator_page_rss($feeds, $category = NULL) {
-  drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
+    $terms = _aggregator_collect_terms();
+    $output = '';
+    foreach ($terms as $tid => $name) {
+      $summary_items = array();
+      // @todo: now it lists the same as in the block. How to do?
+      $items = db_query_range('SELECT i.* FROM {aggregator_item} i LEFT JOIN {term_node} t ON i.nid = t.nid WHERE t.tid = %d ORDER BY i.timestamp DESC', $tid, 0, variable_get('aggregator-' . $tid . '-block', 3));
+      while ($item = db_fetch_object($items)) {
+        $summary_items[] = theme('aggregator_summary_item', $item);
+      }
+      $term = new stdClass();
+      $term->title = $name;
+      $term->url = url('aggregator/terms/' . $tid);
+      $output .= theme('aggregator_summary_items', $summary_items, $term);
 
-  $items = '';
-  $feed_length = variable_get('feed_item_length', 'teaser');
-  foreach ($feeds as $feed) {
-    switch ($feed_length) {
-      case 'teaser':
-        $teaser = node_teaser($feed->description);
-        if ($teaser != $feed->description) {
-          $teaser .= '<p><a href="' . check_url($feed->link) . '">' . t('read more') . "</a></p>\n";
-        }
-        $feed->description = $teaser;
-        break;
-      case 'title':
-        $feed->description = '';
-        break;
     }
-    $items .= format_rss_item($feed->ftitle . ': ' . $feed->title, $feed->link, $feed->description, array('pubDate' => date('r', $feed->timestamp)));
-  }
-
-  $site_name = variable_get('site_name', 'Drupal');
-  $url = url((isset($category) ? 'aggregator/categories/' . $category->cid : 'aggregator'), array('absolute' => TRUE));
-  $description = isset($category) ? t('@site_name - aggregated feeds in category @title', array('@site_name' => $site_name, '@title' => $category->title)) : t('@site_name - aggregated feeds', array('@site_name' => $site_name));
-
-  $output  = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-  $output .= "<rss version=\"2.0\">\n";
-  $output .= format_rss_channel(t('@site_name aggregator', array('@site_name' => $site_name)), $url, $description, $items);
-  $output .= "</rss>\n";
-
-  print $output;
-}
-
-/**
- * Menu callback; generates an OPML representation of all feeds.
- *
- * @param $cid
- *   If set, feeds are exported only from a category with this ID. Otherwise, all feeds are exported.
- * @return
- *   The output XML.
- */
-function aggregator_page_opml($cid = NULL) {
-  if ($cid) {
-    $result = db_query('SELECT f.title, f.url FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} c on f.fid = c.fid WHERE c.cid = %d ORDER BY title', $cid);
-  }
-  else {
-    $result = db_query('SELECT * FROM {aggregator_feed} ORDER BY title');
   }
-
-  $feeds = array();
-  while ($item = db_fetch_object($result)) {
-    $feeds[] = $item;
-  }
-
-  return theme('aggregator_page_opml', $feeds);
-}
-
-/**
- * Theme the OPML feed output.
- *
- * @param $feeds
- *   An array of the feeds to theme.
- * @ingroup themeable
- */
-function theme_aggregator_page_opml($feeds) {
-  drupal_set_header('Content-Type: text/xml; charset=utf-8');
-
-  $output  = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-  $output .= "<opml version=\"1.1\">\n";
-  $output .= "<head>\n";
-  $output .= '<title>' . check_plain(variable_get('site_name', 'Drupal')) . "</title>\n";
-  $output .= '<dateModified>' . gmdate('r') . "</dateModified>\n";
-  $output .= "</head>\n";
-  $output .= "<body>\n";
-  foreach ($feeds as $feed) {
-    $output .= '<outline text="' . check_plain($feed->title) . '" xmlUrl="' . check_url($feed->url) . "\" />\n";
-  }
-  $output .= "</body>\n";
-  $output .= "</opml>\n";
-
-  print $output;
+  return theme('aggregator_wrapper', $output);
 }
 
 /**
@@ -479,15 +244,13 @@ function template_preprocess_aggregator_
  * @see aggregator-feed-source.tpl.php
  */
 function template_preprocess_aggregator_feed_source(&$variables) {
-  $feed = $variables['feed'];
-
-  $variables['source_icon'] = theme('feed_icon', $feed->url, t('!title feed', array('!title' => $feed->title)));
-  $variables['source_image'] = $feed->image;
-  $variables['source_description'] = aggregator_filter_xss($feed->description);
-  $variables['source_url'] = check_url(url($feed->link, array('absolute' => TRUE)));
+  $node = $variables['feed'];
+  $variables['source_icon'] = theme('feed_icon', $node->feed->link, t('!title feed', array('!title' => $node->title)));
+  $variables['source_description'] = check_markup($node->body, FILTER_FORMAT_DEFAULT);
+  $variables['source_url'] = check_url(url($node->feed->link, array('absolute' => TRUE)));
 
-  if ($feed->checked) {
-    $variables['last_checked'] = t('@time ago', array('@time' => format_interval(time() - $feed->checked)));
+  if ($node->feed->checked) {
+    $variables['last_checked'] = t('@time ago', array('@time' => format_interval(time() - $node->feed->checked)));
   }
   else {
     $variables['last_checked'] = t('never');
Index: modules/syndication_parser.inc
===================================================================
diff -upP /home/aaron/ures/syndication_parser.inc syndication_parser/syndication_parser.inc
--- modules/syndication_parser/syndication_parser.module	1970-01-01 01:00:00.000000000 +0100
+++ modules/syndication_parser/syndication_parser.inc	2008-07-15 09:07:41.000000000 +0200
@@ -0,0 +1,340 @@
+<?php
+// $Id: syndication_parser.inc,v 1.4 2008/07/13 22:20:03 alexb Exp $
+
+/**
+ * @file
+ *   Various helper functions for Syndication Parser module
+ */
+
+/**
+ * Downloads the feed.
+ * 
+ * @param $url
+ *   Downloads the HTTP or HTTPS url.
+ */
+function syndication_parser_download($url) {
+  $prev = cache_get($url);
+  $prev = isset($prev->data) ? $prev->data : NULL;
+  $headers = array();
+  if (is_array($prev)) {
+    if (!empty($prev['etag'])) {
+      $headers['If-None-Match'] = $prev['etag'];
+    }
+    if (!empty($prev['modified'])) {
+      $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $prev['modified']) .' GMT';
+    }
+  }
+  $result = drupal_http_request($url, $headers);
+  if ($result->code == 304) {
+    return array('data' => $prev['data'], 'url' => $url);
+  }
+  $feed = _syndication_parser_detect_feed_in_html($result->data, $url);
+  if (is_string($feed)) {
+    $result = drupal_http_request($feed, $headers);
+    $url = $feed;
+  }
+  $etag = empty($result->headers['ETag']) ? '' : $result->headers['ETag'];
+  $modified = empty($result->headers['Last-Modified']) ? 0 : strtotime($result->headers['Last-Modified']);
+  cache_set($url, array('data' => $result->data, 'url' => $url, 'etag' => $etag, 'modified' => $modified));
+  return array('data' => $result->data, 'url' => $url);
+}
+
+/**
+ * Detects a feed's format.
+ */
+function _syndication_parser_format_detect($data) {
+  if (is_object($data)) {
+    $attr = $data->attributes();
+    $type = strtolower($data->getName());
+    if (isset($data->entry) && $type == "feed") {
+      return "atom";
+    }
+    if ($type == "rdf" && isset($data->channel)) {
+      return "rdf";
+    }
+    if ($type == "rss" && in_array($attr["version"], array('0.91', "0.92", "2.0"))) {
+      return "rss";
+    }
+  }
+}
+
+/**
+ * Parses RSS 2.0, 0.91, 0.92 feeds.
+ */
+function _syndication_parser_rss(SimpleXMLElement $data) {
+  $feed = new stdClass();
+  $feed->title = isset($data->channel->title) ? "{$data->channel->title}" : "";
+  $feed->description = isset($data->channel->description) ? "{$data->channel->description}" : "";;
+  $feed->link = isset($data->channel->link) ? "{$data->channel->link}" : "";
+  $feed->items = array();
+  $category_splitter = '.';
+  foreach ($data->xpath('//item') as $news) {
+    // Get important namespaces.
+    $content = $news->children('http://purl.org/rss/1.0/modules/content/');
+		$dc = $news->children('http://purl.org/dc/elements/1.1/');
+		$item = new stdClass();
+		$item->title = _syndication_parser_choose("{$news->title}", "{$dc->title}");
+		$item->description = _syndication_parser_choose("{$news->description}", "{$news->encoded}", "{$content->encoded}", "{$dc->description}");
+		$item->link = _syndication_parser_choose("{$news->link}");
+		$item->timestamp = _syndication_parser_parse_date("{$news->pubDate}");
+		$item->categories = array();
+		if (isset($news->category)) {
+			foreach ($news->category as $cat) {
+				if (is_object($cat)) {
+					$item->categories[] = trim(strip_tags("$cat"));
+				}
+				else {
+					foreach (explode($category_splitter, $cat) as $tag) {
+						$item->categories[] = $tag;
+					}
+				}
+			}
+		}
+		$item->categories = array_unique($item->categories);
+		$feed->items[] = $item;
+  }
+  return $feed;
+}
+
+/**
+ * Parses Atom 1.0 feeds.
+ */
+function _syndication_parser_atom(SimpleXMLElement $data) {
+  $feed = new stdClass();
+  $feed->title = isset($data->title) ? "{$data->title}" : "";
+  $feed->description = isset($data->subtitle) ? "{$data->subtitle}" : "";
+  $feed->link = '';
+  if (count($data->link) > 0) {
+    $link = $data->link;
+    $link = $link->attributes();
+    $feed->link = isset($link["href"]) ? "{$link["href"]}" : "";
+  }
+  $feed->items = array();
+  foreach ($data->entry as $news) {
+    $item = new stdClass();
+    $item->id = !empty($news->id) ? "{$news->id}" : NULL;
+    
+    $link_element = "{$news->link}";
+    $link_guid = valid_url($item->id) ? $item->id : '';
+    $item->link = _syndication_parser_choose($link_element, $link_guid);
+    $item->title = "{$news->title}";
+    $body = '';
+    if (!empty($news->content)) {
+      foreach ($news->content->children() as $child)  {
+        $body .= $child->asXML();
+      }
+      $body .= "{$news->content}";
+    }
+    else if (!empty($news->summary)) {
+      foreach ($news->summary->children() as $child)  {
+        $body .= $child->asXML();
+      }
+      $body .= "{$news->summary}";
+    }
+    $item->description = $body;
+    $item->timestamp = _syndication_parser_parse_date("{$news->published}");
+    $item->categories = array();
+    if (isset($news->category)) {
+			foreach ($news->category as $category)
+				$item->categories[] = trim(strip_tags("{$category['term']}"));
+		}
+		$item->categories = array_unique($item->categories);
+    $feed->items[] = $item;
+  }
+  return $feed;
+}
+
+/**
+ * Parses RDF feeds.
+ */
+function _syndication_parser_rdf(SimpleXMLElement $data) {
+  $feed = new stdClass();
+  $feed->title = isset($data->channel->title) ? "{$data->channel->title}" : "";
+  $feed->description = isset($data->channel->description) ? "{$data->channel->description}" : "";
+  $feed->link = isset($data->channel->link) ? "{$data->channel->link}" : "";
+  $namespaces = $data->getNamespaces(TRUE);
+  // Set category splitter (space is for del.icio.us feed).
+  $category_splitter = ' ';
+  $feed->items = array();
+  foreach ($data->item as $news) {
+    // Initialization.
+    $id = $original_url = NULL;
+    $title = $body = '';
+    $categories = array();
+    foreach ($namespaces as $ns_link) {
+      // Get about attribute as guid.
+      foreach ($news->attributes($ns_link) as $name => $value) {
+        if ($name == 'about') {
+          $id = "{$value}";
+        }
+      }
+
+      // Get children for current namespace.
+      if (version_compare(phpversion(), '5.1.2', '<')) {
+        $ns = (array) $news;
+      }
+      else {
+        $ns = (array) $news->children($ns_link);
+      }
+
+      // Title
+      if (!empty($ns['title'])) {
+        $title = "{$ns['title']}";
+      }
+
+      // Description or dc:description
+      if (!empty($ns['description']) && $body == '') {
+        $body = "{$ns['description']}";
+      }
+
+      // Link
+      if (!empty($ns['link'])) {
+        $link = "{$ns['link']}";
+      }
+
+      // content:encoded
+      if (!empty($ns['encoded'])) {
+        $body = "{$ns['encoded']}";
+      }
+      
+      $timestamp = _syndication_parser_parse_date((empty($ns['pubDate']) ? "{$ns['date']}" : "{$ns['pubDate']}"));
+
+      // dc:subject
+      if (!empty($ns['subject'])) {
+        // there can be multiple category tags
+        if (is_array($ns['subject'])) {
+          foreach ($ns['subject'] as $cat) {
+            if (is_object($cat)) {
+              $categories[] = trim(strip_tags($cat->asXML()));
+            }
+            else {
+              $categories[] = $cat;
+            }
+          }
+        }
+        else { //or single tag
+          $categories = explode($category_splitter, "{$ns['subject']}");
+        }
+      }
+    }
+    if (empty($original_url) && !empty($id)) {
+      $original_url = $id;
+    }
+    $item = new stdClass();
+    $item->title = $title;
+    $item->description = $body;
+    $item->timestamp = $timestamp;
+    $item->link = $link;
+    $item->id = $id;
+    $item->categories = $categories;
+    $feed->items[] = $item;
+  }
+  return $feed;
+}
+
+/**
+ * Chooses the first argument which is not empty and return with it.
+ */
+function _syndication_parser_choose() {
+  $args = func_get_args();
+  foreach ($args as $arg) {
+    if (strlen($arg) > 1) {
+      return $arg;
+    }
+  }
+  return '';
+}
+
+/**
+ * Parses a date comes from a feed.
+ *
+ * @param $date_string
+ *   The date string in various formats.
+ * @return
+ *   The timestamp of the string or the current time if can't be parsed
+ */
+function _syndication_parser_parse_date($date_str) {
+  $parsed_date = strtotime($date_str);
+  if ($parsed_date === FALSE || $parsed_date == -1) {
+    $parsed_date = _syndication_parser_parse_w3cdtf($date_str);
+  }
+  return $parsed_date === FALSE ? time() : $parsed_date;
+}
+
+/**
+ * Parses 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.
+ * Originally from MagpieRSS (http://magpierss.sourceforge.net/).
+ *
+ * @param $date_str
+ *   A string with a potentially W3C DTF date.
+ * @return
+ *   A timestamp if parsed successfully or FALSE if not.
+ */
+function _syndication_parser_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]);
+    // Calculate the 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?  If yes, subtract offset.
+      if ($tz_mod == '+') {
+        $offset_secs *= -1;
+      }
+      $epoch += $offset_secs;
+    }
+    return $epoch;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Detects available feeds in a HTML document.
+ * 
+ * @param $site
+ *   The HTML site.
+ * @return
+ *   The detected feed URL.
+ */
+function _syndication_parser_detect_feed_in_html($site, $site_url = FALSE) {
+  @$html = DOMDocument::loadHTML($site);
+  if ($html) {
+    $site_parsed = simplexml_import_dom($html);
+    $allowed_mime = array("text/xml", "application/rss+xml", "application/atom+xml", "application/rdf+xml", "application/xml");
+    foreach ($site_parsed->xpath("//link") as $element) {
+      if (strtolower($element['rel']) == 'alternate' && in_array(strtolower($element['type']), $allowed_mime)) {
+        $detected_url = "{$element['href']}";
+        break;
+      }
+    }
+  }
+  if (!empty($detected_url)) {
+    // If the detected URL is relative, make it absolute.
+    $parsed_url = parse_url($detected_url);
+    if (!isset($parsed_url['scheme']) || !isset($parsed_url['host'])) {
+      $base_element = array_pop($site_parsed->xpath("///head/base"));
+      if (strlen("{$base_element['href']}") > 0) {
+        $detected_url = "{$base_element['href']}" . $detected_url;
+      }
+      else {
+        $original_url = parse_url($site_url);
+        $detected_url = $original_url['scheme'] . '://' . $original_url['host'] . (isset($original_url['port']) ? ':' . $original_url['port'] : '') . $parsed_url['path'] . (isset($original_url['query']) ? '?' . $original_url['query'] : '') . (isset($original_url['fragment']) ? '#' . $original_url['fragment'] : '');
+      }
+    }
+    return $detected_url;
+  }
+  return FALSE;
+}
Index: modules/syndication_parser/syndication_parser.info
===================================================================
diff -upP /home/aaron/ures/syndication_parser.info syndication_parser/syndication_parser.info
--- modules/syndication_parser/syndication_parser.module	1970-01-01 01:00:00.000000000 +0100
+++ modules/syndication_parser/syndication_parser.info	2008-07-15 09:07:41.000000000 +0200
@@ -0,0 +1,9 @@
+; $Id:
+
+name = Syndication Parser
+description = "Parses syndicated content."
+package = Core - optional
+version = VERSION
+core = 7.x
+files[] = syndication_parser.module
+files[] = syndication_parser.inc
Index: modules/syndication_parser/syndication_parser.module
===================================================================
diff -upP /home/aaron/ures/syndication_parser.module syndication_parser/syndication_parser.module
--- modules/syndication_parser/syndication_parser.module	1970-01-01 01:00:00.000000000 +0100
+++ modules/syndication_parser/syndication_parser.module	2008-07-15 14:50:29.000000000 +0200
@@ -0,0 +1,71 @@
+<?php
+// $Id: syndication_parser.module,v 1.7 2008/07/15 12:50:29 aronnovak Exp $
+
+/**
+ * @file
+ *   Provide a parser for RSS, Atom and RDF feeds.
+ * 
+ *   Accepts the downloaded feed and returns with the parsed structure.
+ *   It does not provide a user interface, the aggregator module uses this
+ *   and other feed-related modules can also use this module.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function syndication_parser_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#syndication_parser':
+      $output = '<P>'. t('The Syndication Parser module downloads and parses RSS, Atom and RDF feeds and provides them to other modules. This module is typically used with aggregator module.') .'</P>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_parse().
+ * 
+ * @param $op
+ *   'parse' Parse the feed-nodes
+ *   'info' Metadata about the processor
+ * @param $feed_nodes
+ *   Array of feed-nodes which contains the URL or the URL of the feed (for standalone usage)
+ */
+function syndication_parser_aggregator_parse($op, $feed_nodes = NULL) {
+  switch ($op) {
+    case 'parse':
+      $standalone = FALSE;
+      if (is_string($feed_nodes)) {
+        $standalone = TRUE;
+        $feed_nodes = array();
+        $feed_nodes[] = aggregator_feed_create($feed_nodes, '');
+      }
+      $results = array();
+      foreach ($feed_nodes as $key => $node) {
+        if (isset($node->feed->url)) {
+          if (drupal_function_exists('syndication_parser_download')) {
+            $downloaded = syndication_parser_download($node->feed->url);
+            $data = simplexml_load_string($downloaded['data']);
+            if (drupal_function_exists('_syndication_parser_format_detect')) {
+              $feed_handler = '_syndication_parser_' . _syndication_parser_format_detect($data);
+              if (drupal_function_exists($feed_handler)) {
+                $result = $feed_handler($data);
+                $result->url = $downloaded['url'];
+                if ($standalone) {
+                  return $result;
+                }
+              }
+            }
+          }
+        }
+        $results[$key] = empty($result) ? FALSE : $result;
+        $result = NULL;
+      }
+      return $results;
+      break;
+    case 'info':
+      return array(
+        'title' => t('Syndication Parser'),
+        'description' => t('Parser for RSS, Atom and RDF feeds. Mainly based on SimpleXML.'),
+      );
+  }
+}
