|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00018 00028 defined('MOODLE_INTERNAL') || die(); 00029 00030 require_once("$CFG->libdir/filestorage/stored_file.php"); 00031 00045 class file_storage { 00047 private $filedir; 00049 private $trashdir; 00051 private $tempdir; 00053 private $dirpermissions; 00055 private $filepermissions; 00056 00066 public function __construct($filedir, $trashdir, $tempdir, $dirpermissions, $filepermissions) { 00067 $this->filedir = $filedir; 00068 $this->trashdir = $trashdir; 00069 $this->tempdir = $tempdir; 00070 $this->dirpermissions = $dirpermissions; 00071 $this->filepermissions = $filepermissions; 00072 00073 // make sure the file pool directory exists 00074 if (!is_dir($this->filedir)) { 00075 if (!mkdir($this->filedir, $this->dirpermissions, true)) { 00076 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble 00077 } 00078 // place warning file in file pool root 00079 if (!file_exists($this->filedir.'/warning.txt')) { 00080 file_put_contents($this->filedir.'/warning.txt', 00081 '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.'); 00082 } 00083 } 00084 // make sure the file pool directory exists 00085 if (!is_dir($this->trashdir)) { 00086 if (!mkdir($this->trashdir, $this->dirpermissions, true)) { 00087 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble 00088 } 00089 } 00090 } 00091 00106 public static function get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename) { 00107 return sha1("/$contextid/$component/$filearea/$itemid".$filepath.$filename); 00108 } 00109 00121 public function file_exists($contextid, $component, $filearea, $itemid, $filepath, $filename) { 00122 $filepath = clean_param($filepath, PARAM_PATH); 00123 $filename = clean_param($filename, PARAM_FILE); 00124 00125 if ($filename === '') { 00126 $filename = '.'; 00127 } 00128 00129 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename); 00130 return $this->file_exists_by_hash($pathnamehash); 00131 } 00132 00139 public function file_exists_by_hash($pathnamehash) { 00140 global $DB; 00141 00142 return $DB->record_exists('files', array('pathnamehash'=>$pathnamehash)); 00143 } 00144 00151 public function get_file_instance(stdClass $file_record) { 00152 return new stored_file($this, $file_record, $this->filedir); 00153 } 00154 00164 public function get_file_by_id($fileid) { 00165 global $DB; 00166 00167 if ($file_record = $DB->get_record('files', array('id'=>$fileid))) { 00168 return $this->get_file_instance($file_record); 00169 } else { 00170 return false; 00171 } 00172 } 00173 00180 public function get_file_by_hash($pathnamehash) { 00181 global $DB; 00182 00183 if ($file_record = $DB->get_record('files', array('pathnamehash'=>$pathnamehash))) { 00184 return $this->get_file_instance($file_record); 00185 } else { 00186 return false; 00187 } 00188 } 00189 00201 public function get_file($contextid, $component, $filearea, $itemid, $filepath, $filename) { 00202 $filepath = clean_param($filepath, PARAM_PATH); 00203 $filename = clean_param($filename, PARAM_FILE); 00204 00205 if ($filename === '') { 00206 $filename = '.'; 00207 } 00208 00209 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename); 00210 return $this->get_file_by_hash($pathnamehash); 00211 } 00212 00222 public function is_area_empty($contextid, $component, $filearea, $itemid = false, $ignoredirs = true) { 00223 global $DB; 00224 00225 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea); 00226 $where = "contextid = :contextid AND component = :component AND filearea = :filearea"; 00227 00228 if ($itemid !== false) { 00229 $params['itemid'] = $itemid; 00230 $where .= " AND itemid = :itemid"; 00231 } 00232 00233 if ($ignoredirs) { 00234 $sql = "SELECT 'x' 00235 FROM {files} 00236 WHERE $where AND filename <> '.'"; 00237 } else { 00238 $sql = "SELECT 'x' 00239 FROM {files} 00240 WHERE $where AND (filename <> '.' OR filepath <> '/')"; 00241 } 00242 00243 return !$DB->record_exists_sql($sql, $params); 00244 } 00245 00257 public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort="sortorder, itemid, filepath, filename", $includedirs = true) { 00258 global $DB; 00259 00260 $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea); 00261 if ($itemid !== false) { 00262 $conditions['itemid'] = $itemid; 00263 } 00264 00265 $result = array(); 00266 $file_records = $DB->get_records('files', $conditions, $sort); 00267 foreach ($file_records as $file_record) { 00268 if (!$includedirs and $file_record->filename === '.') { 00269 continue; 00270 } 00271 $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); 00272 } 00273 return $result; 00274 } 00275 00285 public function get_area_tree($contextid, $component, $filearea, $itemid) { 00286 $result = array('dirname'=>'', 'dirfile'=>null, 'subdirs'=>array(), 'files'=>array()); 00287 $files = $this->get_area_files($contextid, $component, $filearea, $itemid, "sortorder, itemid, filepath, filename", true); 00288 // first create directory structure 00289 foreach ($files as $hash=>$dir) { 00290 if (!$dir->is_directory()) { 00291 continue; 00292 } 00293 unset($files[$hash]); 00294 if ($dir->get_filepath() === '/') { 00295 $result['dirfile'] = $dir; 00296 continue; 00297 } 00298 $parts = explode('/', trim($dir->get_filepath(),'/')); 00299 $pointer =& $result; 00300 foreach ($parts as $part) { 00301 if ($part === '') { 00302 continue; 00303 } 00304 if (!isset($pointer['subdirs'][$part])) { 00305 $pointer['subdirs'][$part] = array('dirname'=>$part, 'dirfile'=>null, 'subdirs'=>array(), 'files'=>array()); 00306 } 00307 $pointer =& $pointer['subdirs'][$part]; 00308 } 00309 $pointer['dirfile'] = $dir; 00310 unset($pointer); 00311 } 00312 foreach ($files as $hash=>$file) { 00313 $parts = explode('/', trim($file->get_filepath(),'/')); 00314 $pointer =& $result; 00315 foreach ($parts as $part) { 00316 if ($part === '') { 00317 continue; 00318 } 00319 $pointer =& $pointer['subdirs'][$part]; 00320 } 00321 $pointer['files'][$file->get_filename()] = $file; 00322 unset($pointer); 00323 } 00324 return $result; 00325 } 00326 00340 public function get_directory_files($contextid, $component, $filearea, $itemid, $filepath, $recursive = false, $includedirs = true, $sort = "filepath, filename") { 00341 global $DB; 00342 00343 if (!$directory = $this->get_file($contextid, $component, $filearea, $itemid, $filepath, '.')) { 00344 return array(); 00345 } 00346 00347 if ($recursive) { 00348 00349 $dirs = $includedirs ? "" : "AND filename <> '.'"; 00350 $length = textlib_get_instance()->strlen($filepath); 00351 00352 $sql = "SELECT * 00353 FROM {files} 00354 WHERE contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid 00355 AND ".$DB->sql_substr("filepath", 1, $length)." = :filepath 00356 AND id <> :dirid 00357 $dirs 00358 ORDER BY $sort"; 00359 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id()); 00360 00361 $files = array(); 00362 $dirs = array(); 00363 $file_records = $DB->get_records_sql($sql, $params); 00364 foreach ($file_records as $file_record) { 00365 if ($file_record->filename == '.') { 00366 $dirs[$file_record->pathnamehash] = $this->get_file_instance($file_record); 00367 } else { 00368 $files[$file_record->pathnamehash] = $this->get_file_instance($file_record); 00369 } 00370 } 00371 $result = array_merge($dirs, $files); 00372 00373 } else { 00374 $result = array(); 00375 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id()); 00376 00377 $length = textlib_get_instance()->strlen($filepath); 00378 00379 if ($includedirs) { 00380 $sql = "SELECT * 00381 FROM {files} 00382 WHERE contextid = :contextid AND component = :component AND filearea = :filearea 00383 AND itemid = :itemid AND filename = '.' 00384 AND ".$DB->sql_substr("filepath", 1, $length)." = :filepath 00385 AND id <> :dirid 00386 ORDER BY $sort"; 00387 $reqlevel = substr_count($filepath, '/') + 1; 00388 $file_records = $DB->get_records_sql($sql, $params); 00389 foreach ($file_records as $file_record) { 00390 if (substr_count($file_record->filepath, '/') !== $reqlevel) { 00391 continue; 00392 } 00393 $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); 00394 } 00395 } 00396 00397 $sql = "SELECT * 00398 FROM {files} 00399 WHERE contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid 00400 AND filepath = :filepath AND filename <> '.' 00401 ORDER BY $sort"; 00402 00403 $file_records = $DB->get_records_sql($sql, $params); 00404 foreach ($file_records as $file_record) { 00405 $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); 00406 } 00407 } 00408 00409 return $result; 00410 } 00411 00421 public function delete_area_files($contextid, $component = false, $filearea = false, $itemid = false) { 00422 global $DB; 00423 00424 $conditions = array('contextid'=>$contextid); 00425 if ($component !== false) { 00426 $conditions['component'] = $component; 00427 } 00428 if ($filearea !== false) { 00429 $conditions['filearea'] = $filearea; 00430 } 00431 if ($itemid !== false) { 00432 $conditions['itemid'] = $itemid; 00433 } 00434 00435 $file_records = $DB->get_records('files', $conditions); 00436 foreach ($file_records as $file_record) { 00437 $this->get_file_instance($file_record)->delete(); 00438 } 00439 00440 return true; // BC only 00441 } 00442 00455 public function delete_area_files_select($contextid, $component, 00456 $filearea, $itemidstest, array $params = null) { 00457 global $DB; 00458 00459 $where = "contextid = :contextid 00460 AND component = :component 00461 AND filearea = :filearea 00462 AND itemid $itemidstest"; 00463 $params['contextid'] = $contextid; 00464 $params['component'] = $component; 00465 $params['filearea'] = $filearea; 00466 00467 $file_records = $DB->get_recordset_select('files', $where, $params); 00468 foreach ($file_records as $file_record) { 00469 $this->get_file_instance($file_record)->delete(); 00470 } 00471 $file_records->close(); 00472 } 00473 00482 public function move_area_files_to_new_context($oldcontextid, $newcontextid, $component, $filearea, $itemid = false) { 00483 // Note, this code is based on some code that Petr wrote in 00484 // forum_move_attachments in mod/forum/lib.php. I moved it here because 00485 // I needed it in the question code too. 00486 $count = 0; 00487 00488 $oldfiles = $this->get_area_files($oldcontextid, $component, $filearea, $itemid, 'id', false); 00489 foreach ($oldfiles as $oldfile) { 00490 $filerecord = new stdClass(); 00491 $filerecord->contextid = $newcontextid; 00492 $this->create_file_from_storedfile($filerecord, $oldfile); 00493 $count += 1; 00494 } 00495 00496 if ($count) { 00497 $this->delete_area_files($oldcontextid, $component, $filearea, $itemid); 00498 } 00499 00500 return $count; 00501 } 00502 00514 public function create_directory($contextid, $component, $filearea, $itemid, $filepath, $userid = null) { 00515 global $DB; 00516 00517 // validate all parameters, we do not want any rubbish stored in database, right? 00518 if (!is_number($contextid) or $contextid < 1) { 00519 throw new file_exception('storedfileproblem', 'Invalid contextid'); 00520 } 00521 00522 $component = clean_param($component, PARAM_COMPONENT); 00523 if (empty($component)) { 00524 throw new file_exception('storedfileproblem', 'Invalid component'); 00525 } 00526 00527 $filearea = clean_param($filearea, PARAM_AREA); 00528 if (empty($filearea)) { 00529 throw new file_exception('storedfileproblem', 'Invalid filearea'); 00530 } 00531 00532 if (!is_number($itemid) or $itemid < 0) { 00533 throw new file_exception('storedfileproblem', 'Invalid itemid'); 00534 } 00535 00536 $filepath = clean_param($filepath, PARAM_PATH); 00537 if (strpos($filepath, '/') !== 0 or strrpos($filepath, '/') !== strlen($filepath)-1) { 00538 // path must start and end with '/' 00539 throw new file_exception('storedfileproblem', 'Invalid file path'); 00540 } 00541 00542 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, '.'); 00543 00544 if ($dir_info = $this->get_file_by_hash($pathnamehash)) { 00545 return $dir_info; 00546 } 00547 00548 static $contenthash = null; 00549 if (!$contenthash) { 00550 $this->add_string_to_pool(''); 00551 $contenthash = sha1(''); 00552 } 00553 00554 $now = time(); 00555 00556 $dir_record = new stdClass(); 00557 $dir_record->contextid = $contextid; 00558 $dir_record->component = $component; 00559 $dir_record->filearea = $filearea; 00560 $dir_record->itemid = $itemid; 00561 $dir_record->filepath = $filepath; 00562 $dir_record->filename = '.'; 00563 $dir_record->contenthash = $contenthash; 00564 $dir_record->filesize = 0; 00565 00566 $dir_record->timecreated = $now; 00567 $dir_record->timemodified = $now; 00568 $dir_record->mimetype = null; 00569 $dir_record->userid = $userid; 00570 00571 $dir_record->pathnamehash = $pathnamehash; 00572 00573 $DB->insert_record('files', $dir_record); 00574 $dir_info = $this->get_file_by_hash($pathnamehash); 00575 00576 if ($filepath !== '/') { 00577 //recurse to parent dirs 00578 $filepath = trim($filepath, '/'); 00579 $filepath = explode('/', $filepath); 00580 array_pop($filepath); 00581 $filepath = implode('/', $filepath); 00582 $filepath = ($filepath === '') ? '/' : "/$filepath/"; 00583 $this->create_directory($contextid, $component, $filearea, $itemid, $filepath, $userid); 00584 } 00585 00586 return $dir_info; 00587 } 00588 00596 public function create_file_from_storedfile($file_record, $fileorid) { 00597 global $DB; 00598 00599 if ($fileorid instanceof stored_file) { 00600 $fid = $fileorid->get_id(); 00601 } else { 00602 $fid = $fileorid; 00603 } 00604 00605 $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record! 00606 00607 unset($file_record['id']); 00608 unset($file_record['filesize']); 00609 unset($file_record['contenthash']); 00610 unset($file_record['pathnamehash']); 00611 00612 if (!$newrecord = $DB->get_record('files', array('id'=>$fid))) { 00613 throw new file_exception('storedfileproblem', 'File does not exist'); 00614 } 00615 00616 unset($newrecord->id); 00617 00618 foreach ($file_record as $key=>$value) { 00619 // validate all parameters, we do not want any rubbish stored in database, right? 00620 if ($key == 'contextid' and (!is_number($value) or $value < 1)) { 00621 throw new file_exception('storedfileproblem', 'Invalid contextid'); 00622 } 00623 00624 if ($key == 'component') { 00625 $value = clean_param($value, PARAM_COMPONENT); 00626 if (empty($value)) { 00627 throw new file_exception('storedfileproblem', 'Invalid component'); 00628 } 00629 } 00630 00631 if ($key == 'filearea') { 00632 $value = clean_param($value, PARAM_AREA); 00633 if (empty($value)) { 00634 throw new file_exception('storedfileproblem', 'Invalid filearea'); 00635 } 00636 } 00637 00638 if ($key == 'itemid' and (!is_number($value) or $value < 0)) { 00639 throw new file_exception('storedfileproblem', 'Invalid itemid'); 00640 } 00641 00642 00643 if ($key == 'filepath') { 00644 $value = clean_param($value, PARAM_PATH); 00645 if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) { 00646 // path must start and end with '/' 00647 throw new file_exception('storedfileproblem', 'Invalid file path'); 00648 } 00649 } 00650 00651 if ($key == 'filename') { 00652 $value = clean_param($value, PARAM_FILE); 00653 if ($value === '') { 00654 // path must start and end with '/' 00655 throw new file_exception('storedfileproblem', 'Invalid file name'); 00656 } 00657 } 00658 00659 if ($key === 'timecreated' or $key === 'timemodified') { 00660 if (!is_number($value)) { 00661 throw new file_exception('storedfileproblem', 'Invalid file '.$key); 00662 } 00663 if ($value < 0) { 00664 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) 00665 $value = 0; 00666 } 00667 } 00668 00669 $newrecord->$key = $value; 00670 } 00671 00672 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); 00673 00674 if ($newrecord->filename === '.') { 00675 // special case - only this function supports directories ;-) 00676 $directory = $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); 00677 // update the existing directory with the new data 00678 $newrecord->id = $directory->get_id(); 00679 $DB->update_record('files', $newrecord); 00680 return $this->get_file_instance($newrecord); 00681 } 00682 00683 try { 00684 $newrecord->id = $DB->insert_record('files', $newrecord); 00685 } catch (dml_exception $e) { 00686 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, 00687 $newrecord->filepath, $newrecord->filename, $e->debuginfo); 00688 } 00689 00690 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); 00691 00692 return $this->get_file_instance($newrecord); 00693 } 00694 00704 public function create_file_from_url($file_record, $url, array $options = NULL, $usetempfile = false) { 00705 00706 $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects 00707 $file_record = (object)$file_record; // we support arrays too 00708 00709 $headers = isset($options['headers']) ? $options['headers'] : null; 00710 $postdata = isset($options['postdata']) ? $options['postdata'] : null; 00711 $fullresponse = isset($options['fullresponse']) ? $options['fullresponse'] : false; 00712 $timeout = isset($options['timeout']) ? $options['timeout'] : 300; 00713 $connecttimeout = isset($options['connecttimeout']) ? $options['connecttimeout'] : 20; 00714 $skipcertverify = isset($options['skipcertverify']) ? $options['skipcertverify'] : false; 00715 $calctimeout = isset($options['calctimeout']) ? $options['calctimeout'] : false; 00716 00717 if (!isset($file_record->filename)) { 00718 $parts = explode('/', $url); 00719 $filename = array_pop($parts); 00720 $file_record->filename = clean_param($filename, PARAM_FILE); 00721 } 00722 $source = !empty($file_record->source) ? $file_record->source : $url; 00723 $file_record->source = clean_param($source, PARAM_URL); 00724 00725 if ($usetempfile) { 00726 check_dir_exists($this->tempdir); 00727 $tmpfile = tempnam($this->tempdir, 'newfromurl'); 00728 $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify, $tmpfile, $calctimeout); 00729 if ($content === false) { 00730 throw new file_exception('storedfileproblem', 'Can not fetch file form URL'); 00731 } 00732 try { 00733 $newfile = $this->create_file_from_pathname($file_record, $tmpfile); 00734 @unlink($tmpfile); 00735 return $newfile; 00736 } catch (Exception $e) { 00737 @unlink($tmpfile); 00738 throw $e; 00739 } 00740 00741 } else { 00742 $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify, NULL, $calctimeout); 00743 if ($content === false) { 00744 throw new file_exception('storedfileproblem', 'Can not fetch file form URL'); 00745 } 00746 return $this->create_file_from_string($file_record, $content); 00747 } 00748 } 00749 00757 public function create_file_from_pathname($file_record, $pathname) { 00758 global $DB; 00759 00760 $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects 00761 $file_record = (object)$file_record; // we support arrays too 00762 00763 // validate all parameters, we do not want any rubbish stored in database, right? 00764 if (!is_number($file_record->contextid) or $file_record->contextid < 1) { 00765 throw new file_exception('storedfileproblem', 'Invalid contextid'); 00766 } 00767 00768 $file_record->component = clean_param($file_record->component, PARAM_COMPONENT); 00769 if (empty($file_record->component)) { 00770 throw new file_exception('storedfileproblem', 'Invalid component'); 00771 } 00772 00773 $file_record->filearea = clean_param($file_record->filearea, PARAM_AREA); 00774 if (empty($file_record->filearea)) { 00775 throw new file_exception('storedfileproblem', 'Invalid filearea'); 00776 } 00777 00778 if (!is_number($file_record->itemid) or $file_record->itemid < 0) { 00779 throw new file_exception('storedfileproblem', 'Invalid itemid'); 00780 } 00781 00782 if (!empty($file_record->sortorder)) { 00783 if (!is_number($file_record->sortorder) or $file_record->sortorder < 0) { 00784 $file_record->sortorder = 0; 00785 } 00786 } else { 00787 $file_record->sortorder = 0; 00788 } 00789 00790 $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); 00791 if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { 00792 // path must start and end with '/' 00793 throw new file_exception('storedfileproblem', 'Invalid file path'); 00794 } 00795 00796 $file_record->filename = clean_param($file_record->filename, PARAM_FILE); 00797 if ($file_record->filename === '') { 00798 // filename must not be empty 00799 throw new file_exception('storedfileproblem', 'Invalid file name'); 00800 } 00801 00802 $now = time(); 00803 if (isset($file_record->timecreated)) { 00804 if (!is_number($file_record->timecreated)) { 00805 throw new file_exception('storedfileproblem', 'Invalid file timecreated'); 00806 } 00807 if ($file_record->timecreated < 0) { 00808 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) 00809 $file_record->timecreated = 0; 00810 } 00811 } else { 00812 $file_record->timecreated = $now; 00813 } 00814 00815 if (isset($file_record->timemodified)) { 00816 if (!is_number($file_record->timemodified)) { 00817 throw new file_exception('storedfileproblem', 'Invalid file timemodified'); 00818 } 00819 if ($file_record->timemodified < 0) { 00820 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) 00821 $file_record->timemodified = 0; 00822 } 00823 } else { 00824 $file_record->timemodified = $now; 00825 } 00826 00827 $newrecord = new stdClass(); 00828 00829 $newrecord->contextid = $file_record->contextid; 00830 $newrecord->component = $file_record->component; 00831 $newrecord->filearea = $file_record->filearea; 00832 $newrecord->itemid = $file_record->itemid; 00833 $newrecord->filepath = $file_record->filepath; 00834 $newrecord->filename = $file_record->filename; 00835 00836 $newrecord->timecreated = $file_record->timecreated; 00837 $newrecord->timemodified = $file_record->timemodified; 00838 $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; 00839 $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; 00840 $newrecord->source = empty($file_record->source) ? null : $file_record->source; 00841 $newrecord->author = empty($file_record->author) ? null : $file_record->author; 00842 $newrecord->license = empty($file_record->license) ? null : $file_record->license; 00843 $newrecord->sortorder = $file_record->sortorder; 00844 00845 list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_file_to_pool($pathname); 00846 00847 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); 00848 00849 try { 00850 $newrecord->id = $DB->insert_record('files', $newrecord); 00851 } catch (dml_exception $e) { 00852 if ($newfile) { 00853 $this->deleted_file_cleanup($newrecord->contenthash); 00854 } 00855 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, 00856 $newrecord->filepath, $newrecord->filename, $e->debuginfo); 00857 } 00858 00859 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); 00860 00861 return $this->get_file_instance($newrecord); 00862 } 00863 00871 public function create_file_from_string($file_record, $content) { 00872 global $DB; 00873 00874 $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects 00875 $file_record = (object)$file_record; // we support arrays too 00876 00877 // validate all parameters, we do not want any rubbish stored in database, right? 00878 if (!is_number($file_record->contextid) or $file_record->contextid < 1) { 00879 throw new file_exception('storedfileproblem', 'Invalid contextid'); 00880 } 00881 00882 $file_record->component = clean_param($file_record->component, PARAM_COMPONENT); 00883 if (empty($file_record->component)) { 00884 throw new file_exception('storedfileproblem', 'Invalid component'); 00885 } 00886 00887 $file_record->filearea = clean_param($file_record->filearea, PARAM_AREA); 00888 if (empty($file_record->filearea)) { 00889 throw new file_exception('storedfileproblem', 'Invalid filearea'); 00890 } 00891 00892 if (!is_number($file_record->itemid) or $file_record->itemid < 0) { 00893 throw new file_exception('storedfileproblem', 'Invalid itemid'); 00894 } 00895 00896 if (!empty($file_record->sortorder)) { 00897 if (!is_number($file_record->sortorder) or $file_record->sortorder < 0) { 00898 $file_record->sortorder = 0; 00899 } 00900 } else { 00901 $file_record->sortorder = 0; 00902 } 00903 00904 $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); 00905 if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { 00906 // path must start and end with '/' 00907 throw new file_exception('storedfileproblem', 'Invalid file path'); 00908 } 00909 00910 $file_record->filename = clean_param($file_record->filename, PARAM_FILE); 00911 if ($file_record->filename === '') { 00912 // path must start and end with '/' 00913 throw new file_exception('storedfileproblem', 'Invalid file name'); 00914 } 00915 00916 $now = time(); 00917 if (isset($file_record->timecreated)) { 00918 if (!is_number($file_record->timecreated)) { 00919 throw new file_exception('storedfileproblem', 'Invalid file timecreated'); 00920 } 00921 if ($file_record->timecreated < 0) { 00922 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) 00923 $file_record->timecreated = 0; 00924 } 00925 } else { 00926 $file_record->timecreated = $now; 00927 } 00928 00929 if (isset($file_record->timemodified)) { 00930 if (!is_number($file_record->timemodified)) { 00931 throw new file_exception('storedfileproblem', 'Invalid file timemodified'); 00932 } 00933 if ($file_record->timemodified < 0) { 00934 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) 00935 $file_record->timemodified = 0; 00936 } 00937 } else { 00938 $file_record->timemodified = $now; 00939 } 00940 00941 $newrecord = new stdClass(); 00942 00943 $newrecord->contextid = $file_record->contextid; 00944 $newrecord->component = $file_record->component; 00945 $newrecord->filearea = $file_record->filearea; 00946 $newrecord->itemid = $file_record->itemid; 00947 $newrecord->filepath = $file_record->filepath; 00948 $newrecord->filename = $file_record->filename; 00949 00950 $newrecord->timecreated = $file_record->timecreated; 00951 $newrecord->timemodified = $file_record->timemodified; 00952 $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; 00953 $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; 00954 $newrecord->source = empty($file_record->source) ? null : $file_record->source; 00955 $newrecord->author = empty($file_record->author) ? null : $file_record->author; 00956 $newrecord->license = empty($file_record->license) ? null : $file_record->license; 00957 $newrecord->sortorder = $file_record->sortorder; 00958 00959 list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content); 00960 00961 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); 00962 00963 try { 00964 $newrecord->id = $DB->insert_record('files', $newrecord); 00965 } catch (dml_exception $e) { 00966 if ($newfile) { 00967 $this->deleted_file_cleanup($newrecord->contenthash); 00968 } 00969 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, 00970 $newrecord->filepath, $newrecord->filename, $e->debuginfo); 00971 } 00972 00973 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); 00974 00975 return $this->get_file_instance($newrecord); 00976 } 00977 00989 public function convert_image($file_record, $fid, $newwidth = NULL, $newheight = NULL, $keepaspectratio = true, $quality = NULL) { 00990 if (!function_exists('imagecreatefromstring')) { 00991 //Most likely the GD php extension isn't installed 00992 //image conversion cannot succeed 00993 throw new file_exception('storedfileproblem', 'imagecreatefromstring() doesnt exist. The PHP extension "GD" must be installed for image conversion.'); 00994 } 00995 00996 if ($fid instanceof stored_file) { 00997 $fid = $fid->get_id(); 00998 } 00999 01000 $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record! 01001 01002 if (!$file = $this->get_file_by_id($fid)) { // make sure file really exists and we we correct data 01003 throw new file_exception('storedfileproblem', 'File does not exist'); 01004 } 01005 01006 if (!$imageinfo = $file->get_imageinfo()) { 01007 throw new file_exception('storedfileproblem', 'File is not an image'); 01008 } 01009 01010 if (!isset($file_record['filename'])) { 01011 $file_record['filename'] = $file->get_filename(); 01012 } 01013 01014 if (!isset($file_record['mimetype'])) { 01015 $file_record['mimetype'] = mimeinfo('type', $file_record['filename']); 01016 } 01017 01018 $width = $imageinfo['width']; 01019 $height = $imageinfo['height']; 01020 $mimetype = $imageinfo['mimetype']; 01021 01022 if ($keepaspectratio) { 01023 if (0 >= $newwidth and 0 >= $newheight) { 01024 // no sizes specified 01025 $newwidth = $width; 01026 $newheight = $height; 01027 01028 } else if (0 < $newwidth and 0 < $newheight) { 01029 $xheight = ($newwidth*($height/$width)); 01030 if ($xheight < $newheight) { 01031 $newheight = (int)$xheight; 01032 } else { 01033 $newwidth = (int)($newheight*($width/$height)); 01034 } 01035 01036 } else if (0 < $newwidth) { 01037 $newheight = (int)($newwidth*($height/$width)); 01038 01039 } else { //0 < $newheight 01040 $newwidth = (int)($newheight*($width/$height)); 01041 } 01042 01043 } else { 01044 if (0 >= $newwidth) { 01045 $newwidth = $width; 01046 } 01047 if (0 >= $newheight) { 01048 $newheight = $height; 01049 } 01050 } 01051 01052 $img = imagecreatefromstring($file->get_content()); 01053 if ($height != $newheight or $width != $newwidth) { 01054 $newimg = imagecreatetruecolor($newwidth, $newheight); 01055 if (!imagecopyresized($newimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height)) { 01056 // weird 01057 throw new file_exception('storedfileproblem', 'Can not resize image'); 01058 } 01059 imagedestroy($img); 01060 $img = $newimg; 01061 } 01062 01063 ob_start(); 01064 switch ($file_record['mimetype']) { 01065 case 'image/gif': 01066 imagegif($img); 01067 break; 01068 01069 case 'image/jpeg': 01070 if (is_null($quality)) { 01071 imagejpeg($img); 01072 } else { 01073 imagejpeg($img, NULL, $quality); 01074 } 01075 break; 01076 01077 case 'image/png': 01078 $quality = (int)$quality; 01079 imagepng($img, NULL, $quality, NULL); 01080 break; 01081 01082 default: 01083 throw new file_exception('storedfileproblem', 'Unsupported mime type'); 01084 } 01085 01086 $content = ob_get_contents(); 01087 ob_end_clean(); 01088 imagedestroy($img); 01089 01090 if (!$content) { 01091 throw new file_exception('storedfileproblem', 'Can not convert image'); 01092 } 01093 01094 return $this->create_file_from_string($file_record, $content); 01095 } 01096 01104 public function add_file_to_pool($pathname, $contenthash = NULL) { 01105 if (!is_readable($pathname)) { 01106 throw new file_exception('storedfilecannotread', '', $pathname); 01107 } 01108 01109 if (is_null($contenthash)) { 01110 $contenthash = sha1_file($pathname); 01111 } 01112 01113 $filesize = filesize($pathname); 01114 01115 $hashpath = $this->path_from_hash($contenthash); 01116 $hashfile = "$hashpath/$contenthash"; 01117 01118 if (file_exists($hashfile)) { 01119 if (filesize($hashfile) !== $filesize) { 01120 throw new file_pool_content_exception($contenthash); 01121 } 01122 $newfile = false; 01123 01124 } else { 01125 if (!is_dir($hashpath)) { 01126 if (!mkdir($hashpath, $this->dirpermissions, true)) { 01127 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble 01128 } 01129 } 01130 $newfile = true; 01131 01132 if (!copy($pathname, $hashfile)) { 01133 throw new file_exception('storedfilecannotread', '', $pathname); 01134 } 01135 01136 if (filesize($hashfile) !== $filesize) { 01137 @unlink($hashfile); 01138 throw new file_pool_content_exception($contenthash); 01139 } 01140 chmod($hashfile, $this->filepermissions); // fix permissions if needed 01141 } 01142 01143 01144 return array($contenthash, $filesize, $newfile); 01145 } 01146 01153 public function add_string_to_pool($content) { 01154 $contenthash = sha1($content); 01155 $filesize = strlen($content); // binary length 01156 01157 $hashpath = $this->path_from_hash($contenthash); 01158 $hashfile = "$hashpath/$contenthash"; 01159 01160 01161 if (file_exists($hashfile)) { 01162 if (filesize($hashfile) !== $filesize) { 01163 throw new file_pool_content_exception($contenthash); 01164 } 01165 $newfile = false; 01166 01167 } else { 01168 if (!is_dir($hashpath)) { 01169 if (!mkdir($hashpath, $this->dirpermissions, true)) { 01170 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble 01171 } 01172 } 01173 $newfile = true; 01174 01175 file_put_contents($hashfile, $content); 01176 01177 if (filesize($hashfile) !== $filesize) { 01178 @unlink($hashfile); 01179 throw new file_pool_content_exception($contenthash); 01180 } 01181 chmod($hashfile, $this->filepermissions); // fix permissions if needed 01182 } 01183 01184 return array($contenthash, $filesize, $newfile); 01185 } 01186 01195 protected function path_from_hash($contenthash) { 01196 $l1 = $contenthash[0].$contenthash[1]; 01197 $l2 = $contenthash[2].$contenthash[3]; 01198 return "$this->filedir/$l1/$l2"; 01199 } 01200 01209 protected function trash_path_from_hash($contenthash) { 01210 $l1 = $contenthash[0].$contenthash[1]; 01211 $l2 = $contenthash[2].$contenthash[3]; 01212 return "$this->trashdir/$l1/$l2"; 01213 } 01214 01221 public function try_content_recovery($file) { 01222 $contenthash = $file->get_contenthash(); 01223 $trashfile = $this->trash_path_from_hash($contenthash).'/'.$contenthash; 01224 if (!is_readable($trashfile)) { 01225 if (!is_readable($this->trashdir.'/'.$contenthash)) { 01226 return false; 01227 } 01228 // nice, at least alternative trash file in trash root exists 01229 $trashfile = $this->trashdir.'/'.$contenthash; 01230 } 01231 if (filesize($trashfile) != $file->get_filesize() or sha1_file($trashfile) != $contenthash) { 01232 //weird, better fail early 01233 return false; 01234 } 01235 $contentdir = $this->path_from_hash($contenthash); 01236 $contentfile = $contentdir.'/'.$contenthash; 01237 if (file_exists($contentfile)) { 01238 //strange, no need to recover anything 01239 return true; 01240 } 01241 if (!is_dir($contentdir)) { 01242 if (!mkdir($contentdir, $this->dirpermissions, true)) { 01243 return false; 01244 } 01245 } 01246 return rename($trashfile, $contentfile); 01247 } 01248 01257 public function deleted_file_cleanup($contenthash) { 01258 global $DB; 01259 01260 //Note: this section is critical - in theory file could be reused at the same 01261 // time, if this happens we can still recover the file from trash 01262 if ($DB->record_exists('files', array('contenthash'=>$contenthash))) { 01263 // file content is still used 01264 return; 01265 } 01266 //move content file to trash 01267 $contentfile = $this->path_from_hash($contenthash).'/'.$contenthash; 01268 if (!file_exists($contentfile)) { 01269 //weird, but no problem 01270 return; 01271 } 01272 $trashpath = $this->trash_path_from_hash($contenthash); 01273 $trashfile = $trashpath.'/'.$contenthash; 01274 if (file_exists($trashfile)) { 01275 // we already have this content in trash, no need to move it there 01276 unlink($contentfile); 01277 return; 01278 } 01279 if (!is_dir($trashpath)) { 01280 mkdir($trashpath, $this->dirpermissions, true); 01281 } 01282 rename($contentfile, $trashfile); 01283 chmod($trashfile, $this->filepermissions); // fix permissions if needed 01284 } 01285 01291 public function cron() { 01292 global $CFG, $DB; 01293 01294 // find out all stale draft areas (older than 4 days) and purge them 01295 // those are identified by time stamp of the /. root dir 01296 mtrace('Deleting old draft files... ', ''); 01297 $old = time() - 60*60*24*4; 01298 $sql = "SELECT * 01299 FROM {files} 01300 WHERE component = 'user' AND filearea = 'draft' AND filepath = '/' AND filename = '.' 01301 AND timecreated < :old"; 01302 $rs = $DB->get_recordset_sql($sql, array('old'=>$old)); 01303 foreach ($rs as $dir) { 01304 $this->delete_area_files($dir->contextid, $dir->component, $dir->filearea, $dir->itemid); 01305 } 01306 $rs->close(); 01307 mtrace('done.'); 01308 01309 // remove trash pool files once a day 01310 // if you want to disable purging of trash put $CFG->fileslastcleanup=time(); into config.php 01311 if (empty($CFG->fileslastcleanup) or $CFG->fileslastcleanup < time() - 60*60*24) { 01312 require_once($CFG->libdir.'/filelib.php'); 01313 // Delete files that are associated with a context that no longer exists. 01314 mtrace('Cleaning up files from deleted contexts... ', ''); 01315 $sql = "SELECT DISTINCT f.contextid 01316 FROM {files} f 01317 LEFT OUTER JOIN {context} c ON f.contextid = c.id 01318 WHERE c.id IS NULL"; 01319 $rs = $DB->get_recordset_sql($sql); 01320 if ($rs->valid()) { 01321 $fs = get_file_storage(); 01322 foreach ($rs as $ctx) { 01323 $fs->delete_area_files($ctx->contextid); 01324 } 01325 } 01326 $rs->close(); 01327 mtrace('done.'); 01328 01329 mtrace('Deleting trash files... ', ''); 01330 fulldelete($this->trashdir); 01331 set_config('fileslastcleanup', time()); 01332 mtrace('done.'); 01333 } 01334 } 01335 } 01336