MDL-24283 improved stored_file encapsulation, this should allow us to hack with store...
[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;
47     /** @var string location of content files */
48     private $filedir;
50     /**
51      * Constructor, this constructor should be called ONLY from the file_storage class!
52      *
53      * @param file_storage $fs file  storage instance
54      * @param object $file_record description of file
55      * @param string $filepool location of file directory with sh1 named content files
56      */
57     public function __construct(file_storage $fs, stdClass $file_record, $filedir) {
58         $this->fs          = $fs;
59         $this->file_record = clone($file_record); // prevent modifications
60         $this->filedir     = $filedir; // keep secret, do not expose!
61     }
63     /**
64      * Is this a directory?
65      *
66      * Directories are only emulated, internally they are stored as empty
67      * files with a "." instead of name - this means empty directory contains
68      * exactly one empty file with name dot.
69      *
70      * @return bool true means directory, false means file
71      */
72     public function is_directory() {
73         return ($this->file_record->filename === '.');
74     }
76     /**
77      * Delete file from files table.
78      *
79      * The content of files stored in sha1 pool is reclaimed
80      * later - the occupied disk space is reclaimed much later.
81      *
82      * @return bool always true or exception if error occurred
83      */
84     public function delete() {
85         global $DB;
86         $DB->delete_records('files', array('id'=>$this->file_record->id));
87         // moves pool file to trash if content not needed any more
88         $this->fs->deleted_file_cleanup($this->file_record->contenthash);
89         return true; // BC only
90     }
92     /**
93      * Protected - developers must not gain direct access to this function.
94      *
95      * NOTE: do not make this public, we must not modify or delete the pool files directly! ;-)
96      *
97      * @return string full path to pool file with file content
98      **/
99     protected function get_content_file_location() {
100         $contenthash = $this->file_record->contenthash;
101         $l1 = $contenthash[0].$contenthash[1];
102         $l2 = $contenthash[2].$contenthash[3];
103         return "$this->filedir/$l1/$l2/$contenthash";
104     }
106     /**
107     * adds this file path to a curl request (POST only)
108     *
109     * @param curl $curlrequest the curl request object
110     * @param string $key what key to use in the POST request
111     * @return void
112     */
113     public function add_to_curl_request(&$curlrequest, $key) {
114         $curlrequest->_tmp_file_post_params[$key] = '@' . $this->get_content_file_location();
115     }
117     /**
118      * Returns file handle - read only mode, no writing allowed into pool files!
119      *
120      * When you want to modify a file, create a new file and delete the old one.
121      *
122      * @return resource file handle
123      */
124     public function get_content_file_handle() {
125         $path = $this->get_content_file_location();
126         if (!is_readable($path)) {
127             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
128                 throw new file_exception('storedfilecannotread');
129             }
130         }
131         return fopen($path, 'rb'); //binary reading only!!
132     }
134     /**
135      * Dumps file content to page.
136      *
137      * @return void
138      */
139     public function readfile() {
140         $path = $this->get_content_file_location();
141         if (!is_readable($path)) {
142             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
143                 throw new file_exception('storedfilecannotread');
144             }
145         }
146         readfile($path);
147     }
149     /**
150      * Returns file content as string.
151      *
152      * @return string content
153      */
154     public function get_content() {
155         $path = $this->get_content_file_location();
156         if (!is_readable($path)) {
157             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
158                 throw new file_exception('storedfilecannotread');
159             }
160         }
161         return file_get_contents($this->get_content_file_location());
162     }
164     /**
165      * Copy content of file to given pathname.
166      *
167      * @param string $pathname real path to the new file
168      * @return bool success
169      */
170     public function copy_content_to($pathname) {
171         $path = $this->get_content_file_location();
172         if (!is_readable($path)) {
173             if (!$this->fs->try_content_recovery($this) or !is_readable($path)) {
174                 throw new file_exception('storedfilecannotread');
175             }
176         }
177         return copy($path, $pathname);
178     }
180     /**
181      * List contents of archive.
182      *
183      * @param file_packer $file_packer
184      * @return array of file infos
185      */
186     public function list_files(file_packer $packer) {
187         $archivefile = $this->get_content_file_location();
188         return $packer->list_files($archivefile);
189     }
191     /**
192      * Extract file to given file path (real OS filesystem), existing files are overwritten.
193      *
194      * @param file_packer $file_packer
195      * @param string $pathname target directory
196      * @return array|bool list of processed files; false if error
197      */
198     public function extract_to_pathname(file_packer $packer, $pathname) {
199         $archivefile = $this->get_content_file_location();
200         return $packer->extract_to_pathname($archivefile, $pathname);
201     }
203     /**
204      * Extract file to given file path (real OS filesystem), existing files are overwritten.
205      *
206      * @param file_packer $file_packer
207      * @param int $contextid
208      * @param string $component
209      * @param string $filearea
210      * @param int $itemid
211      * @param string $pathbase
212      * @param int $userid
213      * @return array|bool list of processed files; false if error
214      */
215     public function extract_to_storage(file_packer $packer, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
216         $archivefile = $this->get_content_file_location();
217         return $packer->extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase);
218     }
220     /**
221      * Add file/directory into archive.
222      *
223      * @param file_archive $filearch
224      * @param string $archivepath pathname in archive
225      * @return bool success
226      */
227     public function archive_file(file_archive $filearch, $archivepath) {
228         if ($this->is_directory()) {
229             return $filearch->add_directory($archivepath);
230         } else {
231             $path = $this->get_content_file_location();
232             if (!is_readable($path)) {
233                 return false;
234             }
235             return $filearch->add_file_from_pathname($archivepath, $path);
236         }
237     }
239     /**
240      * Returns information about image,
241      * information is determined from the file content
242      * @return mixed array with width, height and mimetype; false if not an image
243      */
244     public function get_imageinfo() {
245         if (!$imageinfo = getimagesize($this->get_content_file_location())) {
246             return false;
247         }
248         $image = array('width'=>$imageinfo[0], 'height'=>$imageinfo[1], 'mimetype'=>image_type_to_mime_type($imageinfo[2]));
249         if (empty($image['width']) or empty($image['height']) or empty($image['mimetype'])) {
250             // gd can not parse it, sorry
251             return false;
252         }
253         return $image;
254     }
256     /**
257      * Verifies the file is a valid web image - gif, png and jpeg only.
258      *
259      * It should be ok to serve this image from server without any other security workarounds.
260      *
261      * @return bool true if file ok
262      */
263     public function is_valid_image() {
264         $mimetype = $this->get_mimetype();
265         if ($mimetype !== 'image/gif' and $mimetype !== 'image/jpeg' and $mimetype !== 'image/png') {
266             return false;
267         }
268         if (!$info = $this->get_imageinfo()) {
269             return false;
270         }
271         if ($info['mimetype'] !== $mimetype) {
272             return false;
273         }
274         // ok, GD likes this image
275         return true;
276     }
278     /**
279      * Returns parent directory, creates missing parents if needed.
280      *
281      * @return stored_file
282      */
283     public function get_parent_directory() {
284         if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
285             //root dir does not have parent
286             return null;
287         }
289         if ($this->file_record->filename !== '.') {
290             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);
291         }
293         $filepath = $this->file_record->filepath;
294         $filepath = trim($filepath, '/');
295         $dirs = explode('/', $filepath);
296         array_pop($dirs);
297         $filepath = implode('/', $dirs);
298         $filepath = ($filepath === '') ? '/' : "/$filepath/";
300         return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
301     }
303     /**
304      * Returns context id of the file-
305      *
306      * @return int context id
307      */
308     public function get_contextid() {
309         return $this->file_record->contextid;
310     }
312     /**
313      * Returns component name - this is the owner of the areas,
314      * nothing else is allowed to read or modify the files directly!!
315      *
316      * @return string
317      */
318     public function get_component() {
319         return $this->file_record->component;
320     }
322     /**
323      * Returns file area name, this divides files of one component into groups with different access control.
324      * All files in one area have the same access control.
325      *
326      * @return string
327      */
328     public function get_filearea() {
329         return $this->file_record->filearea;
330     }
332     /**
333      * Returns returns item id of file.
334      *
335      * @return int
336      */
337     public function get_itemid() {
338         return $this->file_record->itemid;
339     }
341     /**
342      * Returns file path - starts and ends with /, \ are not allowed.
343      *
344      * @return string
345      */
346     public function get_filepath() {
347         return $this->file_record->filepath;
348     }
350     /**
351      * Returns file name or '.' in case of directories.
352      *
353      * @return string
354      */
355     public function get_filename() {
356         return $this->file_record->filename;
357     }
359     /**
360      * Returns id of user who created the file.
361      *
362      * @return int
363      */
364     public function get_userid() {
365         return $this->file_record->userid;
366     }
368     /**
369      * Returns the size of file in bytes.
370      *
371      * @return int bytes
372      */
373     public function get_filesize() {
374         return $this->file_record->filesize;
375     }
377     /**
378      * Returns mime type of file.
379      *
380      * @return string
381      */
382     public function get_mimetype() {
383         return $this->file_record->mimetype;
384     }
386     /**
387      * Returns unix timestamp of file creation date.
388      *
389      * @return int
390      */
391     public function get_timecreated() {
392         return $this->file_record->timecreated;
393     }
395     /**
396      * Returns unix timestamp of last file modification.
397      *
398      * @return int
399      */
400     public function get_timemodified() {
401         return $this->file_record->timemodified;
402     }
404     /**
405      * Returns file status flag.
406      *
407      * @return int 0 means file OK, anything else is a problem and file can not be used
408      */
409     public function get_status() {
410         return $this->file_record->status;
411     }
413     /**
414      * Returns file id.
415      *
416      * @return int
417      */
418     public function get_id() {
419         return $this->file_record->id;
420     }
422     /**
423      * Returns sha1 hash of file content.
424      *
425      * @return string
426      */
427     public function get_contenthash() {
428         return $this->file_record->contenthash;
429     }
431     /**
432      * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
433      *
434      * @return string
435      */
436     public function get_pathnamehash() {
437         return $this->file_record->pathnamehash;
438     }
440     /**
441      * Returns the license type of the file, it is a short name referred from license table.
442      *
443      * @return string
444      */
445     public function get_license() {
446         return $this->file_record->license;
447     }
449     /**
450      * Returns the author name of the file.
451      *
452      * @return string
453      */
454     public function get_author() {
455         return $this->file_record->license;
456     }
458     /**
459      * Returns the source of the file, usually it is a url.
460      *
461      * @return string
462      */
463     public function get_source() {
464         return $this->file_record->source;
465     }
467     /**
468      * Returns the sort order of file
469      *
470      * @return int
471      */
472     public function get_sortorder() {
473         return $this->file_record->sortorder;
474     }