MDL-33416 files: Remove unused reference fields in files table
[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 require_once($CFG->dirroot . '/lib/filestorage/file_progress.php');
30 /**
31  * Class representing local files stored in a sha1 file pool.
32  *
33  * Since Moodle 2.0 file contents are stored in sha1 pool and
34  * all other file information is stored in new "files" database table.
35  *
36  * @package   core_files
37  * @category  files
38  * @copyright 2008 Petr Skoda {@link http://skodak.org}
39  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40  * @since     Moodle 2.0
41  */
42 class stored_file {
43     /** @var file_storage file storage pool instance */
44     private $fs;
45     /** @var stdClass record from the files table left join files_reference table */
46     private $file_record;
47     /** @var string location of content files */
48     private $filedir;
49     /** @var repository repository plugin instance */
50     private $repository;
52     /**
53      * Constructor, this constructor should be called ONLY from the file_storage class!
54      *
55      * @param file_storage $fs file  storage instance
56      * @param stdClass $file_record description of file
57      * @param string $filedir location of file directory with sh1 named content files
58      */
59     public function __construct(file_storage $fs, stdClass $file_record, $filedir) {
60         global $DB, $CFG;
61         $this->fs          = $fs;
62         $this->file_record = clone($file_record); // prevent modifications
63         $this->filedir     = $filedir; // keep secret, do not expose!
65         if (!empty($file_record->repositoryid)) {
66             require_once("$CFG->dirroot/repository/lib.php");
67             $this->repository = repository::get_repository_by_id($file_record->repositoryid, SYSCONTEXTID);
68             if ($this->repository->supported_returntypes() & FILE_REFERENCE != FILE_REFERENCE) {
69                 // Repository cannot do file reference.
70                 throw new moodle_exception('error');
71             }
72         } else {
73             $this->repository = null;
74         }
75         // make sure all reference fields exist in file_record even when it is not a reference
76         foreach (array('referencelastsync', 'referencelifetime', 'referencefileid', 'reference', 'repositoryid') as $key) {
77             if (empty($this->file_record->$key)) {
78                 $this->file_record->$key = null;
79             }
80         }
81     }
83     /**
84      * Whether or not this is a external resource
85      *
86      * @return bool
87      */
88     public function is_external_file() {
89         return !empty($this->repository);
90     }
92     /**
93      * Update some file record fields
94      * NOTE: Must remain protected
95      *
96      * @param stdClass $dataobject
97      */
98     protected function update($dataobject) {
99         global $DB;
100         $keys = array_keys((array)$this->file_record);
101         foreach ($dataobject as $field => $value) {
102             if (in_array($field, $keys)) {
103                 if ($field == 'contextid' and (!is_number($value) or $value < 1)) {
104                     throw new file_exception('storedfileproblem', 'Invalid contextid');
105                 }
107                 if ($field == 'component') {
108                     $value = clean_param($value, PARAM_COMPONENT);
109                     if (empty($value)) {
110                         throw new file_exception('storedfileproblem', 'Invalid component');
111                     }
112                 }
114                 if ($field == 'filearea') {
115                     $value = clean_param($value, PARAM_AREA);
116                     if (empty($value)) {
117                         throw new file_exception('storedfileproblem', 'Invalid filearea');
118                     }
119                 }
121                 if ($field == 'itemid' and (!is_number($value) or $value < 0)) {
122                     throw new file_exception('storedfileproblem', 'Invalid itemid');
123                 }
126                 if ($field == 'filepath') {
127                     $value = clean_param($value, PARAM_PATH);
128                     if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
129                         // path must start and end with '/'
130                         throw new file_exception('storedfileproblem', 'Invalid file path');
131                     }
132                 }
134                 if ($field == 'filename') {
135                     // folder has filename == '.', so we pass this
136                     if ($value != '.') {
137                         $value = clean_param($value, PARAM_FILE);
138                     }
139                     if ($value === '') {
140                         throw new file_exception('storedfileproblem', 'Invalid file name');
141                     }
142                 }
144                 if ($field === 'timecreated' or $field === 'timemodified') {
145                     if (!is_number($value)) {
146                         throw new file_exception('storedfileproblem', 'Invalid timestamp');
147                     }
148                     if ($value < 0) {
149                         $value = 0;
150                     }
151                 }
153                 if ($field === 'referencefileid') {
154                     if (!is_null($value) and !is_number($value)) {
155                         throw new file_exception('storedfileproblem', 'Invalid reference info');
156                     }
157                 }
159                 // adding the field
160                 $this->file_record->$field = $value;
161             } else {
162                 throw new coding_exception("Invalid field name, $field doesn't exist in file record");
163             }
164         }
165         // Validate mimetype field
166         // we don't use {@link stored_file::get_content_file_location()} here becaues it will try to update file_record
167         $pathname = $this->get_pathname_by_contenthash();
168         // try to recover the content from trash
169         if (!is_readable($pathname)) {
170             if (!$this->fs->try_content_recovery($this) or !is_readable($pathname)) {
171                 throw new file_exception('storedfilecannotread', '', $pathname);
172             }
173         }
174         $mimetype = $this->fs->mimetype($pathname, $this->file_record->filename);
175         $this->file_record->mimetype = $mimetype;
177         $DB->update_record('files', $this->file_record);
178     }
180     /**
181      * Rename filename
182      *
183      * @param string $filepath file path
184      * @param string $filename file name
185      */
186     public function rename($filepath, $filename) {
187         if ($this->fs->file_exists($this->get_contextid(), $this->get_component(), $this->get_filearea(), $this->get_itemid(), $filepath, $filename)) {
188             throw new file_exception('storedfilenotcreated', '', 'file exists, cannot rename');
189         }
190         $filerecord = new stdClass;
191         $filerecord->filepath = $filepath;
192         $filerecord->filename = $filename;
193         // populate the pathname hash
194         $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);
195         $this->update($filerecord);
196     }
198     /**
199      * Replace the content by providing another stored_file instance
200      *
201      * @param stored_file $storedfile
202      */
203     public function replace_content_with(stored_file $storedfile) {
204         $contenthash = $storedfile->get_contenthash();
205         $this->set_contenthash($contenthash);
206         $this->set_filesize($storedfile->get_filesize());
207     }
209     /**
210      * Replaces the fields that might have changed when file was overriden in filepicker:
211      * reference, contenthash, filesize, userid
212      *
213      * Note that field 'source' must be updated separately because
214      * it has different format for draft and non-draft areas and
215      * this function will usually be used to replace non-draft area
216      * file with draft area file.
217      *
218      * @param stored_file $newfile
219      * @throws coding_exception
220      */
221     public function replace_file_with(stored_file $newfile) {
222         if ($newfile->get_referencefileid() &&
223                 $this->fs->get_references_count_by_storedfile($this)) {
224             // The new file is a reference.
225             // The current file has other local files referencing to it.
226             // Double reference is not allowed.
227             throw new moodle_exception('errordoublereference', 'repository');
228         }
230         $filerecord = new stdClass;
231         $contenthash = $newfile->get_contenthash();
232         if ($this->fs->content_exists($contenthash)) {
233             $filerecord->contenthash = $contenthash;
234         } else {
235             throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
236         }
237         $filerecord->filesize = $newfile->get_filesize();
238         $filerecord->referencefileid = $newfile->get_referencefileid();
239         $filerecord->userid = $newfile->get_userid();
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_allow_large($path, $this->get_filesize());
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      * @param file_progress $progress Progress indicator callback or null if not required
480      * @return array|bool list of processed files; false if error
481      */
482     public function extract_to_pathname(file_packer $packer, $pathname,
483             file_progress $progress = null) {
484         $archivefile = $this->get_content_file_location();
485         return $packer->extract_to_pathname($archivefile, $pathname, null, $progress);
486     }
488     /**
489      * Extract file to given file path (real OS filesystem), existing files are overwritten.
490      *
491      * @param file_packer $packer file packer instance
492      * @param int $contextid context ID
493      * @param string $component component
494      * @param string $filearea file area
495      * @param int $itemid item ID
496      * @param string $pathbase path base
497      * @param int $userid user ID
498      * @param file_progress $progress Progress indicator callback or null if not required
499      * @return array|bool list of processed files; false if error
500      */
501     public function extract_to_storage(file_packer $packer, $contextid,
502             $component, $filearea, $itemid, $pathbase, $userid = null, file_progress $progress = null) {
503         $archivefile = $this->get_content_file_location();
504         return $packer->extract_to_storage($archivefile, $contextid,
505                 $component, $filearea, $itemid, $pathbase, $userid, $progress);
506     }
508     /**
509      * Add file/directory into archive.
510      *
511      * @param file_archive $filearch file archive instance
512      * @param string $archivepath pathname in archive
513      * @return bool success
514      */
515     public function archive_file(file_archive $filearch, $archivepath) {
516         if ($this->is_directory()) {
517             return $filearch->add_directory($archivepath);
518         } else {
519             $path = $this->get_content_file_location();
520             if (!is_readable($path)) {
521                 return false;
522             }
523             return $filearch->add_file_from_pathname($archivepath, $path);
524         }
525     }
527     /**
528      * Returns information about image,
529      * information is determined from the file content
530      *
531      * @return mixed array with width, height and mimetype; false if not an image
532      */
533     public function get_imageinfo() {
534         $path = $this->get_content_file_location();
535         if (!is_readable($path)) {
536             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
537                 throw new file_exception('storedfilecannotread', '', $path);
538             }
539         }
540         $mimetype = $this->get_mimetype();
541         if (!preg_match('|^image/|', $mimetype) || !filesize($path) || !($imageinfo = getimagesize($path))) {
542             return false;
543         }
544         $image = array('width'=>$imageinfo[0], 'height'=>$imageinfo[1], 'mimetype'=>image_type_to_mime_type($imageinfo[2]));
545         if (empty($image['width']) or empty($image['height']) or empty($image['mimetype'])) {
546             // gd can not parse it, sorry
547             return false;
548         }
549         return $image;
550     }
552     /**
553      * Verifies the file is a valid web image - gif, png and jpeg only.
554      *
555      * It should be ok to serve this image from server without any other security workarounds.
556      *
557      * @return bool true if file ok
558      */
559     public function is_valid_image() {
560         $mimetype = $this->get_mimetype();
561         if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
562             return false;
563         }
564         if (!$info = $this->get_imageinfo()) {
565             return false;
566         }
567         if ($info['mimetype'] !== $mimetype) {
568             return false;
569         }
570         // ok, GD likes this image
571         return true;
572     }
574     /**
575      * Returns parent directory, creates missing parents if needed.
576      *
577      * @return stored_file
578      */
579     public function get_parent_directory() {
580         if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
581             //root dir does not have parent
582             return null;
583         }
585         if ($this->file_record->filename !== '.') {
586             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);
587         }
589         $filepath = $this->file_record->filepath;
590         $filepath = trim($filepath, '/');
591         $dirs = explode('/', $filepath);
592         array_pop($dirs);
593         $filepath = implode('/', $dirs);
594         $filepath = ($filepath === '') ? '/' : "/$filepath/";
596         return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
597     }
599     /**
600      * Synchronize file if it is a reference and needs synchronizing
601      *
602      * Updates contenthash and filesize
603      */
604     public function sync_external_file() {
605         global $CFG;
606         if (!empty($this->file_record->referencefileid)) {
607             require_once($CFG->dirroot.'/repository/lib.php');
608             repository::sync_external_file($this);
609         }
610     }
612     /**
613      * Returns context id of the file
614      *
615      * @return int context id
616      */
617     public function get_contextid() {
618         return $this->file_record->contextid;
619     }
621     /**
622      * Returns component name - this is the owner of the areas,
623      * nothing else is allowed to read or modify the files directly!!
624      *
625      * @return string
626      */
627     public function get_component() {
628         return $this->file_record->component;
629     }
631     /**
632      * Returns file area name, this divides files of one component into groups with different access control.
633      * All files in one area have the same access control.
634      *
635      * @return string
636      */
637     public function get_filearea() {
638         return $this->file_record->filearea;
639     }
641     /**
642      * Returns returns item id of file.
643      *
644      * @return int
645      */
646     public function get_itemid() {
647         return $this->file_record->itemid;
648     }
650     /**
651      * Returns file path - starts and ends with /, \ are not allowed.
652      *
653      * @return string
654      */
655     public function get_filepath() {
656         return $this->file_record->filepath;
657     }
659     /**
660      * Returns file name or '.' in case of directories.
661      *
662      * @return string
663      */
664     public function get_filename() {
665         return $this->file_record->filename;
666     }
668     /**
669      * Returns id of user who created the file.
670      *
671      * @return int
672      */
673     public function get_userid() {
674         return $this->file_record->userid;
675     }
677     /**
678      * Returns the size of file in bytes.
679      *
680      * @return int bytes
681      */
682     public function get_filesize() {
683         $this->sync_external_file();
684         return $this->file_record->filesize;
685     }
687     /**
688      * Returns the size of file in bytes.
689      *
690      * @param int $filesize bytes
691      */
692     public function set_filesize($filesize) {
693         $filerecord = new stdClass;
694         $filerecord->filesize = $filesize;
695         $this->update($filerecord);
696     }
698     /**
699      * Returns mime type of file.
700      *
701      * @return string
702      */
703     public function get_mimetype() {
704         return $this->file_record->mimetype;
705     }
707     /**
708      * Returns unix timestamp of file creation date.
709      *
710      * @return int
711      */
712     public function get_timecreated() {
713         return $this->file_record->timecreated;
714     }
716     /**
717      * Returns unix timestamp of last file modification.
718      *
719      * @return int
720      */
721     public function get_timemodified() {
722         $this->sync_external_file();
723         return $this->file_record->timemodified;
724     }
726     /**
727      * set timemodified
728      *
729      * @param int $timemodified
730      */
731     public function set_timemodified($timemodified) {
732         $filerecord = new stdClass;
733         $filerecord->timemodified = $timemodified;
734         $this->update($filerecord);
735     }
737     /**
738      * Returns file status flag.
739      *
740      * @return int 0 means file OK, anything else is a problem and file can not be used
741      */
742     public function get_status() {
743         return $this->file_record->status;
744     }
746     /**
747      * Returns file id.
748      *
749      * @return int
750      */
751     public function get_id() {
752         return $this->file_record->id;
753     }
755     /**
756      * Returns sha1 hash of file content.
757      *
758      * @return string
759      */
760     public function get_contenthash() {
761         $this->sync_external_file();
762         return $this->file_record->contenthash;
763     }
765     /**
766      * Set contenthash
767      *
768      * @param string $contenthash
769      */
770     protected function set_contenthash($contenthash) {
771         // make sure the content exists in moodle file pool
772         if ($this->fs->content_exists($contenthash)) {
773             $filerecord = new stdClass;
774             $filerecord->contenthash = $contenthash;
775             $this->update($filerecord);
776         } else {
777             throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
778         }
779     }
781     /**
782      * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
783      *
784      * @return string
785      */
786     public function get_pathnamehash() {
787         return $this->file_record->pathnamehash;
788     }
790     /**
791      * Returns the license type of the file, it is a short name referred from license table.
792      *
793      * @return string
794      */
795     public function get_license() {
796         return $this->file_record->license;
797     }
799     /**
800      * Set license
801      *
802      * @param string $license license
803      */
804     public function set_license($license) {
805         $filerecord = new stdClass;
806         $filerecord->license = $license;
807         $this->update($filerecord);
808     }
810     /**
811      * Returns the author name of the file.
812      *
813      * @return string
814      */
815     public function get_author() {
816         return $this->file_record->author;
817     }
819     /**
820      * Set author
821      *
822      * @param string $author
823      */
824     public function set_author($author) {
825         $filerecord = new stdClass;
826         $filerecord->author = $author;
827         $this->update($filerecord);
828     }
830     /**
831      * Returns the source of the file, usually it is a url.
832      *
833      * @return string
834      */
835     public function get_source() {
836         return $this->file_record->source;
837     }
839     /**
840      * Set license
841      *
842      * @param string $license license
843      */
844     public function set_source($source) {
845         $filerecord = new stdClass;
846         $filerecord->source = $source;
847         $this->update($filerecord);
848     }
851     /**
852      * Returns the sort order of file
853      *
854      * @return int
855      */
856     public function get_sortorder() {
857         return $this->file_record->sortorder;
858     }
860     /**
861      * Set file sort order
862      *
863      * @param int $sortorder
864      * @return int
865      */
866     public function set_sortorder($sortorder) {
867         $filerecord = new stdClass;
868         $filerecord->sortorder = $sortorder;
869         $this->update($filerecord);
870     }
872     /**
873      * Returns repository id
874      *
875      * @return int|null
876      */
877     public function get_repository_id() {
878         if (!empty($this->repository)) {
879             return $this->repository->id;
880         } else {
881             return null;
882         }
883     }
885     /**
886      * get reference file id
887      * @return int
888      */
889     public function get_referencefileid() {
890         return $this->file_record->referencefileid;
891     }
893     /**
894      * Get reference last sync time
895      * @return int
896      */
897     public function get_referencelastsync() {
898         return $this->file_record->referencelastsync;
899     }
901     /**
902      * Get reference last sync time
903      * @return int
904      */
905     public function get_referencelifetime() {
906         return $this->file_record->referencelifetime;
907     }
908     /**
909      * Returns file reference
910      *
911      * @return string
912      */
913     public function get_reference() {
914         return $this->file_record->reference;
915     }
917     /**
918      * Get human readable file reference information
919      *
920      * @return string
921      */
922     public function get_reference_details() {
923         return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
924     }
926     /**
927      * Called after reference-file has been synchronized with the repository
928      *
929      * We update contenthash, filesize and status in files table if changed
930      * and we always update lastsync in files_reference table
931      *
932      * @param string $contenthash
933      * @param int $filesize
934      * @param int $status
935      * @param int $lifetime the life time of this synchronisation results
936      */
937     public function set_synchronized($contenthash, $filesize, $status = 0, $lifetime = null) {
938         global $DB;
939         if (!$this->is_external_file()) {
940             return;
941         }
942         $now = time();
943         if ($contenthash != $this->file_record->contenthash) {
944             $oldcontenthash = $this->file_record->contenthash;
945         }
946         if ($lifetime === null) {
947             $lifetime = $this->file_record->referencelifetime;
948         }
949         // this will update all entries in {files} that have the same filereference id
950         $this->fs->update_references($this->file_record->referencefileid, $now, $lifetime, $contenthash, $filesize, $status);
951         // we don't need to call update() for this object, just set the values of changed fields
952         $this->file_record->contenthash = $contenthash;
953         $this->file_record->filesize = $filesize;
954         $this->file_record->status = $status;
955         $this->file_record->referencelastsync = $now;
956         $this->file_record->referencelifetime = $lifetime;
957         if (isset($oldcontenthash)) {
958             $this->fs->deleted_file_cleanup($oldcontenthash);
959         }
960     }
962     /**
963      * Sets the error status for a file that could not be synchronised
964      *
965      * @param int $lifetime the life time of this synchronisation results
966      */
967     public function set_missingsource($lifetime = null) {
968         $this->set_synchronized($this->get_contenthash(), $this->get_filesize(), 666, $lifetime);
969     }
971     /**
972      * Send file references
973      *
974      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
975      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
976      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
977      * @param array $options additional options affecting the file serving
978      */
979     public function send_file($lifetime, $filter, $forcedownload, $options) {
980         $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
981     }
983     /**
984      * Imports the contents of an external file into moodle filepool.
985      *
986      * @throws moodle_exception if file could not be downloaded or is too big
987      * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
988      */
989     public function import_external_file_contents($maxbytes = 0) {
990         if ($this->repository) {
991             $this->repository->import_external_file_contents($this, $maxbytes);
992         }
993     }
995     /**
996      * Gets a file relative to this file in the repository and sends it to the browser.
997      * Checks the function repository::supports_relative_file() to make sure it can be used.
998      *
999      * @param string $relativepath the relative path to the file we are trying to access
1000      */
1001     public function send_relative_file($relativepath) {
1002         if ($this->repository && $this->repository->supports_relative_file()) {
1003             $relativepath = clean_param($relativepath, PARAM_PATH);
1004             $this->repository->send_relative_file($this, $relativepath);
1005         } else {
1006             send_file_not_found();
1007         }
1008     }