See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
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/>. 16 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 */ 24 25 define('DEFAULT_NUMBER_OF_VIDEOS', 5); 26 27 class block_tag_youtube extends block_base { 28 29 /** 30 * @var Google_Service_Youtube 31 */ 32 protected $service = null; 33 34 function init() { 35 $this->title = get_string('pluginname','block_tag_youtube'); 36 $this->config = new stdClass(); 37 } 38 39 function applicable_formats() { 40 return array('tag' => true); 41 } 42 43 /** 44 * It can be configured. 45 * 46 * @return bool 47 */ 48 public function has_config() { 49 return true; 50 } 51 52 function specialization() { 53 $this->title = !empty($this->config->title) ? $this->config->title : get_string('pluginname', 'block_tag_youtube'); 54 } 55 56 function instance_allow_multiple() { 57 return true; 58 } 59 60 function get_content() { 61 global $CFG; 62 63 //note: do NOT include files at the top of this file 64 require_once($CFG->libdir . '/filelib.php'); 65 66 if ($this->content !== NULL) { 67 return $this->content; 68 } 69 70 $this->content = new stdClass(); 71 $this->content->footer = ''; 72 73 if (!$this->get_service()) { 74 $this->content->text = $this->get_error_message(); 75 return $this->content; 76 } 77 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 } 93 94 $this->content->text = $text; 95 96 return $this->content; 97 } 98 99 function get_videos_by_playlist(){ 100 101 if (!$service = $this->get_service()) { 102 return $this->get_error_message(); 103 } 104 105 $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS; 106 if( !empty($this->config->numberofvideos)) { 107 $numberofvideos = $this->config->numberofvideos; 108 } 109 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 } 119 120 return $this->render_items($response); 121 } 122 123 function get_videos_by_tag(){ 124 125 if (!$service = $this->get_service()) { 126 return $this->get_error_message(); 127 } 128 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. 132 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 } 138 139 if (empty($tagobject)) { 140 return ''; 141 } 142 143 $querytag = urlencode($tagobject->name); 144 145 $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS; 146 if ( !empty($this->config->numberofvideos) ) { 147 $numberofvideos = $this->config->numberofvideos; 148 } 149 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 } 160 161 return $this->render_items($response); 162 } 163 164 function get_videos_by_tag_and_category(){ 165 166 if (!$service = $this->get_service()) { 167 return $this->get_error_message(); 168 } 169 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. 173 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 } 179 180 if (empty($tagobject)) { 181 return ''; 182 } 183 184 $querytag = urlencode($tagobject->name); 185 186 $numberofvideos = DEFAULT_NUMBER_OF_VIDEOS; 187 if( !empty($this->config->numberofvideos)) { 188 $numberofvideos = $this->config->numberofvideos; 189 } 190 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 } 202 203 return $this->render_items($response); 204 } 205 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.'); 216 217 $c = new curl(array('cache' => true, 'module_cache'=>'tag_youtube')); 218 $c->setopt(array('CURLOPT_TIMEOUT' => 3, 'CURLOPT_CONNECTTIMEOUT' => 3)); 219 220 $response = $c->get($request); 221 222 $xml = new SimpleXMLElement($response); 223 return $this->render_video_list($xml); 224 } 225 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 } 237 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; 248 249 if (empty($message)) { 250 $message = get_string('apierror', 'block_tag_youtube'); 251 } 252 return $OUTPUT->notification($message); 253 } 254 255 /** 256 * Gets the youtube service object. 257 * 258 * @return Google_Service_YouTube 259 */ 260 protected function get_service() { 261 global $CFG; 262 263 if (!$apikey = get_config('block_tag_youtube', 'apikey')) { 264 return false; 265 } 266 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 } 275 276 return $this->service; 277 } 278 279 /** 280 * Renders the list of items. 281 * 282 * @param array $videosdata 283 * @return string HTML 284 */ 285 protected function render_items($videosdata) { 286 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 } 293 294 // If we reach that point we already know that the API key is set. 295 $service = $this->get_service(); 296 297 $text = html_writer::start_tag('ul', array('class' => 'yt-video-entry unlist img-text')); 298 foreach ($videosdata->items as $video) { 299 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 } 308 309 $thumbnail = $video->snippet->getThumbnails()->getDefault(); 310 $url = 'http://www.youtube.com/watch?v=' . $id . $playlist; 311 312 $videodetails = $service->videos->listVideos('id,contentDetails', array('id' => $id)); 313 if ($videodetails && !empty($videodetails->items)) { 314 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 } 321 322 $text .= html_writer::start_tag('li'); 323 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')); 328 329 $text .= html_writer::tag('span', html_writer::tag('a', $video->snippet->title, array('href' => $url))); 330 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'); 337 338 return $text; 339 } 340 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(); 352 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']; 361 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 } 382 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 } 407 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(); 421 422 return (object) [ 423 'instance' => $instanceconfigs, 424 'plugin' => $pluginconfigs, 425 ]; 426 } 427 } 428
title
Description
Body
title
Description
Body
title
Description
Body
title
Body