Merge branch 'MDL-70072-master' of git://github.com/rezaies/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         // Convert numeric categories (old YouTube API) to
55         // textual ones (new Google Data API)
56         $this->config->category = !empty($this->config->category) ? $this->category_map_old2new($this->config->category) : '0';
57     }
59     function instance_allow_multiple() {
60         return true;
61     }
63     function get_content() {
64         global $CFG;
66         //note: do NOT include files at the top of this file
67         require_once($CFG->libdir . '/filelib.php');
69         if ($this->content !== NULL) {
70             return $this->content;
71         }
73         $this->content = new stdClass();
74         $this->content->footer = '';
76         if (!$this->get_service()) {
77             $this->content->text = $this->get_error_message();
78             return $this->content;
79         }
81         $text = '';
82         if(!empty($this->config->playlist)){
83             //videos from a playlist
84             $text = $this->get_videos_by_playlist();
85         }
86         else{
87             if(!empty($this->config->category)){
88                 //videos from category with tag
89                 $text = $this->get_videos_by_tag_and_category();
90             }
91             else {
92                 //videos with tag
93                 $text = $this->get_videos_by_tag();
94             }
95         }
97         $this->content->text = $text;
99         return $this->content;
100     }
102     function get_videos_by_playlist(){
104         if (!$service = $this->get_service()) {
105             return $this->get_error_message();
106         }
108         $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS;
109         if( !empty($this->config->numberofvideos)) {
110             $numberofvideos = $this->config->numberofvideos;
111         }
113         try {
114             $response = $service->playlistItems->listPlaylistItems('id,snippet', array(
115                 'playlistId' => $this->config->playlist,
116                 'maxResults' => $numberofvideos
117             ));
118         } catch (Google_Service_Exception $e) {
119             debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
120             return $this->get_error_message(get_string('requesterror', 'block_tag_youtube'));
121         }
123         return $this->render_items($response);
124     }
126     function get_videos_by_tag(){
128         if (!$service = $this->get_service()) {
129             return $this->get_error_message();
130         }
132         $tagid = optional_param('id', 0, PARAM_INT);   // tag id - for backware compatibility
133         $tag = optional_param('tag', '', PARAM_TAG); // tag
134         $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
136         if ($tagid) {
137             $tagobject = core_tag_tag::get($tagid);
138         } else if ($tag) {
139             $tagobject = core_tag_tag::get_by_name($tc, $tag);
140         }
142         if (empty($tagobject)) {
143             return '';
144         }
146         $querytag = urlencode($tagobject->name);
148         $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS;
149         if ( !empty($this->config->numberofvideos) ) {
150             $numberofvideos = $this->config->numberofvideos;
151         }
153         try {
154             $response = $service->search->listSearch('id,snippet', array(
155                 'q' => $querytag,
156                 'type' => 'video',
157                 'maxResults' => $numberofvideos
158             ));
159         } catch (Google_Service_Exception $e) {
160             debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
161             return $this->get_error_message(get_string('requesterror', 'block_tag_youtube'));
162         }
164         return $this->render_items($response);
165     }
167     function get_videos_by_tag_and_category(){
169         if (!$service = $this->get_service()) {
170             return $this->get_error_message();
171         }
173         $tagid = optional_param('id', 0, PARAM_INT);   // tag id - for backware compatibility
174         $tag = optional_param('tag', '', PARAM_TAG); // tag
175         $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
177         if ($tagid) {
178             $tagobject = core_tag_tag::get($tagid);
179         } else if ($tag) {
180             $tagobject = core_tag_tag::get_by_name($tc, $tag);
181         }
183         if (empty($tagobject)) {
184             return '';
185         }
187         $querytag = urlencode($tagobject->name);
189         $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS;
190         if( !empty($this->config->numberofvideos)) {
191             $numberofvideos = $this->config->numberofvideos;
192         }
194         try {
195             $response = $service->search->listSearch('id,snippet', array(
196                 'q' => $querytag,
197                 'type' => 'video',
198                 'maxResults' => $numberofvideos,
199                 'videoCategoryId' => $this->config->category
200             ));
201         } catch (Google_Service_Exception $e) {
202             debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
203             return $this->get_error_message(get_string('requesterror', 'block_tag_youtube'));
204         }
206         return $this->render_items($response);
207     }
209     /**
210      * Sends a request to fetch data.
211      *
212      * @see block_tag_youtube::service
213      * @deprecated since Moodle 2.8.8, 2.9.2 and 3.0 MDL-49085 - please do not use this function any more.
214      * @param string $request
215      * @throws coding_exception
216      */
217     public function fetch_request($request) {
218         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.');
220         $c = new curl(array('cache' => true, 'module_cache'=>'tag_youtube'));
221         $c->setopt(array('CURLOPT_TIMEOUT' => 3, 'CURLOPT_CONNECTTIMEOUT' => 3));
223         $response = $c->get($request);
225         $xml = new SimpleXMLElement($response);
226         return $this->render_video_list($xml);
227     }
229     /**
230      * Renders the video list.
231      *
232      * @see block_tag_youtube::render_items
233      * @deprecated since Moodle 2.8.8, 2.9.2 and 3.0 MDL-49085 - please do not use this function any more.
234      * @param SimpleXMLElement $xml
235      * @throws coding_exception
236      */
237     function render_video_list(SimpleXMLElement $xml){
238         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.');
239     }
241     /**
242      * Returns an error message.
243      *
244      * Useful when the block is not properly set or something goes wrong.
245      *
246      * @param string $message The message to display.
247      * @return string HTML
248      */
249     protected function get_error_message($message = null) {
250         global $OUTPUT;
252         if (empty($message)) {
253             $message = get_string('apierror', 'block_tag_youtube');
254         }
255         return $OUTPUT->notification($message);
256     }
258     /**
259      * Gets the youtube service object.
260      *
261      * @return Google_Service_YouTube
262      */
263     protected function get_service() {
264         global $CFG;
266         if (!$apikey = get_config('block_tag_youtube', 'apikey')) {
267             return false;
268         }
270         // Wrapped in an if in case we call different get_videos_* multiple times.
271         if (!isset($this->service)) {
272             require_once($CFG->libdir . '/google/lib.php');
273             $client = get_google_client();
274             $client->setDeveloperKey($apikey);
275             $client->setScopes(array(Google_Service_YouTube::YOUTUBE_READONLY));
276             $this->service = new Google_Service_YouTube($client);
277         }
279         return $this->service;
280     }
282     /**
283      * Renders the list of items.
284      *
285      * @param array $videosdata
286      * @return string HTML
287      */
288     protected function render_items($videosdata) {
290         if (!$videosdata || empty($videosdata->items)) {
291             if (!empty($videosdata->error)) {
292                 debugging('Error fetching data from youtube: ' . $videosdata->error->message, DEBUG_DEVELOPER);
293             }
294             return '';
295         }
297         // If we reach that point we already know that the API key is set.
298         $service = $this->get_service();
300         $text = html_writer::start_tag('ul', array('class' => 'yt-video-entry unlist img-text'));
301         foreach ($videosdata->items as $video) {
303             // Link to the video included in the playlist if listing a playlist.
304             if (!empty($video->snippet->resourceId)) {
305                 $id = $video->snippet->resourceId->videoId;
306                 $playlist = '&list=' . $video->snippet->playlistId;
307             } else {
308                 $id = $video->id->videoId;
309                 $playlist = '';
310             }
312             $thumbnail = $video->snippet->getThumbnails()->getDefault();
313             $url = 'http://www.youtube.com/watch?v=' . $id . $playlist;
315             $videodetails = $service->videos->listVideos('id,contentDetails', array('id' => $id));
316             if ($videodetails && !empty($videodetails->items)) {
318                 // We fetch by id so we just use the first one.
319                 $details = $videodetails->items[0];
320                 $start = new DateTime('@0');
321                 $start->add(new DateInterval($details->contentDetails->duration));
322                 $seconds = $start->format('U');
323             }
325             $text .= html_writer::start_tag('li');
327             $imgattrs = array('class' => 'youtube-thumb', 'src' => $thumbnail->url, 'alt' => $video->snippet->title);
328             $thumbhtml = html_writer::empty_tag('img', $imgattrs);
329             $link = html_writer::tag('a', $thumbhtml, array('href' => $url));
330             $text .= html_writer::tag('div', $link, array('class' => 'clearfix'));
332             $text .= html_writer::tag('span', html_writer::tag('a', $video->snippet->title, array('href' => $url)));
334             if (!empty($seconds)) {
335                 $text .= html_writer::tag('div', format_time($seconds));
336             }
337             $text .= html_writer::end_tag('li');
338         }
339         $text .= html_writer::end_tag('ul');
341         return $text;
342     }
344     function get_categories() {
345         // TODO: Right now using sticky categories from
346         // http://gdata.youtube.com/schemas/2007/categories.cat
347         // This should be performed from time to time by the block insead
348         // and cached somewhere, avoiding deprecated ones and observing regions
349         return array (
350             '0' => get_string('anycategory', 'block_tag_youtube'),
351             'Film'  => get_string('filmsanimation', 'block_tag_youtube'),
352             'Autos' => get_string('autosvehicles', 'block_tag_youtube'),
353             'Music' => get_string('music', 'block_tag_youtube'),
354             'Animals'=> get_string('petsanimals', 'block_tag_youtube'),
355             'Sports' => get_string('sports', 'block_tag_youtube'),
356             'Travel' => get_string('travel', 'block_tag_youtube'),
357             'Games'  => get_string('gadgetsgames', 'block_tag_youtube'),
358             'Comedy' => get_string('comedy', 'block_tag_youtube'),
359             'People' => get_string('peopleblogs', 'block_tag_youtube'),
360             'News'   => get_string('newspolitics', 'block_tag_youtube'),
361             'Entertainment' => get_string('entertainment', 'block_tag_youtube'),
362             'Education' => get_string('education', 'block_tag_youtube'),
363             'Howto'  => get_string('howtodiy', 'block_tag_youtube'),
364             'Tech'   => get_string('scienceandtech', 'block_tag_youtube')
365         );
366     }
368     /**
369      * Provide conversion from old numeric categories available in youtube API
370      * to the new ones available in the Google API
371      *
372      * @param int $oldcat old category code
373      * @return mixed new category code or 0 (if no match found)
374      *
375      * TODO: Someday this should be applied on upgrade for all the existing
376      * block instances so we won't need the mapping any more. That would imply
377      * to implement restore handling to perform the conversion of old blocks.
378      */
379     function category_map_old2new($oldcat) {
380         $oldoptions = array (
381             0  => '0',
382             1  => 'Film',
383             2  => 'Autos',
384             23 => 'Comedy',
385             24 => 'Entertainment',
386             10 => 'Music',
387             25 => 'News',
388             22 => 'People',
389             15 => 'Animals',
390             26 => 'Howto',
391             17 => 'Sports',
392             19 => 'Travel',
393             20 => 'Games'
394         );
395         if (array_key_exists($oldcat, $oldoptions)) {
396             return $oldoptions[$oldcat];
397         } else {
398             return $oldcat;
399         }
400     }
402     /**
403      * Return the plugin config settings for external functions.
404      *
405      * @return stdClass the configs for both the block instance and plugin
406      * @since Moodle 3.8
407      */
408     public function get_config_for_external() {
409         // There is a private key, only admins can see it.
410         $pluginconfigs = get_config('block_tag_youtube');
411         if (!has_capability('moodle/site:config', context_system::instance())) {
412             unset($pluginconfigs->apikey);
413         }
414         $instanceconfigs = !empty($this->config) ? $this->config : new stdClass();
416         return (object) [
417             'instance' => $instanceconfigs,
418             'plugin' => $pluginconfigs,
419         ];
420     }