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.
   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 box.net repository
  19   *
  20   * @since Moodle 2.0
  21   * @package    repository_boxnet
  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  require_once($CFG->libdir . '/boxlib.php');
  27  
  28  /**
  29   * repository_boxnet class implements box.net client
  30   *
  31   * @since Moodle 2.0
  32   * @package    repository_boxnet
  33   * @copyright  2010 Dongsheng Cai {@link http://dongsheng.org}
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class repository_boxnet extends repository {
  37  
  38      /** @const MANAGE_URL Manage URL. */
  39      const MANAGE_URL = 'https://app.box.com/files';
  40  
  41      /** @const SESSION_PREFIX Key used to store information in the session. */
  42      const SESSION_PREFIX = 'repository_boxnet';
  43  
  44      /** @var string Client ID */
  45      protected $clientid;
  46  
  47      /** @var string Client secret */
  48      protected $clientsecret;
  49  
  50      /** @var string Access token */
  51      protected $accesstoken;
  52  
  53      /** @var object Box.net object */
  54      protected $boxnetclient;
  55  
  56      /**
  57       * Constructor
  58       *
  59       * @param int $repositoryid
  60       * @param stdClass $context
  61       * @param array $options
  62       */
  63      public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
  64          parent::__construct($repositoryid, $context, $options);
  65  
  66          $clientid = get_config('boxnet', 'clientid');
  67          $clientsecret = get_config('boxnet', 'clientsecret');
  68          $returnurl = new moodle_url('/repository/repository_callback.php');
  69          $returnurl->param('callback', 'yes');
  70          $returnurl->param('repo_id', $this->id);
  71          $returnurl->param('sesskey', sesskey());
  72  
  73          $this->boxnetclient = new boxnet_client($clientid, $clientsecret, $returnurl, '');
  74      }
  75  
  76      /**
  77       * Construct a breadcrumb from a path.
  78       *
  79       * @param string $fullpath Path containing multiple parts separated by slashes.
  80       * @return array Array expected to be generated in {@link self::get_listing()}.
  81       */
  82      protected function build_breadcrumb($fullpath) {
  83          $breadcrumb = array(array(
  84              'name' => get_string('pluginname', 'repository_boxnet'),
  85              'path' => ''
  86          ));
  87          $breadcrumbpath = '';
  88          $crumbs = explode('/', $fullpath);
  89          foreach ($crumbs as $crumb) {
  90              if (empty($crumb)) {
  91                  // That is probably the root crumb, we've already added it.
  92                  continue;
  93              }
  94              list($unused, $tosplit) = explode(':', $crumb, 2);
  95              if (strpos($tosplit, '|') !== false) {
  96                  list($id, $crumbname) = explode('|', $tosplit, 2);
  97              } else {
  98                  $crumbname = $tosplit;
  99              }
 100              $breadcrumbpath .= '/' . $crumb;
 101              $breadcrumb[] = array(
 102                  'name' => urldecode($crumbname),
 103                  'path' => $breadcrumbpath
 104              );
 105          }
 106          return $breadcrumb;
 107      }
 108  
 109      /**
 110       * Build a part of the path.
 111       *
 112       * This is used to construct the path that the user is currently browsing.
 113       * It must contain a 'type', and a 'value'. Then it can also contain a
 114       * 'name' which is very useful to prevent extra queries to get the name only.
 115       *
 116       * See {@link self::split_part} to extra the information from a part.
 117       *
 118       * @param string $type Type of part, typically 'folder' or 'search'.
 119       * @param string $value The value of the part, eg. a folder ID or search terms.
 120       * @param string $name The name of the part.
 121       * @return string type:value or type:value|name
 122       */
 123      protected function build_part($type, $value, $name = '') {
 124          $return = $type . ':' . urlencode($value);
 125          if ($name !== '') {
 126              $return .= '|' . urlencode($name);
 127          }
 128          return $return;
 129      }
 130  
 131      /**
 132       * Extract information from a part of path.
 133       *
 134       * @param string $part value generated from {@link self::build_parth()}.
 135       * @return array containing type, value and name.
 136       */
 137      protected function split_part($part) {
 138          list($type, $tosplit) = explode(':', $part);
 139          $name = '';
 140          if (strpos($tosplit, '|') !== false) {
 141              list($value, $name) = explode('|', $tosplit, 2);
 142          } else {
 143              $value = $tosplit;
 144          }
 145          return array($type, urldecode($value), urldecode($name));
 146      }
 147  
 148      /**
 149       * check if user logged
 150       *
 151       * @return boolean
 152       */
 153      public function check_login() {
 154          return $this->boxnetclient->is_logged_in();
 155      }
 156  
 157      /**
 158       * reset auth token
 159       *
 160       * @return string
 161       */
 162      public function logout() {
 163          if ($this->check_login()) {
 164              $this->boxnetclient->log_out();
 165          }
 166          return $this->print_login();
 167      }
 168  
 169      /**
 170       * Search files from box.net
 171       *
 172       * @param string $search_text
 173       * @return mixed
 174       */
 175      public function search($search_text, $page = 0) {
 176          return $this->get_listing($this->build_part('search', $search_text));
 177      }
 178  
 179      /**
 180       * Downloads a repository file and saves to a path.
 181       *
 182       * @param string $ref reference to the file
 183       * @param string $filename to save file as
 184       * @return array
 185       */
 186      public function get_file($ref, $filename = '') {
 187          global $CFG;
 188  
 189          $ref = unserialize(self::convert_to_valid_reference($ref));
 190          $path = $this->prepare_file($filename);
 191          if (!empty($ref->downloadurl)) {
 192              $c = new curl();
 193              $result = $c->download_one($ref->downloadurl, null, array('filepath' => $filename,
 194                  'timeout' => $CFG->repositorygetfiletimeout, 'followlocation' => true));
 195              $info = $c->get_info();
 196              if ($result !== true || !isset($info['http_code']) || $info['http_code'] != 200) {
 197                  throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
 198              }
 199          } else {
 200              if (!$this->boxnetclient->download_file($ref->fileid, $path)) {
 201                  throw new moodle_exception('cannotdownload', 'repository');
 202              }
 203          }
 204          return array('path' => $path);
 205      }
 206  
 207      /**
 208       * Get file listing
 209       *
 210       * @param string $path
 211       * @param string $page
 212       * @return mixed
 213       */
 214      public function get_listing($fullpath = '', $page = ''){
 215          global $OUTPUT;
 216  
 217          $ret = array();
 218          $ret['list'] = array();
 219          $ret['manage'] = self::MANAGE_URL;
 220          $ret['dynload'] = true;
 221  
 222          $crumbs = explode('/', $fullpath);
 223          $path = array_pop($crumbs);
 224  
 225          if (empty($path)) {
 226              $type = 'folder';
 227              $pathid = 0;
 228              $pathname = get_string('pluginname', 'repository_boxnet');
 229          } else {
 230              list($type, $pathid, $pathname) = $this->split_part($path);
 231          }
 232  
 233          $ret['path'] = $this->build_breadcrumb($fullpath);
 234          $folders = array();
 235          $files = array();
 236  
 237          if ($type == 'search') {
 238              $result = $this->boxnetclient->search($pathname);
 239          } else {
 240              $result = $this->boxnetclient->get_folder_items($pathid);
 241          }
 242          foreach ($result->entries as $item) {
 243              if ($item->type == 'folder') {
 244                  $folders[$item->name . ':' . $item->id] = array(
 245                      'title' => $item->name,
 246                      'path' => $fullpath . '/' . $this->build_part('folder', $item->id, $item->name),
 247                      'date' => strtotime($item->modified_at),
 248                      'thumbnail' => $OUTPUT->image_url(file_folder_icon(64))->out(false),
 249                      'thumbnail_height' => 64,
 250                      'thumbnail_width' => 64,
 251                      'children' => array(),
 252                      'size' => $item->size,
 253                  );
 254              } else {
 255                  $files[$item->name . ':' . $item->id] = array(
 256                      'title' => $item->name,
 257                      'source' => $this->build_part('file', $item->id, $item->name),
 258                      'size' => $item->size,
 259                      'date' => strtotime($item->modified_at),
 260                      'thumbnail' => $OUTPUT->image_url(file_extension_icon($item->name, 64))->out(false),
 261                      'thumbnail_height' => 64,
 262                      'thumbnail_width' => 64,
 263                      'author' => $item->owned_by->name,
 264                  );
 265              }
 266          }
 267  
 268          core_collator::ksort($folders, core_collator::SORT_NATURAL);
 269          core_collator::ksort($files, core_collator::SORT_NATURAL);
 270          $ret['list'] = array_merge($folders, $files);
 271          $ret['list'] = array_filter($ret['list'], array($this, 'filter'));
 272  
 273          return $ret;
 274      }
 275  
 276      /**
 277       * Return login form
 278       *
 279       * @return array
 280       */
 281      public function print_login(){
 282          $url = $this->boxnetclient->get_login_url();
 283          if ($this->options['ajax']) {
 284              $ret = array();
 285              $popup_btn = new stdClass();
 286              $popup_btn->type = 'popup';
 287              $popup_btn->url = $url->out(false);
 288              $ret['login'] = array($popup_btn);
 289              return $ret;
 290          } else {
 291              echo html_writer::link($url, get_string('login', 'repository'), array('target' => '_blank'));
 292          }
 293      }
 294  
 295      /**
 296       * Names of the plugin settings
 297       *
 298       * @return array
 299       */
 300      public static function get_type_option_names() {
 301          return array('clientid', 'clientsecret', 'pluginname');
 302      }
 303  
 304      /**
 305       * Catch the request token.
 306       */
 307      public function callback() {
 308          $this->boxnetclient->is_logged_in();
 309      }
 310  
 311      /**
 312       * Add Plugin settings input to Moodle form
 313       *
 314       * @param moodleform $mform
 315       * @param string $classname
 316       */
 317      public static function type_config_form($mform, $classname = 'repository') {
 318          global $CFG;
 319          parent::type_config_form($mform);
 320  
 321          $clientid = get_config('boxnet', 'clientid');
 322          $clientsecret = get_config('boxnet', 'clientsecret');
 323          $strrequired = get_string('required');
 324  
 325          $mform->addElement('text', 'clientid', get_string('clientid', 'repository_boxnet'),
 326              array('value' => $clientid, 'size' => '40'));
 327          $mform->addRule('clientid', $strrequired, 'required', null, 'client');
 328          $mform->setType('clientid', PARAM_RAW_TRIMMED);
 329  
 330          $mform->addElement('text', 'clientsecret', get_string('clientsecret', 'repository_boxnet'),
 331              array('value' => $clientsecret, 'size' => '40'));
 332          $mform->addRule('clientsecret', $strrequired, 'required', null, 'client');
 333          $mform->setType('clientsecret', PARAM_RAW_TRIMMED);
 334  
 335          $mform->addElement('static', null, '',  get_string('information', 'repository_boxnet'));
 336  
 337          if (!is_https()) {
 338              $mform->addElement('static', null, '',  get_string('warninghttps', 'repository_boxnet'));
 339          }
 340      }
 341  
 342      /**
 343       * Box.net supports copied and links.
 344       *
 345       * Theoretically this API is ready for references, though it only works for
 346       * Box.net Business accounts, but it is not enabled because we are not supporting it.
 347       *
 348       * @return int
 349       */
 350      public function supported_returntypes() {
 351          return FILE_INTERNAL | FILE_EXTERNAL;
 352      }
 353  
 354      /**
 355       * Convert a reference to the new reference style.
 356       *
 357       * While converting Box.net to APIv2 we introduced a new format for
 358       * file references, see {@link self::get_file_reference()}. This function
 359       * ensures that the format is always the same regardless of the whether
 360       * the reference was from APIv1 or v2.
 361       *
 362       * @param mixed $reference File reference.
 363       * @return stdClass Valid file reference.
 364       */
 365      public static function convert_to_valid_reference($reference) {
 366          if (strpos($reference, 'http') === 0) {
 367              // It is faster to check if the reference is a URL rather than trying to unserialize it.
 368              $reference = serialize((object) array('downloadurl' => $reference, 'fileid' => '', 'filename' => '', 'userid' => ''));
 369          }
 370          return $reference;
 371      }
 372  
 373      /**
 374       * Prepare file reference information
 375       *
 376       * @param string $source
 377       * @return string file referece
 378       */
 379      public function get_file_reference($source) {
 380          global $USER;
 381          list($type, $fileid, $filename) = $this->split_part($source);
 382          $reference = new stdClass();
 383          $reference->fileid = $fileid;
 384          $reference->filename = $filename;
 385          $reference->userid = $USER->id;
 386          $reference->downloadurl = '';
 387          if (optional_param('usefilereference', false, PARAM_BOOL)) {
 388              try {
 389                  $shareinfo = $this->boxnetclient->share_file($reference->fileid);
 390              } catch (moodle_exception $e) {
 391                  throw new repository_exception('cannotcreatereference', 'repository_boxnet');
 392              }
 393              $reference->downloadurl = $shareinfo->download_url;
 394          }
 395          return serialize($reference);
 396      }
 397  
 398      /**
 399       * Get a link to the file.
 400       *
 401       * This returns the URL of the web view of the file. To generate this link the
 402       * file must be shared.
 403       *
 404       * @param stdClass $reference Reference.
 405       * @return string URL.
 406       */
 407      public function get_link($reference) {
 408          $reference = unserialize(self::convert_to_valid_reference($reference));
 409          $shareinfo = $this->boxnetclient->share_file($reference->fileid, false);
 410          return $shareinfo->url;
 411      }
 412  
 413      /**
 414       * Synchronize the references.
 415       *
 416       * @param stored_file $file Stored file.
 417       * @return boolean
 418       */
 419      public function sync_reference(stored_file $file) {
 420          global $CFG;
 421          if ($file->get_referencelastsync() + DAYSECS > time()) {
 422              // Synchronise not more often than once a day.
 423              return false;
 424          }
 425          $c = new curl();
 426          $reference = unserialize(self::convert_to_valid_reference($file->get_reference()));
 427          $url = $reference->downloadurl;
 428          if (file_extension_in_typegroup($file->get_filename(), 'web_image')) {
 429              $path = $this->prepare_file('');
 430              $result = $c->download_one($url, null, array('filepath' => $path, 'timeout' => $CFG->repositorysyncimagetimeout));
 431              $info = $c->get_info();
 432              if ($result === true && isset($info['http_code']) && $info['http_code'] == 200) {
 433                  $file->set_synchronised_content_from_file($path);
 434                  return true;
 435              }
 436          }
 437          $c->get($url, null, array('timeout' => $CFG->repositorysyncimagetimeout, 'followlocation' => true, 'nobody' => true));
 438          $info = $c->get_info();
 439          if (isset($info['http_code']) && $info['http_code'] == 200 &&
 440                  array_key_exists('download_content_length', $info) &&
 441                  $info['download_content_length'] >= 0) {
 442              $filesize = (int)$info['download_content_length'];
 443              $file->set_synchronized(null, $filesize);
 444              return true;
 445          }
 446          $file->set_missingsource();
 447          return true;
 448      }
 449  
 450      /**
 451       * Return human readable reference information
 452       * {@link stored_file::get_reference()}
 453       *
 454       * @param string $reference
 455       * @param int $filestatus status of the file, 0 - ok, 666 - source missing
 456       * @return string
 457       */
 458      public function get_reference_details($reference, $filestatus = 0) {
 459          // Indicate it's from box.net repository.
 460          $reference = unserialize(self::convert_to_valid_reference($reference));
 461          if (!$filestatus) {
 462              return $this->get_name() . ': ' . $reference->filename;
 463          } else {
 464              return get_string('lostsource', 'repository', $reference->filename);
 465          }
 466      }
 467  
 468      /**
 469       * Return the source information.
 470       *
 471       * @param string $source Not the reference, just the source.
 472       * @return string|null
 473       */
 474      public function get_file_source_info($source) {
 475          global $USER;
 476          list($type, $fileid, $filename) = $this->split_part($source);
 477          return 'Box ('. fullname($USER) . '): ' . $filename;
 478      }
 479  
 480      /**
 481       * Repository method to serve the referenced file
 482       *
 483       * @param stored_file $storedfile the file that contains the reference
 484       * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
 485       * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
 486       * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
 487       * @param array $options additional options affecting the file serving
 488       */
 489      public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
 490          $ref = unserialize(self::convert_to_valid_reference($storedfile->get_reference()));
 491          header('Location: ' . $ref->downloadurl);
 492      }
 493  }