Commit | Line | Data |
---|---|---|
25aebf09 | 1 | <?php |
25aebf09 | 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/>. | |
16 | ||
17 | ||
18 | /** | |
19 | * Core file storage class definition. | |
20 | * | |
d2b7803e DC |
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 | |
25aebf09 | 24 | */ |
172dd12c | 25 | |
64f93798 PS |
26 | defined('MOODLE_INTERNAL') || die(); |
27 | ||
28 | require_once("$CFG->libdir/filestorage/stored_file.php"); | |
172dd12c | 29 | |
25aebf09 | 30 | /** |
31 | * File storage class used for low level access to stored files. | |
bf9ffe27 | 32 | * |
25aebf09 | 33 | * Only owner of file area may use this class to access own files, |
34 | * for example only code in mod/assignment/* may access assignment | |
bf9ffe27 PS |
35 | * attachments. When some other part of moodle needs to access |
36 | * files of modules it has to use file_browser class instead or there | |
37 | * has to be some callback API. | |
38 | * | |
d2b7803e DC |
39 | * @package core_files |
40 | * @category files | |
bf9ffe27 PS |
41 | * @copyright 2008 Petr Skoda {@link http://skodak.org} |
42 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
43 | * @since Moodle 2.0 | |
25aebf09 | 44 | */ |
172dd12c | 45 | class file_storage { |
bf9ffe27 | 46 | /** @var string Directory with file contents */ |
172dd12c | 47 | private $filedir; |
bf9ffe27 | 48 | /** @var string Contents of deleted files not needed any more */ |
1aa01caf | 49 | private $trashdir; |
3a1055a5 PS |
50 | /** @var string tempdir */ |
51 | private $tempdir; | |
bf9ffe27 | 52 | /** @var int Permissions for new directories */ |
1aa01caf | 53 | private $dirpermissions; |
bf9ffe27 | 54 | /** @var int Permissions for new files */ |
1aa01caf | 55 | private $filepermissions; |
bf9ffe27 | 56 | |
172dd12c | 57 | /** |
d2b7803e | 58 | * Constructor - do not use directly use {@link get_file_storage()} call instead. |
bf9ffe27 | 59 | * |
172dd12c | 60 | * @param string $filedir full path to pool directory |
bf9ffe27 | 61 | * @param string $trashdir temporary storage of deleted area |
3a1055a5 | 62 | * @param string $tempdir temporary storage of various files |
bf9ffe27 PS |
63 | * @param int $dirpermissions new directory permissions |
64 | * @param int $filepermissions new file permissions | |
172dd12c | 65 | */ |
3a1055a5 | 66 | public function __construct($filedir, $trashdir, $tempdir, $dirpermissions, $filepermissions) { |
1aa01caf | 67 | $this->filedir = $filedir; |
68 | $this->trashdir = $trashdir; | |
3a1055a5 | 69 | $this->tempdir = $tempdir; |
1aa01caf | 70 | $this->dirpermissions = $dirpermissions; |
71 | $this->filepermissions = $filepermissions; | |
172dd12c | 72 | |
73 | // make sure the file pool directory exists | |
74 | if (!is_dir($this->filedir)) { | |
1aa01caf | 75 | if (!mkdir($this->filedir, $this->dirpermissions, true)) { |
145a0a31 | 76 | throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble |
172dd12c | 77 | } |
78 | // place warning file in file pool root | |
1aa01caf | 79 | if (!file_exists($this->filedir.'/warning.txt')) { |
80 | file_put_contents($this->filedir.'/warning.txt', | |
81 | 'This directory contains the content of uploaded files and is controlled by Moodle code. Do not manually move, change or rename any of the files and subdirectories here.'); | |
82 | } | |
83 | } | |
84 | // make sure the file pool directory exists | |
85 | if (!is_dir($this->trashdir)) { | |
86 | if (!mkdir($this->trashdir, $this->dirpermissions, true)) { | |
87 | throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble | |
88 | } | |
172dd12c | 89 | } |
90 | } | |
91 | ||
92 | /** | |
bf9ffe27 PS |
93 | * Calculates sha1 hash of unique full path name information. |
94 | * | |
95 | * This hash is a unique file identifier - it is used to improve | |
96 | * performance and overcome db index size limits. | |
97 | * | |
d2b7803e DC |
98 | * @param int $contextid context ID |
99 | * @param string $component component | |
100 | * @param string $filearea file area | |
101 | * @param int $itemid item ID | |
102 | * @param string $filepath file path | |
103 | * @param string $filename file name | |
bf9ffe27 | 104 | * @return string sha1 hash |
172dd12c | 105 | */ |
64f93798 PS |
106 | public static function get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename) { |
107 | return sha1("/$contextid/$component/$filearea/$itemid".$filepath.$filename); | |
172dd12c | 108 | } |
109 | ||
110 | /** | |
111 | * Does this file exist? | |
bf9ffe27 | 112 | * |
d2b7803e DC |
113 | * @param int $contextid context ID |
114 | * @param string $component component | |
115 | * @param string $filearea file area | |
116 | * @param int $itemid item ID | |
117 | * @param string $filepath file path | |
118 | * @param string $filename file name | |
172dd12c | 119 | * @return bool |
120 | */ | |
64f93798 | 121 | public function file_exists($contextid, $component, $filearea, $itemid, $filepath, $filename) { |
172dd12c | 122 | $filepath = clean_param($filepath, PARAM_PATH); |
123 | $filename = clean_param($filename, PARAM_FILE); | |
124 | ||
125 | if ($filename === '') { | |
126 | $filename = '.'; | |
127 | } | |
128 | ||
64f93798 | 129 | $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename); |
172dd12c | 130 | return $this->file_exists_by_hash($pathnamehash); |
131 | } | |
132 | ||
133 | /** | |
d2b7803e | 134 | * Whether or not the file exist |
bf9ffe27 | 135 | * |
d2b7803e | 136 | * @param string $pathnamehash path name hash |
172dd12c | 137 | * @return bool |
138 | */ | |
139 | public function file_exists_by_hash($pathnamehash) { | |
140 | global $DB; | |
141 | ||
142 | return $DB->record_exists('files', array('pathnamehash'=>$pathnamehash)); | |
143 | } | |
144 | ||
693ef3a8 PS |
145 | /** |
146 | * Create instance of file class from database record. | |
147 | * | |
148 | * @param stdClass $file_record record from the files table | |
149 | * @return stored_file instance of file abstraction class | |
150 | */ | |
151 | public function get_file_instance(stdClass $file_record) { | |
152 | return new stored_file($this, $file_record, $this->filedir); | |
153 | } | |
154 | ||
172dd12c | 155 | /** |
25aebf09 | 156 | * Fetch file using local file id. |
bf9ffe27 | 157 | * |
25aebf09 | 158 | * Please do not rely on file ids, it is usually easier to use |
159 | * pathname hashes instead. | |
bf9ffe27 | 160 | * |
d2b7803e DC |
161 | * @param int $fileid file ID |
162 | * @return stored_file|bool stored_file instance if exists, false if not | |
172dd12c | 163 | */ |
164 | public function get_file_by_id($fileid) { | |
165 | global $DB; | |
166 | ||
167 | if ($file_record = $DB->get_record('files', array('id'=>$fileid))) { | |
693ef3a8 | 168 | return $this->get_file_instance($file_record); |
172dd12c | 169 | } else { |
170 | return false; | |
171 | } | |
172 | } | |
173 | ||
174 | /** | |
175 | * Fetch file using local file full pathname hash | |
bf9ffe27 | 176 | * |
d2b7803e DC |
177 | * @param string $pathnamehash path name hash |
178 | * @return stored_file|bool stored_file instance if exists, false if not | |
172dd12c | 179 | */ |
180 | public function get_file_by_hash($pathnamehash) { | |
181 | global $DB; | |
182 | ||
183 | if ($file_record = $DB->get_record('files', array('pathnamehash'=>$pathnamehash))) { | |
693ef3a8 | 184 | return $this->get_file_instance($file_record); |
172dd12c | 185 | } else { |
186 | return false; | |
187 | } | |
188 | } | |
189 | ||
190 | /** | |
bf9ffe27 PS |
191 | * Fetch locally stored file. |
192 | * | |
d2b7803e DC |
193 | * @param int $contextid context ID |
194 | * @param string $component component | |
195 | * @param string $filearea file area | |
196 | * @param int $itemid item ID | |
197 | * @param string $filepath file path | |
198 | * @param string $filename file name | |
199 | * @return stored_file|bool stored_file instance if exists, false if not | |
172dd12c | 200 | */ |
64f93798 | 201 | public function get_file($contextid, $component, $filearea, $itemid, $filepath, $filename) { |
172dd12c | 202 | $filepath = clean_param($filepath, PARAM_PATH); |
203 | $filename = clean_param($filename, PARAM_FILE); | |
204 | ||
205 | if ($filename === '') { | |
206 | $filename = '.'; | |
207 | } | |
208 | ||
64f93798 | 209 | $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename); |
172dd12c | 210 | return $this->get_file_by_hash($pathnamehash); |
211 | } | |
212 | ||
16741cac PS |
213 | /** |
214 | * Are there any files (or directories) | |
d2b7803e DC |
215 | * |
216 | * @param int $contextid context ID | |
217 | * @param string $component component | |
218 | * @param string $filearea file area | |
219 | * @param bool|int $itemid item id or false if all items | |
220 | * @param bool $ignoredirs whether or not ignore directories | |
16741cac PS |
221 | * @return bool empty |
222 | */ | |
223 | public function is_area_empty($contextid, $component, $filearea, $itemid = false, $ignoredirs = true) { | |
224 | global $DB; | |
225 | ||
226 | $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea); | |
227 | $where = "contextid = :contextid AND component = :component AND filearea = :filearea"; | |
228 | ||
229 | if ($itemid !== false) { | |
230 | $params['itemid'] = $itemid; | |
231 | $where .= " AND itemid = :itemid"; | |
232 | } | |
233 | ||
234 | if ($ignoredirs) { | |
235 | $sql = "SELECT 'x' | |
236 | FROM {files} | |
237 | WHERE $where AND filename <> '.'"; | |
238 | } else { | |
239 | $sql = "SELECT 'x' | |
240 | FROM {files} | |
241 | WHERE $where AND (filename <> '.' OR filepath <> '/')"; | |
242 | } | |
243 | ||
244 | return !$DB->record_exists_sql($sql, $params); | |
245 | } | |
246 | ||
172dd12c | 247 | /** |
248 | * Returns all area files (optionally limited by itemid) | |
bf9ffe27 | 249 | * |
d2b7803e DC |
250 | * @param int $contextid context ID |
251 | * @param string $component component | |
252 | * @param string $filearea file area | |
253 | * @param int $itemid item ID or all files if not specified | |
254 | * @param string $sort sort fields | |
255 | * @param bool $includedirs whether or not include directories | |
cd5be217 | 256 | * @return array of stored_files indexed by pathanmehash |
172dd12c | 257 | */ |
16741cac | 258 | public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort="sortorder, itemid, filepath, filename", $includedirs = true) { |
172dd12c | 259 | global $DB; |
260 | ||
64f93798 | 261 | $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea); |
172dd12c | 262 | if ($itemid !== false) { |
263 | $conditions['itemid'] = $itemid; | |
264 | } | |
265 | ||
266 | $result = array(); | |
267 | $file_records = $DB->get_records('files', $conditions, $sort); | |
268 | foreach ($file_records as $file_record) { | |
46fcbcf4 | 269 | if (!$includedirs and $file_record->filename === '.') { |
172dd12c | 270 | continue; |
271 | } | |
693ef3a8 | 272 | $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); |
172dd12c | 273 | } |
274 | return $result; | |
275 | } | |
276 | ||
752b9f42 | 277 | /** |
278 | * Returns array based tree structure of area files | |
bf9ffe27 | 279 | * |
d2b7803e DC |
280 | * @param int $contextid context ID |
281 | * @param string $component component | |
282 | * @param string $filearea file area | |
283 | * @param int $itemid item ID | |
752b9f42 | 284 | * @return array each dir represented by dirname, subdirs, files and dirfile array elements |
285 | */ | |
64f93798 | 286 | public function get_area_tree($contextid, $component, $filearea, $itemid) { |
752b9f42 | 287 | $result = array('dirname'=>'', 'dirfile'=>null, 'subdirs'=>array(), 'files'=>array()); |
d3427cfe | 288 | $files = $this->get_area_files($contextid, $component, $filearea, $itemid, "sortorder, itemid, filepath, filename", true); |
752b9f42 | 289 | // first create directory structure |
290 | foreach ($files as $hash=>$dir) { | |
291 | if (!$dir->is_directory()) { | |
292 | continue; | |
293 | } | |
294 | unset($files[$hash]); | |
295 | if ($dir->get_filepath() === '/') { | |
296 | $result['dirfile'] = $dir; | |
297 | continue; | |
298 | } | |
299 | $parts = explode('/', trim($dir->get_filepath(),'/')); | |
300 | $pointer =& $result; | |
301 | foreach ($parts as $part) { | |
3b607678 | 302 | if ($part === '') { |
303 | continue; | |
304 | } | |
752b9f42 | 305 | if (!isset($pointer['subdirs'][$part])) { |
306 | $pointer['subdirs'][$part] = array('dirname'=>$part, 'dirfile'=>null, 'subdirs'=>array(), 'files'=>array()); | |
307 | } | |
308 | $pointer =& $pointer['subdirs'][$part]; | |
309 | } | |
310 | $pointer['dirfile'] = $dir; | |
311 | unset($pointer); | |
312 | } | |
313 | foreach ($files as $hash=>$file) { | |
314 | $parts = explode('/', trim($file->get_filepath(),'/')); | |
315 | $pointer =& $result; | |
316 | foreach ($parts as $part) { | |
3b607678 | 317 | if ($part === '') { |
318 | continue; | |
319 | } | |
752b9f42 | 320 | $pointer =& $pointer['subdirs'][$part]; |
321 | } | |
322 | $pointer['files'][$file->get_filename()] = $file; | |
323 | unset($pointer); | |
324 | } | |
325 | return $result; | |
326 | } | |
327 | ||
ee03a651 | 328 | /** |
bf9ffe27 PS |
329 | * Returns all files and optionally directories |
330 | * | |
d2b7803e DC |
331 | * @param int $contextid context ID |
332 | * @param string $component component | |
333 | * @param string $filearea file area | |
334 | * @param int $itemid item ID | |
ee03a651 | 335 | * @param int $filepath directory path |
336 | * @param bool $recursive include all subdirectories | |
46fcbcf4 | 337 | * @param bool $includedirs include files and directories |
d2b7803e | 338 | * @param string $sort sort fields |
cd5be217 | 339 | * @return array of stored_files indexed by pathanmehash |
ee03a651 | 340 | */ |
64f93798 | 341 | public function get_directory_files($contextid, $component, $filearea, $itemid, $filepath, $recursive = false, $includedirs = true, $sort = "filepath, filename") { |
ee03a651 | 342 | global $DB; |
343 | ||
64f93798 | 344 | if (!$directory = $this->get_file($contextid, $component, $filearea, $itemid, $filepath, '.')) { |
ee03a651 | 345 | return array(); |
346 | } | |
347 | ||
348 | if ($recursive) { | |
349 | ||
46fcbcf4 | 350 | $dirs = $includedirs ? "" : "AND filename <> '.'"; |
f8311def | 351 | $length = textlib::strlen($filepath); |
ee03a651 | 352 | |
353 | $sql = "SELECT * | |
354 | FROM {files} | |
64f93798 | 355 | WHERE contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid |
655bbf51 | 356 | AND ".$DB->sql_substr("filepath", 1, $length)." = :filepath |
ee03a651 | 357 | AND id <> :dirid |
358 | $dirs | |
359 | ORDER BY $sort"; | |
64f93798 | 360 | $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id()); |
ee03a651 | 361 | |
362 | $files = array(); | |
363 | $dirs = array(); | |
364 | $file_records = $DB->get_records_sql($sql, $params); | |
365 | foreach ($file_records as $file_record) { | |
366 | if ($file_record->filename == '.') { | |
693ef3a8 | 367 | $dirs[$file_record->pathnamehash] = $this->get_file_instance($file_record); |
ee03a651 | 368 | } else { |
693ef3a8 | 369 | $files[$file_record->pathnamehash] = $this->get_file_instance($file_record); |
ee03a651 | 370 | } |
371 | } | |
372 | $result = array_merge($dirs, $files); | |
373 | ||
374 | } else { | |
375 | $result = array(); | |
64f93798 | 376 | $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id()); |
ee03a651 | 377 | |
f8311def | 378 | $length = textlib::strlen($filepath); |
ee03a651 | 379 | |
46fcbcf4 | 380 | if ($includedirs) { |
ee03a651 | 381 | $sql = "SELECT * |
382 | FROM {files} | |
64f93798 | 383 | WHERE contextid = :contextid AND component = :component AND filearea = :filearea |
ee03a651 | 384 | AND itemid = :itemid AND filename = '.' |
655bbf51 | 385 | AND ".$DB->sql_substr("filepath", 1, $length)." = :filepath |
ee03a651 | 386 | AND id <> :dirid |
387 | ORDER BY $sort"; | |
388 | $reqlevel = substr_count($filepath, '/') + 1; | |
389 | $file_records = $DB->get_records_sql($sql, $params); | |
390 | foreach ($file_records as $file_record) { | |
391 | if (substr_count($file_record->filepath, '/') !== $reqlevel) { | |
392 | continue; | |
393 | } | |
693ef3a8 | 394 | $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); |
ee03a651 | 395 | } |
396 | } | |
397 | ||
398 | $sql = "SELECT * | |
399 | FROM {files} | |
64f93798 | 400 | WHERE contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid |
ee03a651 | 401 | AND filepath = :filepath AND filename <> '.' |
402 | ORDER BY $sort"; | |
403 | ||
404 | $file_records = $DB->get_records_sql($sql, $params); | |
405 | foreach ($file_records as $file_record) { | |
693ef3a8 | 406 | $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); |
ee03a651 | 407 | } |
408 | } | |
409 | ||
410 | return $result; | |
411 | } | |
412 | ||
172dd12c | 413 | /** |
bf9ffe27 PS |
414 | * Delete all area files (optionally limited by itemid). |
415 | * | |
d2b7803e DC |
416 | * @param int $contextid context ID |
417 | * @param string $component component | |
418 | * @param string $filearea file area or all areas in context if not specified | |
419 | * @param int $itemid item ID or all files if not specified | |
bf9ffe27 | 420 | * @return bool success |
172dd12c | 421 | */ |
64f93798 | 422 | public function delete_area_files($contextid, $component = false, $filearea = false, $itemid = false) { |
172dd12c | 423 | global $DB; |
424 | ||
6311eb61 | 425 | $conditions = array('contextid'=>$contextid); |
64f93798 PS |
426 | if ($component !== false) { |
427 | $conditions['component'] = $component; | |
428 | } | |
6311eb61 | 429 | if ($filearea !== false) { |
430 | $conditions['filearea'] = $filearea; | |
431 | } | |
172dd12c | 432 | if ($itemid !== false) { |
433 | $conditions['itemid'] = $itemid; | |
434 | } | |
435 | ||
172dd12c | 436 | $file_records = $DB->get_records('files', $conditions); |
437 | foreach ($file_records as $file_record) { | |
af7b3673 | 438 | $this->get_file_instance($file_record)->delete(); |
172dd12c | 439 | } |
440 | ||
bf9ffe27 | 441 | return true; // BC only |
172dd12c | 442 | } |
443 | ||
af7b3673 TH |
444 | /** |
445 | * Delete all the files from certain areas where itemid is limited by an | |
446 | * arbitrary bit of SQL. | |
447 | * | |
448 | * @param int $contextid the id of the context the files belong to. Must be given. | |
449 | * @param string $component the owning component. Must be given. | |
450 | * @param string $filearea the file area name. Must be given. | |
451 | * @param string $itemidstest an SQL fragment that the itemid must match. Used | |
452 | * in the query like WHERE itemid $itemidstest. Must used named parameters, | |
453 | * and may not used named parameters called contextid, component or filearea. | |
454 | * @param array $params any query params used by $itemidstest. | |
455 | */ | |
456 | public function delete_area_files_select($contextid, $component, | |
457 | $filearea, $itemidstest, array $params = null) { | |
458 | global $DB; | |
459 | ||
460 | $where = "contextid = :contextid | |
461 | AND component = :component | |
462 | AND filearea = :filearea | |
463 | AND itemid $itemidstest"; | |
464 | $params['contextid'] = $contextid; | |
465 | $params['component'] = $component; | |
466 | $params['filearea'] = $filearea; | |
467 | ||
468 | $file_records = $DB->get_recordset_select('files', $where, $params); | |
469 | foreach ($file_records as $file_record) { | |
470 | $this->get_file_instance($file_record)->delete(); | |
471 | } | |
472 | $file_records->close(); | |
473 | } | |
474 | ||
d2af1014 TH |
475 | /** |
476 | * Move all the files in a file area from one context to another. | |
d2b7803e DC |
477 | * |
478 | * @param int $oldcontextid the context the files are being moved from. | |
479 | * @param int $newcontextid the context the files are being moved to. | |
d2af1014 TH |
480 | * @param string $component the plugin that these files belong to. |
481 | * @param string $filearea the name of the file area. | |
d2b7803e DC |
482 | * @param int $itemid file item ID |
483 | * @return int the number of files moved, for information. | |
d2af1014 TH |
484 | */ |
485 | public function move_area_files_to_new_context($oldcontextid, $newcontextid, $component, $filearea, $itemid = false) { | |
486 | // Note, this code is based on some code that Petr wrote in | |
487 | // forum_move_attachments in mod/forum/lib.php. I moved it here because | |
488 | // I needed it in the question code too. | |
489 | $count = 0; | |
490 | ||
491 | $oldfiles = $this->get_area_files($oldcontextid, $component, $filearea, $itemid, 'id', false); | |
492 | foreach ($oldfiles as $oldfile) { | |
493 | $filerecord = new stdClass(); | |
494 | $filerecord->contextid = $newcontextid; | |
495 | $this->create_file_from_storedfile($filerecord, $oldfile); | |
496 | $count += 1; | |
497 | } | |
498 | ||
499 | if ($count) { | |
500 | $this->delete_area_files($oldcontextid, $component, $filearea, $itemid); | |
501 | } | |
502 | ||
503 | return $count; | |
504 | } | |
505 | ||
172dd12c | 506 | /** |
bf9ffe27 PS |
507 | * Recursively creates directory. |
508 | * | |
d2b7803e DC |
509 | * @param int $contextid context ID |
510 | * @param string $component component | |
511 | * @param string $filearea file area | |
512 | * @param int $itemid item ID | |
513 | * @param string $filepath file path | |
514 | * @param int $userid the user ID | |
172dd12c | 515 | * @return bool success |
516 | */ | |
64f93798 | 517 | public function create_directory($contextid, $component, $filearea, $itemid, $filepath, $userid = null) { |
172dd12c | 518 | global $DB; |
519 | ||
520 | // validate all parameters, we do not want any rubbish stored in database, right? | |
521 | if (!is_number($contextid) or $contextid < 1) { | |
145a0a31 | 522 | throw new file_exception('storedfileproblem', 'Invalid contextid'); |
172dd12c | 523 | } |
524 | ||
aff24313 PS |
525 | $component = clean_param($component, PARAM_COMPONENT); |
526 | if (empty($component)) { | |
64f93798 PS |
527 | throw new file_exception('storedfileproblem', 'Invalid component'); |
528 | } | |
529 | ||
aff24313 PS |
530 | $filearea = clean_param($filearea, PARAM_AREA); |
531 | if (empty($filearea)) { | |
145a0a31 | 532 | throw new file_exception('storedfileproblem', 'Invalid filearea'); |
172dd12c | 533 | } |
534 | ||
535 | if (!is_number($itemid) or $itemid < 0) { | |
145a0a31 | 536 | throw new file_exception('storedfileproblem', 'Invalid itemid'); |
172dd12c | 537 | } |
538 | ||
539 | $filepath = clean_param($filepath, PARAM_PATH); | |
540 | if (strpos($filepath, '/') !== 0 or strrpos($filepath, '/') !== strlen($filepath)-1) { | |
541 | // path must start and end with '/' | |
145a0a31 | 542 | throw new file_exception('storedfileproblem', 'Invalid file path'); |
172dd12c | 543 | } |
544 | ||
64f93798 | 545 | $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, '.'); |
172dd12c | 546 | |
547 | if ($dir_info = $this->get_file_by_hash($pathnamehash)) { | |
548 | return $dir_info; | |
549 | } | |
550 | ||
551 | static $contenthash = null; | |
552 | if (!$contenthash) { | |
b48f3e06 | 553 | $this->add_string_to_pool(''); |
172dd12c | 554 | $contenthash = sha1(''); |
555 | } | |
556 | ||
557 | $now = time(); | |
558 | ||
ac6f1a82 | 559 | $dir_record = new stdClass(); |
172dd12c | 560 | $dir_record->contextid = $contextid; |
64f93798 | 561 | $dir_record->component = $component; |
172dd12c | 562 | $dir_record->filearea = $filearea; |
563 | $dir_record->itemid = $itemid; | |
564 | $dir_record->filepath = $filepath; | |
565 | $dir_record->filename = '.'; | |
566 | $dir_record->contenthash = $contenthash; | |
567 | $dir_record->filesize = 0; | |
568 | ||
569 | $dir_record->timecreated = $now; | |
570 | $dir_record->timemodified = $now; | |
571 | $dir_record->mimetype = null; | |
572 | $dir_record->userid = $userid; | |
573 | ||
574 | $dir_record->pathnamehash = $pathnamehash; | |
575 | ||
576 | $DB->insert_record('files', $dir_record); | |
577 | $dir_info = $this->get_file_by_hash($pathnamehash); | |
578 | ||
579 | if ($filepath !== '/') { | |
580 | //recurse to parent dirs | |
581 | $filepath = trim($filepath, '/'); | |
582 | $filepath = explode('/', $filepath); | |
583 | array_pop($filepath); | |
584 | $filepath = implode('/', $filepath); | |
585 | $filepath = ($filepath === '') ? '/' : "/$filepath/"; | |
64f93798 | 586 | $this->create_directory($contextid, $component, $filearea, $itemid, $filepath, $userid); |
172dd12c | 587 | } |
588 | ||
589 | return $dir_info; | |
590 | } | |
591 | ||
592 | /** | |
bf9ffe27 PS |
593 | * Add new local file based on existing local file. |
594 | * | |
d2b7803e DC |
595 | * @param stdClass|array $file_record object or array describing changes |
596 | * @param stored_file|int $fileorid id or stored_file instance of the existing local file | |
bf9ffe27 | 597 | * @return stored_file instance of newly created file |
172dd12c | 598 | */ |
72d0aed6 | 599 | public function create_file_from_storedfile($file_record, $fileorid) { |
4fb2306e | 600 | global $DB; |
172dd12c | 601 | |
72d0aed6 | 602 | if ($fileorid instanceof stored_file) { |
603 | $fid = $fileorid->get_id(); | |
604 | } else { | |
605 | $fid = $fileorid; | |
8eb1e0a1 | 606 | } |
607 | ||
ec8b711f | 608 | $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record! |
609 | ||
172dd12c | 610 | unset($file_record['id']); |
611 | unset($file_record['filesize']); | |
612 | unset($file_record['contenthash']); | |
8eb1e0a1 | 613 | unset($file_record['pathnamehash']); |
172dd12c | 614 | |
ebcac6c6 | 615 | if (!$newrecord = $DB->get_record('files', array('id'=>$fid))) { |
145a0a31 | 616 | throw new file_exception('storedfileproblem', 'File does not exist'); |
172dd12c | 617 | } |
618 | ||
619 | unset($newrecord->id); | |
620 | ||
621 | foreach ($file_record as $key=>$value) { | |
622 | // validate all parameters, we do not want any rubbish stored in database, right? | |
623 | if ($key == 'contextid' and (!is_number($value) or $value < 1)) { | |
145a0a31 | 624 | throw new file_exception('storedfileproblem', 'Invalid contextid'); |
172dd12c | 625 | } |
626 | ||
64f93798 | 627 | if ($key == 'component') { |
aff24313 PS |
628 | $value = clean_param($value, PARAM_COMPONENT); |
629 | if (empty($value)) { | |
64f93798 PS |
630 | throw new file_exception('storedfileproblem', 'Invalid component'); |
631 | } | |
632 | } | |
633 | ||
172dd12c | 634 | if ($key == 'filearea') { |
aff24313 PS |
635 | $value = clean_param($value, PARAM_AREA); |
636 | if (empty($value)) { | |
145a0a31 | 637 | throw new file_exception('storedfileproblem', 'Invalid filearea'); |
172dd12c | 638 | } |
639 | } | |
640 | ||
641 | if ($key == 'itemid' and (!is_number($value) or $value < 0)) { | |
145a0a31 | 642 | throw new file_exception('storedfileproblem', 'Invalid itemid'); |
172dd12c | 643 | } |
644 | ||
645 | ||
646 | if ($key == 'filepath') { | |
647 | $value = clean_param($value, PARAM_PATH); | |
00c32c54 | 648 | if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) { |
172dd12c | 649 | // path must start and end with '/' |
145a0a31 | 650 | throw new file_exception('storedfileproblem', 'Invalid file path'); |
172dd12c | 651 | } |
652 | } | |
653 | ||
654 | if ($key == 'filename') { | |
655 | $value = clean_param($value, PARAM_FILE); | |
656 | if ($value === '') { | |
657 | // path must start and end with '/' | |
145a0a31 | 658 | throw new file_exception('storedfileproblem', 'Invalid file name'); |
172dd12c | 659 | } |
660 | } | |
661 | ||
260c4a5b PS |
662 | if ($key === 'timecreated' or $key === 'timemodified') { |
663 | if (!is_number($value)) { | |
664 | throw new file_exception('storedfileproblem', 'Invalid file '.$key); | |
665 | } | |
666 | if ($value < 0) { | |
667 | //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) | |
668 | $value = 0; | |
669 | } | |
670 | } | |
671 | ||
172dd12c | 672 | $newrecord->$key = $value; |
673 | } | |
674 | ||
64f93798 | 675 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
172dd12c | 676 | |
cd5be217 | 677 | if ($newrecord->filename === '.') { |
678 | // special case - only this function supports directories ;-) | |
64f93798 | 679 | $directory = $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
cd5be217 | 680 | // update the existing directory with the new data |
681 | $newrecord->id = $directory->get_id(); | |
b8ac7ece | 682 | $DB->update_record('files', $newrecord); |
693ef3a8 | 683 | return $this->get_file_instance($newrecord); |
cd5be217 | 684 | } |
685 | ||
172dd12c | 686 | try { |
687 | $newrecord->id = $DB->insert_record('files', $newrecord); | |
8a680500 | 688 | } catch (dml_exception $e) { |
64f93798 | 689 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, |
694f3b74 | 690 | $newrecord->filepath, $newrecord->filename, $e->debuginfo); |
172dd12c | 691 | } |
692 | ||
64f93798 | 693 | $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
172dd12c | 694 | |
693ef3a8 | 695 | return $this->get_file_instance($newrecord); |
172dd12c | 696 | } |
697 | ||
6e73ac42 | 698 | /** |
bf9ffe27 PS |
699 | * Add new local file. |
700 | * | |
d2b7803e DC |
701 | * @param stdClass|array $file_record object or array describing file |
702 | * @param string $url the URL to the file | |
703 | * @param array $options {@link download_file_content()} options | |
3a1055a5 | 704 | * @param bool $usetempfile use temporary file for download, may prevent out of memory problems |
d2b7803e | 705 | * @return stored_file |
6e73ac42 | 706 | */ |
3a1055a5 | 707 | public function create_file_from_url($file_record, $url, array $options = NULL, $usetempfile = false) { |
ec8b711f | 708 | |
709 | $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects | |
6e73ac42 | 710 | $file_record = (object)$file_record; // we support arrays too |
711 | ||
712 | $headers = isset($options['headers']) ? $options['headers'] : null; | |
713 | $postdata = isset($options['postdata']) ? $options['postdata'] : null; | |
714 | $fullresponse = isset($options['fullresponse']) ? $options['fullresponse'] : false; | |
715 | $timeout = isset($options['timeout']) ? $options['timeout'] : 300; | |
716 | $connecttimeout = isset($options['connecttimeout']) ? $options['connecttimeout'] : 20; | |
717 | $skipcertverify = isset($options['skipcertverify']) ? $options['skipcertverify'] : false; | |
5f1c825d | 718 | $calctimeout = isset($options['calctimeout']) ? $options['calctimeout'] : false; |
6e73ac42 | 719 | |
6e73ac42 | 720 | if (!isset($file_record->filename)) { |
721 | $parts = explode('/', $url); | |
722 | $filename = array_pop($parts); | |
723 | $file_record->filename = clean_param($filename, PARAM_FILE); | |
724 | } | |
1dce6261 DC |
725 | $source = !empty($file_record->source) ? $file_record->source : $url; |
726 | $file_record->source = clean_param($source, PARAM_URL); | |
6e73ac42 | 727 | |
3a1055a5 | 728 | if ($usetempfile) { |
c426ef3a | 729 | check_dir_exists($this->tempdir); |
3a1055a5 | 730 | $tmpfile = tempnam($this->tempdir, 'newfromurl'); |
60b5a2fe | 731 | $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify, $tmpfile, $calctimeout); |
3a1055a5 PS |
732 | if ($content === false) { |
733 | throw new file_exception('storedfileproblem', 'Can not fetch file form URL'); | |
734 | } | |
735 | try { | |
736 | $newfile = $this->create_file_from_pathname($file_record, $tmpfile); | |
737 | @unlink($tmpfile); | |
738 | return $newfile; | |
739 | } catch (Exception $e) { | |
740 | @unlink($tmpfile); | |
741 | throw $e; | |
742 | } | |
743 | ||
744 | } else { | |
60b5a2fe | 745 | $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify, NULL, $calctimeout); |
3a1055a5 PS |
746 | if ($content === false) { |
747 | throw new file_exception('storedfileproblem', 'Can not fetch file form URL'); | |
748 | } | |
749 | return $this->create_file_from_string($file_record, $content); | |
750 | } | |
6e73ac42 | 751 | } |
752 | ||
172dd12c | 753 | /** |
bf9ffe27 PS |
754 | * Add new local file. |
755 | * | |
d2b7803e DC |
756 | * @param stdClass|array $file_record object or array describing file |
757 | * @param string $pathname path to file or content of file | |
758 | * @return stored_file | |
172dd12c | 759 | */ |
760 | public function create_file_from_pathname($file_record, $pathname) { | |
4fb2306e | 761 | global $DB; |
172dd12c | 762 | |
ec8b711f | 763 | $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects |
172dd12c | 764 | $file_record = (object)$file_record; // we support arrays too |
765 | ||
766 | // validate all parameters, we do not want any rubbish stored in database, right? | |
767 | if (!is_number($file_record->contextid) or $file_record->contextid < 1) { | |
145a0a31 | 768 | throw new file_exception('storedfileproblem', 'Invalid contextid'); |
172dd12c | 769 | } |
770 | ||
aff24313 PS |
771 | $file_record->component = clean_param($file_record->component, PARAM_COMPONENT); |
772 | if (empty($file_record->component)) { | |
64f93798 PS |
773 | throw new file_exception('storedfileproblem', 'Invalid component'); |
774 | } | |
775 | ||
aff24313 PS |
776 | $file_record->filearea = clean_param($file_record->filearea, PARAM_AREA); |
777 | if (empty($file_record->filearea)) { | |
145a0a31 | 778 | throw new file_exception('storedfileproblem', 'Invalid filearea'); |
172dd12c | 779 | } |
780 | ||
781 | if (!is_number($file_record->itemid) or $file_record->itemid < 0) { | |
145a0a31 | 782 | throw new file_exception('storedfileproblem', 'Invalid itemid'); |
172dd12c | 783 | } |
784 | ||
f79321f1 DC |
785 | if (!empty($file_record->sortorder)) { |
786 | if (!is_number($file_record->sortorder) or $file_record->sortorder < 0) { | |
787 | $file_record->sortorder = 0; | |
788 | } | |
789 | } else { | |
790 | $file_record->sortorder = 0; | |
791 | } | |
792 | ||
172dd12c | 793 | $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); |
794 | if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { | |
795 | // path must start and end with '/' | |
145a0a31 | 796 | throw new file_exception('storedfileproblem', 'Invalid file path'); |
172dd12c | 797 | } |
798 | ||
799 | $file_record->filename = clean_param($file_record->filename, PARAM_FILE); | |
800 | if ($file_record->filename === '') { | |
e1dcb950 | 801 | // filename must not be empty |
145a0a31 | 802 | throw new file_exception('storedfileproblem', 'Invalid file name'); |
172dd12c | 803 | } |
804 | ||
805 | $now = time(); | |
260c4a5b PS |
806 | if (isset($file_record->timecreated)) { |
807 | if (!is_number($file_record->timecreated)) { | |
808 | throw new file_exception('storedfileproblem', 'Invalid file timecreated'); | |
809 | } | |
810 | if ($file_record->timecreated < 0) { | |
811 | //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) | |
812 | $file_record->timecreated = 0; | |
813 | } | |
814 | } else { | |
815 | $file_record->timecreated = $now; | |
816 | } | |
817 | ||
818 | if (isset($file_record->timemodified)) { | |
819 | if (!is_number($file_record->timemodified)) { | |
820 | throw new file_exception('storedfileproblem', 'Invalid file timemodified'); | |
821 | } | |
822 | if ($file_record->timemodified < 0) { | |
823 | //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) | |
824 | $file_record->timemodified = 0; | |
825 | } | |
826 | } else { | |
827 | $file_record->timemodified = $now; | |
828 | } | |
172dd12c | 829 | |
ac6f1a82 | 830 | $newrecord = new stdClass(); |
172dd12c | 831 | |
832 | $newrecord->contextid = $file_record->contextid; | |
64f93798 | 833 | $newrecord->component = $file_record->component; |
172dd12c | 834 | $newrecord->filearea = $file_record->filearea; |
835 | $newrecord->itemid = $file_record->itemid; | |
836 | $newrecord->filepath = $file_record->filepath; | |
837 | $newrecord->filename = $file_record->filename; | |
838 | ||
260c4a5b PS |
839 | $newrecord->timecreated = $file_record->timecreated; |
840 | $newrecord->timemodified = $file_record->timemodified; | |
172dd12c | 841 | $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; |
842 | $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; | |
4fb2306e PS |
843 | $newrecord->source = empty($file_record->source) ? null : $file_record->source; |
844 | $newrecord->author = empty($file_record->author) ? null : $file_record->author; | |
845 | $newrecord->license = empty($file_record->license) ? null : $file_record->license; | |
f79321f1 | 846 | $newrecord->sortorder = $file_record->sortorder; |
172dd12c | 847 | |
b48f3e06 | 848 | list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_file_to_pool($pathname); |
172dd12c | 849 | |
64f93798 | 850 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
172dd12c | 851 | |
852 | try { | |
853 | $newrecord->id = $DB->insert_record('files', $newrecord); | |
8a680500 | 854 | } catch (dml_exception $e) { |
172dd12c | 855 | if ($newfile) { |
ead14290 | 856 | $this->deleted_file_cleanup($newrecord->contenthash); |
172dd12c | 857 | } |
64f93798 | 858 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, |
694f3b74 | 859 | $newrecord->filepath, $newrecord->filename, $e->debuginfo); |
172dd12c | 860 | } |
861 | ||
64f93798 | 862 | $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
172dd12c | 863 | |
693ef3a8 | 864 | return $this->get_file_instance($newrecord); |
172dd12c | 865 | } |
866 | ||
867 | /** | |
bf9ffe27 PS |
868 | * Add new local file. |
869 | * | |
d2b7803e | 870 | * @param stdClass|array $file_record object or array describing file |
172dd12c | 871 | * @param string $content content of file |
d2b7803e | 872 | * @return stored_file |
172dd12c | 873 | */ |
874 | public function create_file_from_string($file_record, $content) { | |
4fb2306e | 875 | global $DB; |
172dd12c | 876 | |
ec8b711f | 877 | $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects |
172dd12c | 878 | $file_record = (object)$file_record; // we support arrays too |
879 | ||
880 | // validate all parameters, we do not want any rubbish stored in database, right? | |
881 | if (!is_number($file_record->contextid) or $file_record->contextid < 1) { | |
145a0a31 | 882 | throw new file_exception('storedfileproblem', 'Invalid contextid'); |
172dd12c | 883 | } |
884 | ||
aff24313 PS |
885 | $file_record->component = clean_param($file_record->component, PARAM_COMPONENT); |
886 | if (empty($file_record->component)) { | |
64f93798 PS |
887 | throw new file_exception('storedfileproblem', 'Invalid component'); |
888 | } | |
889 | ||
65e9eac0 | 890 | $file_record->filearea = clean_param($file_record->filearea, PARAM_AREA); |
aff24313 | 891 | if (empty($file_record->filearea)) { |
145a0a31 | 892 | throw new file_exception('storedfileproblem', 'Invalid filearea'); |
172dd12c | 893 | } |
894 | ||
895 | if (!is_number($file_record->itemid) or $file_record->itemid < 0) { | |
145a0a31 | 896 | throw new file_exception('storedfileproblem', 'Invalid itemid'); |
172dd12c | 897 | } |
898 | ||
f79321f1 DC |
899 | if (!empty($file_record->sortorder)) { |
900 | if (!is_number($file_record->sortorder) or $file_record->sortorder < 0) { | |
901 | $file_record->sortorder = 0; | |
902 | } | |
903 | } else { | |
904 | $file_record->sortorder = 0; | |
905 | } | |
906 | ||
172dd12c | 907 | $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); |
908 | if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { | |
909 | // path must start and end with '/' | |
145a0a31 | 910 | throw new file_exception('storedfileproblem', 'Invalid file path'); |
172dd12c | 911 | } |
912 | ||
913 | $file_record->filename = clean_param($file_record->filename, PARAM_FILE); | |
914 | if ($file_record->filename === '') { | |
915 | // path must start and end with '/' | |
145a0a31 | 916 | throw new file_exception('storedfileproblem', 'Invalid file name'); |
172dd12c | 917 | } |
918 | ||
919 | $now = time(); | |
260c4a5b PS |
920 | if (isset($file_record->timecreated)) { |
921 | if (!is_number($file_record->timecreated)) { | |
922 | throw new file_exception('storedfileproblem', 'Invalid file timecreated'); | |
923 | } | |
924 | if ($file_record->timecreated < 0) { | |
925 | //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) | |
926 | $file_record->timecreated = 0; | |
927 | } | |
928 | } else { | |
929 | $file_record->timecreated = $now; | |
930 | } | |
931 | ||
932 | if (isset($file_record->timemodified)) { | |
933 | if (!is_number($file_record->timemodified)) { | |
934 | throw new file_exception('storedfileproblem', 'Invalid file timemodified'); | |
935 | } | |
936 | if ($file_record->timemodified < 0) { | |
937 | //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) | |
938 | $file_record->timemodified = 0; | |
939 | } | |
940 | } else { | |
941 | $file_record->timemodified = $now; | |
942 | } | |
172dd12c | 943 | |
ac6f1a82 | 944 | $newrecord = new stdClass(); |
172dd12c | 945 | |
946 | $newrecord->contextid = $file_record->contextid; | |
64f93798 | 947 | $newrecord->component = $file_record->component; |
172dd12c | 948 | $newrecord->filearea = $file_record->filearea; |
949 | $newrecord->itemid = $file_record->itemid; | |
950 | $newrecord->filepath = $file_record->filepath; | |
951 | $newrecord->filename = $file_record->filename; | |
952 | ||
260c4a5b PS |
953 | $newrecord->timecreated = $file_record->timecreated; |
954 | $newrecord->timemodified = $file_record->timemodified; | |
172dd12c | 955 | $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; |
956 | $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; | |
4fb2306e PS |
957 | $newrecord->source = empty($file_record->source) ? null : $file_record->source; |
958 | $newrecord->author = empty($file_record->author) ? null : $file_record->author; | |
959 | $newrecord->license = empty($file_record->license) ? null : $file_record->license; | |
f79321f1 | 960 | $newrecord->sortorder = $file_record->sortorder; |
1dce6261 | 961 | |
b48f3e06 | 962 | list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content); |
172dd12c | 963 | |
64f93798 | 964 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
172dd12c | 965 | |
966 | try { | |
967 | $newrecord->id = $DB->insert_record('files', $newrecord); | |
8a680500 | 968 | } catch (dml_exception $e) { |
172dd12c | 969 | if ($newfile) { |
ead14290 | 970 | $this->deleted_file_cleanup($newrecord->contenthash); |
172dd12c | 971 | } |
64f93798 | 972 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, |
694f3b74 | 973 | $newrecord->filepath, $newrecord->filename, $e->debuginfo); |
172dd12c | 974 | } |
975 | ||
64f93798 | 976 | $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
172dd12c | 977 | |
693ef3a8 | 978 | return $this->get_file_instance($newrecord); |
172dd12c | 979 | } |
980 | ||
797f19e8 | 981 | /** |
982 | * Creates new image file from existing. | |
bf9ffe27 | 983 | * |
d2b7803e DC |
984 | * @param stdClass|array $file_record object or array describing new file |
985 | * @param int|stored_file $fid file id or stored file object | |
797f19e8 | 986 | * @param int $newwidth in pixels |
987 | * @param int $newheight in pixels | |
d2b7803e | 988 | * @param bool $keepaspectratio whether or not keep aspect ratio |
bf9ffe27 | 989 | * @param int $quality depending on image type 0-100 for jpeg, 0-9 (0 means no compression) for png |
d2b7803e | 990 | * @return stored_file |
797f19e8 | 991 | */ |
bf9ffe27 | 992 | public function convert_image($file_record, $fid, $newwidth = NULL, $newheight = NULL, $keepaspectratio = true, $quality = NULL) { |
6b2f2184 AD |
993 | if (!function_exists('imagecreatefromstring')) { |
994 | //Most likely the GD php extension isn't installed | |
995 | //image conversion cannot succeed | |
996 | throw new file_exception('storedfileproblem', 'imagecreatefromstring() doesnt exist. The PHP extension "GD" must be installed for image conversion.'); | |
997 | } | |
998 | ||
797f19e8 | 999 | if ($fid instanceof stored_file) { |
1000 | $fid = $fid->get_id(); | |
1001 | } | |
1002 | ||
1003 | $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record! | |
1004 | ||
1005 | if (!$file = $this->get_file_by_id($fid)) { // make sure file really exists and we we correct data | |
1006 | throw new file_exception('storedfileproblem', 'File does not exist'); | |
1007 | } | |
1008 | ||
1009 | if (!$imageinfo = $file->get_imageinfo()) { | |
1010 | throw new file_exception('storedfileproblem', 'File is not an image'); | |
1011 | } | |
1012 | ||
1013 | if (!isset($file_record['filename'])) { | |
0ea7fc25 | 1014 | $file_record['filename'] = $file->get_filename(); |
797f19e8 | 1015 | } |
1016 | ||
1017 | if (!isset($file_record['mimetype'])) { | |
1018 | $file_record['mimetype'] = mimeinfo('type', $file_record['filename']); | |
1019 | } | |
1020 | ||
1021 | $width = $imageinfo['width']; | |
1022 | $height = $imageinfo['height']; | |
1023 | $mimetype = $imageinfo['mimetype']; | |
1024 | ||
1025 | if ($keepaspectratio) { | |
1026 | if (0 >= $newwidth and 0 >= $newheight) { | |
1027 | // no sizes specified | |
1028 | $newwidth = $width; | |
1029 | $newheight = $height; | |
1030 | ||
1031 | } else if (0 < $newwidth and 0 < $newheight) { | |
1032 | $xheight = ($newwidth*($height/$width)); | |
1033 | if ($xheight < $newheight) { | |
1034 | $newheight = (int)$xheight; | |
1035 | } else { | |
1036 | $newwidth = (int)($newheight*($width/$height)); | |
1037 | } | |
1038 | ||
1039 | } else if (0 < $newwidth) { | |
1040 | $newheight = (int)($newwidth*($height/$width)); | |
1041 | ||
1042 | } else { //0 < $newheight | |
1043 | $newwidth = (int)($newheight*($width/$height)); | |
1044 | } | |
1045 | ||
1046 | } else { | |
1047 | if (0 >= $newwidth) { | |
1048 | $newwidth = $width; | |
1049 | } | |
1050 | if (0 >= $newheight) { | |
1051 | $newheight = $height; | |
1052 | } | |
1053 | } | |
1054 | ||
1055 | $img = imagecreatefromstring($file->get_content()); | |
1056 | if ($height != $newheight or $width != $newwidth) { | |
1057 | $newimg = imagecreatetruecolor($newwidth, $newheight); | |
1058 | if (!imagecopyresized($newimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height)) { | |
1059 | // weird | |
1060 | throw new file_exception('storedfileproblem', 'Can not resize image'); | |
1061 | } | |
1062 | imagedestroy($img); | |
1063 | $img = $newimg; | |
1064 | } | |
1065 | ||
1066 | ob_start(); | |
1067 | switch ($file_record['mimetype']) { | |
1068 | case 'image/gif': | |
1069 | imagegif($img); | |
1070 | break; | |
1071 | ||
1072 | case 'image/jpeg': | |
1073 | if (is_null($quality)) { | |
1074 | imagejpeg($img); | |
1075 | } else { | |
1076 | imagejpeg($img, NULL, $quality); | |
1077 | } | |
1078 | break; | |
1079 | ||
1080 | case 'image/png': | |
8bd49ec0 | 1081 | $quality = (int)$quality; |
797f19e8 | 1082 | imagepng($img, NULL, $quality, NULL); |
1083 | break; | |
1084 | ||
1085 | default: | |
1086 | throw new file_exception('storedfileproblem', 'Unsupported mime type'); | |
1087 | } | |
1088 | ||
1089 | $content = ob_get_contents(); | |
1090 | ob_end_clean(); | |
1091 | imagedestroy($img); | |
1092 | ||
1093 | if (!$content) { | |
1094 | throw new file_exception('storedfileproblem', 'Can not convert image'); | |
1095 | } | |
1096 | ||
1097 | return $this->create_file_from_string($file_record, $content); | |
1098 | } | |
1099 | ||
172dd12c | 1100 | /** |
bf9ffe27 PS |
1101 | * Add file content to sha1 pool. |
1102 | * | |
172dd12c | 1103 | * @param string $pathname path to file |
bf9ffe27 PS |
1104 | * @param string $contenthash sha1 hash of content if known (performance only) |
1105 | * @return array (contenthash, filesize, newfile) | |
172dd12c | 1106 | */ |
bf9ffe27 | 1107 | public function add_file_to_pool($pathname, $contenthash = NULL) { |
172dd12c | 1108 | if (!is_readable($pathname)) { |
d610cb89 | 1109 | throw new file_exception('storedfilecannotread', '', $pathname); |
172dd12c | 1110 | } |
1111 | ||
1112 | if (is_null($contenthash)) { | |
1113 | $contenthash = sha1_file($pathname); | |
1114 | } | |
1115 | ||
1116 | $filesize = filesize($pathname); | |
1117 | ||
1118 | $hashpath = $this->path_from_hash($contenthash); | |
1119 | $hashfile = "$hashpath/$contenthash"; | |
1120 | ||
1121 | if (file_exists($hashfile)) { | |
1122 | if (filesize($hashfile) !== $filesize) { | |
1123 | throw new file_pool_content_exception($contenthash); | |
1124 | } | |
1125 | $newfile = false; | |
1126 | ||
1127 | } else { | |
1aa01caf | 1128 | if (!is_dir($hashpath)) { |
1129 | if (!mkdir($hashpath, $this->dirpermissions, true)) { | |
1130 | throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble | |
1131 | } | |
172dd12c | 1132 | } |
1133 | $newfile = true; | |
1134 | ||
6c0e2d08 | 1135 | if (!copy($pathname, $hashfile)) { |
d610cb89 | 1136 | throw new file_exception('storedfilecannotread', '', $pathname); |
172dd12c | 1137 | } |
172dd12c | 1138 | |
1139 | if (filesize($hashfile) !== $filesize) { | |
1140 | @unlink($hashfile); | |
1141 | throw new file_pool_content_exception($contenthash); | |
1142 | } | |
1aa01caf | 1143 | chmod($hashfile, $this->filepermissions); // fix permissions if needed |
172dd12c | 1144 | } |
1145 | ||
1146 | ||
1147 | return array($contenthash, $filesize, $newfile); | |
1148 | } | |
1149 | ||
1150 | /** | |
bf9ffe27 PS |
1151 | * Add string content to sha1 pool. |
1152 | * | |
172dd12c | 1153 | * @param string $content file content - binary string |
bf9ffe27 | 1154 | * @return array (contenthash, filesize, newfile) |
172dd12c | 1155 | */ |
b48f3e06 | 1156 | public function add_string_to_pool($content) { |
172dd12c | 1157 | $contenthash = sha1($content); |
1158 | $filesize = strlen($content); // binary length | |
1159 | ||
1160 | $hashpath = $this->path_from_hash($contenthash); | |
1161 | $hashfile = "$hashpath/$contenthash"; | |
1162 | ||
1163 | ||
1164 | if (file_exists($hashfile)) { | |
1165 | if (filesize($hashfile) !== $filesize) { | |
1166 | throw new file_pool_content_exception($contenthash); | |
1167 | } | |
1168 | $newfile = false; | |
1169 | ||
1170 | } else { | |
1aa01caf | 1171 | if (!is_dir($hashpath)) { |
1172 | if (!mkdir($hashpath, $this->dirpermissions, true)) { | |
1173 | throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble | |
1174 | } | |
172dd12c | 1175 | } |
1176 | $newfile = true; | |
1177 | ||
6c0e2d08 | 1178 | file_put_contents($hashfile, $content); |
172dd12c | 1179 | |
1180 | if (filesize($hashfile) !== $filesize) { | |
1181 | @unlink($hashfile); | |
1182 | throw new file_pool_content_exception($contenthash); | |
1183 | } | |
1aa01caf | 1184 | chmod($hashfile, $this->filepermissions); // fix permissions if needed |
172dd12c | 1185 | } |
1186 | ||
1187 | return array($contenthash, $filesize, $newfile); | |
1188 | } | |
1189 | ||
1190 | /** | |
bf9ffe27 | 1191 | * Return path to file with given hash. |
172dd12c | 1192 | * |
17d9269f | 1193 | * NOTE: must not be public, files in pool must not be modified |
172dd12c | 1194 | * |
d2b7803e | 1195 | * @param string $contenthash content hash |
172dd12c | 1196 | * @return string expected file location |
1197 | */ | |
17d9269f | 1198 | protected function path_from_hash($contenthash) { |
172dd12c | 1199 | $l1 = $contenthash[0].$contenthash[1]; |
1200 | $l2 = $contenthash[2].$contenthash[3]; | |
d0b6f92a | 1201 | return "$this->filedir/$l1/$l2"; |
172dd12c | 1202 | } |
1203 | ||
1aa01caf | 1204 | /** |
bf9ffe27 | 1205 | * Return path to file with given hash. |
1aa01caf | 1206 | * |
1207 | * NOTE: must not be public, files in pool must not be modified | |
1208 | * | |
d2b7803e | 1209 | * @param string $contenthash content hash |
1aa01caf | 1210 | * @return string expected file location |
1211 | */ | |
1212 | protected function trash_path_from_hash($contenthash) { | |
1213 | $l1 = $contenthash[0].$contenthash[1]; | |
1214 | $l2 = $contenthash[2].$contenthash[3]; | |
d0b6f92a | 1215 | return "$this->trashdir/$l1/$l2"; |
1aa01caf | 1216 | } |
1217 | ||
1218 | /** | |
bf9ffe27 PS |
1219 | * Tries to recover missing content of file from trash. |
1220 | * | |
d2b7803e | 1221 | * @param stored_file $file stored_file instance |
1aa01caf | 1222 | * @return bool success |
1223 | */ | |
1224 | public function try_content_recovery($file) { | |
1225 | $contenthash = $file->get_contenthash(); | |
1226 | $trashfile = $this->trash_path_from_hash($contenthash).'/'.$contenthash; | |
1227 | if (!is_readable($trashfile)) { | |
1228 | if (!is_readable($this->trashdir.'/'.$contenthash)) { | |
1229 | return false; | |
1230 | } | |
1231 | // nice, at least alternative trash file in trash root exists | |
1232 | $trashfile = $this->trashdir.'/'.$contenthash; | |
1233 | } | |
1234 | if (filesize($trashfile) != $file->get_filesize() or sha1_file($trashfile) != $contenthash) { | |
1235 | //weird, better fail early | |
1236 | return false; | |
1237 | } | |
1238 | $contentdir = $this->path_from_hash($contenthash); | |
1239 | $contentfile = $contentdir.'/'.$contenthash; | |
1240 | if (file_exists($contentfile)) { | |
1241 | //strange, no need to recover anything | |
1242 | return true; | |
1243 | } | |
1244 | if (!is_dir($contentdir)) { | |
1245 | if (!mkdir($contentdir, $this->dirpermissions, true)) { | |
1246 | return false; | |
1247 | } | |
1248 | } | |
1249 | return rename($trashfile, $contentfile); | |
1250 | } | |
1251 | ||
172dd12c | 1252 | /** |
bf9ffe27 PS |
1253 | * Marks pool file as candidate for deleting. |
1254 | * | |
1255 | * DO NOT call directly - reserved for core!! | |
1256 | * | |
172dd12c | 1257 | * @param string $contenthash |
1258 | */ | |
1aa01caf | 1259 | public function deleted_file_cleanup($contenthash) { |
172dd12c | 1260 | global $DB; |
1261 | ||
1aa01caf | 1262 | //Note: this section is critical - in theory file could be reused at the same |
1263 | // time, if this happens we can still recover the file from trash | |
1264 | if ($DB->record_exists('files', array('contenthash'=>$contenthash))) { | |
1265 | // file content is still used | |
1266 | return; | |
1267 | } | |
1268 | //move content file to trash | |
1269 | $contentfile = $this->path_from_hash($contenthash).'/'.$contenthash; | |
1270 | if (!file_exists($contentfile)) { | |
1271 | //weird, but no problem | |
172dd12c | 1272 | return; |
1273 | } | |
1aa01caf | 1274 | $trashpath = $this->trash_path_from_hash($contenthash); |
1275 | $trashfile = $trashpath.'/'.$contenthash; | |
1276 | if (file_exists($trashfile)) { | |
1277 | // we already have this content in trash, no need to move it there | |
1278 | unlink($contentfile); | |
1279 | return; | |
1280 | } | |
1281 | if (!is_dir($trashpath)) { | |
1282 | mkdir($trashpath, $this->dirpermissions, true); | |
1283 | } | |
1284 | rename($contentfile, $trashfile); | |
1285 | chmod($trashfile, $this->filepermissions); // fix permissions if needed | |
172dd12c | 1286 | } |
1287 | ||
1288 | /** | |
1289 | * Cron cleanup job. | |
1290 | */ | |
1291 | public function cron() { | |
a881f970 | 1292 | global $CFG, $DB; |
64f93798 | 1293 | |
2e69ea4a PS |
1294 | // find out all stale draft areas (older than 4 days) and purge them |
1295 | // those are identified by time stamp of the /. root dir | |
1296 | mtrace('Deleting old draft files... ', ''); | |
1297 | $old = time() - 60*60*24*4; | |
1298 | $sql = "SELECT * | |
1299 | FROM {files} | |
1300 | WHERE component = 'user' AND filearea = 'draft' AND filepath = '/' AND filename = '.' | |
1301 | AND timecreated < :old"; | |
1302 | $rs = $DB->get_recordset_sql($sql, array('old'=>$old)); | |
1303 | foreach ($rs as $dir) { | |
1304 | $this->delete_area_files($dir->contextid, $dir->component, $dir->filearea, $dir->itemid); | |
1305 | } | |
be981316 | 1306 | $rs->close(); |
b5541735 | 1307 | mtrace('done.'); |
64f93798 | 1308 | |
1aa01caf | 1309 | // remove trash pool files once a day |
1310 | // if you want to disable purging of trash put $CFG->fileslastcleanup=time(); into config.php | |
1311 | if (empty($CFG->fileslastcleanup) or $CFG->fileslastcleanup < time() - 60*60*24) { | |
1312 | require_once($CFG->libdir.'/filelib.php'); | |
a881f970 SH |
1313 | // Delete files that are associated with a context that no longer exists. |
1314 | mtrace('Cleaning up files from deleted contexts... ', ''); | |
1315 | $sql = "SELECT DISTINCT f.contextid | |
1316 | FROM {files} f | |
1317 | LEFT OUTER JOIN {context} c ON f.contextid = c.id | |
1318 | WHERE c.id IS NULL"; | |
be981316 EL |
1319 | $rs = $DB->get_recordset_sql($sql); |
1320 | if ($rs->valid()) { | |
a881f970 SH |
1321 | $fs = get_file_storage(); |
1322 | foreach ($rs as $ctx) { | |
1323 | $fs->delete_area_files($ctx->contextid); | |
1324 | } | |
1325 | } | |
be981316 | 1326 | $rs->close(); |
a881f970 SH |
1327 | mtrace('done.'); |
1328 | ||
1aa01caf | 1329 | mtrace('Deleting trash files... ', ''); |
1330 | fulldelete($this->trashdir); | |
1331 | set_config('fileslastcleanup', time()); | |
1332 | mtrace('done.'); | |
172dd12c | 1333 | } |
1334 | } | |
1335 | } | |
bf9ffe27 | 1336 |