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