Updated the HEAD build version to 20080906
[moodle.git] / lib / file / file_storage.php
CommitLineData
172dd12c 1<?php //$Id$
2
3require_once("$CFG->libdir/file/stored_file.php");
4
5class 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)
28 * @return string pathname
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
172dd12c 363 $file_record = (array)$file_record; // we support arrays too
364 unset($file_record['id']);
365 unset($file_record['filesize']);
366 unset($file_record['contenthash']);
8eb1e0a1 367 unset($file_record['pathnamehash']);
172dd12c 368
369 $now = time();
370
ebcac6c6 371 if (!$newrecord = $DB->get_record('files', array('id'=>$fid))) {
145a0a31 372 throw new file_exception('storedfileproblem', 'File does not exist');
172dd12c 373 }
374
375 unset($newrecord->id);
376
377 foreach ($file_record as $key=>$value) {
378 // validate all parameters, we do not want any rubbish stored in database, right?
379 if ($key == 'contextid' and (!is_number($value) or $value < 1)) {
145a0a31 380 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 381 }
382
383 if ($key == 'filearea') {
6c0e2d08 384 $value = clean_param($value, PARAM_ALPHAEXT);
172dd12c 385 if ($value === '') {
145a0a31 386 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 387 }
388 }
389
390 if ($key == 'itemid' and (!is_number($value) or $value < 0)) {
145a0a31 391 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 392 }
393
394
395 if ($key == 'filepath') {
396 $value = clean_param($value, PARAM_PATH);
00c32c54 397 if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
172dd12c 398 // path must start and end with '/'
145a0a31 399 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 400 }
401 }
402
403 if ($key == 'filename') {
404 $value = clean_param($value, PARAM_FILE);
405 if ($value === '') {
406 // path must start and end with '/'
145a0a31 407 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 408 }
409 }
410
411 $newrecord->$key = $value;
412 }
413
414 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
415
416 try {
417 $newrecord->id = $DB->insert_record('files', $newrecord);
418 } catch (database_exception $e) {
419 $newrecord->id = false;
420 }
421
422 if (!$newrecord->id) {
423 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid,
424 $newrecord->filepath, $newrecord->filename);
425 }
426
427 $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
428
429 return new stored_file($this, $newrecord);
430 }
431
432 /**
433 * Add new local file
434 * @param mixed $file_record object or array describing file
435 * @param string $path path to file or content of file
436 * @return object stored_file instance
437 */
438 public function create_file_from_pathname($file_record, $pathname) {
439 global $DB;
440
441 $file_record = (object)$file_record; // we support arrays too
442
443 // validate all parameters, we do not want any rubbish stored in database, right?
444 if (!is_number($file_record->contextid) or $file_record->contextid < 1) {
145a0a31 445 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 446 }
447
6c0e2d08 448 $file_record->filearea = clean_param($file_record->filearea, PARAM_ALPHAEXT);
172dd12c 449 if ($file_record->filearea === '') {
145a0a31 450 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 451 }
452
453 if (!is_number($file_record->itemid) or $file_record->itemid < 0) {
145a0a31 454 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 455 }
456
457 $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH);
458 if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) {
459 // path must start and end with '/'
145a0a31 460 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 461 }
462
463 $file_record->filename = clean_param($file_record->filename, PARAM_FILE);
464 if ($file_record->filename === '') {
e1dcb950 465 // filename must not be empty
145a0a31 466 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 467 }
468
469 $now = time();
470
471 $newrecord = new object();
472
473 $newrecord->contextid = $file_record->contextid;
474 $newrecord->filearea = $file_record->filearea;
475 $newrecord->itemid = $file_record->itemid;
476 $newrecord->filepath = $file_record->filepath;
477 $newrecord->filename = $file_record->filename;
478
479 $newrecord->timecreated = empty($file_record->timecreated) ? $now : $file_record->timecreated;
480 $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified;
481 $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype;
482 $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid;
483
b48f3e06 484 list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_file_to_pool($pathname);
172dd12c 485
486 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
487
488 try {
489 $newrecord->id = $DB->insert_record('files', $newrecord);
490 } catch (database_exception $e) {
491 $newrecord->id = false;
492 }
493
494 if (!$newrecord->id) {
495 if ($newfile) {
496 $this->mark_delete_candidate($newrecord->contenthash);
497 }
498 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid,
499 $newrecord->filepath, $newrecord->filename);
500 }
501
502 $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
503
504 return new stored_file($this, $newrecord);
505 }
506
507 /**
508 * Add new local file
509 * @param mixed $file_record object or array describing file
510 * @param string $content content of file
511 * @return object stored_file instance
512 */
513 public function create_file_from_string($file_record, $content) {
514 global $DB;
515
516 $file_record = (object)$file_record; // we support arrays too
517
518 // validate all parameters, we do not want any rubbish stored in database, right?
519 if (!is_number($file_record->contextid) or $file_record->contextid < 1) {
145a0a31 520 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 521 }
522
6c0e2d08 523 $file_record->filearea = clean_param($file_record->filearea, PARAM_ALPHAEXT);
172dd12c 524 if ($file_record->filearea === '') {
145a0a31 525 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 526 }
527
528 if (!is_number($file_record->itemid) or $file_record->itemid < 0) {
145a0a31 529 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 530 }
531
532 $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH);
533 if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) {
534 // path must start and end with '/'
145a0a31 535 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 536 }
537
538 $file_record->filename = clean_param($file_record->filename, PARAM_FILE);
539 if ($file_record->filename === '') {
540 // path must start and end with '/'
145a0a31 541 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 542 }
543
544 $now = time();
545
546 $newrecord = new object();
547
548 $newrecord->contextid = $file_record->contextid;
549 $newrecord->filearea = $file_record->filearea;
550 $newrecord->itemid = $file_record->itemid;
551 $newrecord->filepath = $file_record->filepath;
552 $newrecord->filename = $file_record->filename;
553
554 $newrecord->timecreated = empty($file_record->timecreated) ? $now : $file_record->timecreated;
555 $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified;
556 $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype;
557 $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid;
558
b48f3e06 559 list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content);
172dd12c 560
561 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
562
563 try {
564 $newrecord->id = $DB->insert_record('files', $newrecord);
565 } catch (database_exception $e) {
566 $newrecord->id = false;
567 }
568
569 if (!$newrecord->id) {
570 if ($newfile) {
571 $this->mark_delete_candidate($newrecord->contenthash);
572 }
573 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid,
574 $newrecord->filepath, $newrecord->filename);
575 }
576
577 $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
578
579 return new stored_file($this, $newrecord);
580 }
581
49583e9e 582 /**
583 * Move one or more files from a given itemid location in the current user's draft files
584 * to a new filearea. Note that you can't rename files using this function.
585 * @param int $itemid - existing itemid in user draft_area with one or more files
586 * @param int $newcontextid - the new contextid to move files to
587 * @param string $newfilearea - the new filearea to move files to
882eb790 588 * @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 589 * @param string $newfilepath - the new path to move all files to
590 * @param bool $overwrite - overwrite files from the destination if they exist
591 * @param int $newuserid - new userid if required
592 * @return mixed stored_file object or false if error; may throw exception if duplicate found
593 * @return array(contenthash, filesize, newfile)
594 */
595 public function move_draft_to_final($itemid, $newcontextid, $newfilearea, $newitemid,
596 $newfilepath='/', $overwrite=false) {
597
598 global $USER;
599
600 /// Get files from the draft area
601 if (!$usercontext = get_context_instance(CONTEXT_USER, $USER->id)) {
602 return false;
603 }
604 if (!$files = $this->get_area_files($usercontext->id, 'user_draft', $itemid, 'filename', false)) {
605 return false;
606 }
607
882eb790 608 $newcontext = get_context_instance_by_id($newcontextid);
dd64e310 609 if (($newcontext->contextlevel == CONTEXT_USER) && ($newfilearea != 'user_draft')) {
882eb790 610 $newitemid = 0;
611 }
612
49583e9e 613 /// Process each file in turn
614
615 $returnfiles = array();
616 foreach ($files as $file) {
617
618 /// Delete any existing files in destination if required
619 if ($oldfile = $this->get_file($newcontextid, $newfilearea, $newitemid,
620 $newfilepath, $file->get_filename())) {
621 if ($overwrite) {
622 $oldfile->delete();
623 } else {
00c32c54 624 $returnfiles[] = $oldfile;
49583e9e 625 continue; // Can't overwrite the existing file so skip it
626 }
627 }
628
629 /// Create the new file
630 $newrecord = new object();
631 $newrecord->contextid = $newcontextid;
632 $newrecord->filearea = $newfilearea;
633 $newrecord->itemid = $newitemid;
634 $newrecord->filepath = $newfilepath;
635 $newrecord->filename = $file->get_filename();
636 $newrecord->timecreated = $file->get_timecreated();
637 $newrecord->timemodified = $file->get_timemodified();
638 $newrecord->mimetype = $file->get_mimetype();
639 $newrecord->userid = $file->get_userid();
640
641 if ($newfile = $this->create_file_from_storedfile($newrecord, $file->get_id())) {
642 $file->delete();
643 $returnfiles[] = $newfile;
644 }
645 }
646
647 return $returnfiles;
648 }
649
650
172dd12c 651 /**
652 * Add file content to sha1 pool
653 * @param string $pathname path to file
654 * @param string sha1 hash of content if known (performance only)
655 * @return array(contenthash, filesize, newfile)
656 */
b48f3e06 657 public function add_file_to_pool($pathname, $contenthash=null) {
172dd12c 658 if (!is_readable($pathname)) {
145a0a31 659 throw new file_exception('storedfilecannotread');
172dd12c 660 }
661
662 if (is_null($contenthash)) {
663 $contenthash = sha1_file($pathname);
664 }
665
666 $filesize = filesize($pathname);
667
668 $hashpath = $this->path_from_hash($contenthash);
669 $hashfile = "$hashpath/$contenthash";
670
671 if (file_exists($hashfile)) {
672 if (filesize($hashfile) !== $filesize) {
673 throw new file_pool_content_exception($contenthash);
674 }
675 $newfile = false;
676
677 } else {
678 if (!check_dir_exists($hashpath, true, true)) {
145a0a31 679 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble
172dd12c 680 }
681 $newfile = true;
682
6c0e2d08 683 if (!copy($pathname, $hashfile)) {
145a0a31 684 throw new file_exception('storedfilecannotread');
172dd12c 685 }
172dd12c 686
687 if (filesize($hashfile) !== $filesize) {
688 @unlink($hashfile);
689 throw new file_pool_content_exception($contenthash);
690 }
691 }
692
693
694 return array($contenthash, $filesize, $newfile);
695 }
696
697 /**
698 * Add string content to sha1 pool
699 * @param string $content file content - binary string
700 * @return array(contenthash, filesize, newfile)
701 */
b48f3e06 702 public function add_string_to_pool($content) {
172dd12c 703 $contenthash = sha1($content);
704 $filesize = strlen($content); // binary length
705
706 $hashpath = $this->path_from_hash($contenthash);
707 $hashfile = "$hashpath/$contenthash";
708
709
710 if (file_exists($hashfile)) {
711 if (filesize($hashfile) !== $filesize) {
712 throw new file_pool_content_exception($contenthash);
713 }
714 $newfile = false;
715
716 } else {
717 if (!check_dir_exists($hashpath, true, true)) {
145a0a31 718 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble
172dd12c 719 }
720 $newfile = true;
721
6c0e2d08 722 file_put_contents($hashfile, $content);
172dd12c 723
724 if (filesize($hashfile) !== $filesize) {
725 @unlink($hashfile);
726 throw new file_pool_content_exception($contenthash);
727 }
728 }
729
730 return array($contenthash, $filesize, $newfile);
731 }
732
733 /**
734 * Return path to file with given hash
735 *
17d9269f 736 * NOTE: must not be public, files in pool must not be modified
172dd12c 737 *
738 * @param string $contenthash
739 * @return string expected file location
740 */
17d9269f 741 protected function path_from_hash($contenthash) {
172dd12c 742 $l1 = $contenthash[0].$contenthash[1];
743 $l2 = $contenthash[2].$contenthash[3];
744 $l3 = $contenthash[4].$contenthash[5];
745 return "$this->filedir/$l1/$l2/$l3";
746 }
747
748 /**
749 * Marks pool file as candidate for deleting
750 * @param string $contenthash
751 */
752 public function mark_delete_candidate($contenthash) {
753 global $DB;
754
755 if ($DB->record_exists('files_cleanup', array('contenthash'=>$contenthash))) {
756 return;
757 }
758 $rec = new object();
759 $rec->contenthash = $contenthash;
760 $DB->insert_record('files_cleanup', $rec);
761 }
762
763 /**
764 * Cron cleanup job.
765 */
766 public function cron() {
767 global $DB;
768
769 //TODO: there is a small chance that reused files might be deleted
770 // if this function takes too long we should add some table locking here
771
772 $sql = "SELECT 1 AS id, fc.contenthash
773 FROM {files_cleanup} fc
774 LEFT JOIN {files} f ON f.contenthash = fc.contenthash
775 WHERE f.id IS NULL";
776 while ($hash = $DB->get_record_sql($sql, null, true)) {
777 $file = $this->path_from_hash($hash->contenthash).'/'.$hash->contenthash;
778 @unlink($file);
779 $DB->delete_records('files_cleanup', array('contenthash'=>$hash->contenthash));
780 }
781 }
782}