Feeds are based on content-types. Before you can create a feed, you have to go to the admin/content/types/add page. There choose a name for the content-type, then enable the "Is a feed content type" checkbox under the Feed API group. Next choose the processors and parsers that you want to use. At least one parser and one processor must be enabled. Then create the content-type. Make sure that permissions are given to the users who will handle the feeds. '); } } /** * Implementation of hook_menu(). */ function feedapi_menu($may_cache) { if ($may_cache) { $items[] = array( 'path' => 'admin/content/feed', 'title' => t('Feeds'), 'description' => t("Overview which content your site aggregates from other sites and see detailed statistics about the feeds."), 'callback' => 'feedapi_management_page', 'access' => user_access('administer feedapi'), ); $items[] = array( 'path' => 'admin/content/feed/list', 'title' => t('List'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'access' => user_access('administer feedapi'), 'weight' => -15, ); $items[] = array( 'path' => 'admin/content/feed/import_opml', 'title' => t('Import OPML'), 'type' => MENU_NORMAL_ITEM, 'access' => user_access('administer feedapi'), 'callback' => 'drupal_get_form', 'callback arguments' => array('feedapi_import_feeds_form'), ); $items[] = array( 'path' => 'admin/content/feed/export_opml', 'title' => t('Import OPML'), 'type' => MENU_CALLBACK, 'access' => user_access('administer feedapi'), 'callback' => 'drupal_get_form', 'callback arguments' => array('_feedapi_export_opml'), ); $items[] = array('path' => 'admin/settings/feedapi', 'title' => t('FeedAPI settings'), 'callback' => 'drupal_get_form', 'callback arguments' => array('feedapi_admin_settings'), 'type' => MENU_NORMAL_ITEM, 'access' => user_access('administer feedapi'), ); } else if (arg(0) == 'node' && is_numeric(arg(1))) { $node = node_load(arg(1)); if (isset($node->feed)) { global $user; $own_feed = $node->uid == $user->uid && user_access('edit own '. $node->type . ' content') ? TRUE : FALSE; $items[] = array('path' => 'node/'. $node->nid. '/refresh', 'title' => t('Refresh'), 'callback' => 'feedapi_invoke_feedapi', 'callback arguments' => array("refresh", $node->feed), 'type' => MENU_LOCAL_TASK, 'access' => variable_get('feedapi_feed_show_commands_link', TRUE) && (user_access('administer feedapi') || $own_feed), ); $items[] = array('path' => 'node/'. $node->nid. '/purge', 'title' => t('Remove items'), 'callback' => 'feedapi_invoke_feedapi', 'callback arguments' => array("purge", $node->feed, 'items'), 'type' => MENU_LOCAL_TASK, 'access' => variable_get('feedapi_feed_show_commands_link', TRUE) && (user_access('administer feedapi') || $own_feed), ); } } return $items; } /** * Implementation of hook_nodeapi(). */ function feedapi_nodeapi(&$node, $op, $teaser, $page) { if (isset($node->feed) || _feedapi_is_enabled($node->type)) { switch ($op) { case 'insert': if (isset($node->feed->url) && isset($node->feed->feed_type)) { db_query("INSERT INTO {feedapi} ( nid, url, link, feed_type, processors, parsers, checked, settings) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d, '%s')", $node->nid, $node->feed->url, $node->feed->options->link, $node->feed->feed_type, serialize($node->feed->processors), serialize($node->feed->parsers), 0, serialize(array()) ); _feedapi_store_settings(array('nid' => $node->nid), $node->feedapi); } break; case 'update': if (isset($node->feed)) { $old_config = node_load($node->nid); foreach ($old_config->feed->processors as $processor) { if (!in_array($processor, $node->feed->processors)) { $items = module_invoke($processor, 'feedapi_item', 'fetch', $node->feed); foreach ($items as $item) { module_invoke($processor, 'feedapi_item', 'delete', $item); } } } db_query("UPDATE {feedapi} SET url = '%s', feed_type = '%s', processors = '%s', parsers = '%s', link = '%s', items_delete = %d, update_existing = %d WHERE nid = %d", $node->feed->url, $node->feed->feed_type, serialize($node->feed->processors), serialize($node->feed->parsers), $node->feed->options->link, $node->feedapi['feedapi_items_delete'], $node->feedapi['feedapi_update_existing'], $node->nid ); _feedapi_store_settings(array('nid' => $node->nid), $node->feedapi); } break; case 'load': $node->feed = db_fetch_object(db_query('SELECT * FROM {feedapi} WHERE nid = %d', $node->nid)); $node->feed->settings = _feedapi_get_settings(array('nid' => $node->nid)); // ksort is because of ensure the ordering of the processors $node->feed->processors = unserialize($node->feed->processors); $node->feed->statistics = unserialize($node->feed->statistics); if (is_array($node->feed->processors)) { ksort($node->feed->processors); } else { $node->feed->processors = array(); } $node->feed->parsers = unserialize($node->feed->parsers); break; case 'delete': // Could be a performance problem - think of thousands of node feed items. feedapi_invoke_feedapi('purge', $node->feed); db_query("DELETE FROM {feedapi} WHERE nid = %d", $node->nid); break; case 'validate': if (!isset($node->nid) && !empty($node->feedapi['feedapi_url'])) { if (db_result(db_query("SELECT COUNT(*) FROM {feedapi} WHERE url = '%s'", $node->feedapi['feedapi_url'])) > 0) { form_set_error('feedapi_url', t('A feed from this URL (%url) already exists.', array('%url' => $node->feedapi['feedapi_url']))); } $feed = new stdClass(); $feed->url = $node->feedapi['feedapi_url']; $settings = _feedapi_get_settings(array('node_type' => $node->type)); $assigned = _feedapi_assign_parser_processor($feed->url, $settings); $feed->processors = $assigned['processors']; $feed->parsers = $assigned['parsers']; $feed->feed_type = $assigned['type']; if (empty($feed->feed_type)) { form_set_error('feedapi', t('Error parsing feed.')); } else { $feed = _feedapi_call_parsers($feed, $feed->parsers, FALSE); } if (!$feed->title) { form_set_error('feedapi_url', t('Feed information could not be retrieved.')); } } break; case 'submit': $feed = new stdClass(); $feed->url = $node->feedapi['feedapi_url']; $assigned = _feedapi_assign_parser_processor($feed->url, _feedapi_get_settings(array('node_type' => $node->type))); $feed->processors = $assigned['processors']; $feed->parsers = $assigned['parsers']; $feed->feed_type = $assigned['type']; $feed = _feedapi_call_parsers($feed, $feed->parsers, FALSE); $feed->link = $feed->options->link; $node->title = empty($node->title) ? $feed->title : $node->title; $node->body = empty($node->body) ? $feed->description : $node->body; $node->feed->options->link = $feed->options->link; $node->feed->url = $feed->url; $node->feed = $feed; break; } } } /** * Implementation of hook_node_type(). */ function feedapi_node_type($op, $info) { switch ($op){ case 'delete': variable_del('feedapi_settings_'. $info->type); // Todo: find out, why and where there is still feedapi_$info->type being stored. variable_del('feedapi_'. $info->type); break; case 'update': if (!empty($info->old_type) && $info->old_type != $info->type) { $setting = variable_get('feedapi_settings_'. $info->old_type, array()); variable_del('feedapi_settings_'. $info->old_type); variable_set('feedapi_settings_'. $info->type, $setting); } break; } } /** * Implementation of hook_block(). */ function feedapi_block($op = 'list', $delta = 0) { $types = array_keys(node_get_types()); foreach ($types as $key => $type) { if (!_feedapi_is_enabled($type)) { unset($types[$key]); } } switch ($op) { case 'list': foreach ($types as $type) { $blocks[$type]['info'] = t('FeedAPI: Create feed with !preset preset', array('!preset' => $type)); } return $blocks; case 'view': $block['subject'] = t('Create feed with !preset preset', array('!preset' => $delta)); $block['content'] = drupal_get_form('feedapi_simplified_form', $delta); return $block; } } /** * Implementation of hook_perm(). */ function feedapi_perm() { return array('administer feedapi', 'advanced feedapi options'); } /** * Implementation of hook_link(). */ function feedapi_link($type, $node = NULL) { if ($type == 'node' && isset($node->feed)) { if (variable_get('feedapi_show_feed_origin_link', TRUE) && strlen($node->feed->link) > 0) { $links['feedapi_original'] = array( 'title' => t('Link to site'), 'href' => $node->feed->link, ); return $links; } } } /** * Implementation of hook_simpletest(). */ function feedapi_simpletest() { $tests = file_scan_directory(drupal_get_path('module', 'feedapi'), '\.test'); return array_keys($tests); } /** * Do various things with feed. Handle the core data and call the * underyling modules (parsers/processors) too. * TODO: Clean up this function, break it in smaller pieces that are * easier to handle. * * @param $op * "load" Load the feed items basic data into the $feed->items[] * "refresh" Re-download the feed and process newly arrived item * * @param $feed * A feed object. If only the ID is known, you should pass something like this: $feed->nid = X * @param $param * Depends on the $op value. */ function feedapi_invoke_feedapi($op, &$feed, $param = NULL) { if (!is_object($feed)) { return FALSE; } if (!isset($feed->processors)) { $node = node_load($feed->nid); if (!isset($node->feed)) { return FALSE; } $feed = $node->feed; } _feedapi_sanitize_processors($feed); switch ($op) { case 'load': $feed->items = array(); foreach ($feed->processors as $weight) { foreach ($weight as $processor) { $items = module_invoke($processor, 'feedapi_item', 'fetch', $feed); if (is_array($items)) { foreach ($items as $item) { $feed->items[] = $item; } } } } break; case 'refresh': timer_start('feedapi_'. $feed->nid); $cron = $param; if (!is_array($feed->processors) || count($feed->processors) == 0) { if (!$cron) { drupal_set_message(t("This feed (%url) doesn't have any processor. It's not possible to refresh the feed.", array('%url' => $feed->url)), "error"); drupal_goto('node/'. $feed->nid); } return; } $processors = $feed->processors; // Force the processors to delete old items and determine the max. create elements $refresh = feedapi_expire($feed, $cron === TRUE ? FALSE : TRUE); if ($refresh == FALSE) { return; } $nid = $feed->nid; // force non-caching mode if the feed is only processed half-done $feed = _feedapi_call_parsers($feed, $feed->parsers, $feed->half_done ? FALSE : TRUE); if ($feed === FALSE) { // Refresh the Last refresh field at all the cases db_query("UPDATE {feedapi} SET checked = %d, half_done = %d WHERE nid = %d", time(), FALSE, $nid); if (!$cron) { drupal_set_message(t('This feed has not been refreshed after the last check.')); drupal_goto('node/'. $nid); } return; } $feed->processors = $processors; $items = array_reverse($feed->items); $updated = 0; $new = 0; $half_done = FALSE; // Walk through the items foreach ($items as $item) { // Call each item parser $is_updated = FALSE; $is_new = FALSE; foreach ($feed->processors as $weight) { foreach ($weight as $processor) { if (!module_invoke($processor, 'feedapi_item', 'unique', $item, $feed->nid, $feed->settings['processors'][$processor])) { if ($feed->update_existing == TRUE) { module_invoke($processor, 'feedapi_item', 'update', $item, $feed->nid, $feed->settings['processors'][$processor]); $is_updated = TRUE; } } else { module_invoke($processor, 'feedapi_item', 'save', $item, $feed->nid, $feed->settings['processors'][$processor]); $is_new = TRUE; } } } $new = $is_new ? $new + 1 : $new; $updated = ($is_updated && !$is_new) ? $updated + 1 : $updated; // decision on time. If the exec time is greather than the user-set percentage of php max execution time if ($cron && ((timer_read('feedapi_cron') / 1000) > ((variable_get('feedapi_cron_percentage', 15) / 100) * ini_get('max_execution_time')))) { $timeout = TRUE; $half_done = ($new + $updated) == count($items) ? FALSE : TRUE; break; } } foreach (module_implements('feedapi_after_refresh') as $module) { $func = $module. '_feedapi_after_refresh'; $func($feed); } if ($new > 0) { $feed->statistics['update_times'] = _feedapi_queue($feed->statistics['update_times'], time()); $feed->statistics['new'] = _feedapi_queue($feed->statistics['new'], $new); $feed->statistics['download_num'] = _feedapi_queue($feed->statistics['download_num'], count($items)); $feed->statistics['process_time'] = _feedapi_queue($feed->statistics['process_time'], timer_read('feedapi_'. $feed->nid)); } db_query("UPDATE {feedapi} SET checked = %d, statistics = '%s', half_done = %d WHERE nid = %d", time(), serialize($feed->statistics), $half_done, $feed->nid); if (!$cron) { drupal_set_message(t("%new new item(s) were saved. %updated existing item(s) were updated.", array("%new" => $new, "%updated" => $updated))); drupal_goto('node/'. $nid); } if ($timeout === TRUE) { return FALSE; } break; case 'purge': $node = node_load($feed->nid); if ($param == 'items') { return drupal_get_form('feedapi_purge_confirm', $node); } feedapi_invoke_feedapi('load', $feed); // Delete items from the processors foreach ($feed->items as $item) { foreach($feed->processors as $weight) { foreach ($weight as $processor) { // FIXME: it's possible now to delete an item from another processor as an incident module_invoke($processor, 'feedapi_item', 'delete', $item, $feed->settings['processors'][$processor]); } } } if ($param == 'items_confirmed') { drupal_set_message(t('!count feed items have been deleted successfully from the feed.', array('!count' => count($feed->items)))); } break; } } /** * Ask for confirmation before deletig all the items */ function feedapi_purge_confirm($node) { $output = confirm_form( array('nid' => array('#type' => hidden, '#value' => $node->nid)), t('Delete all the feed items from !name', array('!name' => $node->title)), 'node/'. $node->nid, t("Are you sure you want to delete all the feed items from !name?", array('!name' => $node->title)), t('Yes'), t('No'), 'feedapi_purge_confirm' ); return $output; } /** * Submitted items purging form. Drop all the items. */ function feedapi_purge_confirm_submit($form_id, $form_values) { $feed->nid = $form_values['nid']; feedapi_invoke_feedapi('purge', $feed, 'items_confirmed'); drupal_goto('node/'. $form_values['nid']); } /** * OPML Feed import form, also allows setting defaults to be applied to each feed */ function feedapi_import_feeds_form() { global $user; $form['#attributes'] = array('enctype' => 'multipart/form-data'); $form['opml'] = array( '#type' => 'file', '#title' => t('OPML File'), '#size' => 50, '#description' => t('Upload an OPML file containing a list of newsfeeds to be imported.'), ); $nt = node_get_types(); foreach ($nt as $type => $info) { if (variable_get('feedapi_settings_'. $type, FALSE) !== FALSE) { $feed_types[$type] = $info->name; } } $form['feed_type'] = array( '#type' => 'select', '#title' => t('Feed Type'), '#description' => t("The type of feed you would like to associate this import with."), '#options' => $feed_types, '#default_value' => '', '#size' => 1, '#required' => TRUE, ); $node = new stdClass(); $node->uid = $user->uid; $form['#node'] = $node; $form['#method'] = 'post'; $form['#attributes']['enctype'] = 'multipart/form-data'; $form['submit'] = array('#type' => 'submit', '#value' => 'Submit'); if (module_exists('og')) { og_form_add_og_audience('feedapi_import_feeds_form', $form); } return $form; } /** * Handle the submission of the OPML import form */ function feedapi_import_feeds_form_submit($form_id, $form_values) { $file = file_check_upload('opml'); if ($file = file($file->filepath)) { $file = implode('', $file); if ($count = _feedapi_import_process($file, $form_values)) { drupal_set_message(t('Successfuly imported %count feeds from OPML', array('%count' => $count))); } else { drupal_set_message(t('Feed list could not be imported. Please check that this is an OPML file.'), 'error'); } } else { drupal_set_message(t('Data could not be retrieved, invalid or empty file.'), 'error'); } return 'admin/content/feed'; } /** * Delete expired items and return informations about the feed refreshing * * @param $feed * The feed object * @param $force * If TRUE, do not examine that the feed needs refresh * @return * FALSE if the feed don't have to be refreshed. (forbidden if the $force is TRUE) */ function feedapi_expire($feed, $force) { $needs_refresh = time() - $feed->checked > $feed->refresh; if (is_array($feed->items) && ($needs_refresh || $force) && $feed->items_delete != FEEDAPI_NEVER_DELETE_OLD) { foreach ($feed->items as $item) { if (isset($item->arrived) || isset($item->timestamp)) { $diff = abs(time() - (isset($item->arrived) ? $item->arrived : $item->timestamp)); if ($diff > $feed->items_delete) { foreach($feed->processors as $weight) { foreach ($weight as $processor) { module_invoke($processor, 'feedapi_item', 'delete', $item, $feed->nid); } } } } } } if ($needs_refresh == FALSE && $force == FALSE) { return FALSE; } return TRUE; } /** * Implementation of hook_form_alter(). */ function feedapi_form_alter($form_id, &$form) { // Content type form. if ($form_id == 'node_type_form' && isset($form['identity']['type'])) { if (isset($form['#post']['feedapi'])) { // TODO: Drupal automatically stores mutilated 'feedapi_'. $form['#node_type']->type - remove. $type = !empty($form['#node_type']->type) ? $form['#node_type']->type : $form['#post']['type']; _feedapi_store_settings(array('node_type' => $type), $form['#post']['feedapi']); } if (!$settings = _feedapi_get_settings(array('node_type' => $form['#node_type']->type))) { $settings = array(); } $form['feedapi'] = array( '#type' => 'fieldset', '#title' => t('Feed API'), '#collapsible' => TRUE, '#collapsed' => !$settings['enabled'], '#tree' => TRUE, ); $form['feedapi']['enabled'] = array( '#type' => 'checkbox', '#title' => t('Is a feed content type'), '#description' => t('Check this box if you want to use this content type for downloading feeds to your site.'), '#default_value' => $settings['enabled'], '#weight' => -15, ); $form['feedapi']['parsers'] = array( '#type' => 'fieldset', '#title' => t('Parser settings'), '#description' => t('Parsers turn a feed into an object ready for processing. Choose at least one.'), '#collapsible' => FALSE, '#tree' => TRUE, ); $parsers = module_implements('feedapi_feed', TRUE); rsort($parsers); foreach ($parsers as $parser) { $form['feedapi']['parsers'][$parser] = array( '#type' => 'fieldset', '#title' => feedapi_get_natural_name($parser), '#collapsible' => TRUE, '#collapsed' => !($settings['parsers'][$parser]['enabled'] || (count($parsers) == 1)), '#tree' => TRUE, '#weight' => $settings['parsers'][$parser]['weight'], ); $form['feedapi']['parsers'][$parser]['enabled'] = array( '#type' => 'checkbox', '#title' => t('Enable'), '#description' => t('Check this box if you want to enable the @name parser on this feed.', array('@name' => t($parser))), '#default_value' => $settings['parsers'][$parser]['enabled'] || (count($parsers) == 1), '#weight' => -15, ); $form['feedapi']['parsers'][$parser]['weight'] = array( '#type' => 'weight', '#delta' => 15, '#title' => t('Weight'), '#description' => t('Control the execution order. Parsers with lower weights are called before parsers with higher weights.'), '#default_value' => $settings['parsers'][$parser]['weight'], '#weight' => -14, ); } $form['feedapi']['processors'] = array( '#type' => 'fieldset', '#title' => t('Processor settings'), '#description' => t('Processors are any kind of add on modules that hook into the feed handling process on download time - you can decide here what should happen to feed items once they are downloaded and parsed.'), '#collapsible' => FALSE, '#tree' => TRUE, ); $processors = module_implements('feedapi_item', TRUE); rsort($processors); foreach ($processors as $processor) { $form['feedapi']['processors'][$processor] = array( '#type' => 'fieldset', '#title' => feedapi_get_natural_name($processor), '#collapsible' => TRUE, '#collapsed' => !($settings['processors'][$processor]['enabled'] || (count($processors) == 1)), '#tree' => TRUE, '#weight' => $settings['processors'][$processor]['weight'], ); $form['feedapi']['processors'][$processor]['enabled'] = array( '#type' => 'checkbox', '#title' => t('Enable'), '#description' => t('Check this box if you want to enable the @name processor on this feed.', array('@name' => t($processor))), '#default_value' => $settings['processors'][$processor]['enabled'], '#weight' => -15, ); $form['feedapi']['processors'][$processor]['weight'] = array( '#type' => 'weight', '#delta' => 15, '#title' => t('Weight'), '#description' => t('Control the execution order. Processors with lower weights are called before processors with higher weights.'), '#default_value' => $settings['processors'][$processor]['weight'], '#weight' => -14, ); } $form['feedapi'] = array_merge_recursive($form['feedapi'], _feedapi_invoke_settings_form('node_type_form', $form['#node_type']->type)); // We add again to ensure the correct order. Without it, the submit and delete buttons are in a wrong place (not last) $submit = $form['submit']; $delete = $form['delete']; unset($form['submit'], $form['delete']); $form['submit'] = $submit; $form['delete'] = $delete; } // FeedAPI-enabled node form. if ($form['type']['#value'] .'_node_form' == $form_id && _feedapi_is_enabled($form['type']['#value'])) { $form['title']['#required'] = FALSE; $form['title']['#description'] = t('This field will be populated with the feed title. You can override by filling in this field.'); $form['body_filter']['body']['#description'] = t('This field will be populated with the feed description. You can override by filling in this field.'); $form['feedapi'] = array( '#type' => 'fieldset', '#title' => t('Feed'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#tree' => TRUE, ); $feed_url = $form['#node']->feed->url ? $form['#node']->feed->url : $form['#post']['feedapi']['feedapi_url']; $form['feedapi']['feedapi_url'] = array( '#type' => 'textfield', '#title' => t('Feed URL'), '#description' => t('Enter feed URL.'), '#default_value' => $feed_url, '#maxlength' => 255, ); // Prepopulate title and description if not set yet and URL available. if ($feed_url && !($form['#post']['title'] && $form['#post']['body'])) { // Todo: duplicate code - see nodeapi('validate') and 'submit' $feed = new stdClass(); $feed->url = $feed_url; $settings = _feedapi_get_settings(array('node_type' => $form['type']['#value'])); $assigned = _feedapi_assign_parser_processor($feed_url, $settings); $feed->processors = $assigned['processors']; $feed->parsers = $assigned['parsers']; $feed->feed_type = $assigned['type']; if (!empty($feed->feed_type)) { $feed = _feedapi_call_parsers($feed, $feed->parsers, FALSE); } if (!$form['#post']['title']) { $form['title']['#value'] = $feed->title; } if (!$form['#post']['body']) { $form['body_filter']['body']['#value'] = $feed->description; } } $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 3628800, 4838400, 7257600, 15724800, 31536000), 'format_interval'); $period[FEEDAPI_NEVER_DELETE_OLD] = t('Never'); $form['feedapi']['feedapi_items_delete'] = array( '#type' => 'select', '#title' => t('Delete news items older than'), '#options' => $period, '#default_value' => isset($form['#node']->feed->items_delete) ? $form['#node']->feed->items_delete : $form['#post']['feedapi_items_delete'], ); if (isset($form['#node']->feed->update_existing)) { $update_existing = $form['#node']->feed->update_existing; } elseif (isset($form['#post']['feedapi_update_existing'])) { $update_existing = $form['#post']['feedapi_update_existing']; } else { $update_existing = TRUE; } $update_existing = isset($form['#node']->feed->update_existing) ? $form['#node']->feed->update_existing : $form['#post']['feedapi_update_existing']; $form['feedapi']['feedapi_update_existing'] = array( '#type' => 'checkbox', '#title' => t('Update already existing news items'), '#default_value' => $update_existing, ); // Retrieve form snippets from add on modules. $form_snippets = _feedapi_invoke_settings_form('node_form', $form['type']['#value'], $form['#node']->nid); // Format snippets - make fieldsets out of them. foreach ($form_snippets as $k => $stage) { $form_snippets[$k]['#type'] = 'fieldset'; $form_snippets[$k]['#title'] = feedapi_get_natural_name($k); $form_snippets[$k]['#collapsible'] = TRUE; $form_snippets[$k]['#collapsed'] = TRUE; $form_snippets[$k]['#tree'] = TRUE; foreach (array_keys($stage) as $m) { $form_snippets[$k][$m]['#type'] = 'fieldset'; $form_snippets[$k][$m]['#title'] = feedapi_get_natural_name($m); $form_snippets[$k][$m]['#collapsible'] = TRUE; $form_snippets[$k][$m]['#collapsed'] = FALSE; $form_snippets[$k][$m]['#tree'] = TRUE; } } $form_snippets['#tree'] = TRUE; $form['feedapi'] = array_merge_recursive($form['feedapi'], $form_snippets); } } /** * Implementation of hook_cron(). */ function feedapi_cron() { $result = db_query("SELECT nid FROM {feedapi} ORDER BY checked ASC"); timer_start('feedapi_cron'); while ($feed = db_fetch_object($result, $row++)) { feedapi_invoke_feedapi('load', $feed); $timeout = feedapi_invoke_feedapi('refresh', $feed, TRUE); if ($timeout === FALSE) { break; } } } /** * Provide a UI for overviewing the existing feeds */ function feedapi_management_page() { $header = array( t('Title'), t('Last refresh'), t('New elements added per update'), t('Update rate'), t('Number of items in the feed'), t('Processing time'), t('Commands'), ); $rows = array(); $result = db_query("SELECT nid from {feedapi} ORDER BY checked DESC"); while ($nid = db_result($result, $row++)) { $node = node_load($nid); $commands = array(l(t('Delete'), 'node/'. $node->nid . '/delete', NULL, 'destination=admin/content/feed'), l(t('Remove items'), 'node/'. $node->nid . '/purge', NULL, 'destination=admin/content/feed'), l(t('Refresh'), 'node/'. $node->nid . '/refresh'), l(t('Edit'), 'node/'. $node->nid . '/edit'), ); $ext_commands = module_invoke_all('feedapi_edit_option', $node->feed); if (count($ext_commands) > 0) { foreach ($ext_commands as $command) { $commands[] = l($command['name'], $command['link']); } } if (count($node->feed->statistics['download_num']) != 0 && count($node->feed->statistics['new']) != 0 && count($node->feed->statistics['process_time']) != 0) { $update_rate = _feedapi_update_rate($node->feed->statistics['update_times']); $rows[] = array( l($node->title, "node/$node->nid"), $node->feed->checked == 0 ? t('Never') : format_interval(time() - $node->feed->checked) ." ". t("ago"), round(array_sum($node->feed->statistics['new']) / count($node->feed->statistics['new']), 2), is_numeric($update_rate) ? format_interval($update_rate) : $update_rate, round((array_sum($node->feed->statistics['download_num']) / count($node->feed->statistics['download_num'])), 2), round((array_sum($node->feed->statistics['process_time']) / count($node->feed->statistics['process_time'])), 2). ' '. t('ms'), theme('item_list', $commands), ); } else { $rows[] = array( l($node->title, "node/$node->nid"), $node->feed->checked == 0 ? t('Never') : format_interval(time() - $node->feed->checked) ." ". t("ago"), '', '', t('No enough data for statistics'), '', theme('item_list', $commands), ); } } $info = format_plural(variable_get('feedapi_statistics_queue', 10), "Average over the last refresh.", "Averages over the last @count refreshes." ); return $info. theme('table', $header, $rows). t(''). l(t('Export site feeds as OPML'), 'admin/content/feed/export_opml'); } /** * This is shown instead of normal node form when the simplified form is chosen at the settings */ function feedapi_simplified_form($type) { $form['feedapi']['type'] = array( '#type' => 'hidden', '#value' => $type ); $form['feedapi']['feedapi_url'] = array( '#title' => t('Feed URL'), '#type' => 'textfield', '#size' => 40, '#required' => TRUE, '#maxlength' => 255, ); $form['feedapi']['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), ); $form['feedapi']['next'] = array( '#type' => 'submit', '#value' => t('Submit and edit'), ); return $form; } /** * Validate by feedapi_nodeapi */ function feedapi_simplified_form_validate($form_id, $form_values) { $node = (object) $form_values; feedapi_nodeapi($node, 'validate', NULL, NULL); } /** * Create the node object and save */ function feedapi_simplified_form_submit($form_id, $form_values) { $node = (object) $form_values; $node_options = variable_get('node_options_'. $node->type, array('status', 'promote')); // If this is a new node, fill in the default values. foreach (array('status', 'promote', 'sticky') as $key) { $node->$key = in_array($key, $node_options); } node_object_prepare($node); $node = node_submit($node); global $user; $node->uid = $user->uid; node_save($node); drupal_goto('node/'. $node->nid. ($form_values['op'] == t('Submit and edit') ? '/edit' : '')); } /** * Get the module-defined natural name of FeedAPI parser or processor * Define this name in hook_help(): * * function hook_help($section) { * switch ($section) { * case 'feedapi/full_name': * return t('Natural name'); * break; * } * } */ function feedapi_get_natural_name($module) { $help = $module .'_help'; $module_natural = function_exists($help) ? $help('feedapi/full_name') : t($module); return empty($module_natural) ? t($module) : $module_natural; } /** * Settings: * Allowed HTML tags, number of feeds refreshed in one round */ function feedapi_admin_settings() { $form['feedapi_allowed_html_tags'] = array( '#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#size' => 80, '#maxlength' => 255, '#default_value' => variable_get('feedapi_allowed_html_tags', '