From e5ffbcce327ba4d724ad8eb1229e6d93db636f6f Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 14 Sep 2022 17:22:35 +0200 Subject: [PATCH] Big refactor of the plugin: * End of the dependency with rss_client * Use of SimpleXML instead of Simplepie * Changed carousselspeed into caroussel delay * Updated & validated workflow * New tests * Validated run-ci.sh * Added some TODOs for future * Fixed phpdoc of all files --- README.txt => README.md | 0 amd/build/config.min.js | 11 +- amd/build/config.min.js.map | 2 +- amd/build/glide.min.js | 12 +- amd/build/glide.min.js.map | 2 +- amd/src/glide.js | 1 + block_rss_thumbnails.php | 268 +++++++++-------------- classes/feed_creator.php | 201 +++++++++++++++++ classes/output/block.php | 56 +++-- classes/output/channel_image.php | 117 ++++++++++ classes/output/feed.php | 149 +++++++++++++ classes/output/footer.php | 110 ++++++++++ classes/output/item.php | 174 ++++++++++++--- classes/output/renderer.php | 34 +-- db/access.php | 46 ++-- db/install.xml | 24 +++ db/upgrade.php | 69 ++++++ edit_form.php | 117 +++++++++- editfeed.php | 279 ++++++++++++++++++++++++ lang/en/block_rss_thumbnails.php | 27 ++- managefeeds.php | 159 ++++++++++++++ styles.css | 7 +- templates/block.mustache | 8 +- tests/block_rss_thumbnails_test.php | 43 ++-- tests/feed_creator_test.php | 105 +++++++++ tests/fixtures/sample-feed2.xml | 320 ++++++++++++++++++++++++++++ tests/fixtures/sample-item.xml | 25 +++ version.php | 10 +- 28 files changed, 2084 insertions(+), 292 deletions(-) rename README.txt => README.md (100%) create mode 100644 classes/feed_creator.php create mode 100644 classes/output/channel_image.php create mode 100644 classes/output/feed.php create mode 100644 classes/output/footer.php create mode 100644 db/install.xml create mode 100644 db/upgrade.php create mode 100644 editfeed.php create mode 100644 managefeeds.php create mode 100644 tests/feed_creator_test.php create mode 100644 tests/fixtures/sample-feed2.xml create mode 100644 tests/fixtures/sample-item.xml diff --git a/README.txt b/README.md similarity index 100% rename from README.txt rename to README.md diff --git a/amd/build/config.min.js b/amd/build/config.min.js index 2cb9a0f..1974831 100644 --- a/amd/build/config.min.js +++ b/amd/build/config.min.js @@ -1,2 +1,9 @@ -define ("block_rss_thumbnails/config",["core/config"],function(a){window.requirejs.config({paths:{glide:a.wwwroot+"/lib/javascript.php/"+a.jsrev+"/blocks/rss_thumbnails/js/glide/dist/glide"+(a.developerdebug?".min":"")},shim:{glide:{exports:"glide"}}})}); -//# sourceMappingURL=config.min.js.map +/** + * RSS Thumbnails block + * + * @copyright 2020 - CALL Learning - Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define("block_rss_thumbnails/config",["core/config"],(function(cfg){window.requirejs.config({paths:{glide:cfg.wwwroot+"/lib/javascript.php/"+cfg.jsrev+"/blocks/rss_thumbnails/js/glide/dist/glide"+(cfg.developerdebug?".min":"")},shim:{glide:{exports:"glide"}}})})); + +//# sourceMappingURL=config.min.js.map \ No newline at end of file diff --git a/amd/build/config.min.js.map b/amd/build/config.min.js.map index e19fa3f..c95afe4 100644 --- a/amd/build/config.min.js.map +++ b/amd/build/config.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/config.js"],"names":["define","cfg","window","requirejs","config","paths","wwwroot","jsrev","developerdebug","shim","exports"],"mappings":"AAOAA,OAAM,+BAAC,CAAC,aAAD,CAAD,CAAkB,SAAUC,CAAV,CAAe,CACnCC,MAAM,CAACC,SAAP,CAAiBC,MAAjB,CAAwB,CACpBC,KAAK,CAAE,CACH,MACIJ,CAAG,CAACK,OAAJ,CACE,sBADF,CAEEL,CAAG,CAACM,KAFN,CAGE,4CAHF,EAIGN,CAAG,CAACO,cAAJ,CAAqB,MAArB,CAA8B,EAJjC,CAFD,CADa,CASpBC,IAAI,CAAE,CACF,MAAS,CAACC,OAAO,CAAE,OAAV,CADP,CATc,CAAxB,CAaH,CAdK,CAAN","sourcesContent":["/**\n * RSS Thumbnails block\n *\n * @package block_rss_thumbnails\n * @copyright 2020 - CALL Learning - Laurent David \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/config'], function (cfg) {\n window.requirejs.config({\n paths: {\n \"glide\":\n cfg.wwwroot\n + '/lib/javascript.php/'\n + cfg.jsrev\n + '/blocks/rss_thumbnails/js/glide/dist/glide'\n + (cfg.developerdebug ? '.min' : ''),\n },\n shim: {\n 'glide': {exports: 'glide'},\n }\n });\n});\n"],"file":"config.min.js"} \ No newline at end of file +{"version":3,"file":"config.min.js","sources":["../src/config.js"],"sourcesContent":["/**\n * RSS Thumbnails block\n *\n * @copyright 2020 - CALL Learning - Laurent David \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/config'], function(cfg) {\n window.requirejs.config({\n paths: {\n \"glide\":\n cfg.wwwroot\n + '/lib/javascript.php/'\n + cfg.jsrev\n + '/blocks/rss_thumbnails/js/glide/dist/glide'\n + (cfg.developerdebug ? '.min' : ''),\n },\n shim: {\n 'glide': {exports: 'glide'},\n }\n });\n});\n"],"names":["define","cfg","window","requirejs","config","paths","wwwroot","jsrev","developerdebug","shim","exports"],"mappings":";;;;;;AAMAA,qCAAO,CAAC,gBAAgB,SAASC,KAC7BC,OAAOC,UAAUC,OAAO,CACpBC,MAAO,OAECJ,IAAIK,QACF,uBACAL,IAAIM,MACJ,8CACCN,IAAIO,eAAiB,OAAS,KAEzCC,KAAM,OACO,CAACC,QAAS"} \ No newline at end of file diff --git a/amd/build/glide.min.js b/amd/build/glide.min.js index 53a3bf6..aff07bc 100644 --- a/amd/build/glide.min.js +++ b/amd/build/glide.min.js @@ -1,2 +1,10 @@ -define ("block_rss_thumbnails/glide",["jquery","block_rss_thumbnails/config"],function(a){return function(b,c){require(["glide"],function(d){a(b).removeClass("d-none");new d(b,c).mount()})}}); -//# sourceMappingURL=glide.min.js.map +/** + * RSS Thumbnails block + * + * @package + * @copyright 2020 - CALL Learning - Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define("block_rss_thumbnails/glide",["jquery","block_rss_thumbnails/config"],(function($){return function(locator,config){require(["glide"],(function(Glide){$(locator).removeClass("d-none"),new Glide(locator,config).mount()}))}})); + +//# sourceMappingURL=glide.min.js.map \ No newline at end of file diff --git a/amd/build/glide.min.js.map b/amd/build/glide.min.js.map index a3cc818..62d40fb 100644 --- a/amd/build/glide.min.js.map +++ b/amd/build/glide.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/glide.js"],"names":["define","$","locator","config","require","Glide","removeClass","mount"],"mappings":"AAOAA,OAAM,8BAAC,CAAE,QAAF,CAAY,6BAAZ,CAAD,CAA6C,SAAUC,CAAV,CAAa,CAC5D,MAAO,UAAUC,CAAV,CAAmBC,CAAnB,CAA2B,CAC9BC,OAAO,CAAC,CAAC,OAAD,CAAD,CAAY,SAAUC,CAAV,CAAiB,CAEhCJ,CAAC,CAACC,CAAD,CAAD,CAAWI,WAAX,CAAuB,QAAvB,EACA,GAAID,CAAAA,CAAJ,CAAUH,CAAV,CAAmBC,CAAnB,EAA2BI,KAA3B,EACH,CAJM,CAKV,CACJ,CARK,CAAN","sourcesContent":["/**\n * RSS Thumbnails block\n *\n * @package block_rss_thumbnails\n * @copyright 2020 - CALL Learning - Laurent David \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([ 'jquery', 'block_rss_thumbnails/config'], function ($) {\n return function (locator, config) {\n require(['glide'], function (Glide) {\n // Show the slider now we are initialised.\n $(locator).removeClass('d-none');\n new Glide(locator, config).mount();\n });\n };\n});"],"file":"glide.min.js"} \ No newline at end of file +{"version":3,"file":"glide.min.js","sources":["../src/glide.js"],"sourcesContent":["/**\n * RSS Thumbnails block\n *\n * @package\n * @copyright 2020 - CALL Learning - Laurent David \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'block_rss_thumbnails/config'], function($) {\n return function(locator, config) {\n require(['glide'], function(Glide) {\n // Show the slider now we are initialised.\n $(locator).removeClass('d-none');\n new Glide(locator, config).mount();\n });\n };\n});"],"names":["define","$","locator","config","require","Glide","removeClass","mount"],"mappings":";;;;;;;AAOAA,oCAAO,CAAC,SAAU,gCAAgC,SAASC,UAChD,SAASC,QAASC,QACrBC,QAAQ,CAAC,UAAU,SAASC,OAExBJ,EAAEC,SAASI,YAAY,cACnBD,MAAMH,QAASC,QAAQI"} \ No newline at end of file diff --git a/amd/src/glide.js b/amd/src/glide.js index a7fe35e..04e70dc 100644 --- a/amd/src/glide.js +++ b/amd/src/glide.js @@ -1,6 +1,7 @@ /** * RSS Thumbnails block * + * @package * @copyright 2020 - CALL Learning - Laurent David * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/block_rss_thumbnails.php b/block_rss_thumbnails.php index cf8bb1c..65b2e17 100644 --- a/block_rss_thumbnails.php +++ b/block_rss_thumbnails.php @@ -18,153 +18,155 @@ * RSS Thumbnail Block * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -use block_rss_client\output\channel_image; -use block_rss_client\output\feed; use block_rss_thumbnails\output\block; -use block_rss_thumbnails\output\item; +use block_rss_thumbnails\output\feed; +use block_rss_thumbnails\output\footer; +use block_rss_thumbnails\feed_creator; defined('MOODLE_INTERNAL') || die(); global $CFG; -require_once($CFG->dirroot . '/blocks/rss_client/block_rss_client.php'); /** * Class block_rss_thumbnails * - * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class block_rss_thumbnails extends block_rss_client { +class block_rss_thumbnails extends block_base { + + // TODO Think about adding a slider to allow navigation between feed items. + // TODO Add a message when there is no internet connection or if the rss feed can't be found. + + /** @var int The default delay between 2 slides */ + const DEFAULT_CAROUSSEL_DELAY = 4000; + + /** @var string The default name of the block */ + const DEFAULT_TITLE = "RSS Thumbnail"; - /** @var int The default caroussel speed */ - const DEFAULT_CAROUSSEL_SPEED = 4000; + /** @var int The maximum number of item entries for a feed by default */ + const DEFAULT_MAX_ENTRIES = 5; - /** @var bool track whether any of the output feeds have recorded failures */ + + /** @var bool Track whether any of the output feeds have recorded failures */ private $hasfailedfeeds = false; + /** @var int Defines the delay between two slides of the caroussel (ms) */ + private $carousseldelay = self::DEFAULT_CAROUSSEL_DELAY; + + /** @var int Defines the number of maximum feeds in the thumbnail */ + private $maxentries = self::DEFAULT_MAX_ENTRIES; + + /** * Init function * + * @param int|null $carousseldelay * @throws coding_exception */ - public function init() { + public function init(?int $carousseldelay = null): void { + + if (!empty($carousseldelay)) { + $this->carousseldelay = $carousseldelay; + } + $this->title = get_string('pluginname', 'block_rss_thumbnails'); + + // Initialise content. + $this->content = new stdClass(); + $this->content->text = ''; + $this->content->footer = ''; } /** * Content for the block * - * @return stdClass|string|null + * @return stdClass|null * @throws coding_exception */ public function get_content() { + global $DB; + $this->page->requires->css( new moodle_url('/blocks/rss_thumbnails/js/glide/dist/css/glide.core' . (debugging() ? '.min' : '') . '.css')); - global $CFG, $DB; - - if ($this->content !== null) { - return $this->content; - } - - // Initialise block content object. - $this->content = new stdClass; - $this->content->text = ''; - $this->content->footer = ''; - - if (empty($this->instance)) { - return $this->content; - } 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 (has_capability('block/rss_thumbnails:manageanyfeeds', $this->context)) { + $this->content->text = get_string('configureblock', 'block_rss_thumbnails'); } return $this->content; } - // How many feed items should we display? - $maxentries = 5; - if (!empty($this->config->shownumentries)) { - $maxentries = intval($this->config->shownumentries); - } else if (isset($CFG->block_rss_client_num_entries)) { - $maxentries = intval($CFG->block_rss_client_num_entries); - } - - /* --------------------------------- - * Begin Normal Display of Block Content - * --------------------------------- */ + // We need this if an user deletes a field in the configuration of the block. + $this->title = $this->config->title ?? self::DEFAULT_TITLE; + $this->carousseldelay = $this->config->carousseldelay ?? self::DEFAULT_CAROUSSEL_DELAY; + $this->maxentries = $this->config->numentries ?? self::DEFAULT_MAX_ENTRIES; - $renderer = $this->page->get_renderer('block_rss_thumbnails'); - $carousselspeed = empty($this->config->carousselspeed) ? self::DEFAULT_CAROUSSEL_SPEED : $this->config->carousselspeed; - $block = new block($carousselspeed); + $block = new block($this->get_carousseldelay()); if (!empty($this->config->rssid)) { list($rssidssql, $params) = $DB->get_in_or_equal($this->config->rssid); - $rssfeeds = $DB->get_records_select('block_rss_client', "id $rssidssql", $params); - - if (!empty($rssfeeds)) { - $showtitle = false; - if (count($rssfeeds) > 1) { - // When many feeds show the title for each feed. - $showtitle = true; - } + $rssfeeds = $DB->get_records_select('block_rss_thumbnails', "id $rssidssql", $params); - foreach ($rssfeeds as $feed) { - if ($renderablefeed = $this->get_feed($feed, $maxentries, $showtitle)) { - $block->add_feed($renderablefeed); - } + foreach ($rssfeeds as $feed) { + $renderablefeed = $this->get_feed($feed, $this->maxentries); + if ($renderablefeed) { + $block->add_feed($renderablefeed); } - - $footer = $this->get_footer($rssfeeds); } - } - $this->content->text = $renderer->render_block($block); - if (isset($footer)) { - $this->content->footer = $renderer->render_footer($footer); + $footer = $this->get_footer($rssfeeds); + } else { + $rssfeeds = array(); } + $renderer = $this->page->get_renderer('block_rss_thumbnails'); + + $this->content = (object) [ + 'text' => $renderer->render($block, $rssfeeds), + 'footer' => $footer ?? '' + ]; return $this->content; } /** * Gets the footer, which is the channel link of the last feed in our list of feeds * - * @param array $feedrecords The feed records from the database. - * @return block_rss_client\output\footer|null The renderable footer or null if none should be displayed. + * @param array $feedrecords The feed records from the database + * @param int $maxentries The max number of items in the footer feed + * @return footer|null The renderable footer or null if none should be displaye */ - protected function get_footer($feedrecords) : ?block_rss_client\output\footer { + protected function get_footer($feedrecords, $maxentries = self::DEFAULT_MAX_ENTRIES): ?footer { $footer = null; if (!empty($this->config->show_channel_link)) { - global $CFG; - require_once($CFG->libdir . '/simplepie/moodle_simplepie.php'); - $feedrecord = array_pop($feedrecords); - $feed = new moodle_simplepie($feedrecord->url); + $feed = feed_creator::create_feed(file_get_contents($feedrecord->url), $maxentries); $channellink = new moodle_url($feed->get_link()); if (!empty($channellink)) { - $footer = new block_rss_client\output\footer($channellink); + $footer = new footer($channellink); } } if ($this->hasfailedfeeds) { - if (has_any_capability(['block/rss_client:manageownfeeds', 'block/rss_client:manageanyfeeds'], $this->context)) { + if ( + has_any_capability(['block/rss_thumbnails:manageownfeeds', 'block/rss_thumbnails:manageanyfeeds'], $this->context) + ) { if ($footer === null) { - $footer = new block_rss_client\output\footer(); + $footer = new footer(); } - $manageurl = new moodle_url('/blocks/rss_client/managefeeds.php', - ['courseid' => $this->page->course->id]); + $manageurl = new moodle_url('/blocks/rss_thumbnails/managefeeds.php', + ['courseid' => $this->page->course->id]); $footer->set_failed($manageurl); } } @@ -178,11 +180,9 @@ protected function get_footer($feedrecords) : ?block_rss_client\output\footer { * @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 block_rss_client\output\feed|null The renderable feed or null of there is an error + * @return block_rss_thumbnails\output\feed|null The renderable feed or null of there is an error */ - public function get_feed($feedrecord, $maxentries, $showtitle) : ?feed { - global $CFG; - require_once($CFG->libdir . '/simplepie/moodle_simplepie.php'); + public function get_feed($feedrecord, $maxentries, $showtitle = true): ?feed { if ($feedrecord->skipuntil) { // Last attempt to gather this feed via cron failed - do not try to fetch it now. @@ -190,101 +190,29 @@ public function get_feed($feedrecord, $maxentries, $showtitle) : ?feed { return null; } - if (!empty($feedrecord->url)) { - $simplepiefeed = new moodle_simplepie($feedrecord->url); - } else { - $simplepiefeed = new moodle_simplepie(); - $simplepiefeed->set_file($feedrecord->fileurl); - $simplepiefeed->enable_cache(false); - $simplepiefeed->init(); - - } - - if (isset($CFG->block_rss_client_timeout)) { - $simplepiefeed->set_cache_duration($CFG->block_rss_client_timeout * 60); - } - - if ($simplepiefeed->error()) { - if (!defined('BEHAT_SITE_RUNNING')) { - // No error while doing the behat tests. - debugging($feedrecord->url . ' Failed with code: ' . $simplepiefeed->error()); - } - return null; - } - - if (empty($feedrecord->preferredtitle)) { - // Simplepie does escape HTML entities. - $feedtitle = $this->format_title($simplepiefeed->get_title()); - } else { - // Moodle custom title does not does escape HTML entities. - $feedtitle = $this->format_title(s($feedrecord->preferredtitle)); - } + return feed_creator::create_feed_from_url($feedrecord->url, $maxentries, $showtitle); - 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); - } - - $feed = new feed($feedtitle, $showtitle, false); - - if ($simplepieitems = $simplepiefeed->get_items(0, $maxentries)) { - foreach ($simplepieitems as $simplepieitem) { - try { - $imageurl = null; - $content = $simplepieitem->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'content'); - if ($content && !empty($content[0])) { - $imageurl = $content[0]['attribs']['']['url']; - if (!empty($this->config->remove_image_size_suffix)) { - $imageurl = preg_replace('/-[0-9]+x[0-9]+/', '', $imageurl); - } - } - $categories = array_map(function($cat) { - return $cat->term; - }, $simplepieitem->get_categories()); - - $item = new item( - $simplepieitem->get_id(), - new moodle_url($simplepieitem->get_link()), - $simplepieitem->get_title(), - $simplepieitem->get_description(), - new moodle_url($simplepieitem->get_permalink()), - $simplepieitem->get_date('U'), - $this->config->display_description, - $imageurl, - $categories - ); - - $feed->add_item($item); - } catch (moodle_exception $e) { - // If there is an error with the RSS item, we don't - // want to crash the page. Specifically, moodle_url can - // throw an exception if the param is an extremely - // malformed url. - debugging($e->getMessage()); - } - } - } - - // Feed image. - if ($imageurl = $simplepiefeed->get_image_url()) { - try { - $image = new channel_image( - new moodle_url($imageurl), - $simplepiefeed->get_image_title(), - new moodle_url($simplepiefeed->get_image_link()) - ); - - $feed->set_image($image); - } catch (moodle_exception $e) { - // If there is an error with the RSS image, we don't want to - // crash the page. Specifically, moodle_url can throw an - // exception if the param is an extremely malformed url. - debugging($e->getMessage()); - } - } + } - return $feed; + /** + * Strips a large title to size and adds ... if title too long + * This function does not escape HTML entities, so they have to be escaped + * before being passed here. + * + * @param string $title title to shorten + * @param int $max max character length of title + * @return string title shortened if necessary + */ + public function format_title($title, $max = 64): string { + return (core_text::strlen($title) <= $max) ? $title : core_text::substr($title, 0, $max - 3) . '...'; } + /** + * Gets the caroussel delay between two slides + * + * @return int + */ + public function get_carousseldelay(): int { + return $this->carousseldelay; + } } diff --git a/classes/feed_creator.php b/classes/feed_creator.php new file mode 100644 index 0000000..9cc50e4 --- /dev/null +++ b/classes/feed_creator.php @@ -0,0 +1,201 @@ +. + +/** + * Contains class bock_rss_thumbnails\feed_creator + * + * @package block_rss_thumbnails + * @copyright 202 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_rss_thumbnails; + +use block_rss_thumbnails\output\channel_image; +use block_rss_thumbnails\output\feed; +use block_rss_thumbnails\output\item; +use moodle_exception; +use moodle_url; +use SimpleXMLElement; + +/** + * Class allowing to create feed objects from RSS feeds as XML files by calling several static methods. + * + * @package block_rss_thumbnails + * @copyright 202 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class feed_creator { + + /** A list of XML entities. */ + const XML_ENTITIES = array( + '"', '&', '&', '<', '>', ' ', '¡', '¢', '£', '¤', '¥', '¦', '§', + '¨', '©', 'ª', '«', '¬', '­', '®', '¯', '°', '±', '²', '³', + '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', + 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', + 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã', + 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', + 'ü', 'ý', 'þ', 'ÿ', 'Œ' + ); + + /** A list of HTML entities. */ + const HTML_ENTITIES = array( + '"', '&', '&', '<', '>', ' ', '¡', '¢', '£', '¤', '¥', '¦', + '§', '¨', '©', 'ª', '«', '¬', '­', '®', '¯', '°', '±', '²', + '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', + '¾', '¿', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', + 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', + 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', + 'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', + 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', + 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', + 'ÿ', '&oelig' + ); + + /** + * A function that creates a feed from the given url of an xml file. + * + * @param string $xmlurl the url of the file + * @param int $maxentries the maximum number of items this feed will have + * @param bool $showtitle should we display the title of the feed ? + * @return feed|null the feed created or null if an error occurred + */ + public static function create_feed_from_url(string $xmlurl, int $maxentries, bool $showtitle = true) { + return self::create_feed(file_get_contents($xmlurl), $maxentries, $showtitle); + } + + /** + * A function that creates a feed from the given xml file as a string. + * + * Notice that the xml source isn't the path of the xml file. You'll have to use {@see file_get_contents()} first. + * + * @param string $xmlsource the xml file content as a string. + * @param int $maxentries the maximum number of items this feed will have + * @param bool $showtitle should we display the title of the feed ? + * @return feed|null the feed created or null if an error occurred. + */ + public static function create_feed(string $xmlsource, int $maxentries, bool $showtitle = true): ?feed { + $xmlsource = self::normalize_xml_file($xmlsource); + $simplexmlelt = simplexml_load_string($xmlsource); + + $channel = $simplexmlelt->channel; + if (!$channel) { + return null; + } + + $image = $channel->image; + if (!empty($image)) { + $image = new channel_image( + new moodle_url($image->url), + $image->title, + new moodle_url($image->link) + ); + } else { + $image = null; + } + $feed = new feed( + $channel->title, + new moodle_url($channel->link), + $image, + $showtitle, + $image ? true : false, + ); + + $counter = 0; + foreach (self::create_items($simplexmlelt) as $item) { + if ($counter >= $maxentries) { + break; + } + if (empty($item->imageurl->url) && $feed->get_image()) { + $item->imageurl = $feed->get_image()->get_url(); + } + $feed->add_item($item); + $counter++; + } + + return $feed; + } + + /** + * Creates all the items of a feed by calling {@see self::create_item()} and returns them into an array. + * + * It has to be a whole xml file loaded by simplexml or you may have errors by calling ->item on $simplexmlelt->channel + * which could be null if the file is incorrect or if it hasn't been loaded correctly. + * + * @param SimpleXMLElement $simplexmlelt the source. + * @return array the array containing all the feed's items. + */ + private static function create_items(SimpleXMLElement $simplexmlelt): array { + $items = []; + foreach ($simplexmlelt->channel->item as $itemxml) { + $items[] = self::create_item($itemxml); + } + return $items; + } + + /** + * Creates an item from an {@see SimpleXMLElement}. + *

+ * Please make sure the {@see SimpleXMLElement} contains and only contains one item.
+ * (ex : cf rss_thumbnails\tests\fixtures\sample-item.xml) + *

+ * First of all the function will start searching for an 'image' tag. + * If it doesn't exist, it'll search for a 'media' namespace hoping to find an image url.
+ * If it still cannot find it, the function will lay {@see item::$imageurl} to null so the feed creator will be able to replace + * it with the feed's image. + * + * @param SimpleXMLElement $xmlitem The source of the item to be created. + * @return item The item created. + * @throws moodle_exception See the use of {@see moodle_url} + */ + public static function create_item(SimpleXMLElement $xmlitem) { + $categories = []; + foreach ($xmlitem->category as $category) { + $categories[] = $category; + } + + $namespaces = $xmlitem->getNamespaces(true); + + if ($xmlitem->image) { + $imageurl = new moodle_url($xmlitem->image); + } else if (array_key_exists("media", $namespaces)) { + $medianode = $xmlitem->children($namespaces["media"]); + $imageurl = new moodle_url($medianode->attributes()["url"]); + } else { + $imageurl = new moodle_url(""); + } + + $id = $xmlitem->guid ?? $xmlitem->title; + $link = new moodle_url($xmlitem->link); + $description = $xmlitem->description; + $timestamp = strtotime($xmlitem->pubDate); + $title = $xmlitem->title; + + return new item($id, $link, $description, $timestamp, true, $title, $imageurl, $categories); + } + + /** + * Replaces HTML entities that could be in the xml file so SimpleXML will be able to interpret every entity. + * + * @param string $xmlfile the source file content as a string. + * @return string the source file content with only XML entities. + */ + public static function normalize_xml_file(string $xmlfile): string { + return str_replace(self::HTML_ENTITIES, self::XML_ENTITIES, $xmlfile); + } +} diff --git a/classes/output/block.php b/classes/output/block.php index 227e553..415af83 100644 --- a/classes/output/block.php +++ b/classes/output/block.php @@ -18,34 +18,41 @@ * Contains class block_rss_client\output\feed * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_thumbnails\output; +use block_rss_thumbnails; +use renderable; use renderer_base; +use templatable; /** * Class to help display an RSS Feeds block * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class block extends \block_rss_client\output\block { +class block implements renderable, templatable { + + /** @var int The delay between two slides of the caroussel (in milliseconds) */ + private $carousseldelay; + + /** @var array An array of renderable feeds */ + private $feeds; - /** @var int The delay between two slides */ - protected int $carousselspeed = 0; /** - * Contruct + * Contructor * - * @param int $carousselspeed the caroussel speed of the block + * @param int $carousseldelay An integer representing the speed of the carroussel * @param array $feeds An array of renderable feeds */ - public function __construct($carousselspeed, array $feeds = array()) { - parent::__construct($feeds); - $this->carousselspeed = $carousselspeed; + public function __construct(int $carousseldelay = block_rss_thumbnails::DEFAULT_CAROUSSEL_DELAY, array $feeds = array()) { + $this->feeds = $feeds; + $this->carousseldelay = $carousseldelay; } /** @@ -55,8 +62,33 @@ public function __construct($carousselspeed, array $feeds = array()) { * @return array */ public function export_for_template(renderer_base $output): array { - $data = parent::export_for_template($output); - $data['carousselspeed'] = $this->carousselspeed; + $data = array( + 'feeds' => array(), + 'carousseldelay' => $this->carousseldelay + ); + foreach ($this->feeds as $feed) { + $data['feeds'][] = $feed->export_for_template($output); + } + return $data; } + + /** + * Adds a feed + * + * @param feed $renderablefeed + * @return void + */ + public function add_feed(feed $renderablefeed) { + $this->feeds[] = $renderablefeed; + } + + /** + * Get feeds + * + * @return array + */ + public function get_feeds(): array { + return $this->feeds; + } } diff --git a/classes/output/channel_image.php b/classes/output/channel_image.php new file mode 100644 index 0000000..a4a888f --- /dev/null +++ b/classes/output/channel_image.php @@ -0,0 +1,117 @@ +. + +/** + * Contains class block_rss_thumbnails\output\channel_image + * + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_rss_thumbnails\output; + +use moodle_url; +use renderable; +use renderer_base; +use templatable; + +/** + * Class to display RSS channel images + * + * @package block_rss_thumbnails + * @copyright 2022 CALL Learning + * @author Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class channel_image implements renderable, templatable { + + /** + * The URL location of the image + * + * @var string + */ + private $url; + + /** + * The title of the image + * + * @var string + */ + private $title; + + /** + * The URL of the image link + * + * @var string + */ + protected $link; + + /** + * Contructor + * + * @param moodle_url $url The URL location of the image + * @param string $title The title of the image + * @param moodle_url|null $link The URL of the image link + */ + public function __construct(moodle_url $url, string $title, ?moodle_url $link = null) { + $this->url = $url; + $this->title = $title; + $this->link = $link; + } + + /** + * Export this for use in a mustache template context. + * + * @see templatable::export_for_template() + * @param renderer_base $output + * @return array The data for the template + */ + public function export_for_template(renderer_base $output) { + return array( + 'url' => clean_param($this->url, PARAM_URL), + 'title' => $this->title, + 'link' => clean_param($this->link, PARAM_URL), + ); + } + + /** + * Get the URL + * + * @return moodle_url + */ + public function get_url() { + return $this->url; + } + + /** + * Get the title + * + * @return string + */ + public function get_title(): string { + return $this->title; + } + + /** + * Get the link + * + * @return moodle_url + */ + public function get_link() { + return $this->link; + } +} diff --git a/classes/output/feed.php b/classes/output/feed.php new file mode 100644 index 0000000..dfd9cfc --- /dev/null +++ b/classes/output/feed.php @@ -0,0 +1,149 @@ +. + +/** + * Contains class {@see block_rss_client\output\feed}. + * + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_rss_thumbnails\output; + +use moodle_url; +use renderable; +use renderer_base; +use templatable; + +/** + * Class to represent an RSS feed. + * + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class feed implements renderable, templatable { + + /** @var string|null The feed's title. */ + private $title; + + /** @var array An array of renderable feed items. */ + private $items = array(); + + /** @var channel_image|null The channel image. */ + private $image; + + /** @var boolean Whether to show the title. */ + private $showtitle; + + /** @var boolean Whether to show the channel image. */ + private $showimage; + + /** @var moodle_url The URL of the feed's link. */ + private $link; + + /** + * Contructor. + * + * @param string|null $title The title of the RSS feed. + * @param moodle_url $link The link of the RSS feed. + * @param channel_image|null $image The image of the RSS feed. + * @param boolean $showtitle Whether to show the title. + * @param boolean $showimage Whether to show the channel image. + */ + public function __construct( + $title, + $link, + $image = null, + $showtitle = true, + $showimage = true + ) { + $this->title = $title; + $this->link = $link; + $this->image = $image; + $this->showtitle = $showtitle; + $this->showimage = $showimage; + } + + /** + * Adds an item into the feed. + * + * @param item $item The item to add. + * @return void + */ + public function add_item(item $item) { + $this->items[] = $item; + } + + /** + * Gets the feed items. + * + * @return array + */ + public function get_items() { + return $this->items; + } + + /** + * Gets the feed's image. + * + * @return channel_image + */ + public function get_image() { + return $this->image; + } + + /** + * Gets the link of the feed. + * + * @return moodle_url + */ + public function get_link() { + return $this->link; + } + + /** + * Gets the title of the feed. + * + * @return string|null + */ + public function get_title() { + return $this->title; + } + + /** + * Export this for use in a mustache template context. + * + * @param renderer_base $output + * @return array + * @see templatable::export_for_template() + */ + public function export_for_template(renderer_base $output) { + $data = array( + 'title' => $this->showtitle ? $this->title : '', + 'items' => array(), + ); + + $data['image'] = ($this->showimage && $this->image) ? $this->image->export_for_template($output) : null; + + foreach ($this->items as $item) { + $data['items'][] = $item->export_for_template($output); + } + + return $data; + } +} diff --git a/classes/output/footer.php b/classes/output/footer.php new file mode 100644 index 0000000..4fa27b1 --- /dev/null +++ b/classes/output/footer.php @@ -0,0 +1,110 @@ +. + +/** + * Contains class block_rss_thumbnails\output\footer + * + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_rss_thumbnails\output; + +use moodle_url; +use renderable; +use renderer_base; +use stdClass; +use templatable; + +/** + * Class to display an RSS Block Thumbnails footer + * + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class footer implements renderable, templatable { + + /** + * The link provided in the RSS channel + * + * @var moodle_url|null + */ + private $channelurl; + + /** + * Link to manage feeds, only provided if a feed has failed. + * + * @var moodle_url|null + */ + private $manageurl = null; + + /** + * Constructor + * + * @param moodle_url|null $channelurl (optional) The link provided in the RSS channel + */ + public function __construct(moodle_url $channelurl = null) { + $this->channelurl = $channelurl; + } + + /** + * Get the channel url + * + * @return moodle_url + */ + public function get_channelurl() { + return $this->channelurl; + } + + /** + * Gets the manage url + * + * @return moodle_url|null + */ + public function get_manageurl() { + return $this->manageurl; + } + + /** + * Record the fact that there is at least one failed feed (and the URL for viewing + * these failed feeds). + * + * @param moodle_url $manageurl the URL to link to for more information + */ + public function set_failed(moodle_url $manageurl) { + $this->manageurl = $manageurl; + } + + /** + * Export context for use in mustache templates + * + * @see templatable::export_for_template() + * @param renderer_base $output + * @return stdClass + */ + 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); + } + + return $data; + } +} diff --git a/classes/output/item.php b/classes/output/item.php index 53806f0..ce4ea8a 100644 --- a/classes/output/item.php +++ b/classes/output/item.php @@ -18,66 +18,95 @@ * Contains class block_rss_client\output\feed * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_thumbnails\output; use core_text; use moodle_url; +use renderable; use renderer_base; +use templatable; /** * Class to help display an RSS Item * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class item extends \block_rss_client\output\item { - /** @var ?string The url of the RSS item's image */ - protected $imageurl = null; +class item implements renderable, templatable { - /** @var ?array The categories of the RSS item*/ - protected $categories = null; + /** @var string The ID of the item. */ + private $id; + + /** @var moodle_url|null The link of the item. */ + private $link; + + /** @var string|null The title of the item. */ + private $title; + + /** @var string The description of the item. */ + private $description; + + /** @var int|null The timestamp of the item. */ + private $timestamp; + + /** @var bool $showdescription Decides wether to show the description of the item. */ + private $showdescription; + + /** @var moodle_url $imageurl The url to the item's image. */ + public $imageurl; + + /** @var array $categories The categories of the item. */ + private $categories = array(); /** * Contructor * - * @param string $id The id of the RSS item - * @param moodle_url $link The URL of the RSS item - * @param string $title The title pf the RSS item - * @param string $description The description of the RSS item - * @param moodle_url $permalink The permalink of the RSS item - * @param int $timestamp The Unix timestamp that represents the published date - * @param boolean $showdescription Whether to show the description - * @param string $imageurl the image's url of the item - * @param array $categories the categories of the item + * @param string $id The id of the RSS item. + * @param moodle_url $link The URL of the RSS item. + * @param string $description The description of the RSS item. + * @param int $timestamp The Unix timestamp that represents the published date. + * @param boolean $showdescription Whether to show the description. + * @param string $title The title pf the RSS item. + * @param moodle_url $imageurl The URL of the item's image. + * @param array $categories The Categories of the item. */ - public function __construct($id, moodle_url $link, - $title, $description, - moodle_url $permalink, + public function __construct( + $id, + $link, + $description, $timestamp, - $showdescription = true, - $imageurl = null, + $showdescription, + $title, + $imageurl, $categories ) { - parent::__construct($id, $link, $title, $description, $permalink, $timestamp, $showdescription); + $this->id = $id; + $this->link = $link; + $this->title = self::format_title($title); + $this->description = $description; + $this->timestamp = $timestamp; + $this->showdescription = $showdescription; $this->imageurl = $imageurl; $this->categories = $categories; } /** - * Export context for use in mustache templates + * Export context for use in mustache templates. * * @param renderer_base $output * @return array * @see templatable::export_for_template() */ public function export_for_template(renderer_base $output) : array { + + // TODO find a way to add an image if the item's image url is empty. + $data = array( 'id' => $this->id, - 'permalink' => clean_param($this->permalink, PARAM_URL), 'timestamp' => $this->timestamp, 'link' => clean_param($this->link, PARAM_URL), 'imageurl' => (new moodle_url($this->imageurl))->out(), @@ -85,16 +114,99 @@ public function export_for_template(renderer_base $output) : array { ); // If the item does not have a title, create one from the description. - $title = $this->title; - if (!$title) { - $title = strip_tags($this->description); - $title = core_text::substr($title, 0, 20) . '...'; + if (!$this->title) { + $this->title = strip_tags($this->description); + $this->title = core_text::substr($this->title, 0, 20) . '...'; + } + + // Allow the renderer to format the title and description if it extends rss_thumbnails\output\renderer. + if ($output instanceof renderer) { + $title = strip_tags($output->format_item_title($this->title)); + $description = $output->format_item_description($this->description); + } else { + $title = $this->title; + $description = $this->description; } - // Allow the renderer to format the title and description. - $data['title'] = strip_tags($output->format_title($title)); - $data['description'] = $this->showdescription ? $output->format_description($this->description) : null; + $data['title'] = $title; + $data['description'] = $this->showdescription ? $description : ''; return $data; } + + /** + * Strips a large title to size and adds ... if title too long + * This function does not escape HTML entities, so they have to be escaped + * before being passed here. + * + * @param string $title title to shorten. + * @param int $max max character length of title. + * @return string title shortened if necessary. + */ + public static function format_title($title, $max = 64) { + return (core_text::strlen($title) <= $max) ? $title : core_text::substr($title, 0, $max - 3) . '...'; + } + + /** + * Gets the link of the item. + * + * @return moodle_url|null the link of the item. + */ + public function get_link(): ?moodle_url { + return $this->link; + } + + /** + * Gets the categories of the item. + * + * @return array the categories of the item. + */ + public function get_categories(): array { + return $this->categories; + } + + /** + * Gets the description of the item. + * + * @return string the description of the item. + */ + public function get_description(): string { + return $this->description; + } + + /** + * Gets the id of the item. + * + * @return string the id of the item. + */ + public function get_id(): string { + return $this->id; + } + + /** + * Gets the imageurl of the item. + * + * @return string|null the image url of the item. + */ + public function get_imageurl(): ?string { + return $this->imageurl; + } + + /** + * Gets the timestamp of the item. + * + * @return int|null the timestamp of the item. + */ + public function get_timestamp(): ?int { + return $this->timestamp; + } + + /** + * Gets the title of the item. + * + * @return string|null the title of the item. + */ + public function get_title(): ?string { + return $this->title; + } } diff --git a/classes/output/renderer.php b/classes/output/renderer.php index fa78cea..cde83da 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -18,32 +18,42 @@ * Contains class block_rss_client\output\block_renderer_html * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace block_rss_thumbnails\output; - -use templatable; +use moodle_page; +use plugin_renderer_base; /** - * Renderer for RSS Client block + * Renderer for RSS Thumbnails block, any renderer for an RSS Thumbnails Block should extend this class. * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class renderer extends \block_rss_client\output\renderer { +class renderer extends plugin_renderer_base { + /** - * Render an RSS feeds block + * Format an RSS thumbnails item title * - * @param templatable $block - * @return string|boolean + * @param string $title + * @return string */ - public function render_block(templatable $block) { - $data = $block->export_for_template($this); + public function format_item_title($title): string { + return break_up_long_words($title, 30); + } - return $this->render_from_template('block_rss_thumbnails/block', $data); + /** + * Format an RSS thumbnails item description + * + * @param string $descritpion + * @return string + */ + public function format_item_description($descritpion): string { + return $descritpion; } } + diff --git a/db/access.php b/db/access.php index 56aa048..57fa511 100644 --- a/db/access.php +++ b/db/access.php @@ -15,37 +15,57 @@ // along with Moodle. If not, see . /** - * Vetagronews block caps. + * RSS_thumbnails block caps. * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -$capabilities = array( +$capabilities = [ - 'block/rss_thumbnails:myaddinstance' => array( + 'block/rss_thumbnails:myaddinstance' => [ 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, - 'archetypes' => array( + 'archetypes' => [ 'user' => CAP_ALLOW - ), + ], 'clonepermissionsfrom' => 'moodle/my:manageblocks' - ), + ], - 'block/rss_thumbnails:addinstance' => array( + 'block/rss_thumbnails:addinstance' => [ 'riskbitmask' => RISK_SPAM | RISK_XSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_BLOCK, + 'archetypes' => [ + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ], + 'clonepermissionsfrom' => 'moodle/site:manageblocks' + ], + 'block/rss_thumbnails:manageownfeeds' => [ 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, - 'archetypes' => array( + 'archetypes' => [ + 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ), + ] + ], - 'clonepermissionsfrom' => 'moodle/site:manageblocks' - ), -); + 'block/rss_thumbnails:manageanyfeeds' => [ + + 'riskbitmask' => RISK_SPAM, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_BLOCK, + 'archetypes' => [ + 'manager' => CAP_ALLOW + ] + ] + +]; diff --git a/db/install.xml b/db/install.xml new file mode 100644 index 0000000..698ca92 --- /dev/null +++ b/db/install.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + +
+
+
diff --git a/db/upgrade.php b/db/upgrade.php new file mode 100644 index 0000000..796bb0a --- /dev/null +++ b/db/upgrade.php @@ -0,0 +1,69 @@ +. + +/** + * Upgrade + * + * @package block_rss_thumbnails + * @copyright 2020 - CALL Learning - Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Upgrade the block_rss_client database. + * + * @param int $oldversion The version number of the plugin that was installed. + * @return boolean + */ +function xmldb_block_rss_thumbnails_upgrade($oldversion) { + global $DB; + + // Automatically generated Moodle v3.9.0 release upgrade line. + // Put any upgrade step following this. + $dbman = $DB->get_manager(); + if ($oldversion < 2022091502) { + + // Define table block_rss_thumbnails to be created. + $table = new xmldb_table('block_rss_thumbnails'); + + // Adding fields to table block_rss_thumbnails. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('title', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null); + $table->add_field('preferredtitle', XMLDB_TYPE_CHAR, '64', null, XMLDB_NOTNULL, null, null); + $table->add_field('description', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null); + $table->add_field('shared', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('url', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('skiptime', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('skipuntil', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + + // Adding keys to table block_rss_thumbnails. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Conditionally launch create table for block_rss_thumbnails. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Rss_thumbnails savepoint reached. + upgrade_block_savepoint(true, 2022091502, 'rss_thumbnails'); + } + + // Automatically generated Moodle v4.0.0 release upgrade line. + // Put any upgrade step following this. + + return true; +} diff --git a/edit_form.php b/edit_form.php index dcbadf0..7929fd8 100644 --- a/edit_form.php +++ b/edit_form.php @@ -18,22 +18,25 @@ * Edit Form * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use block_rss_thumbnails\output\block; + defined('MOODLE_INTERNAL') || die(); global $CFG; -require_once($CFG->dirroot.'/blocks/rss_client/edit_form.php'); +require_once($CFG->dirroot.'/blocks/rss_thumbnails/edit_form.php'); /** * Class block_rss_thumbnails_edit_form * - * @copyright 2020 - CALL Learning - Laurent David + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class block_rss_thumbnails_edit_form extends block_rss_client_edit_form { +class block_rss_thumbnails_edit_form extends block_edit_form { /** * Creates a form to define caroussel's parameteters * @@ -42,15 +45,107 @@ class block_rss_thumbnails_edit_form extends block_rss_client_edit_form { * @return void */ protected function specific_definition($mform) { - parent::specific_definition($mform); - $mform->removeElement('config_block_rss_client_show_channel_image'); - $mform->removeElement('config_block_rss_client_show_channel_link'); + global $CFG, $DB, $USER, $SESSION; + + // Fields for editing block contents. + $mform->addElement('header', 'configheader', get_string('blocksettings', 'block')); + + $mform->addElement( + 'selectyesno', + 'config_display_description', + get_string('displaydescriptionlabel', 'block_rss_thumbnails') + ); + $mform->setDefault('config_display_description', 0); + + $mform->addElement( + 'text', + 'config_numentries', + get_string('numentrieslabel', 'block_rss_thumbnails'), + array('size' => block_rss_thumbnails::DEFAULT_MAX_ENTRIES) + ); + $mform->setType('config_numentries', PARAM_INT); + $mform->addRule('config_numentries', null, 'numeric', null, 'client'); + if (!empty($CFG->block_rss_thumbnails_num_entries)) { + $mform->setDefault('config_numentries', $CFG->block_rss_thumbnails_num_entries); + } else { + $mform->setDefault('config_numentries', 5); + } + + $insql = ''; + $params = array('userid' => $USER->id); + if (!empty($this->block->config) && !empty($this->block->config->rssid)) { + list($insql, $inparams) = $DB->get_in_or_equal($this->block->config->rssid, SQL_PARAMS_NAMED); + $insql = "OR id $insql "; + $params += $inparams; + } + + $titlesql = "CASE WHEN {$DB->sql_isempty('block_rss_thumbnails','preferredtitle', false, false)} + THEN {$DB->sql_compare_text('title', 64)} ELSE preferredtitle END"; + + $rssfeeds = $DB->get_records_sql_menu(" + SELECT id, $titlesql + FROM {block_rss_thumbnails} + WHERE userid = :userid OR shared = 1 $insql + ORDER BY $titlesql", + $params); - $mform->addElement('text', 'config_carousselspeed', get_string('carousselspeed', 'block_rss_thumbnails')); - $mform->setDefault('config_carousselspeed', block_rss_thumbnails::DEFAULT_CAROUSSEL_SPEED); - $mform->setType('config_carousselspeed', PARAM_INT); + if ($rssfeeds) { + $select = $mform->addElement( + 'select', + 'config_rssid', + get_string('choosefeedlabel', 'block_rss_thumbnails'), + $rssfeeds + ); + $select->setMultiple(true); + } else { + $mform->addElement('static', 'config_rssid_no_feeds', get_string('choosefeedlabel', 'block_rss_thumbnails'), + get_string('nofeeds', 'block_rss_thumbnails')); + } - $mform->addElement('selectyesno', 'config_show_channel_link', get_string('clientshowchannellinklabel', 'block_rss_client')); + if (has_any_capability( + array('block/rss_thumbnails:manageanyfeeds', 'block/rss_thumbnails:manageownfeeds'), + $this->block->context) + ) { + $mform->addElement('static', 'nofeedmessage', '', + '' . + get_string('feedsaddedit', 'block_rss_thumbnails') . + ''); + } + $mform->addElement('text', 'config_title', get_string('uploadlabel')); + $mform->setType('config_title', PARAM_NOTAGS); + $mform->setDefault('config_title', block_rss_thumbnails::DEFAULT_TITLE); + + $mform->addElement('selectyesno', 'config_block_rss_thumbnails_show_channel_link', get_string( + 'clientshowchannellinklabel', + 'block_rss_thumbnails') + ); + $mform->setDefault('config_block_rss_thumbnails_show_channel_link', 0); + + $mform->addElement( + 'selectyesno', + 'config_block_rss_thumbnails_show_channel_image', + get_string('clientshowimagelabel', 'block_rss_thumbnails') + ); + $mform->setDefault('config_block_rss_thumbnails_show_channel_image', 0); + $mform->removeElement('config_block_rss_thumbnails_show_channel_image'); + $mform->removeElement('config_block_rss_thumbnails_show_channel_link'); + + $mform->addElement( + 'text', + 'config_carousseldelay', + get_string('carousseldelay', 'block_rss_thumbnails') + ); + $mform->setDefault('config_carousseldelay', block_rss_thumbnails::DEFAULT_CAROUSSEL_DELAY); + $mform->setType('config_carousseldelay', PARAM_INT); + + $mform->addElement( + 'selectyesno', + 'config_show_channel_link', + get_string('clientshowchannellinklabel', 'block_rss_thumbnails') + ); $mform->setDefault('config_show_channel_link', false); $mform->setType('config_show_channel_link', PARAM_BOOL); diff --git a/editfeed.php b/editfeed.php new file mode 100644 index 0000000..0238583 --- /dev/null +++ b/editfeed.php @@ -0,0 +1,279 @@ +. + +/** + * Script to let a user edit the properties of a particular RSS feed. + * + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +require_once(__DIR__ . '/../../config.php'); +require_login(); +require_once($CFG->libdir . '/formslib.php'); +require_once($CFG->libdir .'/simplepie/moodle_simplepie.php'); + +/** + * A class to be able to edit the feeds we import in the plugin. + * + * @package block_rss_thumbnails + * @copyright 2022 - CALL Learning - Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class feed_edit_form extends moodleform { + + // TODO review this file because a lot of things are just none sense. + + /** @var bool $isadding checks whether the user is adding a new feed or not. */ + protected $isadding; + + /** @var bool $caneditshared checks whether the user has the capability to edit a shared feed or not. */ + protected $caneditshared; + + /** @var string $title The title */ + protected $title = ''; + + /** @var string $description The description */ + protected $description = ''; + + /** + * Constructor. + * + * @param string $actionurl the url of the action. + * @param bool $isadding to know if the user is adding a new feed or not. + * @param bool $caneditshared to know if the user has the ability to edit a shared feed or not. + */ + public function __construct($actionurl, $isadding, $caneditshared) { + $this->isadding = $isadding; + $this->caneditshared = $caneditshared; + parent::__construct($actionurl); + } + + /** + * Defines the form allowing to edit a feed. + * + * @return void + */ + public function definition() { + $mform =& $this->_form; + + // Then show the fields about where this block appears. + $mform->addElement('header', 'rsseditfeedheader', get_string('feed', 'block_rss_thumbnails')); + + $mform->addElement('text', 'url', get_string('feedurl', 'block_rss_thumbnails'), array('size' => 60)); + $mform->setType('url', PARAM_URL); + $mform->addRule('url', null, 'required'); + + $mform->addElement('checkbox', 'autodiscovery', get_string('enableautodiscovery', 'block_rss_thumbnails')); + $mform->setDefault('autodiscovery', 1); + $mform->setAdvanced('autodiscovery'); + $mform->addHelpButton('autodiscovery', 'enableautodiscovery', 'block_rss_thumbnails'); + + $mform->addElement('text', 'preferredtitle', get_string('customtitlelabel', 'block_rss_thumbnails'), array('size' => 60)); + $mform->setType('preferredtitle', PARAM_NOTAGS); + + if ($this->caneditshared) { + $mform->addElement('selectyesno', 'shared', get_string('sharedfeed', 'block_rss_thumbnails')); + $mform->setDefault('shared', 0); + } + + $submitlabal = null; // Default. + if ($this->isadding) { + $submitlabal = get_string('addnewfeed', 'block_rss_thumbnails'); + } + $this->add_action_buttons(true, $submitlabal); + } + + /** + * Defines the form after the discovery of data + * + * @return void + */ + public function definition_after_data() { + $mform =& $this->_form; + + if ($mform->getElementValue('autodiscovery')) { + $mform->applyFilter('url', 'feed_edit_form::autodiscover_feed_url'); + } + } + + /** + * Validates the edition form. + * + * @param array $data Datas of the form. + * @param array $files Files of the form. + * @return array + */ + public function validation($data, $files): array { + $errors = parent::validation($data, $files); + + $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['url']); + $rss->set_autodiscovery_cache_duration(0); + $rss->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE); + $rss->init(); + + if ($rss->error()) { + $errors['url'] = get_string('couldnotfindloadrssfeed', 'block_rss_thumbnails'); + } else { + $this->title = $rss->get_title(); + $this->description = $rss->get_description(); + } + + return $errors; + } + + /** + * Gets data of the form. + * + * @return object|null + */ + public function get_data(): ?object { + $data = parent::get_data(); + if ($data) { + $data->title = ''; + $data->description = ''; + + if ($this->title) { + $data->title = $this->title; + } + + if ($this->description) { + $data->description = $this->description; + } + } + return $data; + } + + /** + * Autodiscovers a feed url from a given url, to be used by the formslibs + * filter function + * + * Uses simplepie with autodiscovery set to maximum level to try and find + * a feed to subscribe to. + * See: http://simplepie.org/wiki/reference/simplepie/set_autodiscovery_level + * + * @param string $url URL to autodiscover a url + * @return string URL of feed or original url if none found + */ + public static function autodiscover_feed_url($url) { + $rss = new moodle_simplepie(); + $rss->set_feed_url($url); + $rss->set_autodiscovery_level(SIMPLEPIE_LOCATOR_ALL); + // When autodiscovering an RSS feed, simplepie will try lots of + // rss links on a page, so set the timeout high. + $rss->set_timeout(20); + $rss->init(); + + if ($rss->error()) { + return $url; + } + + // Return URL without quoting.. + $discoveredurl = new moodle_url($rss->subscribe_url()); + return $discoveredurl->out(false); + } +} + +$returnurl = optional_param('returnurl', '', PARAM_LOCALURL); +$courseid = optional_param('courseid', 0, PARAM_INT); +$rssid = optional_param('rssid', 0, PARAM_INT); // 0 mean create new. + +if ($courseid == SITEID) { + $courseid = 0; +} +if ($courseid) { + $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + $PAGE->set_course($course); + $context = $PAGE->context; +} else { + $context = context_system::instance(); + $PAGE->set_context($context); +} + +$managesharedfeeds = has_capability('block/rss_thumbnails:manageanyfeeds', $context); +if (!$managesharedfeeds) { + require_capability('block/rss_thumbnails:manageownfeeds', $context); +} + +$urlparams = array('rssid' => $rssid); +if ($courseid) { + $urlparams['courseid'] = $courseid; +} +if ($returnurl) { + $urlparams['returnurl'] = $returnurl; +} +$managefeeds = new moodle_url('/blocks/rss_thumbnails/managefeeds.php', $urlparams); + +$PAGE->set_url('/blocks/rss_thumbnails/editfeed.php', $urlparams); +$PAGE->set_pagelayout('admin'); + +if ($rssid) { + $isadding = false; + $rssrecord = $DB->get_record('block_rss_thumbnails', array('id' => $rssid), '*', MUST_EXIST); +} else { + $isadding = true; + $rssrecord = new stdClass; +} + +$mform = new feed_edit_form($PAGE->url, $isadding, $managesharedfeeds); +$mform->set_data($rssrecord); + +if ($mform->is_cancelled()) { + redirect($managefeeds); + +} else if ($data = $mform->get_data()) { + $data->userid = $USER->id; + if (!$managesharedfeeds) { + $data->shared = 0; + } + + if ($isadding) { + $DB->insert_record('block_rss_thumbnails', $data); + } else { + $data->id = $rssid; + $DB->update_record('block_rss_thumbnails', $data); + } + + redirect($managefeeds); + +} else { + if ($isadding) { + $strtitle = get_string('addnewfeed', 'block_rss_thumbnails'); + } else { + $strtitle = get_string('editafeed', 'block_rss_thumbnails'); + } + + $PAGE->set_title($strtitle); + $PAGE->set_heading($strtitle); + + $PAGE->navbar->add(get_string('blocks')); + $PAGE->navbar->add(get_string('pluginname', 'block_rss_thumbnails')); + $PAGE->navbar->add(get_string('managefeeds', 'block_rss_thumbnails'), $managefeeds ); + $PAGE->navbar->add($strtitle); + + echo $OUTPUT->header(); + echo $OUTPUT->heading($strtitle, 2); + + $mform->display(); + + echo $OUTPUT->footer(); +} + diff --git a/lang/en/block_rss_thumbnails.php b/lang/en/block_rss_thumbnails.php index c5ce2d6..988f09a 100644 --- a/lang/en/block_rss_thumbnails.php +++ b/lang/en/block_rss_thumbnails.php @@ -18,13 +18,32 @@ * Strings for component 'block_rss_thumbnails', language 'en' * * @package block_rss_thumbnails - * @copyright 2020 - CALL Learning - Laurent David + * @copyright 202 - CALL Learning - Martin CORNU-MANSUY * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['rss_thumbnails:addinstance'] = 'Add a RSS Thumnail block'; $string['rss_thumbnails:myaddinstance'] = 'Add a RSS Thumnail block to my moodle'; -$string['pluginname'] = 'RSS Thumnail'; -$string['title'] = 'RSS Thumnail'; -$string['carousselspeed'] = 'Caroussel speed'; +$string['pluginname'] = 'RSS Thumbnail'; +$string['title'] = 'RSS Thumbnails'; +$string['carousseldelay'] = 'Caroussel delay (ms)'; $string['removeimagesizesuffix'] = 'Remove image size suffix (wordpress RSS).'; +$string['displaydescriptionlabel'] = 'Display each link\'s description?'; +$string['numentrieslabel'] = 'Max number entries to show per block.'; +$string['choosefeedlabel'] = 'Choose the feeds which you would like to make available in this block:'; +$string['nofeeds'] = 'There are no RSS feeds defined for this site.'; +$string['feedsaddedit'] = 'Add/edit feeds'; +$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['managefeeds'] = 'Manage feeds'; +$string['addnewfeed'] = 'Add a new feed'; +$string['feedurl'] = 'URL of the feed'; +$string['feed'] = 'Feed'; +$string['customtitlelabel'] = 'Enter feed\'s title'; +$string['customtitlelabel'] = 'Enter feed\'s title'; +$string['sharedfeed'] = 'Is it a shared feed ?'; +$string['clientshowimagelabel'] = 'Should the image label be shown ?'; +$string['deletefeedconfirm'] = 'Are you sure you want to delete this feed ?'; +$string['enableautodiscovery'] = 'Enable auto discovery'; +$string['enableautodiscovery_help'] = 'By checking this, the RSS feed will reload automatically'; // TODO make sure it is correct. +$string['feeddeleted'] = 'The feed has been successfully deleted'; +$string['configureblock'] = "Click the edit icon above to configure this block to display RSS thumbnails ."; diff --git a/managefeeds.php b/managefeeds.php new file mode 100644 index 0000000..7eb9061 --- /dev/null +++ b/managefeeds.php @@ -0,0 +1,159 @@ +. + +/** + * Script to let a user manage their RSS feeds. + * + * @package block_rss_thumbnails + * @copyright 2022 Martin CORNU-MANSUY + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../../config.php'); +require_once($CFG->libdir . '/tablelib.php'); + +require_login(); + +// TODO check why we never get the returnurl. + +$returnurl = optional_param('returnurl', '', PARAM_LOCALURL); +$courseid = optional_param('courseid', 0, PARAM_INT); +$deleterssid = optional_param('deleterssid', 0, PARAM_INT); + +if ($courseid == SITEID) { + $courseid = 0; +} +if ($courseid) { + $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + $PAGE->set_course($course); + $context = $PAGE->context; +} else { + $context = context_system::instance(); + $PAGE->set_context($context); +} + +$managesharedfeeds = has_capability('block/rss_thumbnails:manageanyfeeds', $context); +if (!$managesharedfeeds) { + require_capability('block/rss_thumbnails:manageownfeeds', $context); +} + +$urlparams = array(); +$extraparams = ''; +if ($courseid) { + $urlparams['courseid'] = $courseid; + $extraparams = '&courseid=' . $courseid; +} +if ($returnurl) { + $urlparams['returnurl'] = $returnurl; + $extraparams = '&returnurl=' . $returnurl; +} +$baseurl = new moodle_url('/blocks/rss_thumbnails/managefeeds.php', $urlparams); +$PAGE->set_url($baseurl); + +// Process any actions. +if ($deleterssid && confirm_sesskey()) { + $DB->delete_records('block_rss_thumbnails', array('id' => $deleterssid)); + + redirect($PAGE->url, get_string('feeddeleted', 'block_rss_thumbnails')); +} + +// Display the list of feeds. +if ($managesharedfeeds) { + $select = '(userid = ' . $USER->id . ' OR shared = 1)'; +} else { + $select = 'userid = ' . $USER->id; +} +$feeds = $DB->get_records_select('block_rss_thumbnails', $select, null, $DB->sql_order_by_text('title')); + +$strmanage = get_string('managefeeds', 'block_rss_thumbnails'); + +$PAGE->set_pagelayout('standard'); +$PAGE->set_title($strmanage); +$PAGE->set_heading($strmanage); + +$managefeeds = new moodle_url('/blocks/rss_thumbnails/managefeeds.php', $urlparams); +$PAGE->navbar->add(get_string('blocks')); +$PAGE->navbar->add(get_string('pluginname', 'block_rss_thumbnails')); +$PAGE->navbar->add(get_string('managefeeds', 'block_rss_thumbnails'), $managefeeds); +echo $OUTPUT->header(); + +$table = new flexible_table('rss-display-feeds'); + +$table->define_columns(array('feed', 'actions')); +$table->define_headers(array(get_string('feed', 'block_rss_thumbnails'), get_string('actions', 'moodle'))); +$table->define_baseurl($baseurl); + +$table->set_attribute('cellspacing', '0'); +$table->set_attribute('id', 'rssfeeds'); +$table->set_attribute('class', 'generaltable generalbox'); +$table->column_class('feed', 'feed'); +$table->column_class('actions', 'actions'); + +$table->setup(); + +foreach ($feeds as $feed) { + if (!empty($feed->preferredtitle)) { + $feedtitle = s($feed->preferredtitle); + } else { + $feedtitle = $feed->title; + } + + $viewlink = html_writer::link( + $CFG->wwwroot .'/blocks/rss_thumbnails/viewfeed.php?rssid=' . $feed->id . $extraparams, $feedtitle + ); + + $feedinfo = '
' . $viewlink . '
' . + '
' . html_writer::link($feed->url, $feed->url) .'
' . + '
' . $feed->description . '
'; + if ($feed->skipuntil) { + $skipuntil = userdate($feed->skipuntil, get_string('strftimedatetime', 'langconfig')); + $skipmsg = get_string('failedfeed', 'block_rss_thumbnails', $skipuntil); + $notification = new \core\output\notification($skipmsg, 'error'); + $notification->set_show_closebutton(false); + $feedinfo .= $OUTPUT->render($notification); + } + + $editurl = new moodle_url('/blocks/rss_thumbnails/editfeed.php?rssid=' . $feed->id . $extraparams); + $editaction = $OUTPUT->action_icon($editurl, new pix_icon('t/edit', get_string('edit'))); + + $deleteurl = new moodle_url( + '/blocks/rss_thumbnails/managefeeds.php?deleterssid=' . $feed->id . '&sesskey=' . sesskey() . $extraparams + ); + $deleteicon = new pix_icon('t/delete', get_string('delete')); + $deleteaction = $OUTPUT->action_icon( + $deleteurl, + $deleteicon, + new confirm_action(get_string('deletefeedconfirm', 'block_rss_thumbnails')) + ); + + $feedicons = $editaction . ' ' . $deleteaction; + + $table->add_data(array($feedinfo, $feedicons)); +} + +$table->print_html(); + +$url = $CFG->wwwroot . '/blocks/rss_thumbnails/editfeed.php?' . substr($extraparams, 1); +echo '
' . + $OUTPUT->single_button($url, get_string('addnewfeed', 'block_rss_thumbnails'), 'get') . + '
'; + + +if ($returnurl) { + echo ''; +} + +echo $OUTPUT->footer(); diff --git a/styles.css b/styles.css index 550a0ea..5db4a4f 100644 --- a/styles.css +++ b/styles.css @@ -1,5 +1,5 @@ .block_rss_thumbnails .slide-title { - padding: 80px 30px 25px; + padding: 0; width: 100%; min-height: 200px; } @@ -9,4 +9,7 @@ .block_rss_thumbnails .thumbnail { min-height: 350px; -} \ No newline at end of file + display: flex; + justify-content: space-around; + flex-direction: column; +} diff --git a/templates/block.mustache b/templates/block.mustache index a9bcc35..b2edc82 100644 --- a/templates/block.mustache +++ b/templates/block.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template block_rss_client/block + @template block_rss_thumbnails/block Template which defines an RSS Feeds block @@ -30,7 +30,7 @@ Example context (json): { - "carousselspeed": 1, + "carousseldelay" : 10, "feeds": [ { "title": "News from around my living room", @@ -97,7 +97,7 @@