Merge branch 'MDL-70063-master-1' of git://github.com/mihailges/moodle
[moodle.git] / blocks / tag_youtube / block_tag_youtube.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Tag youtube block
19  *
20  * @package    block_tag_youtube
21  * @copyright  1999 onwards Martin Dougiamas (http://dougiamas.com)
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 define('DEFAULT_NUMBER_OF_VIDEOS', 5);
27 class block_tag_youtube extends block_base {
29     /**
30      * @var Google_Service_Youtube
31      */
32     protected $service = null;
34     function init() {
35         $this->title = get_string('pluginname','block_tag_youtube');
36         $this->config = new stdClass();
37     }
39     function applicable_formats() {
40         return array('tag' => true);
41     }
43     /**
44      * It can be configured.
45      *
46      * @return bool
47      */
48     public function has_config() {
49         return true;
50     }
52     function specialization() {
53         $this->title = !empty($this->config->title) ? $this->config->title : get_string('pluginname', 'block_tag_youtube');
54     }
56     function instance_allow_multiple() {
57         return true;
58     }
60     function get_content() {
61         global $CFG;
63         //note: do NOT include files at the top of this file
64         require_once($CFG->libdir . '/filelib.php');
66         if ($this->content !== NULL) {
67             return $this->content;
68         }
70         $this->content = new stdClass();
71         $this->content->footer = '';
73         if (!$this->get_service()) {
74             $this->content->text = $this->get_error_message();
75             return $this->content;
76         }
78         $text = '';
79         if(!empty($this->config->playlist)){
80             //videos from a playlist
81             $text = $this->get_videos_by_playlist();
82         }
83         else{
84             if(!empty($this->config->category)){
85                 //videos from category with tag
86                 $text = $this->get_videos_by_tag_and_category();
87             }
88             else {
89                 //videos with tag
90                 $text = $this->get_videos_by_tag();
91             }
92         }
94         $this->content->text = $text;
96         return $this->content;
97     }
99     function get_videos_by_playlist(){
101         if (!$service = $this->get_service()) {
102             return $this->get_error_message();
103         }
105         $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS;
106         if( !empty($this->config->numberofvideos)) {
107             $numberofvideos = $this->config->numberofvideos;
108         }
110         try {
111             $response = $service->playlistItems->listPlaylistItems('id,snippet', array(
112                 'playlistId' => $this->config->playlist,
113                 'maxResults' => $numberofvideos
114             ));
115         } catch (Google_Service_Exception $e) {
116             debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
117             return $this->get_error_message(get_string('requesterror', 'block_tag_youtube'));
118         }
120         return $this->render_items($response);
121     }
123     function get_videos_by_tag(){
125         if (!$service = $this->get_service()) {
126             return $this->get_error_message();
127         }
129         $tagid = optional_param('id', 0, PARAM_INT);   // tag id - for backware compatibility
130         $tag = optional_param('tag', '', PARAM_TAG); // tag
131         $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
133         if ($tagid) {
134             $tagobject = core_tag_tag::get($tagid);
135         } else if ($tag) {
136             $tagobject = core_tag_tag::get_by_name($tc, $tag);
137         }
139         if (empty($tagobject)) {
140             return '';
141         }
143         $querytag = urlencode($tagobject->name);
145         $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS;
146         if ( !empty($this->config->numberofvideos) ) {
147             $numberofvideos = $this->config->numberofvideos;
148         }
150         try {
151             $response = $service->search->listSearch('id,snippet', array(
152                 'q' => $querytag,
153                 'type' => 'video',
154                 'maxResults' => $numberofvideos
155             ));
156         } catch (Google_Service_Exception $e) {
157             debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
158             return $this->get_error_message(get_string('requesterror', 'block_tag_youtube'));
159         }
161         return $this->render_items($response);
162     }
164     function get_videos_by_tag_and_category(){
166         if (!$service = $this->get_service()) {
167             return $this->get_error_message();
168         }
170         $tagid = optional_param('id', 0, PARAM_INT);   // tag id - for backware compatibility
171         $tag = optional_param('tag', '', PARAM_TAG); // tag
172         $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
174         if ($tagid) {
175             $tagobject = core_tag_tag::get($tagid);
176         } else if ($tag) {
177             $tagobject = core_tag_tag::get_by_name($tc, $tag);
178         }
180         if (empty($tagobject)) {
181             return '';
182         }
184         $querytag = urlencode($tagobject->name);
186         $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS;
187         if( !empty($this->config->numberofvideos)) {
188             $numberofvideos = $this->config->numberofvideos;
189         }
191         try {
192             $response = $service->search->listSearch('id,snippet', array(
193                 'q' => $querytag,
194                 'type' => 'video',
195                 'maxResults' => $numberofvideos,
196                 'videoCategoryId' => $this->config->category
197             ));
198         } catch (Google_Service_Exception $e) {
199             debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
200             return $this->get_error_message(get_string('requesterror', 'block_tag_youtube'));
201         }
203         return $this->render_items($response);
204     }
206     /**
207      * Sends a request to fetch data.
208      *
209      * @see block_tag_youtube::service
210      * @deprecated since Moodle 2.8.8, 2.9.2 and 3.0 MDL-49085 - please do not use this function any more.
211      * @param string $request
212      * @throws coding_exception
213      */
214     public function fetch_request($request) {
215         throw new coding_exception('Sorry, this function has been deprecated in Moodle 2.8.8, 2.9.2 and 3.0. Use block_tag_youtube::get_service instead.');
217         $c = new curl(array('cache' => true, 'module_cache'=>'tag_youtube'));
218         $c->setopt(array('CURLOPT_TIMEOUT' => 3, 'CURLOPT_CONNECTTIMEOUT' => 3));
220         $response = $c->get($request);
222         $xml = new SimpleXMLElement($response);
223         return $this->render_video_list($xml);
224     }
226     /**
227      * Renders the video list.
228      *
229      * @see block_tag_youtube::render_items
230      * @deprecated since Moodle 2.8.8, 2.9.2 and 3.0 MDL-49085 - please do not use this function any more.
231      * @param SimpleXMLElement $xml
232      * @throws coding_exception
233      */
234     function render_video_list(SimpleXMLElement $xml){
235         throw new coding_exception('Sorry, this function has been deprecated in Moodle 2.8.8, 2.9.2 and 3.0. Use block_tag_youtube::render_items instead.');
236     }
238     /**
239      * Returns an error message.
240      *
241      * Useful when the block is not properly set or something goes wrong.
242      *
243      * @param string $message The message to display.
244      * @return string HTML
245      */
246     protected function get_error_message($message = null) {
247         global $OUTPUT;
249         if (empty($message)) {
250             $message = get_string('apierror', 'block_tag_youtube');
251         }
252         return $OUTPUT->notification($message);
253     }
255     /**
256      * Gets the youtube service object.
257      *
258      * @return Google_Service_YouTube
259      */
260     protected function get_service() {
261         global $CFG;
263         if (!$apikey = get_config('block_tag_youtube', 'apikey')) {
264             return false;
265         }
267         // Wrapped in an if in case we call different get_videos_* multiple times.
268         if (!isset($this->service)) {
269             require_once($CFG->libdir . '/google/lib.php');
270             $client = get_google_client();
271             $client->setDeveloperKey($apikey);
272             $client->setScopes(array(Google_Service_YouTube::YOUTUBE_READONLY));
273             $this->service = new Google_Service_YouTube($client);
274         }
276         return $this->service;
277     }
279     /**
280      * Renders the list of items.
281      *
282      * @param array $videosdata
283      * @return string HTML
284      */
285     protected function render_items($videosdata) {
287         if (!$videosdata || empty($videosdata->items)) {
288             if (!empty($videosdata->error)) {
289                 debugging('Error fetching data from youtube: ' . $videosdata->error->message, DEBUG_DEVELOPER);
290             }
291             return '';
292         }
294         // If we reach that point we already know that the API key is set.
295         $service = $this->get_service();
297         $text = html_writer::start_tag('ul', array('class' => 'yt-video-entry unlist img-text'));
298         foreach ($videosdata->items as $video) {
300             // Link to the video included in the playlist if listing a playlist.
301             if (!empty($video->snippet->resourceId)) {
302                 $id = $video->snippet->resourceId->videoId;
303                 $playlist = '&list=' . $video->snippet->playlistId;
304             } else {
305                 $id = $video->id->videoId;
306                 $playlist = '';
307             }
309             $thumbnail = $video->snippet->getThumbnails()->getDefault();
310             $url = 'http://www.youtube.com/watch?v=' . $id . $playlist;
312             $videodetails = $service->videos->listVideos('id,contentDetails', array('id' => $id));
313             if ($videodetails && !empty($videodetails->items)) {
315                 // We fetch by id so we just use the first one.
316                 $details = $videodetails->items[0];
317                 $start = new DateTime('@0');
318                 $start->add(new DateInterval($details->contentDetails->duration));
319                 $seconds = $start->format('U');
320             }
322             $text .= html_writer::start_tag('li');
324             $imgattrs = array('class' => 'youtube-thumb', 'src' => $thumbnail->url, 'alt' => $video->snippet->title);
325             $thumbhtml = html_writer::empty_tag('img', $imgattrs);
326             $link = html_writer::tag('a', $thumbhtml, array('href' => $url));
327             $text .= html_writer::tag('div', $link, array('class' => 'clearfix'));
329             $text .= html_writer::tag('span', html_writer::tag('a', $video->snippet->title, array('href' => $url)));
331             if (!empty($seconds)) {
332                 $text .= html_writer::tag('div', format_time($seconds));
333             }
334             $text .= html_writer::end_tag('li');
335         }
336         $text .= html_writer::end_tag('ul');
338         return $text;
339     }
341     /**
342      * Method that returns an array containing all relevant video categories obtained through an API call, where the
343      * array index represents the category ID and the array value represents the category name.
344      *
345      * @return array The array containing the relevant video categories
346      * @throws moodle_exception If the API key is not set
347      * @throws Google_Service_Exception If an error occurs while obtaining the categories through the API call
348      */
349     public function get_categories() {
350         // Get the default categories and it's translations.
351         $categorytranslations = $this->category_map_translation();
353         if ($service = $this->get_service()) {
354             // Call the API to fetch the youtube video categories.
355             // This API call requires the regionCode parameter which instructs the API to return the list of video
356             // categories available in the specified country. Currently 'us' is hardcoded as the returned categories
357             // for this region correspond to the previously used (legacy) hardcoded list of categories.
358             // TODO: We should improve this in the future and avoid hardcoding this value.
359             $response = $service->videoCategories->listVideoCategories('snippet', ['regionCode' => 'us']);
360             $categoryitems = $response['modelData']['items'];
362             // Return an array with the relevant categories.
363             return array_reduce($categoryitems, function($categories, $category) use ($categorytranslations) {
364                 $categoryid = $category['id'];
365                 $categoryname = $category['snippet']['title'];
366                 // Videos can be associated with this category.
367                 if ($category['snippet']['assignable']) {
368                     // If the category name can be mapped with a translation, add it to the categories array.
369                     if (array_key_exists($categoryname, $categorytranslations)) {
370                         $categories[$categoryid] = $categorytranslations[$categoryname];
371                     } else { // Otherwise, display the untranslated category name and show a debugging message.
372                         $categories[$categoryid] = $categoryname;
373                         debugging("The category '{$categoryname}' does not have a translatable language string.");
374                     }
375                 }
376                 return $categories;
377             }, []);
378         } else {
379             throw new \moodle_exception('apierror', 'block_tag_youtube');
380         }
381     }
383     /**
384      * Method that provides mapping between the video category names and their translations.
385      *
386      * @return array The array that maps the video category names with their translations
387      */
388     private function category_map_translation() {
389         return [
390             'Film & Animation' => get_string('filmsanimation', 'block_tag_youtube'),
391             'Autos & Vehicles' => get_string('autosvehicles', 'block_tag_youtube'),
392             'Music' => get_string('music', 'block_tag_youtube'),
393             'Pets & Animals' => get_string('petsanimals', 'block_tag_youtube'),
394             'Sports' => get_string('sports', 'block_tag_youtube'),
395             'Travel & Events' => get_string('travel', 'block_tag_youtube'),
396             'Gaming' => get_string('gadgetsgames', 'block_tag_youtube'),
397             'People & Blogs' => get_string('peopleblogs', 'block_tag_youtube'),
398             'Comedy' => get_string('comedy', 'block_tag_youtube'),
399             'Entertainment' => get_string('entertainment', 'block_tag_youtube'),
400             'News & Politics' => get_string('newspolitics', 'block_tag_youtube'),
401             'Howto & Style'  => get_string('howtodiy', 'block_tag_youtube'),
402             'Education' => get_string('education', 'block_tag_youtube'),
403             'Science & Technology' => get_string('scienceandtech', 'block_tag_youtube'),
404             'Nonprofits & Activism' => get_string('nonprofitactivism', 'block_tag_youtube'),
405         ];
406     }
408     /**
409      * Return the plugin config settings for external functions.
410      *
411      * @return stdClass the configs for both the block instance and plugin
412      * @since Moodle 3.8
413      */
414     public function get_config_for_external() {
415         // There is a private key, only admins can see it.
416         $pluginconfigs = get_config('block_tag_youtube');
417         if (!has_capability('moodle/site:config', context_system::instance())) {
418             unset($pluginconfigs->apikey);
419         }
420         $instanceconfigs = !empty($this->config) ? $this->config : new stdClass();
422         return (object) [
423             'instance' => $instanceconfigs,
424             'plugin' => $pluginconfigs,
425         ];
426     }