Search moodle.org's
Developer Documentation

  • 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.
  • Differences Between: [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311]

       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  }