View source
<?php
define('FEEDAPI_NEVER_DELETE_OLD', 0);
define('FEEDAPI_TIMEOUT', 1);
define('FEEDAPI_CRON_FEEDS', 100);
define('FEEDAPI_CRON_MIN_REFRESH_TIME', 1800);
define('FEEDAPI_CRON_STAT_LIFETIME', 28 * 24 * 3600);
function feedapi_help($section) {
switch ($section) {
case 'admin/help#feedapi':
$output = '<p>' . t('Provides feed management interface and handles underlying processors and parsers for any type of feeds.') . '</p>';
$output .= '<p>' . t('Feeds are based on content types. Default content types are created on install. You can create new content types on the <a href="@content-types">add content types</a> 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'),
)) . '</p>';
return $output;
case 'admin/content/feed':
return '<p>' . t('Current feeds are listed below. For each FeedAPI-enabled content type, the <em>Quick create</em> block may be enabled at the <a href="@block">blocks administration page</a>.', array(
'@block' => url('admin/build/block'),
)) . '</p>';
case 'admin/content/feed/import_opml':
return '<p>' . t('Feeds can be imported from a valid OPML file. You can check your OPML file at <a href="@validator">OPML Validator</a>.', array(
'@validator' => url('http://validator.opml.org/'),
)) . '</p>';
case 'admin/settings/feedapi':
return '<p>' . t('You can find more configuration options on the content type edit form of FeedAPI-enabled <a href="@content-types">content types</a>.', array(
'@content-types' => url('admin/content/types'),
)) . '</p>';
}
}
function feedapi_menu($may_cache) {
$items = array();
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_admin_overview',
'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'),
'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('Export all feeds as OPML'),
'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',
),
'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_refresh',
'callback arguments' => array(
$node,
'node/' . $node->nid,
),
'type' => MENU_LOCAL_TASK,
'access' => user_access('administer feedapi') || $own_feed,
);
$items[] = array(
'path' => 'node/' . $node->nid . '/purge',
'title' => t('Remove items'),
'callback' => 'feedapi_invoke',
'callback arguments' => array(
"purge",
$node->feed,
'items',
),
'type' => MENU_LOCAL_TASK,
'access' => user_access('administer feedapi') || $own_feed,
);
}
}
}
return $items;
}
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->settings = feedapi_get_settings($node->type, $node->nid);
$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':
db_query("DELETE FROM {feedapi_stat} WHERE id = %d", $node->nid);
db_query("DELETE FROM {feedapi} WHERE nid = %d", $node->nid);
break;
case 'validate':
if (!$node->title) {
form_set_error('title', t('Title could not be retrieved from feed.'));
}
break;
case 'submit':
$node->feed = isset($node->feedapi_feed_object) ? $node->feedapi_feed_object : _feedapi_build_feed_object($node->type, $node->feedapi['feedapi_url']);
break;
}
}
}
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;
}
}
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,
));
}
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;
}
function feedapi_perm() {
return array(
'administer feedapi',
'advanced feedapi options',
);
}
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;
}
}
}
function feedapi_invoke($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':
return _feedapi_invoke_load($feed, $param);
case 'refresh':
return _feedapi_invoke_refresh($feed, $param);
case 'purge':
return _feedapi_invoke_purge($feed, $param);
default:
return _feedapi_invoke($op, $feed, $param);
}
}
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;
}
function feedapi_purge_confirm_submit($form_id, $form_values) {
$feed->nid = $form_values['nid'];
feedapi_invoke('purge', $feed, 'items_confirmed');
return 'node/' . $form_values['nid'];
}
function feedapi_import_feeds_form() {
$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('feedapi_import_feeds_form', $form);
}
return $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_opml_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 a valid OPML file.'), 'error');
}
}
else {
drupal_set_message(t('Data could not be retrieved, invalid or empty file.'), 'error');
}
return 'admin/content/feed';
}
function feedapi_expire($feed, $settings = NULL) {
$settings = is_null($settings) ? feedapi_get_settings(NULL, $feed->nid) : $settings;
$expired = _feedapi_invoke('expire', $feed, $settings);
return $expired ? array_sum($expired) : 0;
}
function feedapi_expire_item($feed, $item) {
foreach ($feed->processors as $processor) {
module_invoke($processor, 'feedapi_item', 'delete', $item, $feed->nid);
}
}
function feedapi_form_alter($form_id, &$form) {
if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
if (isset($form['#post']['feedapi'])) {
$type = !empty($form['#node_type']->type) ? $form['#node_type']->type : $form['#post']['type'];
_feedapi_store_settings(array(
'node_type' => $type,
), $form['#post']['feedapi']);
}
$node_type_settings = feedapi_get_settings($form['#node_type']->type);
$form['feedapi'] = array(
'#type' => 'fieldset',
'#title' => t('Feed API'),
'#collapsible' => TRUE,
'#collapsed' => isset($node_type_settings['enabled']) ? FALSE : TRUE,
'#tree' => TRUE,
'#validate' => array(
'feedapi_content_type_validate' => array(),
),
);
$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' => '<strong>' . t('Default settings') . '</strong><hr/>',
);
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' => !($node_type_settings['parsers'][$parser]['enabled'] || count($parsers) == 1),
'#tree' => TRUE,
'#weight' => $node_type_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' => $node_type_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' => $node_type_settings['parsers'][$parser]['weight'],
'#weight' => -14,
);
if ($parser_form = module_invoke($parser, 'feedapi_settings_form', 'parsers')) {
$form['feedapi']['parsers'][$parser]['defaults'] = array(
'#type' => 'markup',
'#value' => '<strong>' . t('Default settings') . '</strong><hr/>',
);
$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' => !($node_type_settings['processors'][$processor]['enabled'] || count($processors) == 1),
'#tree' => TRUE,
'#weight' => $node_type_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' => $node_type_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' => $node_type_settings['processors'][$processor]['weight'],
'#weight' => -14,
);
if ($processor_form = module_invoke($processor, 'feedapi_settings_form', 'processors')) {
$form['feedapi']['processors'][$processor]['defaults'] = array(
'#type' => 'markup',
'#value' => '<strong>' . t('Default settings') . '</strong><hr/>',
);
$form['feedapi']['processors'][$processor] = array_merge_recursive($form['feedapi']['processors'][$processor], $processor_form);
}
}
if ($node_type_settings) {
$form['feedapi'] = _feedapi_populate($form['feedapi'], $node_type_settings);
}
$submit = $form['submit'];
$delete = $form['delete'];
unset($form['submit'], $form['delete']);
$form['submit'] = $submit;
$form['delete'] = $delete;
}
if (isset($form['type']) && $form['type']['#value'] . '_node_form' == $form_id && feedapi_enabled_type($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,
);
if ($_GET['feedapi_url']) {
$feedapi_url_default = $_GET['feedapi_url'];
}
else {
if ($form['#node']->feed->url) {
$feedapi_url_default = $form['#node']->feed->url;
}
else {
if ($form['#post']['feedapi']['feedapi_url']) {
$feedapi_url_default = $form['#post']['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,
);
if ($form['#post']['op']) {
$feed = _feedapi_build_feed_object($form['type']['#value'], $form['#post']['feedapi']['feedapi_url']);
$form['feedapi_feed_object'] = array(
'#type' => 'value',
'#value' => $feed,
);
if (!$form['#post']['title']) {
$form['title']['#value'] = $feed->title;
}
if (!$form['#post']['body']) {
$form['body_filter']['body']['#value'] = $feed->description;
}
}
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);
}
}
$node_type_settings = feedapi_get_settings($form['type']['#value']);
foreach (array(
"parsers" => "feedapi_feed",
"processors" => "feedapi_item",
) as $type => $requirement) {
$suitable_handlers = module_implements($requirement, TRUE);
foreach ($suitable_handlers as $module) {
if ($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 (!($settings = feedapi_get_settings($form['type']['#value'], $form['#node']->nid))) {
$settings = $node_type_settings;
}
$form['feedapi'] = _feedapi_populate($form['feedapi'], $settings);
}
}
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;
}
function feedapi_cron() {
db_query('DELETE FROM {feedapi_stat} WHERE timestamp < %d', variable_get('cron_semaphore', FALSE) - FEEDAPI_CRON_STAT_LIFETIME);
$count = array(
'%feeds' => 0,
'%expired' => 0,
'%new' => 0,
'%updated' => 0,
);
$start = time() - FEEDAPI_CRON_MIN_REFRESH_TIME;
$process = 0;
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))) {
$counter = feedapi_invoke('refresh', $feed, TRUE);
if ($counter) {
foreach ($counter as $name => $value) {
$count['%' . $name] += $value;
}
}
$count['%feeds']++;
$process--;
}
}
}
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');
$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);
}
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)) {
$node = node_load($nid['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', NULL, 'destination=admin/content/feed'),
l(t('Edit'), 'node/' . $node->nid . '/edit', NULL, 'destination=admin/content/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;
}
function feedapi_simplified_form($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;
}
function feedapi_simplified_form_submit($form_id, $form_values) {
$node_template = (object) $form_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_values['url'])) {
drupal_set_message(t('Feed successfully created.'));
return 'node/' . $node->nid;
}
else {
drupal_set_message(t('Could not retrieve title from feed.'), 'error');
return array(
'node/add/' . $node_template->type,
'feedapi_url=' . urlencode($form_values['url']),
);
}
}
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;
}
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', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'),
'#description' => t('The list of tags which are allowed in feeds, i.e., which will not be removed by Drupal.'),
);
$form['feedapi_allow_html_all'] = array(
'#type' => 'checkbox',
'#title' => t('Allow all HTML tags'),
'#default_value' => variable_get('feedapi_allow_html_all', FALSE),
'#description' => t('In this case the module does\'t filter any HTML elements from the incoming fields. This checkbox overrides the above list of allowed tags.'),
);
if (variable_get('feedapi_allow_html_all', FALSE)) {
$form['feedapi_allowed_html_tags']['#disabled'] = TRUE;
}
$max_exec = !ini_get('safe_mode') ? 240 : ini_get('max_execution_time');
$form['feedapi_cron_percentage'] = array(
'#type' => 'select',
'#title' => t('Cron time for FeedAPI [%]'),
'#options' => drupal_map_assoc(array(
15,
25,
50,
75,
)),
'#default_value' => variable_get('feedapi_cron_percentage', 15),
'#description' => t('Percentage of maximal PHP execution time (currently !exec seconds).
At current settings, the FeedAPI cron process can run for up to !now seconds.', array(
"!exec" => $max_exec,
"!now" => variable_get('feedapi_cron_percentage', 15) / 100 * $max_exec,
)),
);
return system_settings_form($form);
}
function feedapi_create_node($param, $url) {
if (is_object($param)) {
$node = $param;
}
else {
$node = new stdClass();
$node->type = $param;
}
if (!feedapi_enabled_type($node->type)) {
return FALSE;
}
$feed = _feedapi_build_feed_object($node->type, $url);
if (!$feed->title && !$node->title) {
return FALSE;
}
$node->title = $node->title ? $node->title : $feed->title;
$node->body = $node->body ? $node->body : $feed->description;
$node->feed = $feed;
$node_options = variable_get('node_options_' . $node->type, array(
'status',
'promote',
));
foreach (array(
'status',
'promote',
'sticky',
) as $key) {
$node->{$key} = in_array($key, $node_options);
}
$node->feedapi = feedapi_get_settings($node->type);
node_object_prepare($node);
global $user;
$node->uid = $user->uid;
node_save($node);
return $node;
}
function feedapi_load_node($args) {
if ($nid = db_result(db_query("SELECT nid FROM {feedapi} WHERE url = '%s'", $args['url']))) {
return node_load($nid);
}
return FALSE;
}
function feedapi_refresh($node, $destination_path = NULL) {
feedapi_invoke('refresh', $node->feed, FALSE);
if ($destination_path) {
drupal_goto($destination_path);
}
}
function _feedapi_insert(&$node, $teaser, $page) {
if (isset($node->feed->url) && isset($node->feed->feed_type)) {
db_query("INSERT INTO {feedapi} (\n nid, url, link, feed_type, processors,\n parsers, checked, settings) VALUES\n (%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()));
if (user_access('advanced feedapi options')) {
_feedapi_store_settings(array(
'nid' => $node->nid,
), $node->feedapi);
}
if ($node->feedapi['refresh_on_create'] == TRUE) {
$node->feed->nid = $node->nid;
$node->feed->settings = feedapi_get_settings(NULL, $node->nid);
feedapi_invoke('refresh', $node->feed);
}
}
}
function _feedapi_update(&$node, $teaser, $page) {
if (isset($node->feed)) {
$old_config = node_load($node->nid);
if (!is_numeric($old_config->feed->nid)) {
$node->feed = _feedapi_build_feed_object($node->type, $node->feed->url);
_feedapi_insert($node, 'insert', $teaser, $page);
return;
}
db_query("UPDATE {feedapi} SET url = '%s', feed_type = '%s', processors = '%s', parsers = '%s', link = '%s' WHERE nid = %d", isset($node->feed->url) ? $node->feed->url : $node->feedapi['feedapi_url'], isset($node->feed->feed_type) ? $node->feed->feed_type : '', isset($node->feed->processors) ? serialize($node->feed->processors) : serialize($old_config->feed->processors), isset($node->feed->parsers) ? serialize($node->feed->parsers) : serialize($old_config->feed->parsers), $node->feed->options->link, $node->nid);
if (user_access('advanced feedapi options')) {
_feedapi_store_settings(array(
'nid' => $node->nid,
), $node->feedapi);
}
}
}
function _feedapi_call_parsers($feed, $parsers) {
$nid = $feed->nid;
$parser_primary = array_shift($parsers);
$parsers_secondary = $parsers;
if (module_exists($parser_primary)) {
$feed->feed_type = module_invoke($parser_primary, 'feedapi_feed', 'compatible', $feed);
$parser_output = module_invoke($parser_primary, 'feedapi_feed', 'parse', $feed);
if ($parser_output === FALSE) {
return $parser_output;
}
$feed = (object) array_merge((array) $feed, (array) $parser_output);
}
$parsers_secondary = is_array($parsers_secondary) ? $parsers_secondary : array();
foreach ($parsers_secondary as $parser) {
$feed_ext = module_invoke($parser, 'feedapi_feed', 'parse', $feed, FALSE);
$feed->options = (object) ((array) $feed->options + (array) $feed_ext->options);
if (is_array($feed_ext->items)) {
foreach ($feed_ext->items as $key => $item) {
$feed->items[$key]->options = (object) ((array) $feed->items[$key]->options + (array) $item->options);
}
}
}
$feed->nid = $nid;
foreach (module_implements('feedapi_after_parse') as $module) {
$func = $module . '_feedapi_after_parse';
$func($feed);
}
if (!variable_get('feedapi_allow_html_all', FALSE)) {
$allowed = preg_split('/\\s+|<|>/', variable_get('feedapi_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), -1, PREG_SPLIT_NO_EMPTY);
foreach (array(
'title',
'description',
) as $property) {
if (isset($feed->{$property})) {
if (is_string($feed->{$property})) {
$feed->{$property} = filter_xss($feed->{$property}, $allowed);
}
}
}
for ($i = 0; $i < count($feed->items); $i++) {
$feed->items[$i]->title = filter_xss($feed->items[$i]->title, $allowed);
$feed->items[$i]->description = filter_xss($feed->items[$i]->description, $allowed);
if ($feed->items[$i]->options->timestamp == 0) {
$feed->items[$i]->options->timestamp = time();
}
}
}
return $feed;
}
function _feedapi_store_settings($args, $settings) {
if ($args['nid']) {
db_query("UPDATE {feedapi} SET settings = '%s', skip = %d WHERE nid = %d", serialize($settings), $settings['skip'], $args['nid']);
module_invoke_all('feedapi_after_settings', $args['nid'], $settings);
feedapi_get_settings(NULL, $args['nid'], TRUE);
}
elseif ($args['node_type']) {
variable_set('feedapi_settings_' . $args['node_type'], $settings);
}
}
function feedapi_enabled_type($node_type, $parser_or_processor = '') {
$settings = feedapi_get_settings($node_type);
if (empty($parser_or_processor)) {
if (isset($settings['enabled'])) {
return $settings['enabled'] ? TRUE : FALSE;
}
else {
return FALSE;
}
}
foreach (array(
'parsers',
'processors',
) as $stage) {
if (isset($settings[$stage][$parser_or_processor]['enabled'])) {
if ($settings[$stage][$parser_or_processor]['enabled'] == TRUE) {
return TRUE;
}
}
}
return FALSE;
}
function _feedapi_invoke($op, &$feed, $param) {
$output = array();
foreach ($feed->processors as $processor) {
$result = module_invoke($processor, 'feedapi_item', $op, $feed, $param);
if ($result) {
if (is_array($result)) {
$output = array_merge($output, $result);
}
else {
$output[] = $result;
}
}
}
return $output;
}
function _feedapi_invoke_load(&$feed, $param) {
$feed->items = array();
foreach ($feed->processors as $processor) {
$items = module_invoke($processor, 'feedapi_item', 'fetch', $feed);
if (is_array($items)) {
foreach ($items as $item) {
$feed->items[] = $item;
}
}
}
}
function _feedapi_invoke_refresh(&$feed, $param) {
$timestamp = variable_get('cron_semaphore', FALSE) !== FALSE ? variable_get('cron_semaphore', FALSE) : time();
$counter = array();
timer_start('feedapi_' . $feed->nid);
$cron = $param;
if (!is_array($feed->processors) || count($feed->processors) == 0) {
if (!$cron) {
drupal_set_message(t("No processors specified for URL %url. Could not refresh.", array(
'%url' => $feed->url,
)), "error");
drupal_goto('node/' . $feed->nid);
}
return 0;
}
else {
if (!is_array($feed->parsers) || count($feed->parsers) == 0) {
drupal_set_message(t("No parsers specified for URL %url. Could not refresh.", array(
'%url' => $feed->url,
)), "error");
drupal_goto('node/' . $feed->nid);
}
}
$settings = feedapi_get_settings(NULL, $feed->nid);
$counter['expired'] = feedapi_expire($feed);
$nid = $feed->nid;
$hash_old = $feed->hash;
$feed = _feedapi_call_parsers($feed, $feed->parsers, $feed->half_done);
if (is_object($feed)) {
$feed->hash = md5(serialize($feed->items));
}
if ($feed === FALSE || $hash_old == $feed->hash) {
db_query("UPDATE {feedapi} SET checked = %d, half_done = %d WHERE nid = %d", time(), FALSE, $nid);
if (!$cron) {
if ($hash_old == $feed->hash && is_object($feed)) {
drupal_set_message(t('There are no new items in the feed.'), 'status');
}
else {
drupal_set_message(t('Could not refresh feed.'), 'error');
}
}
return $counter;
}
$items = $feed->items;
$updated = 0;
$new = 0;
$half_done = FALSE;
foreach ($items as $index => $item) {
$item->is_updated = FALSE;
$item->is_new = FALSE;
foreach ($feed->processors as $processor) {
if (!module_invoke($processor, 'feedapi_item', 'unique', $item, $feed->nid, $settings['processors'][$processor])) {
if ($settings['update_existing'] == TRUE) {
module_invoke($processor, 'feedapi_item', 'update', $item, $feed->nid, $settings['processors'][$processor]);
$item->is_updated = TRUE;
}
}
else {
$items_delete = $settings['items_delete'];
$diff = abs(time() - (isset($item->options->timestamp) ? $item->options->timestamp : time()));
if ($diff > $items_delete && $items_delete > FEEDAPI_NEVER_DELETE_OLD) {
break;
}
$result = module_invoke($processor, 'feedapi_item', 'save', $item, $feed->nid, $settings['processors'][$processor]);
if ($result !== FALSE) {
$item->is_new = TRUE;
}
}
}
$new = $item->is_new ? $new + 1 : $new;
$updated = $item->is_updated && !$item->is_new ? $updated + 1 : $updated;
if ($cron && !feedapi_cron_time()) {
$half_done = $new + $updated == count($items) ? FALSE : TRUE;
break;
}
$feed->items[$index] = $item;
}
foreach (module_implements('feedapi_after_refresh') as $module) {
$func = $module . '_feedapi_after_refresh';
$func($feed);
}
if ($new > 0) {
_feedapi_store_stat($nid, 'update_times', time(), $timestamp);
_feedapi_store_stat($nid, 'new', $new, $timestamp);
_feedapi_store_stat($nid, 'download_num', count($items), $timestamp);
_feedapi_store_stat($nid, 'process_time', timer_read('feedapi_' . $feed->nid), $timestamp);
}
db_query("UPDATE {feedapi} SET checked = %d, half_done = %d, hash = '%s' WHERE nid = %d", time(), $half_done, $feed->hash, $feed->nid);
if (!$cron) {
if ($new == 0 && $updated == 0) {
drupal_set_message(t('There are no new items in the feed.'), 'status');
}
else {
drupal_set_message(t("%new new item(s) were saved. %updated existing item(s) were updated.", array(
"%new" => $new,
"%updated" => $updated,
)));
}
}
else {
$counter['new'] = $new;
$counter['updated'] = $updated;
return $counter;
}
}
function _feedapi_invoke_purge(&$feed, $param) {
$node = node_load($feed->nid);
db_query("UPDATE {feedapi} SET hash = 0 WHERE nid = %d", $feed->nid);
if ($param == 'items') {
return drupal_get_form('feedapi_purge_confirm', $node);
}
feedapi_invoke('load', $feed);
foreach ($feed->items as $item) {
foreach ($feed->processors as $processor) {
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),
)));
}
}
function _feedapi_build_feed_object($node_type, $url) {
$feed = new stdClass();
$feed->url = $url;
$node_type_settings = feedapi_get_settings($node_type);
$feed->processors = _feedapi_format_settings($node_type_settings, 'processors');
$feed->parsers = _feedapi_format_settings($node_type_settings, 'parsers');
if ($feed->url) {
$feed = _feedapi_call_parsers($feed, $feed->parsers);
}
$feed->link = $feed->options->link;
return $feed;
}
function _feedapi_format_settings($node_type_settings, $stage_type) {
$result = array();
$settings = $node_type_settings[$stage_type];
if (is_array($settings)) {
foreach ($settings as $name => $properties) {
if (isset($properties['enabled'])) {
if ($properties['enabled'] == TRUE) {
$result[$settings[$name]['weight']] = $name;
}
}
}
ksort($result);
}
return $result;
}
function feedapi_get_settings($node_type, $nid = FALSE, $reset = FALSE) {
static $node_settings;
if (is_numeric($nid)) {
if (!isset($node_settings[$nid]) || $reset) {
if ($settings = db_fetch_object(db_query('SELECT settings FROM {feedapi} WHERE nid = %d', $nid))) {
$settings = unserialize($settings->settings);
}
$node_settings[$nid] = is_array($settings) && !empty($settings) ? $settings : FALSE;
}
if (!is_array($node_settings[$nid])) {
if (empty($node_type)) {
$node_type = db_result(db_query("SELECT type FROM {node} WHERE nid = %d", $nid));
}
}
else {
return $node_settings[$nid];
}
}
if (isset($node_type) && is_string($node_type)) {
if ($settings = variable_get('feedapi_settings_' . $node_type, FALSE)) {
return $settings;
}
}
return FALSE;
}
function _feedapi_populate($form, $settings) {
foreach ($form as $k => $v) {
if (is_array($v)) {
if (array_key_exists('#default_value', $v)) {
if ($k != 'feedapi_url') {
if (isset($form[$k]['#parents']) && is_array($form[$k]['#parents'])) {
$form[$k]['#default_value'] = _feedapi_populate_get_setting($form[$k]['#parents'], $settings);
}
elseif (isset($settings[$k])) {
$form[$k]['#default_value'] = $settings[$k];
}
}
}
elseif (isset($settings[$k])) {
$form[$k] = _feedapi_populate($form[$k], $settings[$k]);
}
}
}
return $form;
}
function _feedapi_populate_get_setting($parents, $settings) {
if (is_array($parents) && count($parents)) {
$this_parent = array_shift($parents);
return _feedapi_populate_get_setting($parents, $settings[$this_parent]);
}
else {
return $settings[$parents];
}
}
function _feedapi_update_rate($update_times) {
for ($i = 0; $i < count($update_times) - 1; $i++) {
$between[] = abs($update_times[$i] - $update_times[$i + 1]);
}
return count($between) > 0 ? round(array_sum($between) / count($between), 2) : t('No data yet');
}
function _feedapi_sanitize_processors(&$feed) {
if (is_array($feed->processors)) {
foreach ($feed->processors as $key => $processor) {
if (!module_exists($processor)) {
unset($feed->processors[$key]);
}
}
}
}
function feedapi_export_opml() {
$result = db_query(db_rewrite_sql('SELECT n.title, f.url FROM {feedapi} f INNER JOIN {node} n ON f.nid = n.nid ORDER BY n.title ASC'));
$output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
$output .= "<opml version=\"1.1\">\n";
$output .= "<head>\n";
$output .= '<title>' . variable_get('site_name', 'drupal') . ' - ' . variable_get('site_slogan', '') . "</title>\n";
$output .= '<dateModified>' . gmdate('r') . "</dateModified>\n";
$output .= "</head>\n";
$output .= "<body>\n";
while ($feed = db_fetch_object($result)) {
$output .= '<outline text="' . check_plain($feed->title) . '" xmlUrl="' . check_plain($feed->url) . '" />' . "\n";
}
$output .= "</body>\n";
$output .= "</opml>\n";
drupal_set_header('Content-Type: text/xml; charset=utf-8');
drupal_set_header('Content-Disposition: attachment; filename="' . variable_get('site_name', 'drupal') . '.opml"');
print $output;
exit(0);
}
function _feedapi_import_opml_process($opml, $args = array()) {
$feeds = array();
$count = 0;
$parser = drupal_xml_parser_create($opml);
if (xml_parse_into_struct($parser, $opml, $vals, $index) || xml_parse_into_struct($parser, '<?xml version="1.0"?>' . $opml, $vals, $index)) {
foreach ($vals as $entry) {
if ($entry['tag'] == 'OUTLINE') {
$feeds[] = $entry['attributes'];
}
}
foreach ($feeds as $feed) {
if (strlen($feed['XMLURL']) > 1) {
$dupe = db_result(db_query("SELECT nid FROM {feedapi} WHERE url = '%s'", $feed['XMLURL']));
if ($dupe === FALSE) {
$node = new stdClass();
$node->type = $args['feed_type'];
if ($args['override_title']) {
$node->title = $feed['TITLE'];
}
if ($args['override_body']) {
$node->body = $feed['TEXT'];
}
$node->og_groups = $args['og_groups'];
$node->og_public = (int) $args['og_public'];
$node = feedapi_create_node($node, $feed['XMLURL']);
$count++;
}
}
}
}
return $count;
}
function _feedapi_store_stat($id, $type, $val, $timestamp, $time = NULL, $update = FALSE) {
if (!$time) {
$time = date("Y-m-d H:i", $timestamp);
}
if ($update) {
db_query("UPDATE {feedapi_stat} SET value = %d, timestamp = %d WHERE time = '%s' AND type = '%s' AND id = %d", $val, $timestamp, $time, $type, $id);
}
if (!$update || !db_affected_rows()) {
db_query("INSERT INTO {feedapi_stat} (id, value, time, timestamp, type) VALUES (%d, %d, '%s', %d, '%s')", $id, $val, $time, $timestamp, $type);
}
}
function _feedapi_get_stat($id, $type, $only_val = FALSE) {
$result = db_query("SELECT timestamp, time, value FROM {feedapi_stat} WHERE type = '%s' AND id = %d", $type, $id);
while ($row = db_fetch_array($result)) {
if ($only_val) {
$stat[] = $row['value'];
}
else {
foreach (array(
'timestamp',
'time',
'value',
) as $member) {
$stat[$member][] = $row[$member];
}
}
}
return $stat;
}
function feedapi_get_types() {
$names = node_get_types('names');
foreach ($names as $type => $name) {
if (!feedapi_enabled_type($type)) {
unset($names[$type]);
}
}
return $names;
}
function feedapi_content_type_validate($form) {
$parsers = module_implements('feedapi_feed', TRUE);
rsort($parsers);
$processors = module_implements('feedapi_item', TRUE);
rsort($processors);
foreach (array(
'processors',
'parsers',
) as $type) {
$proc_weight = array();
foreach (${$type} as $stuff) {
if (isset($form[$type][$stuff]) && $form[$type][$stuff]['enabled']['#value'] == TRUE) {
if (++$proc_weight[$form[$type][$stuff]['weight']['#value']] > 1) {
form_error($form, t('Two enabled processors or parsers cannot have the same weight.'), 'error');
}
}
}
}
}