Merge branch 'wip-MDL-42016-master' of git://github.com/marinaglancy/moodle
[moodle.git] / repository / dropbox / lib.php
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/>.
17 /**
18  * This plugin is used to access user's dropbox files
19  *
20  * @since 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 require_once(dirname(__FILE__).'/locallib.php');
29 /**
30  * Repository to access Dropbox files
31  *
32  * @package    repository_dropbox
33  * @copyright  2010 Dongsheng Cai
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class repository_dropbox extends repository {
37     /** @var dropbox the instance of dropbox client */
38     private $dropbox;
39     /** @var array files */
40     public $files;
41     /** @var bool flag of login status */
42     public $logged=false;
43     /** @var int maximum size of file to cache in moodle filepool */
44     public $cachelimit=null;
46     /** @var int cached file ttl */
47     private $cachedfilettl = null;
49     /**
50      * Constructor of dropbox plugin
51      *
52      * @param int $repositoryid
53      * @param stdClass $context
54      * @param array $options
55      */
56     public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
57         global $CFG;
58         $options['page']    = optional_param('p', 1, PARAM_INT);
59         parent::__construct($repositoryid, $context, $options);
61         $this->setting = 'dropbox_';
63         $this->dropbox_key = $this->get_option('dropbox_key');
64         $this->dropbox_secret  = $this->get_option('dropbox_secret');
66         // one day
67         $this->cachedfilettl = 60 * 60 * 24;
69         if (isset($options['access_key'])) {
70             $this->access_key = $options['access_key'];
71         } else {
72             $this->access_key = get_user_preferences($this->setting.'_access_key', '');
73         }
74         if (isset($options['access_secret'])) {
75             $this->access_secret = $options['access_secret'];
76         } else {
77             $this->access_secret = get_user_preferences($this->setting.'_access_secret', '');
78         }
80         if (!empty($this->access_key) && !empty($this->access_secret)) {
81             $this->logged = true;
82         }
84         $callbackurl = new moodle_url($CFG->wwwroot.'/repository/repository_callback.php', array(
85             'callback'=>'yes',
86             'repo_id'=>$repositoryid
87             ));
89         $args = array(
90             'oauth_consumer_key'=>$this->dropbox_key,
91             'oauth_consumer_secret'=>$this->dropbox_secret,
92             'oauth_callback' => $callbackurl->out(false),
93             'api_root' => 'https://api.dropbox.com/1/oauth',
94         );
96         $this->dropbox = new dropbox($args);
97     }
99     /**
100      * Set access key
101      *
102      * @param string $access_key
103      */
104     public function set_access_key($access_key) {
105         $this->access_key = $access_key;
106     }
108     /**
109      * Set access secret
110      *
111      * @param string $access_secret
112      */
113     public function set_access_secret($access_secret) {
114         $this->access_secret = $access_secret;
115     }
118     /**
119      * Check if moodle has got access token and secret
120      *
121      * @return bool
122      */
123     public function check_login() {
124         return !empty($this->logged);
125     }
127     /**
128      * Generate dropbox login url
129      *
130      * @return array
131      */
132     public function print_login() {
133         $result = $this->dropbox->request_token();
134         set_user_preference($this->setting.'_request_secret', $result['oauth_token_secret']);
135         $url = $result['authorize_url'];
136         if ($this->options['ajax']) {
137             $ret = array();
138             $popup_btn = new stdClass();
139             $popup_btn->type = 'popup';
140             $popup_btn->url = $url;
141             $ret['login'] = array($popup_btn);
142             return $ret;
143         } else {
144             echo '<a target="_blank" href="'.$url.'">'.get_string('login', 'repository').'</a>';
145         }
146     }
148     /**
149      * Request access token
150      *
151      * @return array
152      */
153     public function callback() {
154         $token  = optional_param('oauth_token', '', PARAM_TEXT);
155         $secret = get_user_preferences($this->setting.'_request_secret', '');
156         $access_token = $this->dropbox->get_access_token($token, $secret);
157         set_user_preference($this->setting.'_access_key', $access_token['oauth_token']);
158         set_user_preference($this->setting.'_access_secret', $access_token['oauth_token_secret']);
159     }
161     /**
162      * Get dropbox files
163      *
164      * @param string $path
165      * @param int $page
166      * @return array
167      */
168     public function get_listing($path = '', $page = '1') {
169         global $OUTPUT;
170         if (empty($path) || $path=='/') {
171             $path = '/';
172         } else {
173             $path = file_correct_filepath($path);
174         }
175         $encoded_path = str_replace("%2F", "/", rawurlencode($path));
177         $list = array();
178         $list['list'] = array();
179         $list['manage'] = 'https://www.dropbox.com/home';
180         $list['dynload'] = true;
181         $list['nosearch'] = true;
182         $list['logouturl'] = 'https://www.dropbox.com/logout';
183         $list['message'] = get_string('logoutdesc', 'repository_dropbox');
184         // process breadcrumb trail
185         $list['path'] = array(
186             array('name'=>get_string('dropbox', 'repository_dropbox'), 'path'=>'/')
187         );
189         $result = $this->dropbox->get_listing($encoded_path, $this->access_key, $this->access_secret);
191         if (!is_object($result) || empty($result)) {
192             return $list;
193         }
194         if (empty($result->path)) {
195             $current_path = '/';
196         } else {
197             $current_path = file_correct_filepath($result->path);
198         }
200         $trail = '';
201         if (!empty($path)) {
202             $parts = explode('/', $path);
203             if (count($parts) > 1) {
204                 foreach ($parts as $part) {
205                     if (!empty($part)) {
206                         $trail .= ('/'.$part);
207                         $list['path'][] = array('name'=>$part, 'path'=>$trail);
208                     }
209                 }
210             } else {
211                 $list['path'][] = array('name'=>$path, 'path'=>$path);
212             }
213         }
215         if (!empty($result->error)) {
216             // reset access key
217             set_user_preference($this->setting.'_access_key', '');
218             set_user_preference($this->setting.'_access_secret', '');
219             throw new repository_exception('repositoryerror', 'repository', '', $result->error);
220         }
221         if (empty($result->contents) or !is_array($result->contents)) {
222             return $list;
223         }
224         $files = $result->contents;
225         $dirslist = array();
226         $fileslist = array();
227         foreach ($files as $file) {
228             if ($file->is_dir) {
229                 $dirslist[] = array(
230                     'title' => substr($file->path, strpos($file->path, $current_path)+strlen($current_path)),
231                     'path' => file_correct_filepath($file->path),
232                     'date' => strtotime($file->modified),
233                     'thumbnail' => $OUTPUT->pix_url(file_folder_icon(64))->out(false),
234                     'thumbnail_height' => 64,
235                     'thumbnail_width' => 64,
236                     'children' => array(),
237                 );
238             } else {
239                 $thumbnail = null;
240                 if ($file->thumb_exists) {
241                     $thumburl = new moodle_url('/repository/dropbox/thumbnail.php',
242                             array('repo_id' => $this->id,
243                                 'ctx_id' => $this->context->id,
244                                 'source' => $file->path,
245                                 'rev' => $file->rev // include revision to avoid cache problems
246                             ));
247                     $thumbnail = $thumburl->out(false);
248                 }
249                 $fileslist[] = array(
250                     'title' => substr($file->path, strpos($file->path, $current_path)+strlen($current_path)),
251                     'source' => $file->path,
252                     'size' => $file->bytes,
253                     'date' => strtotime($file->modified),
254                     'thumbnail' => $OUTPUT->pix_url(file_extension_icon($file->path, 64))->out(false),
255                     'realthumbnail' => $thumbnail,
256                     'thumbnail_height' => 64,
257                     'thumbnail_width' => 64,
258                 );
259             }
260         }
261         $fileslist = array_filter($fileslist, array($this, 'filter'));
262         $list['list'] = array_merge($dirslist, array_values($fileslist));
263         return $list;
264     }
266     /**
267      * Displays a thumbnail for current user's dropbox file
268      *
269      * @param string $string
270      */
271     public function send_thumbnail($source) {
272         $saveas = $this->prepare_file('');
273         try {
274             $access_key = get_user_preferences($this->setting.'_access_key', '');
275             $access_secret = get_user_preferences($this->setting.'_access_secret', '');
276             $this->dropbox->set_access_token($access_key, $access_secret);
277             $this->dropbox->get_thumbnail($source, $saveas, self::SYNCIMAGE_TIMEOUT);
278             $content = file_get_contents($saveas);
279             unlink($saveas);
280             // set 30 days lifetime for the image. If the image is changed in dropbox it will have
281             // different revision number and URL will be different. It is completely safe
282             // to cache thumbnail in the browser for a long time
283             send_file($content, basename($source), 30*24*60*60, 0, true);
284         } catch (Exception $e) {}
285     }
287     /**
288      * Logout from dropbox
289      * @return array
290      */
291     public function logout() {
292         set_user_preference($this->setting.'_access_key', '');
293         set_user_preference($this->setting.'_access_secret', '');
294         $this->access_key    = '';
295         $this->access_secret = '';
296         return $this->print_login();
297     }
299     /**
300      * Set dropbox option
301      * @param array $options
302      * @return mixed
303      */
304     public function set_option($options = array()) {
305         if (!empty($options['dropbox_key'])) {
306             set_config('dropbox_key', trim($options['dropbox_key']), 'dropbox');
307         }
308         if (!empty($options['dropbox_secret'])) {
309             set_config('dropbox_secret', trim($options['dropbox_secret']), 'dropbox');
310         }
311         if (!empty($options['dropbox_cachelimit'])) {
312             $this->cachelimit = (int)trim($options['dropbox_cachelimit']);
313             set_config('dropbox_cachelimit', $this->cachelimit, 'dropbox');
314         }
315         unset($options['dropbox_key']);
316         unset($options['dropbox_secret']);
317         unset($options['dropbox_cachelimit']);
318         $ret = parent::set_option($options);
319         return $ret;
320     }
322     /**
323      * Get dropbox options
324      * @param string $config
325      * @return mixed
326      */
327     public function get_option($config = '') {
328         if ($config==='dropbox_key') {
329             return trim(get_config('dropbox', 'dropbox_key'));
330         } elseif ($config==='dropbox_secret') {
331             return trim(get_config('dropbox', 'dropbox_secret'));
332         } elseif ($config==='dropbox_cachelimit') {
333             return $this->max_cache_bytes();
334         } else {
335             $options = parent::get_option();
336             $options['dropbox_key'] = trim(get_config('dropbox', 'dropbox_key'));
337             $options['dropbox_secret'] = trim(get_config('dropbox', 'dropbox_secret'));
338             $options['dropbox_cachelimit'] = $this->max_cache_bytes();
339         }
340         return $options;
341     }
343     /**
344      * Fixes references in DB that contains user credentials
345      *
346      * @param string $reference contents of DB field files_reference.reference
347      */
348     public function fix_old_style_reference($reference) {
349         $ref = unserialize($reference);
350         if (!isset($ref->url)) {
351             $this->dropbox->set_access_token($ref->access_key, $ref->access_secret);
352             $ref->url = $this->dropbox->get_file_share_link($ref->path, self::GETFILE_TIMEOUT);
353             if (!$ref->url) {
354                 // some error occurred, do not fix reference for now
355                 return $reference;
356             }
357         }
358         unset($ref->access_key);
359         unset($ref->access_secret);
360         $newreference = serialize($ref);
361         if ($newreference !== $reference) {
362             // we need to update references in the database
363             global $DB;
364             $params = array(
365                 'newreference' => $newreference,
366                 'newhash' => sha1($newreference),
367                 'reference' => $reference,
368                 'hash' => sha1($reference),
369                 'repoid' => $this->id
370             );
371             $refid = $DB->get_field_sql('SELECT id FROM {files_reference}
372                 WHERE reference = :reference AND referencehash = :hash
373                 AND repositoryid = :repoid', $params);
374             if (!$refid) {
375                 return $newreference;
376             }
377             $existingrefid = $DB->get_field_sql('SELECT id FROM {files_reference}
378                     WHERE reference = :newreference AND referencehash = :newhash
379                     AND repositoryid = :repoid', $params);
380             if ($existingrefid) {
381                 // the same reference already exists, we unlink all files from it,
382                 // link them to the current reference and remove the old one
383                 $DB->execute('UPDATE {files} SET referencefileid = :refid
384                     WHERE referencefileid = :existingrefid',
385                     array('refid' => $refid, 'existingrefid' => $existingrefid));
386                 $DB->delete_records('files_reference', array('id' => $existingrefid));
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     }
397     /**
398      * Converts a URL received from dropbox API function 'shares' into URL that
399      * can be used to download/access file directly
400      *
401      * @param string $sharedurl
402      * @return string
403      */
404     private function get_file_download_link($sharedurl) {
405         return preg_replace('|^(\w*://)www(.dropbox.com)|','\1dl\2',$sharedurl);
406     }
408     /**
409      * Downloads a file from external repository and saves it in temp dir
410      *
411      * @throws moodle_exception when file could not be downloaded
412      *
413      * @param string $reference the content of files.reference field or result of
414      * function {@link repository_dropbox::get_file_reference()}
415      * @param string $saveas filename (without path) to save the downloaded file in the
416      * temporary directory, if omitted or file already exists the new filename will be generated
417      * @return array with elements:
418      *   path: internal location of the file
419      *   url: URL to the source (from parameters)
420      */
421     public function get_file($reference, $saveas = '') {
422         $ref = unserialize($reference);
423         $saveas = $this->prepare_file($saveas);
424         if (isset($ref->access_key) && isset($ref->access_secret) && isset($ref->path)) {
425             $this->dropbox->set_access_token($ref->access_key, $ref->access_secret);
426             return $this->dropbox->get_file($ref->path, $saveas, self::GETFILE_TIMEOUT);
427         } else if (isset($ref->url)) {
428             $c = new curl;
429             $url = $this->get_file_download_link($ref->url);
430             $result = $c->download_one($url, null, array('filepath' => $saveas, 'timeout' => self::GETFILE_TIMEOUT, 'followlocation' => true));
431             $info = $c->get_info();
432             if ($result !== true || !isset($info['http_code']) || $info['http_code'] != 200) {
433                 throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
434             }
435             return array('path'=>$saveas, 'url'=>$url);
436         }
437         throw new moodle_exception('cannotdownload', 'repository');
438     }
439     /**
440      * Add Plugin settings input to Moodle form
441      *
442      * @param moodleform $mform Moodle form (passed by reference)
443      * @param string $classname repository class name
444      */
445     public static function type_config_form($mform, $classname = 'repository') {
446         global $CFG;
447         parent::type_config_form($mform);
448         $key    = get_config('dropbox', 'dropbox_key');
449         $secret = get_config('dropbox', 'dropbox_secret');
451         if (empty($key)) {
452             $key = '';
453         }
454         if (empty($secret)) {
455             $secret = '';
456         }
458         $strrequired = get_string('required');
460         $mform->addElement('text', 'dropbox_key', get_string('apikey', 'repository_dropbox'), array('value'=>$key,'size' => '40'));
461         $mform->setType('dropbox_key', PARAM_RAW_TRIMMED);
462         $mform->addElement('text', 'dropbox_secret', get_string('secret', 'repository_dropbox'), array('value'=>$secret,'size' => '40'));
464         $mform->addRule('dropbox_key', $strrequired, 'required', null, 'client');
465         $mform->addRule('dropbox_secret', $strrequired, 'required', null, 'client');
466         $mform->setType('dropbox_secret', PARAM_RAW_TRIMMED);
467         $str_getkey = get_string('instruction', 'repository_dropbox');
468         $mform->addElement('static', null, '',  $str_getkey);
470         $mform->addElement('text', 'dropbox_cachelimit', get_string('cachelimit', 'repository_dropbox'), array('size' => '40'));
471         $mform->addRule('dropbox_cachelimit', null, 'numeric', null, 'client');
472         $mform->setType('dropbox_cachelimit', PARAM_INT);
473         $mform->addElement('static', 'dropbox_cachelimit_info', '',  get_string('cachelimit_info', 'repository_dropbox'));
474     }
476     /**
477      * Option names of dropbox plugin
478      *
479      * @return array
480      */
481     public static function get_type_option_names() {
482         return array('dropbox_key', 'dropbox_secret', 'pluginname', 'dropbox_cachelimit');
483     }
485     /**
486      * Dropbox plugin supports all kinds of files
487      *
488      * @return array
489      */
490     public function supported_filetypes() {
491         return '*';
492     }
494     /**
495      * User cannot use the external link to dropbox
496      *
497      * @return int
498      */
499     public function supported_returntypes() {
500         return FILE_INTERNAL | FILE_REFERENCE | FILE_EXTERNAL;
501     }
503     /**
504      * Return file URL for external link
505      *
506      * @param string $reference the result of get_file_reference()
507      * @return string
508      */
509     public function get_link($reference) {
510         $ref = unserialize($reference);
511         if (!isset($ref->url)) {
512             $this->dropbox->set_access_token($ref->access_key, $ref->access_secret);
513             $ref->url = $this->dropbox->get_file_share_link($ref->path, self::GETFILE_TIMEOUT);
514         }
515         return $this->get_file_download_link($ref->url);
516     }
518     /**
519      * Prepare file reference information
520      *
521      * @param string $source
522      * @return string file referece
523      */
524     public function get_file_reference($source) {
525         global $USER;
526         $reference = new stdClass;
527         $reference->path = $source;
528         $reference->userid = $USER->id;
529         $reference->username = fullname($USER);
530         $reference->access_key = get_user_preferences($this->setting.'_access_key', '');
531         $reference->access_secret = get_user_preferences($this->setting.'_access_secret', '');
533         // by API we don't know if we need this reference to just download a file from dropbox
534         // into moodle filepool or create a reference. Since we need to create a shared link
535         // only in case of reference we analyze the script parameter
536         $usefilereference = optional_param('usefilereference', false, PARAM_BOOL);
537         if ($usefilereference) {
538             $this->dropbox->set_access_token($reference->access_key, $reference->access_secret);
539             $url = $this->dropbox->get_file_share_link($source, self::GETFILE_TIMEOUT);
540             if ($url) {
541                 unset($reference->access_key);
542                 unset($reference->access_secret);
543                 $reference->url = $url;
544             }
545         }
546         return serialize($reference);
547     }
549     public function sync_reference(stored_file $file) {
550         if ($file->get_referencelastsync() + DAYSECS > time()) {
551             // Synchronise not more often than once a day.
552             return false;
553         }
554         $ref = unserialize($file->get_reference());
555         if (!isset($ref->url)) {
556             // this is an old-style reference in DB. We need to fix it
557             $ref = unserialize($this->fix_old_style_reference($file->get_reference()));
558         }
559         if (!isset($ref->url)) {
560             return false;
561         }
562         $c = new curl;
563         $url = $this->get_file_download_link($ref->url);
564         if (file_extension_in_typegroup($ref->path, 'web_image')) {
565             $saveas = $this->prepare_file('');
566             try {
567                 $result = $c->download_one($url, array(), array('filepath' => $saveas, 'timeout' => self::SYNCIMAGE_TIMEOUT, 'followlocation' => true));
568                 $info = $c->get_info();
569                 if ($result === true && isset($info['http_code']) && $info['http_code'] == 200) {
570                     $fs = get_file_storage();
571                     list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($saveas);
572                     $file->set_synchronized($contenthash, $filesize);
573                     return true;
574                 }
575             } catch (Exception $e) {}
576         }
577         $c->get($url, null, array('timeout' => self::SYNCIMAGE_TIMEOUT, 'followlocation' => true, 'nobody' => true));
578         $info = $c->get_info();
579         if (isset($info['http_code']) && $info['http_code'] == 200 &&
580                 array_key_exists('download_content_length', $info) &&
581                 $info['download_content_length'] >= 0) {
582             $filesize = (int)$info['download_content_length'];
583             $file->set_synchronized(null, $filesize);
584             return true;
585         }
586         $file->set_missingsource();
587         return true;
588     }
590     /**
591      * Cache file from external repository by reference
592      *
593      * Dropbox repository regularly caches all external files that are smaller than
594      * {@link repository_dropbox::max_cache_bytes()}
595      *
596      * @param string $reference this reference is generated by
597      *                          repository::get_file_reference()
598      * @param stored_file $storedfile created file reference
599      */
600     public function cache_file_by_reference($reference, $storedfile) {
601         try {
602             $this->import_external_file_contents($storedfile, $this->max_cache_bytes());
603         } catch (Exception $e) {}
604     }
606     /**
607      * Return human readable reference information
608      * {@link stored_file::get_reference()}
609      *
610      * @param string $reference
611      * @param int $filestatus status of the file, 0 - ok, 666 - source missing
612      * @return string
613      */
614     public function get_reference_details($reference, $filestatus = 0) {
615         global $USER;
616         $ref  = unserialize($reference);
617         $detailsprefix = $this->get_name();
618         if (isset($ref->userid) && $ref->userid != $USER->id && isset($ref->username)) {
619             $detailsprefix .= ' ('.$ref->username.')';
620         }
621         $details = $detailsprefix;
622         if (isset($ref->path)) {
623             $details .= ': '. $ref->path;
624         }
625         if (isset($ref->path) && !$filestatus) {
626             // Indicate this is from dropbox with path
627             return $details;
628         } else {
629             if (isset($ref->url)) {
630                 $details = $detailsprefix. ': '. $ref->url;
631             }
632             return get_string('lostsource', 'repository', $details);
633         }
634     }
636     /**
637      * Return the source information
638      *
639      * @param string $source
640      * @return string
641      */
642     public function get_file_source_info($source) {
643         global $USER;
644         return 'Dropbox ('.fullname($USER).'): ' . $source;
645     }
647     /**
648      * Returns the maximum size of the Dropbox files to cache in moodle
649      *
650      * Note that {@link repository_dropbox::sync_reference()} will try to cache images even
651      * when they are bigger in order to generate thumbnails. However there is
652      * a small timeout for downloading images for synchronisation and it will
653      * probably fail if the image is too big.
654      *
655      * @return int
656      */
657     public function max_cache_bytes() {
658         if ($this->cachelimit === null) {
659             $this->cachelimit = (int)get_config('dropbox', 'dropbox_cachelimit');
660         }
661         return $this->cachelimit;
662     }
664     /**
665      * Repository method to serve the referenced file
666      *
667      * This method is ivoked from {@link send_stored_file()}.
668      * Dropbox repository first caches the file by reading it into temporary folder and then
669      * serves from there.
670      *
671      * @param stored_file $storedfile the file that contains the reference
672      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
673      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
674      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
675      * @param array $options additional options affecting the file serving
676      */
677     public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
678         $ref = unserialize($storedfile->get_reference());
679         if ($storedfile->get_filesize() > $this->max_cache_bytes()) {
680             header('Location: '.$this->get_file_download_link($ref->url));
681             die;
682         }
683         try {
684             $this->import_external_file_contents($storedfile, $this->max_cache_bytes());
685             if (!is_array($options)) {
686                 $options = array();
687             }
688             $options['sendcachedexternalfile'] = true;
689             send_stored_file($storedfile, $lifetime, $filter, $forcedownload, $options);
690         } catch (moodle_exception $e) {
691             // redirect to Dropbox, it will show the error.
692             // We redirect to Dropbox shared link, not to download link here!
693             header('Location: '.$ref->url);
694             die;
695         }
696     }
698     /**
699      * Caches all references to Dropbox files in moodle filepool
700      *
701      * Invoked by {@link repository_dropbox_cron()}. Only files smaller than
702      * {@link repository_dropbox::max_cache_bytes()} and only files which
703      * synchronisation timeout have not expired are cached.
704      */
705     public function cron() {
706         $fs = get_file_storage();
707         $files = $fs->get_external_files($this->id);
708         foreach ($files as $file) {
709             try {
710                 // This call will cache all files that are smaller than max_cache_bytes()
711                 // and synchronise file size of all others
712                 $this->import_external_file_contents($file, $this->max_cache_bytes());
713             } catch (moodle_exception $e) {}
714         }
715     }
718 /**
719  * Dropbox plugin cron task
720  */
721 function repository_dropbox_cron() {
722     $instances = repository::get_instances(array('type'=>'dropbox'));
723     foreach ($instances as $instance) {
724         $instance->cron();
725     }