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