MDL-37792 Course - Fix upgrade step and set version number for completion field fix.
[moodle.git] / lib / filestorage / stored_file.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/>.
18 /**
19  * Definition of a class stored_file.
20  *
21  * @package   core_files
22  * @copyright 2008 Petr Skoda {@link http://skodak.org}
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 /**
29  * Class representing local files stored in a sha1 file pool.
30  *
31  * Since Moodle 2.0 file contents are stored in sha1 pool and
32  * all other file information is stored in new "files" database table.
33  *
34  * @package   core_files
35  * @category  files
36  * @copyright 2008 Petr Skoda {@link http://skodak.org}
37  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  * @since     Moodle 2.0
39  */
40 class stored_file {
41     /** @var file_storage file storage pool instance */
42     private $fs;
43     /** @var stdClass record from the files table left join files_reference table */
44     private $file_record;
45     /** @var string location of content files */
46     private $filedir;
47     /** @var repository repository plugin instance */
48     private $repository;
50     /**
51      * Constructor, this constructor should be called ONLY from the file_storage class!
52      *
53      * @param file_storage $fs file  storage instance
54      * @param stdClass $file_record description of file
55      * @param string $filedir location of file directory with sh1 named content files
56      */
57     public function __construct(file_storage $fs, stdClass $file_record, $filedir) {
58         global $DB, $CFG;
59         $this->fs          = $fs;
60         $this->file_record = clone($file_record); // prevent modifications
61         $this->filedir     = $filedir; // keep secret, do not expose!
63         if (!empty($file_record->repositoryid)) {
64             require_once("$CFG->dirroot/repository/lib.php");
65             $this->repository = repository::get_repository_by_id($file_record->repositoryid, SYSCONTEXTID);
66             if ($this->repository->supported_returntypes() & FILE_REFERENCE != FILE_REFERENCE) {
67                 // Repository cannot do file reference.
68                 throw new moodle_exception('error');
69             }
70         } else {
71             $this->repository = null;
72         }
73         // make sure all reference fields exist in file_record even when it is not a reference
74         foreach (array('referencelastsync', 'referencelifetime', 'referencefileid', 'reference', 'repositoryid') as $key) {
75             if (empty($this->file_record->$key)) {
76                 $this->file_record->$key = null;
77             }
78         }
79     }
81     /**
82      * Whether or not this is a external resource
83      *
84      * @return bool
85      */
86     public function is_external_file() {
87         return !empty($this->repository);
88     }
90     /**
91      * Update some file record fields
92      * NOTE: Must remain protected
93      *
94      * @param stdClass $dataobject
95      */
96     protected function update($dataobject) {
97         global $DB;
98         $keys = array_keys((array)$this->file_record);
99         foreach ($dataobject as $field => $value) {
100             if (in_array($field, $keys)) {
101                 if ($field == 'contextid' and (!is_number($value) or $value < 1)) {
102                     throw new file_exception('storedfileproblem', 'Invalid contextid');
103                 }
105                 if ($field == 'component') {
106                     $value = clean_param($value, PARAM_COMPONENT);
107                     if (empty($value)) {
108                         throw new file_exception('storedfileproblem', 'Invalid component');
109                     }
110                 }
112                 if ($field == 'filearea') {
113                     $value = clean_param($value, PARAM_AREA);
114                     if (empty($value)) {
115                         throw new file_exception('storedfileproblem', 'Invalid filearea');
116                     }
117                 }
119                 if ($field == 'itemid' and (!is_number($value) or $value < 0)) {
120                     throw new file_exception('storedfileproblem', 'Invalid itemid');
121                 }
124                 if ($field == 'filepath') {
125                     $value = clean_param($value, PARAM_PATH);
126                     if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
127                         // path must start and end with '/'
128                         throw new file_exception('storedfileproblem', 'Invalid file path');
129                     }
130                 }
132                 if ($field == 'filename') {
133                     // folder has filename == '.', so we pass this
134                     if ($value != '.') {
135                         $value = clean_param($value, PARAM_FILE);
136                     }
137                     if ($value === '') {
138                         throw new file_exception('storedfileproblem', 'Invalid file name');
139                     }
140                 }
142                 if ($field === 'timecreated' or $field === 'timemodified') {
143                     if (!is_number($value)) {
144                         throw new file_exception('storedfileproblem', 'Invalid timestamp');
145                     }
146                     if ($value < 0) {
147                         $value = 0;
148                     }
149                 }
151                 if ($field === 'referencefileid') {
152                     if (!is_null($value) and !is_number($value)) {
153                         throw new file_exception('storedfileproblem', 'Invalid reference info');
154                     }
155                 }
157                 if ($field === 'referencelastsync' or $field === 'referencelifetime') {
158                     // do not update those fields
159                     // TODO MDL-33416 [2.4] fields referencelastsync and referencelifetime to be removed from {files} table completely
160                     continue;
161                 }
163                 // adding the field
164                 $this->file_record->$field = $value;
165             } else {
166                 throw new coding_exception("Invalid field name, $field doesn't exist in file record");
167             }
168         }
169         // Validate mimetype field
170         // we don't use {@link stored_file::get_content_file_location()} here becaues it will try to update file_record
171         $pathname = $this->get_pathname_by_contenthash();
172         // try to recover the content from trash
173         if (!is_readable($pathname)) {
174             if (!$this->fs->try_content_recovery($this) or !is_readable($pathname)) {
175                 throw new file_exception('storedfilecannotread', '', $pathname);
176             }
177         }
178         $mimetype = $this->fs->mimetype($pathname, $this->file_record->filename);
179         $this->file_record->mimetype = $mimetype;
181         $DB->update_record('files', $this->file_record);
182     }
184     /**
185      * Rename filename
186      *
187      * @param string $filepath file path
188      * @param string $filename file name
189      */
190     public function rename($filepath, $filename) {
191         if ($this->fs->file_exists($this->get_contextid(), $this->get_component(), $this->get_filearea(), $this->get_itemid(), $filepath, $filename)) {
192             throw new file_exception('storedfilenotcreated', '', 'file exists, cannot rename');
193         }
194         $filerecord = new stdClass;
195         $filerecord->filepath = $filepath;
196         $filerecord->filename = $filename;
197         // populate the pathname hash
198         $filerecord->pathnamehash = $this->fs->get_pathname_hash($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath, $filename);
199         $this->update($filerecord);
200     }
202     /**
203      * Replace the content by providing another stored_file instance
204      *
205      * @param stored_file $storedfile
206      */
207     public function replace_content_with(stored_file $storedfile) {
208         $contenthash = $storedfile->get_contenthash();
209         $this->set_contenthash($contenthash);
210         $this->set_filesize($storedfile->get_filesize());
211     }
213     /**
214      * Unlink the stored file from the referenced file
215      *
216      * This methods destroys the link to the record in files_reference table. This effectively
217      * turns the stored file from being an alias to a plain copy. However, the caller has
218      * to make sure that the actual file's content has beed synced prior to calling this method.
219      */
220     public function delete_reference() {
221         global $DB;
223         if (!$this->is_external_file()) {
224             throw new coding_exception('An attempt to unlink a non-reference file.');
225         }
227         $transaction = $DB->start_delegated_transaction();
229         // Are we the only one referring to the original file? If so, delete the
230         // referenced file record. Note we do not use file_storage::search_references_count()
231         // here because we want to count draft files too and we are at a bit lower access level here.
232         $countlinks = $DB->count_records('files',
233             array('referencefileid' => $this->file_record->referencefileid));
234         if ($countlinks == 1) {
235             $DB->delete_records('files_reference', array('id' => $this->file_record->referencefileid));
236         }
238         // Update the underlying record in the database.
239         $update = new stdClass();
240         $update->referencefileid = null;
241         $this->update($update);
243         $transaction->allow_commit();
245         // Update our properties and the record in the memory.
246         $this->repository = null;
247         $this->file_record->repositoryid = null;
248         $this->file_record->reference = null;
249         $this->file_record->referencefileid = null;
250         $this->file_record->referencelastsync = null;
251         $this->file_record->referencelifetime = null;
252     }
254     /**
255      * Is this a directory?
256      *
257      * Directories are only emulated, internally they are stored as empty
258      * files with a "." instead of name - this means empty directory contains
259      * exactly one empty file with name dot.
260      *
261      * @return bool true means directory, false means file
262      */
263     public function is_directory() {
264         return ($this->file_record->filename === '.');
265     }
267     /**
268      * Delete file from files table.
269      *
270      * The content of files stored in sha1 pool is reclaimed
271      * later - the occupied disk space is reclaimed much later.
272      *
273      * @return bool always true or exception if error occurred
274      */
275     public function delete() {
276         global $DB;
278         $transaction = $DB->start_delegated_transaction();
280         // If there are other files referring to this file, convert them to copies.
281         if ($files = $this->fs->get_references_by_storedfile($this)) {
282             foreach ($files as $file) {
283                 $this->fs->import_external_file($file);
284             }
285         }
287         // If this file is a reference (alias) to another file, unlink it first.
288         if ($this->is_external_file()) {
289             $this->delete_reference();
290         }
292         // Now delete the file record.
293         $DB->delete_records('files', array('id'=>$this->file_record->id));
295         $transaction->allow_commit();
297         // moves pool file to trash if content not needed any more
298         $this->fs->deleted_file_cleanup($this->file_record->contenthash);
299         return true; // BC only
300     }
302     /**
303      * Get file pathname by contenthash
304      *
305      * NOTE, this function is not calling sync_external_file, it assume the contenthash is current
306      * Protected - developers must not gain direct access to this function.
307      *
308      * @return string full path to pool file with file content
309      */
310     protected function get_pathname_by_contenthash() {
311         // Detect is local file or not.
312         $contenthash = $this->file_record->contenthash;
313         $l1 = $contenthash[0].$contenthash[1];
314         $l2 = $contenthash[2].$contenthash[3];
315         return "$this->filedir/$l1/$l2/$contenthash";
316     }
318     /**
319      * Get file pathname by given contenthash, this method will try to sync files
320      *
321      * Protected - developers must not gain direct access to this function.
322      *
323      * NOTE: do not make this public, we must not modify or delete the pool files directly! ;-)
324      *
325      * @return string full path to pool file with file content
326      **/
327     protected function get_content_file_location() {
328         $this->sync_external_file();
329         return $this->get_pathname_by_contenthash();
330     }
332     /**
333     * adds this file path to a curl request (POST only)
334     *
335     * @param curl $curlrequest the curl request object
336     * @param string $key what key to use in the POST request
337     * @return void
338     */
339     public function add_to_curl_request(&$curlrequest, $key) {
340         $curlrequest->_tmp_file_post_params[$key] = '@' . $this->get_content_file_location();
341     }
343     /**
344      * Returns file handle - read only mode, no writing allowed into pool files!
345      *
346      * When you want to modify a file, create a new file and delete the old one.
347      *
348      * @return resource file handle
349      */
350     public function get_content_file_handle() {
351         $path = $this->get_content_file_location();
352         if (!is_readable($path)) {
353             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
354                 throw new file_exception('storedfilecannotread', '', $path);
355             }
356         }
357         return fopen($path, 'rb'); // Binary reading only!!
358     }
360     /**
361      * Dumps file content to page.
362      */
363     public function readfile() {
364         $path = $this->get_content_file_location();
365         if (!is_readable($path)) {
366             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
367                 throw new file_exception('storedfilecannotread', '', $path);
368             }
369         }
370         readfile($path);
371     }
373     /**
374      * Returns file content as string.
375      *
376      * @return string content
377      */
378     public function get_content() {
379         $path = $this->get_content_file_location();
380         if (!is_readable($path)) {
381             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
382                 throw new file_exception('storedfilecannotread', '', $path);
383             }
384         }
385         return file_get_contents($this->get_content_file_location());
386     }
388     /**
389      * Copy content of file to given pathname.
390      *
391      * @param string $pathname real path to the new file
392      * @return bool success
393      */
394     public function copy_content_to($pathname) {
395         $path = $this->get_content_file_location();
396         if (!is_readable($path)) {
397             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
398                 throw new file_exception('storedfilecannotread', '', $path);
399             }
400         }
401         return copy($path, $pathname);
402     }
404     /**
405      * Copy content of file to temporary folder and returns file path
406      *
407      * @param string $dir name of the temporary directory
408      * @param string $fileprefix prefix of temporary file.
409      * @return string|bool path of temporary file or false.
410      */
411     public function copy_content_to_temp($dir = 'files', $fileprefix = 'tempup_') {
412         $tempfile = false;
413         if (!$dir = make_temp_directory($dir)) {
414             return false;
415         }
416         if (!$tempfile = tempnam($dir, $fileprefix)) {
417             return false;
418         }
419         if (!$this->copy_content_to($tempfile)) {
420             // something went wrong
421             @unlink($tempfile);
422             return false;
423         }
424         return $tempfile;
425     }
427     /**
428      * List contents of archive.
429      *
430      * @param file_packer $packer file packer instance
431      * @return array of file infos
432      */
433     public function list_files(file_packer $packer) {
434         $archivefile = $this->get_content_file_location();
435         return $packer->list_files($archivefile);
436     }
438     /**
439      * Extract file to given file path (real OS filesystem), existing files are overwritten.
440      *
441      * @param file_packer $packer file packer instance
442      * @param string $pathname target directory
443      * @return array|bool list of processed files; false if error
444      */
445     public function extract_to_pathname(file_packer $packer, $pathname) {
446         $archivefile = $this->get_content_file_location();
447         return $packer->extract_to_pathname($archivefile, $pathname);
448     }
450     /**
451      * Extract file to given file path (real OS filesystem), existing files are overwritten.
452      *
453      * @param file_packer $packer file packer instance
454      * @param int $contextid context ID
455      * @param string $component component
456      * @param string $filearea file area
457      * @param int $itemid item ID
458      * @param string $pathbase path base
459      * @param int $userid user ID
460      * @return array|bool list of processed files; false if error
461      */
462     public function extract_to_storage(file_packer $packer, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
463         $archivefile = $this->get_content_file_location();
464         return $packer->extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase);
465     }
467     /**
468      * Add file/directory into archive.
469      *
470      * @param file_archive $filearch file archive instance
471      * @param string $archivepath pathname in archive
472      * @return bool success
473      */
474     public function archive_file(file_archive $filearch, $archivepath) {
475         if ($this->is_directory()) {
476             return $filearch->add_directory($archivepath);
477         } else {
478             $path = $this->get_content_file_location();
479             if (!is_readable($path)) {
480                 return false;
481             }
482             return $filearch->add_file_from_pathname($archivepath, $path);
483         }
484     }
486     /**
487      * Returns information about image,
488      * information is determined from the file content
489      *
490      * @return mixed array with width, height and mimetype; false if not an image
491      */
492     public function get_imageinfo() {
493         $path = $this->get_content_file_location();
494         if (!is_readable($path)) {
495             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
496                 throw new file_exception('storedfilecannotread', '', $path);
497             }
498         }
499         $mimetype = $this->get_mimetype();
500         if (!preg_match('|^image/|', $mimetype) || !filesize($path) || !($imageinfo = getimagesize($path))) {
501             return false;
502         }
503         $image = array('width'=>$imageinfo[0], 'height'=>$imageinfo[1], 'mimetype'=>image_type_to_mime_type($imageinfo[2]));
504         if (empty($image['width']) or empty($image['height']) or empty($image['mimetype'])) {
505             // gd can not parse it, sorry
506             return false;
507         }
508         return $image;
509     }
511     /**
512      * Verifies the file is a valid web image - gif, png and jpeg only.
513      *
514      * It should be ok to serve this image from server without any other security workarounds.
515      *
516      * @return bool true if file ok
517      */
518     public function is_valid_image() {
519         $mimetype = $this->get_mimetype();
520         if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
521             return false;
522         }
523         if (!$info = $this->get_imageinfo()) {
524             return false;
525         }
526         if ($info['mimetype'] !== $mimetype) {
527             return false;
528         }
529         // ok, GD likes this image
530         return true;
531     }
533     /**
534      * Returns parent directory, creates missing parents if needed.
535      *
536      * @return stored_file
537      */
538     public function get_parent_directory() {
539         if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
540             //root dir does not have parent
541             return null;
542         }
544         if ($this->file_record->filename !== '.') {
545             return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $this->file_record->filepath);
546         }
548         $filepath = $this->file_record->filepath;
549         $filepath = trim($filepath, '/');
550         $dirs = explode('/', $filepath);
551         array_pop($dirs);
552         $filepath = implode('/', $dirs);
553         $filepath = ($filepath === '') ? '/' : "/$filepath/";
555         return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
556     }
558     /**
559      * Synchronize file if it is a reference and needs synchronizing
560      *
561      * Updates contenthash and filesize
562      */
563     public function sync_external_file() {
564         global $CFG;
565         if (!empty($this->file_record->referencefileid)) {
566             require_once($CFG->dirroot.'/repository/lib.php');
567             repository::sync_external_file($this);
568         }
569     }
571     /**
572      * Returns context id of the file
573      *
574      * @return int context id
575      */
576     public function get_contextid() {
577         return $this->file_record->contextid;
578     }
580     /**
581      * Returns component name - this is the owner of the areas,
582      * nothing else is allowed to read or modify the files directly!!
583      *
584      * @return string
585      */
586     public function get_component() {
587         return $this->file_record->component;
588     }
590     /**
591      * Returns file area name, this divides files of one component into groups with different access control.
592      * All files in one area have the same access control.
593      *
594      * @return string
595      */
596     public function get_filearea() {
597         return $this->file_record->filearea;
598     }
600     /**
601      * Returns returns item id of file.
602      *
603      * @return int
604      */
605     public function get_itemid() {
606         return $this->file_record->itemid;
607     }
609     /**
610      * Returns file path - starts and ends with /, \ are not allowed.
611      *
612      * @return string
613      */
614     public function get_filepath() {
615         return $this->file_record->filepath;
616     }
618     /**
619      * Returns file name or '.' in case of directories.
620      *
621      * @return string
622      */
623     public function get_filename() {
624         return $this->file_record->filename;
625     }
627     /**
628      * Returns id of user who created the file.
629      *
630      * @return int
631      */
632     public function get_userid() {
633         return $this->file_record->userid;
634     }
636     /**
637      * Returns the size of file in bytes.
638      *
639      * @return int bytes
640      */
641     public function get_filesize() {
642         $this->sync_external_file();
643         return $this->file_record->filesize;
644     }
646     /**
647      * Returns the size of file in bytes.
648      *
649      * @param int $filesize bytes
650      */
651     public function set_filesize($filesize) {
652         $filerecord = new stdClass;
653         $filerecord->filesize = $filesize;
654         $this->update($filerecord);
655     }
657     /**
658      * Returns mime type of file.
659      *
660      * @return string
661      */
662     public function get_mimetype() {
663         return $this->file_record->mimetype;
664     }
666     /**
667      * Returns unix timestamp of file creation date.
668      *
669      * @return int
670      */
671     public function get_timecreated() {
672         return $this->file_record->timecreated;
673     }
675     /**
676      * Returns unix timestamp of last file modification.
677      *
678      * @return int
679      */
680     public function get_timemodified() {
681         $this->sync_external_file();
682         return $this->file_record->timemodified;
683     }
685     /**
686      * set timemodified
687      *
688      * @param int $timemodified
689      */
690     public function set_timemodified($timemodified) {
691         $filerecord = new stdClass;
692         $filerecord->timemodified = $timemodified;
693         $this->update($filerecord);
694     }
696     /**
697      * Returns file status flag.
698      *
699      * @return int 0 means file OK, anything else is a problem and file can not be used
700      */
701     public function get_status() {
702         return $this->file_record->status;
703     }
705     /**
706      * Returns file id.
707      *
708      * @return int
709      */
710     public function get_id() {
711         return $this->file_record->id;
712     }
714     /**
715      * Returns sha1 hash of file content.
716      *
717      * @return string
718      */
719     public function get_contenthash() {
720         $this->sync_external_file();
721         return $this->file_record->contenthash;
722     }
724     /**
725      * Set contenthash
726      *
727      * @param string $contenthash
728      */
729     protected function set_contenthash($contenthash) {
730         // make sure the content exists in moodle file pool
731         if ($this->fs->content_exists($contenthash)) {
732             $filerecord = new stdClass;
733             $filerecord->contenthash = $contenthash;
734             $this->update($filerecord);
735         } else {
736             throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
737         }
738     }
740     /**
741      * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
742      *
743      * @return string
744      */
745     public function get_pathnamehash() {
746         return $this->file_record->pathnamehash;
747     }
749     /**
750      * Returns the license type of the file, it is a short name referred from license table.
751      *
752      * @return string
753      */
754     public function get_license() {
755         return $this->file_record->license;
756     }
758     /**
759      * Set license
760      *
761      * @param string $license license
762      */
763     public function set_license($license) {
764         $filerecord = new stdClass;
765         $filerecord->license = $license;
766         $this->update($filerecord);
767     }
769     /**
770      * Returns the author name of the file.
771      *
772      * @return string
773      */
774     public function get_author() {
775         return $this->file_record->author;
776     }
778     /**
779      * Set author
780      *
781      * @param string $author
782      */
783     public function set_author($author) {
784         $filerecord = new stdClass;
785         $filerecord->author = $author;
786         $this->update($filerecord);
787     }
789     /**
790      * Returns the source of the file, usually it is a url.
791      *
792      * @return string
793      */
794     public function get_source() {
795         return $this->file_record->source;
796     }
798     /**
799      * Set license
800      *
801      * @param string $license license
802      */
803     public function set_source($source) {
804         $filerecord = new stdClass;
805         $filerecord->source = $source;
806         $this->update($filerecord);
807     }
810     /**
811      * Returns the sort order of file
812      *
813      * @return int
814      */
815     public function get_sortorder() {
816         return $this->file_record->sortorder;
817     }
819     /**
820      * Set file sort order
821      *
822      * @param int $sortorder
823      * @return int
824      */
825     public function set_sortorder($sortorder) {
826         $filerecord = new stdClass;
827         $filerecord->sortorder = $sortorder;
828         $this->update($filerecord);
829     }
831     /**
832      * Returns repository id
833      *
834      * @return int|null
835      */
836     public function get_repository_id() {
837         if (!empty($this->repository)) {
838             return $this->repository->id;
839         } else {
840             return null;
841         }
842     }
844     /**
845      * get reference file id
846      * @return int
847      */
848     public function get_referencefileid() {
849         return $this->file_record->referencefileid;
850     }
852     /**
853      * Get reference last sync time
854      * @return int
855      */
856     public function get_referencelastsync() {
857         return $this->file_record->referencelastsync;
858     }
860     /**
861      * Get reference last sync time
862      * @return int
863      */
864     public function get_referencelifetime() {
865         return $this->file_record->referencelifetime;
866     }
867     /**
868      * Returns file reference
869      *
870      * @return string
871      */
872     public function get_reference() {
873         return $this->file_record->reference;
874     }
876     /**
877      * Get human readable file reference information
878      *
879      * @return string
880      */
881     public function get_reference_details() {
882         return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
883     }
885     /**
886      * Called after reference-file has been synchronized with the repository
887      *
888      * We update contenthash, filesize and status in files table if changed
889      * and we always update lastsync in files_reference table
890      *
891      * @param string $contenthash
892      * @param int $filesize
893      * @param int $status
894      * @param int $lifetime the life time of this synchronisation results
895      */
896     public function set_synchronized($contenthash, $filesize, $status = 0, $lifetime = null) {
897         global $DB;
898         if (!$this->is_external_file()) {
899             return;
900         }
901         $now = time();
902         if ($contenthash != $this->file_record->contenthash) {
903             $oldcontenthash = $this->file_record->contenthash;
904         }
905         if ($lifetime === null) {
906             $lifetime = $this->file_record->referencelifetime;
907         }
908         // this will update all entries in {files} that have the same filereference id
909         $this->fs->update_references($this->file_record->referencefileid, $now, $lifetime, $contenthash, $filesize, $status);
910         // we don't need to call update() for this object, just set the values of changed fields
911         $this->file_record->contenthash = $contenthash;
912         $this->file_record->filesize = $filesize;
913         $this->file_record->status = $status;
914         $this->file_record->referencelastsync = $now;
915         $this->file_record->referencelifetime = $lifetime;
916         if (isset($oldcontenthash)) {
917             $this->fs->deleted_file_cleanup($oldcontenthash);
918         }
919     }
921     /**
922      * Sets the error status for a file that could not be synchronised
923      *
924      * @param int $lifetime the life time of this synchronisation results
925      */
926     public function set_missingsource($lifetime = null) {
927         $this->set_synchronized($this->get_contenthash(), $this->get_filesize(), 666, $lifetime);
928     }
930     /**
931      * Send file references
932      *
933      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
934      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
935      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
936      * @param array $options additional options affecting the file serving
937      */
938     public function send_file($lifetime, $filter, $forcedownload, $options) {
939         $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
940     }
942     /**
943      * Imports the contents of an external file into moodle filepool.
944      *
945      * @throws moodle_exception if file could not be downloaded or is too big
946      * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
947      */
948     public function import_external_file_contents($maxbytes = 0) {
949         if ($this->repository) {
950             $this->repository->import_external_file_contents($this, $maxbytes);
951         }
952     }