Merge branch 'MDL-70063-master-1' of git://github.com/mihailges/moodle
authorSara Arjona <sara@moodle.com>
Wed, 11 Nov 2020 15:31:21 +0000 (16:31 +0100)
committerSara Arjona <sara@moodle.com>
Wed, 11 Nov 2020 15:32:08 +0000 (16:32 +0100)
blocks/tag_youtube/block_tag_youtube.php
blocks/tag_youtube/db/upgrade.php [new file with mode: 0644]
blocks/tag_youtube/edit_form.php
blocks/tag_youtube/lang/en/block_tag_youtube.php
blocks/tag_youtube/tests/behat/configure_tag_youtube_block.feature [new file with mode: 0644]
blocks/tag_youtube/upgrade.txt
blocks/tag_youtube/version.php

index 51a33d5..c8ec5fc 100644 (file)
@@ -51,9 +51,6 @@ class block_tag_youtube extends block_base {
 
     function specialization() {
         $this->title = !empty($this->config->title) ? $this->config->title : get_string('pluginname', 'block_tag_youtube');
-        // Convert numeric categories (old YouTube API) to
-        // textual ones (new Google Data API)
-        $this->config->category = !empty($this->config->category) ? $this->category_map_old2new($this->config->category) : '0';
     }
 
     function instance_allow_multiple() {
@@ -341,64 +338,73 @@ class block_tag_youtube extends block_base {
         return $text;
     }
 
-    function get_categories() {
-        // TODO: Right now using sticky categories from
-        // http://gdata.youtube.com/schemas/2007/categories.cat
-        // This should be performed from time to time by the block insead
-        // and cached somewhere, avoiding deprecated ones and observing regions
-        return array (
-            '0' => get_string('anycategory', 'block_tag_youtube'),
-            'Film'  => get_string('filmsanimation', 'block_tag_youtube'),
-            'Autos' => get_string('autosvehicles', 'block_tag_youtube'),
-            'Music' => get_string('music', 'block_tag_youtube'),
-            'Animals'=> get_string('petsanimals', 'block_tag_youtube'),
-            'Sports' => get_string('sports', 'block_tag_youtube'),
-            'Travel' => get_string('travel', 'block_tag_youtube'),
-            'Games'  => get_string('gadgetsgames', 'block_tag_youtube'),
-            'Comedy' => get_string('comedy', 'block_tag_youtube'),
-            'People' => get_string('peopleblogs', 'block_tag_youtube'),
-            'News'   => get_string('newspolitics', 'block_tag_youtube'),
-            'Entertainment' => get_string('entertainment', 'block_tag_youtube'),
-            'Education' => get_string('education', 'block_tag_youtube'),
-            'Howto'  => get_string('howtodiy', 'block_tag_youtube'),
-            'Tech'   => get_string('scienceandtech', 'block_tag_youtube')
-        );
-    }
-
     /**
-     * Provide conversion from old numeric categories available in youtube API
-     * to the new ones available in the Google API
-     *
-     * @param int $oldcat old category code
-     * @return mixed new category code or 0 (if no match found)
+     * Method that returns an array containing all relevant video categories obtained through an API call, where the
+     * array index represents the category ID and the array value represents the category name.
      *
-     * TODO: Someday this should be applied on upgrade for all the existing
-     * block instances so we won't need the mapping any more. That would imply
-     * to implement restore handling to perform the conversion of old blocks.
+     * @return array The array containing the relevant video categories
+     * @throws moodle_exception If the API key is not set
+     * @throws Google_Service_Exception If an error occurs while obtaining the categories through the API call
      */
-    function category_map_old2new($oldcat) {
-        $oldoptions = array (
-            0  => '0',
-            1  => 'Film',
-            2  => 'Autos',
-            23 => 'Comedy',
-            24 => 'Entertainment',
-            10 => 'Music',
-            25 => 'News',
-            22 => 'People',
-            15 => 'Animals',
-            26 => 'Howto',
-            17 => 'Sports',
-            19 => 'Travel',
-            20 => 'Games'
-        );
-        if (array_key_exists($oldcat, $oldoptions)) {
-            return $oldoptions[$oldcat];
+    public function get_categories() {
+        // Get the default categories and it's translations.
+        $categorytranslations = $this->category_map_translation();
+
+        if ($service = $this->get_service()) {
+            // Call the API to fetch the youtube video categories.
+            // This API call requires the regionCode parameter which instructs the API to return the list of video
+            // categories available in the specified country. Currently 'us' is hardcoded as the returned categories
+            // for this region correspond to the previously used (legacy) hardcoded list of categories.
+            // TODO: We should improve this in the future and avoid hardcoding this value.
+            $response = $service->videoCategories->listVideoCategories('snippet', ['regionCode' => 'us']);
+            $categoryitems = $response['modelData']['items'];
+
+            // Return an array with the relevant categories.
+            return array_reduce($categoryitems, function($categories, $category) use ($categorytranslations) {
+                $categoryid = $category['id'];
+                $categoryname = $category['snippet']['title'];
+                // Videos can be associated with this category.
+                if ($category['snippet']['assignable']) {
+                    // If the category name can be mapped with a translation, add it to the categories array.
+                    if (array_key_exists($categoryname, $categorytranslations)) {
+                        $categories[$categoryid] = $categorytranslations[$categoryname];
+                    } else { // Otherwise, display the untranslated category name and show a debugging message.
+                        $categories[$categoryid] = $categoryname;
+                        debugging("The category '{$categoryname}' does not have a translatable language string.");
+                    }
+                }
+                return $categories;
+            }, []);
         } else {
-            return $oldcat;
+            throw new \moodle_exception('apierror', 'block_tag_youtube');
         }
     }
 
+    /**
+     * Method that provides mapping between the video category names and their translations.
+     *
+     * @return array The array that maps the video category names with their translations
+     */
+    private function category_map_translation() {
+        return [
+            'Film & Animation' => get_string('filmsanimation', 'block_tag_youtube'),
+            'Autos & Vehicles' => get_string('autosvehicles', 'block_tag_youtube'),
+            'Music' => get_string('music', 'block_tag_youtube'),
+            'Pets & Animals' => get_string('petsanimals', 'block_tag_youtube'),
+            'Sports' => get_string('sports', 'block_tag_youtube'),
+            'Travel & Events' => get_string('travel', 'block_tag_youtube'),
+            'Gaming' => get_string('gadgetsgames', 'block_tag_youtube'),
+            'People & Blogs' => get_string('peopleblogs', 'block_tag_youtube'),
+            'Comedy' => get_string('comedy', 'block_tag_youtube'),
+            'Entertainment' => get_string('entertainment', 'block_tag_youtube'),
+            'News & Politics' => get_string('newspolitics', 'block_tag_youtube'),
+            'Howto & Style'  => get_string('howtodiy', 'block_tag_youtube'),
+            'Education' => get_string('education', 'block_tag_youtube'),
+            'Science & Technology' => get_string('scienceandtech', 'block_tag_youtube'),
+            'Nonprofits & Activism' => get_string('nonprofitactivism', 'block_tag_youtube'),
+        ];
+    }
+
     /**
      * Return the plugin config settings for external functions.
      *
diff --git a/blocks/tag_youtube/db/upgrade.php b/blocks/tag_youtube/db/upgrade.php
new file mode 100644 (file)
index 0000000..897eb1e
--- /dev/null
@@ -0,0 +1,130 @@
+<?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 file keeps track of upgrades to the tag_youtube block
+ *
+ * @package block_tag_youtube
+ * @copyright 2020 Mihail Geshoski <mihail@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Upgrade code for the Tag Youtube block.
+ *
+ * @param int $oldversion
+ */
+function xmldb_block_tag_youtube_upgrade($oldversion) {
+    global $DB, $CFG, $OUTPUT;
+
+    // Automatically generated Moodle v3.10.0 release upgrade line.
+    // Put any upgrade step following this.
+
+    if ($oldversion < 2021052501) {
+        // We need to fix every tag_youtube block instance that has used a legacy category name as a category config.
+        // The category config needs to store the category ID instead.
+
+        // If tag_youtube block instances exist.
+        if ($blockinstances = $DB->get_records('block_instances', ['blockname' => 'tag_youtube'])) {
+            $categories = [];
+            // The block tag youtube needs to be configured and have a valid API key in order to obtain the video
+            // category list.
+            if ($apikey = get_config('block_tag_youtube', 'apikey')) {
+                require_once($CFG->libdir . '/google/lib.php');
+                $client = get_google_client();
+                $client->setDeveloperKey($apikey);
+                $client->setScopes(array(Google_Service_YouTube::YOUTUBE_READONLY));
+                $service = new Google_Service_YouTube($client);
+
+                try {
+                    // Get the video category list.
+                    $response = $service->videoCategories->listVideoCategories('snippet', ['regionCode' => 'us']);
+
+                    // Return an array of categories, where the key is the category name and the value is the
+                    // category ID.
+                    $categories = array_reduce($response['modelData']['items'], function ($categories, $category) {
+                        $categoryid = $category['id'];
+                        $categoryname = $category['snippet']['title'];
+                        // If videos can be associated with this category, add it to the categories list.
+                        if ($category['snippet']['assignable']) {
+                            $categories[$categoryname] = $categoryid;
+                        }
+                        return $categories;
+                    }, []);
+                } catch (Exception $e) {
+                    $warn = "Due to the following error the youtube video categories were not obtained through the API:
+                    '{$e->getMessage()}'. Therefore, any legacy values used as a category setting in Tag Youtube
+                    block instances cannot be properly mapped and updated. All legacy values used as category setting
+                    will still be updated and set by default to 'Any category'.";
+                    echo $OUTPUT->notification($warn, 'notifyproblem');
+                }
+            } else {
+                $warn = "The API key is missing in the Tag Youtube block configuration. Therefore, the youtube video
+                categories cannot be obtained and mapped with the legacy values used as category setting. All legacy
+                values used as category setting will still be updated and set by default to 'Any category'.";
+                echo $OUTPUT->notification($warn, 'notifyproblem');
+            }
+
+            // Array that maps the old category names to the current category names.
+            $categorynamemap = [
+                'Film' => 'Film & Animation',
+                'Autos' => 'Autos & Vehicles',
+                'Comedy' => 'Comedy',
+                'Entertainment' => 'Entertainment',
+                'Music' => 'Music',
+                'News' => 'News & Politics',
+                'People' => 'People & Blogs',
+                'Animals' => 'Pets & Animals',
+                'Howto' => 'Howto & Style',
+                'Sports' => 'Sports',
+                'Travel' => 'Travel & Events',
+                'Games' => 'Gaming',
+                'Education' => 'Education',
+                'Tech' => 'Tech'
+            ];
+
+            // If the block uses a legacy category name, update it to use the current category ID instead.
+            foreach ($blockinstances as $blockinstance) {
+                $blockconfig = unserialize(base64_decode($blockinstance->configdata));
+                $blockcategoryconfig = $blockconfig->category;
+                // The block is using a legacy category name as a category config.
+                if (array_key_exists($blockcategoryconfig, $categorynamemap)) {
+                    if (!empty($categories)) { // The categories were successfully obtained through the API call.
+                        // Get the current category name.
+                        $currentcategoryname = $categorynamemap[$blockcategoryconfig];
+                        // Add the category ID as a new category config for this block instance.
+                        $blockconfig->category = $categories[$currentcategoryname];
+                    } else { // The categories were not obtained through the API call.
+                        // If the categories were not obtained through the API call, we are not able to map the
+                        // current legacy category name with the category ID. Therefore, we should default the category
+                        // config value to 0 ('Any category') to at least enable the block to function properly. The
+                        // user can later manually select the desired category and re-save the config through the UI.
+                        $blockconfig->category = 0;
+                    }
+
+                    $blockinstance->configdata = base64_encode(serialize($blockconfig));
+                    $DB->update_record('block_instances', $blockinstance);
+                }
+            }
+        }
+
+        upgrade_block_savepoint(true, 2021052501, 'tag_youtube', false);
+    }
+
+    return true;
+}
index 92b54d9..75e5c0d 100644 (file)
@@ -30,6 +30,8 @@
  */
 class block_tag_youtube_edit_form extends block_edit_form {
     protected function specific_definition($mform) {
+        global $OUTPUT;
+
         $mform->addElement('header', 'configheader', get_string('blocksettings', 'block'));
 
         $mform->addElement('text', 'config_title', get_string('configtitle', 'block_tag_youtube'));
@@ -38,10 +40,26 @@ class block_tag_youtube_edit_form extends block_edit_form {
         $mform->addElement('text', 'config_numberofvideos', get_string('numberofvideos', 'block_tag_youtube'), array('size' => 5));
         $mform->setType('config_numberofvideos', PARAM_INT);
 
-        $categorychoices = $this->block->get_categories();
-        $mform->addElement('select', 'config_category', get_string('category', 'block_tag_youtube'), $categorychoices);
+        // Category setting.
+        $categorychoices = ['0' => get_string('anycategory', 'block_tag_youtube')];
+        $categoryerror = '';
+
+        try {
+            // Get all video categories through an API call and add them to the category list.
+            $categorychoices += $this->block->get_categories();
+        } catch (Exception $e) {
+            $categoryerror = $e->getMessage();
+        }
+        $mform->addElement('select', 'config_category', get_string('category', 'block_tag_youtube'),
+            $categorychoices);
         $mform->setDefault('config_category', 0);
 
+        if ($categoryerror) {
+            $notification = $OUTPUT->notification(get_string('categoryerror', 'block_tag_youtube', $categoryerror),
+                'error');
+            $mform->addElement('static', 'config_category_error', '', $notification);
+        }
+
         $mform->addElement('text', 'config_playlist', get_string('includeonlyvideosfromplaylist', 'block_tag_youtube'));
         $mform->setType('config_playlist', PARAM_ALPHANUM);
     }
index 7b3fd69..defa7bd 100644 (file)
@@ -32,12 +32,14 @@ $string['comedy'] = 'Comedy';
 $string['configtitle'] = 'YouTube block title';
 $string['education'] = 'Education';
 $string['entertainment'] = 'Entertainment';
+$string['categoryerror'] = 'Failed to obtain the list of categories. <br> {$a}';
 $string['filmsanimation'] = 'Films & Animation';
 $string['gadgetsgames'] = 'Gadgets & Games';
 $string['howtodiy'] = 'How-to & DIY';
 $string['includeonlyvideosfromplaylist'] = 'Include only videos from the playlist with id';
 $string['music'] = 'Music';
 $string['newspolitics'] = 'News & Politics';
+$string['nonprofitactivism'] = 'Nonprofits & Activism';
 $string['numberofvideos'] = 'Number of videos';
 $string['peopleblogs'] = 'People & Blogs';
 $string['petsanimals'] = 'Pets & Animals';
diff --git a/blocks/tag_youtube/tests/behat/configure_tag_youtube_block.feature b/blocks/tag_youtube/tests/behat/configure_tag_youtube_block.feature
new file mode 100644 (file)
index 0000000..6952b95
--- /dev/null
@@ -0,0 +1,48 @@
+@block @block_tag_youtube
+Feature: Adding and configuring YouTube block
+  In order to have the YouTube block used
+  As a admin
+  I need to add the YouTube block to the tags site page
+
+  Background:
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Manage blocks" in site administration
+    And I click on "Show" "icon" in the "YouTube" "table_row"
+
+  @javascript
+  Scenario: Category options are not available (except default) in the block settings if the YouTube API key is not set.
+    Given the following config values are set as admin:
+      | apikey |  | block_tag_youtube |
+    And I follow "Dashboard" in the user menu
+    And I press "Customise this page"
+    # TODO MDL-57120 site "Tags" link not accessible without navigation block.
+    And I add the "Navigation" block if not present
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Tags" "link" in the "Navigation" "block"
+    And I add the "YouTube" block
+    When I configure the "YouTube" block
+    Then I should see "Category"
+    And I should see "Failed to obtain the list of categories."
+    And I should see "The YouTube API key is not set. Contact your administrator."
+    And the "Category" select box should contain "Any category"
+    And the "Category" select box should not contain "Films & Animation"
+    And the "Category" select box should not contain "Entertainment"
+    And the "Category" select box should not contain "Education"
+
+  @javascript
+  Scenario: Category options are not available (except default) in the block settings when invalid YouTube API key is set.
+    Given the following config values are set as admin:
+      | apikey | invalidapikeyvalue | block_tag_youtube |
+    And I follow "Dashboard" in the user menu
+    And I press "Customise this page"
+    And I add the "Navigation" block if not present
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Tags" "link" in the "Navigation" "block"
+    And I add the "YouTube" block
+    When I configure the "YouTube" block
+    Then I should see "Category"
+    And I should see "Failed to obtain the list of categories."
+    And the "Category" select box should contain "Any category"
+    And the "Category" select box should not contain "Comedy"
+    And the "Category" select box should not contain "Autos & Vehicles"
+    And the "Category" select box should not contain "News & Politics"
index ae3d80d..b8afc46 100644 (file)
@@ -1,5 +1,11 @@
 This files describes API changes in the block tag_youtube code.
 
+=== 3.10.1 ===
+
+* The config category now stores the category ID, instead of a string representation of the category name.
+  In YouTube Data API v3, the API call to fetch the videos related to a certain category expects the category ID to be
+  passed to the videoCategoryId parameter, instead of the category name.
+
 === 3.0 ===
 
 * Due to the final YouTube API v2.0 deprecation we needed to adapt the current
index eb799bb..61ca95e 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2021052500;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2021052501;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2021052500;        // Requires this Moodle version
 $plugin->component = 'block_tag_youtube'; // Full name of the plugin (used for diagnostics)