Merge branch 'w08_MDL-38154_m25_delperf' of git://github.com/skodak/moodle
[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         if ($this->is_directory()) {
279             // Directories can not be referenced, just delete the record.
280             $DB->delete_records('files', array('id'=>$this->file_record->id));
282         } else {
283             $transaction = $DB->start_delegated_transaction();
285             // If there are other files referring to this file, convert them to copies.
286             if ($files = $this->fs->get_references_by_storedfile($this)) {
287                 foreach ($files as $file) {
288                     $this->fs->import_external_file($file);
289                 }
290             }
292             // If this file is a reference (alias) to another file, unlink it first.
293             if ($this->is_external_file()) {
294                 $this->delete_reference();
295             }
297             // Now delete the file record.
298             $DB->delete_records('files', array('id'=>$this->file_record->id));
300             $transaction->allow_commit();
301         }
303         // Move pool file to trash if content not needed any more.
304         $this->fs->deleted_file_cleanup($this->file_record->contenthash);
305         return true; // BC only
306     }
308     /**
309      * Get file pathname by contenthash
310      *
311      * NOTE, this function is not calling sync_external_file, it assume the contenthash is current
312      * Protected - developers must not gain direct access to this function.
313      *
314      * @return string full path to pool file with file content
315      */
316     protected function get_pathname_by_contenthash() {
317         // Detect is local file or not.
318         $contenthash = $this->file_record->contenthash;
319         $l1 = $contenthash[0].$contenthash[1];
320         $l2 = $contenthash[2].$contenthash[3];
321         return "$this->filedir/$l1/$l2/$contenthash";
322     }
324     /**
325      * Get file pathname by given contenthash, this method will try to sync files
326      *
327      * Protected - developers must not gain direct access to this function.
328      *
329      * NOTE: do not make this public, we must not modify or delete the pool files directly! ;-)
330      *
331      * @return string full path to pool file with file content
332      **/
333     protected function get_content_file_location() {
334         $this->sync_external_file();
335         return $this->get_pathname_by_contenthash();
336     }
338     /**
339     * adds this file path to a curl request (POST only)
340     *
341     * @param curl $curlrequest the curl request object
342     * @param string $key what key to use in the POST request
343     * @return void
344     */
345     public function add_to_curl_request(&$curlrequest, $key) {
346         $curlrequest->_tmp_file_post_params[$key] = '@' . $this->get_content_file_location();
347     }
349     /**
350      * Returns file handle - read only mode, no writing allowed into pool files!
351      *
352      * When you want to modify a file, create a new file and delete the old one.
353      *
354      * @return resource file handle
355      */
356     public function get_content_file_handle() {
357         $path = $this->get_content_file_location();
358         if (!is_readable($path)) {
359             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
360                 throw new file_exception('storedfilecannotread', '', $path);
361             }
362         }
363         return fopen($path, 'rb'); // Binary reading only!!
364     }
366     /**
367      * Dumps file content to page.
368      */
369     public function readfile() {
370         $path = $this->get_content_file_location();
371         if (!is_readable($path)) {
372             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
373                 throw new file_exception('storedfilecannotread', '', $path);
374             }
375         }
376         readfile($path);
377     }
379     /**
380      * Returns file content as string.
381      *
382      * @return string content
383      */
384     public function get_content() {
385         $path = $this->get_content_file_location();
386         if (!is_readable($path)) {
387             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
388                 throw new file_exception('storedfilecannotread', '', $path);
389             }
390         }
391         return file_get_contents($this->get_content_file_location());
392     }
394     /**
395      * Copy content of file to given pathname.
396      *
397      * @param string $pathname real path to the new file
398      * @return bool success
399      */
400     public function copy_content_to($pathname) {
401         $path = $this->get_content_file_location();
402         if (!is_readable($path)) {
403             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
404                 throw new file_exception('storedfilecannotread', '', $path);
405             }
406         }
407         return copy($path, $pathname);
408     }
410     /**
411      * Copy content of file to temporary folder and returns file path
412      *
413      * @param string $dir name of the temporary directory
414      * @param string $fileprefix prefix of temporary file.
415      * @return string|bool path of temporary file or false.
416      */
417     public function copy_content_to_temp($dir = 'files', $fileprefix = 'tempup_') {
418         $tempfile = false;
419         if (!$dir = make_temp_directory($dir)) {
420             return false;
421         }
422         if (!$tempfile = tempnam($dir, $fileprefix)) {
423             return false;
424         }
425         if (!$this->copy_content_to($tempfile)) {
426             // something went wrong
427             @unlink($tempfile);
428             return false;
429         }
430         return $tempfile;
431     }
433     /**
434      * List contents of archive.
435      *
436      * @param file_packer $packer file packer instance
437      * @return array of file infos
438      */
439     public function list_files(file_packer $packer) {
440         $archivefile = $this->get_content_file_location();
441         return $packer->list_files($archivefile);
442     }
444     /**
445      * Extract file to given file path (real OS filesystem), existing files are overwritten.
446      *
447      * @param file_packer $packer file packer instance
448      * @param string $pathname target directory
449      * @return array|bool list of processed files; false if error
450      */
451     public function extract_to_pathname(file_packer $packer, $pathname) {
452         $archivefile = $this->get_content_file_location();
453         return $packer->extract_to_pathname($archivefile, $pathname);
454     }
456     /**
457      * Extract file to given file path (real OS filesystem), existing files are overwritten.
458      *
459      * @param file_packer $packer file packer instance
460      * @param int $contextid context ID
461      * @param string $component component
462      * @param string $filearea file area
463      * @param int $itemid item ID
464      * @param string $pathbase path base
465      * @param int $userid user ID
466      * @return array|bool list of processed files; false if error
467      */
468     public function extract_to_storage(file_packer $packer, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
469         $archivefile = $this->get_content_file_location();
470         return $packer->extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase);
471     }
473     /**
474      * Add file/directory into archive.
475      *
476      * @param file_archive $filearch file archive instance
477      * @param string $archivepath pathname in archive
478      * @return bool success
479      */
480     public function archive_file(file_archive $filearch, $archivepath) {
481         if ($this->is_directory()) {
482             return $filearch->add_directory($archivepath);
483         } else {
484             $path = $this->get_content_file_location();
485             if (!is_readable($path)) {
486                 return false;
487             }
488             return $filearch->add_file_from_pathname($archivepath, $path);
489         }
490     }
492     /**
493      * Returns information about image,
494      * information is determined from the file content
495      *
496      * @return mixed array with width, height and mimetype; false if not an image
497      */
498     public function get_imageinfo() {
499         $path = $this->get_content_file_location();
500         if (!is_readable($path)) {
501             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
502                 throw new file_exception('storedfilecannotread', '', $path);
503             }
504         }
505         $mimetype = $this->get_mimetype();
506         if (!preg_match('|^image/|', $mimetype) || !filesize($path) || !($imageinfo = getimagesize($path))) {
507             return false;
508         }
509         $image = array('width'=>$imageinfo[0], 'height'=>$imageinfo[1], 'mimetype'=>image_type_to_mime_type($imageinfo[2]));
510         if (empty($image['width']) or empty($image['height']) or empty($image['mimetype'])) {
511             // gd can not parse it, sorry
512             return false;
513         }
514         return $image;
515     }
517     /**
518      * Verifies the file is a valid web image - gif, png and jpeg only.
519      *
520      * It should be ok to serve this image from server without any other security workarounds.
521      *
522      * @return bool true if file ok
523      */
524     public function is_valid_image() {
525         $mimetype = $this->get_mimetype();
526         if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
527             return false;
528         }
529         if (!$info = $this->get_imageinfo()) {
530             return false;
531         }
532         if ($info['mimetype'] !== $mimetype) {
533             return false;
534         }
535         // ok, GD likes this image
536         return true;
537     }
539     /**
540      * Returns parent directory, creates missing parents if needed.
541      *
542      * @return stored_file
543      */
544     public function get_parent_directory() {
545         if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
546             //root dir does not have parent
547             return null;
548         }
550         if ($this->file_record->filename !== '.') {
551             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);
552         }
554         $filepath = $this->file_record->filepath;
555         $filepath = trim($filepath, '/');
556         $dirs = explode('/', $filepath);
557         array_pop($dirs);
558         $filepath = implode('/', $dirs);
559         $filepath = ($filepath === '') ? '/' : "/$filepath/";
561         return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
562     }
564     /**
565      * Synchronize file if it is a reference and needs synchronizing
566      *
567      * Updates contenthash and filesize
568      */
569     public function sync_external_file() {
570         global $CFG;
571         if (!empty($this->file_record->referencefileid)) {
572             require_once($CFG->dirroot.'/repository/lib.php');
573             repository::sync_external_file($this);
574         }
575     }
577     /**
578      * Returns context id of the file
579      *
580      * @return int context id
581      */
582     public function get_contextid() {
583         return $this->file_record->contextid;
584     }
586     /**
587      * Returns component name - this is the owner of the areas,
588      * nothing else is allowed to read or modify the files directly!!
589      *
590      * @return string
591      */
592     public function get_component() {
593         return $this->file_record->component;
594     }
596     /**
597      * Returns file area name, this divides files of one component into groups with different access control.
598      * All files in one area have the same access control.
599      *
600      * @return string
601      */
602     public function get_filearea() {
603         return $this->file_record->filearea;
604     }
606     /**
607      * Returns returns item id of file.
608      *
609      * @return int
610      */
611     public function get_itemid() {
612         return $this->file_record->itemid;
613     }
615     /**
616      * Returns file path - starts and ends with /, \ are not allowed.
617      *
618      * @return string
619      */
620     public function get_filepath() {
621         return $this->file_record->filepath;
622     }
624     /**
625      * Returns file name or '.' in case of directories.
626      *
627      * @return string
628      */
629     public function get_filename() {
630         return $this->file_record->filename;
631     }
633     /**
634      * Returns id of user who created the file.
635      *
636      * @return int
637      */
638     public function get_userid() {
639         return $this->file_record->userid;
640     }
642     /**
643      * Returns the size of file in bytes.
644      *
645      * @return int bytes
646      */
647     public function get_filesize() {
648         $this->sync_external_file();
649         return $this->file_record->filesize;
650     }
652     /**
653      * Returns the size of file in bytes.
654      *
655      * @param int $filesize bytes
656      */
657     public function set_filesize($filesize) {
658         $filerecord = new stdClass;
659         $filerecord->filesize = $filesize;
660         $this->update($filerecord);
661     }
663     /**
664      * Returns mime type of file.
665      *
666      * @return string
667      */
668     public function get_mimetype() {
669         return $this->file_record->mimetype;
670     }
672     /**
673      * Returns unix timestamp of file creation date.
674      *
675      * @return int
676      */
677     public function get_timecreated() {
678         return $this->file_record->timecreated;
679     }
681     /**
682      * Returns unix timestamp of last file modification.
683      *
684      * @return int
685      */
686     public function get_timemodified() {
687         $this->sync_external_file();
688         return $this->file_record->timemodified;
689     }
691     /**
692      * set timemodified
693      *
694      * @param int $timemodified
695      */
696     public function set_timemodified($timemodified) {
697         $filerecord = new stdClass;
698         $filerecord->timemodified = $timemodified;
699         $this->update($filerecord);
700     }
702     /**
703      * Returns file status flag.
704      *
705      * @return int 0 means file OK, anything else is a problem and file can not be used
706      */
707     public function get_status() {
708         return $this->file_record->status;
709     }
711     /**
712      * Returns file id.
713      *
714      * @return int
715      */
716     public function get_id() {
717         return $this->file_record->id;
718     }
720     /**
721      * Returns sha1 hash of file content.
722      *
723      * @return string
724      */
725     public function get_contenthash() {
726         $this->sync_external_file();
727         return $this->file_record->contenthash;
728     }
730     /**
731      * Set contenthash
732      *
733      * @param string $contenthash
734      */
735     protected function set_contenthash($contenthash) {
736         // make sure the content exists in moodle file pool
737         if ($this->fs->content_exists($contenthash)) {
738             $filerecord = new stdClass;
739             $filerecord->contenthash = $contenthash;
740             $this->update($filerecord);
741         } else {
742             throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
743         }
744     }
746     /**
747      * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
748      *
749      * @return string
750      */
751     public function get_pathnamehash() {
752         return $this->file_record->pathnamehash;
753     }
755     /**
756      * Returns the license type of the file, it is a short name referred from license table.
757      *
758      * @return string
759      */
760     public function get_license() {
761         return $this->file_record->license;
762     }
764     /**
765      * Set license
766      *
767      * @param string $license license
768      */
769     public function set_license($license) {
770         $filerecord = new stdClass;
771         $filerecord->license = $license;
772         $this->update($filerecord);
773     }
775     /**
776      * Returns the author name of the file.
777      *
778      * @return string
779      */
780     public function get_author() {
781         return $this->file_record->author;
782     }
784     /**
785      * Set author
786      *
787      * @param string $author
788      */
789     public function set_author($author) {
790         $filerecord = new stdClass;
791         $filerecord->author = $author;
792         $this->update($filerecord);
793     }
795     /**
796      * Returns the source of the file, usually it is a url.
797      *
798      * @return string
799      */
800     public function get_source() {
801         return $this->file_record->source;
802     }
804     /**
805      * Set license
806      *
807      * @param string $license license
808      */
809     public function set_source($source) {
810         $filerecord = new stdClass;
811         $filerecord->source = $source;
812         $this->update($filerecord);
813     }
816     /**
817      * Returns the sort order of file
818      *
819      * @return int
820      */
821     public function get_sortorder() {
822         return $this->file_record->sortorder;
823     }
825     /**
826      * Set file sort order
827      *
828      * @param int $sortorder
829      * @return int
830      */
831     public function set_sortorder($sortorder) {
832         $filerecord = new stdClass;
833         $filerecord->sortorder = $sortorder;
834         $this->update($filerecord);
835     }
837     /**
838      * Returns repository id
839      *
840      * @return int|null
841      */
842     public function get_repository_id() {
843         if (!empty($this->repository)) {
844             return $this->repository->id;
845         } else {
846             return null;
847         }
848     }
850     /**
851      * get reference file id
852      * @return int
853      */
854     public function get_referencefileid() {
855         return $this->file_record->referencefileid;
856     }
858     /**
859      * Get reference last sync time
860      * @return int
861      */
862     public function get_referencelastsync() {
863         return $this->file_record->referencelastsync;
864     }
866     /**
867      * Get reference last sync time
868      * @return int
869      */
870     public function get_referencelifetime() {
871         return $this->file_record->referencelifetime;
872     }
873     /**
874      * Returns file reference
875      *
876      * @return string
877      */
878     public function get_reference() {
879         return $this->file_record->reference;
880     }
882     /**
883      * Get human readable file reference information
884      *
885      * @return string
886      */
887     public function get_reference_details() {
888         return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
889     }
891     /**
892      * Called after reference-file has been synchronized with the repository
893      *
894      * We update contenthash, filesize and status in files table if changed
895      * and we always update lastsync in files_reference table
896      *
897      * @param string $contenthash
898      * @param int $filesize
899      * @param int $status
900      * @param int $lifetime the life time of this synchronisation results
901      */
902     public function set_synchronized($contenthash, $filesize, $status = 0, $lifetime = null) {
903         global $DB;
904         if (!$this->is_external_file()) {
905             return;
906         }
907         $now = time();
908         if ($contenthash != $this->file_record->contenthash) {
909             $oldcontenthash = $this->file_record->contenthash;
910         }
911         if ($lifetime === null) {
912             $lifetime = $this->file_record->referencelifetime;
913         }
914         // this will update all entries in {files} that have the same filereference id
915         $this->fs->update_references($this->file_record->referencefileid, $now, $lifetime, $contenthash, $filesize, $status);
916         // we don't need to call update() for this object, just set the values of changed fields
917         $this->file_record->contenthash = $contenthash;
918         $this->file_record->filesize = $filesize;
919         $this->file_record->status = $status;
920         $this->file_record->referencelastsync = $now;
921         $this->file_record->referencelifetime = $lifetime;
922         if (isset($oldcontenthash)) {
923             $this->fs->deleted_file_cleanup($oldcontenthash);
924         }
925     }
927     /**
928      * Sets the error status for a file that could not be synchronised
929      *
930      * @param int $lifetime the life time of this synchronisation results
931      */
932     public function set_missingsource($lifetime = null) {
933         $this->set_synchronized($this->get_contenthash(), $this->get_filesize(), 666, $lifetime);
934     }
936     /**
937      * Send file references
938      *
939      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
940      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
941      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
942      * @param array $options additional options affecting the file serving
943      */
944     public function send_file($lifetime, $filter, $forcedownload, $options) {
945         $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
946     }
948     /**
949      * Imports the contents of an external file into moodle filepool.
950      *
951      * @throws moodle_exception if file could not be downloaded or is too big
952      * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
953      */
954     public function import_external_file_contents($maxbytes = 0) {
955         if ($this->repository) {
956             $this->repository->import_external_file_contents($this, $maxbytes);
957         }
958     }