Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/filestorage/file_storage.php
Go to the documentation of this file.
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 
 All Data Structures Namespaces Files Functions Variables Enumerations