Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

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