Based on parts of Aggregator2 module,
Also depends on other modules from Drupal basic distribution and, in some cases, contains parts of their code.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the LICENSE file for more details.
*/
// Permissions
define('LEECH_PERM_CREATE', 'create feed');
define('LEECH_PERM_EDIT_OWN', 'edit own feeds');
define('LEECH_PERM_REFRESH_OWN', 'manually leech data');
define('LEECH_PERM_ACCESS', 'access feeds');
define('LEECH_NEWS_PERM_EDIT_OWN_ITEM', 'edit own news items');
define("LEECH_SHOW_LINK_ALWAYS", 0);
define("LEECH_SHOW_LINK_NEVER", 1);
define("LEECH_SHOW_LINK_TEASER_ONLY", 2);
define("LEECH_SHOW_LINK_PAGE_ONLY", 3);
define("LEECH_SHOW_LINK_IN_LINKS", 1);
define("LEECH_SHOW_LINK_INLINE", 2);
/**
* Implementation of hook_help().
*/
function leech_help($section) {
switch ($section) {
case 'admin/help#leech':
return t('Feed aggretagor, creates nodes from RSS or ATOM feeds.');
}
}
/**
* Implementation of hook_perm().
*/
function leech_perm() {
return array(LEECH_PERM_CREATE,
LEECH_PERM_EDIT_OWN,
LEECH_PERM_REFRESH_OWN,
LEECH_PERM_ACCESS,
LEECH_NEWS_PERM_EDIT_OWN_ITEM);
}
/**
* Implementation of hook_menu().
* heads up: there are a couple of renamed paths here, old paths are kept
* for a while for backwards compatibility. check out what's new and change your
* site accordingly.
*/
function leech_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'leech/list', 'title' => t('leech'),
'callback' => 'leech_page_feed_list', 'access' => user_access(LEECH_PERM_ACCESS),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/content/leech', 'title' => t('leech'),
'callback' => 'leech_page_admin');
// DEPRECATED, use leech/feed without any further argument instead
$items[] = array('path' => 'leech/sources', 'title' => t('sources'),
'callback' => 'leech_news_page_default', 'access' => user_access(LEECH_PERM_ACCESS),
'type' => MENU_CALLBACK);
// DEPRECATED, use leech/feed/opml instead
$items[] = array('path' => 'leech/sources/opml', 'title' => t('opml'),
'callback' => 'leech_page_opml', 'access' => user_access(LEECH_PERM_ACCESS),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'leech/feed/opml', 'title' => t('opml'),
'callback' => 'leech_page_opml', 'access' => user_access(LEECH_PERM_ACCESS),
'type' => MENU_CALLBACK);
// TODO: find a nice way to allow refresh only to feed owner
$items[] = array('path' => 'leech/refresh', 'title' => t('refresh data'),
'callback' => 'leech_page_refresh', 'access' => user_access(LEECH_PERM_REFRESH_OWN),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'leech/get/form', 'title' => t('get addons form'),
'callback' => 'leech_get_form', 'access' => user_access(LEECH_PERM_CREATE),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'leech.js', 'title' => t('javascript file'),
'callback' => 'leech_javascript', 'access' => user_access(LEECH_PERM_CREATE),
'type' => MENU_CALLBACK);
// DEPRECATED, use leech/feed instead
$items[] = array('path' => 'leech_news/feed', 'title' => t('leech news'),
'callback' => 'leech_news_page_default', 'access' => user_access(LEECH_PERM_ACCESS),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'leech/feed', 'title' => t('All feeds'),
'callback' => 'leech_news_page_default', 'access' => user_access(LEECH_PERM_ACCESS));
// DEPRECATED, use leech/remove_items instead
$items[] = array('path' => 'leech_news/remove', 'title' => t('leech news'),
'callback' => 'leech_news_page_delete_confirm_page', 'access' => user_access(LEECH_NEWS_PERM_EDIT_OWN_ITEM),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'leech/remove_items', 'title' => t('leech news'),
'callback' => 'leech_news_page_delete_confirm_page', 'access' => user_access(LEECH_NEWS_PERM_EDIT_OWN_ITEM),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/settings/leech', 'title' => t('Leech'),
'callback' => 'drupal_get_form', 'callback arguments' => array('leech_admin_settings'));
}
return $items;
}
/**
* prints view of latest cron times
*/
function _leech_cron_times_view($crontimes = array()){
$output = '
';
$last_time = 0;
if (count($crontimes) > 1) {
$output .= '
'.t('Latest').' '.(count($crontimes)-1).' '.t('cron runs').'
';
foreach ($crontimes as $t) {
if ($last_time != 0) {
$output .= '
';
$output .= format_date($t, 'large');
$output .= ', '.t('after').' '.format_interval($t-$last_time);
$output .= '
';
}
$last_time = $t;
}
}
else {
$output .= '
'.t('Latest').' '.t('cron runs').'
';
$output .= t('Cron has not been run.');
}
$output .= '
';
return $output;
}
/**
* Implementation of hook_settings().
*/
function leech_admin_settings() {
$form = array();
if (!function_exists('curl_init')) {
drupal_set_message(t('Some features may be disabled because the cURL module for PHP is not available.', array('@curl' => url('http://php.net/curl'))), 'error');
}
$form['leech_display'] = array(
'#type' => 'fieldset',
'#title' => t('Display settings'),
'#tree' => FALSE,
);
$form['leech_display']['leech_news_show_feed_link'] = array(
'#type' => 'select',
'#title' => t('Show link to feed with each item'),
'#default_value' => variable_get('leech_news_show_feed_link', 0),
'#options' => array(0 => t('Don\'t show'),
LEECH_SHOW_LINK_IN_LINKS => t('In links list'),
LEECH_SHOW_LINK_INLINE => t('Inline in body/teaser')),
'#description' => t('If "In links list" selected, Leech News will show link to source feed on each item in links list, if "Inline in body/teaser" selected, it will show this link in the feed item\'s body.'));
$form['leech_display']['leech_news_show_items_link'] = array(
'#type' => 'select',
'#title' => t('Show link to items with each feed'),
'#default_value' => variable_get('leech_news_show_items_link', 0),
'#options' => array(0 => t('Don\'t show'),
LEECH_SHOW_LINK_IN_LINKS => t('In links list')) ,
'#description' => t('If "In links list" selected, Leech News will show "items" link with each feed. It will point to list of all items created by that feed.'));
$form['leech_blacklist'] = array(
'#type' => 'fieldset',
'#title' => t('Blacklist'),
'#tree' => FALSE,
);
$form['leech_blacklist']['leech_blacklist_url'] = array(
'#type' => 'textarea',
'#title' => t('Blacklist URLs'),
'#default_value' => variable_get('leech_blacklist_url', ''),
'#rows' => 5,
'#description' => t('One entry per line. You can enter full URLs or domain names only. You can also enter regular expression (find out more about what it is at %link. more examples can be found also at %link2). For example "http://some.url.com/some/page.html" will blacklist that specific URL. ".url.com" will blacklist all URLs from url.com domain, and all it\'s subdomains. "some.url.com" will blacklist all URLs from "some" subdomain. "/^ftp\:\/\//" will blacklist any ftp:// URL. Leech module will not download any data from URL which matches any of the rules on blacklist.', array('%link' => l('http://www.php.net/manual/en/reference.pcre.pattern.syntax.php', 'http://www.php.net/manual/en/reference.pcre.pattern.syntax.php'), '%link2' => l('http://www.php.net/manual/en/function.preg-match.php', 'http://www.php.net/manual/en/function.preg-match.php'))));
$form['leech_url_profile'] = array(
'#type' => 'fieldset',
'#title' => t('URL profile'),
'#tree' => FALSE,
);
$msg = ''.t('The URL profile module allows you to surf feed items per original source and optionally retrieve information from third parties such as Alexa or Technorati.').'
';
if (module_exists('url_profile')) {
$msg .= ''.t('URL profile module detected. Leech feed item URLs are being profiled.').'
';
}
else {
$msg .= ''.t('URL profile module not installed.').'
';
}
$form['leech_url_profile']['detected'] = array(
'#type' => 'markup',
'#value' => $msg,
);
$form['leech_og'] = array(
'#type' => 'fieldset',
'#title' => t('Organic groups'),
'#tree' => FALSE,
);
if (module_exists('og')) {
$msg = ''.t('Organic groups (og) module detected. Leech feeds are passing on their group association to their feed items.').'
';
}
else {
$msg = ''.t('Organic groups (og) module not installed.').'
';
}
$form['leech_og']['detected'] = array(
'#type' => 'markup',
'#value' => $msg,
);
$form['leech_advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced leech settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#tree' => FALSE,
);
$parsers = array('leech_news' => 'leech_news');
foreach (module_implements('parse_news_feed') as $name) {
$parsers[$name] = $name;
}
$form['leech_advanced']['leech_news_parser'] = array(
'#type' => 'select',
'#title' => t('Select parser'),
'#default_value' => variable_get('leech_news_parser', 'leech_news'),
'#options' => $parsers,
'#description' => t('Select which parser to use to parse RSS/ATOM feed.'));
$form['leech_advanced']['leech_news_original_links'] = array(
'#type' => 'checkbox',
'#title' => t('Use source link'),
'#default_value' => variable_get('leech_news_original_links', 1),
'#description' => t('Use link to original source of article when possible - usually the source link is preferrable.'),
);
$form['leech_advanced']['leech_news_verbose'] = array(
'#type' => 'checkbox',
'#title' => t('Verbose output to watchdog'),
'#default_value' => variable_get('leech_news_verbose', 0),
'#description' => t('Check if you would like to have more verbose output about leech on the %watchdoglink.', array('%watchdoglink' => l('watchdog', 'admin', array('title' => 'watchdog')))),
);
if (module_exists('taxonomy')) {
$form['leech_advanced']['leech_news_pass_on_taxonomy'] = array(
'#type' => 'checkbox',
'#title' => t('Pass on taxonomy from feed to feed items'),
'#default_value' => variable_get('leech_news_pass_on_taxonomy', 0),
'#description' => t('If enabled, taxonomy of a feed node will be passed on
to its existing and future child feed items. Only those
taxonomies will get passed on, that are enabled for both,
feed and feed item.'),
);
}
$form['leech_advanced']['cron'] = array(
'#type' => 'fieldset',
'#title' => t('Cron settings'),
'#tree' => FALSE,
);
// list last n times cron was called
$form['leech_advanced']['cron']['leech_cron_times_overview'] = array(
'#type' => 'markup',
'#value' => _leech_cron_times_view(variable_get('leech_cron_times', array())),
);
// how many leeches to update at one cron run
$leech_count['9999999'] = t('All');
$leech_count = drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 100));
$form['leech_advanced']['cron']['leech_cron_count'] = array(
'#type' => 'select',
'#title' => t('Update count'),
'#default_value' => variable_get('leech_cron_count', 5),
'#options' => $leech_count,
'#description' => t('Select how many leeches can be updated at one cron run.'));
// how long sleep interval should be
$interval = drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15));
$form['leech_advanced']['cron']['leech_sleep_interval'] = array(
'#type' => 'select',
'#title' => t('Sleep time'),
'#default_value' => variable_get('leech_sleep_interval', 0),
'#options' => $leech_count,
'#description' => t('Select how many seconds module should wait before updating next leech.'));
return system_settings_form($form);
}
/**
* url_profile hook - list urls to be url_profiled. interface to url_profile.module
*/
function leech_url_profile(&$object, $op, $arg = NULL) {
switch ($op) {
case 'list':
/*
no profiling for leech feeds - we could make this optional
if ($node->leech_news && $node->leech_news->link) {
$list[] = $node->leech_news->link;
}
*/
$list = array();
// sometimes stuff comes in as array - todo: clean this up
if ($object->leech_news_item) {
if (isset($object->leech_news_item->link)) {
$list[] = $object->leech_news_item->link;
}
else if (isset($object->leech_news_item['link'])) {
$list[] = $object->leech_news_item['link'];
}
if (isset($object->leech_news_item->source_link)) {
$list[] = $object->leech_news_item->source_link;
}
else if (isset($object->leech_news_item['source_link'])) {
if (isset($object->leech_news_item['source_link'])) {
$list[] = $object->leech_news_item['source_link'];
}
}
}
return $list;
case 'list_matches':
$result = db_query ('SELECT DISTINCT li.nid, li.link as url
FROM {leech_news_item} li
JOIN {node} n ON n.nid = li.nid
WHERE li.link LIKE "%s%%"
AND li.link LIKE "%%%s%%" ',
$object->url, $object->criteria);
while ($feed_item = db_fetch_object($result)) {
$list[] = $feed_item;
}
return $list;
}
}
/**
* This function gives control of contents and functions on the link of 'full article'.
*/
function theme_leech_link_full_article($node) {
$link = $node->leech_news_item->link;
if (variable_get('leech_news_original_links', 0) && $node->leech_news->source_link) {
$link = $node->leech_news->source_link;
}
return ''. t('original article') .'';
}
/**
* This function gives control of contents and functions on the link of 'visit site'.
*/
function theme_leech_link_visit_site($node) {
return ''. t('visit site') .'';
}
/**
* Menu callback; Generate a listing of feed items.
*/
function leech_news_page_default($nid = NULL) {
$output = '';
if ($nid != NULL && is_numeric($nid)) {
drupal_set_title(t('Feed'));
$feed = node_load($nid);
if ($feed && $feed->leech_news) {
$output .= node_view($feed, 1);
$result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n INNER JOIN {leech_news_item} i ON i.nid = n.nid WHERE i.fid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10), 0, NULL, $feed->nid);
while ($node = db_fetch_object($result)) {
$output .= node_view(node_load($node->nid), 1);
}
$output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
}
else {
drupal_goto('node/'. $nid);
}
}
else {
// If there's no nid given, then show page with feed nodes
$result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n INNER JOIN {leech_news_feed} f ON f.nid = n.nid WHERE n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10), 0, NULL);
while ($node = db_fetch_object($result)) {
$output .= node_view(node_load($node->nid), 1);
}
$output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
}
return $output;
}
/**
* Menu callback; Remove items.
*/
function leech_news_page_delete_confirm_page() {
return drupal_get_form('leech_news_page_delete_confirm', arg(2));
}
function leech_news_page_delete_confirm($nid) {
$feed = node_load($nid);
$form = array();
$result = db_query('SELECT n.title, n.nid FROM {node} n LEFT JOIN {leech_news_item} i ON i.nid = n.nid WHERE i.fid = %d ORDER BY n.title', $feed->nid);
$items = array();
while ($item = db_fetch_object($result)) {
$items[$item->nid] = $item->title;
}
$form['nodes'] = array(
'#type' => 'checkboxes',
'#title' => t('Delete following items:'),
'#default_value' => array_keys($items),
'#options' => $items);
return confirm_form($form,
t('Please confirm which items You really want to delete'),
'node/'. $feed->nid, t('This action cannot be undone.'),
t('Delete'), t('Cancel') );
}
function leech_news_page_delete_confirm_submit() {
global $form_values;
foreach ($form_values['nodes'] as $nid => $value) {
if ($value == $nid) {
node_delete($nid);
}
}
return "/node";
}
/**
* Create an XML document header and tail for opml
*/
function _opml_skeleton($content) {
$output = "\n";
$output .= "\n";
$output .= "\n";
$output .= ''. variable_get('site_name', 'drupal') .' - '. variable_get('site_slogan', '') ."\n";
$output .= ''. gmdate('r') ."\n";
$output .= "\n";
$output .= "\n";
$output .= $content;
$output .= "\n";
$output .= "\n";
return $output;
}
/**
* Parse xml into feed data
*/
function _leech_news_parse($xml) {
$parser = variable_get('leech_news_parser', 'leech_news');
if ($parser == 'leech_news') {
include_once('leech_news_parser.inc');
}
if ($parser) {
$function = $parser .'_parse_news_feed';
return $function($xml);
}
}
/**
* Implementation of hook_form_alter().
*/
function leech_form_alter($form_id, &$form) {
// Add leech fields to node edit form
if ((strstr($form_id, "_node_form") !== FALSE || strstr($form_id, "leech") !== FALSE) && variable_get('leech_for_'. $form['#node']->type, 0)) {
$node = $form['#node'];
if (isset($form['body_filter']['body'])) {
$form['body_filter']['body']['#required'] = 0;
}
$form['leech'] = array(
'#type' => 'fieldset',
'#title' => t('Leech'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#tree' => TRUE
);
$form['leech']['nid'] = array(
'#type' => 'hidden',
'#value' => $node->nid,
'#attributes' => array('id' => 'edit-leech-nid')
);
drupal_add_js('?q=leech.js');
$form['leech']['url'] = array(
'#type' => 'textfield',
'#title' => t('URL'),
'#default_value' => $node->leech->url,
'#size' => 60,
'#maxlength' => 2048,
'#description' => t('URL from which data will be downloaded.'),
'#weight' => -99,
'#attributes' => array('onFocus' => 'leech_prepare_check_url(this)', 'onBlur' => 'leech_close_check_url(this)')
);
$period = drupal_map_assoc(array(0, 900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
$period[0] = t('Freezed');
if (isset($node->leech->adaptive) && $node->leech->adaptive == TRUE) {
$form['leech']['refresh'] = array(
'#type' => 'textfield',
'#title' => t('Frequency'),
'#default_value' => $node->leech->refresh,
'#description' => t('The current frequency at which new data will be downloaded. This is not editable while Adaptive mode is turned on.'),
'#attributes' => array('disabled' => 'true'),
'#weight' => -97
);
}
else {
$form['leech']['refresh'] = array(
'#type' => 'select',
'#title' => t('Frequency'),
'#default_value' => (isset($node->leech->refresh) ? $node->leech->refresh : 10800),
'#options' => $period,
'#description' => t('Frequency at which new data will be downloaded.'),
'#weight' => -97
);
}
$form['leech']['adaptive'] = array(
'#type' => 'checkbox',
'#title' => t('Adaptive'),
'#default_value' => isset($node->leech->adaptive) ? $node->leech->adaptive : TRUE,
'#description' => t('The module modifies continously the leech frequency according to the behaviour of the feed. More active feeds will be leeched more often and reverse too.')
);
// Add fields from other modules which want to work with us through leechapi
$form['leech']['addons'] = array(
'#type' => 'fieldset',
'#title' => t('Additional options'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#weight' => 1,
'#attributes' => array('id' => 'leech-fieldset'),
'#prefix' => '',
'#suffix' => '
'
);
// Mark if there was leech data with node or not
if (!$node->leech->nid) {
$form['leech']['is_new'] = array(
'#type' => 'hidden',
'#value' => 1
);
}
// since 4.7.2 (or .3) there is no easy way to get node type html field cause now it's ID is like: node-[type]-node-form
// so just create our own ;p
$form['leech']['node_type'] = array(
'#type' => 'hidden',
'#value' => $node->type
);
// Some values we may want to use
if (isset($node->leech->connection)) {
$form['leech']['mime'] = array('#type' => 'value', '#value' => $node->leech->mime);
$form['leech']['connection'] = array('#type' => 'value', '#value' => $node->leech->connection);
}
if ($node->leech->_prerender_id) {
// Hookup prerendering of addons part for our ajax magic ;]
if (!is_array($form['#pre_render'])) {
$form['#pre_render'] = array();
}
$form['#pre_render'][] = 'leech_pre_render_addons';
}
}
// "Workflow" form
if (isset($form['#node_type']) && 'node_type_form' == $form_id) {
$node_type = $form['old_type']['#value'];
$form['workflow']['leech_defs'] = array(
'#type' => 'fieldset',
'#title' => t('Default leech options'),
'#collapsible' => TRUE,
'#collapsed' => FALSE
);
$form['workflow']['leech_defs']['leech_for'] = array(
'#type' => 'checkbox',
'#title' => t('Enable leech'),
'#default_value' => variable_get('leech_for_'. $node_type, 0),
/*'#attributes' => array(
'onclick' => 'Drupal.toggleClass(this.parentNode.parentNode.parentNode, "collapsed");',
)*/
);
/*if (!variable_get('leech_for_'. $node_type, 0)) {
$form['workflow']['leech_defs']['#attributes']['class'] = 'collapsed';
}*/
$period = drupal_map_assoc(array(0, 900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
$period[0] = t('Freezed');
$form['workflow']['leech_defs']['leech_refresh'] = array (
'#type' => 'select',
'#title' => t('Update interval'),
'#default_value' => variable_get('leech_refresh_'.$node_type, 900),
'#options' => $period,
'#description' => t('The refresh interval indicating how often you want leech to download data.')
);
$form['workflow']['leech_defs']['leech_news_template'] = array(
'#type' => 'select',
'#title' => t('Template'),
'#options' => node_template_list(),
'#default_value' => variable_get('leech_news_template_'. $node_type, 0),
'#description' => t('Select which node template should be used as template for new items')
);
$form['workflow']['leech_defs']['leech_news_items_guid'] = array(
'#type' => 'checkbox',
'#title' => t('Generate GUIDs'),
'#default_value' => variable_get('leech_news_items_guid_'. $node_type, 0)
);
$form['workflow']['leech_defs']['leech_news_items_status'] = array(
'#type' => 'checkbox',
'#title' => t('Publish news items'),
'#default_value' => variable_get('leech_news_items_status_'. $node_type, 1)
);
$form['workflow']['leech_defs']['leech_news_items_update'] = array(
'#type' => 'checkbox',
'#title' => t('Update already existing news items'),
'#default_value' => variable_get('leech_news_items_update_' . $node_type, 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['1000000000'] = t('Never');
$form['workflow']['leech_defs']['leech_news_items_delete'] = array(
'#type' => 'select',
'#title' => t('Delete news items older than'),
'#options' => $period,
'#default_value' => variable_get('leech_news_items_delete_'. $node_type, 15724800)
);
$promote = drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30));
$promote['0'] = t('None');
$promote['1000000000'] = t('All');
$form['workflow']['leech_defs']['leech_news_items_promote'] = array(
'#type' => 'select',
'#title' => t('Promote items'),
'#options' => $promote,
'#default_value' => variable_get('leech_news_items_promote_'. $node_type, 3)
);
$form['workflow']['leech_defs']['leech_news_items_date'] = array(
'#type' => 'checkbox',
'#title' => t('Use dates found in feed (if possible)'),
'#default_value' => variable_get('leech_news_items_date_'. $node_type, 1)
);
$form['workflow']['leech_defs']['leech_news_links'] = array (
'#type' => 'select',
'#title' => t('Show "original article"/"visit site" link'),
'#options' => array(
LEECH_SHOW_LINK_ALWAYS => t('Always'),
LEECH_SHOW_LINK_NEVER => t('Do not display'),
LEECH_SHOW_LINK_TEASER_ONLY => t('Only with teaser'),
LEECH_SHOW_LINK_PAGE_ONLY => t('Only on full page')
),
'#default_value' => variable_get('leech_news_links_'. $node_type, LEECH_SHOW_LINK_ALWAYS),
'#description' => t('Select place(s) where link to original article (for news items) or visit site (for news feeds) will be shown.')
);
/*$form['#after_build'][] = 'leech_after_build_defs';*/
}
// Add item's data to node edit form
if (isset($form['type']) && $form['#node']->leech_news_item) {
$node = $form['#node'];
$form['leech_news_item'] = array(
'#type' => 'fieldset',
'#title' => t('Leeched news item'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#tree' => TRUE
);
if (user_access('administer nodes')) {
// don't allow regular user's to "steal fame" :)
$feeds = array();
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n, {leech_news_feed} f WHERE f.nid = n.nid'));
while ($temp = db_fetch_array($result)) {
$feeds[$temp['nid']] = $temp['title'];
}
$form['leech_news_item']['fid'] = array (
'#type' => 'select',
'#title' => t('Feed Name'),
'#default_value' => $node->leech_news_item->fid,
'#options' => $feeds,
'#required' => TRUE,
'#description' => t('The RSS/ATOM feed which this item belongs to.')
);
$form['leech_news_item']['link'] = array(
'#type' => 'textfield',
'#title' => t('Link'),
'#default_value' => $node->leech_news_item->link,
'#size' => 60,
'#maxlength' => 250
);
$form['leech_news_item']['author'] = array(
'#type' => 'textfield',
'#title' => t('Original author'),
'#default_value' => $node->leech_news_item->author,
'#size' => 60,
'#maxlength' => 60
);
}
else {
// don't allow regular user's to "steal fame" :)
$form['leech_news_item']['fid'] = array(
'#type' => 'hidden',
'#value' => $node->leech_news_item->fid
);
$form['leech_news_item']['link'] = array(
'#type' => 'hidden',
'#value' => $node->leech_news_item->link
);
$form['leech_news_item']['author'] = array(
'#type' => 'hidden',
'#value' => $node->leech_news_item->author
);
}
$form['leech_news_item']['source_link'] = array(
'#value' => $node->leech_news_item->source_link
);
$form['leech_news_item']['source_xml'] = array(
'#value' => $node->leech_news_item->source_xml
);
$form['leech_news_item']['source_title'] = array(
'#value' => $node->leech_news_item->source_title
);
}
// Add leech fields to node edit form
if (isset($form['#type']) && ($form_id == $form['#node']->type .'_node_form' || $form_id == 'leech_addons') && variable_get('leech_for_'. $form['#node']->type, 0)) {
$node = $form['#node'];
if (!isset($node->leech->connection) || !isset($node->leech->connection->news_feed)) {
return;
}
// Shortcut to make things more readable ;)
$feed = &$node->leech->connection->news_feed;
/*
// All seems ok, we can present additional settings to user
$form['leech']['addons']['news'] = array(
'#type' => 'fieldset'
//,'#title' => t('News feed options'),
//'#collapsible' => TRUE,
//'#collapsed' => FALSE
);
*/
if (user_access('administer nodes')) {
$form['leech']['addons']['news']['template'] = array(
'#type' => 'select',
'#title' => t('Template'),
'#options' => node_template_list(),
'#default_value' => $node->leech_news->template,
'#description' => t('Select which node template should be used as template for new items')
);
}
$form['leech']['addons']['news']['logo'] = array(
'#type' => 'textfield',
'#title' => t('URL of logo image'),
'#default_value' => (isset($node->leech_news->logo) ? $node->leech_news->logo : $feed->logo)
);
$form['leech']['addons']['news']['link'] = array(
'#type' => 'textfield',
'#title' => t('URL of site'),
'#default_value' => (isset($node->leech_news->link) ? $node->leech_news->link : $feed->link)
);
$form['leech']['addons']['news']['author'] = array(
'#type' => 'textfield',
'#title' => t('Author of feed'),
'#default_value' => (isset($node->leech_news->author) ? $node->leech_news->author : $feed->author),
'#autocomplete_path' => 'user/autocomplete'
);
$form['leech']['addons']['news']['title'] = array(
'#type' => 'textfield',
'#default_value' => (!$node->title ? $feed->channel[TITLE][0][VALUE] : $node->title),
'#attributes' => array('style' => 'display:none')
);
if (user_access('administer nodes')) {
$form['leech']['addons']['news']['items_guid'] = array(
'#type' => 'checkbox',
'#title' => t('Generate GUIDs'),
'#default_value' => (isset($node->leech_news->items_guid) ? $node->leech_news->items_guid : (($feed->has_guids || $feed->has_unique_links) ? FALSE : variable_get('leech_news_items_guid_'.$form['type']['#value'], 0)))
);
$form['leech']['addons']['news']['items_status'] = array(
'#type' => 'checkbox',
'#title' => t('Publish news items'),
'#default_value' => (isset($node->leech_news->items_status) ? $node->leech_news->items_status : variable_get('leech_news_items_status_' . $form['type']['#value'], 1))
);
$form['leech']['addons']['news']['items_update'] = array(
'#type' => 'checkbox',
'#title' => t('Update already existing news items'),
'#default_value' => (isset($node->leech_news->items_update) ? $node->leech_news->items_update : variable_get('leech_news_items_update_'.$form['type']['#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['1000000000'] = t('Never');
$form['leech']['addons']['news']['items_delete'] = array(
'#type' => 'select',
'#title' => t('Delete news items older than'),
'#options' => $period,
'#default_value' => (isset($node->leech_news->items_delete) ? $node->leech_news->items_delete : variable_get('leech_news_items_delete_'.$form['type']['#value'], 15724800))
);
$promote = drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30));
$promote['0'] = t('None');
$promote['1000000000'] = t('All');
$form['leech']['addons']['news']['items_promote'] = array(
'#type' => 'select',
'#title' => t('Promote items'),
'#options' => $promote,
'#default_value' => (isset($node->leech_news->items_promote) ? $node->leech_news->items_promote : variable_get('leech_news_items_promote_'.$form['type']['#value'], 3))
);
$form['leech']['addons']['news']['items_date'] = array(
'#type' => 'checkbox',
'#title' => t('Use dates found in feed if available'),
'#default_value' => ($node->leech_news->items_date ? $node->leech_news->items_date : ($feed->has_dates && variable_get('leech_news_items_date_'.$form['type']['#value'], 0) ? TRUE : FALSE))
);
$form['leech']['addons']['news']['links_display_mode'] = array (
'#type' => 'select',
'#title' => t('Show "original article"/"visit site" link'),
'#options' => array(
LEECH_SHOW_LINK_ALWAYS => t('Always'),
LEECH_SHOW_LINK_NEVER => t('Do not display'),
LEECH_SHOW_LINK_TEASER_ONLY => t('Only with teaser'),
LEECH_SHOW_LINK_PAGE_ONLY => t('Only on full page')
),
'#default_value' => (isset($node->leech_news->links_display_mode) ? $node->leech_news->links_display_mode : variable_get('leech_news_links_'.$form['type']['#value'], LEECH_SHOW_LINK_ALWAYS)),
'#description' => t('Select place(s) where link to original article (for news items) or visit site (for news feeds) will be shown.')
);
}
// Mark if there was leech_news data with node or not
if ($node->leech_news->nid) {
$form['leech']['addons']['news']['nid'] = array(
'#type' => 'hidden',
'#value' => $node->leech_news->nid
);
}
}
// For OPML
if (isset($form['type']) && $form['type']['#value'] .'_node_settings' == $form_id) {
$form['workflow']['leech_defs']['opml'] = array(
'#type' => 'fieldset',
'#title' => t('Default leech opml options'),
'#collapsible' => FALSE,
'#collapsed' => FALSE
);
$form['workflow']['leech_defs']['opml']['leech_opml_template_'. $form['#node']->type] = array(
'#type' => 'select',
'#title' => t('Template'),
'#options' => node_template_list(),
'#default_value' => variable_get('leech_opml_template_'. $form['type']['#value'], 0),
'#description' => t('Select which node template should be used as template for opml items')
);
}
}
function leech_pre_render_addons($form_id, $form) {
global $LEECH_PRERENDERED_ADDONS;
if (is_array($LEECH_PRERENDERED_ADDONS) && isset($form['#node']) && isset($form['leech']['addons'])) {
$node = $form['#node'];
if (isset($node->leech->_prerender_id)) {
$LEECH_PRERENDERED_ADDONS[$node->leech->_prerender_id] = str_replace(array('', '
'), '', drupal_render_form('leech-addons', $form['leech']['addons']));
}
}
}
/**
* Move "Enable leech" checkbox to be used as a legend for leech defs fieldset
*/
function leech_after_build_defs($form, $form_values) {
// To do that first we render checkbox field and remove divs around it (so we have label and input left)
preg_match('%\%U', drupal_render_form('leech_enable', $form['workflow']['leech_defs']['leech_for_'.$form['#node']->type]), $matches);
// Finally we put it as title of fieldset :)
$form['workflow']['leech_defs']['#title'] = $matches[0];
return $form;
}
/**
* Implementation of hook_nodeapi().
*/
function leech_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
switch ($op) {
case 'insert':
if (leech_access('create', $node)) {
leech_insert($node);
}
break;
case 'update':
if (leech_access('update', $node)) {
leech_update($node);
}
break;
case 'delete':
if (leech_access('delete', $node)) {
leech_delete($node);
}
break;
case 'load':
return leech_load($node);
break;
case 'prepare':
leech_prepare($node, $teaser);
break;
case 'validate':
leech_validate($node, $teaser);
break;
case 'submit':
leech_submit($node);
break;
case 'view':
if (leech_access('view', $node)) {
leech_view($node, $teaser, $page);
}
break;
case 'rss item':
if ($node->leech_news_item) {
return array(array('key' => 'source',
'attributes' => array('url' => ($node->leech_news_item->source_xml ? $node->leech_news_item->source_xml : $node->leech_news_item->feed_url)),
'value' => check_plain(($node->leech_news_item->source_title ? $node->leech_news_item->source_title : $node->leech_news_item->feed_title))),
array('key' => 'dc:source',
'value' => check_plain(($node->leech_news_item->source_link ? $node->leech_news_item->source_link : $node->leech_news_item->link))));
}
if ($node->leech_news) {
return array(array('key' => 'source',
'attributes' => array('url' => $node->leech->url),
'value' => check_plain($node->title)),
array('key' => 'dc:source',
'value' => check_plain($node->leech_news->link)));
}
break;
}
}
/**
* Implementation of hook_link().
*/
function leech_link($type, $node = NULL, $teaser = FALSE) {
$links = array();
global $user;
if ($type == 'node' && $node != NULL) {
if (variable_get('leech_for_'.$node->type, 0) && isset($node->leech)) {
if ((user_access(LEECH_PERM_REFRESH_OWN) && ($user->uid == $node->uid)) || user_access('administer nodes')) {
$links['leech_leech_now'] = array('title' => t('leech data now'), 'href' => "leech/refresh/{$node->nid}", 'query' => 'destination='.$_GET['q']);
}
}
$show = ($node->leech_news_item ? $node->leech_news_item->links_display_mode : $node->leech_news->links_display_mode);
if (($show == LEECH_SHOW_LINK_ALWAYS) ||
($teaser && $show == LEECH_SHOW_LINK_TEASER_ONLY) ||
(!$teaser && $show == LEECH_SHOW_LINK_PAGE_ONLY)) {
if ($node->leech_news_item) {
$links['leech_link_full_article'] = array('title' => t('Read original article.'), 'href' => $node->leech_news_item->link);
}
if ($node->leech_news) {
$links['leech_visit_site'] = array('title' => t('Visit the site where this news was first published.'), 'href' => $node->leech_news->link);
}
}
if ($node->leech_news) {
if ((user_access(LEECH_NEWS_PERM_EDIT_OWN_ITEM) && ($user->uid == $node->uid)) || user_access('administer nodes')) {
$links['leech_remove_items'] = array('title' => t('remove items'), 'href' => "leech/remove_items/{$node->nid}");
}
if (variable_get('leech_news_show_items_link', 0) && arg(1) != 'sources') {
$links['leech_view_items'] = array('title' => t('view items'), 'href' => "leech/feed/{$node->nid}");
}
}
else if ($node->leech_news_item && arg(1) != 'feed') {
if (variable_get('leech_news_show_feed_link', 0) == LEECH_SHOW_LINK_IN_LINKS) {
$links['leech_feed'] = array('title' => t('feed'), 'href' => "leech/feed/{$node->leech_news_item->fid}", array('title' => t('view other articles from %title', array('%title' => $node->leech_news_item->feed_title))));
}
}
}
return $links;
}
/**
* Implementation of hook_cron().
*
* Checks news feeds for updates once their refresh interval has elapsed.
*/
function leech_cron() {
global $user;
$old_user = $user;
timer_start("leech_cron");
$crontimes = variable_get('leech_cron_times', array());
$crontimes[] = time();
if (count($crontimes) > 6) {
array_shift($crontimes);
}
variable_set('leech_cron_times', $crontimes);
$updated = array();
$result = db_query_range('SELECT l.nid, n.uid FROM {leech} l, {node} n WHERE l.refresh > 0 AND l.checked + l.refresh <= %d AND l.nid = n.nid ORDER BY l.checked ASC ', time(), 0, variable_get('leech_cron_count', 5));
while ($temp = db_fetch_object($result)) {
if ($temp->uid != $user->uid) {
// Fake login
if ($account = user_load(array('uid' => $temp->uid, 'status' => 1))) {
$user = $account;
}
}
// Check again if it's correct uid and only then refresh feed
if ($temp->uid == $user->uid) {
if ($leech = node_load($temp->nid)) {
leech_refresh($leech);
}
}
}
// Now "logout"
if ($user->uid != $old_user->uid) {
$user = $old_user;
}
}
/**
* Implementation of hook_prepare().
*/
function leech_prepare(&$node) {
// Overwrite only if it's not saved from cron run, so it not gets freezed after each update :)
// TODO: if there will be some other module saving nodes, it will trigger overwriting values. Find way to workaround that?
// Set default values
if (!$node->leech->_refresh_running && (!user_access('administer nodes') || !$node->nid)) {
if (variable_get('leech_for_'. $node->type, 0)) {
$node->leech->refresh = variable_get('leech_refresh_'.$node->type, 900);
}
}
// check user access here too?
if (variable_get('leech_for_'. $node->type, 0)) {
// Forms API doesn't offer a way to check values BEFORE form is build.
// And we need it for addon modules to add their options after we check what type of data
// URL points to.
// TODO: this is BAD. If there are more nodes prepared this will add leech data to EACH of them :(
if (isset($_POST['form_id']) && $_POST['form_id'] == $node->type . '_node_form') {
$url = trim($_POST['leech']['url']);
if ($url) {
$node->leech->url = $url;
}
}
// hook_prepare() seems to be used only when showing edit form, so we can download data each time
// If it is called in other cases, we have TODO something to prevent downloading data
// Also downloading whole file each time is not too wise in case of large files (video?)
if (isset($node->leech) && $node->leech->url) {
// Prepare connection object
$connection = array('url' => $node->leech->url);
if ($node->leech->_refresh_running) {
// Generate conditional GET headers.
$connection['headers'] = array();
if ($node->leech->etag) {
$connection['headers']['If-None-Match'] = $node->leech->etag;
}
if ($node->leech->modified) {
$connection['headers']['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $node->leech->modified) .' GMT';
}
}
// If it's already existing node, let other modules know about it when leechapi is called
if ($node->nid) {
$connection['node'] = &$node;
}
// Setup connection data
$connection = leech_connection($connection);
// Setup values based on connection data
if (in_array($connection->result->code, array(200, 206, 301, 302, 304, 307))) {
$node->leech->checked = time();
$node->leech->etag = $connection->result->headers['etag'];
$node->leech->mime = $connection->result->headers['content-type'];
if ($connection->result->headers['last-modified']) {
$node->leech->modified = strtotime($connection->result->headers['last-modified']);
}
}
// Remove reference to node to prevent recursive referencing
unset($connection->node);
// Let other modules know about connection too :)
$node->leech->connection = $connection;
}
}
if (isset($node->leech_news) && isset($node->leech->connection->news_feed)) {
$feed = &$node->leech->connection->news_feed;
$node->leech_news->link = $feed->link;
if (!$node->leech_news->logo && $feed->logo) {
$node->leech_news->logo = $feed->logo;
}
if (!$node->leech_news->author && $feed->author) {
$node->leech_news->author = $feed->author;
}
if (!$node->body && $feed->description) {
$node->body = $feed->description;
}
}
// Set default values
if (!$node->leech->_refresh_running && !$node->nid) {
if ($node->leech->connection->news_feed) {
$feed = &$node->leech->connection->news_feed;
$node->leech_news->template = variable_get('leech_news_template_'.$node->type, 0);
$node->leech_news->items_guid = variable_get('leech_news_items_guid_'.$node->type, 0);
$node->leech_news->items_status = variable_get('leech_news_items_status_'.$node->type, 1);
$node->leech_news->items_update = variable_get('leech_news_items_update_'.$node->type, 0);
$node->leech_news->items_delete = variable_get('leech_news_items_delete_'.$node->type, 15724800);
$node->leech_news->items_promote = variable_get('leech_news_items_promote_'.$node->type, 3);
$node->leech_news->items_date = variable_get('leech_news_items_date_'.$node->type, 1);
$node->leech_news->links_display_mode = variable_get('leech_news_links_'.$node->type, LEECH_SHOW_LINK_ALWAYS);
// Autopopulate title field
if (strlen($feed->channel[TITLE][0][VALUE]) > 1) {
$node->title = $feed->channel[TITLE][0][VALUE];
}
}
}
// For OPML
if (isset($node->leech) && isset($node->leech->connection->result->dataXML)) {
$opml = &$node->leech->connection->result->dataXML;
$title = leech_opml_parse_value($opml, $opml['opml'][0]['head'][0]['title'][0]);
if (!$node->title && $title) {
$node->title = $title;
}
}
// Set default values
if (!$node->leech->_refresh_running && (!user_access('administer nodes') || !$node->nid)) {
if (variable_get('leech_for_'.$node->type, 0) && $node->leech->connection->result->dataXML) {
$node->leech_opml->template = variable_get('leech_opml_template_'.$node->type, 0);
}
}
return $node;
}
/**
* Implementation of hook_access().
*/
function leech_access($op, $node) {
global $user;
switch ($op) {
case 'create':
return user_access(LEECH_PERM_CREATE);
break;
case 'update':
case 'delete':
if ($node->leech->_refresh_running || user_access('administer nodes') || (user_access(LEECH_PERM_EDIT_OWN) && ($user->uid == $node->uid))) {
return TRUE;
}
break;
case 'view':
return user_access(LEECH_PERM_ACCESS);
break;
}
}
/**
* Implementation of hook_validate().
*/
function leech_validate(&$node, $form = array()) {
if (isset($form['leech']) && isset($form['leech']['url']) && isset($form['leech']['url']['#value'])) {
if (trim($form['leech']['url']['#value']) == '') {
return;
}
$result = db_query("SELECT l.nid, n.title FROM {node} n, {leech} l WHERE l.url = '%s' AND l.nid = n.nid", $form['leech']['url']['#value']);
while ($leech = db_fetch_object($result)) {
if ($leech->nid != $node->nid) {
$link = l($leech->title, "node/{$leech->nid}");
form_set_error('leech][url', t('Duplicate URL: %link already uses that URL.', array('%link' => $link)));
return;
}
}
if (!leech_is_valid_url($form['leech']['url']['#value'])) {
form_set_error('leech][url', t('URL not allowed (blacklisted).'));
return;
}
if (isset($form['leech']['connection']['#value'])) {
$connection = $form['leech']['connection']['#value'];
switch ($connection->result->code) {
case 200:
case 206:
case 302:
case 307:
// Data was downloaded successfully
break;
case 401: // Unauthorized
break;
default:
form_set_error('leech][url', t('Error while checking URL: %code', array('%code' => $connection->result->code .', '. $connection->result->error)));
break;
}
}
}
}
/**
* Implementation of hook_submit().
*/
function leech_submit(&$node) {
if (isset($node->leech)) {
$node->leech = (object)$node->leech;
if (!trim($node->leech->url)) {
unset($node->leech);
}
}
if (isset($node->leech) && !isset($node->leech_news) && isset($node->leech->addons['news'])) {
$node->leech_news = (object)$node->leech->addons['news'];
unset($node->leech->addons['news']);
}
// Create items after we know feed was validated ok (submit is called after validate)
if ($node->leech->_refresh_running && isset($node->leech_news) && isset($node->leech->connection->news_feed)) {
_leech_news_save_items($node);
}
if (isset($node->leech_news)) {
if (variable_get('leech_news_pass_on_taxonomy', 0)) {
_leech_news_pass_on_taxonomy($node);
}
_leech_news_pass_on_groups($node);
}
// For OPML
if (isset($node->leech) && !isset($node->leech_opml) && isset($node->leech->addons['opml'])) {
$node->leech_opml = (object)$node->leech->addons['opml'];
unset($node->leech->addons['opml']);
}
// Create items after we know feed was validated ok (submit is called after validate)
if ($node->leech->_refresh_running && isset($node->leech_opml) && isset($node->leech->connection->result->dataXML)) {
leech_opml_save_items($node);
}
}
/**
* Implementation of hook_insert().
*/
function leech_insert($node) {
if (isset($node->leech)) {
db_query("INSERT INTO {leech} (nid, url, refresh, news_last_arrived, adaptive) VALUES (%d, '%s', %d, %d, %d)", $node->nid, $node->leech->url, $node->leech->refresh, time(), $node->leech->adaptive);
}
if (isset($node->leech_news)) {
db_query("INSERT INTO {leech_news_feed} (nid, template, logo, link, author, items_guid, items_status, items_update, items_delete, items_promote, items_date, links_display_mode) VALUES (%d, %d, '%s', '%s', '%s', %d, %d, %d, %d, %d, %d, %d)", $node->nid, $node->leech_news->template, $node->leech_news->logo, $node->leech_news->link, $node->leech_news->author, $node->leech_news->items_guid, $node->leech_news->items_status, $node->leech_news->items_update, $node->leech_news->items_delete, $node->leech_news->items_promote, $node->leech_news->items_date, $node->leech_news->links_display_mode);
}
if (isset($node->leech_news_item)) {
db_query("INSERT INTO {leech_news_item} (nid, fid, link, author, guid, source_link, source_xml, source_title) VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->leech_news_item->fid, $node->leech_news_item->link, $node->leech_news_item->author, $node->leech_news_item->guid, $node->leech_news_item->source_link, $node->leech_news_item->source_xml, $node->leech_news_item->source_title);
}
if (isset($node->leech_opml)) {
db_query("INSERT INTO {leech_opml} (nid, template) VALUES (%d, %d)", $node->nid, $node->leech_opml->template);
}
}
/**
* Implementation of hook_update().
*/
function leech_update($node) {
if (isset($node->leech)) {
if ($node->leech->is_new) {
leech_insert($node);
}
else {
db_query("UPDATE {leech} SET url = '%s', refresh = %d, checked = %d, modified = %d, etag = '%s', mime = '%s', adaptive = %d WHERE nid = %d", $node->leech->url, $node->leech->refresh, $node->leech->checked, $node->leech->modified, $node->leech->etag, $node->leech->mime, $node->leech->adaptive, $node->nid);
}
}
else {
db_query('DELETE FROM {leech} WHERE nid = %d', $node->nid);
}
if (isset($node->leech_news)) {
if ($node->leech_news->nid) {
db_query("UPDATE {leech_news_feed} SET template = %d, logo = '%s', link = '%s', author = '%s', items_guid = %d, items_status = %d, items_update = %d, items_delete = %d, items_promote = %d, items_date = %d, links_display_mode = %d WHERE nid = %d", $node->leech_news->template, $node->leech_news->logo, $node->leech_news->link, $node->leech_news->author, $node->leech_news->items_guid, $node->leech_news->items_status, $node->leech_news->items_update, $node->leech_news->items_delete, $node->leech_news->items_promote, $node->leech_news->items_date, $node->leech_news->links_display_mode, $node->leech_news->nid);
}
else {
leech_insert($node);
}
}
if (isset($node->leech_news_item)) {
db_query("UPDATE {leech_news_item} SET fid = %d, link = '%s', author = '%s' WHERE nid = %d", $node->leech_news_item->fid, $node->leech_news_item->link, $node->leech_news_item->author, $node->leech_news_item->nid);
}
if (isset($node->leech_opml)) {
if ($node->leech_opml->nid) {
db_query("UPDATE {leech_opml} SET template = %d WHERE nid = %d", $node->leech_opml->template, $node->leech_opml->nid);
}
/*else {
leech_opml_insert($node);
}*/
}
}
/**
* Implementation of hook_delete().
*/
function leech_delete(&$node) {
db_query('DELETE FROM {leech} WHERE nid = %d', $node->nid);
db_query('DELETE FROM {leech_news_feed} WHERE nid = %d', $node->nid);
db_query('DELETE FROM {leech_news_item} WHERE nid = %d', $node->nid);
db_query('DELETE FROM {leech_opml} WHERE nid = %d', $node->nid);
}
/**
* Implementation of hook_load().
*/
function leech_load($node) {
$temp = array();
$result = db_fetch_object(db_query('SELECT * FROM {leech_news_feed} WHERE nid = %d', $node->nid));
if ($result) {
$temp['leech_news'] = $result;
}
$result = db_fetch_object(db_query('SELECT i.*, n.title AS feed_title, f.links_display_mode, f.link AS feed_link, l.url AS feed_url FROM {leech_news_item} i LEFT JOIN {node} n ON n.nid = i.fid LEFT JOIN {leech_news_feed} f ON f.nid = i.fid LEFT JOIN {leech} l ON l.nid = i.fid WHERE i.nid = %d', $node->nid));
if ($result) {
$temp['leech_news_item'] = $result;
}
$result = db_fetch_object(db_query('SELECT * FROM {leech} WHERE nid = %d', $node->nid));
if ($result) {
$temp['leech'] = $result;
}
$result = db_fetch_object(db_query('SELECT * FROM {leech_opml} WHERE nid = %d', $node->nid));
if ($result) {
$temp['leech_opml'] = $result;
}
if (count($temp) > 0) {
return $temp;
}
}
/**
* theme function for inline links from feed items to feeds
*
* @param unknown_type $links
*/
function theme_leech_inline_link_to_feed($links) {
foreach ($links as $link) {
$formatted_links[] = l($link['text'], 'leech/feed/'.$link['fid'], array('title' => $link['text']));
}
return ''.t('Feed').": ".implode(', ', $formatted_links).'
';
}
/**
* Implementation of hook_view().
*/
function leech_view(&$node, $teaser = FALSE, $page = FALSE) {
if (isset($node->leech_news_item->feed_title)) {
if (variable_get('leech_news_show_feed_link', 0) == LEECH_SHOW_LINK_INLINE) {
$links[] = array('fid' => $node->leech_news_item->fid, 'text' => $node->leech_news_item->feed_title);
$themed_links = theme('leech_inline_link_to_feed', $links);
if (isset($node->teaser)) {
$teaser->content['leech_links'] = array('#value' => $themed_links, '#weight' => 10);
}
if (isset($node->body)) {
$node->content['leech_links'] = array('#value' => $themed_links, '#weight' => 10);
}
}
}
if (!isset($node->leech)) {
return;
}
// Provide some statistics
$rows = array();
$rows[] = array(t('Source URL'), $node->leech->url);
$rows[] = array(t('Last checked'), ($node->leech->checked ? t('%time ago', array('%time' => format_interval(time() - $node->leech->checked))) : t('never')) );
$rows[] = array(t('Time until next refresh'), ($node->leech->checked ? t('%time left', array('%time' => format_interval($node->leech->checked + $node->leech->refresh - time()))) : ($node->leech->refresh > 0 ? t('immediately') : t('never') )) );
$output .= theme('table', array(), $rows);
$node->content['leech_stat'] = array('#value' => $output, '#weight' => 10);
$teaser->content['leech_stat'] = array('#value' => $output, '#weight' => 10);
}
/**
* Implementation of hook_leechapi().
*/
/*
function leech_leechapi(&$connection, $op) {
if ($op != 'resolve' || !isset($connection->result)
|| !in_array($connection->result->code, array(200, 206, 301, 302, 304, 307))) {
return;
}
// Check MIME
$mime = explode(';', $connection->result->headers['content-type'], 2);
if ($mime[0]) $mime = $mime[0];
if (!in_array($mime, array('text/xml', 'application/xml', 'text/html', 'application/rss+xml', 'application/atom+xml', 'application/rdf+xml', 'application/opml+xml'))) {
return;
}
// Check if data contains format we can handle
if (preg_match('%<(rss|rdf:rdf|feed|channel)[^>]*>(?=.*\1>)%siU', $connection->result->data, $matches) < 1) {
return;
}
// We can also do some format specific settings. $matches[1] contains tag name (rss, rdf:rdf, feed or channel :).
// ...
if (!isset($connection->news_feed)) {
$connection->news_feed = _leech_news_parse($connection->result->data);
}
$connection->result->dataXML = leech_opml_parse($connection->result->data);
}
*/
/**
* Menu callback; displays the leech administration page.
*/
function leech_page_admin() {
global $user;
if (!user_access('administer nodes')) {
$uid = '';
$can_edit = user_access(LEECH_PERM_EDIT_OWN);
$can_refresh = user_access(LEECH_PERM_REFRESH_OWN);
}
else {
$uid = '';
$can_edit = TRUE;
$can_refresh = TRUE;
}
$result = db_query("SELECT n.nid, n.title, l.checked, l.refresh FROM {node} n INNER JOIN {leech} l ON n.nid = l.nid $uid ORDER BY n.title ASC");
$header = array(t('Title'), t('Last checked'), t('Next check'), array('data' => t('Operations'), 'colspan' => '3'));
$rows = array();
while ($leech = db_fetch_object($result)) {
$rows[] = array(l($leech->title, "leech/feed/$leech->nid"), ($leech->checked ? t('%time ago', array('%time' => format_interval(time() - $leech->checked))) : t('never')), ($leech->refresh < 1 ? t('Freezed') : t('%time left', array('%time' => format_interval($leech->checked + $leech->refresh - time())))), ($can_edit ? l(t('edit'), "node/$leech->nid/edit") : ''), ($can_refresh ? l(t('leech'), "leech/refresh/{$leech->nid}") : ''));
}
$output .= theme('table', $header, $rows);
return $output;
}
/**
* Menu callback; Generate a listing of feed items.
*/
function leech_page_feed_list() {
return leech_page_admin();
}
/**
* Menu callback; Generates an OPML representation of all feeds.
*/
function leech_page_opml() {
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, l.url FROM {node} n, {leech} l WHERE l.nid = n.nid ORDER BY n.title ASC'));
// should we do this?: $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, l.url FROM {node} n, {leech} l, {leech_news_feed} f WHERE f.nid = n.nid AND l.nid = f.nid ORDER BY n.title ASC'));
$output = '';
while ($leech = db_fetch_object($result)) {
$output .= ''."\n";
}
drupal_set_header('Content-Type: text/xml; charset=utf-8');
print _opml_skeleton($output);
}
/**
* Menu callback; refresh feed.
*/
function leech_page_refresh($nid = NULL) {
if ($nid == NULL && is_numeric(arg(1))) {
$nid = arg(1);
}
$leech = node_load($nid);
if ($leech && variable_get('leech_for_'.$leech->type, 0) && isset($leech->leech)) {
global $user;
$old_user = FALSE;
if ($leech->uid != $user->uid || !user_access(LEECH_PERM_REFRESH_OWN)) {
if (!user_access('administer nodes')) {
drupal_access_denied();
return;
}
else if ($leech->uid != $user->uid) {
// Fake owner user
$old_user = $user;
$user = user_load(array('uid' => $leech->uid));
}
}
// Reset modified date to "really" refresh
$leech->leech->modified = 0;
$leech->leech->etag = 0;
$leech->leech->checked = 0;
leech_refresh($leech);
// If needed, back to "real" user
if ($old_user !== FALSE) {
$user = $old_user;
}
}
if ($_GET['destination']) {
drupal_goto($_GET['destination']);
}
else {
drupal_goto('node/'. $nid);
}
}
/**
* Menu callback; return html for addons part of the node form.
*/
function leech_get_form() {
$url = $_POST['url'];
$nid = $_POST['nid'];
$type = $_POST['type'];
if (!$url) {
echo t('No URL received');
return;
}
// prepare $node, get form for it, and output [leech][addons] part of it
if (!$nid) {
// From node_add()
global $user;
$node = array('uid' => $user->uid, 'name' => $user->name, 'type' => $type, 'leech' => new StdClass());
$node['leech']->url = $url;
$node['leech']->_prerender_id = time();
$node = (object)$node;
}
else {
$node = node_load($nid);
$node->leech->url = $url;
$node->leech->_prerender_id = time();
}
// Make it array, just in case some internal mechanism starts new rendering, of other node
// In alter_form we'll check for _prerender_id and at #pre_render time we'll setup
// $LEECH_PRERENDERED_ADDONS[_prerender_id] = render_form($form['leech']['addons']);
global $LEECH_PRERENDERED_ADDONS;
$LEECH_PRERENDERED_ADDONS = array();
$output = node_form($node);
drupal_prepare_form('leech_addons', $output);
drupal_render_form('leech_addons', $output);
if (isset($LEECH_PRERENDERED_ADDONS[$node->leech->_prerender_id])) {
echo $LEECH_PRERENDERED_ADDONS[$node->leech->_prerender_id];
exit();
}
else {
echo t('Could not read feed');
exit();
}
}
/**
* Menu callback; generate javascript.
*/
function leech_javascript() {
global $base_url;
// This is leech.js
echo '
function leech_prepare_check_url(field) {
field.old_value = field.value;
$(this).addClass(field, "form-autocomplete");
field.onkeyup = function (event) { leech_keyup(field, event); };
}
function leech_close_check_url(field) {
leech_check_url(field);
$(this).removeClass(field, "form-autocomplete");
$(this).removeClass(field, "throbbing");
}
function leech_keyup(field, e) {
if (!e) {
e = window.event;
}
switch (e.keyCode) {
case 16: // shift
case 17: // ctrl
case 18: // alt
case 20: // caps lock
case 33: // page up
case 34: // page down
case 35: // end
case 36: // home
case 37: // left arrow
case 38: // up arrow
case 39: // right arrow
case 40: // down arrow
return true;
case 9: // tab
case 13: // enter
case 27: // esc
return true;
default: // all other keys
clearTimeout(field.timer);
field.timer = setTimeout(function() { leech_check_url(field); }, 500);
return true;
}
}
function leech_check_url(field) {
if (field.old_value == field.value) {
return;
}
field.old_value = field.value;
if (field.value.length < 1) {
$("edit-leech-addons").innerHTML = "";
return;
}
$(this).addClass(field, "throbbing");
var param = new Array();
param["url"] = field.value;
if ($("#edit-leech-nid").val()) {
param["nid"] = $("#edit-leech-nid").val();
}
var node_type = $("#edit-leech-node-type");
param["type"] = node_type.val();
HTTPPost("'. $base_url .'/?q=/leech/get/form", leech_update_addons, field, param);
}
function leech_update_addons(string, xmlhttp, field) {
if (xmlhttp.status != 200 && typeof xmlhttp.status != "undefined") {
alert("An HTTP error "+ xmlhttp.status +" occured.\n"+ field.value);
}
var node = $("#edit-leech-addons");
node.html(string);
var nodetitle = $("#edit-title").val();
var feedtitle = $("#edit-leech-addons-news-title");
if (nodetitle.length == 0) {
$("#edit-title").val(feedtitle.val());
}
collapseAttach(node);
$(this).removeClass(field, "throbbing");
// Now animate scroll page to additional options
var h = self.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0;
var offset = self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
var pos = Drupal.absolutePosition($("edit-leech-addons"));
var nh = $("edit-leech-addons").scrollHeight;
if (pos.y + nh > h + offset) {
var scrollto = pos.y;
if (nh <= h) scrollto = pos.y + nh - h;
var count = 0;
for (var i=offset;i<=scrollto;i=i+((scrollto+2-i)/2)) {setTimeout("window.scrollTo(0, "+i+")",70*(count++));} // from quirksmode.org
for (var i=0;i<=10;i++) {setTimeout("setBG("+(155+(10*i))+")",70*i);}
for (var i=10;i<=20;i++) {setTimeout("setBG("+(355-(10*i))+")",70*i);}
for (var i=20;i<=30;i++) {setTimeout("setBG("+(-45+(10*i))+")",70*i);}
setBG = function (c) {
var n = $("#edit-leech-addons");
n.style.backgroundColor = "rgb("+c+","+c+","+c+")";
}
}
}
function collapseAttach(element) {
var fieldsets = $("leech-fieldset");
var legend, fieldset;
for (var i = 0; fieldset = fieldsets[i]; i++) {
if (!Drupal.hasClass(fieldset, "collapsible")) {
continue;
}
legend = fieldset.getElementsByTagName("legend");
if (legend.length == 0) {
continue;
}
legend = legend[0];
var a = document.createElement("a");
a.href = "#";
a.onclick = function() {
toggleClass(this.parentNode.parentNode, "collapsed");
if (!Drupal.hasClass(this.parentNode.parentNode, "collapsed")) {
Drupal.collapseScrollIntoView(this.parentNode.parentNode);
if (typeof textAreaAutoAttach != "undefined") {
// Add the grippie to a textarea in a collapsed fieldset.
Drupal.textAreaAutoAttach(null, this.parentNode.parentNode);
}
}
this.blur();
return false;
};
a.innerHTML = legend.innerHTML;
while (legend.hasChildNodes()) {
$(this).removeNode(legend.childNodes[0]);
}
legend.appendChild(a);
Drupal.collapseEnsureErrorsVisible(fieldset);
}
}
function HTTPPost(uri, callbackFunction, callbackParameter, object) {
var xmlHttp = new XMLHttpRequest();
var bAsync = true;
if (!callbackFunction) {
bAsync = false;
}
xmlHttp.open("POST", uri, bAsync);
var toSend = "";
if (typeof object == "object") {
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
for (var i in object) {
toSend += (toSend ? "&" : "") + i + "=" + encodeURIComponent(object[i]);
}
}
else {
toSend = object;
}
xmlHttp.send(toSend);
if (bAsync) {
if (callbackFunction) {
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4) {
callbackFunction(xmlHttp.responseText, xmlHttp, callbackParameter);
}
}
}
return xmlHttp;
}
else {
return xmlHttp.responseText;
}
}
';
}
/**
* Invoke a hook_leechapi() operation in all modules.
*
* @param &$connection
* A connection object.
* @param $op
* A string containing the name of the leechapi operation.
* @return
* The returned value of the invoked hooks.
*/
function leech_invoke_leechapi(&$connection, $op) {
$return = array();
foreach (module_implements('leechapi') as $name) {
$function = $name .'_leechapi';
$result = $function($connection, $op);
if (isset($result) && is_array($result)) {
$return = array_merge($return, $result);
}
else if (isset($result)) {
$return[] = $result;
}
}
return $return;
}
/**
* Private function; If URL is ok returns 1. If it's blacklisted returns 0. If expression contains error returns FALSE.
*/
function leech_is_valid_url($url) {
static $blacklist = NULL;
// Prepare pattern string
if ($blacklist == NULL) {
$temp = variable_get('leech_blacklist_url', '');
$blacklist = array();
if ($temp) {
$temp = explode("\n", $temp);
foreach ($temp as $line) {
// Simple comparision
if (preg_match('/^\w+:\/\//', $line)) {
$blacklist[] = '^'.preg_replace('/[^\w]/', '\\\$0', $line).'$';
}
// Check by domain and subdomain
else if ($line[0] != '/') {
if ($line[0] == '.') {
$blacklist[] = '^\w+:\/\/(?:\w+\.|\.)*'.preg_replace('/[^\w]/', '\\\$0', substr($line, 1)).'.*?$';
}
else {
$blacklist[] = '^\w+:\/\/'.preg_replace('/[^\w]/', '\\\$0', $line).'.*$';
}
}
// Use pattern
else {
$blacklist[] = substr($line, 1, -1);
}
}
if (count($blacklist) > 0) {
$blacklist = '/'.implode('|', $blacklist).'/';
}
else {
$blacklist = '';
}
}
}
if ($blacklist) {
return !preg_match($blacklist, $url, $matches);;
}
return 1;
}
/**
* Private function; Leech data.
*
* @param $connection
* Array with connection parameters, simple url string, or already existing connection object.
* @return
* Connection object with $connection->result containing data and http code downloaded from $connection->url.
*/
function leech_connection($connection) {
// Validate connection data
/*
$connection = array (
'url' => $node->leech->url, // required
'headers' => array(), // optional
'method' => 'GET', // optional, default: GET
'data' => NULL, // optional
'follow' => 5, // optional, default: 3
'timeout' => 15, // optional, default: 15
'cookie' => NULL, // optional
'onlyHeaders' => FALSE, // optional, default: FALSE
'username' => NULL, // optional
'password' => NULL // optional
);
*/
$connection = (object)$connection;
if (!isset($connection->url) && isset($connection->scalar) && is_string($connection->scalar)) {
$connection->url = $connection->scalar;
unset($connection->scalar);
}
if (!isset($connection->url)) {
return;
}
if (!is_array($connection->headers)) {
$connection->headers = array();
}
if (!isset($connection->method)) {
$connection->method = 'GET';
}
if (!isset($connection->follow)) {
$connection->follow = '3';
}
if (!isset($connection->timeout)) {
$connection->timeout = '15';
}
if (!isset($connection->onlyHeaders)) {
$connection->onlyHeaders = FALSE;
}
leech_invoke_leechapi($connection, 'prepare');
// Check if it's legal url
if (!leech_is_valid_url($conection->url)) {
return $connection;
}
// Connect and download data
$connection->result = leech_http_request($connection->url, $connection->headers, $connection->data, $connection->follow, $connection->timeout, $connection->cookie, $connection->onlyHeaders, $connection->username, $connection->password);
// this was what was before in leech_news_leechapi() and leech_opml_leechapi()
if (in_array($connection->result->code, array(200, 206, 301, 302, 304, 307))) {
// Check MIME
$mime = isset($connection->result->headers['content-type']) ?
explode(';', $connection->result->headers['content-type'], 2) :
explode(';', $connection->result->headers['Content-Type'], 2);
if ($mime[0]) $mime = $mime[0];
if (!in_array($mime, array('text/xml', 'application/xml', 'text/html', 'application/rss+xml', 'application/atom+xml', 'application/rdf+xml', 'application/opml+xml'))) {
return;
}
// Check if data contains format we can handle
if (preg_match('%<(rss|rdf:rdf|feed|channel)[^>]*>(?=.*\1>)%siU', $connection->result->data, $matches) < 1) {
return;
}
// We can also do some format specific settings. $matches[1] contains tag name (rss, rdf:rdf, feed or channel :).
// ...
if (!isset($connection->news_feed)) {
$connection->news_feed = _leech_news_parse($connection->result->data);
}
$connection->result->dataXML = leech_opml_parse($connection->result->data);
}
leech_invoke_leechapi($connection, 'resolve');
return $connection;
}
/**
* Private function; Checks a news feed for new items.
*/
function leech_refresh(&$leech) {
if (!isset($leech->leech)) {
return;
}
$leech->leech->_refresh_running = TRUE;
// Download and prepare data
node_object_prepare($leech);
$result = &$leech->leech->connection->result;
// If moved permanently update URL
if ($result->code == 301 && $result->redirect_url) {
$leech->leech->url = $result->redirect_url;
watchdog('leech', t('Updated URL for leech %title to %url.', array('%title' => $leech->title, '%url' => $leech->leech->url)), WATCHDOG_NOTICE, l(t('view'), 'node/'.$leech->nid));
}
// If response was ok, save it :)
if (in_array($result->code, array(200, 301, 302, 307))) {
$node->changed = time();
$_POST['edit']['changed'] = time();
node_validate($leech);
if (!($errors = form_get_errors())) {
leech_submit($leech);
leech_update($leech);
watchdog('leech', t('Leeched content from %site.', array('%site' => $leech->title)), WATCHDOG_NOTICE, l(t('view'), 'node/'.$leech->nid));
sleep(variable_get('leech_sleep_interval', 0));
}
else {
watchdog('leech', t('Failed to validate %site: %error.', array('%site' => $leech->title, '%error' => implode("
\n", $errors))), WATCHDOG_ERROR, l(t('view'), 'node/'.$leech->nid));
}
}
// Update checked time and adaptive infos,
//so it won't be checked each on cron run, and thus block other feeds (outside of limit count) to be checked.
db_query('UPDATE {leech}
SET checked = %d, avg_btw_news = %d, deviation = %d,
num_of_tests = %d, news_last_arrived = %d, refresh = %d
WHERE nid = %d',
time(),
$leech->leech->avg_btw_news,
$leech->leech->deviation,
$leech->leech->num_of_tests,
$leech->leech->news_last_arrived,
$leech->leech->refresh,
$leech->nid
);
unset($leech->leech->_refresh_running);
}
/**
* Private function; Parse HTTP headers from data retreived with cURL
* from: http://pl2.php.net/manual/en/function.curl-setopt.php#42009
*/
function leech_parse_http_response($response) {
/*
***original code extracted from examples at
***http://www.webreference.com/programming/php/cookbook/chap11/1/3.html
***returns an array in the following format which varies depending on headers returned
[0] => the HTTP error or response code such as 404
[1] => Array
(
[Server] => Microsoft-IIS/5.0
[Date] => Wed, 28 Apr 2004 23:29:20 GMT
[X-Powered-By] => ASP.NET
[Connection] => close
[Set-Cookie] => COOKIESTUFF
[Expires] => Thu, 01 Dec 1994 16:00:00 GMT
[Content-Type] => text/html
[Content-Length] => 4040
)
[2] => Response body (string)
*/
do {
list($response_headers, $response) = explode("\r\n\r\n", $response, 2);
$response_header_lines = explode("\r\n", $response_headers);
// first line of headers is the HTTP response code
$http_response_line = array_shift($response_header_lines);
if (preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@', $http_response_line, $matches)) {
$response_code = $matches[1];
}
else {
$response_code = "Error";
}
}
while (substr($response_code, 0, 1) == "1");
$response_body = $response;
// put the rest of the headers in an array
$response_header_array = array();
foreach ($response_header_lines as $header_line) {
list($header, $value) = explode(':', $header_line, 2);
$response_header_array[strtolower($header)] = trim($value);
}
return array($response_code, $response_header_array, $response_body, $http_response_line);
}
/**
* Downloads data from given url, follow redirects and provides coherent UTF-8 output for feeds
*/
function leech_http_request($url, $headers = array(), $data = NULL, $follow = 3, $timeout = 15, $cookie = NULL, $onlyHeaders = FALSE, $username = NULL, $password = NULL) {
$method = 'GET';
if (!function_exists('curl_init')) {
if ($onlyHeaders) {
$headers['Range'] = 'bytes=0-1';
}
if ($username != NULL && $password != NULL) {
$headers['Authorization'] = 'Basic: '. base64_encode("$username:$password");
}
return drupal_http_request($url, $headers, $method, $data, $follow);
}
// Convert headers array to format used by cURL
$temp = array();
if (is_array($headers)) {
foreach ($headers as $header => $value) {
$temp[] = $header .': '. $value;
}
}
$headers = $temp;
$result = new StdClass();
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_USERAGENT, 'Drupal');
if ($onlyHeaders) {
curl_setopt($ch, CURLOPT_NOBODY, TRUE);
}
if (strlen($username) > 0 && strlen($password) > 0) {
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
}
if ($cookie != NULL) {
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
}
$temp = curl_exec($ch);
if (($result->code = curl_errno($ch)) != 0) {
$result->error = curl_error($ch);
curl_close($ch);
unset($ch);
return $result;
}
curl_close($ch);
unset($ch);
$response = leech_parse_http_response($temp);
$result->code = $response[0];
$result->headers = $response[1];
$result->data = $response[2];
// Convert any exotic charset into UTF-8 standard
if (function_exists("mb_check_encoding")) {
preg_match("/encoding=['\"]([a-z0-9-\/]*)['\"]/", $result->data, $matches);
$enc_from = $matches[1];
if (!empty($enc_from) && mb_check_encoding($result->data, $enc_from)) {
$result->data = mb_convert_encoding($result->data, "UTF-8", $enc_from);
}
}
$error = $response[3];
switch ($result->code) {
case 200: // OK
case 206: // Partial content returned (we asked for range of bytes)
case 304: // Not modified
break;
case 301: // Moved permanently
case 302: // Moved temporarily
case 307: // Moved temporarily
$location = $result->headers['location'];
if ($follow) {
$result = leech_http_request($result->headers['location'], $headers, $data, --$follow, $timeout, $cookie, $onlyHeaders, $username, $password);
$result->redirect_code = $result->code;
}
$result->redirect_url = $location;
break;
default:
$result->error = $error;
break;
}
$result->code = $response[0];
return $result;
}
/**
* Update feed node and create news items
*
* @param $node ... feed node
*/
function _leech_news_save_items(&$node) {
if (!is_array($node->leech->connection->news_feed->items) || count($node->leech->connection->news_feed->items) < 1) {
return;
}
$edit = (object) node_template_load($node->leech_news->template);
if ($edit == NULL) {
return;
}
// Don't add items older than allowed age for items
if ($node->leech_news->items_delete != 1000000000) {
// calculate time horizont converted to user's timezone
$time_horizont = time() - $node->leech_news->items_delete;
}
else {
$time_horizont = 0;
}
// Prepare data needed for promoting items
// TODO: maybe select just those which will not be deleted? so update at the end of this funtion would have less nodes to update :)
$promoted = array();
$promoted_changed = FALSE;
if ($node->leech_news->items_promote != 1000000000 && $node->leech_news->items_promote != 0) {
$result = db_query('SELECT i.nid AS nid, n.created AS created FROM {node} n, {leech_news_item} i WHERE i.fid = %d AND i.nid = n.nid AND n.status = 1 AND n.promote = 1 ORDER BY n.created ASC', $node->nid);
while ($temp = db_fetch_array($result)) {
$promoted[$temp['nid']] = $temp['created'];
}
if (count($promoted) != $node->leech_news->items_promote) {
$promoted_changed = TRUE;
}
}
// if og installed, feed items should inherit feed's og settings
if (module_exists('og')){
$inheritgroup = true;
}
if (module_exists('taxonomy')) {
$inherittaxonomy = variable_get('leech_news_pass_on_taxonomy', 0);
}
global $user;
$duplicate_count = 0;
$items_added = 0;
$unique_links = $node->leech->connection->news_feed->has_unique_links;
foreach ($node->leech->connection->news_feed->items as $item) {
if (!$item->guid && $node->leech_news->items_guid) {
$item->guid = md5("$item->title - $item->body");
}
if (!$node->leech_news->items_date) {
$item->date = time();
}
// Ignore items older than allowed for feed
if ($item->date < $time_horizont) {
continue;
}
// check for duplicates
if (trim($item->link)) {
$result = db_result(db_query("SELECT COUNT(nid) FROM {leech_news_item} WHERE link = '%s' AND fid = %d", $item->link, $node->nid));
if ($result > 0) {
$duplicate_count++;
if (variable_get('leech_news_verbose', FALSE) === 1) {
watchdog('leech', t('Found duplicate. Link: !link', array('!link' => l($item->link, $item->link))));
}
continue;
}
}
else {
watchdog('leech', t('Feed item has no link. Title: %title', array('%title' => $item->title)));
}
$entry = NULL;
if (isset($item->guid) && strlen($item->guid) > 0) {
$entry = db_fetch_object(db_query("SELECT nid FROM {leech_news_item} WHERE guid = '%s' AND fid = %d", $item->guid, $node->nid));
}
else if (isset($unique_links) && isset($item->link) && $item->link != $node->leech_news->link && $item->link != $node->leech->url) {
$entry = db_fetch_object(db_query("SELECT nid FROM {leech_news_item} WHERE link = '%s' AND fid = %d", $item->link, $node->nid));
}
else {
$entry = db_fetch_object(db_query("SELECT n.nid AS nid FROM {node} n, {leech_news_item} i WHERE i.fid = %d AND i.nid = n.nid AND n.title = '%s'", $node->nid, $item->title));
}
if (isset($entry) && isset($entry->nid)) {
if (!$node->leech_news->items_update) {
continue;
}
$edit = node_load($entry->nid);
}
else {
$edit = (object) node_template_load($node->leech_news->template);
}
// avoid overwriting user's changes
if (!isset($edit->nid)) {
// From node.module
$node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
foreach (array('moderate', 'sticky', 'revision') as $key) {
$node->$key = in_array($key, $node_options);
}
$edit->status = $node->leech_news->items_status;// in_array('status', $node_options);
if ($node->leech_news->items_promote == 0) {
$edit->promote = 0;
}
else {
$edit->promote = 1;
}
$edit->date = format_date($item->date, 'custom', 'Y-m-d H:i O');
$edit->created = strtotime($edit->date);
$edit->leech_news_item->guid = ($item->guid ? $item->guid : '');
}
$edit->leech_news_item->fid = $node->nid;
$edit->leech_news_item->link = ($item->link ? $item->link : '');
if (!$edit->leech_news_item->author) {
$edit->leech_news_item->author = ($item->author ? $item->author : '');
}
$edit->leech_news_item->source_link = ($item->source_link ? $item->source_link : '');
$edit->leech_news_item->source_xml = ($item->source_xml ? $item->source_xml : '');
$edit->leech_news_item->source_title = ($item->source_title ? $item->source_title : '');
$edit->title = $item->title;
$edit->body = $item->body;
$edit->teaser = ($item->teaser ? $item->teaser : node_teaser($edit->body, isset($edit->format) ? $edit->format : NULL));
// copy group properties from feed node - note: groups present here
// pass on group properties from feed to item
if ($inheritgroup == TRUE) {
if (isset($node->og_public) ) {
if (!isset($edit->og_public)) {
$edit->og_public = 1;
}
$edit->og_public = $edit->og_public & $node->og_public;
}
if (isset($node->og_groups)) {
if (!is_array($edit->og_groups)) {
$edit->og_groups = array();
}
$edit->og_groups = array_merge($node->og_groups, $edit->og_groups);
}
if (isset($node->og_groups_names)) {
if (!is_array($edit->og_groups_names)) {
$edit->og_groups_names = array();
}
$edit->og_groups_names = array_merge($node->og_groups_names, $edit->og_groups_names);
}
}
// pass on taxonomy from feed to item
if ($inherittaxonomy == TRUE) {
if (isset($node->taxonomy)) {
if (!is_array($edit->taxonomy)) {
$edit->taxonomy = array();
}
$edit->taxonomy = array_merge($node->taxonomy, $edit->taxonomy);
}
if (isset($node->taxonomy_user)) {
if (!is_array($edit->taxonomy_user)) {
$edit->taxonomy_user = array();
}
$edit->taxonomy = array_merge($node->taxonomy_user, $edit->taxonomy_user);
}
}
// Temporary properties
//$edit->leech_news_item->feed_data = &$node->leech->connection->news_feed;
//$edit->leech_news_item->item_data = &$item;
//$edit->leech_news_item->feed_node = &$node;
// Postprocess the node, call hook_leech_news with $op = 'postproces
node_invoke_nodeapi($edit, 'leech_news_presave');
if ($errors = node_template_save($edit)) {
watchdog('leech', t('Could not save %type node %title from feed: %feed.', array('%type' => t($edit->type), '%title' => $edit->title, '%feed' => $node->title)), WATCHDOG_ERROR, l(t('view'), 'node/'. $node->nid));
}
else {
if ($edit->is_new) {
if ($edit->promote && $node->leech_news->items_promote != 1000000000) {
$promoted[$edit->nid] = $edit->created;
$promoted_changed = TRUE;
}
$items_added++;
watchdog('leech', t('%type: added %title from feed: %feed.', array('%type' => t($edit->type), '%title' => $edit->title , '%feed' => $node->title )), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit->nid));
}
else {
watchdog('leech', t('%type: updated %title from feed: %feed.', array('%type' => t($edit->type) , '%title' => $edit->title , '%feed' => $node->title )), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit->nid));
}
sleep(variable_get('leech_sleep_interval', 0));
}
}
// Update adaptive informations
if ($node->leech->adaptive) {
_leech_compute_adaptive_values(
$node->leech->refresh,
$node->leech->avg_btw_news,
$node->leech->deviation,
$node->leech->num_of_tests,
$node->leech->news_last_arrived,
$items_added
);
}
watchdog('leech', "$items_added item(s) added, $duplicate_count duplicate(s) found. Leech node: {$node->nid}, {$node->title}");
drupal_set_message("$items_added item(s) added, $duplicate_count duplicate(s) found.");
// Handle promotion stuff
if ($node->leech_news->items_promote != 1000000000 && $node->leech_news->items_promote != 0 && $promoted_changed == TRUE) {
$temp = array();
$range = count($promoted) - $node->leech_news->items_promote;
// If there is less promoted items than needed we have to create new list of promoted
if ($range < 0) {
$result = db_query_range('SELECT n.nid, n.created FROM {node} n, {leech_news_item} i WHERE n.status = 1 AND n.nid = i.nid AND i.fid = %d ORDER BY n.created DESC', $node->nid, 0, $node->leech_news->items_promote);
while ($temp = db_fetch_object($result)) {
$promoted[$temp->nid] = $temp->created;
}
}
asort($promoted);
$promoted = array_unique(array_keys($promoted));
$range = count($promoted) - $node->leech_news->items_promote;
if ($range > 0) {
$demoted = array_slice($promoted, 0, $range);
$promoted = array_slice($promoted, $range);
}
// Promote those which should be promoted
if (count($promoted) > 0) {
db_query('UPDATE {node} SET promote = 1 WHERE nid IN (%s)', implode(',', $promoted));
}
// Demote those which should not be promoted
if (count($demoted) > 0) {
db_query('UPDATE {node} SET promote = 0 WHERE nid IN (%s)', implode(',', $demoted));
}
}
return $items_added;
}
/**
* Create opml items
*/
function leech_opml_save_items(&$node) {
if (!is_array($node->leech->connection->result->dataXML['opml'][0]['body'][0]['outline'])) {
return;
}
$edit = (object) node_template_load($node->leech_opml->template);
if (!$edit) {
return;
}
global $user;
// TODO: this handles only 1 level of outline. Add handling of nested outlines.
foreach ($node->leech->connection->result->dataXML['opml'][0]['body'][0]['outline'] as $item) {
$url = $item['xmlUrl'];
$entry = NULL;
if ($url && strlen($url) > 0) {
$entry = db_fetch_object(db_query("SELECT nid FROM {leech} WHERE url = '%s'", $url));
}
if ($entry && $entry->nid) {
// TODO: add items_update option to OPML edit form :)
// allow to select if user wants to alway keep only feeds existing in current OPML data
// or add all new (so even if one of the feeds is not in opml data anymore, it will be left working ok here).
if (!$node->leech_opml->items_update) {
continue;
}
$edit = node_load($entry->nid);
}
else {
$edit = (object) node_template_load($node->leech_opml->template);
}
// avoid overwriting user's changes
if (!$edit->nid) {
// From node.module
$node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
foreach (array('moderate', 'sticky', 'revision', 'status', 'promote') as $key) {
$edit->$key = in_array($key, $node_options);
}
$edit->date = format_date(time(), 'custom', 'Y-m-d H:i O');
$edit->created = strtotime($edit->date);
}
$edit->title = $item['text'];
$edit->leech->url = $url;
// Set defaults
if (!isset($edit->leech->refresh)) {
$edit->leech->refresh = variable_get('leech_refresh#'.$edit->type, 0);
}
if (!isset($edit->leech_news->template)) {
$edit->leech_news->template = variable_get('leech_news_template_'.$edit->type, 0);
$edit->leech_news->items_guid = variable_get('leech_news_items_guid_'.$edit->type, 0);
$edit->leech_news->items_status = variable_get('leech_news_items_status_'.$edit->type, 1);
$edit->leech_news->items_update = variable_get('leech_news_items_update_'.$edit->type, 0);
$edit->leech_news->items_delete = variable_get('leech_news_items_delete_#'.$edit->type, 15724800);
$edit->leech_news->items_promote = variable_get('leech_news_items_promote_'.$edit->type, 3);
$edit->leech_news->items_date = variable_get('leech_news_items_date_'.$edit->type, 1);
$edit->leech_news->links_display_mode = variable_get('leech_news_links_'.$edit->type, LEECH_SHOW_LINK_ALWAYS);
}
// Temporary properties
$edit->leech_opml_item->opml_data = &$node->leech->connection->result->dataXML;
$edit->leech_opml_item->item_data = &$item;
if ($errors = node_template_save($edit)) {
watchdog('leech', t('Could not save %type node %title from opml: %feed.', array('%type' => t($edit->type) , '%title' => $edit->title , '%feed' => $node->title)), WATCHDOG_ERROR, l(t('view'), 'node/'. $node->nid));
}
else {
if ($edit->is_new) {
watchdog('leech', t('%type: added %title from opml: %feed.', array('%type' => t($edit->type) , '%title' => $edit->title , '%feed' => $node->title)), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit->nid));
}
else {
watchdog('leech', t('%type: updated %title from opml: %feed.', array('%type' => t($edit->type) , '%title' => $edit->title , '%feed' => $node->title)), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit->nid));
}
sleep(variable_get('leech_sleep_interval', 0));
}
}
}
function leech_opml_parse(&$xml) {
// "Parse" tags :)
preg_match_all('%(?:\<)((|\w+\:)?\w+)(| .*?)(?=\/>()|\>().*?()\<\/\1\>())%s', $xml, $tags, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
/*
Spec:
$tags[$i] = Array with info about tag. each "info" is array of 2 elements.
1st may contain text data.
2nd contains offset in document where data (or just taht "point") starts.
$tags[$i][1] = Tag name
[1] = Name string (may include namespace if such exist).
[2] = Offset integer.
$tags[$i][2] = Namespace
[1] = Namespace string (including ":" sign, like: "rss:")
[2] = Offset integer.
$tags[$i][3] = Attributes
[1] = String containing all attributes data.
$tags[$i][4] = End of "open-end" tag
[2] = Integer offset if tag was of type "", else -1
Following fields exist only if tag was of type ".." (ie. had open and end part).
$tags[$i][5] = End of "open" part of tag (and start of "value" part at the same time :)
[2] = Integer offset
$tags[$i][6] = End of "value" part of tag (and start of "end" part at the same time :)
[2] = Integer offset
$tags[$i][7] = End of "end" part of tag
[2] = Integer offset
Following fields will be added in loop below.
$tags[$i][8] = Integer offset of tag end (ie. value from [4] or [7] depending on type of tag).
$tags[$i][9] = Integer key of "parent" tag (ie. 2 means $tags[2] is parent of $tags[$i]).
$tags[$i][10] = Integer of depth.
$tags[$i][11] = Array of integer keys of "children".
*/
$tags_count = count($tags);
$depth = 0;
for ($i = 0; $i < $tags_count; $i++) {
$name = strtolower($tags[$i][1][0]);
// Remember end of tag so we don't have to check it each time
$tags[$i][8] = ($tags[$i][7] ? $tags[$i][7][1] : $tags[$i][4][1]);
// Find parent
$prev = $i-1;
$tags[$i][9] = ($prev > -1 ? $prev : -1);
while (isset($tags[$prev][8]) && $tags[$i][0][1] > $tags[$prev][8]) {
$tags[$i][9] = $prev-1;
$prev--;
}
// Get depth
$tags[$i][10] = ($tags[$i][9] > -1 ? $tags[$tags[$i][9]][10]+1 : $depth+1);
// If depth changed, change stack
$depth_change = 0;
if ($depth != $tags[$i][10]) {
// Calculate difference of depth
$depth_change = $tags[$i][10] - $depth;
// Remember "current" depth
$depth = $tags[$i][10];
}
// Attributes
if (trim($tags[$i][3][0])) {
preg_match_all('/((\w+\:)?\w+)((\s*=\s*"(.*?)")|(\s*=\s*\'(.*?)\')|(\s*=\s*(\w+))|())/s', $tags[$i][3][0], $attributes, PREG_SET_ORDER);
foreach ($attributes as $attr) {
$tags[$i][$attr[1]] = end($attr);
}
}
// Make "alias" for this tag
if ($tags[$i][9] > -1) {
if (!$tags[$tags[$i][9]][$name]) {
$tags[$tags[$i][9]][$name] = array();
}
if (is_array($tags[$tags[$i][9]][$name])) {
$tags[$tags[$i][9]][$name][] = &$tags[$i];
}
}
else {
if (!$tags[$name]) {
$tags[$name] = array();
}
$tags[$name][] = &$tags[$i];
}
}
// Remember full xml data as value.
$tags['#value'] = $xml;
return $tags;
}
/**
* Handle the adaptive behaviour of the feed. Feed-refreshing means that the original feed is refreshed.
*
* @param integer $refresh_frequency
*
* @param $avg_btw_news
* The average value of the statistical sample of how much time is between the feed-refreshing
* @param $deviation
* The standard deviation of the statistical sample; not exactly, just something approximation
* @param $num_of_tests
* How many times the feed-refresging interval is measured
* @param $news_last_arrived
* The last feed-refreshing instant
* @param $items_added
* Currently added items to the feed
*
*/
function _leech_compute_adaptive_values(&$refresh_frequency, &$avg_btw_news, &$deviation, &$num_of_tests, &$news_last_arrived, $items_added) {
if ($items_added <= 0 || $refresh_frequency == 0) {
return;
}
$now = time();
// Computing the new values
$curr_diff = abs($now - $news_last_arrived);
$prev_total = $num_of_tests * $avg_btw_news;
if ($prev_total == 0) {
$new_average = ($refresh_frequency + $curr_diff) / 2;
} else {
$new_average = ($prev_total + $curr_diff) / ($num_of_tests + 1);
}
$prev_dev_sum = sqrt($deviation) * $num_of_tests;
// Not really deviation. But if num_of_test -> infinity then this value -> deviation.
$new_deviation = sqrt(($prev_dev_sum + pow($curr_diff - $new_average, 2)) * (1 / ($num_of_tests + 1)));
// Update the values
$news_last_arrived = $now;
$deviation = $new_deviation;
$avg_btw_news = $new_average;
$num_of_tests += 1;
// $leech->refresh modifying strategy
$refresh_frequency = intval(abs($avg_btw_news * (1 - ($deviation / $avg_btw_news))));
}
function leech_opml_parse_value(&$tree, &$element) {
if ($element[5][1] && $tree['#value']) {
return substr($tree['#value'], $element[5][1], $element[6][1]-$element[5][1]);
}
}
/**
* private function,
* for being called in nodeapi submit hook
* passes on group settings of feed node to all related feed items
*
* @param unknown_type $node
*/
function _leech_news_pass_on_groups(&$node) {
if (!module_exists('og')) {
return;
}
if (!is_array($node->og_groups)) {
return;
}
if (!isset($node->nid)) {
return;
}
timer_start("ogpass");
$r = db_query("SELECT DISTINCT nid FROM {leech_news_item} WHERE fid = %d", $node->nid);
$updated = 0; $total = 0;
while ($n = db_fetch_object($r)) {
db_query("DELETE FROM {node_access} WHERE nid = %d AND realm LIKE '%s'", $n->nid, 'og_%');
if (is_array($node->og_groups)) {
foreach ($node->og_groups as $gid) {
if ($gid != 0) {
$sql = "INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete)
VALUES (%d, %d, 'og_subscriber', 1, 1, 1)";
db_query($sql, $n->nid, $gid);
//Added to make Leech work with Organic Groups
$sql = "DELETE FROM {og_ancestry} WHERE nid = $n->nid AND group_nid = $gid";
db_query($sql, $n->nid, $gid);
$sql = "INSERT INTO {og_ancestry} (nid, group_nid, is_public) VALUES (%d, %d, 1)";
db_query($sql, $n->nid, $gid);
//End of Code Added
if ($node->og_public) {
$sql = "INSERT INTO {node_access} (nid, gid, realm, grant_view) VALUES (%d, 0, 'og_public', %d)";
db_query($sql, $n->nid, $gid);
}
}
}
}
// if the public checkbox was selected, give a universal grant for this node
if ($node->og_public) {
$sql = "INSERT INTO {node_access} (nid, gid, realm, grant_view) VALUES (%d, 0, 'og_all', 1)";
db_query($sql, $n->nid);
}
$updated++;
}
if ($updated > 0) {
drupal_set_message("Group settings of ".$updated." child feed item(s) updated in ".timer_read("ogpass")." ms", "status");
}
// Transform the $node->og_groups according to the og_submit_group taste
foreach($node->og_groups as $gid) {
$transformed_groups[$gid] = $gid;
}
$node->og_groups = $transformed_groups;
timer_stop("ogpass");
}
/**
* passes on taxonomy _changes_ for a given leech node to all its leech item nodes
*
* leaves terms unchanged, that were not inherited from this node.
*
* @param node object $node
*/
function _leech_news_pass_on_taxonomy(&$node) {
if (!is_array($node->taxonomy['tags']))
return;
timer_start("taxpass");
// 1) get terms that where associated to this node before
$result = db_query("SELECT tid FROM {term_node} WHERE nid = %d", $node->nid);
$now = array();
$before = array();
while ($term = db_fetch_object($result)) {
$before[] = $term->tid;
}
// 2) get terms that are associated to this node now
foreach ($node->taxonomy['tags'] as $vid => $tagstring) {
$tagstring = trim($tagstring);
if ($tagstring == "")
continue;
$tnames = explode(",", $tagstring);
foreach ($tnames as $tname) {
$len = count($now);
$termsbyname = taxonomy_get_term_by_name(trim($tname));
foreach ($termsbyname as $tbn) {
if ($tbn->vid == $vid) {
$now[] = $tbn->tid;
break;
}
}
// if term not found, create it
if ($len == count($now)){
$edit['name'] = trim($tname);
$edit['vid'] = $vid;
taxonomy_save_term($edit);
if ($edit['tid']) {
$now[] = $edit['tid'];
watchdog("leech", "error passing on terms to node - error saving new term");
}
}
}
}
// 3) check out, which terms to add and which terms to delete
$add_tids = array_diff($now, $before);
$del_tids = array_diff($before, $now);
// 4) get all nodes and apply changes
$updated = array();
if ((count($add_tids) != 0 || count($del_tids) != 0)) {
$result = db_query("SELECT li.nid, GROUP_CONCAT( tn.tid SEPARATOR ',' ) AS tids
FROM {leech_news_item} li
LEFT JOIN {term_node} tn ON tn.nid = li.nid
WHERE li.fid = %d
GROUP BY nid", $node->nid);
while ($feeditem_node = db_fetch_object($result)) {
$tids = explode(",", $feeditem_node->tids);
$nid = $feeditem_node->nid;
foreach ($tids as $tid) {
if (in_array($tid, $del_tids )) {
db_query("DELETE FROM {term_node} WHERE nid = %d AND tid = %d", $nid, $tid);
$updated[$nid] = 1;
}
}
foreach ($add_tids as $add_tid) {
if (!in_array($add_tid, $tids)) {
db_query("INSERT INTO {term_node}(nid, tid) VALUES(%d, %d)", $nid, $add_tid);
$updated[$nid] = 1;
}
}
}
}
drupal_set_message("Taxonomy of ".count($updated)." child feed item(s) updated in ".timer_read("taxpass")." ms", "status");
timer_stop("taxpass");
}