172dd12c |
1 | <?php //$Id$ |
2 | |
3 | require_once("$CFG->libdir/file/stored_file.php"); |
4 | |
5 | class file_storage { |
6 | private $filedir; |
7 | |
8 | /** |
9 | * Contructor |
10 | * @param string $filedir full path to pool directory |
11 | */ |
744b64ff |
12 | public function __construct($filedir) { |
13 | $this->filedir = $filedir; |
172dd12c |
14 | |
15 | // make sure the file pool directory exists |
16 | if (!is_dir($this->filedir)) { |
17 | if (!check_dir_exists($this->filedir, true, true)) { |
145a0a31 |
18 | throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble |
172dd12c |
19 | } |
20 | // place warning file in file pool root |
17d9269f |
21 | file_put_contents($this->filedir.'/warning.txt', |
6c0e2d08 |
22 | '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.'); |
172dd12c |
23 | } |
24 | } |
25 | |
744b64ff |
26 | /** |
27 | * Returns location of filedir (file pool) |
6e73ac42 |
28 | * @return string pathname |
744b64ff |
29 | */ |
30 | public function get_filedir() { |
31 | return $this->filedir; |
32 | } |
33 | |
172dd12c |
34 | /** |
35 | * Calculates sha1 hash of unique full path name information |
36 | * @param int $contextid |
37 | * @param string $filearea |
38 | * @param int $itemid |
39 | * @param string $filepath |
40 | * @param string $filename |
41 | * @return string |
42 | */ |
43 | public static function get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename) { |
44 | return sha1($contextid.$filearea.$itemid.$filepath.$filename); |
45 | } |
46 | |
47 | /** |
48 | * Does this file exist? |
49 | * @param int $contextid |
50 | * @param string $filearea |
51 | * @param int $itemid |
52 | * @param string $filepath |
53 | * @param string $filename |
54 | * @return bool |
55 | */ |
56 | public function file_exists($contextid, $filearea, $itemid, $filepath, $filename) { |
57 | $filepath = clean_param($filepath, PARAM_PATH); |
58 | $filename = clean_param($filename, PARAM_FILE); |
59 | |
60 | if ($filename === '') { |
61 | $filename = '.'; |
62 | } |
63 | |
64 | $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename); |
65 | return $this->file_exists_by_hash($pathnamehash); |
66 | } |
67 | |
68 | /** |
69 | * Does this file exist? |
70 | * @param string $pathnamehash |
71 | * @return bool |
72 | */ |
73 | public function file_exists_by_hash($pathnamehash) { |
74 | global $DB; |
75 | |
76 | return $DB->record_exists('files', array('pathnamehash'=>$pathnamehash)); |
77 | } |
78 | |
79 | /** |
80 | * Fetch file using local file id |
81 | * @param int $fileid |
82 | * @return mixed stored_file instance if exists, false if not |
83 | */ |
84 | public function get_file_by_id($fileid) { |
85 | global $DB; |
86 | |
87 | if ($file_record = $DB->get_record('files', array('id'=>$fileid))) { |
88 | return new stored_file($this, $file_record); |
89 | } else { |
90 | return false; |
91 | } |
92 | } |
93 | |
94 | /** |
95 | * Fetch file using local file full pathname hash |
96 | * @param string $pathnamehash |
97 | * @return mixed stored_file instance if exists, false if not |
98 | */ |
99 | public function get_file_by_hash($pathnamehash) { |
100 | global $DB; |
101 | |
102 | if ($file_record = $DB->get_record('files', array('pathnamehash'=>$pathnamehash))) { |
103 | return new stored_file($this, $file_record); |
104 | } else { |
105 | return false; |
106 | } |
107 | } |
108 | |
109 | /** |
110 | * Fetch file |
111 | * @param int $contextid |
112 | * @param string $filearea |
113 | * @param int $itemid |
114 | * @param string $filepath |
115 | * @param string $filename |
116 | * @return mixed stored_file instance if exists, false if not |
117 | */ |
118 | public function get_file($contextid, $filearea, $itemid, $filepath, $filename) { |
119 | global $DB; |
120 | |
121 | $filepath = clean_param($filepath, PARAM_PATH); |
122 | $filename = clean_param($filename, PARAM_FILE); |
123 | |
124 | if ($filename === '') { |
125 | $filename = '.'; |
126 | } |
127 | |
128 | $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename); |
129 | return $this->get_file_by_hash($pathnamehash); |
130 | } |
131 | |
132 | /** |
133 | * Returns all area files (optionally limited by itemid) |
134 | * @param int $contextid |
135 | * @param string $filearea |
136 | * @param int $itemid (all files if not specified) |
137 | * @param string $sort |
138 | * @param bool $includedirs |
139 | * @return array of stored_files |
140 | */ |
46fcbcf4 |
141 | public function get_area_files($contextid, $filearea, $itemid=false, $sort="itemid, filepath, filename", $includedirs=true) { |
172dd12c |
142 | global $DB; |
143 | |
144 | $conditions = array('contextid'=>$contextid, 'filearea'=>$filearea); |
145 | if ($itemid !== false) { |
146 | $conditions['itemid'] = $itemid; |
147 | } |
148 | |
149 | $result = array(); |
150 | $file_records = $DB->get_records('files', $conditions, $sort); |
151 | foreach ($file_records as $file_record) { |
46fcbcf4 |
152 | if (!$includedirs and $file_record->filename === '.') { |
172dd12c |
153 | continue; |
154 | } |
155 | $result[] = new stored_file($this, $file_record); |
156 | } |
157 | return $result; |
158 | } |
159 | |
ee03a651 |
160 | /** |
161 | * Returns all files and otionally directories |
162 | * @param int $contextid |
163 | * @param string $filearea |
164 | * @param int $itemid |
165 | * @param int $filepath directory path |
166 | * @param bool $recursive include all subdirectories |
46fcbcf4 |
167 | * @param bool $includedirs include files and directories |
ee03a651 |
168 | * @param string $sort |
169 | * @return array of stored_files |
170 | */ |
46fcbcf4 |
171 | public function get_directory_files($contextid, $filearea, $itemid, $filepath, $recursive=false, $includedirs=true, $sort="filepath, filename") { |
ee03a651 |
172 | global $DB; |
173 | |
174 | if (!$directory = $this->get_file($contextid, $filearea, $itemid, $filepath, '.')) { |
175 | return array(); |
176 | } |
177 | |
178 | if ($recursive) { |
179 | |
46fcbcf4 |
180 | $dirs = $includedirs ? "" : "AND filename <> '.'"; |
ee03a651 |
181 | $length = textlib_get_instance()->strlen($filepath); |
182 | |
183 | $sql = "SELECT * |
184 | FROM {files} |
185 | WHERE contextid = :contextid AND filearea = :filearea AND itemid = :itemid |
186 | AND ".$DB->sql_substr()."(filepath, 1, $length) = :filepath |
187 | AND id <> :dirid |
188 | $dirs |
189 | ORDER BY $sort"; |
190 | $params = array('contextid'=>$contextid, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id()); |
191 | |
192 | $files = array(); |
193 | $dirs = array(); |
194 | $file_records = $DB->get_records_sql($sql, $params); |
195 | foreach ($file_records as $file_record) { |
196 | if ($file_record->filename == '.') { |
197 | $dirs[] = new stored_file($this, $file_record); |
198 | } else { |
199 | $files[] = new stored_file($this, $file_record); |
200 | } |
201 | } |
202 | $result = array_merge($dirs, $files); |
203 | |
204 | } else { |
205 | $result = array(); |
206 | $params = array('contextid'=>$contextid, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id()); |
207 | |
208 | $length = textlib_get_instance()->strlen($filepath); |
209 | |
46fcbcf4 |
210 | if ($includedirs) { |
ee03a651 |
211 | $sql = "SELECT * |
212 | FROM {files} |
213 | WHERE contextid = :contextid AND filearea = :filearea |
214 | AND itemid = :itemid AND filename = '.' |
215 | AND ".$DB->sql_substr()."(filepath, 1, $length) = :filepath |
216 | AND id <> :dirid |
217 | ORDER BY $sort"; |
218 | $reqlevel = substr_count($filepath, '/') + 1; |
219 | $file_records = $DB->get_records_sql($sql, $params); |
220 | foreach ($file_records as $file_record) { |
221 | if (substr_count($file_record->filepath, '/') !== $reqlevel) { |
222 | continue; |
223 | } |
224 | $result[] = new stored_file($this, $file_record); |
225 | } |
226 | } |
227 | |
228 | $sql = "SELECT * |
229 | FROM {files} |
230 | WHERE contextid = :contextid AND filearea = :filearea AND itemid = :itemid |
231 | AND filepath = :filepath AND filename <> '.' |
232 | ORDER BY $sort"; |
233 | |
234 | $file_records = $DB->get_records_sql($sql, $params); |
235 | foreach ($file_records as $file_record) { |
236 | $result[] = new stored_file($this, $file_record); |
237 | } |
238 | } |
239 | |
240 | return $result; |
241 | } |
242 | |
172dd12c |
243 | /** |
244 | * Delete all area files (optionally limited by itemid) |
245 | * @param int $contextid |
6311eb61 |
246 | * @param string $filearea (all areas in context if not specified) |
172dd12c |
247 | * @param int $itemid (all files if not specified) |
248 | * @return success |
249 | */ |
6311eb61 |
250 | public function delete_area_files($contextid, $filearea=false, $itemid=false) { |
172dd12c |
251 | global $DB; |
252 | |
6311eb61 |
253 | $conditions = array('contextid'=>$contextid); |
254 | if ($filearea !== false) { |
255 | $conditions['filearea'] = $filearea; |
256 | } |
172dd12c |
257 | if ($itemid !== false) { |
258 | $conditions['itemid'] = $itemid; |
259 | } |
260 | |
261 | $success = true; |
262 | |
263 | $file_records = $DB->get_records('files', $conditions); |
264 | foreach ($file_records as $file_record) { |
265 | $stored_file = new stored_file($this, $file_record); |
266 | $success = $stored_file->delete() && $success; |
267 | } |
268 | |
269 | return $success; |
270 | } |
271 | |
272 | /** |
273 | * Recursively creates director |
274 | * @param int $contextid |
275 | * @param string $filearea |
276 | * @param int $itemid |
277 | * @param string $filepath |
278 | * @param string $filename |
279 | * @return bool success |
280 | */ |
281 | public function create_directory($contextid, $filearea, $itemid, $filepath, $userid=null) { |
282 | global $DB; |
283 | |
284 | // validate all parameters, we do not want any rubbish stored in database, right? |
285 | if (!is_number($contextid) or $contextid < 1) { |
145a0a31 |
286 | throw new file_exception('storedfileproblem', 'Invalid contextid'); |
172dd12c |
287 | } |
288 | |
6c0e2d08 |
289 | $filearea = clean_param($filearea, PARAM_ALPHAEXT); |
172dd12c |
290 | if ($filearea === '') { |
145a0a31 |
291 | throw new file_exception('storedfileproblem', 'Invalid filearea'); |
172dd12c |
292 | } |
293 | |
294 | if (!is_number($itemid) or $itemid < 0) { |
145a0a31 |
295 | throw new file_exception('storedfileproblem', 'Invalid itemid'); |
172dd12c |
296 | } |
297 | |
298 | $filepath = clean_param($filepath, PARAM_PATH); |
299 | if (strpos($filepath, '/') !== 0 or strrpos($filepath, '/') !== strlen($filepath)-1) { |
300 | // path must start and end with '/' |
145a0a31 |
301 | throw new file_exception('storedfileproblem', 'Invalid file path'); |
172dd12c |
302 | } |
303 | |
304 | $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, '.'); |
305 | |
306 | if ($dir_info = $this->get_file_by_hash($pathnamehash)) { |
307 | return $dir_info; |
308 | } |
309 | |
310 | static $contenthash = null; |
311 | if (!$contenthash) { |
b48f3e06 |
312 | $this->add_string_to_pool(''); |
172dd12c |
313 | $contenthash = sha1(''); |
314 | } |
315 | |
316 | $now = time(); |
317 | |
318 | $dir_record = new object(); |
319 | $dir_record->contextid = $contextid; |
320 | $dir_record->filearea = $filearea; |
321 | $dir_record->itemid = $itemid; |
322 | $dir_record->filepath = $filepath; |
323 | $dir_record->filename = '.'; |
324 | $dir_record->contenthash = $contenthash; |
325 | $dir_record->filesize = 0; |
326 | |
327 | $dir_record->timecreated = $now; |
328 | $dir_record->timemodified = $now; |
329 | $dir_record->mimetype = null; |
330 | $dir_record->userid = $userid; |
331 | |
332 | $dir_record->pathnamehash = $pathnamehash; |
333 | |
334 | $DB->insert_record('files', $dir_record); |
335 | $dir_info = $this->get_file_by_hash($pathnamehash); |
336 | |
337 | if ($filepath !== '/') { |
338 | //recurse to parent dirs |
339 | $filepath = trim($filepath, '/'); |
340 | $filepath = explode('/', $filepath); |
341 | array_pop($filepath); |
342 | $filepath = implode('/', $filepath); |
343 | $filepath = ($filepath === '') ? '/' : "/$filepath/"; |
344 | $this->create_directory($contextid, $filearea, $itemid, $filepath, $userid); |
345 | } |
346 | |
347 | return $dir_info; |
348 | } |
349 | |
350 | /** |
351 | * Add new local file based on existing local file |
352 | * @param mixed $file_record object or array describing changes |
353 | * @param int $fid id of existing local file |
354 | * @return object stored_file instance |
355 | */ |
3501d96b |
356 | public function create_file_from_storedfile($file_record, $fid) { |
172dd12c |
357 | global $DB; |
358 | |
8eb1e0a1 |
359 | if ($fid instanceof stored_file) { |
360 | $fid = $fid->get_id(); |
361 | } |
362 | |
ec8b711f |
363 | $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record! |
364 | |
172dd12c |
365 | unset($file_record['id']); |
366 | unset($file_record['filesize']); |
367 | unset($file_record['contenthash']); |
8eb1e0a1 |
368 | unset($file_record['pathnamehash']); |
172dd12c |
369 | |
370 | $now = time(); |
371 | |
ebcac6c6 |
372 | if (!$newrecord = $DB->get_record('files', array('id'=>$fid))) { |
145a0a31 |
373 | throw new file_exception('storedfileproblem', 'File does not exist'); |
172dd12c |
374 | } |
375 | |
376 | unset($newrecord->id); |
377 | |
378 | foreach ($file_record as $key=>$value) { |
379 | // validate all parameters, we do not want any rubbish stored in database, right? |
380 | if ($key == 'contextid' and (!is_number($value) or $value < 1)) { |
145a0a31 |
381 | throw new file_exception('storedfileproblem', 'Invalid contextid'); |
172dd12c |
382 | } |
383 | |
384 | if ($key == 'filearea') { |
6c0e2d08 |
385 | $value = clean_param($value, PARAM_ALPHAEXT); |
172dd12c |
386 | if ($value === '') { |
145a0a31 |
387 | throw new file_exception('storedfileproblem', 'Invalid filearea'); |
172dd12c |
388 | } |
389 | } |
390 | |
391 | if ($key == 'itemid' and (!is_number($value) or $value < 0)) { |
145a0a31 |
392 | throw new file_exception('storedfileproblem', 'Invalid itemid'); |
172dd12c |
393 | } |
394 | |
395 | |
396 | if ($key == 'filepath') { |
397 | $value = clean_param($value, PARAM_PATH); |
00c32c54 |
398 | if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) { |
172dd12c |
399 | // path must start and end with '/' |
145a0a31 |
400 | throw new file_exception('storedfileproblem', 'Invalid file path'); |
172dd12c |
401 | } |
402 | } |
403 | |
404 | if ($key == 'filename') { |
405 | $value = clean_param($value, PARAM_FILE); |
406 | if ($value === '') { |
407 | // path must start and end with '/' |
145a0a31 |
408 | throw new file_exception('storedfileproblem', 'Invalid file name'); |
172dd12c |
409 | } |
410 | } |
411 | |
412 | $newrecord->$key = $value; |
413 | } |
414 | |
415 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
416 | |
417 | try { |
418 | $newrecord->id = $DB->insert_record('files', $newrecord); |
419 | } catch (database_exception $e) { |
420 | $newrecord->id = false; |
421 | } |
422 | |
423 | if (!$newrecord->id) { |
424 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, |
425 | $newrecord->filepath, $newrecord->filename); |
426 | } |
427 | |
428 | $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
429 | |
430 | return new stored_file($this, $newrecord); |
431 | } |
432 | |
6e73ac42 |
433 | /** |
434 | * Add new local file |
435 | * @param mixed $file_record object or array describing file |
436 | * @param string $path path to file or content of file |
437 | * @param array $options @see download_file_content() options |
438 | * @return object stored_file instance |
439 | */ |
440 | public function create_file_from_url($file_record, $url, $options=null) { |
ec8b711f |
441 | |
442 | $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects |
6e73ac42 |
443 | $file_record = (object)$file_record; // we support arrays too |
444 | |
445 | $headers = isset($options['headers']) ? $options['headers'] : null; |
446 | $postdata = isset($options['postdata']) ? $options['postdata'] : null; |
447 | $fullresponse = isset($options['fullresponse']) ? $options['fullresponse'] : false; |
448 | $timeout = isset($options['timeout']) ? $options['timeout'] : 300; |
449 | $connecttimeout = isset($options['connecttimeout']) ? $options['connecttimeout'] : 20; |
450 | $skipcertverify = isset($options['skipcertverify']) ? $options['skipcertverify'] : false; |
451 | |
452 | // TODO: it might be better to add a new option to file file content to temp file, |
453 | // the problem here is that the size of file is limited by available memory |
454 | |
455 | $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify); |
456 | |
457 | if (!isset($file_record->filename)) { |
458 | $parts = explode('/', $url); |
459 | $filename = array_pop($parts); |
460 | $file_record->filename = clean_param($filename, PARAM_FILE); |
461 | } |
462 | |
463 | return $this->create_file_from_string($file_record, $content); |
464 | } |
465 | |
172dd12c |
466 | /** |
467 | * Add new local file |
468 | * @param mixed $file_record object or array describing file |
469 | * @param string $path path to file or content of file |
470 | * @return object stored_file instance |
471 | */ |
472 | public function create_file_from_pathname($file_record, $pathname) { |
473 | global $DB; |
474 | |
ec8b711f |
475 | $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects |
172dd12c |
476 | $file_record = (object)$file_record; // we support arrays too |
477 | |
478 | // validate all parameters, we do not want any rubbish stored in database, right? |
479 | if (!is_number($file_record->contextid) or $file_record->contextid < 1) { |
145a0a31 |
480 | throw new file_exception('storedfileproblem', 'Invalid contextid'); |
172dd12c |
481 | } |
482 | |
6c0e2d08 |
483 | $file_record->filearea = clean_param($file_record->filearea, PARAM_ALPHAEXT); |
172dd12c |
484 | if ($file_record->filearea === '') { |
145a0a31 |
485 | throw new file_exception('storedfileproblem', 'Invalid filearea'); |
172dd12c |
486 | } |
487 | |
488 | if (!is_number($file_record->itemid) or $file_record->itemid < 0) { |
145a0a31 |
489 | throw new file_exception('storedfileproblem', 'Invalid itemid'); |
172dd12c |
490 | } |
491 | |
492 | $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); |
493 | if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { |
494 | // path must start and end with '/' |
145a0a31 |
495 | throw new file_exception('storedfileproblem', 'Invalid file path'); |
172dd12c |
496 | } |
497 | |
498 | $file_record->filename = clean_param($file_record->filename, PARAM_FILE); |
499 | if ($file_record->filename === '') { |
e1dcb950 |
500 | // filename must not be empty |
145a0a31 |
501 | throw new file_exception('storedfileproblem', 'Invalid file name'); |
172dd12c |
502 | } |
503 | |
504 | $now = time(); |
505 | |
506 | $newrecord = new object(); |
507 | |
508 | $newrecord->contextid = $file_record->contextid; |
509 | $newrecord->filearea = $file_record->filearea; |
510 | $newrecord->itemid = $file_record->itemid; |
511 | $newrecord->filepath = $file_record->filepath; |
512 | $newrecord->filename = $file_record->filename; |
513 | |
514 | $newrecord->timecreated = empty($file_record->timecreated) ? $now : $file_record->timecreated; |
515 | $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified; |
516 | $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; |
517 | $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; |
518 | |
b48f3e06 |
519 | list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_file_to_pool($pathname); |
172dd12c |
520 | |
521 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
522 | |
523 | try { |
524 | $newrecord->id = $DB->insert_record('files', $newrecord); |
525 | } catch (database_exception $e) { |
526 | $newrecord->id = false; |
527 | } |
528 | |
529 | if (!$newrecord->id) { |
530 | if ($newfile) { |
531 | $this->mark_delete_candidate($newrecord->contenthash); |
532 | } |
533 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, |
534 | $newrecord->filepath, $newrecord->filename); |
535 | } |
536 | |
537 | $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
538 | |
539 | return new stored_file($this, $newrecord); |
540 | } |
541 | |
542 | /** |
543 | * Add new local file |
544 | * @param mixed $file_record object or array describing file |
545 | * @param string $content content of file |
546 | * @return object stored_file instance |
547 | */ |
548 | public function create_file_from_string($file_record, $content) { |
549 | global $DB; |
550 | |
ec8b711f |
551 | $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects |
172dd12c |
552 | $file_record = (object)$file_record; // we support arrays too |
553 | |
554 | // validate all parameters, we do not want any rubbish stored in database, right? |
555 | if (!is_number($file_record->contextid) or $file_record->contextid < 1) { |
145a0a31 |
556 | throw new file_exception('storedfileproblem', 'Invalid contextid'); |
172dd12c |
557 | } |
558 | |
6c0e2d08 |
559 | $file_record->filearea = clean_param($file_record->filearea, PARAM_ALPHAEXT); |
172dd12c |
560 | if ($file_record->filearea === '') { |
145a0a31 |
561 | throw new file_exception('storedfileproblem', 'Invalid filearea'); |
172dd12c |
562 | } |
563 | |
564 | if (!is_number($file_record->itemid) or $file_record->itemid < 0) { |
145a0a31 |
565 | throw new file_exception('storedfileproblem', 'Invalid itemid'); |
172dd12c |
566 | } |
567 | |
568 | $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); |
569 | if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { |
570 | // path must start and end with '/' |
145a0a31 |
571 | throw new file_exception('storedfileproblem', 'Invalid file path'); |
172dd12c |
572 | } |
573 | |
574 | $file_record->filename = clean_param($file_record->filename, PARAM_FILE); |
575 | if ($file_record->filename === '') { |
576 | // path must start and end with '/' |
145a0a31 |
577 | throw new file_exception('storedfileproblem', 'Invalid file name'); |
172dd12c |
578 | } |
579 | |
580 | $now = time(); |
581 | |
582 | $newrecord = new object(); |
583 | |
584 | $newrecord->contextid = $file_record->contextid; |
585 | $newrecord->filearea = $file_record->filearea; |
586 | $newrecord->itemid = $file_record->itemid; |
587 | $newrecord->filepath = $file_record->filepath; |
588 | $newrecord->filename = $file_record->filename; |
589 | |
590 | $newrecord->timecreated = empty($file_record->timecreated) ? $now : $file_record->timecreated; |
591 | $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified; |
592 | $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; |
593 | $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; |
594 | |
b48f3e06 |
595 | list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content); |
172dd12c |
596 | |
597 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
598 | |
599 | try { |
600 | $newrecord->id = $DB->insert_record('files', $newrecord); |
601 | } catch (database_exception $e) { |
602 | $newrecord->id = false; |
603 | } |
604 | |
605 | if (!$newrecord->id) { |
606 | if ($newfile) { |
607 | $this->mark_delete_candidate($newrecord->contenthash); |
608 | } |
609 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, |
610 | $newrecord->filepath, $newrecord->filename); |
611 | } |
612 | |
613 | $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
614 | |
615 | return new stored_file($this, $newrecord); |
616 | } |
617 | |
49583e9e |
618 | /** |
6e73ac42 |
619 | * Move one or more files from a given itemid location in the current user's draft files |
49583e9e |
620 | * to a new filearea. Note that you can't rename files using this function. |
621 | * @param int $itemid - existing itemid in user draft_area with one or more files |
622 | * @param int $newcontextid - the new contextid to move files to |
623 | * @param string $newfilearea - the new filearea to move files to |
882eb790 |
624 | * @param int $newitemid - the new itemid to use (this is ignored and automatically set to 0 when moving to a user's user_private area) |
49583e9e |
625 | * @param string $newfilepath - the new path to move all files to |
626 | * @param bool $overwrite - overwrite files from the destination if they exist |
627 | * @param int $newuserid - new userid if required |
628 | * @return mixed stored_file object or false if error; may throw exception if duplicate found |
629 | * @return array(contenthash, filesize, newfile) |
630 | */ |
6e73ac42 |
631 | public function move_draft_to_final($itemid, $newcontextid, $newfilearea, $newitemid, |
49583e9e |
632 | $newfilepath='/', $overwrite=false) { |
633 | |
634 | global $USER; |
635 | |
6e73ac42 |
636 | /// Get files from the draft area |
49583e9e |
637 | if (!$usercontext = get_context_instance(CONTEXT_USER, $USER->id)) { |
638 | return false; |
639 | } |
640 | if (!$files = $this->get_area_files($usercontext->id, 'user_draft', $itemid, 'filename', false)) { |
641 | return false; |
642 | } |
643 | |
882eb790 |
644 | $newcontext = get_context_instance_by_id($newcontextid); |
dd64e310 |
645 | if (($newcontext->contextlevel == CONTEXT_USER) && ($newfilearea != 'user_draft')) { |
882eb790 |
646 | $newitemid = 0; |
647 | } |
648 | |
6e73ac42 |
649 | /// Process each file in turn |
49583e9e |
650 | |
651 | $returnfiles = array(); |
652 | foreach ($files as $file) { |
653 | |
654 | /// Delete any existing files in destination if required |
6e73ac42 |
655 | if ($oldfile = $this->get_file($newcontextid, $newfilearea, $newitemid, |
49583e9e |
656 | $newfilepath, $file->get_filename())) { |
657 | if ($overwrite) { |
658 | $oldfile->delete(); |
659 | } else { |
00c32c54 |
660 | $returnfiles[] = $oldfile; |
49583e9e |
661 | continue; // Can't overwrite the existing file so skip it |
662 | } |
663 | } |
664 | |
665 | /// Create the new file |
666 | $newrecord = new object(); |
667 | $newrecord->contextid = $newcontextid; |
668 | $newrecord->filearea = $newfilearea; |
669 | $newrecord->itemid = $newitemid; |
670 | $newrecord->filepath = $newfilepath; |
671 | $newrecord->filename = $file->get_filename(); |
672 | $newrecord->timecreated = $file->get_timecreated(); |
673 | $newrecord->timemodified = $file->get_timemodified(); |
674 | $newrecord->mimetype = $file->get_mimetype(); |
675 | $newrecord->userid = $file->get_userid(); |
676 | |
677 | if ($newfile = $this->create_file_from_storedfile($newrecord, $file->get_id())) { |
678 | $file->delete(); |
679 | $returnfiles[] = $newfile; |
680 | } |
681 | } |
682 | |
683 | return $returnfiles; |
684 | } |
685 | |
686 | |
172dd12c |
687 | /** |
688 | * Add file content to sha1 pool |
689 | * @param string $pathname path to file |
690 | * @param string sha1 hash of content if known (performance only) |
691 | * @return array(contenthash, filesize, newfile) |
692 | */ |
b48f3e06 |
693 | public function add_file_to_pool($pathname, $contenthash=null) { |
172dd12c |
694 | if (!is_readable($pathname)) { |
145a0a31 |
695 | throw new file_exception('storedfilecannotread'); |
172dd12c |
696 | } |
697 | |
698 | if (is_null($contenthash)) { |
699 | $contenthash = sha1_file($pathname); |
700 | } |
701 | |
702 | $filesize = filesize($pathname); |
703 | |
704 | $hashpath = $this->path_from_hash($contenthash); |
705 | $hashfile = "$hashpath/$contenthash"; |
706 | |
707 | if (file_exists($hashfile)) { |
708 | if (filesize($hashfile) !== $filesize) { |
709 | throw new file_pool_content_exception($contenthash); |
710 | } |
711 | $newfile = false; |
712 | |
713 | } else { |
714 | if (!check_dir_exists($hashpath, true, true)) { |
145a0a31 |
715 | throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble |
172dd12c |
716 | } |
717 | $newfile = true; |
718 | |
6c0e2d08 |
719 | if (!copy($pathname, $hashfile)) { |
145a0a31 |
720 | throw new file_exception('storedfilecannotread'); |
172dd12c |
721 | } |
172dd12c |
722 | |
723 | if (filesize($hashfile) !== $filesize) { |
724 | @unlink($hashfile); |
725 | throw new file_pool_content_exception($contenthash); |
726 | } |
727 | } |
728 | |
729 | |
730 | return array($contenthash, $filesize, $newfile); |
731 | } |
732 | |
733 | /** |
734 | * Add string content to sha1 pool |
735 | * @param string $content file content - binary string |
736 | * @return array(contenthash, filesize, newfile) |
737 | */ |
b48f3e06 |
738 | public function add_string_to_pool($content) { |
172dd12c |
739 | $contenthash = sha1($content); |
740 | $filesize = strlen($content); // binary length |
741 | |
742 | $hashpath = $this->path_from_hash($contenthash); |
743 | $hashfile = "$hashpath/$contenthash"; |
744 | |
745 | |
746 | if (file_exists($hashfile)) { |
747 | if (filesize($hashfile) !== $filesize) { |
748 | throw new file_pool_content_exception($contenthash); |
749 | } |
750 | $newfile = false; |
751 | |
752 | } else { |
753 | if (!check_dir_exists($hashpath, true, true)) { |
145a0a31 |
754 | throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble |
172dd12c |
755 | } |
756 | $newfile = true; |
757 | |
6c0e2d08 |
758 | file_put_contents($hashfile, $content); |
172dd12c |
759 | |
760 | if (filesize($hashfile) !== $filesize) { |
761 | @unlink($hashfile); |
762 | throw new file_pool_content_exception($contenthash); |
763 | } |
764 | } |
765 | |
766 | return array($contenthash, $filesize, $newfile); |
767 | } |
768 | |
769 | /** |
770 | * Return path to file with given hash |
771 | * |
17d9269f |
772 | * NOTE: must not be public, files in pool must not be modified |
172dd12c |
773 | * |
774 | * @param string $contenthash |
775 | * @return string expected file location |
776 | */ |
17d9269f |
777 | protected function path_from_hash($contenthash) { |
172dd12c |
778 | $l1 = $contenthash[0].$contenthash[1]; |
779 | $l2 = $contenthash[2].$contenthash[3]; |
780 | $l3 = $contenthash[4].$contenthash[5]; |
781 | return "$this->filedir/$l1/$l2/$l3"; |
782 | } |
783 | |
784 | /** |
785 | * Marks pool file as candidate for deleting |
786 | * @param string $contenthash |
787 | */ |
788 | public function mark_delete_candidate($contenthash) { |
789 | global $DB; |
790 | |
791 | if ($DB->record_exists('files_cleanup', array('contenthash'=>$contenthash))) { |
792 | return; |
793 | } |
794 | $rec = new object(); |
795 | $rec->contenthash = $contenthash; |
796 | $DB->insert_record('files_cleanup', $rec); |
797 | } |
798 | |
799 | /** |
800 | * Cron cleanup job. |
801 | */ |
802 | public function cron() { |
803 | global $DB; |
804 | |
805 | //TODO: there is a small chance that reused files might be deleted |
806 | // if this function takes too long we should add some table locking here |
807 | |
808 | $sql = "SELECT 1 AS id, fc.contenthash |
809 | FROM {files_cleanup} fc |
810 | LEFT JOIN {files} f ON f.contenthash = fc.contenthash |
811 | WHERE f.id IS NULL"; |
812 | while ($hash = $DB->get_record_sql($sql, null, true)) { |
813 | $file = $this->path_from_hash($hash->contenthash).'/'.$hash->contenthash; |
814 | @unlink($file); |
815 | $DB->delete_records('files_cleanup', array('contenthash'=>$hash->contenthash)); |
816 | } |
817 | } |
818 | } |