Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 402] [Versions 311 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