MDL-39177 More comments about confusing files.source field
[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      * Replaces the fields that might have changed when file was overriden in filepicker:
215      * reference, contenthash, filesize
216      *
217      * Note that field 'source' must be updated separately because
218      * it has different format for draft and non-draft areas and
219      * this function will usually be used to replace non-draft area
220      * file with draft area file.
221      *
222      * @param stored_file $newfile
223      * @throws coding_exception
224      */
225     public function replace_file_with(stored_file $newfile) {
226         if ($newfile->get_referencefileid() &&
227                 $this->fs->get_references_count_by_storedfile($this)) {
228             // The new file is a reference.
229             // The current file has other local files referencing to it.
230             // Double reference is not allowed.
231             throw new moodle_exception('errordoublereference', 'repository');
232         }
234         $filerecord = new stdClass;
235         $contenthash = $newfile->get_contenthash();
236         if ($this->fs->content_exists($contenthash)) {
237             $filerecord->contenthash = $contenthash;
238         } else {
239             throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
240         }
241         $filerecord->filesize = $newfile->get_filesize();
242         $filerecord->referencefileid = $newfile->get_referencefileid();
243         $this->update($filerecord);
244     }
246     /**
247      * Unlink the stored file from the referenced file
248      *
249      * This methods destroys the link to the record in files_reference table. This effectively
250      * turns the stored file from being an alias to a plain copy. However, the caller has
251      * to make sure that the actual file's content has beed synced prior to calling this method.
252      */
253     public function delete_reference() {
254         global $DB;
256         if (!$this->is_external_file()) {
257             throw new coding_exception('An attempt to unlink a non-reference file.');
258         }
260         $transaction = $DB->start_delegated_transaction();
262         // Are we the only one referring to the original file? If so, delete the
263         // referenced file record. Note we do not use file_storage::search_references_count()
264         // here because we want to count draft files too and we are at a bit lower access level here.
265         $countlinks = $DB->count_records('files',
266             array('referencefileid' => $this->file_record->referencefileid));
267         if ($countlinks == 1) {
268             $DB->delete_records('files_reference', array('id' => $this->file_record->referencefileid));
269         }
271         // Update the underlying record in the database.
272         $update = new stdClass();
273         $update->referencefileid = null;
274         $this->update($update);
276         $transaction->allow_commit();
278         // Update our properties and the record in the memory.
279         $this->repository = null;
280         $this->file_record->repositoryid = null;
281         $this->file_record->reference = null;
282         $this->file_record->referencefileid = null;
283         $this->file_record->referencelastsync = null;
284         $this->file_record->referencelifetime = null;
285     }
287     /**
288      * Is this a directory?
289      *
290      * Directories are only emulated, internally they are stored as empty
291      * files with a "." instead of name - this means empty directory contains
292      * exactly one empty file with name dot.
293      *
294      * @return bool true means directory, false means file
295      */
296     public function is_directory() {
297         return ($this->file_record->filename === '.');
298     }
300     /**
301      * Delete file from files table.
302      *
303      * The content of files stored in sha1 pool is reclaimed
304      * later - the occupied disk space is reclaimed much later.
305      *
306      * @return bool always true or exception if error occurred
307      */
308     public function delete() {
309         global $DB;
311         if ($this->is_directory()) {
312             // Directories can not be referenced, just delete the record.
313             $DB->delete_records('files', array('id'=>$this->file_record->id));
315         } else {
316             $transaction = $DB->start_delegated_transaction();
318             // If there are other files referring to this file, convert them to copies.
319             if ($files = $this->fs->get_references_by_storedfile($this)) {
320                 foreach ($files as $file) {
321                     $this->fs->import_external_file($file);
322                 }
323             }
325             // If this file is a reference (alias) to another file, unlink it first.
326             if ($this->is_external_file()) {
327                 $this->delete_reference();
328             }
330             // Now delete the file record.
331             $DB->delete_records('files', array('id'=>$this->file_record->id));
333             $transaction->allow_commit();
334         }
336         // Move pool file to trash if content not needed any more.
337         $this->fs->deleted_file_cleanup($this->file_record->contenthash);
338         return true; // BC only
339     }
341     /**
342      * Get file pathname by contenthash
343      *
344      * NOTE, this function is not calling sync_external_file, it assume the contenthash is current
345      * Protected - developers must not gain direct access to this function.
346      *
347      * @return string full path to pool file with file content
348      */
349     protected function get_pathname_by_contenthash() {
350         // Detect is local file or not.
351         $contenthash = $this->file_record->contenthash;
352         $l1 = $contenthash[0].$contenthash[1];
353         $l2 = $contenthash[2].$contenthash[3];
354         return "$this->filedir/$l1/$l2/$contenthash";
355     }
357     /**
358      * Get file pathname by given contenthash, this method will try to sync files
359      *
360      * Protected - developers must not gain direct access to this function.
361      *
362      * NOTE: do not make this public, we must not modify or delete the pool files directly! ;-)
363      *
364      * @return string full path to pool file with file content
365      **/
366     protected function get_content_file_location() {
367         $this->sync_external_file();
368         return $this->get_pathname_by_contenthash();
369     }
371     /**
372     * adds this file path to a curl request (POST only)
373     *
374     * @param curl $curlrequest the curl request object
375     * @param string $key what key to use in the POST request
376     * @return void
377     */
378     public function add_to_curl_request(&$curlrequest, $key) {
379         $curlrequest->_tmp_file_post_params[$key] = '@' . $this->get_content_file_location();
380     }
382     /**
383      * Returns file handle - read only mode, no writing allowed into pool files!
384      *
385      * When you want to modify a file, create a new file and delete the old one.
386      *
387      * @return resource file handle
388      */
389     public function get_content_file_handle() {
390         $path = $this->get_content_file_location();
391         if (!is_readable($path)) {
392             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
393                 throw new file_exception('storedfilecannotread', '', $path);
394             }
395         }
396         return fopen($path, 'rb'); // Binary reading only!!
397     }
399     /**
400      * Dumps file content to page.
401      */
402     public function readfile() {
403         $path = $this->get_content_file_location();
404         if (!is_readable($path)) {
405             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
406                 throw new file_exception('storedfilecannotread', '', $path);
407             }
408         }
409         readfile($path);
410     }
412     /**
413      * Returns file content as string.
414      *
415      * @return string content
416      */
417     public function get_content() {
418         $path = $this->get_content_file_location();
419         if (!is_readable($path)) {
420             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
421                 throw new file_exception('storedfilecannotread', '', $path);
422             }
423         }
424         return file_get_contents($this->get_content_file_location());
425     }
427     /**
428      * Copy content of file to given pathname.
429      *
430      * @param string $pathname real path to the new file
431      * @return bool success
432      */
433     public function copy_content_to($pathname) {
434         $path = $this->get_content_file_location();
435         if (!is_readable($path)) {
436             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
437                 throw new file_exception('storedfilecannotread', '', $path);
438             }
439         }
440         return copy($path, $pathname);
441     }
443     /**
444      * Copy content of file to temporary folder and returns file path
445      *
446      * @param string $dir name of the temporary directory
447      * @param string $fileprefix prefix of temporary file.
448      * @return string|bool path of temporary file or false.
449      */
450     public function copy_content_to_temp($dir = 'files', $fileprefix = 'tempup_') {
451         $tempfile = false;
452         if (!$dir = make_temp_directory($dir)) {
453             return false;
454         }
455         if (!$tempfile = tempnam($dir, $fileprefix)) {
456             return false;
457         }
458         if (!$this->copy_content_to($tempfile)) {
459             // something went wrong
460             @unlink($tempfile);
461             return false;
462         }
463         return $tempfile;
464     }
466     /**
467      * List contents of archive.
468      *
469      * @param file_packer $packer file packer instance
470      * @return array of file infos
471      */
472     public function list_files(file_packer $packer) {
473         $archivefile = $this->get_content_file_location();
474         return $packer->list_files($archivefile);
475     }
477     /**
478      * Extract file to given file path (real OS filesystem), existing files are overwritten.
479      *
480      * @param file_packer $packer file packer instance
481      * @param string $pathname target directory
482      * @return array|bool list of processed files; false if error
483      */
484     public function extract_to_pathname(file_packer $packer, $pathname) {
485         $archivefile = $this->get_content_file_location();
486         return $packer->extract_to_pathname($archivefile, $pathname);
487     }
489     /**
490      * Extract file to given file path (real OS filesystem), existing files are overwritten.
491      *
492      * @param file_packer $packer file packer instance
493      * @param int $contextid context ID
494      * @param string $component component
495      * @param string $filearea file area
496      * @param int $itemid item ID
497      * @param string $pathbase path base
498      * @param int $userid user ID
499      * @return array|bool list of processed files; false if error
500      */
501     public function extract_to_storage(file_packer $packer, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
502         $archivefile = $this->get_content_file_location();
503         return $packer->extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase);
504     }
506     /**
507      * Add file/directory into archive.
508      *
509      * @param file_archive $filearch file archive instance
510      * @param string $archivepath pathname in archive
511      * @return bool success
512      */
513     public function archive_file(file_archive $filearch, $archivepath) {
514         if ($this->is_directory()) {
515             return $filearch->add_directory($archivepath);
516         } else {
517             $path = $this->get_content_file_location();
518             if (!is_readable($path)) {
519                 return false;
520             }
521             return $filearch->add_file_from_pathname($archivepath, $path);
522         }
523     }
525     /**
526      * Returns information about image,
527      * information is determined from the file content
528      *
529      * @return mixed array with width, height and mimetype; false if not an image
530      */
531     public function get_imageinfo() {
532         $path = $this->get_content_file_location();
533         if (!is_readable($path)) {
534             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
535                 throw new file_exception('storedfilecannotread', '', $path);
536             }
537         }
538         $mimetype = $this->get_mimetype();
539         if (!preg_match('|^image/|', $mimetype) || !filesize($path) || !($imageinfo = getimagesize($path))) {
540             return false;
541         }
542         $image = array('width'=>$imageinfo[0], 'height'=>$imageinfo[1], 'mimetype'=>image_type_to_mime_type($imageinfo[2]));
543         if (empty($image['width']) or empty($image['height']) or empty($image['mimetype'])) {
544             // gd can not parse it, sorry
545             return false;
546         }
547         return $image;
548     }
550     /**
551      * Verifies the file is a valid web image - gif, png and jpeg only.
552      *
553      * It should be ok to serve this image from server without any other security workarounds.
554      *
555      * @return bool true if file ok
556      */
557     public function is_valid_image() {
558         $mimetype = $this->get_mimetype();
559         if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
560             return false;
561         }
562         if (!$info = $this->get_imageinfo()) {
563             return false;
564         }
565         if ($info['mimetype'] !== $mimetype) {
566             return false;
567         }
568         // ok, GD likes this image
569         return true;
570     }
572     /**
573      * Returns parent directory, creates missing parents if needed.
574      *
575      * @return stored_file
576      */
577     public function get_parent_directory() {
578         if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
579             //root dir does not have parent
580             return null;
581         }
583         if ($this->file_record->filename !== '.') {
584             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);
585         }
587         $filepath = $this->file_record->filepath;
588         $filepath = trim($filepath, '/');
589         $dirs = explode('/', $filepath);
590         array_pop($dirs);
591         $filepath = implode('/', $dirs);
592         $filepath = ($filepath === '') ? '/' : "/$filepath/";
594         return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
595     }
597     /**
598      * Synchronize file if it is a reference and needs synchronizing
599      *
600      * Updates contenthash and filesize
601      */
602     public function sync_external_file() {
603         global $CFG;
604         if (!empty($this->file_record->referencefileid)) {
605             require_once($CFG->dirroot.'/repository/lib.php');
606             repository::sync_external_file($this);
607         }
608     }
610     /**
611      * Returns context id of the file
612      *
613      * @return int context id
614      */
615     public function get_contextid() {
616         return $this->file_record->contextid;
617     }
619     /**
620      * Returns component name - this is the owner of the areas,
621      * nothing else is allowed to read or modify the files directly!!
622      *
623      * @return string
624      */
625     public function get_component() {
626         return $this->file_record->component;
627     }
629     /**
630      * Returns file area name, this divides files of one component into groups with different access control.
631      * All files in one area have the same access control.
632      *
633      * @return string
634      */
635     public function get_filearea() {
636         return $this->file_record->filearea;
637     }
639     /**
640      * Returns returns item id of file.
641      *
642      * @return int
643      */
644     public function get_itemid() {
645         return $this->file_record->itemid;
646     }
648     /**
649      * Returns file path - starts and ends with /, \ are not allowed.
650      *
651      * @return string
652      */
653     public function get_filepath() {
654         return $this->file_record->filepath;
655     }
657     /**
658      * Returns file name or '.' in case of directories.
659      *
660      * @return string
661      */
662     public function get_filename() {
663         return $this->file_record->filename;
664     }
666     /**
667      * Returns id of user who created the file.
668      *
669      * @return int
670      */
671     public function get_userid() {
672         return $this->file_record->userid;
673     }
675     /**
676      * Returns the size of file in bytes.
677      *
678      * @return int bytes
679      */
680     public function get_filesize() {
681         $this->sync_external_file();
682         return $this->file_record->filesize;
683     }
685     /**
686      * Returns the size of file in bytes.
687      *
688      * @param int $filesize bytes
689      */
690     public function set_filesize($filesize) {
691         $filerecord = new stdClass;
692         $filerecord->filesize = $filesize;
693         $this->update($filerecord);
694     }
696     /**
697      * Returns mime type of file.
698      *
699      * @return string
700      */
701     public function get_mimetype() {
702         return $this->file_record->mimetype;
703     }
705     /**
706      * Returns unix timestamp of file creation date.
707      *
708      * @return int
709      */
710     public function get_timecreated() {
711         return $this->file_record->timecreated;
712     }
714     /**
715      * Returns unix timestamp of last file modification.
716      *
717      * @return int
718      */
719     public function get_timemodified() {
720         $this->sync_external_file();
721         return $this->file_record->timemodified;
722     }
724     /**
725      * set timemodified
726      *
727      * @param int $timemodified
728      */
729     public function set_timemodified($timemodified) {
730         $filerecord = new stdClass;
731         $filerecord->timemodified = $timemodified;
732         $this->update($filerecord);
733     }
735     /**
736      * Returns file status flag.
737      *
738      * @return int 0 means file OK, anything else is a problem and file can not be used
739      */
740     public function get_status() {
741         return $this->file_record->status;
742     }
744     /**
745      * Returns file id.
746      *
747      * @return int
748      */
749     public function get_id() {
750         return $this->file_record->id;
751     }
753     /**
754      * Returns sha1 hash of file content.
755      *
756      * @return string
757      */
758     public function get_contenthash() {
759         $this->sync_external_file();
760         return $this->file_record->contenthash;
761     }
763     /**
764      * Set contenthash
765      *
766      * @param string $contenthash
767      */
768     protected function set_contenthash($contenthash) {
769         // make sure the content exists in moodle file pool
770         if ($this->fs->content_exists($contenthash)) {
771             $filerecord = new stdClass;
772             $filerecord->contenthash = $contenthash;
773             $this->update($filerecord);
774         } else {
775             throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
776         }
777     }
779     /**
780      * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
781      *
782      * @return string
783      */
784     public function get_pathnamehash() {
785         return $this->file_record->pathnamehash;
786     }
788     /**
789      * Returns the license type of the file, it is a short name referred from license table.
790      *
791      * @return string
792      */
793     public function get_license() {
794         return $this->file_record->license;
795     }
797     /**
798      * Set license
799      *
800      * @param string $license license
801      */
802     public function set_license($license) {
803         $filerecord = new stdClass;
804         $filerecord->license = $license;
805         $this->update($filerecord);
806     }
808     /**
809      * Returns the author name of the file.
810      *
811      * @return string
812      */
813     public function get_author() {
814         return $this->file_record->author;
815     }
817     /**
818      * Set author
819      *
820      * @param string $author
821      */
822     public function set_author($author) {
823         $filerecord = new stdClass;
824         $filerecord->author = $author;
825         $this->update($filerecord);
826     }
828     /**
829      * Returns the source of the file, usually it is a url.
830      *
831      * @return string
832      */
833     public function get_source() {
834         return $this->file_record->source;
835     }
837     /**
838      * Set license
839      *
840      * @param string $license license
841      */
842     public function set_source($source) {
843         $filerecord = new stdClass;
844         $filerecord->source = $source;
845         $this->update($filerecord);
846     }
849     /**
850      * Returns the sort order of file
851      *
852      * @return int
853      */
854     public function get_sortorder() {
855         return $this->file_record->sortorder;
856     }
858     /**
859      * Set file sort order
860      *
861      * @param int $sortorder
862      * @return int
863      */
864     public function set_sortorder($sortorder) {
865         $filerecord = new stdClass;
866         $filerecord->sortorder = $sortorder;
867         $this->update($filerecord);
868     }
870     /**
871      * Returns repository id
872      *
873      * @return int|null
874      */
875     public function get_repository_id() {
876         if (!empty($this->repository)) {
877             return $this->repository->id;
878         } else {
879             return null;
880         }
881     }
883     /**
884      * get reference file id
885      * @return int
886      */
887     public function get_referencefileid() {
888         return $this->file_record->referencefileid;
889     }
891     /**
892      * Get reference last sync time
893      * @return int
894      */
895     public function get_referencelastsync() {
896         return $this->file_record->referencelastsync;
897     }
899     /**
900      * Get reference last sync time
901      * @return int
902      */
903     public function get_referencelifetime() {
904         return $this->file_record->referencelifetime;
905     }
906     /**
907      * Returns file reference
908      *
909      * @return string
910      */
911     public function get_reference() {
912         return $this->file_record->reference;
913     }
915     /**
916      * Get human readable file reference information
917      *
918      * @return string
919      */
920     public function get_reference_details() {
921         return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
922     }
924     /**
925      * Called after reference-file has been synchronized with the repository
926      *
927      * We update contenthash, filesize and status in files table if changed
928      * and we always update lastsync in files_reference table
929      *
930      * @param string $contenthash
931      * @param int $filesize
932      * @param int $status
933      * @param int $lifetime the life time of this synchronisation results
934      */
935     public function set_synchronized($contenthash, $filesize, $status = 0, $lifetime = null) {
936         global $DB;
937         if (!$this->is_external_file()) {
938             return;
939         }
940         $now = time();
941         if ($contenthash != $this->file_record->contenthash) {
942             $oldcontenthash = $this->file_record->contenthash;
943         }
944         if ($lifetime === null) {
945             $lifetime = $this->file_record->referencelifetime;
946         }
947         // this will update all entries in {files} that have the same filereference id
948         $this->fs->update_references($this->file_record->referencefileid, $now, $lifetime, $contenthash, $filesize, $status);
949         // we don't need to call update() for this object, just set the values of changed fields
950         $this->file_record->contenthash = $contenthash;
951         $this->file_record->filesize = $filesize;
952         $this->file_record->status = $status;
953         $this->file_record->referencelastsync = $now;
954         $this->file_record->referencelifetime = $lifetime;
955         if (isset($oldcontenthash)) {
956             $this->fs->deleted_file_cleanup($oldcontenthash);
957         }
958     }
960     /**
961      * Sets the error status for a file that could not be synchronised
962      *
963      * @param int $lifetime the life time of this synchronisation results
964      */
965     public function set_missingsource($lifetime = null) {
966         $this->set_synchronized($this->get_contenthash(), $this->get_filesize(), 666, $lifetime);
967     }
969     /**
970      * Send file references
971      *
972      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
973      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
974      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
975      * @param array $options additional options affecting the file serving
976      */
977     public function send_file($lifetime, $filter, $forcedownload, $options) {
978         $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
979     }
981     /**
982      * Imports the contents of an external file into moodle filepool.
983      *
984      * @throws moodle_exception if file could not be downloaded or is too big
985      * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
986      */
987     public function import_external_file_contents($maxbytes = 0) {
988         if ($this->repository) {
989             $this->repository->import_external_file_contents($this, $maxbytes);
990         }
991     }