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 | */ |
12 | public function __construct() { |
13 | global $CFG; |
14 | if (isset($CFG->filedir)) { |
15 | $this->filedir = $CFG->filedir; |
16 | } else { |
17 | $this->filedir = $CFG->dataroot.'/filedir'; |
18 | } |
19 | |
20 | // make sure the file pool directory exists |
21 | if (!is_dir($this->filedir)) { |
22 | if (!check_dir_exists($this->filedir, true, true)) { |
23 | throw new file_exception('localfilecannotcreatefiledirs'); // permission trouble |
24 | } |
25 | // place warning file in file pool root |
17d9269f |
26 | file_put_contents($this->filedir.'/warning.txt', |
6c0e2d08 |
27 | '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 |
28 | } |
29 | } |
30 | |
31 | /** |
32 | * Calculates sha1 hash of unique full path name information |
33 | * @param int $contextid |
34 | * @param string $filearea |
35 | * @param int $itemid |
36 | * @param string $filepath |
37 | * @param string $filename |
38 | * @return string |
39 | */ |
40 | public static function get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename) { |
41 | return sha1($contextid.$filearea.$itemid.$filepath.$filename); |
42 | } |
43 | |
44 | /** |
45 | * Does this file exist? |
46 | * @param int $contextid |
47 | * @param string $filearea |
48 | * @param int $itemid |
49 | * @param string $filepath |
50 | * @param string $filename |
51 | * @return bool |
52 | */ |
53 | public function file_exists($contextid, $filearea, $itemid, $filepath, $filename) { |
54 | $filepath = clean_param($filepath, PARAM_PATH); |
55 | $filename = clean_param($filename, PARAM_FILE); |
56 | |
57 | if ($filename === '') { |
58 | $filename = '.'; |
59 | } |
60 | |
61 | $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename); |
62 | return $this->file_exists_by_hash($pathnamehash); |
63 | } |
64 | |
65 | /** |
66 | * Does this file exist? |
67 | * @param string $pathnamehash |
68 | * @return bool |
69 | */ |
70 | public function file_exists_by_hash($pathnamehash) { |
71 | global $DB; |
72 | |
73 | return $DB->record_exists('files', array('pathnamehash'=>$pathnamehash)); |
74 | } |
75 | |
76 | /** |
77 | * Fetch file using local file id |
78 | * @param int $fileid |
79 | * @return mixed stored_file instance if exists, false if not |
80 | */ |
81 | public function get_file_by_id($fileid) { |
82 | global $DB; |
83 | |
84 | if ($file_record = $DB->get_record('files', array('id'=>$fileid))) { |
85 | return new stored_file($this, $file_record); |
86 | } else { |
87 | return false; |
88 | } |
89 | } |
90 | |
91 | /** |
92 | * Fetch file using local file full pathname hash |
93 | * @param string $pathnamehash |
94 | * @return mixed stored_file instance if exists, false if not |
95 | */ |
96 | public function get_file_by_hash($pathnamehash) { |
97 | global $DB; |
98 | |
99 | if ($file_record = $DB->get_record('files', array('pathnamehash'=>$pathnamehash))) { |
100 | return new stored_file($this, $file_record); |
101 | } else { |
102 | return false; |
103 | } |
104 | } |
105 | |
106 | /** |
107 | * Fetch file |
108 | * @param int $contextid |
109 | * @param string $filearea |
110 | * @param int $itemid |
111 | * @param string $filepath |
112 | * @param string $filename |
113 | * @return mixed stored_file instance if exists, false if not |
114 | */ |
115 | public function get_file($contextid, $filearea, $itemid, $filepath, $filename) { |
116 | global $DB; |
117 | |
118 | $filepath = clean_param($filepath, PARAM_PATH); |
119 | $filename = clean_param($filename, PARAM_FILE); |
120 | |
121 | if ($filename === '') { |
122 | $filename = '.'; |
123 | } |
124 | |
125 | $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename); |
126 | return $this->get_file_by_hash($pathnamehash); |
127 | } |
128 | |
129 | /** |
130 | * Returns all area files (optionally limited by itemid) |
131 | * @param int $contextid |
132 | * @param string $filearea |
133 | * @param int $itemid (all files if not specified) |
134 | * @param string $sort |
135 | * @param bool $includedirs |
136 | * @return array of stored_files |
137 | */ |
138 | public function get_area_files($contextid, $filearea, $itemid=false, $sort="itemid, filepath, filename", $inludedirs=true) { |
139 | global $DB; |
140 | |
141 | $conditions = array('contextid'=>$contextid, 'filearea'=>$filearea); |
142 | if ($itemid !== false) { |
143 | $conditions['itemid'] = $itemid; |
144 | } |
145 | |
146 | $result = array(); |
147 | $file_records = $DB->get_records('files', $conditions, $sort); |
148 | foreach ($file_records as $file_record) { |
149 | if (!$inludedirs and $file_record->filename === '.') { |
150 | continue; |
151 | } |
152 | $result[] = new stored_file($this, $file_record); |
153 | } |
154 | return $result; |
155 | } |
156 | |
157 | /** |
158 | * Delete all area files (optionally limited by itemid) |
159 | * @param int $contextid |
160 | * @param string $filearea |
161 | * @param int $itemid (all files if not specified) |
162 | * @return success |
163 | */ |
164 | public function delete_area_files($contextid, $filearea, $itemid=false) { |
165 | global $DB; |
166 | |
167 | $conditions = array('contextid'=>$contextid, 'filearea'=>$filearea); |
168 | if ($itemid !== false) { |
169 | $conditions['itemid'] = $itemid; |
170 | } |
171 | |
172 | $success = true; |
173 | |
174 | $file_records = $DB->get_records('files', $conditions); |
175 | foreach ($file_records as $file_record) { |
176 | $stored_file = new stored_file($this, $file_record); |
177 | $success = $stored_file->delete() && $success; |
178 | } |
179 | |
180 | return $success; |
181 | } |
182 | |
183 | /** |
184 | * Recursively creates director |
185 | * @param int $contextid |
186 | * @param string $filearea |
187 | * @param int $itemid |
188 | * @param string $filepath |
189 | * @param string $filename |
190 | * @return bool success |
191 | */ |
192 | public function create_directory($contextid, $filearea, $itemid, $filepath, $userid=null) { |
193 | global $DB; |
194 | |
195 | // validate all parameters, we do not want any rubbish stored in database, right? |
196 | if (!is_number($contextid) or $contextid < 1) { |
197 | throw new file_exception('localfileproblem', 'Invalid contextid'); |
198 | } |
199 | |
6c0e2d08 |
200 | $filearea = clean_param($filearea, PARAM_ALPHAEXT); |
172dd12c |
201 | if ($filearea === '') { |
202 | throw new file_exception('localfileproblem', 'Invalid filearea'); |
203 | } |
204 | |
205 | if (!is_number($itemid) or $itemid < 0) { |
206 | throw new file_exception('localfileproblem', 'Invalid itemid'); |
207 | } |
208 | |
209 | $filepath = clean_param($filepath, PARAM_PATH); |
210 | if (strpos($filepath, '/') !== 0 or strrpos($filepath, '/') !== strlen($filepath)-1) { |
211 | // path must start and end with '/' |
212 | throw new file_exception('localfileproblem', 'Invalid file path'); |
213 | } |
214 | |
215 | $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, '.'); |
216 | |
217 | if ($dir_info = $this->get_file_by_hash($pathnamehash)) { |
218 | return $dir_info; |
219 | } |
220 | |
221 | static $contenthash = null; |
222 | if (!$contenthash) { |
223 | $this->add_to_pool_string(''); |
224 | $contenthash = sha1(''); |
225 | } |
226 | |
227 | $now = time(); |
228 | |
229 | $dir_record = new object(); |
230 | $dir_record->contextid = $contextid; |
231 | $dir_record->filearea = $filearea; |
232 | $dir_record->itemid = $itemid; |
233 | $dir_record->filepath = $filepath; |
234 | $dir_record->filename = '.'; |
235 | $dir_record->contenthash = $contenthash; |
236 | $dir_record->filesize = 0; |
237 | |
238 | $dir_record->timecreated = $now; |
239 | $dir_record->timemodified = $now; |
240 | $dir_record->mimetype = null; |
241 | $dir_record->userid = $userid; |
242 | |
243 | $dir_record->pathnamehash = $pathnamehash; |
244 | |
245 | $DB->insert_record('files', $dir_record); |
246 | $dir_info = $this->get_file_by_hash($pathnamehash); |
247 | |
248 | if ($filepath !== '/') { |
249 | //recurse to parent dirs |
250 | $filepath = trim($filepath, '/'); |
251 | $filepath = explode('/', $filepath); |
252 | array_pop($filepath); |
253 | $filepath = implode('/', $filepath); |
254 | $filepath = ($filepath === '') ? '/' : "/$filepath/"; |
255 | $this->create_directory($contextid, $filearea, $itemid, $filepath, $userid); |
256 | } |
257 | |
258 | return $dir_info; |
259 | } |
260 | |
261 | /** |
262 | * Add new local file based on existing local file |
263 | * @param mixed $file_record object or array describing changes |
264 | * @param int $fid id of existing local file |
265 | * @return object stored_file instance |
266 | */ |
267 | public function create_file_from_localfile($file_record, $fid) { |
268 | global $DB; |
269 | |
270 | $file_record = (array)$file_record; // we support arrays too |
271 | unset($file_record['id']); |
272 | unset($file_record['filesize']); |
273 | unset($file_record['contenthash']); |
274 | |
275 | $now = time(); |
276 | |
277 | if ($newrecord = $DB->get_record('files', array('id'=>$fid))) { |
278 | throw new file_exception('localfileproblem', 'File does not exist'); |
279 | } |
280 | |
281 | unset($newrecord->id); |
282 | |
283 | foreach ($file_record as $key=>$value) { |
284 | // validate all parameters, we do not want any rubbish stored in database, right? |
285 | if ($key == 'contextid' and (!is_number($value) or $value < 1)) { |
286 | throw new file_exception('localfileproblem', 'Invalid contextid'); |
287 | } |
288 | |
289 | if ($key == 'filearea') { |
6c0e2d08 |
290 | $value = clean_param($value, PARAM_ALPHAEXT); |
172dd12c |
291 | if ($value === '') { |
292 | throw new file_exception('localfileproblem', 'Invalid filearea'); |
293 | } |
294 | } |
295 | |
296 | if ($key == 'itemid' and (!is_number($value) or $value < 0)) { |
297 | throw new file_exception('localfileproblem', 'Invalid itemid'); |
298 | } |
299 | |
300 | |
301 | if ($key == 'filepath') { |
302 | $value = clean_param($value, PARAM_PATH); |
303 | if (strpos($value, '/') !== 0 or strpos($value, '/') !== strlen($value)-1) { |
304 | // path must start and end with '/' |
305 | throw new file_exception('localfileproblem', 'Invalid file path'); |
306 | } |
307 | } |
308 | |
309 | if ($key == 'filename') { |
310 | $value = clean_param($value, PARAM_FILE); |
311 | if ($value === '') { |
312 | // path must start and end with '/' |
313 | throw new file_exception('localfileproblem', 'Invalid file name'); |
314 | } |
315 | } |
316 | |
317 | $newrecord->$key = $value; |
318 | } |
319 | |
320 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
321 | |
322 | try { |
323 | $newrecord->id = $DB->insert_record('files', $newrecord); |
324 | } catch (database_exception $e) { |
325 | $newrecord->id = false; |
326 | } |
327 | |
328 | if (!$newrecord->id) { |
329 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, |
330 | $newrecord->filepath, $newrecord->filename); |
331 | } |
332 | |
333 | $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
334 | |
335 | return new stored_file($this, $newrecord); |
336 | } |
337 | |
338 | /** |
339 | * Add new local file |
340 | * @param mixed $file_record object or array describing file |
341 | * @param string $path path to file or content of file |
342 | * @return object stored_file instance |
343 | */ |
344 | public function create_file_from_pathname($file_record, $pathname) { |
345 | global $DB; |
346 | |
347 | $file_record = (object)$file_record; // we support arrays too |
348 | |
349 | // validate all parameters, we do not want any rubbish stored in database, right? |
350 | if (!is_number($file_record->contextid) or $file_record->contextid < 1) { |
351 | throw new file_exception('localfileproblem', 'Invalid contextid'); |
352 | } |
353 | |
6c0e2d08 |
354 | $file_record->filearea = clean_param($file_record->filearea, PARAM_ALPHAEXT); |
172dd12c |
355 | if ($file_record->filearea === '') { |
356 | throw new file_exception('localfileproblem', 'Invalid filearea'); |
357 | } |
358 | |
359 | if (!is_number($file_record->itemid) or $file_record->itemid < 0) { |
360 | throw new file_exception('localfileproblem', 'Invalid itemid'); |
361 | } |
362 | |
363 | $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); |
364 | if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { |
365 | // path must start and end with '/' |
366 | throw new file_exception('localfileproblem', 'Invalid file path'); |
367 | } |
368 | |
369 | $file_record->filename = clean_param($file_record->filename, PARAM_FILE); |
370 | if ($file_record->filename === '') { |
371 | // path must start and end with '/' |
372 | throw new file_exception('localfileproblem', 'Invalid file name'); |
373 | } |
374 | |
375 | $now = time(); |
376 | |
377 | $newrecord = new object(); |
378 | |
379 | $newrecord->contextid = $file_record->contextid; |
380 | $newrecord->filearea = $file_record->filearea; |
381 | $newrecord->itemid = $file_record->itemid; |
382 | $newrecord->filepath = $file_record->filepath; |
383 | $newrecord->filename = $file_record->filename; |
384 | |
385 | $newrecord->timecreated = empty($file_record->timecreated) ? $now : $file_record->timecreated; |
386 | $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified; |
387 | $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; |
388 | $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; |
389 | |
390 | list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_to_pool_pathname($pathname); |
391 | |
392 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
393 | |
394 | try { |
395 | $newrecord->id = $DB->insert_record('files', $newrecord); |
396 | } catch (database_exception $e) { |
397 | $newrecord->id = false; |
398 | } |
399 | |
400 | if (!$newrecord->id) { |
401 | if ($newfile) { |
402 | $this->mark_delete_candidate($newrecord->contenthash); |
403 | } |
404 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, |
405 | $newrecord->filepath, $newrecord->filename); |
406 | } |
407 | |
408 | $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
409 | |
410 | return new stored_file($this, $newrecord); |
411 | } |
412 | |
413 | /** |
414 | * Add new local file |
415 | * @param mixed $file_record object or array describing file |
416 | * @param string $content content of file |
417 | * @return object stored_file instance |
418 | */ |
419 | public function create_file_from_string($file_record, $content) { |
420 | global $DB; |
421 | |
422 | $file_record = (object)$file_record; // we support arrays too |
423 | |
424 | // validate all parameters, we do not want any rubbish stored in database, right? |
425 | if (!is_number($file_record->contextid) or $file_record->contextid < 1) { |
426 | throw new file_exception('localfileproblem', 'Invalid contextid'); |
427 | } |
428 | |
6c0e2d08 |
429 | $file_record->filearea = clean_param($file_record->filearea, PARAM_ALPHAEXT); |
172dd12c |
430 | if ($file_record->filearea === '') { |
431 | throw new file_exception('localfileproblem', 'Invalid filearea'); |
432 | } |
433 | |
434 | if (!is_number($file_record->itemid) or $file_record->itemid < 0) { |
435 | throw new file_exception('localfileproblem', 'Invalid itemid'); |
436 | } |
437 | |
438 | $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); |
439 | if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { |
440 | // path must start and end with '/' |
441 | throw new file_exception('localfileproblem', 'Invalid file path'); |
442 | } |
443 | |
444 | $file_record->filename = clean_param($file_record->filename, PARAM_FILE); |
445 | if ($file_record->filename === '') { |
446 | // path must start and end with '/' |
447 | throw new file_exception('localfileproblem', 'Invalid file name'); |
448 | } |
449 | |
450 | $now = time(); |
451 | |
452 | $newrecord = new object(); |
453 | |
454 | $newrecord->contextid = $file_record->contextid; |
455 | $newrecord->filearea = $file_record->filearea; |
456 | $newrecord->itemid = $file_record->itemid; |
457 | $newrecord->filepath = $file_record->filepath; |
458 | $newrecord->filename = $file_record->filename; |
459 | |
460 | $newrecord->timecreated = empty($file_record->timecreated) ? $now : $file_record->timecreated; |
461 | $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified; |
462 | $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; |
463 | $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; |
464 | |
465 | list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_to_pool_string($content); |
466 | |
467 | $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); |
468 | |
469 | try { |
470 | $newrecord->id = $DB->insert_record('files', $newrecord); |
471 | } catch (database_exception $e) { |
472 | $newrecord->id = false; |
473 | } |
474 | |
475 | if (!$newrecord->id) { |
476 | if ($newfile) { |
477 | $this->mark_delete_candidate($newrecord->contenthash); |
478 | } |
479 | throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, |
480 | $newrecord->filepath, $newrecord->filename); |
481 | } |
482 | |
483 | $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); |
484 | |
485 | return new stored_file($this, $newrecord); |
486 | } |
487 | |
488 | /** |
489 | * Add file content to sha1 pool |
490 | * @param string $pathname path to file |
491 | * @param string sha1 hash of content if known (performance only) |
492 | * @return array(contenthash, filesize, newfile) |
493 | */ |
494 | public function add_to_pool_pathname($pathname, $contenthash=null) { |
495 | if (!is_readable($pathname)) { |
496 | throw new file_exception('localfilecannotread'); |
497 | } |
498 | |
499 | if (is_null($contenthash)) { |
500 | $contenthash = sha1_file($pathname); |
501 | } |
502 | |
503 | $filesize = filesize($pathname); |
504 | |
505 | $hashpath = $this->path_from_hash($contenthash); |
506 | $hashfile = "$hashpath/$contenthash"; |
507 | |
508 | if (file_exists($hashfile)) { |
509 | if (filesize($hashfile) !== $filesize) { |
510 | throw new file_pool_content_exception($contenthash); |
511 | } |
512 | $newfile = false; |
513 | |
514 | } else { |
515 | if (!check_dir_exists($hashpath, true, true)) { |
516 | throw new file_exception('localfilecannotcreatefiledirs'); // permission trouble |
517 | } |
518 | $newfile = true; |
519 | |
6c0e2d08 |
520 | if (!copy($pathname, $hashfile)) { |
521 | throw new file_exception('localfilecannotread'); |
172dd12c |
522 | } |
172dd12c |
523 | |
524 | if (filesize($hashfile) !== $filesize) { |
525 | @unlink($hashfile); |
526 | throw new file_pool_content_exception($contenthash); |
527 | } |
528 | } |
529 | |
530 | |
531 | return array($contenthash, $filesize, $newfile); |
532 | } |
533 | |
534 | /** |
535 | * Add string content to sha1 pool |
536 | * @param string $content file content - binary string |
537 | * @return array(contenthash, filesize, newfile) |
538 | */ |
539 | public function add_to_pool_string($content) { |
540 | $contenthash = sha1($content); |
541 | $filesize = strlen($content); // binary length |
542 | |
543 | $hashpath = $this->path_from_hash($contenthash); |
544 | $hashfile = "$hashpath/$contenthash"; |
545 | |
546 | |
547 | if (file_exists($hashfile)) { |
548 | if (filesize($hashfile) !== $filesize) { |
549 | throw new file_pool_content_exception($contenthash); |
550 | } |
551 | $newfile = false; |
552 | |
553 | } else { |
554 | if (!check_dir_exists($hashpath, true, true)) { |
555 | throw new file_exception('localfilecannotcreatefiledirs'); // permission trouble |
556 | } |
557 | $newfile = true; |
558 | |
6c0e2d08 |
559 | file_put_contents($hashfile, $content); |
172dd12c |
560 | |
561 | if (filesize($hashfile) !== $filesize) { |
562 | @unlink($hashfile); |
563 | throw new file_pool_content_exception($contenthash); |
564 | } |
565 | } |
566 | |
567 | return array($contenthash, $filesize, $newfile); |
568 | } |
569 | |
570 | /** |
571 | * Return path to file with given hash |
572 | * |
17d9269f |
573 | * NOTE: must not be public, files in pool must not be modified |
172dd12c |
574 | * |
575 | * @param string $contenthash |
576 | * @return string expected file location |
577 | */ |
17d9269f |
578 | protected function path_from_hash($contenthash) { |
172dd12c |
579 | $l1 = $contenthash[0].$contenthash[1]; |
580 | $l2 = $contenthash[2].$contenthash[3]; |
581 | $l3 = $contenthash[4].$contenthash[5]; |
582 | return "$this->filedir/$l1/$l2/$l3"; |
583 | } |
584 | |
585 | /** |
586 | * Marks pool file as candidate for deleting |
587 | * @param string $contenthash |
588 | */ |
589 | public function mark_delete_candidate($contenthash) { |
590 | global $DB; |
591 | |
592 | if ($DB->record_exists('files_cleanup', array('contenthash'=>$contenthash))) { |
593 | return; |
594 | } |
595 | $rec = new object(); |
596 | $rec->contenthash = $contenthash; |
597 | $DB->insert_record('files_cleanup', $rec); |
598 | } |
599 | |
600 | /** |
601 | * Cron cleanup job. |
602 | */ |
603 | public function cron() { |
604 | global $DB; |
605 | |
606 | //TODO: there is a small chance that reused files might be deleted |
607 | // if this function takes too long we should add some table locking here |
608 | |
609 | $sql = "SELECT 1 AS id, fc.contenthash |
610 | FROM {files_cleanup} fc |
611 | LEFT JOIN {files} f ON f.contenthash = fc.contenthash |
612 | WHERE f.id IS NULL"; |
613 | while ($hash = $DB->get_record_sql($sql, null, true)) { |
614 | $file = $this->path_from_hash($hash->contenthash).'/'.$hash->contenthash; |
615 | @unlink($file); |
616 | $DB->delete_records('files_cleanup', array('contenthash'=>$hash->contenthash)); |
617 | } |
618 | } |
619 | } |