'. t('Provides feed management interface and handles underlying processors and parsers for any type of feeds.') .'

'; $output .= '

'. t('Feeds are based on content types. Default content types are created on install. You can create new content types on the add content types page. To do that, enable the "Is a feed content type" checkbox under the Feed API group on the content type edit form. Then choose the processors and parsers that you would like to use. At least one parser and one processor must be enabled.', array('@content-types' => url('admin/content/types/add'))) .'

'; return $output; case 'admin/content/feed': return '

'. t('Current feeds are listed below. For each FeedAPI-enabled content type, the Quick create block may be enabled at the blocks administration page.', array('@block' => url('admin/build/block'))) .'

'; case 'admin/content/feed/import_opml': return '

'. t('Feeds can be imported from a valid OPML file. You can check your OPML file at OPML Validator.', array('@validator' => url('http://validator.opml.org/'))) .'

'; case 'admin/settings/feedapi': return '

'. t('You can find more configuration options on the content type edit form of FeedAPI-enabled content types.', array('@content-types' => url('admin/content/types'))) .'

'; } } /** * Implementation of hook_theme(). */ function feedapi_theme() { return array( 'feedapi_export_opml' => array( 'arguments' => array('feeds' => NULL), ), ); } /** * Implementation of hook_menu(). */ function feedapi_menu() { $items = array(); $items['admin/content/feed'] = array( 'title' => 'Feeds', 'description' => 'Overview which content your site aggregates from other sites and see detailed statistics about the feeds.', 'page callback' => 'feedapi_admin_overview', 'access arguments' => array('administer feedapi'), ); $items['admin/content/feed/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'access arguments' => array('administer feedapi'), 'weight' => -15, ); $items['admin/content/feed/import_opml'] = array( 'title' => 'Import OPML', 'access arguments' => array('administer feedapi'), 'page callback' => 'drupal_get_form', 'page arguments' => array('feedapi_import_opml'), ); $items['admin/content/feed/export_opml'] = array( 'title' => 'Export all feeds as OPML', 'access arguments' => array('administer feedapi'), 'page callback' => 'feedapi_export_opml', ); $items['admin/settings/feedapi'] = array( 'title' => 'FeedAPI settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('feedapi_admin_settings'), 'access arguments' => array('administer feedapi'), ); $items['node/%node/refresh'] = array( 'title' => 'Refresh', 'page callback' => 'feedapi_refresh', 'page arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'access callback' => '_feedapi_op_access', 'access arguments' => array(1), ); $items['node/%node/purge'] = array( 'title' => 'Remove items', 'page callback' => 'feedapi_invoke', 'page arguments' => array("purge", 1, 'items'), 'type' => MENU_LOCAL_TASK, 'access callback' => '_feedapi_op_access', 'access arguments' => array(1), ); return $items; } function _feedapi_op_access($node) { if (!feedapi_enabled_type($node->type)) { return FALSE; } global $user; $own_feed = $node->uid == $user->uid && user_access('edit own '. $node->type .' content') ? TRUE : FALSE; return user_access('administer feedapi') || $own_feed; } /** * Implementation of hook_nodeapi(). */ function feedapi_nodeapi(&$node, $op, $teaser, $page) { if (isset($node->feed) || feedapi_enabled_type($node->type)) { switch ($op) { case 'insert': _feedapi_insert($node, $teaser, $page); break; case 'update': _feedapi_update($node, $teaser, $page); break; case 'load': if ($feed = db_fetch_object(db_query('SELECT * FROM {feedapi} WHERE nid = %d', $node->nid))) { $node->feed = $feed; $node->feed->nid = $node->nid; $node->feed->settings = feedapi_get_settings($node->type, $node->nid); // Load parsers and processors from content type $node_type_settings = feedapi_get_settings($node->type); $node->feed->parsers = _feedapi_format_settings($node_type_settings, 'parsers'); $node->feed->processors = _feedapi_format_settings($node_type_settings, 'processors'); } break; case 'delete': // Could be a performance problem - think of thousands of node feed items. // This is a temporary status. See: http://drupal.org/node/195723 // feedapi_invoke('purge', $node->feed); db_query("DELETE FROM {feedapi_stat} WHERE id = %d", $node->nid); db_query("DELETE FROM {feedapi} WHERE nid = %d", $node->nid); break; case 'presave': if (is_array($node->feedapi) || isset($node->feedapi_object)) { $node->feed = isset($node->feedapi_object) ? $node->feedapi_object : _feedapi_build_feed_object($node->type, $node->feedapi['feedapi_url']); } break; } } } /** * Implementation of hook_node_type(). */ function feedapi_node_type($op, $info) { switch ($op) { case 'delete': variable_del('feedapi_settings_'. $info->type); 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) { $blocks = array(); $names = feedapi_get_types(); switch ($op) { case 'list': foreach ($names as $type => $name) { $blocks[$type]['info'] = t('FeedAPI: Quick create !preset', array('!preset' => $name)); $blocks[$type]['cache'] = BLOCK_CACHE_GLOBAL; } break; case 'view': if (node_access('create', $delta)) { $blocks['subject'] = t('Create !preset', array('!preset' => $names[$delta])); $blocks['content'] = drupal_get_form('feedapi_simplified_form', $delta); } break; } return $blocks; } /** * 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 (strlen($node->feed->link) > 0) { $links['feedapi_original'] = array( 'title' => t('Link to site'), 'href' => $node->feed->link, ); return $links; } } } /** * Implementation of hook_node_views(). */ function feedapi_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'feedapi') .'/views', ); } /** * Invoke feedapi API callback functions. * * @param $op * "load" Load the feed items basic data into the $feed->items[] * "refresh" Re-download the feed and process newly arrived item * "purge" Delete all the feed items * * @param $feed * A feed object. If only the ID is known, you should pass something like this: $feed->nid = X * @param $param * Depends on the $op value. */ function feedapi_invoke($op, &$feed, $param = NULL) { if (!is_object($feed)) { return FALSE; } // The node is passed. if (isset($feed->feed) && is_object($feed->feed)) { $feed = $feed->feed; } 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': return _feedapi_invoke_load($feed, $param); case 'refresh': return _feedapi_invoke_refresh($feed, $param); case 'purge': return _feedapi_invoke_purge($feed, $param); default: // Other operations return _feedapi_invoke($op, $feed, $param); } } /** * Ask for confirmation before deleting all the items */ function feedapi_purge_confirm($form_state, $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, &$form_state) { $feed->nid = $form_state['values']['nid']; feedapi_invoke('purge', $feed, 'items_confirmed'); $form_state['redirect'] = 'node/'. $form_state['values']['nid']; } /** * OPML Feed import form, also allows setting defaults to be applied to each feed */ function feedapi_import_opml($form_state) { $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.'), ); $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' => feedapi_get_types(), '#required' => TRUE, ); $form['override_title'] = array( '#type' => 'checkbox', '#title' => t('Use TITLE attribute of OPML entries as feed title'), '#description' => t('If checked feed title will be overriden with the information from OPML file'), ); $form['override_body'] = array( '#type' => 'checkbox', '#title' => t('Use TEXT attribute of OPML entries as feed description'), '#description' => t('If checked feed description will be overriden with the information from OPML file'), ); $form['#attributes']['enctype'] = 'multipart/form-data'; $form['submit'] = array('#type' => 'submit', '#value' => 'Submit'); if (module_exists('og')) { og_form_add_og_audience($form, $form_state); } return $form; } /** * Handle the submission of the OPML import form */ function feedapi_import_opml_submit($form, &$form_state) { $file = file_save_upload('opml'); if ($file = file($file->filepath)) { $file = implode('', $file); if ($count = _feedapi_import_opml($file, $form_state['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 a valid OPML file.'), 'error'); } } else { drupal_set_message(t('Data could not be retrieved, invalid or empty file.'), 'error'); } $form_state['redirect'] = 'admin/content/feed'; } /** * Delete expired items and return informations about the feed refreshing * * @param $feed * The feed object * @param $settings * Optional feed settings * @return * FALSE if the feed don't have to be refreshed. (forbidden if the $force is TRUE) */ function feedapi_expire($feed, $settings = NULL) { // Backwards compatibility, get settings if not passed $settings = is_null($settings) ? feedapi_get_settings(NULL, $feed->nid) : $settings; // Each processor can have its own expiration criteria ? $expired = _feedapi_invoke('expire', $feed, $settings); // Return the number of expired items return $expired ? array_sum($expired) : 0; } /** * Callback for expired items. Does the actual deleting */ function feedapi_expire_item($feed, $item) { foreach ($feed->processors as $processor) { module_invoke($processor, 'feedapi_item', 'delete', $item, $feed->nid); } } /** * Implementation of hook_form_alter(). */ function feedapi_form_alter(&$form, $form_state, $form_id) { // Content type form. if ($form_id == 'node_type_form' && isset($form['identity']['type'])) { $node_type_settings = feedapi_get_settings($form['#node_type']->type); $form['#validate'][] = 'feedapi_content_type_validate'; $form['feedapi'] = array( '#type' => 'fieldset', '#title' => t('Feed API'), '#collapsible' => TRUE, '#collapsed' => isset($node_type_settings['enabled']) ? !($node_type_settings['enabled']) : TRUE, '#tree' => TRUE, ); $form['feedapi']['enabled'] = array( '#type' => 'checkbox', '#title' => t('Is a feed content type'), '#description' => t('Check if you want to use this content type for downloading feeds to your site.'), '#default_value' => isset($node_type_settings['enabled']) ? $node_type_settings['enabled'] : FALSE, '#weight' => -15, ); $modules = module_implements('feedapi_settings_form'); foreach ($modules as $module) { $form['feedapi']['defaults'] = array('#type' => 'markup', '#value' => ''. t('Default settings') .'
'); if ($feedapi_form = module_invoke($module, 'feedapi_settings_form', 'general')) { $form['feedapi'] = array_merge_recursive($form['feedapi'], $feedapi_form); } } $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' => isset($node_type_settings['parsers'][$parser]['enabled']) ? !($node_type_settings['parsers'][$parser]['enabled']) : TRUE, '#tree' => TRUE, '#weight' => isset($node_type_settings['parsers'][$parser]['weight']) ? $node_type_settings['parsers'][$parser]['weight']: 0, ); $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' => isset($node_type_settings['parsers'][$parser]['enabled']) ? $node_type_settings['parsers'][$parser]['enabled'] : FALSE, '#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' => isset($node_type_settings['parsers'][$parser]['weight']) ? $node_type_settings['parsers'][$parser]['weight'] : 0, '#weight' => -14, ); if ($parser_form = module_invoke($parser, 'feedapi_settings_form', 'parsers')) { $form['feedapi']['parsers'][$parser]['defaults'] = array('#type' => 'markup', '#value' => ''. t('Default settings') .'
'); $form['feedapi']['parsers'][$parser] = array_merge_recursive($form['feedapi']['parsers'][$parser], $parser_form); } } $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' => isset($node_type_settings['processors'][$processor]['enabled']) ? !($node_type_settings['processors'][$processor]['enabled']): TRUE, '#tree' => TRUE, '#weight' => isset($node_type_settings['processors'][$processor]['weight']) ? $node_type_settings['processors'][$processor]['weight'] : 0, ); $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' => isset($node_type_settings['processors'][$processor]['enabled']) ? $node_type_settings['processors'][$processor]['enabled'] : FALSE, '#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' => isset($node_type_settings['processors'][$processor]['weight']) ? $node_type_settings['processors'][$processor]['weight'] : 0, '#weight' => -14, ); if ($processor_form = module_invoke($processor, 'feedapi_settings_form', 'processors')) { $form['feedapi']['processors'][$processor]['defaults'] = array('#type' => 'markup', '#value' => ''. t('Default settings') .'
'); $form['feedapi']['processors'][$processor] = array_merge_recursive($form['feedapi']['processors'][$processor], $processor_form); } } // Populate form with node type settings if available. if ($node_type_settings) { $form['feedapi'] = _feedapi_populate($form['feedapi'], $node_type_settings); } $form['#submit'][] = 'feedapi_content_type_submit'; } elseif (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id && feedapi_enabled_type($form['type']['#value'])) { // FeedAPI-enabled node form. $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, ); $feedapi_url_default = ''; if (isset($form['#node']->feed->url)) { $feedapi_url_default = $form['#node']->feed->url; } elseif (isset($form_state['values']['feedapi']['feedapi_url'])) { $feedapi_url_default = $form_state['values']['feedapi']['feedapi_url']; } $form['feedapi']['feedapi_url'] = array( '#type' => 'textfield', '#title' => t('Feed URL'), '#description' => t('Enter feed URL.'), '#default_value' => $feedapi_url_default, '#maxlength' => 2048, ); // Show per-node-type feedapi, parser options only for users with permissions. if (user_access('advanced feedapi options')) { $modules = module_implements('feedapi_settings_form'); foreach ($modules as $module) { if ($feedapi_form = module_invoke($module, 'feedapi_settings_form', 'general')) { $form['feedapi'] = array_merge_recursive($form['feedapi'], $feedapi_form); } } // Get settings for corresponding content type // Which parsers / processors are enabled is a per content-type setting. $node_type_settings = feedapi_get_settings($form['type']['#value']); // retrieve forms. foreach (array("parsers" => "feedapi_feed", "processors" => "feedapi_item") as $type => $requirement) { $suitable_handlers = module_implements($requirement, TRUE); foreach ($suitable_handlers as $module) { if (isset($node_type_settings[$type][$module]) && $node_type_settings[$type][$module]['enabled']) { $result = array(); $result = module_invoke($module, 'feedapi_settings_form', $type); if (is_array($result)) { $result['#weight'] = $node_type_settings[$type][$module]['weight']; $form['feedapi'][$type][$module] = $result; $form['feedapi'][$type][$module]['#type'] = 'fieldset'; $form['feedapi'][$type][$module]['#title'] = feedapi_get_natural_name($module); $form['feedapi'][$type][$module]['#collapsible'] = TRUE; $form['feedapi'][$type][$module]['#collapsed'] = FALSE; $form['feedapi'][$type][$module]['#tree'] = TRUE; } } } if (isset($form['feedapi'][$type])) { $form['feedapi'][$type]['#type'] = 'fieldset'; $form['feedapi'][$type]['#title'] = t(ucfirst($type)); $form['feedapi'][$type]['#collapsible'] = TRUE; $form['feedapi'][$type]['#collapsed'] = TRUE; $form['feedapi'][$type]['#tree'] = TRUE; } } } // If we are on a node form, get per node settings and populate form. if (isset($form['#node']->nid)) { $settings = feedapi_get_settings($form['type']['#value'], $form['#node']->nid); } else { $settings = $node_type_settings; } $form['feedapi'] = _feedapi_populate($form['feedapi'], $settings); $form['#validate'][] = 'feedapi_node_validate'; } } /** * Build feed object on validate and submit. * See feedapi_form_alter on finding out how it is called (via FormAPI) */ function feedapi_node_validate($form, &$form_state) { $feed = _feedapi_build_feed_object($form_state['values']['type'], $form_state['values']['feedapi']['feedapi_url']); // Stick feed object into feedapi form snippet - store it in submit. $form_state['values']['feedapi_object'] = $feed; if (empty($form_state['values']['title']) && isset($feed->title)) { form_set_value($form['title'], $feed->title, $form_state); } //dprint_r($form_state['values']); if (empty($form_state['values']['body']) && isset($feed->description)) { form_set_value($form['body_field']['body'], $feed->description, $form_state); } if (empty($form_state['values']['title'])) { form_set_error('title', t('Title could not be retrieved from feed.')); } } /** * Store per-content-type settings */ function feedapi_content_type_submit($form, &$form_state) { // 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_state['values']['feedapi']); } /** * Implementation of hook_feedapi_settings_form(). */ function feedapi_feedapi_settings_form($type) { if ($type == 'general') { $form['refresh_on_create'] = array( '#type' => 'checkbox', '#title' => t('Refresh feed on creation'), '#description' => t('If checked, feed items will be processed immediately after a feed is created.'), '#default_value' => 0, ); $form['update_existing'] = array( '#type' => 'checkbox', '#title' => t('Update existing feed items'), '#description' => t('If checked, existing feed items will be updated when feed is refreshed.'), '#default_value' => 1, ); $form['skip'] = array( '#type' => 'checkbox', '#title' => t('Pause automatic feed update'), '#description' => t('If checked, feed will not be updated automatically on cron.'), '#default_value' => 0, ); $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['items_delete'] = array( '#type' => 'select', '#title' => t('Delete news items older than'), '#options' => $period, '#default_value' => FEEDAPI_NEVER_DELETE_OLD, ); } return $form; } /** * Implementation of hook_cron(). */ function feedapi_cron() { db_query('DELETE FROM {feedapi_stat} WHERE timestamp < %d', variable_get('cron_semaphore', FALSE) - FEEDAPI_CRON_STAT_LIFETIME); // Initialize counters $count = array( '%feeds' => 0, '%expired' => 0, '%new' => 0, '%updated' => 0, ); // We get feeds in small lots, this will save memory and have the process adjusting to the // time limit even when we have many thousands of them. $start = time() - FEEDAPI_CRON_MIN_REFRESH_TIME; $process = 0; // The counter process will be > 0 if we've selected less feeds while (!$process && feedapi_cron_time()) { $process = FEEDAPI_CRON_FEEDS; $result = db_query_range("SELECT nid FROM {feedapi} WHERE checked <= %d AND skip = 0 ORDER BY checked", $start, 0, FEEDAPI_CRON_FEEDS); while (feedapi_cron_time() && $feed = db_fetch_object($result)) { // Call the refresh process for each feed and store counters $counter = feedapi_invoke('refresh', $feed, TRUE); if ($counter) { foreach ($counter as $name => $value) { $count['%'. $name] += $value; } } $count['%feeds']++; $process--; } } } /** * Check for time limits in cron processing. * * @return * Number of seconds left, zero if none. */ function feedapi_cron_time() { static $time_limit; if (!$time_limit) { $time_limit = time() + (variable_get('feedapi_cron_percentage', 15) / 100) * ini_get('max_execution_time'); // However, check for left time, maybe some other cron processing already occured $time_limit = min($time_limit, variable_get('cron_semaphore', 0) + ini_get('max_execution_time')); timer_start('feedapi_cron'); } return max($time_limit - time(), 0); } /** * Provide a UI for overviewing the existing feeds */ function feedapi_admin_overview() { $header = array( t('Title'), t('Last refresh'), t('New items added per update'), t('Update rate'), t('Number of items'), t('Processing time'), t('Commands'), ); $rows = array(); $result = pager_query("SELECT nid from {feedapi} ORDER BY checked DESC", 50, 0, "SELECT count(*) FROM {feedapi}"); while ($nid = db_fetch_array($result)) { $nid = $nid['nid']; $node = node_load($nid); $commands = array(l(t('Delete'), 'node/'. $node->nid .'/delete', array('query' => 'destination=admin/content/feed')), l(t('Remove items'), 'node/'. $node->nid .'/purge', array('query' => 'destination=admin/content/feed')), l(t('Refresh'), 'node/'. $node->nid .'/refresh'), l(t('Edit'), 'node/'. $node->nid .'/edit'), ); // Fetch statistics for this feed foreach (array('download_num', 'new', 'process_time', 'update_times') as $type) { $node->feed->statistics[$type] = _feedapi_get_stat($node->nid, $type, TRUE); } 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') : t('%time ago', array('%time' => format_interval(time() - $node->feed->checked))), 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') : t('%time ago', array('%time' => format_interval(time() - $node->feed->checked))), '', '', t('No enough data for statistics'), '', theme('item_list', $commands), ); } } $output = format_plural(round(FEEDAPI_CRON_STAT_LIFETIME / (24*3600)), "Average over the last day.", "Averages over the last @count days." ); $output .= theme('table', $header, $rows); $output .= theme('pager', 0, 50); return $output; } /** * This is shown instead of normal node form when the simplified form is chosen at the settings */ function feedapi_simplified_form($form_state, $type) { $form['node']['#tree'] = TRUE; $form['node']['type'] = array( '#type' => 'hidden', '#value' => $type ); $form['url'] = array( '#title' => t('Feed URL'), '#type' => 'textfield', '#size' => 25, '#required' => TRUE, '#maxlength' => 2048, ); $form['add'] = array( '#type' => 'submit', '#value' => t('Add'), ); return $form; } /** * Create the node object and save */ function feedapi_simplified_form_submit($form, &$form_state) { $node_template = (object)$form_state['values']['node']; $feed_type = (string)$_POST['node']['type']; $valid_types = array_keys(feedapi_get_types()); foreach ($valid_types as $type) { if ($type === $feed_type) { $node_template->type = $type; } } if ($node = feedapi_create_node($node_template, $form_state['values']['url'])) { drupal_set_message(t('Feed successfully created.')); $form_state['redirect'] = 'node/'. $node->nid; } else { drupal_set_message(t('Could not retrieve title from feed.'), 'error'); $form_state['redirect'] = array('node/add/'. $node_template->type, 'feedapi_url='. urlencode($form_state['values']['url'])); } } /** * 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', '