diff --git a/blocks/rss_client/block_rss_client.php b/blocks/rss_client/block_rss_client.php index 3f3e292f175..b745aef7a67 100644 --- a/blocks/rss_client/block_rss_client.php +++ b/blocks/rss_client/block_rss_client.php @@ -61,19 +61,6 @@ protected function get_footer($feedrecords) { $footer = null; - if ($this->config->block_rss_client_show_channel_link) { - global $CFG; - require_once($CFG->libdir.'/simplepie/moodle_simplepie.php'); - - $feedrecord = array_pop($feedrecords); - $feed = new moodle_simplepie($feedrecord->url); - $channellink = new moodle_url($feed->get_link()); - - if (!empty($channellink)) { - $footer = new block_rss_client\output\footer($channellink); - } - } - if ($this->hasfailedfeeds) { if (has_any_capability(['block/rss_client:manageownfeeds', 'block/rss_client:manageanyfeeds'], $this->context)) { if ($footer === null) { @@ -104,6 +91,15 @@ return $this->content; } + $managefeedfooterlink = ''; + if (has_any_capability(['block/rss_client:manageanyfeeds', 'block/rss_client:manageownfeeds'], $this->context)) { + $managefeedfooterlink = html_writer::link( + new moodle_url('/blocks/rss_client/managefeeds.php', ['courseid' => $this->page->course->id]), + get_string('managefeeds', 'block_rss_client'), + ['class' => 'btn btn-primary', 'role' => 'button'], + ); + } + 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 @@ -112,6 +108,8 @@ $this->content->text = get_string('feedsconfigurenewinstance2', 'block_rss_client'); } + $this->content->footer = $managefeedfooterlink; + return $this->content; } @@ -156,6 +154,8 @@ $this->content->footer = $renderer->render_footer($footer); } + $this->content->footer .= $managefeedfooterlink; + return $this->content; } @@ -259,6 +259,12 @@ } } + // Feed channel link. + if ($this->config->block_rss_client_show_channel_link) { + $channellink = $simplepiefeed->get_link(); + $feed->set_channellink($channellink ? new moodle_url($channellink) : null); + } + return $feed; } diff --git a/blocks/rss_client/classes/output/feed.php b/blocks/rss_client/classes/output/feed.php index 28ea2cf3594..61b9ae8f995 100644 --- a/blocks/rss_client/classes/output/feed.php +++ b/blocks/rss_client/classes/output/feed.php @@ -44,6 +44,13 @@ class feed implements \renderable, \templatable { */ protected $title = null; + /** + * The feed's channel link. + * + * @var string|null + */ + protected ?string $channellink; + /** * An array of renderable feed items * @@ -78,11 +85,13 @@ class feed implements \renderable, \templatable { * @param string $title The title of the RSS feed * @param boolean $showtitle Whether to show the title * @param boolean $showimage Whether to show the channel image + * @param string|null $channellink The channel link of the RSS feed */ - public function __construct($title, $showtitle = true, $showimage = true) { + public function __construct($title, $showtitle = true, $showimage = true, ?string $channellink = null) { $this->title = $title; $this->showtitle = $showtitle; $this->showimage = $showimage; + $this->channellink = $channellink; } /** @@ -97,6 +106,7 @@ class feed implements \renderable, \templatable { 'title' => $this->showtitle ? $this->title : null, 'image' => null, 'items' => array(), + 'channellink' => $this->channellink ?? null, ); if ($this->showimage && $this->image) { @@ -131,6 +141,15 @@ class feed implements \renderable, \templatable { return $this->title; } + /** + * Set the feed channel link. + * + * @param \moodle_url|null $channellink the URL to the channel website. + */ + public function set_channellink(?\moodle_url $channellink) { + $this->channellink = $channellink; + } + /** * Add an RSS item * diff --git a/blocks/rss_client/classes/output/footer.php b/blocks/rss_client/classes/output/footer.php index c864df3d686..7ff348ad289 100644 --- a/blocks/rss_client/classes/output/footer.php +++ b/blocks/rss_client/classes/output/footer.php @@ -100,7 +100,6 @@ class footer implements \renderable, \templatable { */ public function export_for_template(\renderer_base $output) { $data = new \stdClass(); - $data->channellink = clean_param($this->channelurl, PARAM_URL); if ($this->manageurl) { $data->hasfailedfeeds = true; $data->manageurl = clean_param($this->manageurl, PARAM_URL); diff --git a/blocks/rss_client/edit_form.php b/blocks/rss_client/edit_form.php index 6f481abf83d..dc6e9ef5b2c 100644 --- a/blocks/rss_client/edit_form.php +++ b/blocks/rss_client/edit_form.php @@ -22,6 +22,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir .'/simplepie/moodle_simplepie.php'); + /** * Form for editing RSS client block instances. * @@ -29,24 +33,36 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class block_rss_client_edit_form extends block_edit_form { + + /** @var stdClass|null The new RSS feed URL object. */ + private ?stdClass $newrss = null; + protected function specific_definition($mform) { global $CFG, $DB, $USER; // Fields for editing block contents. $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); - $mform->addElement('selectyesno', 'config_display_description', get_string('displaydescriptionlabel', 'block_rss_client')); - $mform->setDefault('config_display_description', 0); + $radiogroup = [ + $mform->createElement('radio', 'config_method', + get_string('configmethodexisting', 'block_rss_client'), null, 'existing'), + $mform->createElement('radio', 'config_method', + get_string('configmethodnew', 'block_rss_client'), null, 'new'), + ]; + $mform->addGroup( + elements: $radiogroup, + name: 'config_method_group', + separator: ['   '], + appendName: false, + ); + $mform->setDefault('config_method', 'existing'); - $mform->addElement('text', 'config_shownumentries', get_string('shownumentrieslabel', 'block_rss_client'), array('size' => 5)); - $mform->setType('config_shownumentries', PARAM_INT); - $mform->addRule('config_shownumentries', null, 'numeric', null, 'client'); - if (!empty($CFG->block_rss_client_num_entries)) { - $mform->setDefault('config_shownumentries', $CFG->block_rss_client_num_entries); - } else { - $mform->setDefault('config_shownumentries', 5); - } + // Add new RSS feed. + $mform->addElement('text', 'config_feedurl', get_string('feedurl', 'block_rss_client')); + $mform->setType('config_feedurl', PARAM_URL); + $mform->hideIf('config_feedurl', 'config_method', 'ne', 'new'); + // Select existing RSS feed. $insql = ''; $params = array('userid' => $USER->id); if (!empty($this->block->config) && !empty($this->block->config->rssid)) { @@ -68,21 +84,33 @@ class block_rss_client_edit_form extends block_edit_form { if ($rssfeeds) { $select = $mform->addElement('select', 'config_rssid', get_string('choosefeedlabel', 'block_rss_client'), $rssfeeds); $select->setMultiple(true); - + $mform->hideIf('config_rssid', 'config_method', 'ne', 'existing'); } else { $mform->addElement('static', 'config_rssid_no_feeds', get_string('choosefeedlabel', 'block_rss_client'), get_string('nofeeds', 'block_rss_client')); + $mform->hideIf('config_rssid_no_feeds', 'config_method', 'ne', 'existing'); } - if (has_any_capability(array('block/rss_client:manageanyfeeds', 'block/rss_client:manageownfeeds'), $this->block->context)) { - $mform->addElement('static', 'nofeedmessage', '', - '' . - get_string('feedsaddedit', 'block_rss_client') . ''); - } + // Subheading: Display settings for RSS feed. + $startsubheading = '

'; + $endsubheading = '

'; + $mform->addElement('html', $startsubheading . get_string('displaysettings', 'block_rss_client') . $endsubheading); $mform->addElement('text', 'config_title', get_string('uploadlabel')); $mform->setType('config_title', PARAM_NOTAGS); + $mform->addElement('selectyesno', 'config_display_description', get_string('displaydescriptionlabel', 'block_rss_client')); + $mform->setDefault('config_display_description', 0); + + $mform->addElement('text', 'config_shownumentries', get_string('shownumentrieslabel', 'block_rss_client'), ['size' => 5]); + $mform->setType('config_shownumentries', PARAM_INT); + $mform->addRule('config_shownumentries', null, 'numeric', null, 'client'); + if (!empty($CFG->block_rss_client_num_entries)) { + $mform->setDefault('config_shownumentries', $CFG->block_rss_client_num_entries); + } else { + $mform->setDefault('config_shownumentries', 5); + } + $mform->addElement('selectyesno', 'config_block_rss_client_show_channel_link', get_string('clientshowchannellinklabel', 'block_rss_client')); $mform->setDefault('config_block_rss_client_show_channel_link', 0); @@ -90,6 +118,91 @@ class block_rss_client_edit_form extends block_edit_form { $mform->setDefault('config_block_rss_client_show_channel_image', 0); } + /** + * Overriding the get_data function to insert a new RSS ID. + */ + public function get_data(): ?stdClass { + $data = parent::get_data(); + // Force the 'existing` method as a default. + $data->config_method = 'existing'; + // Sanitize the title to prevent XSS (Cross-Site Scripting) attacks by encoding special characters into HTML entities. + $data->config_title = htmlspecialchars($data->config_title, ENT_QUOTES, 'utf-8'); + // If the new RSS is not empty then add the ID to the config_rssid. + if ($data && $this->newrss) { + $data->config_rssid[] = $this->newrss->id; + } + return $data; + } + + /** + * Overriding the definition_after_data to empty the input. + */ + public function definition_after_data(): void { + parent::definition_after_data(); + $mform =& $this->_form; + // If form is not submitted then empty the feed URL. + if (!$this->is_submitted()) { + $mform->getElement('config_feedurl')->setValue(''); + } + } + + /** + * Overriding the validation to validate the RSS URL and store it to the database. + * + * If there are no errors, insert the new feed to the database and store the object in + * the private property so it can be saved to the RSS block config. + * + * @param array $data Data from the form. + * @param array $files Files form the form. + * @return array of errors from validation. + */ + public function validation($data, $files): array { + global $USER, $DB; + $errors = parent::validation($data, $files); + + if ($data['config_method'] === "new") { + // If the "New" method is selected and the feed URL is not empty, then proceed. + if ($data['config_feedurl']) { + if (!filter_var($data['config_feedurl'], FILTER_VALIDATE_URL)) { + $errors['config_feedurl'] = get_string('couldnotfindloadrssfeed', 'block_rss_client'); + return $errors; + } + try { + $rss = new moodle_simplepie(); + // Set timeout for longer than normal to try and grab the feed. + $rss->set_timeout(10); + $rss->set_feed_url($data['config_feedurl']); + $rss->set_autodiscovery_cache_duration(0); + $rss->set_autodiscovery_level(moodle_simplepie::LOCATOR_ALL); + $rss->init(); + if ($rss->error()) { + $errors['config_feedurl'] = get_string('couldnotfindloadrssfeed', 'block_rss_client'); + } else { + // Return URL without quoting. + $discoveredurl = new moodle_url($rss->subscribe_url()); + $theurl = $discoveredurl->out(false); + // Save the RSS to the database. + $this->newrss = new stdClass; + $this->newrss->userid = $USER->id; + $this->newrss->title = $rss->get_title(); + $this->newrss->description = $rss->get_description(); + $this->newrss->url = $theurl; + $newrssid = $DB->insert_record('block_rss_client', $this->newrss); + $this->newrss->id = $newrssid; + } + } catch (Exception $e) { + $errors['config_feedurl'] = get_string('couldnotfindloadrssfeed', 'block_rss_client'); + } + } else { + // If the "New" method is selected, but the feed URL is empty, then raise error. + $errors['config_feedurl'] = get_string('err_required', 'form'); + } + + } + + return $errors; + } + /** * Display the configuration form when block is being added to the page * diff --git a/blocks/rss_client/lang/en/block_rss_client.php b/blocks/rss_client/lang/en/block_rss_client.php index 06facda0af2..310ca7d5cce 100644 --- a/blocks/rss_client/lang/en/block_rss_client.php +++ b/blocks/rss_client/lang/en/block_rss_client.php @@ -27,17 +27,21 @@ $string['addheadlineblock'] = 'Add RSS headline block'; $string['addnew'] = 'Add new'; $string['addnewfeed'] = 'Add a new feed'; $string['cannotmakemodification'] = 'You are not allowed to make modifications to this RSS feed at this time.'; +$string['choosefeedlabel'] = 'Select the feeds to display in this block'; $string['clientchannellink'] = 'Source site...'; $string['clientnumentries'] = 'The default number of entries to show per feed.'; -$string['clientshowchannellinklabel'] = 'Should a link to the original site (channel link) be displayed? (Note that if no feed link is supplied in the news feed then no link will be shown) :'; +$string['clientshowchannellinklabel'] = 'Show source link if available'; $string['clientshowimagelabel'] = 'Show channel image if available :'; $string['configblock'] = 'Configure this block'; +$string['configmethodexisting'] = 'Select existing RSS feed'; +$string['configmethodnew'] = 'Add new RSS feed'; $string['couldnotfindfeed'] = 'Could not find feed with id'; $string['couldnotfindloadrssfeed'] = 'Could not find or load the RSS feed.'; $string['customtitlelabel'] = 'Custom title (leave blank to use title supplied by feed):'; $string['deletefeedconfirm'] = 'Are you sure you want to delete this feed?'; $string['disabledrssfeeds'] = 'RSS feeds are disabled'; -$string['displaydescriptionlabel'] = 'Display each link\'s description?'; +$string['displaydescriptionlabel'] = 'Show descriptions for entries'; +$string['displaysettings'] = 'Display settings for RSS feed'; $string['editafeed'] = 'Edit a feed'; $string['editfeeds'] = 'Edit, subscribe or unsubscribe from RSS/Atom news feeds'; $string['editnewsfeeds'] = 'Edit news feeds'; @@ -54,11 +58,10 @@ $string['feedsaddedit'] = 'Add/edit feeds'; $string['feedsconfigurenewinstance'] = 'Click here to configure this block to display RSS feeds.'; $string['feedsconfigurenewinstance2'] = 'Click the edit icon above to configure this block to display RSS feeds.'; $string['feedupdated'] = 'News feed updated'; -$string['feedurl'] = 'Feed URL'; +$string['feedurl'] = 'RSS link'; $string['findmorefeeds'] = 'Find more RSS feeds'; -$string['choosefeedlabel'] = 'Choose the feeds which you would like to make available in this block:'; $string['managefeeds'] = 'Manage all my feeds'; -$string['nofeeds'] = 'There are no RSS feeds defined for this site.'; +$string['nofeeds'] = 'There are no existing RSS feeds configured for this site. You can add one choosing \'Add new RSS feed\'.'; $string['numentries'] = 'Entries per feed'; $string['pickfeed'] = 'Pick a news feed'; $string['pluginname'] = 'Remote RSS feeds'; @@ -81,7 +84,7 @@ $string['rss_client:manageownfeeds'] = 'Manage own RSS feeds'; $string['rss_client:myaddinstance'] = 'Add a new Remote RSS feeds block to Dashboard'; $string['seeallfeeds'] = 'See all feeds'; $string['sharedfeed'] = 'Shared feed'; -$string['shownumentrieslabel'] = 'Max number entries to show per block.'; +$string['shownumentrieslabel'] = 'Entries to display'; $string['submitters'] = 'Who will be allowed to define new RSS feeds? Defined feeds are available for any page on your site.'; $string['submitters2'] = 'Submitters'; $string['timeout'] = 'Time in minutes before an RSS feed expires in cache. Note that this time defines the minimum time before expiry; the feed will be refreshed in cache on the next cron execution after expiry. Recommended values are 30 mins or greater.'; diff --git a/blocks/rss_client/templates/block.mustache b/blocks/rss_client/templates/block.mustache index 6cc2c71ea07..8dca0217c8c 100644 --- a/blocks/rss_client/templates/block.mustache +++ b/blocks/rss_client/templates/block.mustache @@ -55,7 +55,8 @@ "permalink": "https://www.example.com/my-cat-story.html", "datepublished": "12 January 2016, 9:12 pm" } - ] + ], + "channellink": "https://www.example.com" }, { "title": "News from around my kitchen", @@ -81,7 +82,8 @@ "permalink": "https://www.example.com/oven-smoke.html", "datepublished": "13 January 2016, 8:25 pm" } - ] + ], + "channellink": "https://www.example.com" } ] } diff --git a/blocks/rss_client/templates/channel_image.mustache b/blocks/rss_client/templates/channel_image.mustache index f20166e53d7..230f8d31f5d 100644 --- a/blocks/rss_client/templates/channel_image.mustache +++ b/blocks/rss_client/templates/channel_image.mustache @@ -42,7 +42,7 @@ {{/link}} - {{title}} + {{title}} {{#link}} diff --git a/blocks/rss_client/templates/feed.mustache b/blocks/rss_client/templates/feed.mustache index a69f3e82888..7600bb9fa11 100644 --- a/blocks/rss_client/templates/feed.mustache +++ b/blocks/rss_client/templates/feed.mustache @@ -55,7 +55,8 @@ "permalink": "https://www.example.com/my-cat-story.html", "datepublished": "12 January 2016, 9:12 pm" } - ] + ], + "channellink": "https://www.example.com" } }} {{$image}} @@ -77,3 +78,9 @@ {{/items}} {{/items}} + +{{#channellink}} +
+ {{#str}} clientchannellink, block_rss_client {{/str}} +
+{{/channellink}} diff --git a/blocks/rss_client/templates/footer.mustache b/blocks/rss_client/templates/footer.mustache index dd5d0fe2b39..d3fb1c471fa 100644 --- a/blocks/rss_client/templates/footer.mustache +++ b/blocks/rss_client/templates/footer.mustache @@ -30,13 +30,10 @@ Example context (json): { - "channellink": "https://www.example.com/feeds/rss" + "hasfailedfeeds": true, + "manageurl": "http://moodle.web/blocks/rss_client/managefeeds.php?courseid=1" } }} -{{#channellink}} - {{#str}} clientchannellink, block_rss_client {{/str}} - {{#hasfailedfeeds}}
{{/hasfailedfeeds}} -{{/channellink}} {{#hasfailedfeeds}} - {{#str}} failedfeeds, block_rss_client {{/str}} -{{/hasfailedfeeds}} \ No newline at end of file + {{#str}} failedfeeds, block_rss_client {{/str}} +{{/hasfailedfeeds}} diff --git a/blocks/rss_client/tests/behat/block_rss_client_frontpage.feature b/blocks/rss_client/tests/behat/block_rss_client_frontpage.feature new file mode 100644 index 00000000000..36eddaf2970 --- /dev/null +++ b/blocks/rss_client/tests/behat/block_rss_client_frontpage.feature @@ -0,0 +1,48 @@ +@block @block_rss_client +Feature: Enable RSS client block menu on the frontpage + In order to enable the RSS client block on the frontpage + As an admin + I can add RSS client block to the frontpage + + Background: + Given I log in as "admin" + When I navigate to "Plugins > Blocks > Manage blocks" in site administration + Then I enable "rss_client" "block" plugin + And the following "blocks" exist: + | blockname | contextlevel | reference | pagetypepattern | defaultregion | + | rss_client | System | 1 | site-index | side-pre | + + @javascript + Scenario: Configuring the RSS block on the frontpage + Given I log in as "admin" + And I am on site homepage + And I turn editing mode on + And "Remote news feed" "block" should exist + And I configure the "Remote news feed" block + And I should see "There are no existing RSS feeds configured for this site. You can add one choosing 'Add new RSS feed'." + + # Test filling in an empty URL in the input. + And I click on "Add new RSS feed" "radio" + And I press "Save changes" + And I should see "You must supply a value here." + + # Test filling in with a non-valid URL in the input. + And I set the field "config_feedurl" to "https://example.com/notvalid.rss" + And I press "Save changes" + And I should see "Could not find or load the RSS feed." + + # Test filling in with the correct URL in the input. + And I set the field "config_feedurl" to "https://www.nasa.gov/rss/dyn/breaking_news.rss" + And I set the field "config_block_rss_client_show_channel_link" to "Yes" + And I press "Save changes" + And I should see "NASA" + And I should see "Source site..." + + # Test the existence of the available feeds. + When I configure the "NASA" block + Then I should see "NASA" in the "Select the feeds to display in this block" "select" + And I click on "Cancel" "button" in the "Configure NASA block" "dialogue" + + # Test the Manage all my feeds page. + And I click on "Manage all my feeds" "link" + And I should see "NASA"