MDL-59700 filestorage: Rework repositories to avoid add_file_to_pool
[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');
29 require_once($CFG->dirroot . '/lib/filestorage/file_system.php');
31 /**
32  * Class representing local files stored in a sha1 file pool.
33  *
34  * Since Moodle 2.0 file contents are stored in sha1 pool and
35  * all other file information is stored in new "files" database table.
36  *
37  * @package   core_files
38  * @category  files
39  * @copyright 2008 Petr Skoda {@link http://skodak.org}
40  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  * @since     Moodle 2.0
42  */
43 class stored_file {
44     /** @var file_storage file storage pool instance */
45     private $fs;
46     /** @var stdClass record from the files table left join files_reference table */
47     private $file_record;
48     /** @var repository repository plugin instance */
49     private $repository;
50     /** @var file_system filesystem instance */
51     private $filesystem;
53     /**
54      * @var int Indicates a file handle of the type returned by fopen.
55      */
56     const FILE_HANDLE_FOPEN = 0;
58     /**
59      * @var int Indicates a file handle of the type returned by gzopen.
60      */
61     const FILE_HANDLE_GZOPEN = 1;
64     /**
65      * Constructor, this constructor should be called ONLY from the file_storage class!
66      *
67      * @param file_storage $fs file  storage instance
68      * @param stdClass $file_record description of file
69      * @param string $deprecated
70      */
71     public function __construct(file_storage $fs, stdClass $file_record, $deprecated = null) {
72         global $DB, $CFG;
73         $this->fs          = $fs;
74         $this->file_record = clone($file_record); // prevent modifications
76         if (!empty($file_record->repositoryid)) {
77             require_once("$CFG->dirroot/repository/lib.php");
78             $this->repository = repository::get_repository_by_id($file_record->repositoryid, SYSCONTEXTID);
79             if ($this->repository->supported_returntypes() & FILE_REFERENCE != FILE_REFERENCE) {
80                 // Repository cannot do file reference.
81                 throw new moodle_exception('error');
82             }
83         } else {
84             $this->repository = null;
85         }
86         // make sure all reference fields exist in file_record even when it is not a reference
87         foreach (array('referencelastsync', 'referencefileid', 'reference', 'repositoryid') as $key) {
88             if (empty($this->file_record->$key)) {
89                 $this->file_record->$key = null;
90             }
91         }
93         $this->filesystem = $fs->get_file_system();
94     }
96     /**
97      * Magic method, called during serialization.
98      *
99      * @return array
100      */
101     public function __sleep() {
102         // We only ever want the file_record saved, not the file_storage object.
103         return ['file_record'];
104     }
106     /**
107      * Magic method, called during unserialization.
108      */
109     public function __wakeup() {
110         // Recreate our stored_file based on the file_record, and using file storage retrieved the correct way.
111         $this->__construct(get_file_storage(), $this->file_record);
112     }
114     /**
115      * Whether or not this is a external resource
116      *
117      * @return bool
118      */
119     public function is_external_file() {
120         return !empty($this->repository);
121     }
123     /**
124      * Whether or not this is a controlled link. Note that repositories cannot support FILE_REFERENCE and FILE_CONTROLLED_LINK.
125      *
126      * @return bool
127      */
128     public function is_controlled_link() {
129         return $this->is_external_file() && $this->repository->supported_returntypes() & FILE_CONTROLLED_LINK;
130     }
132     /**
133      * Update some file record fields
134      * NOTE: Must remain protected
135      *
136      * @param stdClass $dataobject
137      */
138     protected function update($dataobject) {
139         global $DB;
140         $updatereferencesneeded = false;
141         $keys = array_keys((array)$this->file_record);
142         $filepreupdate = clone($this->file_record);
143         foreach ($dataobject as $field => $value) {
144             if (in_array($field, $keys)) {
145                 if ($field == 'contextid' and (!is_number($value) or $value < 1)) {
146                     throw new file_exception('storedfileproblem', 'Invalid contextid');
147                 }
149                 if ($field == 'component') {
150                     $value = clean_param($value, PARAM_COMPONENT);
151                     if (empty($value)) {
152                         throw new file_exception('storedfileproblem', 'Invalid component');
153                     }
154                 }
156                 if ($field == 'filearea') {
157                     $value = clean_param($value, PARAM_AREA);
158                     if (empty($value)) {
159                         throw new file_exception('storedfileproblem', 'Invalid filearea');
160                     }
161                 }
163                 if ($field == 'itemid' and (!is_number($value) or $value < 0)) {
164                     throw new file_exception('storedfileproblem', 'Invalid itemid');
165                 }
168                 if ($field == 'filepath') {
169                     $value = clean_param($value, PARAM_PATH);
170                     if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
171                         // path must start and end with '/'
172                         throw new file_exception('storedfileproblem', 'Invalid file path');
173                     }
174                 }
176                 if ($field == 'filename') {
177                     // folder has filename == '.', so we pass this
178                     if ($value != '.') {
179                         $value = clean_param($value, PARAM_FILE);
180                     }
181                     if ($value === '') {
182                         throw new file_exception('storedfileproblem', 'Invalid file name');
183                     }
184                 }
186                 if ($field === 'timecreated' or $field === 'timemodified') {
187                     if (!is_number($value)) {
188                         throw new file_exception('storedfileproblem', 'Invalid timestamp');
189                     }
190                     if ($value < 0) {
191                         $value = 0;
192                     }
193                 }
195                 if ($field === 'referencefileid') {
196                     if (!is_null($value) and !is_number($value)) {
197                         throw new file_exception('storedfileproblem', 'Invalid reference info');
198                     }
199                 }
201                 if (($field == 'contenthash' || $field == 'filesize') && $this->file_record->$field != $value) {
202                     $updatereferencesneeded = true;
203                 }
205                 // adding the field
206                 $this->file_record->$field = $value;
207             } else {
208                 throw new coding_exception("Invalid field name, $field doesn't exist in file record");
209             }
210         }
211         // Validate mimetype field
212         $mimetype = $this->filesystem->mimetype_from_storedfile($this);
213         $this->file_record->mimetype = $mimetype;
215         $DB->update_record('files', $this->file_record);
216         if ($updatereferencesneeded) {
217             // Either filesize or contenthash of this file have changed. Update all files that reference to it.
218             $this->fs->update_references_to_storedfile($this);
219         }
221         // Callback for file update.
222         if (!$this->is_directory()) {
223             if ($pluginsfunction = get_plugins_with_function('after_file_updated')) {
224                 foreach ($pluginsfunction as $plugintype => $plugins) {
225                     foreach ($plugins as $pluginfunction) {
226                         $pluginfunction($this->file_record, $filepreupdate);
227                     }
228                 }
229             }
230         }
231     }
233     /**
234      * Rename filename
235      *
236      * @param string $filepath file path
237      * @param string $filename file name
238      */
239     public function rename($filepath, $filename) {
240         if ($this->fs->file_exists($this->get_contextid(), $this->get_component(), $this->get_filearea(), $this->get_itemid(), $filepath, $filename)) {
241             $a = new stdClass();
242             $a->contextid = $this->get_contextid();
243             $a->component = $this->get_component();
244             $a->filearea  = $this->get_filearea();
245             $a->itemid    = $this->get_itemid();
246             $a->filepath  = $filepath;
247             $a->filename  = $filename;
248             throw new file_exception('storedfilenotcreated', $a, 'file exists, cannot rename');
249         }
250         $filerecord = new stdClass;
251         $filerecord->filepath = $filepath;
252         $filerecord->filename = $filename;
253         // populate the pathname hash
254         $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);
255         $this->update($filerecord);
256     }
258     /**
259      * Function stored_file::replace_content_with() is deprecated. Please use stored_file::replace_file_with()
260      *
261      * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
262      * @see stored_file::replace_file_with()
263      */
264     public function replace_content_with(stored_file $storedfile) {
265         throw new coding_exception('Function stored_file::replace_content_with() can not be used any more . ' .
266             'Please use stored_file::replace_file_with()');
267     }
269     /**
270      * Replaces the fields that might have changed when file was overriden in filepicker:
271      * reference, contenthash, filesize, userid
272      *
273      * Note that field 'source' must be updated separately because
274      * it has different format for draft and non-draft areas and
275      * this function will usually be used to replace non-draft area
276      * file with draft area file.
277      *
278      * @param stored_file $newfile
279      * @throws coding_exception
280      */
281     public function replace_file_with(stored_file $newfile) {
282         if ($newfile->get_referencefileid() &&
283                 $this->fs->get_references_count_by_storedfile($this)) {
284             // The new file is a reference.
285             // The current file has other local files referencing to it.
286             // Double reference is not allowed.
287             throw new moodle_exception('errordoublereference', 'repository');
288         }
290         $filerecord = new stdClass;
291         if ($this->filesystem->is_file_readable_remotely_by_storedfile($newfile)) {
292             $contenthash = $newfile->get_contenthash();
293             $filerecord->contenthash = $contenthash;
294         } else {
295             throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
296         }
297         $filerecord->filesize = $newfile->get_filesize();
298         $filerecord->referencefileid = $newfile->get_referencefileid();
299         $filerecord->userid = $newfile->get_userid();
300         $this->update($filerecord);
301     }
303     /**
304      * Unlink the stored file from the referenced file
305      *
306      * This methods destroys the link to the record in files_reference table. This effectively
307      * turns the stored file from being an alias to a plain copy. However, the caller has
308      * to make sure that the actual file's content has beed synced prior to calling this method.
309      */
310     public function delete_reference() {
311         global $DB;
313         if (!$this->is_external_file()) {
314             throw new coding_exception('An attempt to unlink a non-reference file.');
315         }
317         $transaction = $DB->start_delegated_transaction();
319         // Are we the only one referring to the original file? If so, delete the
320         // referenced file record. Note we do not use file_storage::search_references_count()
321         // here because we want to count draft files too and we are at a bit lower access level here.
322         $countlinks = $DB->count_records('files',
323             array('referencefileid' => $this->file_record->referencefileid));
324         if ($countlinks == 1) {
325             $DB->delete_records('files_reference', array('id' => $this->file_record->referencefileid));
326         }
328         // Update the underlying record in the database.
329         $update = new stdClass();
330         $update->referencefileid = null;
331         $this->update($update);
333         $transaction->allow_commit();
335         // Update our properties and the record in the memory.
336         $this->repository = null;
337         $this->file_record->repositoryid = null;
338         $this->file_record->reference = null;
339         $this->file_record->referencefileid = null;
340         $this->file_record->referencelastsync = null;
341     }
343     /**
344      * Is this a directory?
345      *
346      * Directories are only emulated, internally they are stored as empty
347      * files with a "." instead of name - this means empty directory contains
348      * exactly one empty file with name dot.
349      *
350      * @return bool true means directory, false means file
351      */
352     public function is_directory() {
353         return ($this->file_record->filename === '.');
354     }
356     /**
357      * Delete file from files table.
358      *
359      * The content of files stored in sha1 pool is reclaimed
360      * later - the occupied disk space is reclaimed much later.
361      *
362      * @return bool always true or exception if error occurred
363      */
364     public function delete() {
365         global $DB;
367         if ($this->is_directory()) {
368             // Directories can not be referenced, just delete the record.
369             $DB->delete_records('files', array('id'=>$this->file_record->id));
371         } else {
372             $transaction = $DB->start_delegated_transaction();
374             // If there are other files referring to this file, convert them to copies.
375             if ($files = $this->fs->get_references_by_storedfile($this)) {
376                 foreach ($files as $file) {
377                     $this->fs->import_external_file($file);
378                 }
379             }
381             // If this file is a reference (alias) to another file, unlink it first.
382             if ($this->is_external_file()) {
383                 $this->delete_reference();
384             }
386             // Now delete the file record.
387             $DB->delete_records('files', array('id'=>$this->file_record->id));
389             $transaction->allow_commit();
391             if (!$this->is_directory()) {
392                 // Callback for file deletion.
393                 if ($pluginsfunction = get_plugins_with_function('after_file_deleted')) {
394                     foreach ($pluginsfunction as $plugintype => $plugins) {
395                         foreach ($plugins as $pluginfunction) {
396                             $pluginfunction($this->file_record);
397                         }
398                     }
399                 }
400             }
401         }
403         // Move pool file to trash if content not needed any more.
404         $this->filesystem->remove_file($this->file_record->contenthash);
405         return true; // BC only
406     }
408     /**
409     * adds this file path to a curl request (POST only)
410     *
411     * @param curl $curlrequest the curl request object
412     * @param string $key what key to use in the POST request
413     * @return void
414     */
415     public function add_to_curl_request(&$curlrequest, $key) {
416         return $this->filesystem->add_to_curl_request($this, $curlrequest, $key);
417     }
419     /**
420      * Returns file handle - read only mode, no writing allowed into pool files!
421      *
422      * When you want to modify a file, create a new file and delete the old one.
423      *
424      * @param int $type Type of file handle (FILE_HANDLE_xx constant)
425      * @return resource file handle
426      */
427     public function get_content_file_handle($type = self::FILE_HANDLE_FOPEN) {
428         return $this->filesystem->get_content_file_handle($this, $type);
429     }
431     /**
432      * Dumps file content to page.
433      */
434     public function readfile() {
435         return $this->filesystem->readfile($this);
436     }
438     /**
439      * Returns file content as string.
440      *
441      * @return string content
442      */
443     public function get_content() {
444         return $this->filesystem->get_content($this);
445     }
447     /**
448      * Copy content of file to given pathname.
449      *
450      * @param string $pathname real path to the new file
451      * @return bool success
452      */
453     public function copy_content_to($pathname) {
454         return $this->filesystem->copy_content_from_storedfile($this, $pathname);
455     }
457     /**
458      * Copy content of file to temporary folder and returns file path
459      *
460      * @param string $dir name of the temporary directory
461      * @param string $fileprefix prefix of temporary file.
462      * @return string|bool path of temporary file or false.
463      */
464     public function copy_content_to_temp($dir = 'files', $fileprefix = 'tempup_') {
465         $tempfile = false;
466         if (!$dir = make_temp_directory($dir)) {
467             return false;
468         }
469         if (!$tempfile = tempnam($dir, $fileprefix)) {
470             return false;
471         }
472         if (!$this->copy_content_to($tempfile)) {
473             // something went wrong
474             @unlink($tempfile);
475             return false;
476         }
477         return $tempfile;
478     }
480     /**
481      * List contents of archive.
482      *
483      * @param file_packer $packer file packer instance
484      * @return array of file infos
485      */
486     public function list_files(file_packer $packer) {
487         return $this->filesystem->list_files($this, $packer);
488     }
490     /**
491      * Extract file to given file path (real OS filesystem), existing files are overwritten.
492      *
493      * @param file_packer $packer file packer instance
494      * @param string $pathname target directory
495      * @param file_progress $progress Progress indicator callback or null if not required
496      * @return array|bool list of processed files; false if error
497      */
498     public function extract_to_pathname(file_packer $packer, $pathname,
499             file_progress $progress = null) {
500         return $this->filesystem->extract_to_pathname($this, $packer, $pathname, $progress);
501     }
503     /**
504      * Extract file to given file path (real OS filesystem), existing files are overwritten.
505      *
506      * @param file_packer $packer file packer instance
507      * @param int $contextid context ID
508      * @param string $component component
509      * @param string $filearea file area
510      * @param int $itemid item ID
511      * @param string $pathbase path base
512      * @param int $userid user ID
513      * @param file_progress $progress Progress indicator callback or null if not required
514      * @return array|bool list of processed files; false if error
515      */
516     public function extract_to_storage(file_packer $packer, $contextid,
517             $component, $filearea, $itemid, $pathbase, $userid = null, file_progress $progress = null) {
519         return $this->filesystem->extract_to_storage($this, $packer, $contextid, $component, $filearea,
520                 $itemid, $pathbase, $userid, $progress);
521     }
523     /**
524      * Add file/directory into archive.
525      *
526      * @param file_archive $filearch file archive instance
527      * @param string $archivepath pathname in archive
528      * @return bool success
529      */
530     public function archive_file(file_archive $filearch, $archivepath) {
531         return $this->filesystem->add_storedfile_to_archive($this, $filearch, $archivepath);
532     }
534     /**
535      * Returns information about image,
536      * information is determined from the file content
537      *
538      * @return mixed array with width, height and mimetype; false if not an image
539      */
540     public function get_imageinfo() {
541         return $this->filesystem->get_imageinfo($this);
542     }
544     /**
545      * Verifies the file is a valid web image - gif, png and jpeg only.
546      *
547      * It should be ok to serve this image from server without any other security workarounds.
548      *
549      * @return bool true if file ok
550      */
551     public function is_valid_image() {
552         $mimetype = $this->get_mimetype();
553         if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
554             return false;
555         }
556         if (!$info = $this->get_imageinfo()) {
557             return false;
558         }
559         if ($info['mimetype'] !== $mimetype) {
560             return false;
561         }
562         // ok, GD likes this image
563         return true;
564     }
566     /**
567      * Returns parent directory, creates missing parents if needed.
568      *
569      * @return stored_file
570      */
571     public function get_parent_directory() {
572         if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
573             //root dir does not have parent
574             return null;
575         }
577         if ($this->file_record->filename !== '.') {
578             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);
579         }
581         $filepath = $this->file_record->filepath;
582         $filepath = trim($filepath, '/');
583         $dirs = explode('/', $filepath);
584         array_pop($dirs);
585         $filepath = implode('/', $dirs);
586         $filepath = ($filepath === '') ? '/' : "/$filepath/";
588         return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
589     }
591     /**
592      * Set synchronised content from file.
593      *
594      * @param string $path Path to the file.
595      */
596     public function set_synchronised_content_from_file($path) {
597         $this->fs->synchronise_stored_file_from_file($this, $path, $this->file_record);
598     }
600     /**
601      * Set synchronised content from content.
602      *
603      * @param string $content File content.
604      */
605     public function set_synchronised_content_from_string($content) {
606         $this->fs->synchronise_stored_file_from_string($this, $content, $this->file_record);
607     }
609     /**
610      * Synchronize file if it is a reference and needs synchronizing
611      *
612      * Updates contenthash and filesize
613      */
614     public function sync_external_file() {
615         if (!empty($this->repository)) {
616             $this->repository->sync_reference($this);
617         }
618     }
620     /**
621      * Returns context id of the file
622      *
623      * @return int context id
624      */
625     public function get_contextid() {
626         return $this->file_record->contextid;
627     }
629     /**
630      * Returns component name - this is the owner of the areas,
631      * nothing else is allowed to read or modify the files directly!!
632      *
633      * @return string
634      */
635     public function get_component() {
636         return $this->file_record->component;
637     }
639     /**
640      * Returns file area name, this divides files of one component into groups with different access control.
641      * All files in one area have the same access control.
642      *
643      * @return string
644      */
645     public function get_filearea() {
646         return $this->file_record->filearea;
647     }
649     /**
650      * Returns returns item id of file.
651      *
652      * @return int
653      */
654     public function get_itemid() {
655         return $this->file_record->itemid;
656     }
658     /**
659      * Returns file path - starts and ends with /, \ are not allowed.
660      *
661      * @return string
662      */
663     public function get_filepath() {
664         return $this->file_record->filepath;
665     }
667     /**
668      * Returns file name or '.' in case of directories.
669      *
670      * @return string
671      */
672     public function get_filename() {
673         return $this->file_record->filename;
674     }
676     /**
677      * Returns id of user who created the file.
678      *
679      * @return int
680      */
681     public function get_userid() {
682         return $this->file_record->userid;
683     }
685     /**
686      * Returns the size of file in bytes.
687      *
688      * @return int bytes
689      */
690     public function get_filesize() {
691         $this->sync_external_file();
692         return $this->file_record->filesize;
693     }
695      /**
696      * Function stored_file::set_filesize() is deprecated. Please use stored_file::replace_file_with
697      *
698      * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
699      * @see stored_file::replace_file_with()
700      */
701     public function set_filesize($filesize) {
702         throw new coding_exception('Function stored_file::set_filesize() can not be used any more. ' .
703             'Please use stored_file::replace_file_with()');
704     }
706     /**
707      * Returns mime type of file.
708      *
709      * @return string
710      */
711     public function get_mimetype() {
712         return $this->file_record->mimetype;
713     }
715     /**
716      * Returns unix timestamp of file creation date.
717      *
718      * @return int
719      */
720     public function get_timecreated() {
721         return $this->file_record->timecreated;
722     }
724     /**
725      * Returns unix timestamp of last file modification.
726      *
727      * @return int
728      */
729     public function get_timemodified() {
730         $this->sync_external_file();
731         return $this->file_record->timemodified;
732     }
734     /**
735      * set timemodified
736      *
737      * @param int $timemodified
738      */
739     public function set_timemodified($timemodified) {
740         $filerecord = new stdClass;
741         $filerecord->timemodified = $timemodified;
742         $this->update($filerecord);
743     }
745     /**
746      * Returns file status flag.
747      *
748      * @return int 0 means file OK, anything else is a problem and file can not be used
749      */
750     public function get_status() {
751         return $this->file_record->status;
752     }
754     /**
755      * Returns file id.
756      *
757      * @return int
758      */
759     public function get_id() {
760         return $this->file_record->id;
761     }
763     /**
764      * Returns sha1 hash of file content.
765      *
766      * @return string
767      */
768     public function get_contenthash() {
769         $this->sync_external_file();
770         return $this->file_record->contenthash;
771     }
773     /**
774      * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
775      *
776      * @return string
777      */
778     public function get_pathnamehash() {
779         return $this->file_record->pathnamehash;
780     }
782     /**
783      * Returns the license type of the file, it is a short name referred from license table.
784      *
785      * @return string
786      */
787     public function get_license() {
788         return $this->file_record->license;
789     }
791     /**
792      * Set license
793      *
794      * @param string $license license
795      */
796     public function set_license($license) {
797         $filerecord = new stdClass;
798         $filerecord->license = $license;
799         $this->update($filerecord);
800     }
802     /**
803      * Returns the author name of the file.
804      *
805      * @return string
806      */
807     public function get_author() {
808         return $this->file_record->author;
809     }
811     /**
812      * Set author
813      *
814      * @param string $author
815      */
816     public function set_author($author) {
817         $filerecord = new stdClass;
818         $filerecord->author = $author;
819         $this->update($filerecord);
820     }
822     /**
823      * Returns the source of the file, usually it is a url.
824      *
825      * @return string
826      */
827     public function get_source() {
828         return $this->file_record->source;
829     }
831     /**
832      * Set license
833      *
834      * @param string $license license
835      */
836     public function set_source($source) {
837         $filerecord = new stdClass;
838         $filerecord->source = $source;
839         $this->update($filerecord);
840     }
843     /**
844      * Returns the sort order of file
845      *
846      * @return int
847      */
848     public function get_sortorder() {
849         return $this->file_record->sortorder;
850     }
852     /**
853      * Set file sort order
854      *
855      * @param int $sortorder
856      * @return int
857      */
858     public function set_sortorder($sortorder) {
859         $oldorder = $this->file_record->sortorder;
860         $filerecord = new stdClass;
861         $filerecord->sortorder = $sortorder;
862         $this->update($filerecord);
863         if (!$this->is_directory()) {
864             // Callback for file sort order change.
865             if ($pluginsfunction = get_plugins_with_function('after_file_sorted')) {
866                 foreach ($pluginsfunction as $plugintype => $plugins) {
867                     foreach ($plugins as $pluginfunction) {
868                         $pluginfunction($this->file_record, $oldorder, $sortorder);
869                     }
870                 }
871             }
872         }
873     }
875     /**
876      * Returns repository id
877      *
878      * @return int|null
879      */
880     public function get_repository_id() {
881         if (!empty($this->repository)) {
882             return $this->repository->id;
883         } else {
884             return null;
885         }
886     }
888     /**
889      * Returns repository type.
890      *
891      * @return mixed str|null the repository type or null if is not an external file
892      * @since  Moodle 3.3
893      */
894     public function get_repository_type() {
896         if (!empty($this->repository)) {
897             return $this->repository->get_typename();
898         } else {
899             return null;
900         }
901     }
904     /**
905      * get reference file id
906      * @return int
907      */
908     public function get_referencefileid() {
909         return $this->file_record->referencefileid;
910     }
912     /**
913      * Get reference last sync time
914      * @return int
915      */
916     public function get_referencelastsync() {
917         return $this->file_record->referencelastsync;
918     }
920     /**
921      * Function stored_file::get_referencelifetime() is deprecated as reference
922      * life time is no longer stored in DB or returned by repository. Each
923      * repository should decide by itself when to synchronise the references.
924      *
925      * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
926      * @see repository::sync_reference()
927      */
928     public function get_referencelifetime() {
929         throw new coding_exception('Function stored_file::get_referencelifetime() can not be used any more. ' .
930             'See repository::sync_reference().');
931     }
932     /**
933      * Returns file reference
934      *
935      * @return string
936      */
937     public function get_reference() {
938         return $this->file_record->reference;
939     }
941     /**
942      * Get human readable file reference information
943      *
944      * @return string
945      */
946     public function get_reference_details() {
947         return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
948     }
950     /**
951      * Called after reference-file has been synchronized with the repository
952      *
953      * We update contenthash, filesize and status in files table if changed
954      * and we always update lastsync in files_reference table
955      *
956      * @param null|string $contenthash if set to null contenthash is not changed
957      * @param int $filesize new size of the file
958      * @param int $status new status of the file (0 means OK, 666 - source missing)
959      * @param int $timemodified last time modified of the source, if known
960      */
961     public function set_synchronized($contenthash, $filesize, $status = 0, $timemodified = null) {
962         if (!$this->is_external_file()) {
963             return;
964         }
965         $now = time();
966         if ($contenthash === null) {
967             $contenthash = $this->file_record->contenthash;
968         }
969         if ($contenthash != $this->file_record->contenthash) {
970             $oldcontenthash = $this->file_record->contenthash;
971         }
972         // this will update all entries in {files} that have the same filereference id
973         $this->fs->update_references($this->file_record->referencefileid, $now, null, $contenthash, $filesize, $status, $timemodified);
974         // we don't need to call update() for this object, just set the values of changed fields
975         $this->file_record->contenthash = $contenthash;
976         $this->file_record->filesize = $filesize;
977         $this->file_record->status = $status;
978         $this->file_record->referencelastsync = $now;
979         if ($timemodified) {
980             $this->file_record->timemodified = $timemodified;
981         }
982         if (isset($oldcontenthash)) {
983             $this->filesystem->remove_file($oldcontenthash);
984         }
985     }
987     /**
988      * Sets the error status for a file that could not be synchronised
989      */
990     public function set_missingsource() {
991         $this->set_synchronized($this->file_record->contenthash, $this->file_record->filesize, 666);
992     }
994     /**
995      * Send file references
996      *
997      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
998      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
999      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
1000      * @param array $options additional options affecting the file serving
1001      */
1002     public function send_file($lifetime, $filter, $forcedownload, $options) {
1003         $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
1004     }
1006     /**
1007      * Imports the contents of an external file into moodle filepool.
1008      *
1009      * @throws moodle_exception if file could not be downloaded or is too big
1010      * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
1011      */
1012     public function import_external_file_contents($maxbytes = 0) {
1013         if ($this->repository) {
1014             $this->repository->import_external_file_contents($this, $maxbytes);
1015         }
1016     }
1018     /**
1019      * Gets a file relative to this file in the repository and sends it to the browser.
1020      * Checks the function repository::supports_relative_file() to make sure it can be used.
1021      *
1022      * @param string $relativepath the relative path to the file we are trying to access
1023      */
1024     public function send_relative_file($relativepath) {
1025         if ($this->repository && $this->repository->supports_relative_file()) {
1026             $relativepath = clean_param($relativepath, PARAM_PATH);
1027             $this->repository->send_relative_file($this, $relativepath);
1028         } else {
1029             send_file_not_found();
1030         }
1031     }
1033     /**
1034      * Generates a thumbnail for this stored_file.
1035      *
1036      * If the GD library has at least version 2 and PNG support is available, the returned data
1037      * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function
1038      * returns contents of a JPEG file with black background containing the thumbnail.
1039      *
1040      * @param   int $width the width of the requested thumbnail
1041      * @param   int $height the height of the requested thumbnail
1042      * @return  string|bool false if a problem occurs, the thumbnail image data otherwise
1043      */
1044     public function generate_image_thumbnail($width, $height) {
1045         if (empty($width) or empty($height)) {
1046             return false;
1047         }
1049         $content = $this->get_content();
1051         // Fetch the image information for this image.
1052         $imageinfo = @getimagesizefromstring($content);
1053         if (empty($imageinfo)) {
1054             return false;
1055         }
1057         // Create a new image from the file.
1058         $original = @imagecreatefromstring($content);
1060         // Generate the thumbnail.
1061         return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height);
1062     }
1064     /**
1065      * Generate a resized image for this stored_file.
1066      *
1067      * @param int|null $width The desired width, or null to only use the height.
1068      * @param int|null $height The desired height, or null to only use the width.
1069      * @return string|false False when a problem occurs, else the image data.
1070      */
1071     public function resize_image($width, $height) {
1072         global $CFG;
1073         require_once($CFG->libdir . '/gdlib.php');
1075         $content = $this->get_content();
1077         // Fetch the image information for this image.
1078         $imageinfo = @getimagesizefromstring($content);
1079         if (empty($imageinfo)) {
1080             return false;
1081         }
1083         // Create a new image from the file.
1084         $original = @imagecreatefromstring($content);
1086         // Generate the resized image.
1087         return resize_image_from_image($original, $imageinfo, $width, $height);
1088     }
1090     /**
1091      * Check whether the supplied file is the same as this file.
1092      *
1093      * @param   string $path The path to the file on disk
1094      * @return  boolean
1095      */
1096     public function compare_to_path($path) {
1097         return $this->get_contenthash() === file_storage::hash_from_path($path);
1098     }
1100     /**
1101      * Check whether the supplied content is the same as this file.
1102      *
1103      * @param   string $content The file content
1104      * @return  boolean
1105      */
1106     public function compare_to_string($content) {
1107         return $this->get_contenthash() === file_storage::hash_from_string($content);
1108     }