Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 user's dropbox files
  19   *
  20   * @since Moodle 2.0
  21   * @package    repository_dropbox
  22   * @copyright  2012 Marina Glancy
  23   * @copyright  2010 Dongsheng Cai {@link http://dongsheng.org}
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  require_once($CFG->dirroot . '/repository/lib.php');
  27  
  28  /**
  29   * Repository to access Dropbox files
  30   *
  31   * @package    repository_dropbox
  32   * @copyright  2010 Dongsheng Cai
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class repository_dropbox extends repository {
  36      /**
  37       * @var dropbox     The instance of dropbox client.
  38       */
  39      private $dropbox;
  40  
  41      /**
  42       * @var int         The maximum file size to cache in the moodle filepool.
  43       */
  44      public $cachelimit = null;
  45  
  46      /**
  47       * Constructor of dropbox plugin.
  48       *
  49       * @inheritDocs
  50       */
  51      public function __construct($repositoryid, $context = SYSCONTEXTID, $options = []) {
  52          $options['page'] = optional_param('p', 1, PARAM_INT);
  53          parent::__construct($repositoryid, $context, $options);
  54  
  55          $returnurl = new moodle_url('/repository/repository_callback.php', [
  56                  'callback'  => 'yes',
  57                  'repo_id'   => $repositoryid,
  58                  'sesskey'   => sesskey(),
  59              ]);
  60  
  61          // Create the dropbox API instance.
  62          $key = get_config('dropbox', 'dropbox_key');
  63          $secret = get_config('dropbox', 'dropbox_secret');
  64          $this->dropbox = new repository_dropbox\dropbox(
  65                  $key,
  66                  $secret,
  67                  $returnurl
  68              );
  69      }
  70  
  71      /**
  72       * Repository method to serve the referenced file.
  73       *
  74       * @inheritDocs
  75       */
  76      public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
  77          $reference = $this->unpack_reference($storedfile->get_reference());
  78  
  79          $maxcachesize = $this->max_cache_bytes();
  80          if (empty($maxcachesize)) {
  81              // Always cache the file, regardless of size.
  82              $cachefile = true;
  83          } else {
  84              // Size available. Only cache if it is under maxcachesize.
  85              $cachefile = $storedfile->get_filesize() < $maxcachesize;
  86          }
  87  
  88          if (!$cachefile) {
  89              \core\session\manager::write_close();
  90              header('Location: ' . $this->get_file_download_link($reference->url));
  91              die;
  92          }
  93  
  94          try {
  95              $this->import_external_file_contents($storedfile, $this->max_cache_bytes());
  96              if (!is_array($options)) {
  97                  $options = array();
  98              }
  99              $options['sendcachedexternalfile'] = true;
 100              \core\session\manager::write_close();
 101              send_stored_file($storedfile, $lifetime, $filter, $forcedownload, $options);
 102          } catch (moodle_exception $e) {
 103              // Redirect to Dropbox, it will show the error.
 104              // Note: We redirect to Dropbox shared link, not to the download link here!
 105              \core\session\manager::write_close();
 106              header('Location: ' . $reference->url);
 107              die;
 108          }
 109      }
 110  
 111      /**
 112       * Return human readable reference information.
 113       * {@link stored_file::get_reference()}
 114       *
 115       * @inheritDocs
 116       */
 117      public function get_reference_details($reference, $filestatus = 0) {
 118          global $USER;
 119          $ref  = unserialize($reference);
 120          $detailsprefix = $this->get_name();
 121          if (isset($ref->userid) && $ref->userid != $USER->id && isset($ref->username)) {
 122              $detailsprefix .= ' ('.$ref->username.')';
 123          }
 124          $details = $detailsprefix;
 125          if (isset($ref->path)) {
 126              $details .= ': '. $ref->path;
 127          }
 128          if (isset($ref->path) && !$filestatus) {
 129              // Indicate this is from dropbox with path.
 130              return $details;
 131          } else {
 132              if (isset($ref->url)) {
 133                  $details = $detailsprefix. ': '. $ref->url;
 134              }
 135              return get_string('lostsource', 'repository', $details);
 136          }
 137      }
 138  
 139      /**
 140       * Cache file from external repository by reference.
 141       * {@link repository::get_file_reference()}
 142       * {@link repository::get_file()}
 143       * Invoked at MOODLE/repository/repository_ajax.php.
 144       *
 145       * @inheritDocs
 146       */
 147      public function cache_file_by_reference($reference, $storedfile) {
 148          try {
 149              $this->import_external_file_contents($storedfile, $this->max_cache_bytes());
 150          } catch (Exception $e) {
 151              // Cache failure should not cause a fatal error. This is only a nice-to-have feature.
 152          }
 153      }
 154  
 155      /**
 156       * Return the source information.
 157       *
 158       * The result of the function is stored in files.source field. It may be analysed
 159       * when the source file is lost or repository may use it to display human-readable
 160       * location of reference original.
 161       *
 162       * This method is called when file is picked for the first time only. When file
 163       * (either copy or a reference) is already in moodle and it is being picked
 164       * again to another file area (also as a copy or as a reference), the value of
 165       * files.source is copied.
 166       *
 167       * @inheritDocs
 168       */
 169      public function get_file_source_info($source) {
 170          global $USER;
 171          return 'Dropbox ('.fullname($USER).'): ' . $source;
 172      }
 173  
 174      /**
 175       * Prepare file reference information.
 176       *
 177       * @inheritDocs
 178       */
 179      public function get_file_reference($source) {
 180          global $USER;
 181          $reference = new stdClass;
 182          $reference->userid = $USER->id;
 183          $reference->username = fullname($USER);
 184          $reference->path = $source;
 185  
 186          // Determine whether we are downloading the file, or should use a file reference.
 187          $usefilereference = optional_param('usefilereference', false, PARAM_BOOL);
 188          if ($usefilereference) {
 189              if ($data = $this->dropbox->get_file_share_info($source)) {
 190                  $reference = (object) array_merge((array) $data, (array) $reference);
 191              }
 192          }
 193  
 194          return serialize($reference);
 195      }
 196  
 197      /**
 198       * Return file URL for external link.
 199       *
 200       * @inheritDocs
 201       */
 202      public function get_link($reference) {
 203          $unpacked = $this->unpack_reference($reference);
 204  
 205          return $this->get_file_download_link($unpacked->url);
 206      }
 207  
 208      /**
 209       * Downloads a file from external repository and saves it in temp dir.
 210       *
 211       * @inheritDocs
 212       */
 213      public function get_file($reference, $saveas = '') {
 214          $unpacked = $this->unpack_reference($reference);
 215  
 216          // This is a shared link, and hopefully it is still active.
 217          $downloadlink = $this->get_file_download_link($unpacked->url);
 218  
 219          $saveas = $this->prepare_file($saveas);
 220          file_put_contents($saveas, fopen($downloadlink, 'r'));
 221  
 222          return ['path' => $saveas];
 223      }
 224  
 225      /**
 226       * Dropbox plugin supports all kinds of files.
 227       *
 228       * @inheritDocs
 229       */
 230      public function supported_filetypes() {
 231          return '*';
 232      }
 233  
 234      /**
 235       * User cannot use the external link to dropbox.
 236       *
 237       * @inheritDocs
 238       */
 239      public function supported_returntypes() {
 240          return FILE_INTERNAL | FILE_REFERENCE | FILE_EXTERNAL;
 241      }
 242  
 243      /**
 244       * Get dropbox files.
 245       *
 246       * @inheritDocs
 247       */
 248      public function get_listing($path = '', $page = '1') {
 249          if (empty($path) || $path == '/') {
 250              $path = '';
 251          } else {
 252              $path = file_correct_filepath($path);
 253          }
 254  
 255          $list = [
 256                  'list'      => [],
 257                  'manage'    => 'https://www.dropbox.com/home',
 258                  'logouturl' => 'https://www.dropbox.com/logout',
 259                  'message'   => get_string('logoutdesc', 'repository_dropbox'),
 260                  'dynload'   => true,
 261                  'path'      => $this->process_breadcrumbs($path),
 262              ];
 263  
 264          // Note - we deliberately do not catch the coding exceptions here.
 265          try {
 266              $result = $this->dropbox->get_listing($path);
 267          } catch (\repository_dropbox\authentication_exception $e) {
 268              // The token has expired.
 269              return $this->print_login();
 270          } catch (\repository_dropbox\dropbox_exception $e) {
 271              // There was some other form of non-coding failure.
 272              // This could be a rate limit, or it could be a server-side error.
 273              // Just return early instead.
 274              return $list;
 275          }
 276  
 277          if (!is_object($result) || empty($result)) {
 278              return $list;
 279          }
 280  
 281          if (empty($result->entries) or !is_array($result->entries)) {
 282              return $list;
 283          }
 284  
 285          $list['list'] = $this->process_entries($result->entries);
 286          return $list;
 287      }
 288  
 289      /**
 290       * Get dropbox files in the specified path.
 291       *
 292       * @param   string      $query      The search query
 293       * @param   int         $page       The page number
 294       * @return  array
 295       */
 296      public function search($query, $page = 0) {
 297          $list = [
 298                  'list'      => [],
 299                  'manage'    => 'https://www.dropbox.com/home',
 300                  'logouturl' => 'https://www.dropbox.com/logout',
 301                  'message'   => get_string('logoutdesc', 'repository_dropbox'),
 302                  'dynload'   => true,
 303              ];
 304  
 305          // Note - we deliberately do not catch the coding exceptions here.
 306          try {
 307              $result = $this->dropbox->search($query);
 308          } catch (\repository_dropbox\authentication_exception $e) {
 309              // The token has expired.
 310              return $this->print_login();
 311          } catch (\repository_dropbox\dropbox_exception $e) {
 312              // There was some other form of non-coding failure.
 313              // This could be a rate limit, or it could be a server-side error.
 314              // Just return early instead.
 315              return $list;
 316          }
 317  
 318          if (!is_object($result) || empty($result)) {
 319              return $list;
 320          }
 321  
 322          if (empty($result->matches) or !is_array($result->matches)) {
 323              return $list;
 324          }
 325  
 326          $list['list'] = $this->process_entries($result->matches);
 327          return $list;
 328      }
 329  
 330      /**
 331       * Displays a thumbnail for current user's dropbox file.
 332       *
 333       * @inheritDocs
 334       */
 335      public function send_thumbnail($source) {
 336          $content = $this->dropbox->get_thumbnail($source);
 337  
 338          // Set 30 days lifetime for the image.
 339          // If the image is changed in dropbox it will have different revision number and URL will be different.
 340          // It is completely safe to cache the thumbnail in the browser for a long time.
 341          send_file($content, basename($source), 30 * DAYSECS, 0, true);
 342      }
 343  
 344      /**
 345       * Fixes references in DB that contains user credentials.
 346       *
 347       * @param   string      $packed     Content of DB field files_reference.reference
 348       * @return  string                  New serialized reference
 349       */
 350      protected function fix_old_style_reference($packed) {
 351          $ref = unserialize($packed);
 352          $ref = $this->dropbox->get_file_share_info($ref->path);
 353          if (!$ref || empty($ref->url)) {
 354              // Some error occurred, do not fix reference for now.
 355              return $packed;
 356          }
 357  
 358          $newreference = serialize($ref);
 359          if ($newreference !== $packed) {
 360              // We need to update references in the database.
 361              global $DB;
 362              $params = array(
 363                  'newreference'  => $newreference,
 364                  'newhash'       => sha1($newreference),
 365                  'reference'     => $packed,
 366                  'hash'          => sha1($packed),
 367                  'repoid'        => $this->id,
 368              );
 369              $refid = $DB->get_field_sql('SELECT id FROM {files_reference}
 370                  WHERE reference = :reference AND referencehash = :hash
 371                  AND repositoryid = :repoid', $params);
 372              if (!$refid) {
 373                  return $newreference;
 374              }
 375  
 376              $existingrefid = $DB->get_field_sql('SELECT id FROM {files_reference}
 377                      WHERE reference = :newreference AND referencehash = :newhash
 378                      AND repositoryid = :repoid', $params);
 379              if ($existingrefid) {
 380                  // The same reference already exists, we unlink all files from it,
 381                  // link them to the current reference and remove the old one.
 382                  $DB->execute('UPDATE {files} SET referencefileid = :refid
 383                      WHERE referencefileid = :existingrefid',
 384                      array('refid' => $refid, 'existingrefid' => $existingrefid));
 385                  $DB->delete_records('files_reference', array('id' => $existingrefid));
 386              }
 387  
 388              // Update the reference.
 389              $params['refid'] = $refid;
 390              $DB->execute('UPDATE {files_reference}
 391                  SET reference = :newreference, referencehash = :newhash
 392                  WHERE id = :refid', $params);
 393          }
 394          return $newreference;
 395      }
 396  
 397      /**
 398       * Unpack the supplied serialized reference, fixing it if required.
 399       *
 400       * @param   string      $packed     The packed reference
 401       * @return  object                  The unpacked reference
 402       */
 403      protected function unpack_reference($packed) {
 404          $reference = unserialize($packed);
 405          if (empty($reference->url)) {
 406              // The reference is missing some information. Attempt to update it.
 407              return unserialize($this->fix_old_style_reference($packed));
 408          }
 409  
 410          return $reference;
 411      }
 412  
 413      /**
 414       * Converts a URL received from dropbox API function 'shares' into URL that
 415       * can be used to download/access file directly
 416       *
 417       * @param string $sharedurl
 418       * @return string
 419       */
 420      protected function get_file_download_link($sharedurl) {
 421          $url = new \moodle_url($sharedurl);
 422          $url->param('dl', 1);
 423  
 424          return $url->out(false);
 425      }
 426  
 427      /**
 428       * Logout from dropbox.
 429       *
 430       * @inheritDocs
 431       */
 432      public function logout() {
 433          $this->dropbox->logout();
 434  
 435          return $this->print_login();
 436      }
 437  
 438      /**
 439       * Check if moodle has got access token and secret.
 440       *
 441       * @inheritDocs
 442       */
 443      public function check_login() {
 444          return $this->dropbox->is_logged_in();
 445      }
 446  
 447      /**
 448       * Generate dropbox login url.
 449       *
 450       * @inheritDocs
 451       */
 452      public function print_login() {
 453          $url = $this->dropbox->get_login_url();
 454          if ($this->options['ajax']) {
 455              $ret = array();
 456              $btn = new \stdClass();
 457              $btn->type = 'popup';
 458              $btn->url = $url->out(false);
 459              $ret['login'] = array($btn);
 460              return $ret;
 461          } else {
 462              echo html_writer::link($url, get_string('login', 'repository'), array('target' => '_blank'));
 463          }
 464      }
 465  
 466      /**
 467       * Request access token.
 468       *
 469       * @inheritDocs
 470       */
 471      public function callback() {
 472          $this->dropbox->callback();
 473      }
 474  
 475      /**
 476       * Caches all references to Dropbox files in moodle filepool.
 477       *
 478       * Invoked by {@link repository_dropbox_cron()}. Only files smaller than
 479       * {@link repository_dropbox::max_cache_bytes()} and only files which
 480       * synchronisation timeout have not expired are cached.
 481       *
 482       * @inheritDocs
 483       */
 484      public function cron() {
 485          $fs = get_file_storage();
 486          $files = $fs->get_external_files($this->id);
 487          $fetchedreferences = [];
 488          foreach ($files as $file) {
 489              if (isset($fetchedreferences[$file->get_referencefileid()])) {
 490                  continue;
 491              }
 492              try {
 493                  // This call will cache all files that are smaller than max_cache_bytes()
 494                  // and synchronise file size of all others.
 495                  $this->import_external_file_contents($file, $this->max_cache_bytes());
 496                  $fetchedreferences[$file->get_referencefileid()] = true;
 497              } catch (moodle_exception $e) {
 498                  // If an exception is thrown, just continue. This is only a pre-fetch to help speed up general use.
 499              }
 500          }
 501      }
 502  
 503      /**
 504       * Add Plugin settings input to Moodle form.
 505       *
 506       * @inheritDocs
 507       */
 508      public static function type_config_form($mform, $classname = 'repository') {
 509          parent::type_config_form($mform);
 510          $key    = get_config('dropbox', 'dropbox_key');
 511          $secret = get_config('dropbox', 'dropbox_secret');
 512  
 513          if (empty($key)) {
 514              $key = '';
 515          }
 516          if (empty($secret)) {
 517              $secret = '';
 518          }
 519  
 520          $mform->addElement('text', 'dropbox_key', get_string('apikey', 'repository_dropbox'), array('value'=>$key,'size' => '40'));
 521          $mform->setType('dropbox_key', PARAM_RAW_TRIMMED);
 522          $mform->addElement('text', 'dropbox_secret', get_string('secret', 'repository_dropbox'), array('value'=>$secret,'size' => '40'));
 523  
 524          $mform->addRule('dropbox_key',    get_string('required'), 'required', null, 'client');
 525          $mform->addRule('dropbox_secret', get_string('required'), 'required', null, 'client');
 526          $mform->setType('dropbox_secret', PARAM_RAW_TRIMMED);
 527          $mform->addElement('static', null, '', get_string('instruction', 'repository_dropbox'));
 528          $mform->addElement('static', null,
 529                  get_string('oauth2redirecturi', 'repository_dropbox'),
 530                  self::get_oauth2callbackurl()->out()
 531              );
 532  
 533          $mform->addElement('text', 'dropbox_cachelimit', get_string('cachelimit', 'repository_dropbox'), array('size' => '40'));
 534          $mform->addRule('dropbox_cachelimit', null, 'numeric', null, 'client');
 535          $mform->setType('dropbox_cachelimit', PARAM_INT);
 536          $mform->addElement('static', 'dropbox_cachelimit_info', '',  get_string('cachelimit_info', 'repository_dropbox'));
 537  
 538      }
 539  
 540      /**
 541       * Set options.
 542       *
 543       * @param   array   $options
 544       * @return  mixed
 545       */
 546      public function set_option($options = []) {
 547          if (!empty($options['dropbox_key'])) {
 548              set_config('dropbox_key', trim($options['dropbox_key']), 'dropbox');
 549              unset($options['dropbox_key']);
 550          }
 551          if (!empty($options['dropbox_secret'])) {
 552              set_config('dropbox_secret', trim($options['dropbox_secret']), 'dropbox');
 553              unset($options['dropbox_secret']);
 554          }
 555          if (!empty($options['dropbox_cachelimit'])) {
 556              $this->cachelimit = (int) trim($options['dropbox_cachelimit']);
 557              set_config('dropbox_cachelimit', $this->cachelimit, 'dropbox');
 558              unset($options['dropbox_cachelimit']);
 559          }
 560  
 561          return parent::set_option($options);
 562      }
 563  
 564      /**
 565       * Get dropbox options
 566       * @param string $config
 567       * @return mixed
 568       */
 569      public function get_option($config = '') {
 570          if ($config === 'dropbox_key') {
 571              return trim(get_config('dropbox', 'dropbox_key'));
 572          } else if ($config === 'dropbox_secret') {
 573              return trim(get_config('dropbox', 'dropbox_secret'));
 574          } else if ($config === 'dropbox_cachelimit') {
 575              return $this->max_cache_bytes();
 576          } else {
 577              $options = parent::get_option();
 578              $options['dropbox_key'] = trim(get_config('dropbox', 'dropbox_key'));
 579              $options['dropbox_secret'] = trim(get_config('dropbox', 'dropbox_secret'));
 580              $options['dropbox_cachelimit'] = $this->max_cache_bytes();
 581          }
 582  
 583          return $options;
 584      }
 585  
 586      /**
 587       * Return the OAuth 2 Redirect URI.
 588       *
 589       * @return  moodle_url
 590       */
 591      public static function get_oauth2callbackurl() {
 592          global $CFG;
 593  
 594          return new moodle_url('/admin/oauth2callback.php');
 595      }
 596  
 597      /**
 598       * Option names of dropbox plugin.
 599       *
 600       * @inheritDocs
 601       */
 602      public static function get_type_option_names() {
 603          return [
 604                  'dropbox_key',
 605                  'dropbox_secret',
 606                  'pluginname',
 607                  'dropbox_cachelimit',
 608              ];
 609      }
 610  
 611      /**
 612       * Performs synchronisation of an external file if the previous one has expired.
 613       *
 614       * This function must be implemented for external repositories supporting
 615       * FILE_REFERENCE, it is called for existing aliases when their filesize,
 616       * contenthash or timemodified are requested. It is not called for internal
 617       * repositories (see {@link repository::has_moodle_files()}), references to
 618       * internal files are updated immediately when source is modified.
 619       *
 620       * Referenced files may optionally keep their content in Moodle filepool (for
 621       * thumbnail generation or to be able to serve cached copy). In this
 622       * case both contenthash and filesize need to be synchronized. Otherwise repositories
 623       * should use contenthash of empty file and correct filesize in bytes.
 624       *
 625       * Note that this function may be run for EACH file that needs to be synchronised at the
 626       * moment. If anything is being downloaded or requested from external sources there
 627       * should be a small timeout. The synchronisation is performed to update the size of
 628       * the file and/or to update image and re-generated image preview. There is nothing
 629       * fatal if syncronisation fails but it is fatal if syncronisation takes too long
 630       * and hangs the script generating a page.
 631       *
 632       * Note: If you wish to call $file->get_filesize(), $file->get_contenthash() or
 633       * $file->get_timemodified() make sure that recursion does not happen.
 634       *
 635       * Called from {@link stored_file::sync_external_file()}
 636       *
 637       * @inheritDocs
 638       */
 639      public function sync_reference(stored_file $file) {
 640          global $CFG;
 641  
 642          if ($file->get_referencelastsync() + DAYSECS > time()) {
 643              // Only synchronise once per day.
 644              return false;
 645          }
 646  
 647          $reference = $this->unpack_reference($file->get_reference());
 648          if (!isset($reference->url)) {
 649              // The URL to sync with is missing.
 650              return false;
 651          }
 652  
 653          $c = new curl;
 654          $url = $this->get_file_download_link($reference->url);
 655          if (file_extension_in_typegroup($reference->path, 'web_image')) {
 656              $saveas = $this->prepare_file('');
 657              try {
 658                  $result = $c->download_one($url, [], [
 659                          'filepath' => $saveas,
 660                          'timeout' => $CFG->repositorysyncimagetimeout,
 661                          'followlocation' => true,
 662                      ]);
 663                  $info = $c->get_info();
 664                  if ($result === true && isset($info['http_code']) && $info['http_code'] == 200) {
 665                      $file->set_synchronised_content_from_file($saveas);
 666                      return true;
 667                  }
 668              } catch (Exception $e) {
 669                  // IF the download_one fails, we will attempt to download
 670                  // again with get() anyway.
 671              }
 672          }
 673  
 674          $c->get($url, null, array('timeout' => $CFG->repositorysyncimagetimeout, 'followlocation' => true, 'nobody' => true));
 675          $info = $c->get_info();
 676          if (isset($info['http_code']) && $info['http_code'] == 200 &&
 677                  array_key_exists('download_content_length', $info) &&
 678                  $info['download_content_length'] >= 0) {
 679              $filesize = (int)$info['download_content_length'];
 680              $file->set_synchronized(null, $filesize);
 681              return true;
 682          }
 683          $file->set_missingsource();
 684          return true;
 685      }
 686  
 687      /**
 688       * Process a standard entries list.
 689       *
 690       * @param   array       $entries    The list of entries returned from the API
 691       * @return  array                   The manipulated entries for display in the file picker
 692       */
 693      protected function process_entries(array $entries) {
 694          global $OUTPUT;
 695  
 696          $dirslist   = [];
 697          $fileslist  = [];
 698          foreach ($entries as $entry) {
 699              $entrydata = $entry;
 700              if (isset($entrydata->metadata)) {
 701                  // If this is metadata, fetch the metadata content.
 702                  // We only use the consistent parts of the file, folder, and metadata.
 703                  $entrydata = $entrydata->metadata;
 704              }
 705  
 706              // Due to a change in the api, the actual content is in a nested metadata tree.
 707              if ($entrydata->{".tag"} == "metadata" && isset($entrydata->metadata)) {
 708                  $entrydata = $entrydata->metadata;
 709              }
 710  
 711              if ($entrydata->{".tag"} === "folder") {
 712                  $dirslist[] = [
 713                          'title'             => $entrydata->name,
 714                          // Use the display path here rather than lower.
 715                          // Dropbox is case insensitive but this leads to more accurate breadcrumbs.
 716                          'path'              => file_correct_filepath($entrydata->path_display),
 717                          'thumbnail'         => $OUTPUT->image_url(file_folder_icon(64))->out(false),
 718                          'thumbnail_height'  => 64,
 719                          'thumbnail_width'   => 64,
 720                          'children'          => array(),
 721                      ];
 722              } else if ($entrydata->{".tag"} === "file") {
 723                  $fileslist[] = [
 724                          'title'             => $entrydata->name,
 725                          // Use the path_lower here to make life easier elsewhere.
 726                          'source'            => $entrydata->path_lower,
 727                          'size'              => $entrydata->size,
 728                          'date'              => strtotime($entrydata->client_modified),
 729                          'thumbnail'         => $OUTPUT->image_url(file_extension_icon($entrydata->path_lower, 64))->out(false),
 730                          'realthumbnail'     => $this->get_thumbnail_url($entrydata),
 731                          'thumbnail_height'  => 64,
 732                          'thumbnail_width'   => 64,
 733                      ];
 734              }
 735          }
 736  
 737          $fileslist = array_filter($fileslist, array($this, 'filter'));
 738  
 739          return array_merge($dirslist, array_values($fileslist));
 740      }
 741  
 742      /**
 743       * Process the breadcrumbs for a listing.
 744       *
 745       * @param   string      $path       The path to create breadcrumbs for
 746       * @return  array
 747       */
 748      protected function process_breadcrumbs($path) {
 749          // Process breadcrumb trail.
 750          // Note: Dropbox is case insensitive.
 751          // Without performing an additional API call, it isn't possible to get the path_display.
 752          // As a result, the path here is the path_lower.
 753          $breadcrumbs = [
 754              [
 755                  'path' => '/',
 756                  'name' => get_string('dropbox', 'repository_dropbox'),
 757              ],
 758          ];
 759  
 760          $path = rtrim($path, '/');
 761          $directories = explode('/', $path);
 762          $pathtodate = '';
 763          foreach ($directories as $directory) {
 764              if ($directory === '') {
 765                  continue;
 766              }
 767              $pathtodate .= '/' . $directory;
 768              $breadcrumbs[] = [
 769                      'path'  => $pathtodate,
 770                      'name'  => $directory,
 771                  ];
 772          }
 773  
 774          return $breadcrumbs;
 775      }
 776  
 777      /**
 778       * Grab the thumbnail URL for the specified entry.
 779       *
 780       * @param   object      $entry      The file entry as retrieved from the API
 781       * @return  moodle_url
 782       */
 783      protected function get_thumbnail_url($entry) {
 784          if ($this->dropbox->supports_thumbnail($entry)) {
 785              $thumburl = new moodle_url('/repository/dropbox/thumbnail.php', [
 786                  // The id field in dropbox is unique - no need to specify a revision.
 787                  'source'    => $entry->id,
 788                  'path'      => $entry->path_lower,
 789  
 790                  'repo_id'   => $this->id,
 791                  'ctx_id'    => $this->context->id,
 792              ]);
 793              return $thumburl->out(false);
 794          }
 795  
 796          return '';
 797      }
 798  
 799      /**
 800       * Returns the maximum size of the Dropbox files to cache in moodle.
 801       *
 802       * Note that {@link repository_dropbox::sync_reference()} will try to cache images even
 803       * when they are bigger in order to generate thumbnails. However there is
 804       * a small timeout for downloading images for synchronisation and it will
 805       * probably fail if the image is too big.
 806       *
 807       * @return int
 808       */
 809      public function max_cache_bytes() {
 810          if ($this->cachelimit === null) {
 811              $this->cachelimit = (int) get_config('dropbox', 'dropbox_cachelimit');
 812          }
 813          return $this->cachelimit;
 814      }
 815  }