125d8e35962f38f508e1090d55f40b8e87c916e6
[moodle.git] / lib / filestorage / stored_file.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
19 /**
20  * Definition of a class stored_file.
21  *
22  * @package    core
23  * @subpackage filestorage
24  * @copyright  2008 Petr Skoda {@link http://skodak.org}
25  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
28 defined('MOODLE_INTERNAL') || die();
30 require_once("$CFG->libdir/filestorage/stored_file.php");
32 /**
33  * Class representing local files stored in a sha1 file pool.
34  *
35  * Since Moodle 2.0 file contents are stored in sha1 pool and
36  * all other file information is stored in new "files" database table.
37  *
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 object record from the files table */
46     private $file_record;
48     /**
49      * Constructor, this constructor should be called ONLY from the file_storage class!
50      *
51      * @param file_storage $fs file  storage instance
52      * @param object $file_record description of file
53      */
54     public function __construct(file_storage $fs, stdClass $file_record) {
55         $this->fs          = $fs;
56         $this->file_record = clone($file_record); // prevent modifications
57     }
59     /**
60      * Is this a directory?
61      *
62      * Directories are only emulated, internally they are stored as empty
63      * files with a "." instead of name - this means empty directory contains
64      * exactly one empty file with name dot.
65      *
66      * @return bool true means directory, false means file
67      */
68     public function is_directory() {
69         return ($this->file_record->filename === '.');
70     }
72     /**
73      * Delete file from files table.
74      *
75      * The content of files stored in sha1 pool is reclaimed
76      * later - the occupied disk space is reclaimed much later.
77      *
78      * @return bool always true or exception if error occurred
79      */
80     public function delete() {
81         global $DB;
82         $DB->delete_records('files', array('id'=>$this->file_record->id));
83         // moves pool file to trash if content not needed any more
84         $this->fs->deleted_file_cleanup($this->file_record->contenthash);
85         return true; // BC only
86     }
88     /**
89      * Protected - developers must not gain direct access to this function.
90      *
91      * NOTE: do not make this public, we must not modify or delete the pool files directly! ;-)
92      *
93      * @return string full path to pool file with file content
94      **/
95     protected function get_content_file_location() {
96         $filedir = $this->fs->get_filedir();
97         $contenthash = $this->file_record->contenthash;
98         $l1 = $contenthash[0].$contenthash[1];
99         $l2 = $contenthash[2].$contenthash[3];
100         $l3 = $contenthash[4].$contenthash[5];
101         return "$filedir/$l1/$l2/$l3/$contenthash";
102     }
104     /**
105     * adds this file path to a curl request (POST only)
106     *
107     * @param curl $curlrequest the curl request object
108     * @param string $key what key to use in the POST request
109     * @return void
110     */
111     public function add_to_curl_request(&$curlrequest, $key) {
112         $curlrequest->_tmp_file_post_params[$key] = '@' . $this->get_content_file_location();
113     }
115     /**
116      * Returns file handle - read only mode, no writing allowed into pool files!
117      *
118      * When you want to modify a file, create a new file and delete the old one.
119      *
120      * @return resource file handle
121      */
122     public function get_content_file_handle() {
123         $path = $this->get_content_file_location();
124         if (!is_readable($path)) {
125             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
126                 throw new file_exception('storedfilecannotread');
127             }
128         }
129         return fopen($path, 'rb'); //binary reading only!!
130     }
132     /**
133      * Dumps file content to page.
134      *
135      * @return void
136      */
137     public function readfile() {
138         $path = $this->get_content_file_location();
139         if (!is_readable($path)) {
140             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
141                 throw new file_exception('storedfilecannotread');
142             }
143         }
144         readfile($path);
145     }
147     /**
148      * Returns file content as string.
149      *
150      * @return string content
151      */
152     public function get_content() {
153         $path = $this->get_content_file_location();
154         if (!is_readable($path)) {
155             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
156                 throw new file_exception('storedfilecannotread');
157             }
158         }
159         return file_get_contents($this->get_content_file_location());
160     }
162     /**
163      * Copy content of file to given pathname.
164      *
165      * @param string $pathname real path to the new file
166      * @return bool success
167      */
168     public function copy_content_to($pathname) {
169         $path = $this->get_content_file_location();
170         if (!is_readable($path)) {
171             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
172                 throw new file_exception('storedfilecannotread');
173             }
174         }
175         return copy($path, $pathname);
176     }
178     /**
179      * List contents of archive.
180      *
181      * @param file_packer $file_packer
182      * @return array of file infos
183      */
184     public function list_files(file_packer $packer) {
185         $archivefile = $this->get_content_file_location();
186         return $packer->list_files($archivefile);
187     }
189     /**
190      * Extract file to given file path (real OS filesystem), existing files are overwritten.
191      *
192      * @param file_packer $file_packer
193      * @param string $pathname target directory
194      * @return array|bool list of processed files; false if error
195      */
196     public function extract_to_pathname(file_packer $packer, $pathname) {
197         $archivefile = $this->get_content_file_location();
198         return $packer->extract_to_pathname($archivefile, $pathname);
199     }
201     /**
202      * Extract file to given file path (real OS filesystem), existing files are overwritten.
203      *
204      * @param file_packer $file_packer
205      * @param int $contextid
206      * @param string $component
207      * @param string $filearea
208      * @param int $itemid
209      * @param string $pathbase
210      * @param int $userid
211      * @return array|bool list of processed files; false if error
212      */
213     public function extract_to_storage(file_packer $packer, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
214         $archivefile = $this->get_content_file_location();
215         return $packer->extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase);
216     }
218     /**
219      * Add file/directory into archive.
220      *
221      * @param file_archive $filearch
222      * @param string $archivepath pathname in archive
223      * @return bool success
224      */
225     public function archive_file(file_archive $filearch, $archivepath) {
226         if ($this->is_directory()) {
227             return $filearch->add_directory($archivepath);
228         } else {
229             $path = $this->get_content_file_location();
230             if (!is_readable($path)) {
231                 return false;
232             }
233             return $filearch->add_file_from_pathname($archivepath, $path);
234         }
235     }
237     /**
238      * Returns information about image,
239      * information is determined from the file content
240      * @return mixed array with width, height and mimetype; false if not an image
241      */
242     public function get_imageinfo() {
243         if (!$imageinfo = getimagesize($this->get_content_file_location())) {
244             return false;
245         }
246         $image = array('width'=>$imageinfo[0], 'height'=>$imageinfo[1], 'mimetype'=>image_type_to_mime_type($imageinfo[2]));
247         if (empty($image['width']) or empty($image['height']) or empty($image['mimetype'])) {
248             // gd can not parse it, sorry
249             return false;
250         }
251         return $image;
252     }
254     /**
255      * Verifies the file is a valid web image - gif, png and jpeg only.
256      *
257      * It should be ok to serve this image from server without any other security workarounds.
258      *
259      * @return bool true if file ok
260      */
261     public function is_valid_image() {
262         $mimetype = $this->get_mimetype();
263         if ($mimetype !== 'image/gif' and $mimetype !== 'image/jpeg' and $mimetype !== 'image/png') {
264             return false;
265         }
266         if (!$info = $this->get_imageinfo()) {
267             return false;
268         }
269         if ($info['mimetype'] !== $mimetype) {
270             return false;
271         }
272         // ok, GD likes this image
273         return true;
274     }
276     /**
277      * Returns parent directory, creates missing parents if needed.
278      *
279      * @return stored_file
280      */
281     public function get_parent_directory() {
282         if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
283             //root dir does not have parent
284             return null;
285         }
287         if ($this->file_record->filename !== '.') {
288             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);
289         }
291         $filepath = $this->file_record->filepath;
292         $filepath = trim($filepath, '/');
293         $dirs = explode('/', $filepath);
294         array_pop($dirs);
295         $filepath = implode('/', $dirs);
296         $filepath = ($filepath === '') ? '/' : "/$filepath/";
298         return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
299     }
301     /**
302      * Returns context id of the file-
303      *
304      * @return int context id
305      */
306     public function get_contextid() {
307         return $this->file_record->contextid;
308     }
310     /**
311      * Returns component name - this is the owner of the areas,
312      * nothing else is allowed to read or modify the files directly!!
313      *
314      * @return string
315      */
316     public function get_component() {
317         return $this->file_record->component;
318     }
320     /**
321      * Returns file area name, this divides files of one component into groups with different access control.
322      * All files in one area have the same access control.
323      *
324      * @return string
325      */
326     public function get_filearea() {
327         return $this->file_record->filearea;
328     }
330     /**
331      * Returns returns item id of file.
332      *
333      * @return int
334      */
335     public function get_itemid() {
336         return $this->file_record->itemid;
337     }
339     /**
340      * Returns file path - starts and ends with /, \ are not allowed.
341      *
342      * @return string
343      */
344     public function get_filepath() {
345         return $this->file_record->filepath;
346     }
348     /**
349      * Returns file name or '.' in case of directories.
350      *
351      * @return string
352      */
353     public function get_filename() {
354         return $this->file_record->filename;
355     }
357     /**
358      * Returns id of user who created the file.
359      *
360      * @return int
361      */
362     public function get_userid() {
363         return $this->file_record->userid;
364     }
366     /**
367      * Returns the size of file in bytes.
368      *
369      * @return int bytes
370      */
371     public function get_filesize() {
372         return $this->file_record->filesize;
373     }
375     /**
376      * Returns mime type of file.
377      *
378      * @return string
379      */
380     public function get_mimetype() {
381         return $this->file_record->mimetype;
382     }
384     /**
385      * Returns unix timestamp of file creation date.
386      *
387      * @return int
388      */
389     public function get_timecreated() {
390         return $this->file_record->timecreated;
391     }
393     /**
394      * Returns unix timestamp of last file modification.
395      *
396      * @return int
397      */
398     public function get_timemodified() {
399         return $this->file_record->timemodified;
400     }
402     /**
403      * Returns file status flag.
404      *
405      * @return int 0 means file OK, anything else is a problem and file can not be used
406      */
407     public function get_status() {
408         return $this->file_record->status;
409     }
411     /**
412      * Returns file id.
413      *
414      * @return int
415      */
416     public function get_id() {
417         return $this->file_record->id;
418     }
420     /**
421      * Returns sha1 hash of file content.
422      *
423      * @return string
424      */
425     public function get_contenthash() {
426         return $this->file_record->contenthash;
427     }
429     /**
430      * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
431      *
432      * @return string
433      */
434     public function get_pathnamehash() {
435         return $this->file_record->pathnamehash;
436     }
438     /**
439      * Returns the license type of the file, it is a short name referred from license table.
440      *
441      * @return string
442      */
443     public function get_license() {
444         return $this->file_record->license;
445     }
447     /**
448      * Returns the author name of the file.
449      *
450      * @return string
451      */
452     public function get_author() {
453         return $this->file_record->license;
454     }
456     /**
457      * Returns the source of the file, usually it is a url.
458      *
459      * @return string
460      */
461     public function get_source() {
462         return $this->file_record->source;
463     }
465     /**
466      * Returns the sort order of file
467      *
468      * @return int
469      */
470     public function get_sortorder() {
471         return $this->file_record->sortorder;
472     }