MDL-49085 block_tag_youtube: Using Youtube data api v3
[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->dirroot.'/tag/lib.php');
68         require_once($CFG->libdir . '/filelib.php');
70         if ($this->content !== NULL) {
71             return $this->content;
72         }
74         $this->content = new stdClass();
75         $this->content->footer = '';
77         if (!$this->get_service()) {
78             $this->content->text = $this->get_error_message();
79             return $this->content;
80         }
82         $text = '';
83         if(!empty($this->config->playlist)){
84             //videos from a playlist
85             $text = $this->get_videos_by_playlist();
86         }
87         else{
88             if(!empty($this->config->category)){
89                 //videos from category with tag
90                 $text = $this->get_videos_by_tag_and_category();
91             }
92             else {
93                 //videos with tag
94                 $text = $this->get_videos_by_tag();
95             }
96         }
98         $this->content->text = $text;
100         return $this->content;
101     }
103     function get_videos_by_playlist(){
105         if (!$service = $this->get_service()) {
106             return $this->get_error_message();
107         }
109         $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS;
110         if( !empty($this->config->numberofvideos)) {
111             $numberofvideos = $this->config->numberofvideos;
112         }
114         try {
115             $response = $service->playlistItems->listPlaylistItems('id,snippet', array(
116                 'playlistId' => $this->config->playlist,
117                 'maxResults' => $numberofvideos
118             ));
119         } catch (Google_Service_Exception $e) {
120             debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
121             return $this->get_error_message(get_string('requesterror', 'block_tag_youtube'));
122         }
124         return $this->render_items($response);
125     }
127     function get_videos_by_tag(){
129         if (!$service = $this->get_service()) {
130             return $this->get_error_message();
131         }
133         $tagid = optional_param('id', 0, PARAM_INT);   // tag id - for backware compatibility
134         $tag = optional_param('tag', '', PARAM_TAG); // tag
136         if ($tag) {
137             $tagobject = tag_get('name', $tag);
138         } else if ($tagid) {
139             $tagobject = tag_get('id', $tagid);
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
176         if ($tag) {
177             $tagobject = tag_get('name', $tag);
178         } else if ($tagid) {
179             $tagobject = tag_get('id', $tagid);
180         }
182         if (empty($tagobject)) {
183             return '';
184         }
186         $querytag = urlencode($tagobject->name);
188         $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS;
189         if( !empty($this->config->numberofvideos)) {
190             $numberofvideos = $this->config->numberofvideos;
191         }
193         try {
194             $response = $service->search->listSearch('id,snippet', array(
195                 'q' => $querytag,
196                 'type' => 'video',
197                 'maxResults' => $numberofvideos,
198                 'videoCategoryId' => $this->config->category
199             ));
200         } catch (Google_Service_Exception $e) {
201             debugging('Google service exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
202             return $this->get_error_message(get_string('requesterror', 'block_tag_youtube'));
203         }
205         return $this->render_items($response);
206     }
208     /**
209      * Sends a request to fetch data.
210      *
211      * @see block_tag_youtube::service
212      * @deprecated since Moodle 2.8.8, 2.9.2 and 3.0 MDL-49085 - please do not use this function any more.
213      * @param string $request
214      * @throws coding_exception
215      */
216     public function fetch_request($request) {
217         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.');
219         $c = new curl(array('cache' => true, 'module_cache'=>'tag_youtube'));
220         $c->setopt(array('CURLOPT_TIMEOUT' => 3, 'CURLOPT_CONNECTTIMEOUT' => 3));
222         $response = $c->get($request);
224         $xml = new SimpleXMLElement($response);
225         return $this->render_video_list($xml);
226     }
228     /**
229      * Renders the video list.
230      *
231      * @see block_tag_youtube::render_items
232      * @deprecated since Moodle 2.8.8, 2.9.2 and 3.0 MDL-49085 - please do not use this function any more.
233      * @param SimpleXMLElement $xml
234      * @throws coding_exception
235      */
236     function render_video_list(SimpleXMLElement $xml){
237         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.');
238     }
240     /**
241      * Returns an error message.
242      *
243      * Useful when the block is not properly set or something goes wrong.
244      *
245      * @param string $message The message to display.
246      * @return string HTML
247      */
248     protected function get_error_message($message = null) {
249         global $OUTPUT;
251         if (empty($message)) {
252             $message = get_string('apierror', 'block_tag_youtube');
253         }
254         return $OUTPUT->notification($message);
255     }
257     /**
258      * Gets the youtube service object.
259      *
260      * @return Google_Service_YouTube
261      */
262     protected function get_service() {
263         global $CFG;
265         if (!$apikey = get_config('block_tag_youtube', 'apikey')) {
266             return false;
267         }
269         // Wrapped in an if in case we call different get_videos_* multiple times.
270         if (!isset($this->service)) {
271             require_once($CFG->libdir . '/google/lib.php');
272             $client = get_google_client();
273             $client->setDeveloperKey($apikey);
274             $client->setScopes(array(Google_Service_YouTube::YOUTUBE_READONLY));
275             $this->service = new Google_Service_YouTube($client);
276         }
278         return $this->service;
279     }
281     /**
282      * Renders the list of items.
283      *
284      * @param array $videosdata
285      * @return string HTML
286      */
287     protected function render_items($videosdata) {
289         if (!$videosdata || empty($videosdata->items)) {
290             if (!empty($videosdata->error)) {
291                 debugging('Error fetching data from youtube: ' . $videosdata->error->message, DEBUG_DEVELOPER);
292             }
293             return '';
294         }
296         // If we reach that point we already know that the API key is set.
297         $service = $this->get_service();
299         $text = html_writer::start_tag('ul', array('class' => 'yt-video-entry unlist img-text'));
300         foreach ($videosdata->items as $video) {
302             // Link to the video included in the playlist if listing a playlist.
303             if (!empty($video->snippet->resourceId)) {
304                 $id = $video->snippet->resourceId->videoId;
305                 $playlist = '&list=' . $video->snippet->playlistId;
306             } else {
307                 $id = $video->id->videoId;
308                 $playlist = '';
309             }
311             $thumbnail = $video->snippet->getThumbnails()->getDefault();
312             $url = 'http://www.youtube.com/watch?v=' . $id . $playlist;
314             $videodetails = $service->videos->listVideos('id,contentDetails', array('id' => $id));
315             if ($videodetails && !empty($videodetails->items)) {
317                 // We fetch by id so we just use the first one.
318                 $details = $videodetails->items[0];
319                 $start = new DateTime('@0');
320                 $start->add(new DateInterval($details->contentDetails->duration));
321                 $seconds = $start->format('U');
322             }
324             $text .= html_writer::start_tag('li');
326             $imgattrs = array('class' => 'youtube-thumb', 'src' => $thumbnail->url, 'alt' => $video->snippet->title);
327             $thumbhtml = html_writer::empty_tag('img', $imgattrs);
328             $link = html_writer::tag('a', $thumbhtml, array('href' => $url));
329             $text .= html_writer::tag('div', $link, array('class' => 'clearfix'));
331             $text .= html_writer::tag('span', html_writer::tag('a', $video->snippet->title, array('href' => $url)));
333             if (!empty($seconds)) {
334                 $text .= html_writer::tag('div', format_time($seconds));
335             }
336             $text .= html_writer::end_tag('li');
337         }
338         $text .= html_writer::end_tag('ul');
340         return $text;
341     }
343     function get_categories() {
344         // TODO: Right now using sticky categories from
345         // http://gdata.youtube.com/schemas/2007/categories.cat
346         // This should be performed from time to time by the block insead
347         // and cached somewhere, avoiding deprecated ones and observing regions
348         return array (
349             '0' => get_string('anycategory', 'block_tag_youtube'),
350             'Film'  => get_string('filmsanimation', 'block_tag_youtube'),
351             'Autos' => get_string('autosvehicles', 'block_tag_youtube'),
352             'Music' => get_string('music', 'block_tag_youtube'),
353             'Animals'=> get_string('petsanimals', 'block_tag_youtube'),
354             'Sports' => get_string('sports', 'block_tag_youtube'),
355             'Travel' => get_string('travel', 'block_tag_youtube'),
356             'Games'  => get_string('gadgetsgames', 'block_tag_youtube'),
357             'Comedy' => get_string('comedy', 'block_tag_youtube'),
358             'People' => get_string('peopleblogs', 'block_tag_youtube'),
359             'News'   => get_string('newspolitics', 'block_tag_youtube'),
360             'Entertainment' => get_string('entertainment', 'block_tag_youtube'),
361             'Education' => get_string('education', 'block_tag_youtube'),
362             'Howto'  => get_string('howtodiy', 'block_tag_youtube'),
363             'Tech'   => get_string('scienceandtech', 'block_tag_youtube')
364         );
365     }
367     /**
368      * Provide conversion from old numeric categories available in youtube API
369      * to the new ones available in the Google API
370      *
371      * @param int $oldcat old category code
372      * @return mixed new category code or 0 (if no match found)
373      *
374      * TODO: Someday this should be applied on upgrade for all the existing
375      * block instances so we won't need the mapping any more. That would imply
376      * to implement restore handling to perform the conversion of old blocks.
377      */
378     function category_map_old2new($oldcat) {
379         $oldoptions = array (
380             0  => '0',
381             1  => 'Film',
382             2  => 'Autos',
383             23 => 'Comedy',
384             24 => 'Entertainment',
385             10 => 'Music',
386             25 => 'News',
387             22 => 'People',
388             15 => 'Animals',
389             26 => 'Howto',
390             17 => 'Sports',
391             19 => 'Travel',
392             20 => 'Games'
393         );
394         if (array_key_exists($oldcat, $oldoptions)) {
395             return $oldoptions[$oldcat];
396         } else {
397             return $oldcat;
398         }
399     }