From 0864a0837bcfd80689fc943acd7a59b23f9c8cd1 Mon Sep 17 00:00:00 2001 From: poltawski <poltawski> Date: Wed, 29 Jul 2009 13:44:28 +0000 Subject: [PATCH] block_rss_client: MDL-13932/MDL-7946 - Switch to use Simplepie Simplepie is actively maintained and supports a broader range of feeds --- blocks/rss_client/block_rss_client.php | 371 +++++++++++++------------ blocks/rss_client/editfeed.php | 15 +- blocks/rss_client/viewfeed.php | 27 +- 3 files changed, 211 insertions(+), 202 deletions(-) diff --git a/blocks/rss_client/block_rss_client.php b/blocks/rss_client/block_rss_client.php index 36c454b53d..1241be364c 100644 --- a/blocks/rss_client/block_rss_client.php +++ b/blocks/rss_client/block_rss_client.php @@ -1,20 +1,28 @@ -<?php //$Id$ - -/******************************************************************* -* This file contains one class which defines a block for display on -* any Moodle page. This block can be configured to display the contents -* of a remote RSS news feed in your web site. -* -* @author Daryl Hawes -* @version $Id$ -* @license http://www.gnu.org/copyleft/gpl.html GNU Public License -* @package base -******************************************************************/ +<?php + +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** - * This class is for a block which defines a block for display on - * any Moodle page. + * A block which displays Remote feeds + * + * @package rss_client + * @copyright Daryl Hawes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL */ + class block_rss_client extends block_base { function init() { @@ -43,81 +51,70 @@ } function get_content() { - global $CFG, $editing, $USER; - - if (!empty($this->page->course)) { - $this->courseid = $this->page->course->id; - } - - /// When displaying feeds in block, we double $CFG->block_rss_client_timeout - /// so those feeds retrieved and cached by the cron() process will have a - /// better chance to be used - if (!empty($CFG->block_rss_client_timeout)) { - $CFG->block_rss_client_timeout *= 2; - } - - require_once($CFG->libdir .'/rsslib.php'); + global $CFG, $DB; - if($this->content !== NULL) { + if ($this->content !== NULL) { return $this->content; } + // initalise block content object $this->content = new stdClass; $this->content->text = ''; $this->content->footer = ''; if (empty($this->instance)) { - // We're being asked for content without an associated instance return $this->content; } - $output = ''; - $rssid = -1; - $display_description = false; - if (isset($CFG->block_rss_client_num_entries) && is_numeric($CFG->block_rss_client_num_entries) ) { - $shownumentries = intval($CFG->block_rss_client_num_entries); - } else { - $shownumentries = 5; //default to 5 entries is not specified in admin section or instance - } + if (!isset($this->config)) { + // The block has yet to be configured - just display configure message in + // the block if user has permission to configure it + + if (has_capability('block/rss_client:manageanyfeeds', $this->context)) { + $this->content->text = get_string('feedsconfigurenewinstance2', 'block_rss_client'); + } - if (empty($this->config)) { - $this->content->text = get_string('feedsconfigurenewinstance2', 'block_rss_client'); return $this->content; } + // How many feed items should we display? + $maxentries = 5; + if ( !empty($this->config->shownumentries) ) { + $maxentries = intval($this->config->shownumentries); + }elseif( isset($CFG->block_rss_client_num_entries) ) { + $maxentries = intval($CFG->block_rss_client_num_entries); + } + + + /* --------------------------------- + * Begin Normal Display of Block Content + * --------------------------------- */ + + $output = ''; + + if (!empty($this->config->rssid)) { - if (is_array($this->config->rssid)) { - $rssidarray = $this->config->rssid; - } else { // Make an array of the single value - $rssidarray = array($this->config->rssid); + list($rss_ids_sql, $params) = $DB->get_in_or_equal($this->config->rssid); + + $rss_feeds = $DB->get_records_select('block_rss_client', "id $rss_ids_sql", $params); + + $showtitle = false; + if (count($rss_feeds) > 1) { + // when many feeds show the title for each feed + $showtitle = true; } - } - if (!empty($this->config->display_description)) { - $display_description = intval($this->config->display_description); - } - if (!empty($this->config->shownumentries)) { - $shownumentries = intval($this->config->shownumentries); - } - // Daryl Hawes note: if count of rssidarray is greater than 1 - // we should possibly display a drop down menu of selected feed titles - // so user can select a single feed to view (similar to RSSFeed) - if (!empty($rssidarray)) { - $numids = count($rssidarray); - $count = 0; - foreach ($rssidarray as $rssid) { - $output .= $this->get_rss_by_id($rssid, $display_description, $shownumentries, ($numids > 1) ? true : false); - if ($numids > 1 && $count != $numids -1 && !empty($rssfeedstring)) { - $output .= '<hr style="width=:80%" />'; - } - $count ++; + foreach($rss_feeds as $feed){ + $output.= $this->get_feed_html($feed, $maxentries, $showtitle); } } $this->content->text = $output; + return $this->content; } + function instance_allow_multiple() { return true; } @@ -131,127 +128,145 @@ } /** - * @param int $rssid The feed to be displayed - * @param bool $display_description Should the description information from the feed be displayed or simply the title? - * @param int $shownumentries The maximum number of feed entries to be displayed. - * @param bool $showtitle True if the feed title should be displayed above the feed entries. - * @return string|NULL + * Returns the html of a feed to be displaed in the block + * + * @param mixed feedrecord The feed record from the database + * @param int maxentries The maximum number of entries to be displayed + * @param boolean showtitle Should the feed title be displayed in html + * @return string html representing the rss feed content */ - function get_rss_by_id($rssid, $display_description, $shownumentries, $showtitle=false) { - global $CFG, $DB; - $returnstring = ''; - $now = time(); - require_once($CFG->libdir .'/rsslib.php'); - require_once(MAGPIE_DIR .'rss_fetch.inc'); - - $rss_record = $DB->get_record('block_rss_client', array('id'=>$rssid)); - if (isset($rss_record) && isset($rss_record->id)) { - // By capturing the output from fetch_rss this way - // error messages do not display and clutter up the moodle interface - // however, we do lose out on seeing helpful messages like "cache hit", etc. - ob_start(); - $rss = fetch_rss($rss_record->url); - $rsserror = ob_get_contents(); - ob_end_clean(); - - if ($rss === false) { - debugging($rsserror); - } + function get_feed_html($feedrecord, $maxentries, $showtitle){ + global $CFG; + require_once($CFG->libdir.'/simplepie/moodle_simplepie.php'); - // first we must verify that the rss feed is loaded - // by checking $rss and $rss->items exist before using them - if (empty($rss) || empty($rss->items)) { - return ''; - } + $feed = new moodle_simplepie($feedrecord->url); - if ($shownumentries > 0 && $shownumentries < count($rss->items) ) { - $rss->items = array_slice($rss->items, 0, $shownumentries); - } + if(isset($CFG->block_rss_client_timeout)){ + $feed->set_cache_duration($CFG->block_rss_client_timeout*60); + } - if (empty($rss_record->preferredtitle)) { - if (isset($rss->channel['title'])) { // Just in case feed is dead - $feedtitle = $this->format_title($rss->channel['title']); - } else { - $feedtitle = ''; - } - } else { - $feedtitle = $this->format_title($rss_record->preferredtitle); - } + if(debugging() && $feed->error()){ + return '<p>'. $feedrecord->url .' Failed with code: '.$feed->error().'</p>'; + } - if (isset($this->config) && - isset($this->config->block_rss_client_show_channel_image) && - $this->config->block_rss_client_show_channel_image && - isset($rss->image) && isset($rss->image['link']) && isset($rss->image['title']) && isset($rss->image['url']) ) { + $r = ''; // return string - $rss->image['title'] = s($rss->image['title']); - $returnstring .= "\n".'<div class="image" title="'. $rss->image['title'] .'"><a href="'. $rss->image['link'] .'"><img src="'. $rss->image['url'] .'" alt="'. $rss->image['title'] .'" /></a></div>'; + if($this->config->block_rss_client_show_channel_image){ + if($image = $feed->get_image_url()){ + $imagetitle = s($feed->get_image_title()); + $imagelink = $feed->get_image_link(); + $r.='<div class="image" title="'.$imagetitle.'">'."\n"; + if($imagelink){ + $r.='<a href="'.$imagelink.'">'; + } + $r.='<img src="'.$image.'" alt="'.$imagetitle.'" />'."\n"; + if($imagelink){ + $r.='</a>'; + } + $r.= '</div>'; } + } - if ($showtitle) { - $returnstring .= '<div class="title">'. $feedtitle .'</div>'; - } + if(empty($feedrecord->preferredtitle)){ + $feedtitle = $this->format_title($feed->get_title()); + }else{ + $feedtitle = $this->format_title($feedrecord->preferredtitle); + } - $formatoptions->para = false; + if($showtitle){ + $r.='<div class="title">'.$feedtitle.'</div>'; + } - /// Accessibility: markup as a list. - $returnstring .= '<ul class="list">'."\n"; + $r.='<ul class="list">'."\n"; - foreach ($rss->items as $item) { - if ($item['title'] == '') { - // no title present, use portion of description - $item['title'] = substr(strip_tags($item['description']), 0, 20) . '...'; - } else { - $item['title'] = break_up_long_words($item['title'], 30); - } + $feeditems = $feed->get_items(0, $maxentries); + foreach($feeditems as $item){ + $r.= $this->get_item_html($item); + } - if ($item['link'] == '') { - $item['link'] = $item['guid']; - } - $item['title'] = s($item['title']); + $r.='</ul>'; - $item['link'] = str_replace('&', '&', $item['link']); - $returnstring .= '<li><div class="link"><a href="'. $item['link'] .'" onclick="this.target=\'_blank\'" >'. $item['title'] . "</a></div>\n"; + if ($this->config->block_rss_client_show_channel_link) { - if ($display_description && !empty($item['description'])) { - $item['description'] = break_up_long_words($item['description'], 30); - $returnstring .= '<div class="description">'. - format_text($item['description'], FORMAT_MOODLE, $formatoptions, $this->courseid) . - '</div>'; - } - $returnstring .= "</li>\n"; + $channellink = str_replace('&', '&', $feed->get_link()); + + if (!empty($channellink)){ + //NOTE: this means the 'last feed' display wins the block title - but + //this is exiting behaviour.. + $this->content->footer = '<a href="'. $channellink.'">'. get_string('clientchannellink', 'block_rss_client') .'</a>'; } - $returnstring .= "</ul>\n"; + } - if (!empty($rss->channel['link'])) { - $rss->channel['link'] = str_replace('&', '&', $rss->channel['link']); + if (empty($this->config->title)){ + //NOTE: this means the 'last feed' displayed wins the block title - but + //this is exiting behaviour.. + $this->title = strip_tags($feedtitle); + } - if (!empty($this->config) && isset($this->config->block_rss_client_show_channel_link) && $this->config->block_rss_client_show_channel_link) { - $this->content->footer = '<a href="'. $rss->channel['link'] .'">'. get_string('clientchannellink', 'block_rss_client') .'</a>'; - } - if (!empty($feedtitle) ) { - $feedtitle = '<a href="'. $rss->channel['link'] .'">'. $feedtitle .'</a>'; - } - } + return $r; + } + + + /** + * Returns the html list item of a feed item + * + * @param mixed item simplepie_item representing the feed item + * @return string html li representing the rss feed item + */ + function get_item_html($item){ + + $link = $item->get_link(); + $title = $item->get_title(); + $description = $item->get_description(); + + + if(empty($title)){ + // no title present, use portion of description + $title = substr(strip_tags($description), 0, 20) . '...'; + }else{ + $title = break_up_long_words($title, 30); } - // if block has no custom title - if (empty($this->config) || (!empty($this->config) && empty($this->config->title))) { - // if the feed has a title - if (!empty($feedtitle) and ($feedtitle != '<a href="'. $rss->channel['link'] .'"></a>')) { - // set the block's title to the feed's title - $this->title = strip_tags($feedtitle); - } + if(empty($link)){ + $link = $item->get_id(); } - return $returnstring; + + + $r = "<li>\n"; + $r.= '<div class="link"><a href="'.$link.'" onclick="this.target=\'_blank\'" >'."\n"; + $r.= s($title); + $r.= "</a></div>\n"; + + if($this->config->display_description && !empty($description)){ + + $description = break_up_long_words($description, 30); + + $formatoptions = new object; + $formatoptions->para = false; + + $r.= '<div class="description">'; + $r.= format_text($description, FORMAT_MOODLE, $formatoptions, $this->page->course->id); + $r.= '</div>'; + } + + $r.= '</li>'; + + return $r; } - // just strips the title down and adds ... for excessively long titles. + /** + * Strips a large title to size and adds ... if title too long + * + * @param string title to shorten + * @param int max character length of title + * @return string title s() quoted and shortened if necessary + */ function format_title($title,$max=64) { - /// Loading the textlib singleton instance. We are going to need it. + // Loading the textlib singleton instance. We are going to need it. $textlib = textlib_get_instance(); if ($textlib->strlen($title) <= $max) { @@ -261,45 +276,53 @@ } } - // cron function, used to refresh all the RSS feeds from Moodle cron + /** + * cron - goes through all feeds and retrieves them with the cache + * duration set to 0 in order to force the retrieval of the item and + * refresh the cache + * + * @return boolean true if all feeds were retrieved succesfully + */ function cron() { global $CFG, $DB; + require_once($CFG->libdir.'/simplepie/moodle_simplepie.php'); - /// We are going to measure execution times + // We are going to measure execution times $starttime = microtime(); - /// And we have one initial $status + // And we have one initial $status $status = true; - /// We require some stuff - require_once($CFG->libdir .'/rsslib.php'); - require_once(MAGPIE_DIR .'rss_fetch.inc'); - - /// Fetch all site feeds. + // Fetch all site feeds. $rs = $DB->get_recordset('block_rss_client'); $counter = 0; mtrace(''); foreach ($rs as $rec) { mtrace(' ' . $rec->url . ' ', ''); - /// Fetch the rss feed, using standard magpie caching - /// so feeds will be renewed only if cache has expired - // sometimes the cron times out on moodle.org during fetching, - // there is a 5s limit in magpie which should work, but does not sometimes :-( + // Fetch the rss feed, using standard simplepie caching + // so feeds will be renewed only if cache has expired @set_time_limit(60); - if ($rss = fetch_rss($rec->url)) { - mtrace ('ok'); - } else { + + $feed = new moodle_simplepie(); + $feed->set_cache_duration(0); + $feed->set_feed_url($rec->url); + $feed->init(); + + if ($feed->error()) { mtrace ('error'); + mtrace ('SimplePie failed with error:'.$feed->error()); $status = false; + } else { + mtrace ('ok'); } $counter ++; } $rs->close(); - /// Show times + // Show times mtrace($counter . ' feeds refreshed (took ' . microtime_diff($starttime, microtime()) . ' seconds)'); - /// And return $status + // And return $status return $status; } } diff --git a/blocks/rss_client/editfeed.php b/blocks/rss_client/editfeed.php index 8ae4496a46..1fd9813073 100644 --- a/blocks/rss_client/editfeed.php +++ b/blocks/rss_client/editfeed.php @@ -26,8 +26,7 @@ require_once(dirname(__FILE__) . '/../../config.php'); require_once($CFG->libdir . '/formslib.php'); -require_once($CFG->libdir .'/rsslib.php'); -require_once(MAGPIE_DIR .'rss_fetch.inc'); +require_once($CFG->libdir .'/simplepie/moodle_simplepie.php'); class feed_edit_form extends moodleform { protected $isadding; @@ -75,15 +74,13 @@ class feed_edit_form extends moodleform { function validation($data, $files) { $errors = parent::validation($data, $files); - ob_start(); - $rss = fetch_rss($data['url']); - $rsserrors = ob_get_clean(); + $rss = new moodle_simplepie($data['url']); - if (!$rss) { - $errors['url'] = get_string('errorloadingfeed', 'block_rss_client', $rsserrors); + if ($rss->error()) { + $errors['url'] = get_string('errorloadingfeed', 'block_rss_client', $rss->error()); } else { - $this->title = $rss->channel['title']; - $this->description = $rss->channel['description']; + $this->title = $rss->get_title(); + $this->description = $rss->get_description(); } return $errors; diff --git a/blocks/rss_client/viewfeed.php b/blocks/rss_client/viewfeed.php index a0085e6a15..55e1bbcc93 100644 --- a/blocks/rss_client/viewfeed.php +++ b/blocks/rss_client/viewfeed.php @@ -24,8 +24,7 @@ */ require_once(dirname(__FILE__) . '/../../config.php'); -require_once($CFG->libdir .'/rsslib.php'); -require_once(MAGPIE_DIR .'rss_fetch.inc'); +require_once($CFG->libdir .'/simplepie/moodle_simplepie.php'); require_login(); if (isguest()) { @@ -68,12 +67,10 @@ $PAGE->set_generaltype('popup'); $rssrecord = $DB->get_record('block_rss_client', array('id' => $rssid), '*', MUST_EXIST); -ob_start(); -$rss = fetch_rss($rssrecord->url); -$rsserrors = ob_get_clean(); +$rss = new moodle_simplepie($rssrecord->url); -if (!$rss) { - debugging($rsserrors); +if ($rss->error()) { + debugging($rss->error()); print_error('errorfetchingrssfeed'); } @@ -98,26 +95,18 @@ echo $OUTPUT->header($navigation); if (!empty($rssrecord->preferredtitle)) { $feedtitle = $rssrecord->preferredtitle; } else { - $feedtitle = $rss->channel['title']; + $feedtitle = $rss->get_title(); } echo '<table align="center" width="50%" cellspacing="1">'."\n"; echo '<tr><td colspan="2"><strong>'. $feedtitle .'</strong></td></tr>'."\n"; -foreach ($rss->items as $item) { - if ($item['link'] == '') { - $item['link'] = $item['guid']; - } - - if ($item['title'] == '') { - $item['title'] = '>>'; - } - +foreach ($rss->get_items() as $item) { echo '<tr><td valign="middle">'."\n"; - echo '<a href="'. $item['link'] .'" target="_blank"><strong>'. $item['title']; + echo '<a href="'. $item->get_description() .'" target="_blank"><strong>'. $item->get_title(); echo '</strong></a>'."\n"; echo '</td>'."\n"; echo '</tr>'."\n"; echo '<tr><td colspan="2"><small>'; - echo $item['description'] .'</small></td></tr>'."\n"; + echo $item->get_description() .'</small></td></tr>'."\n"; } echo '</table>'."\n"; -- 2.39.5