Binary files leech-orig/geekomatik-logo.png and leech/geekomatik-logo.png differ Binary files leech-orig/headbg.gif and leech/headbg.gif differ diff -urpN leech-orig/leech.css leech/leech.css --- leech-orig/leech.css 1970-01-01 01:00:00.000000000 +0100 +++ leech/leech.css 2007-11-27 09:50:10.000000000 +0100 @@ -0,0 +1,131 @@ +#leech_overlay { + position: fixed; + z-index:100; + top: 0px; + left: 0px; + background-color:#000; + filter:alpha(opacity=75); + -moz-opacity: 0.75; + opacity: 0.75; + height:100%; + width:100%; +} + +* html #leech_overlay { /* ie6 hack */ + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); +} + +#leech_popup { + position: fixed; + background: #ffffff; + z-index: 102; + color:#000000; + display:none; + border: 4px solid #525252; + text-align:left; + top:50%; + left:50%; +} + +* html #leech_popup { /* ie6 hack */ +position: absolute; +margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); +} + +#leech_popup { + display:block; + margin: 15px 0 0 15px; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + border-top: 1px solid #666; + border-left: 1px solid #666; +} + +#leech_HideSelect{ + z-index:99; + position:fixed; + top: 0; + left: 0; + background-color:#fff; + border:none; + filter:alpha(opacity=0); + -moz-opacity: 0; + opacity: 0; + height:100%; + width:100%; +} + +* html #leech_HideSelect { /* ie6 hack */ + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); +} + +#leech_popup { + text-align: center; +} + +div.leech_content { +} + +div.leech_content_iframe { + border: none; +} + +div.leech_buttons { + height: 150px; + text-align: center; +} + +div.leech_buttons div.vote-up-down-widget { + text-align: center; +} + +div.leech_buttons a { + padding-right : 10px; + padding-left : 10px; +} + +div.leech_loading_animation { + text-align: center; + padding-top: 65px; + height: 150px; +} + +div.leech_vote_up_down { + width: 100px; + text-align: center; + float: left; +} + +div.leech_community_tags_inline { + width: 300px; + text-align: left; + float: left; +} + +div.leech_community_tags_inline form div div.form-item label { + display: inline; +} + +div.leech_community_tags_inline form div div.form-item { + display: block; +} + +td.leech_header { + width: 200px; + text-align: left; +} + +td.leech_vote_up_down { + width: 100px; + text-align: left; +} + +td.leech_community_tags_inline { + text-align: left; +} + +table.leech_buttons tbody { + border: none; +} diff -urpN leech-orig/leech.info leech/leech.info --- leech-orig/leech.info 2007-07-30 19:10:07.000000000 +0200 +++ leech/leech.info 2007-10-15 11:20:19.000000000 +0200 @@ -4,7 +4,7 @@ description = "Feed aggretagor, creates dependencies = node_template package = Leech ; Information added by drupal.org packaging script on 2007-07-30 -version = "5.x-1.9" +version = "5.x-1.9-geekomatik" project = "leech" datestamp = "1185815407" diff -urpN leech-orig/leech.js leech/leech.js --- leech-orig/leech.js 1970-01-01 01:00:00.000000000 +0100 +++ leech/leech.js 2007-11-28 08:45:05.000000000 +0100 @@ -0,0 +1,221 @@ +function leech_show_nodebody(sender, nid) { + sender_position = leech_get_position(sender); + var top = sender_position[0]; + var left = sender_position[1]; + var p = $("div[@id=leech_popup]"); + p.empty(); + p.css("position", "absolute"); + p.css("left", left + "px"); + p.css("top", top + "px"); + p.append('
'); + p.show(); + + $.ajax({ + type: "GET", + async: true, + url: Drupal.settings.leech.nodebody_url, + dataType: "html", + data: "nid=" + nid, + success: function (html) { + p.empty(); + p.append(html); + } + }); + + return false; +}; + +function leech_show_articleoriginal(sender, nid) { + leech_showOverlay(); + + var pagesize = leech_getPageSize(); + var width = pagesize[0] - 60; + var height = pagesize[1] - 60; + + var p = $("div[@id=leech_popup]"); + p.empty(); + + p.css({marginLeft: '-' + parseInt((width / 2),10) + 'px', width: width + 'px'}); + if ( !(jQuery.browser.msie && typeof XMLHttpRequest == 'function')) { // take away IE6 + p.css({marginTop: '-' + parseInt((height / 2),10) + 'px'}); + } + p.css('height', height + 'px'); + p.css('width', width + 'px'); + + p.append('
'); + p.show(); + + $.ajax({ + type: "GET", + async: true, + url: Drupal.settings.leech.articleoriginal_url, + dataType: "html", + data: "nid=" + nid, + success: function (html) { + p.empty(); + p.append(html); + + var lb = $("div.leech_buttons"); + var lc = $("div.leech_content"); + lc.css('height', (height-parseInt(lb.css('height'))) + 'px'); + + leech_community_tags_inline_form_js(nid); + } + }); + + return false; +}; + +function leech_close_popup() { + leech_hideOverlay(); +}; + +function leech_get_position(element) { + var position_top = 0; + var position_left = 0; + + while(element != null) { + position_top += element.offsetTop; + position_left += element.offsetLeft; + element = element.offsetParent + } + + return [position_top, position_left]; +}; + +function leech_getPageSize(){ + var de = document.documentElement; + var w = window.innerWidth || self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; + var h = window.innerHeight || self.innerHeight || (de&&de.clientHeight) || document.body.clientHeight; + arrayPageSize = [w,h]; + return arrayPageSize; +}; + +function leech_showOverlay() { + if (typeof document.body.style.maxHeight === "undefined") {//if IE 6 + $("body","html").css({height: "100%", width: "100%"}); + $("html").css("overflow","hidden"); + if (document.getElementById("leech_HideSelect") === null) {//iframe to hide select elements in ie6 + $("body").append("
"); + $("#leech_overlay").click(leech_hideOverlay); + } + }else{//all others + if(document.getElementById("TB_overlay") === null){ + $("body").append("
"); + $("#leech_overlay").click(leech_hideOverlay); + } + } +}; + +function leech_hideOverlay() { + var p = $("div[@id=leech_popup]"); + p.hide(); + + $("#leech_overlay").unbind("click"); + $('#leech_window,#leech_overlay,#leech_HideSelect').remove(); + if (typeof document.body.style.maxHeight == "undefined") {//if IE 6 + $("body","html").css({height: "auto", width: "auto"}); + $("html").css("overflow",""); + } + document.onkeydown = ""; + return false; +}; + +function leech_community_tags_inline_form_js(nid) { + $.ajax({ + type: "GET", + async: true, + url: Drupal.settings.leech_communityTagsInline.urlJS + Drupal.encodeURIComponent(nid), + dataType: "string", + success: function (js_data) { + if(Drupal.settings) { + if(Drupal.settings.communityTags) { + Drupal.settings.communityTags = null; + } + } + eval(js_data); + leech_community_tags_inline_activate_box(nid); + } + }); +} + +function leech_community_tags_inline_activate_box (nid) { +if (Drupal.jsEnabled) { + $(document).ready(function () { + // Note: all tag fields are autocompleted, and have already been initialized at this point. + $('input.form-tags').each(function () { + // Hide submit buttons. + $('input[@type=submit]', this.form).hide(); + + // Fetch settings. + var o = Drupal.settings.communityTags; + var sequence = 0; + + // Show the textfield and empty its value. + var textfield = $(this).val('').css('display', 'inline'); + + // Prepare the add Ajax handler and add the button. + var addHandler = function () { + // Send existing tags and new tag string. + $.post(o.url, Drupal.serialize({ sequence: ++sequence, tags: o.tags, add: textfield[0].value }), function (data) { + data = Drupal.parseJson(data); + if (data.status && sequence == data.sequence) { + o.tags = data.tags; + updateList(); + } + }); + + // Add tag to local list + if(!o.tags[0]) { + o.tags = new Array; + } + o.tags.push(textfield[0].value); + o.tags.sort(); + updateList(); + + // Clear field and focus it. + textfield.val('').focus(); + }; + var button = $('').click(addHandler); + $(this.form).submit(function () { addHandler(); return false; }); + + // Prepare the delete Ajax handler. + var deleteHandler = function () { + // Remove tag from local list. + var i = $(this).attr('key'); + o.tags.splice(i, 1); + updateList(); + + // Send new tag list. + $.post(o.url, Drupal.serialize({ sequence: ++sequence, tags: o.tags, add: '' }), function (data) { + data = Drupal.parseJson(data); + if (data.status && sequence == data.sequence) { + o.tags = data.tags; + updateList(); + } + }); + + // Clear textfield and focus it. + textfield.val('').focus(); + }; + + // Callback to update the tag list. + function updateList() { + list.empty(); + for (i in o.tags) { + list.append('
  • '+ Drupal.checkPlain(o.tags[i]) +'
  • '); + } + $('li', list).click(deleteHandler); + } + + // Create widget markup. + var widget = $('
    '); + textfield.before(widget); + widget.append(textfield).append(button); + var list = $('ul', widget); + updateList(); + + }); + }); +} +} \ No newline at end of file diff -urpN leech-orig/leech.module leech/leech.module --- leech-orig/leech.module 2007-07-30 19:02:38.000000000 +0200 +++ leech/leech.module 2008-01-23 18:46:47.000000000 +0100 @@ -64,6 +64,43 @@ function leech_perm() { * @todo move deprecated paths to bottom of page, remove not used paths */ function leech_menu($may_cache) { + drupal_add_js('misc/drupal.js', 'core', 'header', FALSE); + drupal_add_js('misc/jquery.js', 'core', 'header', FALSE); + drupal_add_js(drupal_get_path('module', 'leech') .'/leech.js', 'module', 'header', FALSE); + drupal_add_css(drupal_get_path('module', 'leech') .'/leech.css', 'module'); + + static $pass = FALSE; + if(!$pass) { + if(module_exists('community_tags_inline') && $format == 'custom') { + drupal_add_js('misc/jquery.js', 'core', 'header', FALSE); + drupal_add_js('misc/autocomplete.js', 'core', 'header', FALSE); + drupal_add_css(drupal_get_path('module', 'tagadelic') .'/tagadelic.css', 'module'); + drupal_add_css(drupal_get_path('module', 'community_tags') .'/community_tags.css', 'module'); + drupal_add_js(drupal_get_path('module', 'community_tags_inline') . '/js/community_tags_inline.js', 'module', 'header', FALSE); + } + + if(module_exists('vote_up_down') && $format == 'custom') { + drupal_add_css(drupal_get_path('module', 'vote_up_down') .'/vote_up_down.css'); + drupal_add_js(drupal_get_path('module', 'vote_up_down') . '/ajax_vote_up_down.js'); + } + + $settings = array('leech_communityTagsInline' => + array( + 'urlForm' => url('community_tags_inline/tag_form/'), + 'urlJS' => url('community_tags_inline/tag_form_js/'), + ), + 'leech' => + array( + 'loading_animation' => base_path() . drupal_get_path('module', 'leech') .'/loading_animation.gif', + 'nodebody_url' => url('leech/nodebody/'), + 'articleoriginal_url' => url('leech/articleoriginal/'), + ) + ); + drupal_add_js($settings, 'setting'); + + $pass = TRUE; + } + $items = array(); if ($may_cache) { @@ -109,6 +146,34 @@ function leech_menu($may_cache) { 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/settings/leech', 'title' => t('Leech'), 'callback' => 'drupal_get_form', 'callback arguments' => array('leech_admin_settings'), 'description' => t('Configure leech module.'),); + + $items[] = array( + 'path' => 'leech/nodebody', + 'title' => t('Leech node body'), + 'callback' => 'leech_nodebody', + 'access' => user_access('access content'), + 'type' => MENU_CALLBACK); + + $items[] = array( + 'path' => 'leech/articleoriginal', + 'title' => t('Leech article original'), + 'callback' => 'leech_articleoriginal', + 'access' => user_access('access content'), + 'type' => MENU_CALLBACK); + + $items[] = array( + 'path' => 'leech/articlepreview', + 'title' => t('Leech article preview'), + 'callback' => 'leech_articlepreview', + 'access' => user_access('access content'), + 'type' => MENU_CALLBACK); + + $items[] = array( + 'path' => 'leech/articlepreviewheader', + 'title' => t('Leech article preview header'), + 'callback' => 'leech_articlepreviewheader', + 'access' => user_access('access content'), + 'type' => MENU_CALLBACK); } return $items; @@ -346,6 +411,46 @@ function leech_admin_settings() { } } + $formats = array ( + 'teaser' => t('Teaser'), + 'custom' => t('Custom'), + ); + $form['leech_page_items_format'] = array ( + '#type' => 'select', + '#options' => $formats, + '#default_value' => variable_get('leech_page_items_format', 'full'), + '#title' => t('Items format for default page'), + ); + + $form['leech_page_items_custom_length_teaser'] = array ( + '#type' => 'textfield', + '#default_value' => variable_get('leech_page_items_custom_length_teaser', 600), + '#title' => t('Item length (number of chars) for custom format teaser'), + ); + + $form['leech_page_items_custom_length_body'] = array ( + '#type' => 'textfield', + '#default_value' => variable_get('leech_page_items_custom_length_body', 600), + '#title' => t('Item length (number of chars) for custom format body'), + ); + + $form['leech_originalarticle_popup'] = array ( + '#type' => 'checkbox', + '#default_value' => variable_get('leech_originalarticle_popup', FALSE), + '#title' => t('Show the original article in a popup window'), + ); + + $formats = array ( + 'popup' => t('In a popup'), + 'window' => t('In a window'), + ); + $form['leech_originalwebsite_preview'] = array ( + '#type' => 'select', + '#options' => $formats, + '#default_value' => variable_get('leech_originalwebsite_preview', 'popup'), + '#title' => t('How to show the preview of the original website'), + ); + return system_settings_form($form); } @@ -405,7 +510,12 @@ function theme_leech_link_full_article($ if (variable_get('leech_news_original_links', 0) && $node->leech_news->source_link) { $link = $node->leech_news->source_link; } - return ''. t('original article') .''; + if(variable_get('leech_originalarticle_popup', FALSE)) { + return ''. t('original article') .''; + } + else { + return ''. t('original article') .''; + } } /** @@ -420,8 +530,15 @@ function leech_news_page_default($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); + $format = variable_get('leech_page_items_format', 'full'); + while ($node = db_fetch_object($result)) { - $output .= node_view(node_load($node->nid), 1); + if($format == 'teaser') { + $output .= node_view(node_load($node->nid), true, false); + } + else if($format == 'custom') { + $output .= leech_item_custom_format($node->nid); + } } $output .= theme('pager', NULL, variable_get('default_nodes_main', 10)); } @@ -441,6 +558,222 @@ function leech_news_page_default($nid = return $output; } +function theme_leech_item_custom_format($node, $node_content) { + $output = '
    '; + + if (!$node->status) { + $output = '
    '; + } + + if (module_exists('taxonomy')) { + $terms = taxonomy_link('taxonomy terms', $node); + } + + if(variable_get('leech_originalwebsite_preview', 'popup')=='popup') { + $node_link = l($node->title, drupal_get_path_alias('node/' . $node->nid), array('onclick'=>"return leech_show_articleoriginal(this, " . $node->nid . ");"), NULL, NULL, FALSE, TRUE); + } + else { + $node_link = l($node->title, 'leech/articlepreview/' . $node->nid, NULL, NULL, NULL, FALSE, TRUE); + } + $output .= t('!title by !name', array('!title' => '

    '. $node_link .'

    ', '!name' => theme('username', $node))); + + if (count($terms)) { + $output .= ' ('. theme('links', $terms) .')
    '; + } + + $node->content['body']['#value'] = $node_content; + $output .= '
    ' . drupal_render($node->content) . '
    '; + + if ($node->links) { + $output .= ''; + } + + if (!$node->status) { + $output .= '
    '; + } + + $output .= '
    '; + + return $output; +} + +function leech_item_custom_format($nid) { + $item = node_load($nid); + $item = node_build_content($item, true, false); + $item_content = _leech_htmlcorrector($item->body); + $item_content = _leech_truncate_text($item_content, variable_get(leech_page_items_custom_length_teaser, 600), '', false); + $item_content = _leech_htmlcorrector($item_content); + $item->links['leech_fullnode'] = array ( + 'title' => t('Read more'), + 'href' => drupal_get_path_alias('node/'. $item->nid)); + $item->links = array_merge($item->links, module_invoke_all('link', 'node', $item, true)); + return theme('leech_item_custom_format', $item, $item_content); +} + +/** + * Truncates text. + * + * Cuts a string to the length of $length and replaces the last characters + * with the ending if the text is longer than length. + * + * @param string $text String to truncate. + * @param integer $length Length of returned string, including ellipsis. + * @param string $ending Ending to be appended to the trimmed string. + * @param boolean $exact If false, $text will not be cut mid-word + * @return string Trimmed string. + */ +function _leech_truncate_text($text, $length = 600, $ending = '', $exact = false) { + if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { + return $text; + } + + preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER); + $total_length = 0; + $open_tags = array(); + $truncate = ''; + foreach ($lines as $line_matchings) { + if (!empty($line_matchings[1])) { + if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) { + } elseif (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) { + $pos = array_search($tag_matchings[1], $open_tags); + if ($pos !== false) { + unset($open_tags[$pos]); + } + } elseif (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) { + array_unshift($open_tags, strtolower($tag_matchings[1])); + } + $truncate .= $line_matchings[1]; + } + + $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2])); + if ($total_length + $content_length > $length) { + $left = $length - $total_length; + $entities_length = 0; + if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE|PREG_PATTERN_ORDER)) { + foreach ($entities[0] as $entity) { + if ($entity[1] + 1 - $entities_length <= $left) { + $left--; + $entities_length += strlen($entity[0]); + } else { + break; + } + } + } + $truncate .= substr($line_matchings[2], 0, $left + $entities_length); + break; + } else { + $truncate .= $line_matchings[2]; + $total_length += $content_length; + } + if ($total_length >= $length) { + break; + } + } + + if (!$exact) { + $periodpos = strrpos($truncate, '. '); + if ($periodpos === FALSE ) { + $periodpos = strpos($text, '.'); + if($periosdpos !== FALSE) { + $truncate = substr($text, 0, $periodpos+1); + } + else { + $truncate = $text; + } + } + else { + $truncate = substr($truncate, 0, $periodpos+1); + } + } + + $truncate = rtrim($truncate); + + if (!empty($inline)) { + $truncate .= " " . $inline; + } + + foreach ($open_tags as $tag) { + $truncate .= ''; + } + + if ( !empty($ending) ) { + $truncate .= $ending; + } + + return $truncate; +} + +function _leech_htmlcorrector($text) { + // Prepare tag lists. + static $no_nesting, $single_use; + if (!isset($no_nesting)) { + // Tags which cannot be nested but are typically left unclosed. + $no_nesting = drupal_map_assoc(array('li', 'p', 'div')); + + // Single use tags in HTML4 + $single_use = drupal_map_assoc(array('base', 'meta', 'link', 'hr', 'br', 'param', 'img', 'area', 'input', 'col', 'frame')); + } + + // Properly entify angles. + $text = preg_replace('!<([^a-zA-Z/])!', '<\1', $text); + + // Split tags from text. + $split = preg_split('/<([^>]+?)>/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); + // Note: PHP ensures the array consists of alternating delimiters and literals + // and begins and ends with a literal (inserting $null as required). + + $tag = false; // Odd/even counter. Tag or no tag. + $stack = array(); + $output = ''; + foreach ($split as $value) { + // Process HTML tags. + if ($tag) { + list($tagname) = explode(' ', strtolower($value), 2); + // Closing tag + if ($tagname{0} == '/') { + $tagname = substr($tagname, 1); + // Discard XHTML closing tags for single use tags. + if (!isset($single_use[$tagname])) { + // See if we possibly have a matching opening tag on the stack. + if (in_array($tagname, $stack)) { + // Close other tags lingering first. + do { + $output .= ''; + } while (array_shift($stack) != $tagname); + } + // Otherwise, discard it. + } + } + // Opening tag + else { + // See if we have an identical 'no nesting' tag already open and close it if found. + if (count($stack) && ($stack[0] == $tagname) && isset($no_nesting[$stack[0]])) { + $output .= ''; + } + // Push non-single-use tags onto the stack + if (!isset($single_use[$tagname])) { + array_unshift($stack, $tagname); + } + // Add trailing slash to single-use tags as per X(HT)ML. + else { + $value = rtrim($value, ' /') .' /'; + } + $output .= '<'. $value .'>'; + } + } + else { + // Passthrough all text. + $output .= $value; + } + $tag = !$tag; + } + // Close remaining tags. + while (count($stack) > 0) { + $output .= ''; + } + return $output; +} + /** * Menu callback; Remove items. */ @@ -714,7 +1047,7 @@ function leech_form_alter($form_id, &$fo 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')); + $result = db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {leech_news_feed} f ON f.nid = n.nid')); while ($temp = db_fetch_array($result)) { $feeds[$temp['nid']] = $temp['title']; } @@ -994,8 +1327,14 @@ function leech_link($type, $node = NULL, ($teaser && $show == LEECH_SHOW_LINK_TEASER_ONLY) || (!$teaser && $show == LEECH_SHOW_LINK_PAGE_ONLY)) { if ($node->leech_news_item) { + if(variable_get('leech_originalarticle_popup', FALSE)) { + $links['leech_link_full_article'] = array('title' => t('Read original article.'), 'href' => $node->leech_news_item->link, 'attributes'=>array('onclick'=>'return leech_show_articleoriginal(this, ' . $node->nid . ')')); + } + else { $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 site'), 'href' => $node->leech_news->link); } @@ -1071,11 +1410,10 @@ function leech_cron() { } // Delete too old items - $result = db_query("SELECT f.items_delete, n.created, i.nid - FROM {node} AS n, {leech_news_item} AS i, {leech_news_feed} AS f - WHERE ( n.nid = i.nid AND - f.nid = i.fid)" - ); + $sql = 'SELECT f.items_delete, n.created, i.nid FROM {node} n '; + $sql .= 'INNER JOIN {leech_news_item} i ON n.nid = i.nid '; + $sql .= 'INNER JOIN {leech_news_feed} f ON f.nid = i.fid '; + $result = db_query($sql); $now = time(); while ($node = db_fetch_object($result)) { if (abs($now - $node->created) > $node->items_delete) { @@ -1240,7 +1578,7 @@ function leech_validate(&$node, $form = 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']); + $result = db_query("SELECT l.nid, n.title FROM {node} n INNER JOIN {leech} l ON l.nid = n.nid WHERE l.url = '%s' ", $form['leech']['url']['#value']); while ($leech = db_fetch_object($result)) { if ($leech->nid != $node->nid) { $link = l($leech->title, "node/{$leech->nid}"); @@ -1433,6 +1771,22 @@ function leech_view(&$node, $teaser = FA $node->content['leech_links'] = array('#value' => $themed_links, '#weight' => 10); } } + if(variable_get('leech_page_items_format', 'full')=='custom') { + $truncated_text = $node->content['body']['#value']; + if($teaser) { + $truncated_text = _leech_htmlcorrector($node->body); + $truncated_text = _leech_truncate_text($truncated_text, variable_get('leech_page_items_custom_length_teaser', 600), '', false); + $truncated_text = _leech_htmlcorrector($truncated_text); + } + else if($page) { + $truncated_text = _leech_htmlcorrector($node->body); + $truncated_text = _leech_truncate_text($truncated_text, variable_get('leech_page_items_custom_length_body', 600), '', false); + $truncated_text = _leech_htmlcorrector($truncated_text); + } + $node->content['body']['#value'] = $truncated_text; + $node->teaser = $truncated_text; + $node->body = $truncated_text; + } } if (!isset($node->leech)) { @@ -1448,6 +1802,7 @@ function leech_view(&$node, $teaser = FA $output .= theme('table', array(), $rows); $node->content['leech_stat'] = array('#value' => $output, '#weight' => 10); $teaser->content['leech_stat'] = array('#value' => $output, '#weight' => 10); + } /** @@ -1523,7 +1878,7 @@ function leech_page_feed_list() { * 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')); + $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, l.url FROM {node} n INNER JOIN {leech} l ON 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)) { @@ -2202,7 +2557,7 @@ function _leech_news_save_items(&$node) $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); + $result = db_query('SELECT i.nid AS nid, n.created AS created FROM {node} n INNER JOIN {leech_news_item} i ON i.nid = n.nid WHERE i.fid = %d 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']; } @@ -2259,7 +2614,7 @@ function _leech_news_save_items(&$node) $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)); + $entry = db_fetch_object(db_query("SELECT n.nid AS nid FROM {node} n INNER JOIN {leech_news_item} i ON i.nid = n.nid WHERE i.fid = %d AND n.title = '%s'", $node->nid, $item->title)); } if (isset($entry) && isset($entry->nid)) { if (!$node->leech_news->items_update) { @@ -2344,6 +2699,11 @@ function _leech_news_save_items(&$node) // Temporary properties $edit->leech_news_item->feed_data = &$node->leech->connection->news_feed; + if(module_exists('localizernode')) { + $feed_language = localizernode_findbynid($node->nid); + unset($edit->localizernode_pid); + $edit->localizernode_locale = $feed_language['locale']; + } 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)); } @@ -2401,7 +2761,7 @@ function _leech_news_save_items(&$node) $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); + $result = db_query_range('SELECT n.nid, n.created FROM {node} n INNER JOIN {leech_news_item} i ON n.nid = i.nid WHERE n.status = 1 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; } @@ -2805,3 +3165,110 @@ function _leech_news_pass_on_taxonomy(&$ drupal_set_message("Taxonomy of ".count($updated)." child feed item(s) updated in ".timer_read("taxpass")." ms", "status"); timer_stop("taxpass"); } + +function leech_nodebody() { + $nid = $_GET['nid']; + $node = node_load($nid); + echo '
    ' . $node->body . '
    '; + echo '
    '; + echo '' . t('Close') . ''; + echo l(t('Go to the article'), 'node/' . $nid); + echo '
    '; + exit; +} + +function leech_articleoriginal() { + $nid = $_GET['nid']; + $node = node_load($nid); + leech_statistic($nid); + echo '
    '; + echo ''; + echo ''; + echo ''; + + $node_type = in_array($node->type, variable_get('vote_up_down_node_types', array()), TRUE); + if($node_type && user_access('view up-down vote') && module_exists('vote_up_down')) { + echo ''; + } + + if(module_exists('community_tags_inline')) { + echo ''; + } + echo ''; + echo '
    '; + $imagesdir = base_path() . path_to_theme() . '/images'; + echo 'Geekomatik'; + echo '
    '; + echo ''; + echo '
    '; + echo '
    '; + $style = variable_get('vote_up_down_widget_style_node', 0) == 1 ? '_alt' : ''; + echo theme("vote_up_down_widget$style", $node->nid, 'node'); + $links = array(); + $links['vote_up_down_points'] = theme('vote_up_down_points', $node->nid, 'node', TRUE); + echo theme('links', $links); + echo '
    '; + echo '
    '; + echo '
    '; + echo community_tags_inline_view($node); + echo '
    '; + echo '
    '; + + echo '
    '; + echo '
    '; + echo ''; + echo '
    '; + exit; +} + +function leech_statistic($nid) { + global $user; + + if (variable_get('statistics_count_content_views', 0)) { + // We are counting content views. + if (is_numeric($nid)) { + // A node has been viewed, so update the node's counters. + db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), $nid); + // If we affected 0 rows, this is the first time viewing the node. + if (!db_affected_rows()) { + // We must create a new row to store counters for the new node. + db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES (%d, 1, 1, %d)', $nid, time()); + } + } + } + if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) { + // Log this page access. + db_query("INSERT INTO {accesslog} (title, path, url, hostname, uid, sid, timer, timestamp) values('%s', '%s', '%s', '%s', %d, '%s', %d, %d)", strip_tags(drupal_get_title()), 'node/' . $nid, referer_uri(), $_SERVER['REMOTE_ADDR'], $user->uid, session_id(), timer_read('page'), time()); + } +} + + +function leech_articlepreview($nid) { + $node = node_load($nid); + + $o = ''; + $o .= '' . drupal_get_title() . ''; + $o .= ''; + $o .= ''; + $o .= ''; + $o .= ''; + $o .= ''; + $o .= ''; + + echo $o; + exit(); +} + +function leech_articlepreviewheader($nid) { + $o = ''; + $o .= '' . drupal_get_title() . ''; + $o .= ''; + $o .= 'Logo, links, tools'; + $o .= ''; + $o .= ''; + echo $o; + exit(); +} \ No newline at end of file diff -urpN leech-orig/leech.module~ leech/leech.module~ --- leech-orig/leech.module~ 1970-01-01 01:00:00.000000000 +0100 +++ leech/leech.module~ 2007-12-31 15:26:10.000000000 +0100 @@ -0,0 +1,3281 @@ + + 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. + * @todo move deprecated paths to bottom of page, remove not used paths + */ +function leech_menu($may_cache) { + drupal_add_js('misc/drupal.js', 'core', 'header', FALSE); + drupal_add_js('misc/jquery.js', 'core', 'header', FALSE); + drupal_add_js(drupal_get_path('module', 'leech') .'/leech.js', 'module', 'header', FALSE); + drupal_add_css(drupal_get_path('module', 'leech') .'/leech.css', 'module'); + + static $pass = FALSE; + if(!$pass) { + if(module_exists('community_tags_inline') && $format == 'custom') { + drupal_add_js('misc/jquery.js', 'core', 'header', FALSE); + drupal_add_js('misc/autocomplete.js', 'core', 'header', FALSE); + drupal_add_css(drupal_get_path('module', 'tagadelic') .'/tagadelic.css', 'module'); + drupal_add_css(drupal_get_path('module', 'community_tags') .'/community_tags.css', 'module'); + drupal_add_js(drupal_get_path('module', 'community_tags_inline') . '/js/community_tags_inline.js', 'module', 'header', FALSE); + } + + if(module_exists('vote_up_down') && $format == 'custom') { + drupal_add_css(drupal_get_path('module', 'vote_up_down') .'/vote_up_down.css'); + drupal_add_js(drupal_get_path('module', 'vote_up_down') . '/ajax_vote_up_down.js'); + } + + $settings = array('leech_communityTagsInline' => + array( + 'urlForm' => url('community_tags_inline/tag_form/'), + 'urlJS' => url('community_tags_inline/tag_form_js/'), + ), + 'leech' => + array( + 'loading_animation' => base_path() . drupal_get_path('module', 'leech') .'/loading_animation.gif', + 'nodebody_url' => url('leech/nodebody/'), + 'articleoriginal_url' => url('leech/articleoriginal/'), + ) + ); + drupal_add_js($settings, 'setting'); + + $pass = TRUE; + } + + $items = array(); + + if ($may_cache) { + // DEPRECATED, use leech/feed instead + $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('Manage feeds'), + 'callback' => 'leech_page_admin', 'description' => t('Manage leech module feed subscriptions.'),); + // 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('All feeds'), + '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'), 'description' => t('Configure leech module.'),); + + $items[] = array( + 'path' => 'leech/nodebody', + 'title' => t('Leech node body'), + 'callback' => 'leech_nodebody', + 'access' => user_access('access content'), + 'type' => MENU_CALLBACK); + + $items[] = array( + 'path' => 'leech/articleoriginal', + 'title' => t('Leech article original'), + 'callback' => 'leech_articleoriginal', + 'access' => user_access('access content'), + 'type' => MENU_CALLBACK); + + $items[] = array( + 'path' => 'leech/articlepreview', + 'title' => t('Leech article preview'), + 'callback' => 'leech_articlepreview', + 'access' => user_access('access content'), + 'type' => MENU_CALLBACK); + + $items[] = array( + 'path' => 'leech/articlepreviewheader', + 'title' => t('Leech article preview header'), + 'callback' => 'leech_articlepreviewheader', + 'access' => user_access('access content'), + 'type' => MENU_CALLBACK); + } + + 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('Seconds that module waits between updating feeds and between saving feed items.')); + + $modules = module_implements("node_assign_keywords"); + foreach ($modules as $module) { + $list .= $module .", "; + } + $modules = $list; + $form['leech_advanced']['leech_term_extraction'] = array( + '#type' => 'fieldset', + '#title' => t('Term extraction'), + '#tree' => FALSE, + "#description" => t("Currently the following modules do term extraction: %mod", array("%mod" => $modules)), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#tree' => TRUE, + ); + + if (count($modules) > 0) { + $form['leech_advanced']['leech_term_extraction']['leech_terms_per_feed'] = array( + '#type' => 'fieldset', + '#title' => t('Select a vocabulary and query string for each leech'), + '#description' => t('The relevant terms in leech items will be added to the selected vocabulary'), + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#tree' => TRUE, + ); + $select_voc = _yahoo_terms_vocab_select(); + $current = variable_get('leech_term_extraction', 0); + $current = $current['leech_terms_per_feed']; + if (!is_array($current)) { + $current = array(); + } + $result = db_query("SELECT n.nid, n.title, l.url FROM {node} n INNER JOIN {leech} l ON n.nid = l.nid ORDER BY n.title ASC"); + while ($leech = db_fetch_object($result)) { + if (!isset($current[$leech->nid])) { + $current[$leech->nid] = array(); + } + if (!isset($current[$leech->nid]['vocab'])) { + $current[$leech->nid]['vocab'] = variable_get('default_vocab_for_leech', FALSE); + } + $form['leech_advanced']['leech_term_extraction']['leech_terms_per_feed'][$leech->nid] = array( + '#type' => 'fieldset', + '#title' => $leech->title, + '#tree' => TRUE, + ); + $form['leech_advanced']['leech_term_extraction']['leech_terms_per_feed'][$leech->nid]['vocab'] = array( + '#type' => 'select', + '#options' => $select_voc, + '#default_value' => $current[$leech->nid]['vocab'], + '#description' => $leech->url, + ); + $form['leech_advanced']['leech_term_extraction']['leech_terms_per_feed'][$leech->nid]['query'] = array( + '#type' => 'textfield', + '#default_value' => isset($current[$leech->nid]['query']) ? $current[$leech->nid]['query'] : '', + '#description' => 'Query string for ' . $leech->url, + ); + } + } + + $formats = array ( + 'teaser' => t('Teaser'), + 'custom' => t('Custom'), + ); + $form['leech_page_items_format'] = array ( + '#type' => 'select', + '#options' => $formats, + '#default_value' => variable_get('leech_page_items_format', 'full'), + '#title' => t('Items format for default page'), + ); + + $form['leech_page_items_custom_length_teaser'] = array ( + '#type' => 'textfield', + '#default_value' => variable_get('leech_page_items_custom_length_teaser', 600), + '#title' => t('Item length (number of chars) for custom format teaser'), + ); + + $form['leech_page_items_custom_length_body'] = array ( + '#type' => 'textfield', + '#default_value' => variable_get('leech_page_items_custom_length_body', 600), + '#title' => t('Item length (number of chars) for custom format body'), + ); + + $form['leech_originalarticle_popup'] = array ( + '#type' => 'checkbox', + '#default_value' => variable_get('leech_originalarticle_popup', FALSE), + '#title' => t('Show the original article in a popup window'), + ); + + $formats = array ( + 'popup' => t('In a popup'), + 'window' => t('In a window'), + ); + $form['leech_originalwebsite_preview'] = array ( + '#type' => 'select', + '#options' => $formats, + '#default_value' => variable_get('leech_originalwebsite_preview', 'popup'), + '#title' => t('How to show the preview of the original website'), + ); + + 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; + } + if(variable_get('leech_originalarticle_popup', FALSE)) { + return ''. t('original article') .''; + } + else { + return ''. t('original article') .''; + } +} + +/** + * 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); + $format = variable_get('leech_page_items_format', 'full'); + + while ($node = db_fetch_object($result)) { + if($format == 'teaser') { + $output .= node_view(node_load($node->nid), true, false); + } + else if($format == 'custom') { + $output .= leech_item_custom_format($node->nid); + } + } + $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; +} + +function theme_leech_item_custom_format($node, $node_content) { + $output = '
    '; + + if (!$node->status) { + $output = '
    '; + } + + if (module_exists('taxonomy')) { + $terms = taxonomy_link('taxonomy terms', $node); + } + + if(variable_get('leech_originalwebsite_preview', 'popup')=='popup') { + $node_link = l($node->title, drupal_get_path_alias('node/' . $node->nid), array('onclick'=>"return leech_show_articleoriginal(this, " . $node->nid . ");"), NULL, NULL, FALSE, TRUE); + } + else { + $node_link = l($node->title, 'leech/articlepreview/' . $node->nid, NULL, NULL, NULL, FALSE, TRUE); + } + $output .= t('!title by !name', array('!title' => '

    '. $node_link .'

    ', '!name' => theme('username', $node))); + + if (count($terms)) { + $output .= ' ('. theme('links', $terms) .')
    '; + } + + $node->content['body']['#value'] = $node_content; + $output .= '
    ' . drupal_render($node->content) . '
    '; + + if ($node->links) { + $output .= ''; + } + + if (!$node->status) { + $output .= '
    '; + } + + $output .= '
    '; + + return $output; +} + +function leech_item_custom_format($nid) { + $item = node_load($nid); + $item = node_build_content($item, true, false); + $item_content = _leech_htmlcorrector($item->body); + $item_content = _leech_truncate_text($item_content, variable_get(leech_page_items_custom_length_teaser, 600), '', false); + $item_content = _leech_htmlcorrector($item_content); + $item->links['leech_fullnode'] = array ( + 'title' => t('Read more'), + 'href' => drupal_get_path_alias('node/'. $item->nid)); + $item->links = array_merge($item->links, module_invoke_all('link', 'node', $item, true)); + return theme('leech_item_custom_format', $item, $item_content); +} + +/** + * Truncates text. + * + * Cuts a string to the length of $length and replaces the last characters + * with the ending if the text is longer than length. + * + * @param string $text String to truncate. + * @param integer $length Length of returned string, including ellipsis. + * @param string $ending Ending to be appended to the trimmed string. + * @param boolean $exact If false, $text will not be cut mid-word + * @return string Trimmed string. + */ +function _leech_truncate_text($text, $length = 600, $ending = '', $exact = false) { + if($text == "France is hit by transport upheavals as workers strike against President Nicolas Sarkozy's pension reforms.") { + $d=1; + } + else { + $d=0; + } + + if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { + return $text; + } + + preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER); + $total_length = 0; + $open_tags = array(); + $truncate = ''; + foreach ($lines as $line_matchings) { + if (!empty($line_matchings[1])) { + if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) { + } elseif (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) { + $pos = array_search($tag_matchings[1], $open_tags); + if ($pos !== false) { + unset($open_tags[$pos]); + } + } elseif (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) { + array_unshift($open_tags, strtolower($tag_matchings[1])); + } + $truncate .= $line_matchings[1]; + } + + $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $line_matchings[2])); + if ($total_length + $content_length > $length) { + $left = $length - $total_length; + $entities_length = 0; + if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE|PREG_PATTERN_ORDER)) { + foreach ($entities[0] as $entity) { + if ($entity[1] + 1 - $entities_length <= $left) { + $left--; + $entities_length += strlen($entity[0]); + } else { + break; + } + } + } + $truncate .= substr($line_matchings[2], 0, $left + $entities_length); + break; + } else { + $truncate .= $line_matchings[2]; + $total_length += $content_length; + } + if ($total_length >= $length) { + break; + } + } + + if (!$exact) { + $periodpos = strrpos($truncate, '. '); + if ($periodpos === FALSE ) { + $periodpos = strpos($text, '.'); + if($periosdpos !== FALSE) { + $truncate = substr($text, 0, $periodpos+1); + } + else { + $truncate = $text; + } + } + else { + $truncate = substr($truncate, 0, $periodpos+1); + } + } + + $truncate = rtrim($truncate); + + if (!empty($inline)) { + $truncate .= " " . $inline; + } + + foreach ($open_tags as $tag) { + $truncate .= ''; + } + + if ( !empty($ending) ) { + $truncate .= $ending; + } + + return $truncate; +} + +function _leech_htmlcorrector($text) { + // Prepare tag lists. + static $no_nesting, $single_use; + if (!isset($no_nesting)) { + // Tags which cannot be nested but are typically left unclosed. + $no_nesting = drupal_map_assoc(array('li', 'p', 'div')); + + // Single use tags in HTML4 + $single_use = drupal_map_assoc(array('base', 'meta', 'link', 'hr', 'br', 'param', 'img', 'area', 'input', 'col', 'frame')); + } + + // Properly entify angles. + $text = preg_replace('!<([^a-zA-Z/])!', '<\1', $text); + + // Split tags from text. + $split = preg_split('/<([^>]+?)>/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); + // Note: PHP ensures the array consists of alternating delimiters and literals + // and begins and ends with a literal (inserting $null as required). + + $tag = false; // Odd/even counter. Tag or no tag. + $stack = array(); + $output = ''; + foreach ($split as $value) { + // Process HTML tags. + if ($tag) { + list($tagname) = explode(' ', strtolower($value), 2); + // Closing tag + if ($tagname{0} == '/') { + $tagname = substr($tagname, 1); + // Discard XHTML closing tags for single use tags. + if (!isset($single_use[$tagname])) { + // See if we possibly have a matching opening tag on the stack. + if (in_array($tagname, $stack)) { + // Close other tags lingering first. + do { + $output .= ''; + } while (array_shift($stack) != $tagname); + } + // Otherwise, discard it. + } + } + // Opening tag + else { + // See if we have an identical 'no nesting' tag already open and close it if found. + if (count($stack) && ($stack[0] == $tagname) && isset($no_nesting[$stack[0]])) { + $output .= ''; + } + // Push non-single-use tags onto the stack + if (!isset($single_use[$tagname])) { + array_unshift($stack, $tagname); + } + // Add trailing slash to single-use tags as per X(HT)ML. + else { + $value = rtrim($value, ' /') .' /'; + } + $output .= '<'. $value .'>'; + } + } + else { + // Passthrough all text. + $output .= $value; + } + $tag = !$tag; + } + // Close remaining tags. + while (count($stack) > 0) { + $output .= ''; + } + 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 INNER JOIN {leech_news_feed} f ON 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('Retrieve feed items'), '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) { + if(variable_get('leech_originalarticle_popup', FALSE)) { + $links['leech_link_full_article'] = array('title' => t('Read original article.'), 'href' => $node->leech_news_item->link, 'attributes'=>array('onclick'=>'return leech_show_articleoriginal(this, ' . $node->nid . ')')); + } + else { + $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 site'), '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 feed 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); + + $result = db_query_range('SELECT l.nid, n.uid + FROM {leech} l + JOIN {node} n ON l.nid = n.nid + WHERE l.refresh > 0 AND l.checked + l.refresh <= %d ORDER BY l.checked ASC', + time(), 0, variable_get('leech_cron_count', 5)); + $leeches = array(); + $nids_to_update = array(); + while ($temp = db_fetch_object($result)) { + $leeches[] = $temp; + $nids_to_update[] = $temp->nid; + } + if (count($leeches) > 0) { + // Update checked field of leeches right away - this assures that there is a minimum time window + // in that another cron processes could come and update the same feeds. The problem with this approach is, + // that if cron does not finish on these leeches they will not be checked until their check time elapses again. + db_query('UPDATE {leech} SET checked = %d WHERE nid IN (%s)', time(), implode(', ', $nids_to_update)); + + foreach ($leeches as $temp) { + 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); + } + } + } + } + + // Delete too old items + $sql = 'SELECT f.items_delete, n.created, i.nid FROM {node} n '; + $sql .= 'INNER JOIN {leech_news_item} i ON n.nid = i.nid '; + $sql .= 'INNER JOIN {leech_news_feed} f ON f.nid = i.fid '; + $result = db_query($sql); + $now = time(); + while ($node = db_fetch_object($result)) { + if (abs($now - $node->created) > $node->items_delete) { + node_delete($node->nid); + } + } + + // 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 INNER JOIN {leech} l ON l.nid = n.nid WHERE l.url = '%s' ", $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 ''; +} + +/** + * 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(variable_get('leech_page_items_format', 'full')=='custom') { + $truncated_text = $node->content['body']['#value']; + if($teaser) { + $truncated_text = _leech_htmlcorrector($node->body); + $truncated_text = _leech_truncate_text($truncated_text, variable_get('leech_page_items_custom_length_teaser', 600), '', false); + $truncated_text = _leech_htmlcorrector($truncated_text); + } + else if($page) { + $truncated_text = _leech_htmlcorrector($node->body); + $truncated_text = _leech_truncate_text($truncated_text, variable_get('leech_page_items_custom_length_body', 600), '', false); + $truncated_text = _leech_htmlcorrector($truncated_text); + } + $node->content['body']['#value'] = $truncated_text; + $node->teaser = $truncated_text; + $node->body = $truncated_text; + } + } + + if (!isset($node->leech)) { + return; + } + + // Provide some statistics + $rows = array(); + $rows[] = array(t('Feed URL'), l(truncate_utf8($node->leech->url, 40, FALSE, TRUE), $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)[^>]*>(?=.*)%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 INNER JOIN {leech} l ON 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($connection->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', 'text/plain'))) { + return; + } + + // Check if data contains format we can handle + //if (preg_match('%<(rss|rdf:rdf|feed|channel)[^>]*>(?=.*)%siU', $connection->result->data, $matches) < 1) { + // I use this less strict checking because of some PHP4 - PHP5 annoyance + if (preg_match('%<(rss|rdf:rdf|feed|channel)[^>]*>%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]; + + // Workaround for xml w/ single quotes. drupal_xml_create_parser don't handle it well + $result->data = ereg_replace("^(<\?xml[^>]+encoding=)'([^'\"]+)'", '\\1"\\2"', $result->data); + + $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 INNER JOIN {leech_news_item} i ON i.nid = n.nid WHERE i.fid = %d 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 INNER JOIN {leech_news_item} i ON i.nid = n.nid WHERE i.fid = %d 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; + if(module_exists('localizernode')) { + $feed_language = localizernode_findbynid($node->nid); + unset($edit->localizernode_pid); + $edit->localizernode_locale = $feed_language['locale']; + } + 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 { + // Call the term extraction if enabled + $current = variable_get('leech_term_extraction', 0); + $current = $current['leech_terms_per_feed']; + if ($current != 0) { + $vid = $current[$edit->leech_news_item->fid]['vocab']; + $query = $current[$edit->leech_news_item->fid]['query']; + } + if (!is_numeric($vid)) { + $vid = NULL; + } + if (strlen($query) < 1) { + $query = NULL; + } + node_invoke_nodeapi($edit, "terms_tagging", $vid, $query); + + 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 INNER JOIN {leech_news_item} i ON n.nid = i.nid WHERE n.status = 1 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); + + $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); + + 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 nid + FROM {leech_news_item} li + WHERE fid = %d + GROUP BY nid", $node->nid); + while ($feeditem = db_fetch_object($result)) { + $result_term_node = db_query("SELECT tid + FROM {term_node} + WHERE nid = %d", $feeditem->nid); + while ($termnode = db_fetch_object($result_term_node)) { + $tids[] = $termnode->tid; + if (in_array($termnode->tid, $del_tids )) { + db_query("DELETE FROM {term_node} WHERE nid = %d AND tid = %d", $feeditem->nid, $termnode->tid); + $updated[$feeditem->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)", $feeditem->nid, $add_tid); + $updated[$feeditem->nid] = 1; + } + } + } + } + + drupal_set_message("Taxonomy of ".count($updated)." child feed item(s) updated in ".timer_read("taxpass")." ms", "status"); + timer_stop("taxpass"); +} + +function leech_nodebody() { + $nid = $_GET['nid']; + $node = node_load($nid); + echo '
    ' . $node->body . '
    '; + echo '
    '; + echo '' . t('Close') . ''; + echo l(t('Go to the article'), 'node/' . $nid); + echo '
    '; + exit; +} + +function leech_articleoriginal() { + $nid = $_GET['nid']; + $node = node_load($nid); + leech_statistic($nid); + echo '
    '; + echo ''; + echo ''; + echo ''; + + $node_type = in_array($node->type, variable_get('vote_up_down_node_types', array()), TRUE); + if($node_type && user_access('view up-down vote') && module_exists('vote_up_down')) { + echo ''; + } + + if(module_exists('community_tags_inline')) { + echo ''; + } + echo ''; + echo '
    '; + $imagesdir = base_path() . path_to_theme() . '/images'; + echo 'Geekomatik'; + echo '
    '; + echo ''; + echo '
    '; + echo '
    '; + $style = variable_get('vote_up_down_widget_style_node', 0) == 1 ? '_alt' : ''; + echo theme("vote_up_down_widget$style", $node->nid, 'node'); + $links = array(); + $links['vote_up_down_points'] = theme('vote_up_down_points', $node->nid, 'node', TRUE); + echo theme('links', $links); + echo '
    '; + echo '
    '; + echo '
    '; + echo community_tags_inline_view($node); + echo '
    '; + echo '
    '; + + echo '
    '; + echo '
    '; + echo ''; + echo '
    '; + exit; +} + +function leech_statistic($nid) { + global $user; + + if (variable_get('statistics_count_content_views', 0)) { + // We are counting content views. + if (is_numeric($nid)) { + // A node has been viewed, so update the node's counters. + db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), $nid); + // If we affected 0 rows, this is the first time viewing the node. + if (!db_affected_rows()) { + // We must create a new row to store counters for the new node. + db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES (%d, 1, 1, %d)', $nid, time()); + } + } + } + if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) { + // Log this page access. + db_query("INSERT INTO {accesslog} (title, path, url, hostname, uid, sid, timer, timestamp) values('%s', '%s', '%s', '%s', %d, '%s', %d, %d)", strip_tags(drupal_get_title()), 'node/' . $nid, referer_uri(), $_SERVER['REMOTE_ADDR'], $user->uid, session_id(), timer_read('page'), time()); + } +} + + +function leech_articlepreview($nid) { + $node = node_load($nid); + + $o = ''; + $o .= '' . drupal_get_title() . ''; + $o .= ''; + $o .= ''; + $o .= ''; + $o .= ''; + $o .= ''; + $o .= ''; + + echo $o; + exit(); +} + +function leech_articlepreviewheader($nid) { + $o = ''; + $o .= '' . drupal_get_title() . ''; + $o .= ''; + $o .= 'Logo, links, tools'; + $o .= ''; + $o .= ''; + echo $o; + exit(); +} \ No newline at end of file diff -urpN leech-orig/leech_views.info leech/leech_views.info --- leech-orig/leech_views.info 1970-01-01 01:00:00.000000000 +0100 +++ leech/leech_views.info 2007-10-06 09:53:32.000000000 +0200 @@ -0,0 +1,5 @@ +name = Leech views +description = "Provides views support for Leech" +dependencies = leech views +package = Leech +version = 5.0 \ No newline at end of file diff -urpN leech-orig/leech_views.module leech/leech_views.module --- leech-orig/leech_views.module 1970-01-01 01:00:00.000000000 +0100 +++ leech/leech_views.module 2007-09-14 17:12:59.000000000 +0200 @@ -0,0 +1,216 @@ +Requires leech.module and views.module'); + case 'admin/modules#description': + return t('Views for leech. Requires leech.module and views.module'); + } +} + +/** + * Implementation of hook_views_tables() + * + * todo - timestamps are GMT + * - ability to filter on updated time + * - all kinds of sorts + */ +function leech_views_views_tables() { + $tables ['leech'] = array( + 'name' => 'leech', + 'provider' => 'leech', + 'join' => array( + 'left' => array( + 'table' => 'node', + 'field' => 'nid', + ), + 'right' => array( + 'field' => 'nid', + ) + ), + 'fields' => array( + 'news_last_arrived' => array( + 'name' => t('Leech: Last Arrival'), + 'handler' => views_handler_field_dates(), + 'option' => 'string', + 'help' => t('Display the last time new items were found.'), + ), + 'refresh' => array( + 'name' => t('Leech: Refresh Time'), + 'handler' => views_handler_field_dates(), + 'option' => 'string', + 'help' => t('Display the next refresh time.'), + ), + 'checked' => array( + 'name' => t('Leech: Last Checked'), + 'handler' => views_handler_field_dates(), + 'option' => 'string', + 'help' => t('Display the last time the feed was checked.'), + ), + 'refresh_link' => array( + 'name' => t('Leech: Refresh Link'), + 'notafield' => TRUE, + 'handler' => 'leech_views_handler_field_refresh_link', + 'addlfields' => array('nid'), + 'help' => t('Display a refresh link'), + ), + 'url' => array( + 'name' => t('Leech: Source URL'), + 'handler' => 'leech_views_handler_field_source_url', + 'help' => t('Display a the Feed Source URL') + ), + ), + ); + $tables ['leech_news_item'] = array( + 'name' => 'leech_news_item', + 'provider' => 'leech', + 'join' => array( + 'left' => array( + 'table' => 'node', + 'field' => 'nid', + ), + 'right' => array( + 'field' => 'nid', + ) + ), + 'filters' => array( + 'fid' => array( + 'name' => t('Leech: Feed'), + 'operator' => 'views_handler_operator_andor', + 'list' => 'leech_views_handler_feed_filter', + 'value-type' => 'array', + 'help' => t('Filter by Feed'), + ) + ) + ); + $tables ['leech_node'] = array( + 'name' => 'node', + 'provider' => 'internal', + 'join' => array( + 'left' => array( + 'table' => 'leech_news_item', + 'field' => 'fid' + ), + 'right' => array( + 'field' => 'nid' + ) + ), + 'fields' => array( + 'title' => array( + 'name' => t('Leech: Feed Title'), + 'handler' => array( + 'leech_views_handler_field_feed_title' => t('As Link'), + 'leech_views_handler_field_feed_title_nl' => t('Without Link'), + ), + 'addlfields' => array('nid'), + 'help' => t('Display the title of the feed'), + ), + ), + 'sorts' => array( + 'title' => array( + 'name' => t('Leech: Feed Title'), + 'help' => t('Sort by the title of the feed'), + ), + ), + ); + + return $tables; +} + +/** + * Implementation of hook_views_arguments() + */ +function leech_views_views_arguments(){ + $arguments['leech_feed_id'] = array( + 'name' => 'Leech: Feed id', + 'help' => t('Select feed id'), + 'handler' => 'leech_views_arg_fid' + ); + return $arguments; +} + +/** + * Handler for feed ID argument + * + * todo - complete argument support + */ +function leech_views_arg_fid($op, &$query, $argtype, $arg = ''){ + switch($op) { + /* + case 'summary': + $query->ensure_table('leech'); + $query->add_field('fid', 'leech_news_item'); + $fieldinfo['field'] = "leech_news_item.fid"; + return $fieldinfo; + */ + /* + case 'sort': + $query->add_orderby('vocabulary', 'weight', $argtype); + $query->add_orderby('vocabulary', 'name', $argtype); + break; + */ + case 'filter': + $query->ensure_table('leech_news_item'); + $query->add_where('leech_news_item.fid = %d', $arg); + $query->set_distinct(); + break; + /* + case 'link': + return l($query->name, "$arg/" . intval($query->vid)); + */ + case 'title': + $result = db_query("SELECT title FROM {node} WHERE nid = %d", $arg); + $voc = db_fetch_object($result); + return check_plain($voc->name); + } +} + +/** + * Provide filter options for filtering by feed + */ +function leech_views_handler_feed_filter(){ + $result = db_query("SELECT n.nid, n.title FROM {node} n INNER JOIN {leech} l ON n.nid = l.nid ORDER BY n.title ASC"); + + $feeds = array(); + while ($leech = db_fetch_object($result)) { + $feeds[$leech->nid] = $leech->title; + } + + return $feeds; +} + +/** + * Format a field as a link to the feed node + */ +function leech_views_handler_field_feed_title($fieldinfo, $fielddata, $value, $data) { + return l($value, "node/$data->leech_nodes_nid"); +} + +/** + * Format a title field without a link + */ +function leech_views_handler_field_feed_title_nl($fieldinfo, $fielddata, $value, $data) { + return check_plain($value); +} + +/** + * Provide a refresh link + * todo - proper destination handling + */ +function leech_views_handler_field_refresh_link($fieldinfo, $fielddata, $value, $data){ + return l('Refresh', "leech/refresh/$data->nid"); +} + +/** + * Format the source url + */ +function leech_views_handler_field_source_url($fieldinfo, $fielddata, $value, $data) { + return check_plain($value); +} \ No newline at end of file Binary files leech-orig/loading_animation.gif and leech/loading_animation.gif differ