MDL-23885 flattening filepool directory structure - hopefully this will lower the...
[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         return "$filedir/$l1/$l2/$contenthash";
101     }
103     /**
104     * adds this file path to a curl request (POST only)
105     *
106     * @param curl $curlrequest the curl request object
107     * @param string $key what key to use in the POST request
108     * @return void
109     */
110     public function add_to_curl_request(&$curlrequest, $key) {
111         $curlrequest->_tmp_file_post_params[$key] = '@' . $this->get_content_file_location();
112     }
114     /**
115      * Returns file handle - read only mode, no writing allowed into pool files!
116      *
117      * When you want to modify a file, create a new file and delete the old one.
118      *
119      * @return resource file handle
120      */
121     public function get_content_file_handle() {
122         $path = $this->get_content_file_location();
123         if (!is_readable($path)) {
124             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
125                 throw new file_exception('storedfilecannotread');
126             }
127         }
128         return fopen($path, 'rb'); //binary reading only!!
129     }
131     /**
132      * Dumps file content to page.
133      *
134      * @return void
135      */
136     public function readfile() {
137         $path = $this->get_content_file_location();
138         if (!is_readable($path)) {
139             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
140                 throw new file_exception('storedfilecannotread');
141             }
142         }
143         readfile($path);
144     }
146     /**
147      * Returns file content as string.
148      *
149      * @return string content
150      */
151     public function get_content() {
152         $path = $this->get_content_file_location();
153         if (!is_readable($path)) {
154             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
155                 throw new file_exception('storedfilecannotread');
156             }
157         }
158         return file_get_contents($this->get_content_file_location());
159     }
161     /**
162      * Copy content of file to given pathname.
163      *
164      * @param string $pathname real path to the new file
165      * @return bool success
166      */
167     public function copy_content_to($pathname) {
168         $path = $this->get_content_file_location();
169         if (!is_readable($path)) {
170             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
171                 throw new file_exception('storedfilecannotread');
172             }
173         }
174         return copy($path, $pathname);
175     }
177     /**
178      * List contents of archive.
179      *
180      * @param file_packer $file_packer
181      * @return array of file infos
182      */
183     public function list_files(file_packer $packer) {
184         $archivefile = $this->get_content_file_location();
185         return $packer->list_files($archivefile);
186     }
188     /**
189      * Extract file to given file path (real OS filesystem), existing files are overwritten.
190      *
191      * @param file_packer $file_packer
192      * @param string $pathname target directory
193      * @return array|bool list of processed files; false if error
194      */
195     public function extract_to_pathname(file_packer $packer, $pathname) {
196         $archivefile = $this->get_content_file_location();
197         return $packer->extract_to_pathname($archivefile, $pathname);
198     }
200     /**
201      * Extract file to given file path (real OS filesystem), existing files are overwritten.
202      *
203      * @param file_packer $file_packer
204      * @param int $contextid
205      * @param string $component
206      * @param string $filearea
207      * @param int $itemid
208      * @param string $pathbase
209      * @param int $userid
210      * @return array|bool list of processed files; false if error
211      */
212     public function extract_to_storage(file_packer $packer, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
213         $archivefile = $this->get_content_file_location();
214         return $packer->extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase);
215     }
217     /**
218      * Add file/directory into archive.
219      *
220      * @param file_archive $filearch
221      * @param string $archivepath pathname in archive
222      * @return bool success
223      */
224     public function archive_file(file_archive $filearch, $archivepath) {
225         if ($this->is_directory()) {
226             return $filearch->add_directory($archivepath);
227         } else {
228             $path = $this->get_content_file_location();
229             if (!is_readable($path)) {
230                 return false;
231             }
232             return $filearch->add_file_from_pathname($archivepath, $path);
233         }
234     }
236     /**
237      * Returns information about image,
238      * information is determined from the file content
239      * @return mixed array with width, height and mimetype; false if not an image
240      */
241     public function get_imageinfo() {
242         if (!$imageinfo = getimagesize($this->get_content_file_location())) {
243             return false;
244         }
245         $image = array('width'=>$imageinfo[0], 'height'=>$imageinfo[1], 'mimetype'=>image_type_to_mime_type($imageinfo[2]));
246         if (empty($image['width']) or empty($image['height']) or empty($image['mimetype'])) {
247             // gd can not parse it, sorry
248             return false;
249         }
250         return $image;
251     }
253     /**
254      * Verifies the file is a valid web image - gif, png and jpeg only.
255      *
256      * It should be ok to serve this image from server without any other security workarounds.
257      *
258      * @return bool true if file ok
259      */
260     public function is_valid_image() {
261         $mimetype = $this->get_mimetype();
262         if ($mimetype !== 'image/gif' and $mimetype !== 'image/jpeg' and $mimetype !== 'image/png') {
263             return false;
264         }
265         if (!$info = $this->get_imageinfo()) {
266             return false;
267         }
268         if ($info['mimetype'] !== $mimetype) {
269             return false;
270         }
271         // ok, GD likes this image
272         return true;
273     }
275     /**
276      * Returns parent directory, creates missing parents if needed.
277      *
278      * @return stored_file
279      */
280     public function get_parent_directory() {
281         if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
282             //root dir does not have parent
283             return null;
284         }
286         if ($this->file_record->filename !== '.') {
287             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);
288         }
290         $filepath = $this->file_record->filepath;
291         $filepath = trim($filepath, '/');
292         $dirs = explode('/', $filepath);
293         array_pop($dirs);
294         $filepath = implode('/', $dirs);
295         $filepath = ($filepath === '') ? '/' : "/$filepath/";
297         return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
298     }
300     /**
301      * Returns context id of the file-
302      *
303      * @return int context id
304      */
305     public function get_contextid() {
306         return $this->file_record->contextid;
307     }
309     /**
310      * Returns component name - this is the owner of the areas,
311      * nothing else is allowed to read or modify the files directly!!
312      *
313      * @return string
314      */
315     public function get_component() {
316         return $this->file_record->component;
317     }
319     /**
320      * Returns file area name, this divides files of one component into groups with different access control.
321      * All files in one area have the same access control.
322      *
323      * @return string
324      */
325     public function get_filearea() {
326         return $this->file_record->filearea;
327     }
329     /**
330      * Returns returns item id of file.
331      *
332      * @return int
333      */
334     public function get_itemid() {
335         return $this->file_record->itemid;
336     }
338     /**
339      * Returns file path - starts and ends with /, \ are not allowed.
340      *
341      * @return string
342      */
343     public function get_filepath() {
344         return $this->file_record->filepath;
345     }
347     /**
348      * Returns file name or '.' in case of directories.
349      *
350      * @return string
351      */
352     public function get_filename() {
353         return $this->file_record->filename;
354     }
356     /**
357      * Returns id of user who created the file.
358      *
359      * @return int
360      */
361     public function get_userid() {
362         return $this->file_record->userid;
363     }
365     /**
366      * Returns the size of file in bytes.
367      *
368      * @return int bytes
369      */
370     public function get_filesize() {
371         return $this->file_record->filesize;
372     }
374     /**
375      * Returns mime type of file.
376      *
377      * @return string
378      */
379     public function get_mimetype() {
380         return $this->file_record->mimetype;
381     }
383     /**
384      * Returns unix timestamp of file creation date.
385      *
386      * @return int
387      */
388     public function get_timecreated() {
389         return $this->file_record->timecreated;
390     }
392     /**
393      * Returns unix timestamp of last file modification.
394      *
395      * @return int
396      */
397     public function get_timemodified() {
398         return $this->file_record->timemodified;
399     }
401     /**
402      * Returns file status flag.
403      *
404      * @return int 0 means file OK, anything else is a problem and file can not be used
405      */
406     public function get_status() {
407         return $this->file_record->status;
408     }
410     /**
411      * Returns file id.
412      *
413      * @return int
414      */
415     public function get_id() {
416         return $this->file_record->id;
417     }
419     /**
420      * Returns sha1 hash of file content.
421      *
422      * @return string
423      */
424     public function get_contenthash() {
425         return $this->file_record->contenthash;
426     }
428     /**
429      * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
430      *
431      * @return string
432      */
433     public function get_pathnamehash() {
434         return $this->file_record->pathnamehash;
435     }
437     /**
438      * Returns the license type of the file, it is a short name referred from license table.
439      *
440      * @return string
441      */
442     public function get_license() {
443         return $this->file_record->license;
444     }
446     /**
447      * Returns the author name of the file.
448      *
449      * @return string
450      */
451     public function get_author() {
452         return $this->file_record->license;
453     }
455     /**
456      * Returns the source of the file, usually it is a url.
457      *
458      * @return string
459      */
460     public function get_source() {
461         return $this->file_record->source;
462     }
464     /**
465      * Returns the sort order of file
466      *
467      * @return int
468      */
469     public function get_sortorder() {
470         return $this->file_record->sortorder;
471     }