Updated the HEAD build version to 20080913
[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)
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}