Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 402] [Versions 400 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   * This plugin is used to access youtube videos
  19   *
  20   * @since Moodle 2.0
  21   * @package    repository_youtube
  22   * @copyright  2010 Dongsheng Cai {@link http://dongsheng.org}
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  require_once($CFG->dirroot . '/repository/lib.php');
  26  
  27  /**
  28   * repository_youtube class
  29   *
  30   * @since Moodle 2.0
  31   * @package    repository_youtube
  32   * @copyright  2009 Dongsheng Cai {@link http://dongsheng.org}
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  
  36  class repository_youtube extends repository {
  37      /** @var int maximum number of thumbs per page */
  38      const YOUTUBE_THUMBS_PER_PAGE = 27;
  39  
  40      /**
  41       * API key for using the YouTube Data API.
  42       * @var mixed
  43       */
  44      private $apikey;
  45  
  46      /**
  47       * Google Client.
  48       * @var Google_Client
  49       */
  50      private $client = null;
  51  
  52      /**
  53       * YouTube Service.
  54       * @var Google_Service_YouTube
  55       */
  56      private $service = null;
  57  
  58      /**
  59       * Youtube plugin constructor
  60       * @param int $repositoryid
  61       * @param object $context
  62       * @param array $options
  63       */
  64      public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
  65          parent::__construct($repositoryid, $context, $options);
  66  
  67          $this->apikey = $this->get_option('apikey');
  68  
  69          // Without an API key, don't show this repo to users as its useless without it.
  70          if (empty($this->apikey)) {
  71              $this->disabled = true;
  72          }
  73      }
  74  
  75      /**
  76       * Init all the youtube client service stuff.
  77       *
  78       * Instead of instantiating the service in the constructor, we delay
  79       * it until really neeed because it's really memory hungry (2MB). That
  80       * way the editor or any other artifact requiring repository instantiation
  81       * can do it in a cheap way. Sort of lazy loading the plugin.
  82       */
  83      private function init_youtube_service() {
  84          global $CFG;
  85  
  86          if (!isset($this->service)) {
  87              require_once($CFG->libdir . '/google/lib.php');
  88              $this->client = get_google_client();
  89              $this->client->setDeveloperKey($this->apikey);
  90              $this->client->setScopes(array(Google_Service_YouTube::YOUTUBE_READONLY));
  91              $this->service = new Google_Service_YouTube($this->client);
  92          }
  93      }
  94  
  95      /**
  96       * Save apikey in config table.
  97       * @param array $options
  98       * @return boolean
  99       */
 100      public function set_option($options = array()) {
 101          if (!empty($options['apikey'])) {
 102              set_config('apikey', trim($options['apikey']), 'youtube');
 103          }
 104          unset($options['apikey']);
 105          return parent::set_option($options);
 106      }
 107  
 108      /**
 109       * Get apikey from config table.
 110       *
 111       * @param string $config
 112       * @return mixed
 113       */
 114      public function get_option($config = '') {
 115          if ($config === 'apikey') {
 116              return trim(get_config('youtube', 'apikey'));
 117          } else {
 118              $options['apikey'] = trim(get_config('youtube', 'apikey'));
 119          }
 120          return parent::get_option($config);
 121      }
 122  
 123      public function check_login() {
 124          return !empty($this->keyword);
 125      }
 126  
 127      /**
 128       * Return search results
 129       * @param string $search_text
 130       * @return array
 131       */
 132      public function search($search_text, $page = 0) {
 133          global $SESSION;
 134          $sort = optional_param('youtube_sort', '', PARAM_TEXT);
 135          $sess_keyword = 'youtube_'.$this->id.'_keyword';
 136          $sess_sort = 'youtube_'.$this->id.'_sort';
 137  
 138          // This is the request of another page for the last search, retrieve the cached keyword and sort
 139          if ($page && !$search_text && isset($SESSION->{$sess_keyword})) {
 140              $search_text = $SESSION->{$sess_keyword};
 141          }
 142          if ($page && !$sort && isset($SESSION->{$sess_sort})) {
 143              $sort = $SESSION->{$sess_sort};
 144          }
 145          if (!$sort) {
 146              $sort = 'relevance'; // default
 147          }
 148  
 149          // Save this search in session
 150          $SESSION->{$sess_keyword} = $search_text;
 151          $SESSION->{$sess_sort} = $sort;
 152  
 153          $this->keyword = $search_text;
 154          $ret  = array();
 155          $ret['nologin'] = true;
 156          $ret['page'] = (int)$page;
 157          if ($ret['page'] < 1) {
 158              $ret['page'] = 1;
 159          }
 160          $start = ($ret['page'] - 1) * self::YOUTUBE_THUMBS_PER_PAGE + 1;
 161          $max = self::YOUTUBE_THUMBS_PER_PAGE;
 162          $ret['list'] = $this->_get_collection($search_text, $start, $max, $sort);
 163          $ret['norefresh'] = true;
 164          $ret['nosearch'] = true;
 165          // If the number of results is smaller than $max, it means we reached the last page.
 166          $ret['pages'] = (count($ret['list']) < $max) ? $ret['page'] : -1;
 167          return $ret;
 168      }
 169  
 170      /**
 171       * Private method to get youtube search results
 172       * @param string $keyword
 173       * @param int $start
 174       * @param int $max max results
 175       * @param string $sort
 176       * @throws moodle_exception If the google API returns an error.
 177       * @return array
 178       */
 179      private function _get_collection($keyword, $start, $max, $sort) {
 180          global $SESSION;
 181  
 182          // The new API doesn't use "page" numbers for browsing through results.
 183          // It uses a prev and next token in each set that you need to use to
 184          // request the next page of results.
 185          $sesspagetoken = 'youtube_'.$this->id.'_nextpagetoken';
 186          $pagetoken = '';
 187          if ($start > 1 && isset($SESSION->{$sesspagetoken})) {
 188              $pagetoken = $SESSION->{$sesspagetoken};
 189          }
 190  
 191          $list = array();
 192          $error = null;
 193          try {
 194              $this->init_youtube_service(); // About to use the service, ensure it's loaded.
 195              $response = $this->service->search->listSearch('id,snippet', array(
 196                  'q' => $keyword,
 197                  'maxResults' => $max,
 198                  'order' => $sort,
 199                  'pageToken' => $pagetoken,
 200                  'type' => 'video',
 201                  'videoEmbeddable' => 'true',
 202              ));
 203  
 204              // Track the next page token for the next request (when a user
 205              // scrolls down in the file picker for more videos).
 206              $SESSION->{$sesspagetoken} = $response['nextPageToken'];
 207  
 208              foreach ($response['items'] as $result) {
 209                  $title = $result->snippet->title;
 210                  $source = 'http://www.youtube.com/v/' . $result->id->videoId . '#' . $title;
 211                  $thumb = $result->snippet->getThumbnails()->getDefault();
 212  
 213                  $list[] = array(
 214                      'shorttitle' => $title,
 215                      'thumbnail_title' => $result->snippet->description,
 216                      'title' => $title.'.avi', // This is a hack so we accept this file by extension.
 217                      'thumbnail' => $thumb->url,
 218                      'thumbnail_width' => (int)$thumb->width,
 219                      'thumbnail_height' => (int)$thumb->height,
 220                      'size' => '',
 221                      'date' => '',
 222                      'source' => $source,
 223                  );
 224              }
 225          } catch (Google_Service_Exception $e) {
 226              // If we throw the google exception as-is, we may expose the apikey
 227              // to end users. The full message in the google exception includes
 228              // the apikey param, so we take just the part pertaining to the
 229              // actual error.
 230              $error = $e->getErrors()[0]['message'];
 231              throw new moodle_exception('apierror', 'repository_youtube', '', $error);
 232          }
 233  
 234          return $list;
 235      }
 236  
 237      /**
 238       * Youtube plugin doesn't support global search
 239       */
 240      public function global_search() {
 241          return false;
 242      }
 243  
 244      public function get_listing($path='', $page = '') {
 245          return array();
 246      }
 247  
 248      /**
 249       * Generate search form
 250       */
 251      public function print_login($ajax = true) {
 252          $ret = array();
 253          $search = new stdClass();
 254          $search->type = 'text';
 255          $search->id   = 'youtube_search';
 256          $search->name = 's';
 257          $search->label = get_string('search', 'repository_youtube').': ';
 258          $sort = new stdClass();
 259          $sort->type = 'select';
 260          $sort->options = array(
 261              (object)array(
 262                  'value' => 'relevance',
 263                  'label' => get_string('sortrelevance', 'repository_youtube')
 264              ),
 265              (object)array(
 266                  'value' => 'date',
 267                  'label' => get_string('sortpublished', 'repository_youtube')
 268              ),
 269              (object)array(
 270                  'value' => 'rating',
 271                  'label' => get_string('sortrating', 'repository_youtube')
 272              ),
 273              (object)array(
 274                  'value' => 'viewCount',
 275                  'label' => get_string('sortviewcount', 'repository_youtube')
 276              )
 277          );
 278          $sort->id = 'youtube_sort';
 279          $sort->name = 'youtube_sort';
 280          $sort->label = get_string('sortby', 'repository_youtube').': ';
 281          $ret['login'] = array($search, $sort);
 282          $ret['login_btn_label'] = get_string('search');
 283          $ret['login_btn_action'] = 'search';
 284          $ret['allowcaching'] = true; // indicates that login form can be cached in filepicker.js
 285          return $ret;
 286      }
 287  
 288      /**
 289       * file types supported by youtube plugin
 290       * @return array
 291       */
 292      public function supported_filetypes() {
 293          return array('video');
 294      }
 295  
 296      /**
 297       * Youtube plugin only return external links
 298       * @return int
 299       */
 300      public function supported_returntypes() {
 301          return FILE_EXTERNAL;
 302      }
 303  
 304      /**
 305       * Is this repository accessing private data?
 306       *
 307       * @return bool
 308       */
 309      public function contains_private_data() {
 310          return false;
 311      }
 312  
 313      /**
 314       * Add plugin settings input to Moodle form.
 315       * @param object $mform
 316       * @param string $classname
 317       */
 318      public static function type_config_form($mform, $classname = 'repository') {
 319          parent::type_config_form($mform, $classname);
 320          $apikey = get_config('youtube', 'apikey');
 321          if (empty($apikey)) {
 322              $apikey = '';
 323          }
 324  
 325          $mform->addElement('text', 'apikey', get_string('apikey', 'repository_youtube'), array('value' => $apikey, 'size' => '40'));
 326          $mform->setType('apikey', PARAM_RAW_TRIMMED);
 327          $mform->addRule('apikey', get_string('required'), 'required', null, 'client');
 328  
 329          $mform->addElement('static', null, '',  get_string('information', 'repository_youtube'));
 330      }
 331  
 332      /**
 333       * Names of the plugin settings
 334       * @return array
 335       */
 336      public static function get_type_option_names() {
 337          return array('apikey', 'pluginname');
 338      }
 339  }