Index: modules/aggregator/aggregator-feed-info.tpl.php =================================================================== RCS file: modules/aggregator/aggregator-feed-info.tpl.php diff -N modules/aggregator/aggregator-feed-info.tpl.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/aggregator/aggregator-feed-info.tpl.php 4 Apr 2009 14:09:39 -0000 @@ -0,0 +1,36 @@ + +
+ + +
+ +
+ +
+ +
+
+ +
+
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 4 Apr 2009 14:09:39 -0000 @@ -11,10 +11,8 @@ * Available variables: * - $source_icon: Feed icon linked to the source. Rendered through * theme_feed_icon(). - * - $source_image: Image set by the feed source. - * - $source_description: Description set by the feed source. - * - $source_url: URL to the feed source. - * - $last_checked: How long ago the feed was checked locally. + * - $feed_node_url: URL linking to the node that represents the feed. + * - $feed_node_title: Title of the node that represents the feed. * * @see template_preprocess() * @see template_preprocess_aggregator_feed_source() @@ -22,14 +20,7 @@ ?>
- -
- -
-
- -
-
- +
+
Index: modules/aggregator/aggregator.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.admin.inc,v retrieving revision 1.25 diff -u -p -r1.25 aggregator.admin.inc --- modules/aggregator/aggregator.admin.inc 24 Feb 2009 16:48:18 -0000 1.25 +++ modules/aggregator/aggregator.admin.inc 4 Apr 2009 14:09:40 -0000 @@ -20,14 +20,14 @@ function aggregator_admin_overview() { * 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.hash, f.etag, f.modified, f.image, f.block ORDER BY f.title'); + $result = db_query('SELECT n.title, f.*, COUNT(i.iid) AS items FROM {aggregator_feed} f INNER JOIN {node} n ON n.nid = f.nid LEFT JOIN {aggregator_item} i ON f.nid = i.nid GROUP BY f.nid, n.title, f.url, f.refresh, f.checked, f.link, f.hash, f.etag, f.modified, f.image, f.block ORDER BY n.title'); $output = '

' . t('Feed overview') . '

'; $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3')); $rows = array(); foreach ($result as $feed) { - $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(REQUEST_TIME - $feed->checked))) : t('never')), ($feed->checked ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - REQUEST_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")); + $rows[] = array(l($feed->title, "aggregator/sources/$feed->nid"), format_plural($feed->items, '1 item', '@count items'), ($feed->checked ? t('@time ago', array('@time' => format_interval(REQUEST_TIME - $feed->checked))) : t('never')), ($feed->checked ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - REQUEST_TIME))) : t('never')), l(t('edit'), "node/$feed->nid/edit", array('query' => 'destination=' . $_GET['q'])), l(t('remove items'), "node/$feed->nid/remove-items", array('query' => 'destination=' . $_GET['q'])), l(t('update items'), "node/$feed->nid/update", array('query' => 'destination=' . $_GET['q']))); } $output .= theme('table', $header, $rows); @@ -45,159 +45,8 @@ function aggregator_view() { 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, stdClass $feed = NULL) { - $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval'); - - if (!isset($feed)) { - $feed = new stdClass(); - } - - if (empty($feed->refresh)) { - $feed->refresh = 3600; - } - - $form['title'] = array('#type' => 'textfield', - '#title' => t('Title'), - '#default_value' => isset($feed->title) ? $feed->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' => isset($feed->url) ? $feed->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' => isset($feed->refresh) ? $feed->refresh : 900, - '#options' => $period, - '#description' => t('The length of time between feed updates. Requires a correctly configured cron maintenance task.', array('@cron' => url('admin/reports/status'))), - ); - $form['block'] = array('#type' => 'select', - '#title' => t('News items in block'), - '#default_value' => isset($feed->block) ? $feed->block : 5, - '#options' => drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)), - '#description' => t("Drupal can make a block with the most recent news items of this feed. You can configure blocks to be displayed in the sidebar of your page. This setting lets you configure the number of news items to show in this feed's block. If you choose '0' this feed's block will be disabled.", array('@block-admin' => url('admin/build/block'))), - ); - - // 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 = :fid ORDER BY title', array(':fid' => isset($feed->fid) ? $feed->fid : NULL)); - foreach ($categories as $category) { - $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'), - ); - - if (!empty($feed->fid)) { - $form['delete'] = array( - '#type' => 'submit', - '#value' => t('Delete'), - ); - $form['fid'] = array( - '#type' => 'hidden', - '#value' => $feed->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 = :title OR url = :url) AND fid <> :fid", array(':title' => $form_state['values']['title'], ':url' => $form_state['values']['url'], ':fid' => $form_state['values']['fid'])); - } - else { - $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = :title OR url = :url", array(':title' => $form_state['values']['title'], ':url' => $form_state['values']['url'])); - } - foreach ($result as $feed) { - 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. - */ -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) { + $feed_node = node_load($feed->nid); return confirm_form( array( 'feed' => array( @@ -205,7 +54,7 @@ function aggregator_admin_remove_feed($f '#value' => $feed, ), ), - t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed->title)), + t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed_node->title)), 'admin/content/aggregator', t('This action cannot be undone.'), t('Remove items'), @@ -214,14 +63,14 @@ function aggregator_admin_remove_feed($f } /** - * Remove all items from a feed and redirect to the overview page. + * Remove all items from a feed and redirect. * * @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'; + $form_state['redirect'] = 'node/' . $form_state['values']['feed']->nid; } /** @@ -270,6 +119,26 @@ function aggregator_form_opml(&$form_sta '#description' => t('New feed items are automatically filed in the checked categories.'), ); } + $content_types = aggregator_get_enabled_content_types(); + if (count($content_types) == 1) { + $form['content_type'] = array( + '#type' => 'value', + '#value' => key($content_types), + ); + } + elseif (count($content_types) > 1) { + $form['content_type'] = array( + '#type' => 'select', + '#title' => t('Feed content type to create'), + '#options' => $content_types, + '#default_value' => key($content_types), + '#description' => t('Choose what type of feed you would like to create from the data in the OPML file.'), + ); + } + else { + drupal_set_message(t('No feed content type defined, can\'t import OPML'), 'error'); + } + $form['submit'] = array( '#type' => 'submit', '#value' => t('Import') @@ -297,6 +166,7 @@ function aggregator_form_opml_validate($ * Process aggregator_form_opml form submissions. */ function aggregator_form_opml_submit($form, &$form_state) { + global $user; $data = ''; if ($file = file_save_upload('upload')) { $data = file_get_contents($file->filepath); @@ -314,7 +184,6 @@ function aggregator_form_opml_submit($fo return; } - $form_state['values']['op'] = t('Save'); foreach ($feeds as $feed) { // Ensure URL is valid. @@ -324,7 +193,7 @@ function aggregator_form_opml_submit($fo } // Check for duplicate titles or URLs. - $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = :title OR url = :url", array(':title' => $feed['title'], ':url' => $feed['url'])); + $result = db_query("SELECT n.title, f.url FROM {aggregator_feed} f JOIN {node} n ON n.vid = f.vid WHERE n.title = :title OR f.url = :url", array(':title' => $feed['title'], ':url' => $feed['url'])); foreach ($result as $old) { if (strcasecmp($old->title, $feed['title']) == 0) { drupal_set_message(t('A feed named %title already exists.', array('%title' => $old->title)), 'warning'); @@ -336,9 +205,20 @@ function aggregator_form_opml_submit($fo } } - $form_state['values']['title'] = $feed['title']; - $form_state['values']['url'] = $feed['url']; - drupal_execute('aggregator_form_feed', $form_state); + // Create a node for the feed. + $edit = array(); + $edit['values'] = array(); + $edit['values']['title'] = $feed['title']; + $edit['values']['feed']['url'] = $feed['url']; + $edit['values']['feed']['refresh'] = $form_state['values']['refresh']; + if (isset($form_state['values']['category'])) { + $edit['values']['feed']['category'] = $form_state['values']['category']; + } + $edit['values']['name'] = $user->name; + $edit['values']['promote'] = '0'; + $edit['values']['op'] = t('Save'); + $node = array('type' => $form_state['values']['content_type']); + drupal_execute($form_state['values']['content_type'] . '_node_form', $edit, $node); } $form_state['redirect'] = 'admin/content/aggregator'; @@ -377,22 +257,21 @@ function _aggregator_parse_opml($opml) { } /** - * Menu callback; refreshes a feed, then redirects to the overview page. + * Menu callback; refreshes a feed, then redirects. * * @param $feed * An object describing the feed to be refreshed. */ -function aggregator_admin_refresh_feed($feed) { - aggregator_refresh($feed); - drupal_goto('admin/content/aggregator'); +function aggregator_admin_refresh_feed($node) { + aggregator_refresh($node->feed); + drupal_goto('node/' . $node->nid); } /** - * Form builder; Configure the aggregator system. - * - * @ingroup forms + * Implementation of hook_form_node_type_form_alter(). */ -function aggregator_admin_form($form_state) { +function aggregator_form_node_type_form_alter(&$form, $form_state) { + $node_type = empty($form['#node_type']->type) ? '' : $form['#node_type']->type; // Make sure configuration is sane. aggregator_sanitize_configuration(); @@ -444,7 +323,7 @@ function aggregator_admin_form($form_sta '#title' => t('Fetcher'), '#description' => t('Fetchers download data from an external source. Choose a fetcher suitable for the external source you would like to download from.'), '#options' => $fetchers, - '#default_value' => variable_get('aggregator_fetcher', 'aggregator'), + '#default_value' => variable_get('aggregator_fetcher_' . $node_type, 'aggregator'), ); } if (count($parsers) > 1) { @@ -453,7 +332,7 @@ function aggregator_admin_form($form_sta '#title' => t('Parser'), '#description' => t('Parsers transform downloaded data into standard structures. Choose a parser suitable for the type of feeds you would like to aggregate.'), '#options' => $parsers, - '#default_value' => variable_get('aggregator_parser', 'aggregator'), + '#default_value' => variable_get('aggregator_parser_' . $node_type, 'aggregator'), ); } if (count($processors) > 1) { @@ -462,27 +341,40 @@ function aggregator_admin_form($form_sta '#title' => t('Processors'), '#description' => t('Processors act on parsed feed data, for example they store feed items. Choose the processors suitable for your task.'), '#options' => $processors, - '#default_value' => variable_get('aggregator_processors', array('aggregator')), + '#default_value' => variable_get('aggregator_processors_' . $node_type, array('aggregator')), ); } - if (count($basic_conf)) { - $form['basic_conf'] = array( + $form['aggregator'] = array( + '#type' => 'fieldset', + '#title' => t('Aggregator'), + '#collapsible' => TRUE, + '#collapsed' => !variable_get('aggregator_enabled_' . $node_type, FALSE), + ); + $form['aggregator']['aggregator_enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Feed content type'), + '#description' => t('Check if you would like to use this content type to download syndication feeds like RSS or Atom to your web site.'), + '#default_value' => variable_get('aggregator_enabled_' . $node_type, FALSE), + ); + if (count($basic_conf)) { + $form['aggregator']['basic_conf'] = array( '#type' => 'fieldset', '#title' => t('Basic configuration'), '#description' => t('For most aggregation tasks, the default settings are fine.'), - '#collapsible' => TRUE, - '#collapsed' => FALSE, + '#collapsible' => FALSE, ); - $form['basic_conf'] += $basic_conf; + $form['aggregator']['basic_conf'] += $basic_conf; } - // Implementing modules will expect an array at $form['modules']. - $form['modules'] = array(); + // Implementing modules will expect an array at $form[]. + $form['aggregator']['fetchers'] = array(); + $form['aggregator']['parsers'] = array(); + $form['aggregator']['processors'] = array(); - $form['submit'] = array( - '#type' => 'submit', - '#value' => t('Save configuration'), - ); + // Add aggregator default processor specific form elements. + if (drupal_function_exists('aggregator_processor_node_type_form')) { + aggregator_processor_node_type_form($form); + } return $form; } Index: modules/aggregator/aggregator.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.api.php,v retrieving revision 1.2 diff -u -p -r1.2 aggregator.api.php --- modules/aggregator/aggregator.api.php 26 Jan 2009 14:08:42 -0000 1.2 +++ modules/aggregator/aggregator.api.php 4 Apr 2009 14:09:40 -0000 @@ -20,8 +20,8 @@ * finally, it is passed to all active processors which manipulate or store the * data. * - * Modules that define this hook can be set as active fetcher on - * admin/content/aggregator/settings. Only one fetcher can be active at a time. + * Modules that define this hook can be set as active fetcher on the content + * type form of the feed. Only one fetcher can be active at a time. * * @param $feed * The $feed object that describes the resource to be downloaded. @@ -42,10 +42,10 @@ function hook_aggregator_fetch($feed) { * Implement this hook to expose the title and a short description of your * fetcher. * - * The title and the description provided are shown on - * admin/content/aggregator/settings among other places. Use as title the human - * readable name of the fetcher and as description a brief (40 to 80 characters) - * explanation of the fetcher's functionality. + * The title and the description provided are shown for example on content type + * edit forms. Use as title the human readable name of the fetcher and as + * description a brief (40 to 80 characters) explanation of the fetcher's + * functionality. * * This hook is only called if your module implements hook_aggregator_fetch(). * If this hook is not implemented aggregator will use your module's file name @@ -74,8 +74,8 @@ function hook_aggregator_fetch_info() { * finally, it is passed to all active processors which manipulate or store the * data. * - * Modules that define this hook can be set as active parser on - * admin/content/aggregator/settings. Only one parser can be active at a time. + * Modules that define this hook can be set as active parser on the content + * type form of the feed. Only one parser can be active at a time. * * @param $feed * The $feed object that describes the resource to be parsed. @@ -108,10 +108,10 @@ function hook_aggregator_parse($feed) { * Implement this hook to expose the title and a short description of your * parser. * - * The title and the description provided are shown on - * admin/content/aggregator/settings among other places. Use as title the human - * readable name of the parser and as description a brief (40 to 80 characters) - * explanation of the parser's functionality. + * The title and the description provided are shown for example on content type + * edit forms. Use as title the human readable name of the parser and as + * description a brief (40 to 80 characters) explanation of the parser's + * functionality. * * This hook is only called if your module implements hook_aggregator_parse(). * If this hook is not implemented aggregator will use your module's file name @@ -140,8 +140,8 @@ function hook_aggregator_parse_info() { * finally, it is passed to all active processors which manipulate or store the * data. * - * Modules that define this hook can be activated as processor on - * admin/content/aggregator/settings. + * Modules that define this hook can be activated as processor on the content + * type form of the feed * * @param $feed * The $feed object that describes the resource to be processed. $feed->items @@ -166,10 +166,9 @@ function hook_aggregator_process($feed) * Implement this hook to expose the title and a short description of your * processor. * - * The title and the description provided are shown most importantly on - * admin/content/aggregator/settings . Use as title the natural name of the - * processor and as description a brief (40 to 80 characters) explanation of - * the functionality. + * The title and the description provided are shown for example on content type + * edit forms. Use as title the natural name of the processor and as + * description a brief (40 to 80 characters) explanation of the functionality. * * This hook is only called if your module implements * hook_aggregator_process(). If this hook is not implemented aggregator Index: modules/aggregator/aggregator.fetcher.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.fetcher.inc,v retrieving revision 1.4 diff -u -p -r1.4 aggregator.fetcher.inc --- modules/aggregator/aggregator.fetcher.inc 26 Jan 2009 14:08:42 -0000 1.4 +++ modules/aggregator/aggregator.fetcher.inc 4 Apr 2009 14:09:40 -0000 @@ -40,7 +40,7 @@ function aggregator_aggregator_fetch($fe case 304: db_update('aggregator_feed') ->fields(array('checked' => REQUEST_TIME)) - ->condition('fid', $feed->fid) + ->condition('nid', $feed->nid) ->execute(); drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed->title))); break; @@ -63,7 +63,7 @@ function aggregator_aggregator_fetch($fe $md5 = md5($result->data); if ($feed->hash == $md5) { db_update('aggregator_feed') - ->condition('fid', $feed->fid) + ->condition('nid', $feed->nid) ->fields(array('checked' => REQUEST_TIME)) ->execute(); drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed->title))); Index: modules/aggregator/aggregator.install =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.install,v retrieving revision 1.20 diff -u -p -r1.20 aggregator.install --- modules/aggregator/aggregator.install 22 Dec 2008 19:38:31 -0000 1.20 +++ modules/aggregator/aggregator.install 4 Apr 2009 14:09:40 -0000 @@ -26,6 +26,29 @@ function aggregator_uninstall() { } /** + * Implementation of hook_enable(). + * + * Create a feed content type if there is none. + */ +function aggregator_enable() { + if (!node_get_types('type', 'feed')) { + $type = array( + 'type' => 'feed', + 'name' => t('Feed'), + 'base' => 'node_content', + 'description' => t("A feed can be used to download syndicated content like RSS or Atom feeds to your web site."), + 'custom' => 1, + 'modified' => 1, + 'locked' => 0, + 'aggregator_enabled' => 1, + ); + $type = node_type_set_defaults($type); + node_type_save($type); + variable_set('aggregator_enabled_feed', TRUE); + } +} + +/** * Implementation of hook_schema(). */ function aggregator_schema() { @@ -67,11 +90,17 @@ function aggregator_schema() { $schema['aggregator_category_feed'] = array( 'description' => 'Bridge table; maps feeds to categories.', 'fields' => array( - 'fid' => array( + 'nid' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, - 'description' => "The feed's {aggregator_feed}.fid.", + 'description' => "The feed's {aggregator_feed}.nid.", + ), + 'vid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => "The feed's {aggregator_feed}.nid.", ), 'cid' => array( 'type' => 'int', @@ -80,9 +109,10 @@ function aggregator_schema() { 'description' => 'The {aggregator_category}.cid to which the feed is being assigned.', ) ), - 'primary key' => array('cid', 'fid'), + 'primary key' => array('cid', 'vid'), 'indexes' => array( - 'fid' => array('fid'), + 'nid' => array('nid'), + 'vid' => array('vid'), ), ); @@ -111,17 +141,16 @@ function aggregator_schema() { $schema['aggregator_feed'] = array( 'description' => 'Stores feeds to be parsed by the aggregator.', 'fields' => array( - 'fid' => array( - 'type' => 'serial', + 'nid' => array( + 'type' => 'int', 'not null' => TRUE, - 'description' => 'Primary Key: Unique feed ID.', + 'description' => 'Node id of this feed.', ), - 'title' => array( - 'type' => 'varchar', - 'length' => 255, + 'vid' => array( + 'type' => 'int', 'not null' => TRUE, - 'default' => '', - 'description' => 'Title of the feed.', + 'default' => 0, + 'description' => "The feed's {aggregator_feed}.nid.", ), 'url' => array( 'type' => 'varchar', @@ -149,15 +178,9 @@ function aggregator_schema() { 'default' => '', 'description' => 'The parent website of the feed; comes from the <link> element in the feed.', ), - 'description' => array( - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'big', - 'description' => "The parent website's description; comes from the <description> element in the feed.", - ), 'image' => array( 'type' => 'text', - 'not null' => TRUE, + 'not null' => FALSE, 'size' => 'big', 'description' => 'An image representing the feed.', ), @@ -189,11 +212,7 @@ function aggregator_schema() { 'description' => "Number of items to display in the feed's block.", ) ), - 'primary key' => array('fid'), - 'unique keys' => array( - 'url' => array('url'), - 'title' => array('title'), - ), + 'primary key' => array('vid'), ); $schema['aggregator_item'] = array( @@ -204,11 +223,11 @@ function aggregator_schema() { 'not null' => TRUE, 'description' => 'Primary Key: Unique ID for feed item.', ), - 'fid' => array( + 'nid' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, - 'description' => 'The {aggregator_feed}.fid to which this item belongs.', + 'description' => 'The {aggregator_feed}.nid to which this item belongs.', ), 'title' => array( 'type' => 'varchar', @@ -251,7 +270,7 @@ function aggregator_schema() { ), '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.405 diff -u -p -r1.405 aggregator.module --- modules/aggregator/aggregator.module 1 Mar 2009 07:21:02 -0000 1.405 +++ modules/aggregator/aggregator.module 4 Apr 2009 14:09:40 -0000 @@ -18,10 +18,8 @@ function aggregator_help($path, $arg) { return $output; case 'admin/content/aggregator': $output = '

' . 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 RSS, RDF, and Atom.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'http://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) . '

'; - $output .= '

' . t('Current feeds are listed below, and new feeds may be added. For each feed or feed category, the latest items block may be enabled at the blocks administration page.', array('@addfeed' => url('admin/content/aggregator/add/feed'), '@block' => url('admin/build/block'))) . '

'; + $output .= '

' . t('Current feeds are listed below, and new feeds may be added. For each feed or feed category, the latest items block may be enabled at the blocks administration page.', array('@addfeed' => url('node/add'), '@block' => url('admin/build/block'))) . '

'; return $output; - case 'admin/content/aggregator/add/feed': - return '

' . t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') . '

'; case 'admin/content/aggregator/add/category': return '

' . t('Categories allow feed items from different feeds to be grouped together. For example, several sport-related feeds may belong to a category named Sports. Feed items may be grouped automatically (by selecting a category when creating or editing a feed) or manually (via the Categorize page available from feed item listings). Each category provides its own feed page and block.') . '

'; case 'admin/content/aggregator/add/opml': @@ -43,8 +41,13 @@ function aggregator_theme() { 'arguments' => array('form' => NULL), 'file' => 'aggregator.pages.inc', ), + 'aggregator_feed_info' => array( + 'arguments' => array('feed_node' => NULL), + 'file' => 'aggregator.pages.inc', + 'template' => 'aggregator-feed-info', + ), 'aggregator_feed_source' => array( - 'arguments' => array('feed' => NULL), + 'arguments' => array('feed_node' => NULL), 'file' => 'aggregator.pages.inc', 'template' => 'aggregator-feed-source', ), @@ -81,20 +84,30 @@ function aggregator_theme() { * Implementation of hook_menu(). */ function aggregator_menu() { + $items['node/%node/update'] = array( + 'title' => 'Update feed', + 'description' => 'Check feed for new items', + 'page callback' => 'aggregator_admin_refresh_feed', + 'page arguments' => array(1), + 'access callback' => 'aggregator_access_feed_options', + 'access arguments' => array(1), + 'type' => MENU_LOCAL_TASK, + ); + $items['node/%node/remove-items'] = array( + 'title' => 'Remove items', + 'description' => 'Remove all items', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('aggregator_admin_remove_feed', 1), + 'access callback' => 'aggregator_access_feed_options', + 'access arguments' => array(1), + 'type' => MENU_LOCAL_TASK, + ); $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', @@ -111,33 +124,11 @@ function aggregator_menu() { '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_form'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 10, - 'access arguments' => array('administer news feeds'), - ); $items['aggregator'] = array( 'title' => 'Feed aggregator', 'page callback' => 'aggregator_page_last', @@ -194,39 +185,25 @@ function aggregator_menu() { 'type' => MENU_LOCAL_TASK, 'weight' => 1, ); - $items['aggregator/sources/%aggregator_feed'] = array( + $items['aggregator/sources/%node'] = array( 'page callback' => 'aggregator_page_source', 'page arguments' => array(2), - 'access arguments' => array('access news feeds'), + 'access callback' => 'node_access', + 'access arguments' => array('view', 2), 'type' => MENU_CALLBACK, ); - $items['aggregator/sources/%aggregator_feed/view'] = array( + $items['aggregator/sources/%node/view'] = array( 'title' => 'View', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); - $items['aggregator/sources/%aggregator_feed/categorize'] = array( + $items['aggregator/sources/%node/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', @@ -287,13 +264,218 @@ function aggregator_perm() { * Checks news feeds for updates once their refresh interval has elapsed. */ function aggregator_cron() { - $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < :time', array(':time' => REQUEST_TIME)); + $result = db_query('SELECT n.title, n.type, f.* FROM {node} n INNER JOIN {aggregator_feed} f ON f.vid = n.vid WHERE f.checked + f.refresh < :time', array(':time' => REQUEST_TIME)); foreach ($result as $feed) { aggregator_refresh($feed); } } /** + * Implementation of hook_form_alter(). + * + * Add aggregator related options to node forms of feed-enabled content types. + */ +function aggregator_form_alter(&$form, $form_state, $form_id) { + if ($form['#id'] == 'node-form') { + if (variable_get('aggregator_enabled_' . $form['type']['#value'], FALSE)) { + if (!isset($form['#node']->feed)) { + $form['#node']->feed = new stdClass(); + } + if (is_array($form['#node']->feed)) { + $form['#node']->feed = (object) $form['#node']->feed; + } + $form['feed'] = array( + '#type' => 'fieldset', + '#title' => t('Feed'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#tree' => TRUE, + '#weight' => -2, + ); + $form['feed']['url'] = array( + '#type' => 'textfield', + '#title' => t('URL'), + '#description' => t('Enter the full URL of the feed you would like to add.'), + '#default_value' => empty($form['#node']->feed->url) ? '' : $form['#node']->feed->url, + '#required' => TRUE, + ); + // Update interval and news items in blocks only configurable by administrators. + if (user_access('administer news feeds')) { + $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval'); + $form['feed']['refresh'] = array( + '#type' => 'select', + '#title' => t('Update interval'), + '#default_value' => empty($form['#node']->feed->refresh) ? 1800 : $form['#node']->feed->refresh, + '#options' => $period, + '#description' => t('The length of time between feed updates. Requires a correctly configured cron maintenance task.', array('@cron' => url('admin/reports/status'))), + ); + } + else { + $form['feed']['refresh'] = array( + '#type' => 'value', + '#value' => empty($form['#node']->feed->refresh) ? 1800 : $form['#node']->feed->refresh, + ); + } + $processors = variable_get('aggregator_processors_' . $form['type']['#value'], array('aggregator')); + // If aggregator-native processor is enabled, add processor specific form elements. + if (in_array('aggregator', $processors)) { + if (drupal_function_exists('aggregator_processor_node_form')) { + aggregator_processor_node_form($form, $form_state, $form_id); + } + } + } + } +} + +/** + * Implementation of hook_node_load(). + */ +function aggregator_node_load($nodes, $types) { + $nids = array(); + foreach ($nodes as $node) { + if (variable_get('aggregator_enabled_' . $node->type, FALSE)) { + $nids[$node->nid] = $node->nid; + } + } + if (count($nids)) { + $result = db_query('SELECT n.title, n.type, f.* FROM {node} n INNER JOIN {aggregator_feed} f ON f.vid = n.vid WHERE n.nid IN (:nids)', array(':nids' => $nids)); + foreach ($result as $record) { + $nodes[$record->nid]->feed = $record; + } + } +} + +/** + * Implementation of hook_node_validate(). + */ +function aggregator_node_validate($node) { + if (isset($node->feed['url'])) { + if (!valid_url($node->feed['url'], TRUE)) { + form_set_error('feed][url', t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $node->feed['url']))); + } + // Check for duplicate titles. + if (empty($node->nid)) { + $result = db_query("SELECT n.title, f.url FROM {node} n INNER JOIN {aggregator_feed} f ON f.vid = n.vid WHERE n.title = :title OR f.url = :url", array(':title' => $node->title, ':url' => $node->feed['url'])); + } + else { + $result = db_query("SELECT n.title, f.url FROM {node} n INNER JOIN {aggregator_feed} f ON f.vid = n.vid WHERE (n.title = :title OR f.url = :url) AND f.nid <> :nid", array(':title' => $node->title, ':url' => $node->feed['url'], ':nid' => $node->nid)); + } + foreach ($result as $feed) { + if (strcasecmp($feed->title, $node->title) == 0) { + form_set_error('title', t('A feed named %feed already exists. Please enter a unique title.', array('%feed' => $node->title))); + } + if (strcasecmp($feed->url, $node->feed['url']) == 0) { + form_set_error('url', t('A feed with this URL %url already exists. Please enter a unique URL.', array('%url' => $node->feed['url']))); + } + } + } +} + +/** + * Implementation of hook_node_insert(). + */ +function aggregator_node_insert($node) { + if (variable_get('aggregator_enabled_' . $node->type, FALSE)) { + $node->feed['nid'] = $node->nid; + $node->feed['vid'] = $node->vid; + aggregator_save_feed($node->feed); + if (isset($node->feed['category'])) { + if (drupal_function_exists('aggregator_processor_save_feed_category')) { + aggregator_processor_save_feed_category($node); + } + } + } +} + +/** + * Implementation of hook_node_update(). + */ +function aggregator_node_update($node) { + if (variable_get('aggregator_enabled_' . $node->type, FALSE)) { + if (is_object($node->feed)) { + // Query the reverted settings + $node->feed = db_query("SELECT * FROM {aggregator_feed} WHERE vid = :old", array(':old' => $node->old_vid))->fetchAssoc(); + if (drupal_function_exists('aggregator_processor_save_feed_category')) { + $node->feed['category'] = db_query("SELECT cid FROM {aggregator_category_feed} WHERE vid = :old", array(':old' => $node->old_vid))->fetchAssoc(); + } + } + $node->feed['nid'] = $node->nid; + $node->feed['vid'] = $node->vid; + aggregator_save_feed($node->feed); + if (isset($node->feed['category'])) { + if (drupal_function_exists('aggregator_processor_save_feed_category')) { + aggregator_processor_save_feed_category($node); + } + } + } +} + +/** + * Implementation of hook_node_delete(). + */ +function aggregator_node_delete($node) { + if (variable_get('aggregator_enabled_' . $node->type, FALSE)) { + $iids = db_query('SELECT iid FROM {aggregator_item} WHERE nid = :nid', array(':nid' => $node->nid))->fetchCol(); + if ($iids) { + db_delete('aggregator_category_item') + ->condition('iid', $iids, 'IN') + ->execute(); + } + db_delete('aggregator_feed')-> + condition('nid', $node->nid) + ->execute(); + db_delete('aggregator_category_feed') + ->condition('nid', $node->nid) + ->execute(); + db_delete('aggregator_item') + ->condition('nid', $node->nid) + ->execute(); + // Make sure there is no active block for this feed. + db_delete('block') + ->condition('module', 'aggregator') + ->condition('delta', 'feed-' . $node->nid) + ->execute(); + } +} + +/** + * Implementation of hook_node_delete_revision(). + */ +function hook_node_delete_revision($node) { + db_delete('aggregator_feed')->condition('vid', $node->vid)->execute(); + db_delete('aggregator_category_feed')->condition('vid', $node->vid)->execute(); +} + + +/** + * An implementation of hook_node_view(). + */ +function aggregator_node_view($node, $teaser) { + if (variable_get('aggregator_enabled_' . $node->type, FALSE)) { + $processors = variable_get('aggregator_processors_' . $node->type, array('aggregator')); + if (in_array('aggregator', $processors)) { + $links = array(); + $links['aggregator_items'] = array( + 'title' => t('Feed items'), + 'href' => 'aggregator/sources/' . $node->nid, + ); + $node->content['links']['aggregator'] = array( + '#type' => 'node_links', + '#value' => $links, + ); + } + // On a full node view, add aggregator feed info. + if (!$teaser) { + $items = array(); + $node->content['aggregator_feed_info'] = array( + '#weight' => -1, + '#markup' => theme('aggregator_feed_info', $node), + ); + } + } +} + +/** * Implementation of hook_block_list(). */ function aggregator_block_list() { @@ -302,9 +484,9 @@ function aggregator_block_list() { foreach ($result as $category) { $block['category-' . $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title)); } - $result = db_query('SELECT fid, title FROM {aggregator_feed} WHERE block <> 0 ORDER BY fid'); + $result = db_query('SELECT n.vid, n.title FROM {node} n JOIN {aggregator_feed} f ON f.vid = n.vid WHERE f.block <> 0 ORDER BY vid'); foreach ($result as $feed) { - $block['feed-' . $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title)); + $block['feed-' . $feed->vid]['info'] = t('!title feed latest items', array('!title' => $feed->title)); } return $block; } @@ -350,10 +532,10 @@ function aggregator_block_view($delta = list($type, $id) = explode('-', $delta); switch ($type) { case 'feed': - if ($feed = db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE block <> 0 AND fid = :fid', array(':fid' => $id))->fetchObject()) { + if ($feed = db_query('SELECT n.nid, n.title, f.block FROM {node} n INNER JOIN {aggregator_feed} f ON f.vid = n.vid WHERE f.block <> 0 AND f.vid = :vid', array(':vid' => $id))->fetchObject()) { $block['subject'] = check_plain($feed->title); - $result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", array(':fid' => $id), 0, $feed->block); - $read_more = theme('more_link', url('aggregator/sources/' . $feed->fid), t("View this feed's recent news.")); + $result = db_query_range("SELECT * FROM {aggregator_item} WHERE nid = :nid ORDER BY timestamp DESC, iid DESC", array(':nid' => $id), 0, $feed->block); + $read_more = theme('more_link', url('aggregator/sources/' . $feed->nid), t("View this feed's recent news.")); } break; @@ -430,95 +612,52 @@ function aggregator_save_category($edit) /** * Add/edit/delete an aggregator feed. * - * @param $edit - * An associative array describing the feed to be added/edited/deleted. + * @param $feed + * An object or 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_delete('aggregator_category_feed') - ->condition('fid', $edit['fid']) - ->execute(); - } - if (!empty($edit['fid']) && !empty($edit['title'])) { - db_update('aggregator_feed') - ->condition('fid', $edit['fid']) - ->fields(array( - 'title' => $edit['title'], - 'url' => $edit['url'], - 'refresh' => $edit['refresh'], - 'block' => $edit['block'], - )) - ->execute(); - } - elseif (!empty($edit['fid'])) { - $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $edit['fid']))->fetchCol(); - if ($iids) { - db_delete('aggregator_category_item') - ->condition('iid', $iids, 'IN') - ->execute(); +function aggregator_save_feed($feed) { + $keys = drupal_schema_fields_sql('aggregator_feed'); + $fields = array(); + if (is_object($feed)) { + foreach ($keys as $key) { + if (isset($feed->$key)) { + $fields[$key] = $feed->$key; + } } - db_delete('aggregator_feed')-> - condition('fid', $edit['fid']) - ->execute(); - db_delete('aggregator_item') - ->condition('fid', $edit['fid']) - ->execute(); - // Make sure there is no active block for this feed. - db_delete('block') - ->condition('module', 'aggregator') - ->condition('delta', 'feed-' . $edit['fid']) - ->execute(); - } - elseif (!empty($edit['title'])) { - $edit['fid'] = db_insert('aggregator_feed') - ->fields(array( - 'title' => $edit['title'], - 'url' => $edit['url'], - 'refresh' => $edit['refresh'], - 'block' => $edit['block'], - 'description' => '', - 'image' => '', - )) - ->execute(); - } - 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_merge('aggregator_category_feed') - ->key(array('fid' => $edit['fid'])) - ->fields(array( - 'cid' => $cid, - )) - ->execute(); - } + elseif (is_array($feed)) { + foreach ($keys as $key) { + if (isset($feed[$key])) { + $fields[$key] = $feed[$key]; } } } + if (count($fields) && isset($fields['vid']) && isset($fields['nid'])) { + db_merge('aggregator_feed') + ->key(array('vid' => $fields['vid'])) + ->fields($fields) + ->execute(); + } } /** * Removes all items from a feed. * - * @param $feed - * An object describing the feed to be cleared. + * @param $node + * An object describing the feed node to be cleared. */ -function aggregator_remove($feed) { +function aggregator_remove($node) { // Call hook_aggregator_remove() on all modules. - module_invoke_all('aggregator_remove', $feed); + module_invoke_all('aggregator_remove', $node->feed); // Reset feed. - db_merge('aggregator_feed') - ->key(array('fid' => $feed->fid)) + db_update('aggregator_feed') + ->condition('nid', $node->nid) ->fields(array( 'checked' => 0, 'hash' => '', 'etag' => '', 'modified' => 0, - 'description' => $feed->description, - 'image' => $feed->image, )) ->execute(); } @@ -531,17 +670,17 @@ function aggregator_remove($feed) { */ function aggregator_refresh($feed) { // Fetch the feed. - $fetcher = variable_get('aggregator_fetcher', 'aggregator'); + $fetcher = variable_get('aggregator_fetcher_' . $feed->type, 'aggregator'); module_invoke($fetcher, 'aggregator_fetch', $feed); if ($feed->source_string !== FALSE) { // Parse the feed. - $parser = variable_get('aggregator_parser', 'aggregator'); + $parser = variable_get('aggregator_parser_' . $feed->type, 'aggregator'); module_invoke($parser, 'aggregator_parse', $feed); // If there are items on the feed, let all enabled processors do their work on it. if (@count($feed->items)) { - $processors = variable_get('aggregator_processors', array('aggregator')); + $processors = variable_get('aggregator_processors_' . $feed->type, array('aggregator')); foreach ($processors as $processor) { module_invoke($processor, 'aggregator_process', $feed); } @@ -556,18 +695,18 @@ function aggregator_refresh($feed) { /** * Load an aggregator feed. * - * @param $fid + * @param $nid * The feed id. * @return * An object describing the feed. */ -function aggregator_feed_load($fid) { +function aggregator_feed_load($nid) { static $feeds; - if (!isset($feeds[$fid])) { - $feeds[$fid] = db_query('SELECT * FROM {aggregator_feed} WHERE fid = :fid', array(':fid' => $fid))->fetchObject(); + if (!isset($feeds[$nid])) { + $feeds[$nid] = db_query('SELECT n.title, n.type, f.* FROM {node} n INNER JOIN {aggregator_feed} f ON f.vid = n.vid WHERE n.nid = :nid', array(':nid' => $nid))->fetchObject(); } - return $feeds[$fid]; + return $feeds[$nid]; } /** @@ -588,6 +727,22 @@ function aggregator_category_load($cid) } /** + * Access callback for aggregator_menu(). + * + * @param $node + * Node object. + * @return + * TRUE, if given feed is an object and user has administer news feeds + * permissions. FALSE otherwise. + */ +function aggregator_access_feed_options($node) { + if (@is_object($node->feed) && user_access('administer news feeds')) { + return TRUE; + } + return FALSE; +} + +/** * Format an individual feed item for display in the block. * * @param $item @@ -599,10 +754,8 @@ function aggregator_category_load($cid) * @ingroup themeable */ function theme_aggregator_block_item($item, $feed = 0) { - // Display the external link to the item. return '' . check_plain($item->title) . "\n"; - } /** @@ -653,6 +806,20 @@ function aggregator_sanitize_configurati } /** + * Get all aggregator enabled content types. + */ +function aggregator_get_enabled_content_types() { + $types = node_get_types('types'); + $type_names = array(); + foreach ($types as $type_name => $type) { + if (variable_get('aggregator_enabled_' . $type_name, FALSE)) { + $type_names[$type_name] = $type->name; + } + } + return $type_names; +} + +/** * Helper function for drupal_map_assoc. * * @param $count Index: modules/aggregator/aggregator.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.pages.inc,v retrieving revision 1.24 diff -u -p -r1.24 aggregator.pages.inc --- modules/aggregator/aggregator.pages.inc 3 Feb 2009 18:55:29 -0000 1.24 +++ modules/aggregator/aggregator.pages.inc 4 Apr 2009 14:09:40 -0000 @@ -35,13 +35,13 @@ 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_object($arg2) ? $arg2 : $arg1; - drupal_set_title($feed->title); - $feed_source = theme('aggregator_feed_source', $feed); + $feed_node = is_object($arg2) ? $arg2 : $arg1; + drupal_set_title($feed_node->title); + $feed_source = theme('aggregator_feed_source', $feed_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('source', $feed); + $items = aggregator_feed_items_load('source', $feed_node->feed); return _aggregator_page_list($items, arg(3), $feed_source); } @@ -87,13 +87,13 @@ function aggregator_feed_items_load($typ $range_limit = 20; switch ($type) { case 'sum': - $result = db_query_range('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', 0, $range_limit); + $result = db_query_range('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 n.vid = f.vid ORDER BY i.timestamp DESC, i.iid DESC', 0, $range_limit); break; case 'source': - $result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC', array(':fid' => $data->fid), 0, $range_limit); + $result = db_query_range('SELECT * FROM {aggregator_item} WHERE nid = :nid ORDER BY timestamp DESC, iid DESC', array(':nid' => $data->nid), 0, $range_limit); break; case 'category': - $result = db_query_range('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 = :cid ORDER BY timestamp DESC, i.iid DESC', array(':cid' => $data['cid']), 0, $range_limit); + $result = db_query_range('SELECT i.*, n.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.nid = f.nid LEFT JOIN {node} n ON n.vid = f.vid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', array(':cid' => $data['cid']), 0, $range_limit); break; } @@ -299,24 +299,17 @@ 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 = ''; - foreach ($result as $feed) { - // 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 = :fid ORDER BY i.timestamp DESC', array(':fid' => $feed->fid), 0, variable_get('aggregator_summary_items', 3)); - foreach ($items as $item) { - $summary_items[] = theme('aggregator_summary_item', $item); - } - } - $feed->url = url('aggregator/sources/' . $feed->fid); - $output .= theme('aggregator_summary_items', $summary_items, $feed); - } - $output .= theme('feed_icon', url('aggregator/opml'), t('OPML feed')); - - return theme('aggregator_wrapper', $output); + $nids = pager_query(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {aggregator_feed} f ON f.vid = n.vid WHERE n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10))->fetchCol(); + $build = array(); + if (!empty($nids)) { + $nodes = node_load_multiple($nids); + $build = node_build_multiple($nodes); + } + $build['opml'] = array( + '#markup' => theme('feed_icon', url('aggregator/opml'), t('OPML feed')), + '#weight' => 6, + ); + return theme('aggregator_wrapper', drupal_render($build)); } /** @@ -329,7 +322,7 @@ function aggregator_page_categories() { foreach ($result as $category) { if (variable_get('aggregator_summary_items', 3)) { $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 = :cid ORDER BY i.timestamp DESC', array(':cid' => $category->cid), 0, variable_get('aggregator_summary_items', 3)); + $items = db_query_range('SELECT i.title, i.timestamp, i.link, n.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.nid = f.nid LEFT JOIN {node} n ON n.vid = f.vid WHERE ci.cid = :cid ORDER BY i.timestamp DESC', array(':cid' => $category->cid), 0, variable_get('aggregator_summary_items', 3)); foreach ($items as $item) { $summary_items[] = theme('aggregator_summary_item', $item); } @@ -349,12 +342,12 @@ function aggregator_page_rss() { // arg(2) is the passed cid, only select for that category. if (arg(2)) { $category = db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = :cid', array(':cid' => arg(2)))->fetchObject(); - $result = db_query_range('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 = :cid ORDER BY timestamp DESC, i.iid DESC', array(':cid' => $category->cid), 0, variable_get('feed_default_items', 10)); + $result = db_query_range('SELECT i.*, fn.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.nid = f.nid LEFT JOIN {node} fn ON f.vid = fn.vid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', array(':cid' => $category->cid), 0, variable_get('feed_default_items', 10)); } // Or, get the default aggregator items. else { $category = NULL; - $result = db_query_range('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', 0, variable_get('feed_default_items', 10)); + $result = db_query_range('SELECT i.*, fn.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.nid = f.nid LEFT JOIN {node} fn ON f.vid = fn.vid ORDER BY i.timestamp DESC, i.iid DESC', 0, variable_get('feed_default_items', 10)); } $feeds = $result->fetchAll(); @@ -413,10 +406,10 @@ function theme_aggregator_page_rss($feed */ 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 = :cid ORDER BY title', array(':cid' => $cid)); + $result = db_query('SELECT f.title, f.url FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} c on f.nid = c.nid WHERE c.cid = :cid ORDER BY title', array(':cid' => $cid)); } else { - $result = db_query('SELECT * FROM {aggregator_feed} ORDER BY title'); + $result = db_query('SELECT f.*, n.title FROM {aggregator_feed} f LEFT JOIN {node} n ON f.vid = v.nid ORDER BY title'); } $feeds = $result->fetchAll(); @@ -486,21 +479,37 @@ 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))); + $feed_node = $variables['feed_node']; + $variables['feed_node_title'] = $feed_node->title; + $variables['feed_node_url'] = url('node/' . $feed_node->nid); + $variables['source_icon'] = theme('feed_icon', $feed_node->feed->url, t('!title feed', array('!title' => $feed_node->title))); +} - if ($feed->checked) { - $variables['last_checked'] = t('@time ago', array('@time' => format_interval(REQUEST_TIME - $feed->checked))); +/** + * Process variables for aggregator-feed-info.tpl.php + * + * @see aggregator-feed-info.tpl.php + */ +function template_preprocess_aggregator_feed_info(&$variables) { + $feed_node = $variables['feed_node']; + if (is_array($feed_node->feed)) { + $feed_node->feed = (object) $feed_node->feed; + } + $variables['source_icon'] = theme('feed_icon', $feed_node->feed->url, t('!title feed', array('!title' => $feed_node->title))); + if (!empty($feed_node->feed->image)) { + $variables['source_image'] = $feed_node->feed->image; + } + $variables['source_url'] = isset($feed_node->feed->link) ? $feed_node->feed->link : ''; + if (!empty($feed_node->feed->checked)) { + $last_checked = t('@time ago', array('@time' => format_interval(REQUEST_TIME - $feed_node->feed->checked))); } else { - $variables['last_checked'] = t('never'); + $last_checked = t('never'); } - if (user_access('administer news feeds')) { - $variables['last_checked'] = l($variables['last_checked'], 'admin/content/aggregator'); + $last_checked = l($last_checked, 'admin/content/aggregator'); + } + if (!empty($last_checked)) { + $variables['last_checked'] = $last_checked; } } Index: modules/aggregator/aggregator.parser.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.parser.inc,v retrieving revision 1.1 diff -u -p -r1.1 aggregator.parser.inc --- modules/aggregator/aggregator.parser.inc 22 Dec 2008 19:38:31 -0000 1.1 +++ modules/aggregator/aggregator.parser.inc 4 Apr 2009 14:09:40 -0000 @@ -37,7 +37,7 @@ function aggregator_aggregator_parse($fe } if (!empty($image['LINK']) && !empty($image['URL']) && !empty($image['TITLE'])) { - $image = l(theme('image', $image['URL'], $image['TITLE']), $image['LINK'], array('html' => TRUE)); + $image = l(theme('image', $image['URL'], t('Image of the feed'), $image['TITLE'], NULL, FALSE), $image['LINK'], array('html' => TRUE)); } else { $image = ''; @@ -46,12 +46,11 @@ function aggregator_aggregator_parse($fe $etag = empty($feed->http_headers['ETag']) ? '' : $feed->http_headers['ETag']; // Update the feed data. db_merge('aggregator_feed') - ->key(array('fid' => $feed->fid)) + ->key(array('nid' => $feed->nid)) ->fields(array( 'url' => $feed->url, 'checked' => REQUEST_TIME, 'link' => !empty($channel['LINK']) ? $channel['LINK'] : '', - 'description' => !empty($channel['DESCRIPTION']) ? $channel['DESCRIPTION'] : '', 'image' => $image, 'hash' => md5($feed->source_string), 'etag' => $etag, Index: modules/aggregator/aggregator.processor.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.processor.inc,v retrieving revision 1.3 diff -u -p -r1.3 aggregator.processor.inc --- modules/aggregator/aggregator.processor.inc 26 Jan 2009 14:08:42 -0000 1.3 +++ modules/aggregator/aggregator.processor.inc 4 Apr 2009 14:09:40 -0000 @@ -27,18 +27,18 @@ function aggregator_aggregator_process($ // we find a duplicate entry, we resolve it and pass along its ID is such // that we can update it if needed. if (!empty($item['GUID'])) { - $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND guid = :guid", array(':fid' => $feed->fid, ':guid' => $item['GUID']))->fetchObject(); + $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE nid = :nid AND guid = :guid", array(':nid' => $feed->nid, ':guid' => $item['GUID']))->fetchObject(); } elseif ($item['LINK'] && $item['LINK'] != $feed->link && $item['LINK'] != $feed->url) { - $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND link = :link", array(':fid' => $feed->fid, ':link' => $item['LINK']))->fetchObject(); + $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE nid = :nid AND link = :link", array(':nid' => $feed->nid, ':link' => $item['LINK']))->fetchObject(); } else { - $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND title = :title", array(':fid' => $feed->fid, ':title' => $item['TITLE']))->fetchObject(); + $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE nid = :nid AND title = :title", array(':nid' => $feed->nid, ':title' => $item['TITLE']))->fetchObject(); } if (!$item['TIMESTAMP']) { $item['TIMESTAMP'] = isset($entry->timestamp) ? $entry->timestamp : REQUEST_TIME; } - aggregator_save_item(array('iid' => (isset($entry->iid) ? $entry->iid : ''), 'fid' => $feed->fid, 'timestamp' => $item['TIMESTAMP'], 'title' => $item['TITLE'], 'link' => $item['LINK'], 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION'], 'guid' => $item['GUID'])); + aggregator_save_item(array('iid' => (isset($entry->iid) ? $entry->iid : ''), 'nid' => $feed->nid, 'vid' => $feed->vid, 'timestamp' => $item['TIMESTAMP'], 'title' => $item['TITLE'], 'link' => $item['LINK'], 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION'], 'guid' => $item['GUID'])); } } } @@ -48,65 +48,57 @@ function aggregator_aggregator_process($ * Implementation of hook_aggregator_remove(). */ function aggregator_aggregator_remove($feed) { - $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchCol(); + $iids = db_query('SELECT iid FROM {aggregator_item} WHERE nid = :nid', array(':nid' => $feed->nid))->fetchCol(); if ($iids) { db_delete('aggregator_category_item') ->condition('iid', $iids, 'IN') ->execute(); } db_delete('aggregator_item') - ->condition('fid', $feed->fid) + ->condition('nid', $feed->nid) ->execute(); drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed->title))); } /** - * Implementation of hook_form_aggregator_admin_form_alter(). + * Add processor specific elements to the node type form. * - * Form alter aggregator module's own form to keep processor functionality - * separate from aggregator API functionality. + * Called from aggregator_form_node_type_form_alter(). We keep this functionality + * in aggregator.processor.inc because it is processor related. */ -function aggregator_form_aggregator_admin_form_alter(&$form, $form_state) { +function aggregator_processor_node_type_form(&$form) { + $node_type = empty($form['#node_type']->type) ? '' : $form['#node_type']->type; + if (in_array('aggregator', variable_get('aggregator_processors', array('aggregator')))) { - $info = module_invoke('aggregator', 'aggregator_process', 'info'); $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'); // Only wrap into a collapsible fieldset if there is a basic configuration. - if (isset($form['basic_conf'])) { - $form['modules']['aggregator'] = array( + if (isset($form['aggregator']['basic_conf'])) { + $form['aggregator']['processors']['aggregator'] = array( '#type' => 'fieldset', '#title' => t('Default processor settings'), - '#description' => $info['description'], '#collapsible' => TRUE, - '#collapsed' => !in_array('aggregator', variable_get('aggregator_processors', array('aggregator'))), + '#collapsed' => !in_array('aggregator', variable_get('aggregator_processors_' . $node_type, array('aggregator'))), ); } else { - $form['modules']['aggregator'] = array(); + $form['aggregator']['processors']['aggregator'] = array(); } - $form['modules']['aggregator']['aggregator_summary_items'] = array( - '#type' => 'select', - '#title' => t('Items shown in sources and categories pages') , - '#default_value' => variable_get('aggregator_summary_items', 3), - '#options' => $items, - '#description' => t('Number of feed items displayed in feed and category summary pages.'), - ); - - $form['modules']['aggregator']['aggregator_clear'] = array( + $form['aggregator']['processors']['aggregator']['aggregator_clear'] = array( '#type' => 'select', '#title' => t('Discard items older than'), - '#default_value' => variable_get('aggregator_clear', 9676800), + '#default_value' => variable_get('aggregator_clear_' . $node_type, 9676800), '#options' => $period, '#description' => t('The length of time to retain feed items before discarding. (Requires a correctly configured cron maintenance task.)', array('@cron' => url('admin/reports/status'))), ); - $form['modules']['aggregator']['aggregator_category_selector'] = array( + $form['aggregator']['processors']['aggregator']['aggregator_category_selector'] = array( '#type' => 'radios', '#title' => t('Category selection type'), - '#default_value' => variable_get('aggregator_category_selector', 'checkboxes'), + '#default_value' => variable_get('aggregator_category_selector_' . $node_type, 'checkboxes'), '#options' => array('checkboxes' => t('checkboxes'), 'select' => t('multiple selector')), '#description' => t('The type of category selection widget displayed on categorization pages. (For a small number of categories, checkboxes are easier to use, while a multiple selector works well with large numbers of categories.)'), @@ -115,6 +107,49 @@ function aggregator_form_aggregator_admi } /** + * Add processor specific elements to the node form. + * + * Called from aggregator_form_alter(). Keep this functionality in + * aggregator.processor.inc because it is processor related. + */ +function aggregator_processor_node_form(&$form, $form_state, $form_id) { + // Handling of categories. + $options = array(); + $values = array(); + $categories = db_query('SELECT c.cid, c.title, f.nid FROM {aggregator_category} c LEFT JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.vid = :vid ORDER BY title', array(':vid' => empty($form['#node']->vid) ? NULL : $form['#node']->vid)); + foreach ($categories as $category) { + $options[$category->cid] = check_plain($category->title); + if ($category->nid) $values[] = $category->cid; + } + if ($options) { + $form['feed']['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.'), + ); + } + // Update interval and news items in blocks only configurable by administrators. + if (user_access('administer news feeds')) { + $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval'); + $form['feed']['refresh'] = array( + '#type' => 'select', + '#title' => t('Update interval'), + '#default_value' => empty($form['#node']->feed->refresh) ? 1800 : $form['#node']->feed->refresh, + '#options' => $period, + '#description' => t('The length of time between feed updates. Requires a correctly configured cron maintenance task.', array('@cron' => url('admin/reports/status'))), + ); + $form['feed']['block'] = array( + '#type' => 'select', + '#title' => t('News items in block'), + '#default_value' => empty($form['#node']->feed->block) ? 5 : $form['#node']->feed->block, + '#options' => drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)), + '#description' => t("Drupal can make a block with the most recent news items of this feed. You can configure blocks to be displayed in the sidebar of your page. This setting lets you configure the number of news items to show in this feed's block. If you choose '0' this feed's block will be disabled.", array('@block-admin' => url('admin/build/block'))), + ); + } +} +/** * Add/edit/delete an aggregator item. * * @param $edit @@ -130,7 +165,7 @@ function aggregator_save_item($edit) { 'description' => $edit['description'], 'guid' => $edit['guid'], 'timestamp' => $edit['timestamp'], - 'fid' => $edit['fid'], + 'nid' => $edit['nid'], )) ->execute(); } @@ -144,7 +179,7 @@ function aggregator_save_item($edit) { } elseif ($edit['title'] && $edit['link']) { // file the items in the categories indicated by the feed - $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $edit['fid'])); + $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE vid = :vid', array(':vid' => $edit['vid'])); foreach ($result as $category) { db_merge('aggregator_category_item') ->key(array('iid' => $edit['iid'])) @@ -157,6 +192,30 @@ function aggregator_save_item($edit) { } /** + * Saves a feed node's category associations. + */ +function aggregator_processor_save_feed_category($node) { + if (!isset($node->old_vid)) { + db_delete('aggregator_category_feed') + ->condition('vid', $node->vid) + ->execute(); + } + if (@is_array($node->feed['category'])) { + foreach ($node->feed['category'] as $key => $value) { + if (!empty($value)) { + db_insert('aggregator_category_feed') + ->fields(array( + 'nid' => $node->nid, + 'vid' => $node->vid, + 'cid' => $value, + )) + ->execute(); + } + } + } +} + +/** * Expire feed items on $feed that are older than aggregator_clear. * * @param $feed @@ -165,7 +224,7 @@ function aggregator_save_item($edit) { function aggregator_expire($feed) { // Remove all items that are older than flush item timer. $age = REQUEST_TIME - variable_get('aggregator_clear', 9676800); - $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid AND timestamp < :timestamp', array(':fid' => $feed->fid, ':timestamp' => $age))->fetchCol(); + $iids = db_query('SELECT iid FROM {aggregator_item} WHERE nid = :nid AND timestamp < :timestamp', array(':nid' => $feed->nid, ':timestamp' => $age))->fetchCol(); if ($iids) { db_delete('aggregator_category_item') ->condition('iid', $iids, 'IN') Index: modules/aggregator/aggregator.test =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.test,v retrieving revision 1.22 diff -u -p -r1.22 aggregator.test --- modules/aggregator/aggregator.test 2 Apr 2009 20:50:37 -0000 1.22 +++ modules/aggregator/aggregator.test 4 Apr 2009 14:09:40 -0000 @@ -11,7 +11,7 @@ class AggregatorTestCase extends DrupalW function setUp() { parent::setUp('aggregator', 'aggregator_test'); - $web_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content')); + $web_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content', 'create feed content', 'edit any feed content', 'delete any feed content')); $this->drupalLogin($web_user); } @@ -20,17 +20,20 @@ class AggregatorTestCase extends DrupalW * * @param $feed_url * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * @param $content_type + * Machine readable content type. * @return $feed * Full feed object if possible. - * + * * @see getFeedEditArray() */ - function createFeed($feed_url = NULL) { + function createFeed($content_type = 'feed', $feed_url = NULL) { $edit = $this->getFeedEditArray($feed_url); - $this->drupalPost('admin/content/aggregator/add/feed', $edit, t('Save')); - $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title']))); + $this->drupalPost('node/add/' . $content_type, $edit, t('Save')); + $test_string = t('!name %title has been created.', array('!name' => node_get_types('name', $content_type), '%title' => $edit['title'])); + $this->assertRaw($test_string, $test_string); - $feed = db_query("SELECT * FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title'], ':url' => $edit['url']))->fetch(); + $feed = db_query("SELECT * FROM {aggregator_feed} f INNER JOIN {node} n ON n.nid = f.nid WHERE n.title = :title AND f.url = :url", array(':title' => $edit['title'], ':url' => $edit['feed[url]']))->fetch(); $this->assertTrue(!empty($feed), t('The feed found in database.')); return $feed; } @@ -42,8 +45,8 @@ class AggregatorTestCase extends DrupalW * Feed object representing the feed. */ function deleteFeed($feed) { - $this->drupalPost('admin/content/aggregator/edit/feed/' . $feed->fid, array(), t('Delete')); - $this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->title)), t('Feed deleted successfully.')); + $this->drupalPost('node/' . $feed->nid . '/delete', array(), t('Delete')); + $this->assertRaw(t('Feed %title has been deleted.', array('%title' => $feed->title)), t('Feed deleted successfully.')); } /** @@ -61,16 +64,16 @@ class AggregatorTestCase extends DrupalW } $edit = array( 'title' => $feed_name, - 'url' => $feed_url, - 'refresh' => '900', + 'feed[url]' => $feed_url, + 'feed[refresh]' => '900', ); return $edit; } /** * Return the count of the randomly created feed array. - * - * @return + * + * @return * Number of feed items on default feed created by createFeed(). */ function getDefaultFeedItemCount() { @@ -80,7 +83,7 @@ class AggregatorTestCase extends DrupalW } /** - * Update feed items (simulate click to admin/content/aggregator/update/$fid). + * Update feed items (simulate click to node/$nid/update). * * @param $feed * Feed object representing the feed. @@ -93,10 +96,10 @@ class AggregatorTestCase extends DrupalW $this->assertResponse(200, t('!url is reachable.', array('!url' => $feed->url))); // Refresh the feed (simulated link click). - $this->drupalGet('admin/content/aggregator/update/' . $feed->fid); + $this->drupalGet('node/' . $feed->nid . '/update'); // Ensure we have the right number of items. - $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid)); + $result = db_query('SELECT iid FROM {aggregator_item} WHERE nid = :nid', array(':nid' => $feed->nid)); $items = array(); $feed->items = array(); foreach ($result as $item) { @@ -113,23 +116,23 @@ class AggregatorTestCase extends DrupalW * Feed object representing the feed. */ function removeFeedItems($feed) { - $this->drupalPost('admin/content/aggregator/remove/' . $feed->fid, array(), t('Remove items')); + $this->drupalPost('node/' . $feed->nid . '/remove-items', array(), t('Remove items')); $this->assertRaw(t('The news items from %title have been removed.', array('%title' => $feed->title)), t('Feed items removed.')); } /** * Add and remove feed items and ensure that the count is zero. - * - * @param $feed + * + * @param $feed * Feed object representing the feed. - * @param $expected_count + * @param $expected_count * Expected number of feed items. */ function updateAndRemove($feed, $expected_count) { $this->updateFeedItems($feed, $expected_count); $this->assertText('There is new syndicated content from'); $this->removeFeedItems($feed); - $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE nid = :nid', array(':nid' => $feed->nid))->fetchField(); $this->assertTrue($count == 0); } @@ -141,7 +144,7 @@ class AggregatorTestCase extends DrupalW */ function getFeedCategories($feed) { // add the categories to the feed so we can use them - $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $feed->fid)); + $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE nid = :nid', array(':nid' => $feed->nid)); foreach ($result as $category) { $feed->categories[] = $category->cid; } @@ -158,7 +161,7 @@ class AggregatorTestCase extends DrupalW * TRUE if feed is unique. */ function uniqueFeed($feed_name, $feed_url) { - $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField(); + $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} f INNER JOIN {node} n ON n.vid = f.vid WHERE n.title = :title AND f.url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField(); return (1 == $result); } @@ -179,18 +182,18 @@ class AggregatorTestCase extends DrupalW - + - + - + - + EOF; @@ -244,7 +247,7 @@ EOF; function createSampleNodes() { // Post 5 articles. - for($i = 0; $i < 5; $i++) { + for ($i = 0; $i < 5; $i++) { $edit = array(); $edit['title'] = $this->randomName(); $edit['body'] = $this->randomName(); @@ -269,11 +272,11 @@ class AddFeedTestCase extends Aggregator $feed = $this->createFeed(); // Check feed data. - $this->assertEqual($this->getUrl(), url('admin/content/aggregator/add/feed', array('absolute' => TRUE)), t('Directed to correct url.')); + $this->assertEqual($this->getUrl(), url('node/' . $feed->nid, array('absolute' => TRUE)), t('Directed to correct url.')); $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), t('The feed is unique.')); // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->drupalGet('aggregator/sources/' . $feed->nid); $this->assertResponse(200, t('Feed source exists.')); $this->assertText($feed->title, t('Page title')); @@ -295,25 +298,25 @@ class UpdateFeedTestCase extends Aggrega * Create a feed and attempt to update it. */ function testUpdateFeed() { - $remamining_fields = array('title', 'url', ''); + $remamining_fields = array('title', 'feed[url]', ''); foreach ($remamining_fields as $same_field) { $feed = $this->createFeed(); // Get new feed data array and modify newly created feed. $edit = $this->getFeedEditArray(); - $edit['refresh'] = 1800; // Change refresh value. + $edit['feed[refresh]'] = 1800; // Change refresh value. if (isset($feed->{$same_field})) { $edit[$same_field] = $feed->{$same_field}; } - $this->drupalPost('admin/content/aggregator/edit/feed/' . $feed->fid, $edit, t('Save')); - $this->assertRaw(t('The feed %name has been updated.', array('%name' => $edit['title'])), t('The feed %name has been updated.', array('%name' => $edit['title']))); + $this->drupalPost('node/' . $feed->nid . '/edit', $edit, t('Save')); + $this->assertRaw(t('Feed %name has been updated.', array('%name' => $edit['title'])), t('The feed %name has been updated.', array('%name' => $edit['title']))); // Check feed data. - $this->assertEqual($this->getUrl(), url('admin/content/aggregator/', array('absolute' => TRUE))); - $this->assertTrue($this->uniqueFeed($edit['title'], $edit['url']), t('The feed is unique.')); + $this->assertEqual($this->getUrl(), url('node/' . $feed->nid, array('absolute' => TRUE))); + $this->assertTrue($this->uniqueFeed($edit['title'], $edit['feed[url]']), t('The feed is unique.')); // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->drupalGet('aggregator/sources/' . $feed->nid); $this->assertResponse(200, t('Feed source exists.')); $this->assertText($edit['title'], t('Page title')); @@ -343,11 +346,11 @@ class RemoveFeedTestCase extends Aggrega $this->deleteFeed($feed); // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->drupalGet('aggregator/sources/' . $feed->nid); $this->assertResponse(404, t('Deleted feed source does not exists.')); // Check database for feed. - $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed->title, ':url' => $feed->url))->fetchField(); + $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} f INNER JOIN {node} n ON n.nid = f.nid WHERE n.title = :title AND f.url = :url", array(':title' => $feed->title, ':url' => $feed->url))->fetchField(); $this->assertFalse($result, t('Feed not found in database')); } } @@ -380,24 +383,24 @@ class UpdateFeedItemTestCase extends Agg // Test updating feed items without valid timestamp information. $edit = array( 'title' => "Feed without publish timestamp", - 'url' => $this->getRSS091Sample(), + 'feed[url]' => $this->getRSS091Sample(), ); - $this->drupalGet($edit['url']); - $this->assertResponse(array(200), t('URL !url is accessible', array('!url' => $edit['url']))); + $this->drupalGet($edit['feed[url]']); + $this->assertResponse(array(200), t('URL !url is accessible', array('!url' => $edit['feed[url]']))); - $this->drupalPost('admin/content/aggregator/add/feed', $edit, t('Save')); - $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title']))); + $this->drupalPost('node/add/feed', $edit, t('Save')); + $this->assertRaw(t('Feed %name has been created.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title']))); - $feed = db_query("SELECT * FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url']))->fetchObject(); - $this->drupalGet('admin/content/aggregator/update/' . $feed->fid); + $feed = db_query("SELECT * FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['feed[url]']))->fetchObject(); + $this->drupalGet('node/' . $feed->nid . '/update'); - $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE nid = :nid', array(':nid' => $feed->nid))->fetchField(); // Sleep for 3 second. sleep(3); db_update('aggregator_feed') - ->condition('fid', $feed->fid) + ->condition('nid', $feed->nid) ->fields(array( 'checked' => 0, 'hash' => '', @@ -405,9 +408,9 @@ class UpdateFeedItemTestCase extends Agg 'modified' => 0, )) ->execute(); - $this->drupalGet('admin/content/aggregator/update/' . $feed->fid); + $this->drupalGet('node/' . $feed->nid . '/update'); - $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE nid = :nid', array(':nid' => $feed->nid))->fetchField(); $this->assertTrue($before === $after, t('Publish timestamp of feed item was not updated (!before === !after)', array('!before' => $before, '!after' => $after))); } @@ -438,9 +441,9 @@ class RemoveFeedItemTestCase extends Agg $feed_urls[] = url('aggregator/test-feed/1/1', array('absolute' => TRUE)); foreach ($feed_urls as $feed_url) { - $feed = $this->createFeed($feed_url); + $feed = $this->createFeed('feed', $feed_url); // Update and remove items two times in a row to make sure that removal - // resets all 'modified' information (modified, etag, hash) and allows for + // resets all 'modified' information (modified, etag, hash) and allows for // immediate update. $this->updateAndRemove($feed, 2); $this->updateAndRemove($feed, 2); @@ -485,7 +488,8 @@ class CategorizeFeedItemTestCase extends db_insert('aggregator_category_feed') ->fields(array( 'cid' => $category->cid, - 'fid' => $feed->fid, + 'vid' => $feed->vid, + 'nid' => $feed->nid, )) ->execute(); $this->updateFeedItems($feed, $this->getDefaultFeedItemCount()); @@ -510,6 +514,70 @@ class CategorizeFeedItemTestCase extends } } +class FeedRevisionTestCase extends AggregatorTestCase { + + private static $prefix = 'simpletest_aggregator_'; + + public static function getInfo() { + return array( + 'name' => t('Revision support'), + 'description' => t('Tries out the revision support for feed settings.'), + 'group' => t('Aggregator'), + ); + } + + /** + * Checks if the revision support works correctly + */ + function testFeedAPI_Node_Revisions() { + + // We need administer nodes permission here + $web_user = $this->drupalCreateUser(array('administer nodes', 'administer news feeds', 'access news feeds', 'create article content', 'create feed content', 'edit any feed content', 'delete any feed content')); + $this->drupalLogin($web_user); + + // Create the initial revision, first submission + $edit = $this->getFeedEditArray(); + $feed = $this->createFeed('feed', $edit['feed[url]']); + $vids[] = $feed->vid; + + $num = db_query('SELECT COUNT(*) FROM {aggregator_feed} WHERE vid = :vid', array(':vid' => $feed->vid))->fetchField(); + $this->assertTrue($num == 1, t('Feed table contains the initial revision just right after the feed creation.')); + + // Edit the node, change a setting, create a new revision + $edit = array(); + $edit['feed[refresh]'] = 1800; + $edit['revision'] = 1; + $edit['log'] = 'changelog blabla'; + $this->drupalPost('node/' . $feed->nid . '/edit', $edit, t('Save')); + $vids[] = db_query("SELECT vid FROM {node} WHERE nid = :nid", array(':nid' => $feed->nid))->fetchField(); + + $this->assertTrue($vids[0] != $vids[1], 'The node has two separate revisions now.'); + $num = db_query('SELECT COUNT(*) FROM {aggregator_feed} WHERE vid = :vid', array(':vid' => $vids[0]))->fetchField(); + $this->assertTrue($num == 1, t('Feed table contains the initial revision.')); + $prev = $num; + $num += db_query('SELECT COUNT(*) FROM {aggregator_feed} WHERE vid = :vid', array(':vid' => $vids[1]))->fetchField(); + $this->assertTrue($num == ($prev + 1), t('Feed table contains the first revision.')); + + $settings = array(); + $settings[] = db_query("SELECT refresh FROM {aggregator_feed} WHERE vid = :vid", array(':vid' => $vids[0]))->fetchField(); + $settings[] = db_query("SELECT refresh FROM {aggregator_feed} WHERE vid = :vid", array(':vid' => $vids[1]))->fetchField();; + $this->assertTrue($settings[0] == 900, t('The initial revision has correct setting')); + $this->assertTrue($settings[1] == 1800, t('The first revision has correct setting')); + + // Do a revert to the initial revision + $this->drupalPost('node/'. $feed->nid .'/revisions/'. $vids[0] .'/revert', array(), 'Revert'); + $vids[] = db_query("SELECT vid FROM {node} WHERE nid = :nid", array(':nid' => $feed->nid))->fetchField(); + $settings[] = db_query("SELECT refresh FROM {aggregator_feed} WHERE vid = :vid", array(':vid' => $vids[2]))->fetchField(); + $this->assertTrue($settings[2] == 900, t('The reverted revision has correct setting.')); + file_put_contents('/tmp/foo', serialize(array($vids, $settings))); + + $this->drupalPost("node/{$feed->nid}/edit", array(), t('Delete')); + $this->drupalPost("node/{$feed->nid}/delete", array(), t('Delete')); + $num = db_query('SELECT COUNT(*) FROM {aggregator_feed} WHERE nid = :nid', array(':nid' => $feed->nid))->fetchField(); + $this->assertTrue($num == 0, t('All revision entries were deleted from FeedAPI table')); + } +} + class ImportOPMLTestCase extends AggregatorTestCase { private static $prefix = 'simpletest_aggregator_'; @@ -608,13 +676,13 @@ class ImportOPMLTestCase extends Aggrega 'category[1]' => $category, ); $this->drupalPost('admin/content/aggregator/add/opml', $form, t('Import')); - $this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url'])), t('Verifying that a duplicate URL was identified')); + $this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['feed[url]'])), t('Verifying that a duplicate URL was identified')); $this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title'])), t('Verifying that a duplicate title was identified')); $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); $this->assertEqual($after, 2, t('Verifying that two distinct feeds were added.')); - $feeds_from_db = db_query("SELECT f.title, f.url, f.refresh, cf.cid FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} cf ON f.fid = cf.fid"); + $feeds_from_db = db_query("SELECT n.title, f.url, f.refresh, cf.cid FROM {node} n INNER JOIN {aggregator_feed} f ON f.nid = n.nid LEFT JOIN {aggregator_category_feed} cf ON f.nid = cf.nid"); $refresh = $category = TRUE; foreach ($feeds_from_db as $feed) { $title[$feed->url] = $feed->title; @@ -623,8 +691,8 @@ class ImportOPMLTestCase extends Aggrega $refresh = $refresh && $feed->refresh == 900; } - $this->assertEqual($title[$feeds[0]['url']], $feeds[0]['title'], t('First feed was added correctly.')); - $this->assertEqual($url[$feeds[1]['title']], $feeds[1]['url'], t('Second feed was added correctly.')); + $this->assertEqual($title[$feeds[0]['feed[url]']], $feeds[0]['title'], t('First feed was added correctly.')); + $this->assertEqual($url[$feeds[1]['title']], $feeds[1]['feed[url]'], t('Second feed was added correctly.')); $this->assertTrue($refresh, t('Refresh times are correct.')); $this->assertTrue($category, t('Categories are correct.')); } @@ -635,3 +703,101 @@ class ImportOPMLTestCase extends Aggrega $this->submitImportForm(); } } + +/** + * Test creating and configuring a new feed content type. Uses test + * hook implementations from aggregator_test module. + */ +class FeedContentTypeTestCase extends AggregatorTestCase { + /** + * Set up test. + */ + function setUp() { + DrupalWebTestCase::setUp('aggregator', 'aggregator_test'); + $web_user = $this->drupalCreateUser(array('administer permissions', 'administer news feeds', 'access news feeds', 'administer content types')); + $this->drupalLogin($web_user); + } + + /** + * Info about test. + */ + function getInfo() { + return array( + 'name' => t('New feed content type'), + 'description' => t('Create and configure a new feed content type, create a node from it and test its functionality.'), + 'group' => t('Aggregator'), + ); + } + + /** + * Create and configure a new feed content type. + * + * @param $content_type + * Type name of the new content type to create. + */ + function createFeedContentType($content_type) { + $edit = array( + 'name' => $content_type, + 'type' => $content_type, + 'aggregator_enabled' => TRUE, + 'aggregator_fetcher' => 'aggregator_test', + 'aggregator_parser' => 'aggregator_test', + 'aggregator_processors[aggregator]' => FALSE, + 'aggregator_processors[aggregator_test]' => 'aggregator_test', + ); + $this->drupalPost('admin/build/types/add', $edit, t('Save content type')); + // Test configuration. + $fetcher = variable_get('aggregator_fetcher_testfeed', 'aggregator'); + $this->assertEqual($fetcher, 'aggregator_test', t('Correct fetcher selected.')); + $processors = variable_get('aggregator_parser_testfeed', 'aggregator'); + $this->assertEqual($processors, 'aggregator_test', t('Correct parser selected.')); + $processors = variable_get('aggregator_processors_testfeed', array('aggregator')); + $this->assertEqual($processors, array('aggregator_test'), t('Correct processors selected.')); + } + + /** + * Update feed items. + * + * @param $feed + * Feed object representing the feed. + */ + function updateFeedItems(&$feed) { + // Refresh the feed (simulated link click). + $this->drupalGet('node/' . $feed->nid . '/update'); + // Check database for stored results. + $parsed = aggregator_test_feed_simple(TRUE); + $processed = variable_get('aggregator_test_processed', array()); + $this->assertEqual($parsed, $processed, t('Found stored results from processing stage.')); + // $this->assertEqual(count(array_diff($parsed, $processed)), 0, t('Found stored results from processing stage.')); + } + + /** + * Remove all feed items from a feed. + * + * @param $feed + * Feed object representing the feed. + */ + function removeFeedItems($feed) { + parent::removeFeedItems($feed); + $this->assertFalse(variable_get('aggregator_test_processed', FALSE), t('Items not present in database.')); + } + + /** + * Run test procedure. + */ + function testFeedContentType() { + // Create a new feed content type. + $this->createFeedContentType('testfeed'); + + // Create a new user and log in, reset static caches. + node_get_types('types', NULL, TRUE); + $this->checkPermissions(array(), TRUE); + $web_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create testfeed content', 'edit any testfeed content', 'delete any testfeed content')); + $this->drupalLogin($web_user); + + // Create a node from the new feed content type, update and remove items from it. + $feed = $this->createFeed('testfeed'); + $this->updateFeedItems($feed); + $this->removeFeedItems($feed); + } +} Index: modules/aggregator/tests/aggregator_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/tests/aggregator_test.module,v retrieving revision 1.1 diff -u -p -r1.1 aggregator_test.module --- modules/aggregator/tests/aggregator_test.module 2 Apr 2009 20:50:37 -0000 1.1 +++ modules/aggregator/tests/aggregator_test.module 4 Apr 2009 14:09:40 -0000 @@ -57,3 +57,79 @@ function aggregator_test_feed($use_last_ print $feed; } + +/** + * Returns a simple test feed either as string or parsed. + * + * @param $parsed + * If TRUE function returns a parsed feed. + * @return + * The feed string if $parsed = FALSE or an array containing the parsed + * feed string. + */ +function aggregator_test_feed_simple($parsed = FALSE) { + $feed = 'test1,test2,test3'; + if ($parsed) { + return explode(',', $feed); + } + return $feed; +} + +/** + * Implementation of hook_aggregator_fetch_info(). + */ +function aggregator_test_aggregator_fetch_info() { + return array( + 'title' => t('Test fetcher'), + 'description' => t('This fetcher is for testing purposes only. Only compatible with Test parser and Test processor.'), + ); +} + +/** + * Implementation of hook_aggregator_fetch(). + * + */ +function aggregator_test_aggregator_fetch($feed) { + $feed->source_string = aggregator_test_feed_simple(); +} + +/** + * Implementation of hook_aggregator_parse_info(). + */ +function aggregator_test_aggregator_parse_info() { + return array( + 'title' => t('Test parser'), + 'description' => t('This parser is for testing purposes only. Only compatible with Test fetcher and Test processor.'), + ); +} + +/** + * Implementation of hook_aggregator_test_parse(). + */ +function aggregator_test_aggregator_parse($feed) { + $feed->items = explode(',', $feed->source_string); +} + +/** + * Implementation of hook_aggregator_process_info(). + */ +function aggregator_test_aggregator_process_info() { + return array( + 'title' => t('Test processor'), + 'description' => t('This processor is for testing purposes only. Only compatible with Test fetcher and Test parser.'), + ); +} + +/** + * Implementation of hook_aggregator_process(). + */ +function aggregator_test_aggregator_process($feed) { + variable_set('aggregator_test_processed', $feed->items); +} + +/** + * Implementation of hook_aggregator_remove(). + */ +function aggregator_test_aggregator_remove($feed) { + variable_del('aggregator_test_processed'); +}