From: skodak Date: Thu, 31 Jul 2008 22:15:30 +0000 (+0000) Subject: MDL-14589 initial file storage implementation, temporary file manager, migration... X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=172dd12c63311698a99865ddde36e4f84e95ad72;p=moodle.git MDL-14589 initial file storage implementation, temporary file manager, migration of course files; blog conversion MDL-15905; assignment conversion MDL-15904; fromslib related file improvements MDL-15906 --- diff --git a/admin/roles/manage.php b/admin/roles/manage.php index 254db546f4..54299961df 100755 --- a/admin/roles/manage.php +++ b/admin/roles/manage.php @@ -55,7 +55,7 @@ case 'add': if ($data = data_submitted() and confirm_sesskey()) { - $shortname = moodle_strtolower(clean_param(clean_filename($shortname), PARAM_SAFEDIR)); // only lowercase safe ASCII characters + $shortname = moodle_strtolower(clean_filename($shortname)); // only lowercase safe ASCII characters $legacytype = required_param('legacytype', PARAM_RAW); $legacyroles = get_legacy_roles(); diff --git a/admin/uploadpicture.php b/admin/uploadpicture.php index bce28a86d7..65566ce8fc 100644 --- a/admin/uploadpicture.php +++ b/admin/uploadpicture.php @@ -79,14 +79,14 @@ if ($formdata = $mform->get_data()) { // Create a unique temporary directory, to process the zip file // contents. - $zipdir = my_mktempdir($CFG->dataroot.'/temp/', 'usrpic'); + $zipodir = my_mktempdir($CFG->dataroot.'/temp/', 'usrpic'); + $dstfile = $zipodir.'/images.zip'; - if (!$mform->save_files($zipdir)) { + if (!$mform->save_file('userfile', $dstfile, true)) { notify(get_string('uploadpicture_cannotmovezip','admin')); @remove_dir($zipdir); } else { - $dstfile = $zipdir.'/'.$mform->get_new_filename(); - if(!unzip_file($dstfile, $zipdir, false)) { + if (!unzip_file($dstfile, $zipdir, false)) { notify(get_string('uploadpicture_cannotunzip','admin')); @remove_dir($zipdir); } else { diff --git a/blog/edit.php b/blog/edit.php index 42b8944cad..fb46a81aed 100755 --- a/blog/edit.php +++ b/blog/edit.php @@ -155,11 +155,11 @@ die; function do_delete($post) { global $returnurl, $DB; + blog_delete_attachments($post); + $status = $DB->delete_records('post', array('id'=>$post->id)); tag_set('post', $post->id, array()); - blog_delete_old_attachments($post); - add_to_log(SITEID, 'blog', 'delete', 'index.php?userid='. $post->userid, 'deleted blog entry with entry id# '. $post->id); if (!$status) { @@ -182,10 +182,12 @@ function do_add($post, $blogeditform) { if ($id = $DB->insert_record('post', $post)) { $post->id = $id; // add blog attachment - $dir = blog_file_area_name($post); - if ($blogeditform->save_files($dir) and $newfilename = $blogeditform->get_new_filename()) { - $DB->set_field("post", "attachment", $newfilename, array("id"=>$post->id)); + if ($blogeditform->get_new_filename('attachment')) { + if ($blogeditform->save_stored_file('attachment', SYSCONTEXTID, 'blog', $post->id, '/', false, $USER->id)) { + $DB->set_field("post", "attachment", 1, array("id"=>$post->id)); + } } + add_tags_info($post->id); add_to_log(SITEID, 'blog', 'add', 'index.php?userid='.$post->userid.'&postid='.$post->id, $post->subject); @@ -205,9 +207,13 @@ function do_edit($post, $blogeditform) { $post->lastmodified = time(); - $dir = blog_file_area_name($post); - if ($blogeditform->save_files($dir) and $newfilename = $blogeditform->get_new_filename()) { - $post->attachment = $newfilename; + if ($blogeditform->get_new_filename('attachment')) { + blog_delete_attachments($post); + if ($blogeditform->save_stored_file('attachment', SYSCONTEXTID, 'blog', $post->id, '/', false, $USER->id)) { + $post->attachment = 1; + } else { + $post->attachment = 1; + } } // update record diff --git a/blog/edit_form.php b/blog/edit_form.php index 4d47139b9f..14fdd40bdd 100644 --- a/blog/edit_form.php +++ b/blog/edit_form.php @@ -12,9 +12,6 @@ class blog_edit_form extends moodleform { $post = $this->_customdata['existing']; $sitecontext = $this->_customdata['sitecontext']; - // the upload manager is used directly in entry processing, moodleform::save_files() is not used yet - $this->set_upload_manager(new upload_manager('attachment', true, false, $COURSE, false, 0, true, true, false)); - $mform->addElement('header', 'general', get_string('general', 'form')); $mform->addElement('text', 'subject', get_string('entrytitle', 'blog'), 'size="60"'); $mform->setType('subject', PARAM_TEXT); diff --git a/blog/lib.php b/blog/lib.php index f37e084f58..de5501c626 100755 --- a/blog/lib.php +++ b/blog/lib.php @@ -154,7 +154,7 @@ if($blogEntry->created != $blogEntry->lastmodified){ $template['lastmod'] = userdate($blogEntry->lastmodified); } - + $template['publishstate'] = $blogEntry->publishstate; /// preventing user to browse blogs that they aren't supposed to see @@ -232,7 +232,7 @@ print(get_string('tags', 'tag') .': '. $blogtags); } echo ''; - } + } /// Commands @@ -257,36 +257,12 @@ } - /** - * Creates a directory file name, suitable for make_upload_directory() - * $CFG->dataroot/blog/attachments/xxxx/file.jpg - */ - function blog_file_area_name($blogentry) { - return "blog/attachments/$blogentry->id"; - } - - function blog_file_area($blogentry) { - return make_upload_directory( blog_file_area_name($blogentry) ); - } - /** * Deletes all the user files in the attachments area for a post - * EXCEPT for any file named $exception */ - function blog_delete_old_attachments($post, $exception="") { - if ($basedir = blog_file_area($post)) { - if ($files = get_directory_list($basedir)) { - foreach ($files as $file) { - if ($file != $exception) { - unlink("$basedir/$file"); - notify("Existing file '$file' has been deleted!"); - } - } - } - if (!$exception) { // Delete directory as well, if empty - rmdir("$basedir"); - } - } + function blog_delete_attachments($post) { + $fs = get_file_storage(); + $fs->delete_area_files(SYSCONTEXTID, 'blog', $post->id); } /** @@ -297,36 +273,44 @@ function blog_print_attachments($blogentry, $return=NULL) { global $CFG; - $filearea = blog_file_area_name($blogentry); + require_once($CFG->libdir.'/filelib.php'); + + $fs = get_file_storage(); + $browser = get_file_browser(); + + $files = $fs->get_area_files(SYSCONTEXTID, 'blog', $blogentry->id); $imagereturn = ""; $output = ""; - if ($basedir = blog_file_area($blogentry)) { - if ($files = get_directory_list($basedir)) { - $strattachment = get_string("attachment", "forum"); - foreach ($files as $file) { - include_once($CFG->libdir.'/filelib.php'); - $icon = mimeinfo("icon", $file); - $type = mimeinfo("type", $file); - $ffurl = get_file_url("$filearea/$file"); - $image = "pixpath/f/$icon\" class=\"icon\" alt=\"\" />"; + $strattachment = get_string("attachment", "forum"); - if ($return == "html") { - $output .= "$image "; - $output .= "$file
"; + foreach ($files as $file) { + if ($file->is_directory()) { + continue; + } - } else if ($return == "text") { - $output .= "$strattachment $file:\n$ffurl\n"; + $filename = $file->get_filename(); + $ffurl = $browser->encodepath($CFG->wwwroot.'/pluginfile.php', '/'.SYSCONTEXTID.'/blog/'.$blogentry->id.'/'.$filename); + $type = $file->get_mimetype(); + $icon = mimeinfo_from_type("icon", $type); + $type = mimeinfo_from_type("type", $type); - } else { - if (in_array($type, array('image/gif', 'image/jpeg', 'image/png'))) { // Image attachments don't get printed as links - $imagereturn .= "
\"\""; - } else { - echo "$image "; - echo filter_text("$file
"); - } - } + $image = "pixpath/f/$icon\" class=\"icon\" alt=\"\" />"; + + if ($return == "html") { + $output .= "$image "; + $output .= "$filename
"; + + } else if ($return == "text") { + $output .= "$strattachment $filename:\n$ffurl\n"; + + } else { + if (in_array($type, array('image/gif', 'image/jpeg', 'image/png'))) { // Image attachments don't get printed as links + $imagereturn .= "
\"\""; + } else { + echo "$image "; + echo filter_text("$filename
"); } } } @@ -699,7 +683,7 @@ function blog_get_participants() { global $CFG, $DB; - return $DB->get_records_sql("SELECT userid AS id + return $DB->get_records_sql("SELECT userid AS id FROM {post} WHERE module = 'blog' AND courseid = 0"); } diff --git a/config-dist.php b/config-dist.php index 98015a15c9..c0c29d995f 100644 --- a/config-dist.php +++ b/config-dist.php @@ -164,13 +164,6 @@ $CFG->admin = 'admin'; // $CFG->defaultblocks = 'participants,activity_modules,search_forums,admin,course_list:news_items,calendar_upcoming,recent_activity'; // // -// Allow unicode characters in uploaded files, generated reports, etc. -// This setting is new and not much tested, there are known problems -// with backup/restore that will not be solved, because native infozip -// binaries are doing some weird conversions - use internal PHP zipping instead. -// NOT RECOMMENDED FOR PRODUCTION SITES -// $CFG->unicodecleanfilename = true; -// // Seconds for files to remain in caches. Decrease this if you are worried // about students being served outdated versions of uploaded files. // $CFG->filelifetime = 86400; diff --git a/draftfile.php b/draftfile.php new file mode 100644 index 0000000000..41d10f4d86 --- /dev/null +++ b/draftfile.php @@ -0,0 +1 @@ +dataroot.$relativepath; - // extract relative path components - $args = explode('/', trim($relativepath, '/')); + $args = explode('/', ltrim($relativepath, '/')); + if (count($args) == 0) { // always at least courseid, may search for index.html in course root print_error('invalidarguments'); } - - // security: limit access to existing course subdirectories - if (($args[0]!='blog') and (!$course = $DB->get_record_sql("SELECT * FROM {course} WHERE id=?", array((int)$args[0])))) { - print_error('invalidcourseid'); - } - // security: prevent access to "000" or "1 something" directories - // hack for blogs, needs proper security check too - if (($args[0] != 'blog') and ($args[0] != $course->id)) { + $courseid = (int)array_shift($args); + $relativepath = '/'.implode('/', $args); + + // security: limit access to existing course subdirectories + if (!$course = $DB->get_record('course', array('id'=>$courseid))) { print_error('invalidcourseid'); } - // security: login to course if necessary - // Note: file.php always calls require_login() with $setwantsurltome=false - // in order to avoid messing redirects. MDL-14495 - if ($args[0] == 'blog') { - if (empty($CFG->bloglevel)) { - print_error('blogdisable', 'blog'); - } else if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) { - require_login(0, true, null, false); - } else if ($CFG->forcelogin) { - require_login(0, true, null, false); - } - } else if ($course->id != SITEID) { + if ($course->id != SITEID) { require_login($course->id, true, null, false); + } else if ($CFG->forcelogin) { if (!empty($CFG->sitepolicy) and ($CFG->sitepolicy == $CFG->wwwroot.'/file.php'.$relativepath @@ -79,109 +65,40 @@ } } - // security: only editing teachers can access backups - if ((count($args) >= 2) and (strtolower($args[1]) == 'backupdata')) { - if (!has_capability('moodle/site:backup', get_context_instance(CONTEXT_COURSE, $course->id))) { - print_error('nopermissions'); - } else { - $lifetime = 0; //disable browser caching for backups - } - } + $context = get_context_instance(CONTEXT_COURSE, $course->id); - if (is_dir($pathname)) { - if (file_exists($pathname.'/index.html')) { - $pathname = rtrim($pathname, '/').'/index.html'; - $args[] = 'index.html'; - } else if (file_exists($pathname.'/index.htm')) { - $pathname = rtrim($pathname, '/').'/index.htm'; - $args[] = 'index.htm'; - } else if (file_exists($pathname.'/Default.htm')) { - $pathname = rtrim($pathname, '/').'/Default.htm'; - $args[] = 'Default.htm'; - } else { - // security: do not return directory node! - not_found($course->id); - } - } - - // security: teachers can view all assignments, students only their own - if ((count($args) >= 3) - and (strtolower($args[1]) == 'moddata') - and (strtolower($args[2]) == 'assignment')) { + $fs = get_file_storage(); - $lifetime = 0; // do not cache assignments, students may reupload them - if ($args[4] == $USER->id) { - //can view own assignemnt submissions - } else { - $instance = (int)$args[3]; - if (!$cm = get_coursemodule_from_instance('assignment', $instance, $course->id)) { - not_found($course->id); - } - if (!has_capability('mod/assignment:grade', get_context_instance(CONTEXT_MODULE, $cm->id))) { - print_error('nopermissions'); - } - } - } + $fullpath = $context->id.'course_content0'.$relativepath; - // security: force download of all attachments submitted by students - if ((count($args) >= 3) - and (strtolower($args[1]) == 'moddata') - and ((strtolower($args[2]) == 'forum') - or (strtolower($args[2]) == 'assignment') - or (strtolower($args[2]) == 'data') - or (strtolower($args[2]) == 'glossary') - or (strtolower($args[2]) == 'wiki') - or (strtolower($args[2]) == 'exercise') - or (strtolower($args[2]) == 'workshop') - )) { - $forcedownload = 1; // force download of all attachments - } - if ($args[0] == 'blog') { - $forcedownload = 1; // force download of all attachments - } - - // security: some protection of hidden resource files - // warning: it may break backwards compatibility - if ((!empty($CFG->preventaccesstohiddenfiles)) - and (count($args) >= 2) - and (!(strtolower($args[1]) == 'moddata' and strtolower($args[2]) != 'resource')) // do not block files from other modules! - and (!has_capability('moodle/course:viewhiddenactivities', get_context_instance(CONTEXT_COURSE, $course->id)))) { - - $rargs = $args; - array_shift($rargs); - $reference = implode('/', $rargs); - - $sql = "SELECT COUNT(r.id) - FROM {resource} r, {course_modules} cm, {modules} m - WHERE r.course = ? - AND m.name = 'resource' - AND cm.module = m.id - AND cm.instance = r.id - AND cm.visible = 0 - AND r.type = 'file' - AND r.reference = ?"; - $params = array($course->id, $reference); - - if ($DB->count_records_sql($sql, $params)) { - print_error('nopermissions'); + if (!$file = $fs->get_file_by_hash(sha1($fullpath))) { + if (strrpos($fullpath, '/') !== strlen($fullpath) -1 ) { + $fullpath .= '/'; + } + if (!$file = $fs->get_file_by_hash(sha1($fullpath.'/.'))) { + not_found(); } } - - // check that file exists - if (!file_exists($pathname)) { - not_found($course->id); + // do not serve dirs + if ($file->get_filename() == '.') { + if (!$file = $fs->get_file_by_hash(sha1($fullpath.'index.html'))) { + if (!$file = $fs->get_file_by_hash(sha1($fullpath.'index.htm'))) { + if (!$file = $fs->get_file_by_hash(sha1($fullpath.'Default.htm'))) { + not_found(); + } + } + } } // ======================================== // finally send the file // ======================================== session_write_close(); // unlock session during fileserving - $filename = $args[count($args)-1]; - send_file($pathname, $filename, $lifetime, $CFG->filteruploadedfiles, false, $forcedownload); + send_stored_file($file, $lifetime, $CFG->filteruploadedfiles, $forcedownload); - function not_found($courseid) { - global $CFG; + function not_found() { + global $CFG, $COURSE; header('HTTP/1.0 404 not found'); - print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$courseid); //this is not displayed on IIS?? + print_error('filenotfound', 'error', $CFG->wwwroot.'/course/view.php?id='.$COURSE->id); //this is not displayed on IIS?? } -?> + diff --git a/files/index.php b/files/index.php index e869873d86..b76d57ad58 100644 --- a/files/index.php +++ b/files/index.php @@ -1,689 +1,179 @@ libdir.'/filelib.php'); - require($CFG->libdir.'/adminlib.php'); - - $id = required_param('id', PARAM_INT); - $file = optional_param('file', '', PARAM_PATH); - $wdir = optional_param('wdir', '', PARAM_PATH); - $action = optional_param('action', '', PARAM_ACTION); - $name = optional_param('name', '', PARAM_FILE); - $oldname = optional_param('oldname', '', PARAM_FILE); - $choose = optional_param('choose', '', PARAM_FILE); //in fact it is always 'formname.inputname' - $userfile= optional_param('userfile','',PARAM_FILE); - $save = optional_param('save', 0, PARAM_BOOL); - $text = optional_param('text', '', PARAM_RAW); - $confirm = optional_param('confirm', 0, PARAM_BOOL); - - if ($choose) { - if (count(explode('.', $choose)) > 2) { - print_error('invalidformatpara'); - } - } - + require_once($CFG->libdir.'/filelib.php'); + require_once($CFG->libdir.'/adminlib.php'); - if (! $course = $DB->get_record("course", array("id"=>$id))) { - print_error('invalidcourseid'); - } + $courseid = optional_param('id', 0, PARAM_INT); - require_login($course); - - require_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $course->id)); - - function html_footer() { - global $COURSE, $choose; - - echo ''; - - print_footer($COURSE); - } + $contextid = optional_param('contextid', SYSCONTEXTID, PARAM_INT); + $filearea = optional_param('filearea', '', PARAM_SAFEDIR); + $itemid = optional_param('itemid', -1, PARAM_INT); + $filepath = optional_param('filepath', '', PARAM_PATH); + $filename = optional_param('filename', '', PARAM_FILE); - function html_header($course, $wdir, $formfield=""){ - global $CFG, $ME, $choose; + $newdirname = optional_param('newdirname', '', PARAM_FILE); + $delete = optional_param('delete', 0, PARAM_BOOL); - $navlinks = array(); - // $navlinks[] = array('name' => $course->shortname, 'link' => "../course/view.php?id=$course->id", 'type' => 'misc'); - - if ($course->id == SITEID) { - $strfiles = get_string("sitefiles"); - } else { - $strfiles = get_string("files"); + if ($courseid) { + if (!$course = $DB->get_record('course', array('id'=>$courseid))) { + print_error('invalidcourseid'); } - - if ($wdir == "/") { - $navlinks[] = array('name' => $strfiles, 'link' => null, 'type' => 'misc'); - } else { - $dirs = explode("/", $wdir); - $numdirs = count($dirs); - $link = ""; - $navlinks[] = array('name' => $strfiles, - 'link' => $ME."?id=$course->id&wdir=/&choose=$choose", - 'type' => 'misc'); - - for ($i=1; $i<$numdirs-1; $i++) { - $link .= "/".urlencode($dirs[$i]); - $navlinks[] = array('name' => $dirs[$i], - 'link' => $ME."?id=$course->id&wdir=$link&choose=$choose", - 'type' => 'misc'); - } - $navlinks[] = array('name' => $dirs[$numdirs-1], 'link' => null, 'type' => 'misc'); + if (!$context = get_context_instance(CONTEXT_COURSE, $course->id)) { + print_error('invalidcontext'); } - - $navigation = build_navigation($navlinks); - - if ($choose) { - print_header(); - - $chooseparts = explode('.', $choose); - if (count($chooseparts)==2){ - ?> - - - - - - '.$navlink['name'].''; - } - $fullnav .= ' -> '; - $i++; - } - $fullnav = substr($fullnav, 0, -4); - $fullnav = str_replace('->', '»', format_string($course->shortname) . " -> " . $fullnav); - echo ''; - - if ($course->id == SITEID and $wdir != "/backupdata") { - print_heading(get_string("publicsitefileswarning"), "center", 2); - } - - } else { - - if ($course->id == SITEID) { - - if ($wdir == "/backupdata") { - admin_externalpage_setup('frontpagerestore'); - admin_externalpage_print_header(); - } else { - admin_externalpage_setup('sitefiles'); - admin_externalpage_print_header(); - - print_heading(get_string("publicsitefileswarning"), "center", 2); - - } - - } else { - print_header("$course->shortname: $strfiles", $course->fullname, $navigation, $formfield); - } - } - - - echo ""; - echo ""; - echo "
"; - + redirect('index.php?contextid='.$context->id.'&filearea=coursefiles'); } - - if (! $basedir = make_upload_directory("$course->id")) { - print_error("nopermissiontomkdir"); + if (!$context = get_context_instance_by_id($contextid)) { + print_error('invalidcontext'); } - $baseweb = $CFG->wwwroot; + require_login(); + require_capability('moodle/course:managefiles', $context); -// End of configuration and access control - - - if ($wdir == '') { - $wdir = "/"; + if ($filearea === '') { + $filearea = null; } - if ($wdir{0} != '/') { //make sure $wdir starts with slash - $wdir = "/".$wdir; + if ($itemid < 0) { + $itemid = null; } - if ($wdir == "/backupdata") { - if (! make_upload_directory("$course->id/backupdata")) { // Backup folder - print_error('cannotcreatebackupdir'); - } + if ($filepath === '') { + $filepath = null; } - if (!is_dir($basedir.$wdir)) { - html_header($course, $wdir); - print_error('nofolder', '', "$CFG->wwwroot/files/index.php?id=$id"); + if ($filename === '') { + $filename = null; } - switch ($action) { - - case "upload": - html_header($course, $wdir); - require_once($CFG->dirroot.'/lib/uploadlib.php'); - - if ($save and confirm_sesskey()) { - $course->maxbytes = 0; // We are ignoring course limits - $um = new upload_manager('userfile',false,false,$course,false,0); - $dir = "$basedir$wdir"; - if ($um->process_file_uploads($dir)) { - notify(get_string('uploadedfile')); - } - // um will take care of error reporting. - displaydir($wdir); - } else { - $upload_max_filesize = get_max_upload_file_size($CFG->maxbytes); - $filesize = display_size($upload_max_filesize); - - $struploadafile = get_string("uploadafile"); - $struploadthisfile = get_string("uploadthisfile"); - $strmaxsize = get_string("maxsize", "", $filesize); - $strcancel = get_string("cancel"); - - echo "

$struploadafile ($strmaxsize) --> $wdir

"; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " sesskey\" />"; - upload_print_form_fragment(1,array('userfile'),null,false,null,$upload_max_filesize,0,false); - echo "
"; - echo " "; - echo "
"; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - } - html_footer(); - break; - - case "delete": - if ($confirm and confirm_sesskey()) { - html_header($course, $wdir); - if (!empty($USER->filelist)) { - foreach ($USER->filelist as $file) { - $fullfile = $basedir.'/'.$file; - if (! fulldelete($fullfile)) { - echo "
Error: Could not delete: $fullfile"; - } - } - } - clearfilelist(); - displaydir($wdir); - html_footer(); - - } else { - html_header($course, $wdir); - - if (setfilelist($_POST)) { - notify(get_string('deletecheckwarning').':'); - print_simple_box_start("center"); - printfilelist($USER->filelist); - print_simple_box_end(); - echo "
"; - - require_once($CFG->dirroot.'/mod/resource/lib.php'); - $block = resource_delete_warning($course, $USER->filelist); - - if (empty($CFG->resource_blockdeletingfile) or $block == '') { - $optionsyes = array('id'=>$id, 'wdir'=>$wdir, 'action'=>'delete', 'confirm'=>1, 'sesskey'=>sesskey(), 'choose'=>$choose); - $optionsno = array('id'=>$id, 'wdir'=>$wdir, 'action'=>'cancel', 'choose'=>$choose); - notice_yesno (get_string('deletecheckfiles'), 'index.php', 'index.php', $optionsyes, $optionsno, 'post', 'get'); - } else { - - notify(get_string('warningblockingdelete', 'resource')); - $options = array('id'=>$id, 'wdir'=>$wdir, 'action'=>'cancel', 'choose'=>$choose); - print_continue("index.php?id=$id&wdir=$wdir&action=cancel&choose=$choose"); - } - } else { - displaydir($wdir); - } - html_footer(); - } - break; - - case "move": - html_header($course, $wdir); - if (($count = setfilelist($_POST)) and confirm_sesskey()) { - $USER->fileop = $action; - $USER->filesource = $wdir; - echo "

"; - print_string("selectednowmove", "moodle", $count); - echo "

"; - } - displaydir($wdir); - html_footer(); - break; - - case "paste": - html_header($course, $wdir); - if (isset($USER->fileop) and ($USER->fileop == "move") and confirm_sesskey()) { - foreach ($USER->filelist as $file) { - $shortfile = basename($file); - $oldfile = $basedir.'/'.$file; - $newfile = $basedir.$wdir."/".$shortfile; - if (!rename($oldfile, $newfile)) { - echo "

Error: $shortfile not moved

"; - } - } - } - clearfilelist(); - displaydir($wdir); - html_footer(); - break; - - case "rename": - if (($name != '') and confirm_sesskey()) { - html_header($course, $wdir); - $name = clean_filename($name); - if (file_exists($basedir.$wdir."/".$name)) { - echo "
Error: $name already exists!
"; - } else if (!rename($basedir.$wdir."/".$oldname, $basedir.$wdir."/".$name)) { - echo "

Error: could not rename $oldname to $name

"; - } else { - //file was renamed now update resources if needed - require_once($CFG->dirroot.'/mod/resource/lib.php'); - resource_renamefiles($course, $wdir, $oldname, $name); - } - displaydir($wdir); - - } else { - $strrename = get_string("rename"); - $strcancel = get_string("cancel"); - $strrenamefileto = get_string("renamefileto", "moodle", $file); - html_header($course, $wdir, "form.name"); - echo "

$strrenamefileto:

"; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo " sesskey\" />"; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; - } - html_footer(); - break; - - case "makedir": - if (($name != '') and confirm_sesskey()) { - html_header($course, $wdir); - $name = clean_filename($name); - if (file_exists("$basedir$wdir/$name")) { - echo "Error: $name already exists!"; - } else if (! make_upload_directory("$course->id$wdir/$name")) { - echo "Error: could not create $name"; - } - displaydir($wdir); - - } else { - $strcreate = get_string("create"); - $strcancel = get_string("cancel"); - $strcreatefolder = get_string("createfolder", "moodle", $wdir); - html_header($course, $wdir, "form.name"); - echo "

$strcreatefolder:

"; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo " sesskey\" />"; - echo " "; - echo "
"; - echo "
"; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; - } - html_footer(); - break; - - case "edit": - html_header($course, $wdir); - if (($text != '') and confirm_sesskey()) { - $fileptr = fopen($basedir.'/'.$file,"w"); - $text = preg_replace('/\x0D/', '', $text); // http://moodle.org/mod/forum/discuss.php?d=38860 - fputs($fileptr, $text); - fclose($fileptr); - displaydir($wdir); - - } else { - $streditfile = get_string("edit", "", "$file"); - $fileptr = fopen($basedir.'/'.$file, "r"); - $contents = fread($fileptr, filesize($basedir.'/'.$file)); - fclose($fileptr); - - if (mimeinfo("type", $file) == "text/html") { - $usehtmleditor = can_use_html_editor(); - } else { - $usehtmleditor = false; - } - $usehtmleditor = false; // Always keep it off for now - - print_heading("$streditfile"); - - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo " sesskey\" />"; - print_textarea($usehtmleditor, 25, 80, 680, 400, "text", $contents); - echo "
"; - echo " "; - echo ""; - echo ""; - echo ""; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; - - if ($usehtmleditor) { - use_html_editor(); - } - - - } - html_footer(); - break; + $error = ''; - case "zip": - if (($name != '') and confirm_sesskey()) { - html_header($course, $wdir); - $name = clean_filename($name); + $browser = get_file_browser(); - $files = array(); - foreach ($USER->filelist as $file) { - $files[] = "$basedir/$file"; - } + $file_info = $browser->get_file_info($context, $filearea, $itemid, $filepath, $filename); - if (!zip_files($files,"$basedir$wdir/$name")) { - print_error("zipfileserror", "error"); - } +/// process actions + if ($file_info and $file_info->is_directory() and $file_info->is_writable() and $newdirname !== '' and data_submitted() and confirm_sesskey()) { + if ($newdir_info = $file_info->create_directory($newdirname, $USER->id)) { + $params = $newdir_info->get_params_rawencoded(); + $params = implode('&', $params); + redirect("index.php?$params"); + } else { + $error = "Could not create new dir"; // TODO: localise + } + } - clearfilelist(); - displaydir($wdir); + if ($file_info and $file_info->is_directory() and $file_info->is_writable() and isset($_FILES['newfile']) and data_submitted() and confirm_sesskey()) { + $file = $_FILES['newfile']; + $newfilename = clean_param($file['name'], PARAM_FILE); + if (is_uploaded_file($_FILES['newfile']['tmp_name'])) { + try { + if ($newfile = $file_info->create_file_from_pathname($newfilename, $_FILES['newfile']['tmp_name'], $USER->id)) { + $params = $file_info->get_params_rawencoded(); + $params = implode('&', $params); + redirect("index.php?$params"); - } else { - html_header($course, $wdir, "form.name"); - - if (setfilelist($_POST)) { - echo "

".get_string("youareabouttocreatezip").":

"; - print_simple_box_start("center"); - printfilelist($USER->filelist); - print_simple_box_end(); - echo "
"; - echo "

".get_string("whattocallzip")."

"; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo " sesskey\" />"; - echo " "; - echo "
"; - echo ""; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; } else { - displaydir($wdir); - clearfilelist(); + $error = "Could not create upload file"; // TODO: localise } + } catch (file_exception $e) { + $error = "Exception: Could not create upload file"; // TODO: localise } - html_footer(); - break; - - case "unzip": - html_header($course, $wdir); - if (($file != '') and confirm_sesskey()) { - $strok = get_string("ok"); - $strunpacking = get_string("unpacking", "", $file); + } + } - echo "

$strunpacking:

"; + if ($file_info and $delete) { + if (!data_submitted() or !confirm_sesskey()) { + print_header(); + notify(get_string('deletecheckwarning').': '.$file_info->get_visible_name()); + $parent_info = $file_info->get_parent(); - $file = basename($file); + $optionsno = $parent_info->get_params(); + $optionsyes = $file_info->get_params(); + $optionsyes['delete'] = 1; + $optionsyes['sesskey'] = sesskey(); - if (!unzip_file("$basedir$wdir/$file")) { - print_error("unzipfileserror", "error"); - } - - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; - } else { - displaydir($wdir); - } - html_footer(); - break; - - case "listzip": - html_header($course, $wdir); - if (($file != '') and confirm_sesskey()) { - $strname = get_string("name"); - $strsize = get_string("size"); - $strmodified = get_string("modified"); - $strok = get_string("ok"); - $strlistfiles = get_string("listfiles", "", $file); - - echo "

$strlistfiles:

"; - $file = basename($file); - - include_once("$CFG->libdir/pclzip/pclzip.lib.php"); - $archive = new PclZip(cleardoubleslashes("$basedir$wdir/$file")); - if (!$list = $archive->listContent(cleardoubleslashes("$basedir$wdir"))) { - notify($archive->errorInfo(true)); + notice_yesno (get_string('deletecheckfiles'), 'index.php', 'index.php', $optionsyes, $optionsno, 'post', 'get'); + print_footer(); + die; + } - } else { - echo ""; - echo ""; - foreach ($list as $item) { - echo ""; - print_cell("left", s($item['filename']), 'name'); - if (! $item['folder']) { - print_cell("right", display_size($item['size']), 'size'); - } else { - echo ""; - } - $filedate = userdate($item['mtime'], get_string("strftimedatetime")); - print_cell("right", $filedate, 'date'); - echo ""; - } - echo "
$strname$strsize$strmodified
 
"; - } - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; - } else { - displaydir($wdir); + if ($parent_info = $file_info->get_parent() and $parent_info->is_writable()) { + if (!$file_info->delete()) { + $error = "Could not delete file!"; // TODO: localise } - html_footer(); - break; - - case "restore": - html_header($course, $wdir); - if (($file != '') and confirm_sesskey()) { - echo "

".get_string("youaregoingtorestorefrom").":

"; - print_simple_box_start("center"); - echo $file; - print_simple_box_end(); - echo "
"; - echo "

".get_string("areyousuretorestorethisinfo")."

"; - $restore_path = "$CFG->wwwroot/backup/restore.php"; - notice_yesno (get_string("areyousuretorestorethis"), - $restore_path."?id=".$id."&file=".cleardoubleslashes($id.$wdir."/".$file)."&method=manual", - "index.php?id=$id&wdir=$wdir&action=cancel"); - } else { - displaydir($wdir); - } - html_footer(); - break; - - case "cancel": - clearfilelist(); - - default: - html_header($course, $wdir); - displaydir($wdir); - html_footer(); - break; -} - - -/// FILE FUNCTIONS /////////////////////////////////////////////////////////// + $params = $parent_info->get_params_rawencoded(); + $params = implode('&', $params); + redirect("index.php?$params", $error); + } + } -function setfilelist($VARS) { - global $USER; +/// print dir listing + html_header($context, $file_info); - $USER->filelist = array (); - $USER->fileop = ""; + if ($error !== '') { + notify($error); + } - $count = 0; - foreach ($VARS as $key => $val) { - if (substr($key,0,4) == "file") { - $count++; - $val = rawurldecode($val); - $USER->filelist[] = clean_param($val, PARAM_PATH); - } + displaydir($file_info); + + if ($file_info and $file_info->is_directory() and $file_info->is_writable()) { + echo '
'; + + echo '
'; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo '
'; + + echo '
'; + + echo '
'; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo '
'; } - return $count; -} -function clearfilelist() { - global $USER; + html_footer(); - $USER->filelist = array (); - $USER->fileop = ""; +/// UI functions ///////////////////////// + +function html_footer() { + echo '
'; + print_footer(); } +function html_header($context, $file_info){ + global $CFG, $SITE; -function printfilelist($filelist) { - global $CFG, $basedir; + $navlinks = array(); + $strfiles = get_string("files"); - $strfolder = get_string("folder"); - $strfile = get_string("file"); + $navlinks[] = array('name' => $strfiles, 'link' => null, 'type' => 'misc'); - foreach ($filelist as $file) { - if (is_dir($basedir.'/'.$file)) { - echo ''. $strfolder .' '. htmlspecialchars($file) .'
'; - $subfilelist = array(); - $currdir = opendir($basedir.'/'.$file); - while (false !== ($subfile = readdir($currdir))) { - if ($subfile <> ".." && $subfile <> ".") { - $subfilelist[] = $file."/".$subfile; - } - } - printfilelist($subfilelist); + $navigation = build_navigation($navlinks); + print_header("$SITE->shortname: $strfiles", '', $navigation); - } else { - $icon = mimeinfo("icon", $file); - echo ''. $strfile .' '. htmlspecialchars($file) .'
'; - } - } + echo ""; + echo ""; + echo "'; } -function displaydir ($wdir) { -// $wdir == / or /a or /a/b/c/d etc +function displaydir($file_info) { + global $CFG; + + $children = $file_info->get_children(); + $parent_info = $file_info->get_parent(); + + $strname = get_string('name'); + $strsize = get_string('size'); + $strmodified = get_string('modified'); + $strfolder = get_string('folder'); + $strfile = get_string('file'); + $strdownload = get_string('download'); + $strdelete = get_string('delete'); + $straction = get_string('action'); + + $path = array(); + $params = $file_info->get_params_rawencoded(); + $params = implode('&', $params); + $path[] = $file_info->get_visible_name(); + + $level = $parent_info; + while ($level) { + $params = $level->get_params_rawencoded(); + $params = implode('&', $params); + $path[] = ''.$level->get_visible_name().''; + $level = $level->get_parent(); + } - global $basedir; - global $id; - global $USER, $CFG; - global $choose; + $path = array_reverse($path); - $fullpath = $basedir.$wdir; - $dirlist = array(); + $path = implode (' / ', $path); + echo $path. ' /'; - $directory = opendir($fullpath); // Find all files - while (false !== ($file = readdir($directory))) { - if ($file == "." || $file == "..") { - continue; - } - - if (is_dir($fullpath."/".$file)) { - $dirlist[] = $file; - } else { - $filelist[] = $file; - } - } - closedir($directory); - - $strname = get_string("name"); - $strsize = get_string("size"); - $strmodified = get_string("modified"); - $straction = get_string("action"); - $strmakeafolder = get_string("makeafolder"); - $struploadafile = get_string("uploadafile"); - $strselectall = get_string("selectall"); - $strselectnone = get_string("deselectall"); - $strwithchosenfiles = get_string("withchosenfiles"); - $strmovetoanotherfolder = get_string("movetoanotherfolder"); - $strmovefilestohere = get_string("movefilestohere"); - $strdeletecompletely = get_string("deletecompletely"); - $strcreateziparchive = get_string("createziparchive"); - $strrename = get_string("rename"); - $stredit = get_string("edit"); - $strunzip = get_string("unzip"); - $strlist = get_string("list"); - $strrestore= get_string("restore"); - $strchoose = get_string("choose"); - $strfolder = get_string("folder"); - $strfile = get_string("file"); - - - echo ""; echo "
"; - echo ''; - // echo "
"; echo "
"; - echo "
"; } +/// FILE FUNCTIONS /////////////////////////////////////////////////////////// function print_cell($alignment='center', $text=' ', $class='') { if ($class) { @@ -692,60 +182,42 @@ function print_cell($alignment='center', $text=' ', $class='') { echo ''.$text.'
"; + echo "
"; echo ""; echo ""; echo ""; @@ -754,175 +226,86 @@ function displaydir ($wdir) { echo ""; echo "\n"; - if ($wdir != "/") { - $dirlist[] = '..'; - } - - $count = 0; - - if (!empty($dirlist)) { - asort($dirlist); - foreach ($dirlist as $dir) { - echo ""; + $parentwritable = $file_info->is_writable(); - if ($dir == '..') { - $fileurl = rawurlencode(dirname($wdir)); - print_cell(); - // alt attribute intentionally empty to prevent repetition in screen reader - print_cell('left', ' '.get_string('parentfolder').'', 'name'); - print_cell(); - print_cell(); - print_cell(); + if ($parent_info) { + $params = $parent_info->get_params_rawencoded(); + $params = implode('&', $params); - } else { - $count++; - $filename = $fullpath."/".$dir; - $fileurl = rawurlencode($wdir."/".$dir); - $filesafe = rawurlencode($dir); - $filesize = display_size(get_directory_size("$fullpath/$dir")); - $filedate = userdate(filemtime($filename), "%d %b %Y, %I:%M %p"); - print_cell("center", "", 'checkbox'); - print_cell("left", "pixpath/f/folder.gif\" class=\"icon\" alt=\"$strfolder\" /> ".htmlspecialchars($dir)."", 'name'); - print_cell("right", $filesize, 'size'); - print_cell("right", $filedate, 'date'); - print_cell("right", "$strrename", 'commands'); - } + echo ""; + print_cell(); + print_cell('left', ' '.get_string('parentfolder').'', 'name'); + print_cell(); + print_cell(); + print_cell(); - echo ""; - } + echo ""; } + if ($children) { + foreach ($children as $child_info) { + $filename = $child_info->get_visible_name(); + $filesize = $child_info->get_filesize(); + $filesize = $filesize ? display_size($filesize) : ''; + $filedate = $child_info->get_timemodified(); + $filedate = $filedate ? userdate($filedate) : ''; - if (!empty($filelist)) { - asort($filelist); - foreach ($filelist as $file) { - - $icon = mimeinfo("icon", $file); + $mimetype = $child_info->get_mimetype(); - $count++; - $filename = $fullpath."/".$file; - $fileurl = trim($wdir, "/")."/$file"; - $filesafe = rawurlencode($file); - $fileurlsafe = rawurlencode($fileurl); - $filedate = userdate(filemtime($filename), "%d %b %Y, %I:%M %p"); + $params = $child_info->get_params_rawencoded(); + $params = implode('&', $params); - $selectfile = trim($fileurl, "/"); + if ($child_info->is_directory()) { - echo ""; + echo ""; + print_cell(); + print_cell("left", "pixpath/f/folder.gif\" class=\"icon\" alt=\"$strfolder\" /> ".s($filename)."", 'name'); + print_cell("right", $filesize, 'size'); + print_cell("right", $filedate, 'date'); + if ($parentwritable) { + print_cell("right", "pixpath/t/delete.gif\" class=\"iconsmall\" alt=\"$strdelete\" />", 'command'); + } else { + print_cell(); + } + echo ""; - print_cell("center", "", 'checkbox'); - echo ""; + $icon = mimeinfo_from_type("icon", $mimetype); + if ($downloadurl = $child_info->get_url(true)) { + $downloadurl = " pixpath/t/down.gif\" class=\"iconsmall\" alt=\"$strdownload\" />"; + } else { + $downloadurl = ''; + } - $file_size = filesize($filename); - print_cell("right", display_size($file_size), 'size'); - print_cell("right", $filedate, 'date'); + if ($viewurl = $child_info->get_url()) { + $viewurl = " ".link_to_popup_window ($viewurl, "display", + "pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strfile\" /> ", + 480, 640, null, null, true); + } else { + $viewurl = ''; + } - if ($choose) { - $edittext = "$strchoose "; - } else { - $edittext = ''; - } - if ($icon == "text.gif" || $icon == "html.gif") { - $edittext .= "$stredit"; - } else if ($icon == "zip.gif") { - $edittext .= "sesskey&choose=$choose\">$strunzip "; - $edittext .= "sesskey&choose=$choose\">$strlist "; - if (!empty($CFG->backup_version) and has_capability('moodle/site:restore', get_context_instance(CONTEXT_COURSE, $id))) { - $edittext .= "sesskey&choose=$choose\">$strrestore "; + echo ""; + print_cell(); + print_cell("left", "pixpath/f/$icon\" class=\"icon\" alt=\"$strfile\" /> ".s($filename).$downloadurl.$viewurl, 'name'); + print_cell("right", $filesize, 'size'); + print_cell("right", $filedate, 'date'); + if ($parentwritable) { + print_cell("right", "pixpath/t/delete.gif\" class=\"iconsmall\" alt=\"$strdelete\" />", 'command'); + } else { + print_cell(); } + echo ""; } - - print_cell("right", "$edittext $strrename", 'commands'); - - echo ""; } } - echo "
$strname$straction
"; + } else { - $ffurl = get_file_url($id.'/'.$fileurl); - link_to_popup_window ($ffurl, "display", - "pixpath/f/$icon\" class=\"icon\" alt=\"$strfile\" /> ".htmlspecialchars($file), - 480, 640); - echo "
"; - echo "
"; - //echo "
"; - - echo ""; - echo "
"; - echo ""; - echo ''; - echo " "; - echo "sesskey\" />"; - $options = array ( - "move" => "$strmovetoanotherfolder", - "delete" => "$strdeletecompletely", - "zip" => "$strcreateziparchive" - ); - if (!empty($count)) { - - choose_from_menu ($options, "action", "", "$strwithchosenfiles...", "javascript:getElementById('dirform').submit()"); - echo '
'; - echo ''; - echo ''; - echo '
'; - } - echo "
"; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; echo "
"; - if (!empty($USER->fileop) and ($USER->fileop == "move") and ($USER->filesource <> $wdir)) { - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " sesskey\" />"; - echo " "; - echo "
"; - echo "
"; - } - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; - echo "
"; //dummy form - alignment only - echo "
"; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; - echo "
"; - echo "
"; - echo ' '; - echo " "; - echo " "; - echo " "; - echo " "; - echo "
"; - echo "
"; - echo "
"; + echo ""; echo "
"; - //echo "
"; } diff --git a/group/group.php b/group/group.php index b75326ae7d..27eecbd621 100644 --- a/group/group.php +++ b/group/group.php @@ -77,11 +77,11 @@ if ($editform->is_cancelled()) { } elseif ($data = $editform->get_data()) { if ($data->id) { - if (!groups_update_group($data, $editform->_upload_manager)) { + if (!groups_update_group($data, $editform)) { print_error('cannotupdategroup'); } } else { - if (!$id = groups_create_group($data, $editform->_upload_manager)) { + if (!$id = groups_create_group($data, $editform)) { print_error('cannotcreategroup'); } $returnurl = $CFG->wwwroot.'/group/index.php?id='.$course->id.'&group='.$id; diff --git a/group/group_form.php b/group/group_form.php index 9a960503e7..14c568507f 100644 --- a/group/group_form.php +++ b/group/group_form.php @@ -28,7 +28,6 @@ class group_form extends moodleform { $options = array(get_string('no'), get_string('yes')); $mform->addElement('select', 'hidepicture', get_string('hidepicture'), $options); - $this->set_upload_manager(new upload_manager('imagefile', false, false, null, false, 0, true, true, false)); $mform->addElement('file', 'imagefile', get_string('newpicture', 'group')); $mform->setHelpButton('imagefile', array ('picture', get_string('helppicture')), true); } diff --git a/group/lib.php b/group/lib.php index af3411fb8a..23e359820e 100644 --- a/group/lib.php +++ b/group/lib.php @@ -90,7 +90,7 @@ function groups_remove_member($groupid, $userid) { * @param object $um upload manager with group picture * @return id of group or false if error */ -function groups_create_group($data, $um=false) { +function groups_create_group($data, $editform=false) { global $CFG, $DB; require_once("$CFG->libdir/gdlib.php"); @@ -101,9 +101,9 @@ function groups_create_group($data, $um=false) { if ($id) { $data->id = $id; - if ($um) { + if ($editform) { //update image - if (save_profile_image($id, $um, 'groups')) { + if (save_profile_image($id, $editform, 'groups')) { $DB->set_field('groups', 'picture', 1, array('id'=>$id)); } $data->picture = 1; @@ -144,7 +144,7 @@ function groups_create_grouping($data) { * @param object $um upload manager with group picture * @return boolean success */ -function groups_update_group($data, $um=false) { +function groups_update_group($data, $editform=false) { global $CFG, $DB; require_once("$CFG->libdir/gdlib.php"); @@ -153,9 +153,9 @@ function groups_update_group($data, $um=false) { $result = $DB->update_record('groups', $data); if ($result) { - if ($um) { + if ($editform) { //update image - if (save_profile_image($data->id, $um, 'groups')) { + if (save_profile_image($data->id, $editform, 'groups')) { $DB->set_field('groups', 'picture', 1, array('id'=>$data->id)); $data->picture = 1; } diff --git a/lang/en_utf8/error.php b/lang/en_utf8/error.php index 70240f6333..27dc40ddf3 100644 --- a/lang/en_utf8/error.php +++ b/lang/en_utf8/error.php @@ -223,6 +223,7 @@ $string['guestnocomment'] = 'Guests are not allowed to post comments!'; $string['guestnoeditprofile'] = 'The guest user cannot edit their profile'; $string['guestnorate'] = 'Guests are not allowed to rate entries'; $string['guestnoeditprofileother'] = 'The guest user profile cannot be edited'; +$string['hashpoolproblem'] = 'Incorrect pool file content $a.'; $string['invalidaction'] = 'Invalid action parameter'; $string['invalidarguments'] = 'No valid arguments supplied'; $string['invalidargorconf'] = 'No valid arguments supplied or incorrect server configuration'; @@ -290,6 +291,10 @@ $string['listcantmoveup'] = 'Failed to move the item up, as it is the first of i $string['listcantmovedown'] = 'Failed to move item down, as it is the last of it\'s peers'; $string['listcantmoveleft'] = 'Failed to move item left, as it has no parent'; $string['listcantmoveright'] = 'Failed to move item right, as there is no peer to make it a child of. Move it below another peer and then you can move it right.'; +$string['localfilecannotcreatefiledirs'] = 'Can not create local file pool directories, please verify permissions in dataroot.'; +$string['localfilecannotread'] = 'Can not read file, either file does not exist or there are permission problems'; +$string['localfilenotcreated'] = 'Can not create file \"$a->contextid/$a->filearea/$a->itemid/$a->filepath/$a->filename\"'; +$string['localfileproblem'] = 'Unknown exception related to local files ($a)'; $string['loginasonecourse'] = 'You cannot enter this course.
You have to terminate the \"Login as\" session before entering any other course.'; $string['loginasnoenrol'] = 'You cannot use enrol or unenrol when in course \"Login as\" session'; $string['logfilenotavailable'] = 'Logs not available'; diff --git a/lib/db/access.php b/lib/db/access.php index 95c4c530b0..aa12f54ff7 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -168,6 +168,34 @@ $moodle_capabilities = array( ) ), + 'moodle/site:backupdownload' => array( + + 'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ), + + 'clonepermissionsfrom' => 'moodle/site:backup' + ), + + 'moodle/site:backupupload' => array( + + 'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ), + + 'clonepermissionsfrom' => 'moodle/site:restore' + ), + 'moodle/site:restore' => array( 'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS, diff --git a/lib/db/install.xml b/lib/db/install.xml index 8ee33f227c..0154ff5aad 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1780,7 +1780,7 @@ - +
@@ -1796,6 +1796,46 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 870fcba089..d3cfa033fd 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -264,13 +264,26 @@ function xmldb_main_upgrade($oldversion=0) { } if ($result && $oldversion < 2008072400) { - /// Create the database tables for message_processors + /// Create the database tables for message_processors and message_providers + $table = new xmldb_table('message_providers'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null); + $table->add_field('modulename', XMLDB_TYPE_CHAR, '166', null, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('modulefile', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + $table = new xmldb_table('message_processors'); $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null); $table->add_field('name', XMLDB_TYPE_CHAR, '166', null, XMLDB_NOTNULL, null, null, null, null); $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); $dbman->create_table($table); + + $provider = new object(); + $provider->modulename = 'moodle'; + $provider->modulefile = 'index.php'; + $DB->insert_record('message_providers', $provider); + /// delete old and create new fields $table = new xmldb_table('message'); $field = new xmldb_field('messagetype'); @@ -465,12 +478,78 @@ function xmldb_main_upgrade($oldversion=0) { upgrade_main_savepoint($result, 2008073104); } - -/* - * TODO: - * drop adodb_logsql table and create a new general sql log table - * - */ + + if ($result && $oldversion < 2008073111) { + /// Define table files to be created + $table = new xmldb_table('files'); + + /// Adding fields to table files + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null); + $table->add_field('contenthash', XMLDB_TYPE_CHAR, '40', null, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('pathnamehash', XMLDB_TYPE_CHAR, '40', null, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('filearea', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('filepath', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('filename', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null); + $table->add_field('filesize', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('mimetype', XMLDB_TYPE_CHAR, '100', null, null, null, null, null, null); + $table->add_field('status', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0'); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null); + + /// Adding keys to table files + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('contextid', XMLDB_KEY_FOREIGN, array('contextid'), 'context', array('id')); + $table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id')); + + /// Adding indexes to table files + $table->add_index('filearea-contextid-itemid', XMLDB_INDEX_NOTUNIQUE, array('filearea', 'contextid', 'itemid')); + $table->add_index('contenthash', XMLDB_INDEX_NOTUNIQUE, array('contenthash')); + $table->add_index('pathnamehash', XMLDB_INDEX_UNIQUE, array('pathnamehash')); + + /// Conditionally launch create table for files + $dbman->create_table($table); + + /// Main savepoint reached + upgrade_main_savepoint($result, 2008073111); + } + + if ($result && $oldversion < 2008073112) { + /// Define table files_cleanup to be created + $table = new xmldb_table('files_cleanup'); + + /// Adding fields to table files_cleanup + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null); + $table->add_field('contenthash', XMLDB_TYPE_CHAR, '40', null, XMLDB_NOTNULL, null, null, null, null); + + /// Adding keys to table files_cleanup + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + /// Adding indexes to table files_cleanup + $table->add_index('contenthash', XMLDB_INDEX_UNIQUE, array('contenthash')); + + /// Conditionally launch create table for files_cleanup + $dbman->create_table($table); + + /// Main savepoint reached + upgrade_main_savepoint($result, 2008073112); + } + + if ($result && $oldversion < 2008073113) { + /// move all course, backup and other files to new filepool based storage + upgrade_migrate_files_courses(); + /// Main savepoint reached + upgrade_main_savepoint($result, 2008073113); + } + + if ($result && $oldversion < 2008073114) { + /// move all course, backup and other files to new filepool based storage + upgrade_migrate_files_blog(); + /// Main savepoint reached + upgrade_main_savepoint($result, 2008073114); + } return $result; } diff --git a/lib/db/upgradelib.php b/lib/db/upgradelib.php index 26d653770e..cf08fead39 100644 --- a/lib/db/upgradelib.php +++ b/lib/db/upgradelib.php @@ -55,4 +55,169 @@ function upgrade_fix_category_depths() { } } -?> +/** + * Moves all course files except the moddata to new file storage + * + * Unfortunately this function uses core file related functions - it might be necessary to tweak it if something changes there :-( + */ +function upgrade_migrate_files_courses() { + global $DB, $CFG; + require_once($CFG->libdir.'/filelib.php'); + + $count = $DB->count_records('course'); + $pbar = new progress_bar('migratecoursefiles', 500, true); + + $rs = $DB->get_recordset('course'); + $olddebug = $DB->get_debug(); + $DB->set_debug(false); // lower debug level, there might be many files + $i = 0; + foreach ($rs as $course) { + $i++; + $context = get_context_instance(CONTEXT_COURSE, $course->id); + upgrade_migrate_files_course($context, '/', true); + $pbar->update($i, $count, "Migrated course files - course $i/$count."); + } + $DB->set_debug($olddebug); // reset debug level + $rs->close(); + + return true; +} + +/** + * Internal function - do not use directly + */ +function upgrade_migrate_files_course($context, $path, $delete) { + global $CFG; + + $fullpathname = $CFG->dataroot.'/'.$context->instanceid.$path; + if (!file_exists($fullpathname)) { + return; + } + $items = new DirectoryIterator($fullpathname); + $fs = get_file_storage(); + + foreach ($items as $item) { + if ($item->isDot()) { + continue; + } + + if ($item->isLink()) { + // do not delete symbolic links or its children + $delete_this = false; + } else { + $delete_this = $delete; + } + + if (strpos($path, '/backupdata/') === 0) { + $filearea = 'course_backup'; + $filepath = substr($path, strlen('/backupdata')); + } else { + $filearea = 'course_content'; + $filepath = $path; + } + + if ($item->isFile()) { + if (!$item->isReadable()) { + notify(" File not readable, skipping: ".$fullpathname.$item->getFilename()); + continue; + } + + $filepath = clean_param($filepath, PARAM_PATH); + $filename = clean_param($item->getFilename(), PARAM_FILE); + + if ($filename === '') { + continue; + } + + if (!$fs->file_exists($context->id, $filearea, '0', $filepath, $filename)) { + $file_record = array('contextid'=>$context->id, 'filearea'=>$filearea, 'itemid'=>0, 'filepath'=>$filepath, 'filename'=>$filename, + 'timecreated'=>$item->getCTime(), 'timemodified'=>$item->getMTime()); + if ($fs->create_file_from_pathname($file_record, $fullpathname.$item->getFilename())) { + if ($delete_this) { + @unlink($fullpathname.$item->getFilename()); + } + } + } + + } else { + if ($path == '/' and $item->getFilename() == 'moddata') { + continue; // modules are responsible + } + + $filepath = clean_param($filepath.$item->getFilename().'/', PARAM_PATH); + if ($filepath !== '/backupdata/') { + $fs->create_directory($context->id, $filearea, 0, $filepath); + } + + //migrate recursively all subdirectories + upgrade_migrate_files_course($context, $path.$item->getFilename().'/', $delete_this); + if ($delete_this) { + // delete dir if empty + @rmdir($fullpathname.$item->getFilename()); + } + } + } + unset($items); //release file handles +} + +/** + * Moves all block attachments + * + * Unfortunately this function uses core file related functions - it might be necessary to tweak it if something changes there :-( + */ +function upgrade_migrate_files_blog() { + global $DB, $CFG; + + $fs = get_file_storage(); + + $count = $DB->count_records_select('post', "module='blog' AND attachment IS NOT NULL AND attachment <> 1"); + + if ($rs = $DB->get_recordset_select('post', "module='blog' AND attachment IS NOT NULL AND attachment <> 1")) { + + $pbar = new progress_bar('migrateblogfiles', 500, true); + + $olddebug = $DB->get_debug(); + $DB->set_debug(false); // lower debug level, there might be many files + $i = 0; + foreach ($rs as $entry) { + $i++; + $pathname = "$CFG->dataroot/blog/attachments/$entry->id/$entry->attachment"; + if (!file_exists($pathname)) { + // hmm, we could set atatchment NULL here, but it would break badly in concurrent ugprades, disabling for now + //$entry->attachment = NULL; + //$DB->update_record('post', $entry); + continue; + } + + $filename = clean_param($entry->attachment, PARAM_FILE); + if ($filename === '') { + // weird file name, ignore it + $entry->attachment = NULL; + $DB->update_record('post', $entry); + continue; + } + + if (!is_readable($pathname)) { + notify(" File not readable, skipping: ".$pathname); + continue; + } + + if (!$fs->file_exists(SYSCONTEXTID, 'blog', $entry->id, '/', $filename)) { + $file_record = array('contextid'=>SYSCONTEXTID, 'filearea'=>'blog', 'itemid'=>$entry->id, 'filepath'=>'/', 'filename'=>$filename, + 'timecreated'=>filectime($pathname), 'timemodified'=>filemtime($pathname), 'userid'=>$post->userid); + $fs->create_file_from_pathname($file_record, $pathname); + } + @unlink($pathname); + @rmdir("$CFG->dataroot/blog/attachments/$entry->id/"); + + $entry->attachment = 1; // file name not needed there anymore + $DB->update_record('post', $entry); + $pbar->update($i, $count, "Migrated blog attachments - $i/$count."); + } + $DB->set_debug($olddebug); // reset debug level + $rs->close(); + } + + @rmdir("$CFG->dataroot/blog/attachments/"); + @rmdir("$CFG->dataroot/blog/"); +} diff --git a/lib/editor/htmlarea/coursefiles.php b/lib/editor/htmlarea/coursefiles.php index f1ea8f28f4..bb9d928886 100644 --- a/lib/editor/htmlarea/coursefiles.php +++ b/lib/editor/htmlarea/coursefiles.php @@ -14,6 +14,8 @@ require("../../../config.php"); require_once($CFG->libdir.'/filelib.php'); +error('Not reimplemented yet, sorry'); + $id = required_param('id', PARAM_INT); $file = optional_param('file', '', PARAM_PATH); $wdir = optional_param('wdir', '', PARAM_PATH); diff --git a/lib/editor/tinymce/coursefiles.php b/lib/editor/tinymce/coursefiles.php index d8dc13265c..1faf836109 100644 --- a/lib/editor/tinymce/coursefiles.php +++ b/lib/editor/tinymce/coursefiles.php @@ -14,6 +14,8 @@ require("../../../config.php"); require_once($CFG->libdir.'/filelib.php'); +error('Not reimplemented yet, sorry'); + $id = required_param('id', PARAM_INT); $file = optional_param('file', '', PARAM_PATH); $wdir = optional_param('wdir', '', PARAM_PATH); diff --git a/lib/file/file_browser.php b/lib/file/file_browser.php new file mode 100644 index 0000000000..62d405c60f --- /dev/null +++ b/lib/file/file_browser.php @@ -0,0 +1,293 @@ +libdir/file/file_info.php"); +require_once("$CFG->libdir/file/file_info_stored.php"); +require_once("$CFG->libdir/file/file_info_system.php"); +require_once("$CFG->libdir/file/file_info_user.php"); +require_once("$CFG->libdir/file/file_info_coursecat.php"); +require_once("$CFG->libdir/file/file_info_course.php"); +require_once("$CFG->libdir/file/file_info_coursefile.php"); + +/** + * Main interface for browsing of file tree (local files, areas, virtual files, etc.). + */ +class file_browser { + + /** + * Looks up file_info object + * @param object $context + * @param string $filearea + * @param int $itemid + * @param string $filepath + * @param string $filename + * @return object file_info object or null if not found or access not allowed + */ + public function get_file_info($context, $filearea=null, $itemid=null, $filepath=null, $filename=null) { + global $USER, $CFG, $DB; + + $fs = get_file_storage(); + + if ($context->contextlevel == CONTEXT_SYSTEM) { + if (is_null($filearea)) { + return new file_info_system($this); + } + //TODO: question files browsing + + } else if ($context->contextlevel == CONTEXT_USER) { + // access control: only own files + if ($context->instanceid != $USER->id) { + return null; + } + + if (!is_null($filearea) and !in_array($filearea, array('user_private', 'user_draft'))) { + // file area does not exist, sorry + return null; + } + + if (is_null($filearea)) { + return new file_info_user($this, $context); + } else { + if ($filearea == 'user_private') { + if (is_null($itemid)) { + return new file_info_user($this, $context); + } + $filepath = is_null($filepath) ? '/' : $filepath; + $filename = is_null($filename) ? '.' : $filename; + + if (!$localfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) { + if ($filepath === '/' and $filename === '.') { + $localfile = $fs->create_directory($context->id, $filearea, 0, $filepath, $USER->id); + } else { + // not found + return null; + } + } + $urlbase = $CFG->wwwroot.'/userfile.php'; + // TODO: localise + return new file_info_stored($this, $context, $localfile, $urlbase, 'Personal files', false, true, true); + + } else if ($filearea == 'user_draft') { + if (empty($itemid)) { + return new file_info_user($this, $context); + } + $urlbase = $CFG->wwwroot.'/draftfile.php'; + if (!$localfile = $fs->get_file($context->id, $filearea, $itemid, $filepath, $filename)) { + return null; + } + //something must create the top most directory + // TODO: localise + return new file_info_stored($this, $context, $localfile, $urlbase, 'Draft file area', true, true, true); + } + } + + } else if ($context->contextlevel == CONTEXT_COURSECAT) { + if (!$category = $DB->get_record('course_categories', array('id'=>$context->instanceid))) { + return null; + } + + if (!$category->visible and !has_capability('moodle/course:viewhiddencourses', $context)) { + return null; + } + + if (!is_null($filearea) and !in_array($filearea, array('coursecat_intro'))) { + // file area does not exist, sorry + $filearea = null; + } + + if (is_null($filearea) or is_null($itemid)) { + return new file_info_coursecat($this, $context, $category); + + } else { + if ($filearea == 'coursecat_intro') { + if (!has_capability('moodle/course:update', $context)) { + return null; + } + + $filepath = is_null($filepath) ? '/' : $filepath; + $filename = is_null($filename) ? '.' : $filename; + + $urlbase = $CFG->wwwroot.'/pluginfile.php'; + if (!$localfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) { + if ($filepath === '/' and $filename === '.') { + $localfile = $fs->create_directory($context->id, $filearea, 0, $filepath); + } else { + // not found + return null; + } + } + // TODO: localise + return new file_info_stored($this, $context, $localfile, $urlbase, 'Category introduction files', false, true, true); + + } + } + + } else if ($context->contextlevel == CONTEXT_COURSE) { + if (!$course = $DB->get_record('course', array('id'=>$context->instanceid))) { + return null; + } + + if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) { + return null; + } + + if (!is_null($filearea) and !in_array($filearea, array('course_intro', 'course_content', 'course_backup'))) { + // file area does not exist, sorry + $filearea = null; + } + + $filepath = is_null($filepath) ? '/' : $filepath; + $filename = is_null($filename) ? '.' : $filename; + + if (is_null($filearea) or is_null($itemid)) { + return new file_info_course($this, $context, $course); + + } else { + if ($filearea === 'course_intro') { + if (!has_capability('moodle/course:update', $context)) { + return null; + } + + $urlbase = $CFG->wwwroot.'/pluginfile.php'; + if (!$localfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) { + if ($filepath === '/' and $filename === '.') { + $localfile = $fs->create_directory($context->id, $filearea, 0, $filepath); + } else { + // not found + return null; + } + } + // TODO: localise + return new file_info_stored($this, $context, $localfile, $urlbase, 'Course introduction files', false, true, true); + + } else if ($filearea == 'course_backup') { + if (!has_capability('moodle/site:backup', $context) and !has_capability('moodle/site:restore', $context)) { + return null; + } + + $urlbase = $CFG->wwwroot.'/pluginfile.php'; + if (!$localfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) { + if ($filepath === '/' and $filename === '.') { + $localfile = $fs->create_directory($context->id, $filearea, 0, $filepath); + } else { + // not found + return null; + } + } + + $downloadable = has_capability('moodle/site:backupdownload', $context); + $uploadable = has_capability('moodle/site:backupupload', $context); + // TODO: localise + return new file_info_stored($this, $context, $localfile, $urlbase, 'Backup files', false, $downloadable, $uploadable); + + } else if ($filearea == 'course_content') { + if (!has_capability('moodle/course:managefiles', $context)) { + return null; + } + + if (!$localfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) { + if ($filepath === '/' and $filename === '.') { + $localfile = $fs->create_directory($context->id, $filearea, 0, $filepath); + } else { + // not found + return null; + } + } + + return new file_info_coursefile($this, $context, $localfile); + } + } + + } else if ($context->contextlevel == CONTEXT_MODULE) { + //TODO + } + + return null; + } + + /** + * Returns content of local directory + */ + public function build_stored_file_children($context, $filearea, $itemid, $filepath, $urlbase, $areavisiblename, $itemidused, $readaccess, $writeaccess) { + global $DB; + + $dirs = array(); + $files = array(); + $fs = get_file_storage(); + $level = substr_count($filepath, '/'); + + // TODO: this should be improved ;-) + $localfiles = $fs->get_area_files($context->id, $filearea, $itemid, "filepath, filename"); + foreach ($localfiles as $file) { + $name = $file->get_filename(); + $path = $file->get_filepath(); + $l = substr_count($path, '/'); + + if ($filepath === $path) { + if ($name !== '.') { + $files[] = new file_info_stored($this, $context, $file, $urlbase, $areavisiblename, $itemidused, $readaccess, $writeaccess); + } + } else if ($level == $l-1 and $name === '.' and strpos($path, $filepath) === 0) { + $dirs[] = new file_info_stored($this, $context, $file, $urlbase, $areavisiblename, $itemidused, $readaccess, $writeaccess); + } + } + + return array_merge($dirs, $files); + } + + /** + * Returns content of coursefiles directory + */ + public function build_coursefile_children($context, $filepath) { + global $DB; + + $dirs = array(); + $files = array(); + $fs = get_file_storage(); + $level = substr_count($filepath, '/'); + + // TODO: this should be improved ;-) + $localfiles = $fs->get_area_files($context->id, 'course_content', 0, "filepath, filename"); + foreach ($localfiles as $file) { + $name = $file->get_filename(); + $path = $file->get_filepath(); + $l = substr_count($path, '/'); + + if ($filepath === $path) { + if ($name !== '.') { + $files[] = new file_info_coursefile($this, $context, $file); + } + } else if ($level == $l-1 and $name === '.' and strpos($path, $filepath) === 0) { + $dirs[] = new file_info_coursefile($this, $context, $file); + } + } + + return array_merge($dirs, $files); + } + + public function encodepath($urlbase, $path, $forcedownload=false, $https=false) { + global $CFG; + + if ($CFG->slasharguments) { + $parts = explode('/', $path); + $parts = array_map('rawurlencode', $parts); + $path = implode('/', $parts); + $return = $urlbase.$path; + if ($forcedownload) { + $return .= '?forcedownload=1'; + } + } else { + $path = rawurlencode($path); + $return = $urlbase.'?file='.$path; + if ($forcedownload) { + $return .= '&forcedownload=1'; + } + } + + if ($https) { + $return = str_replace('http://', 'https://', $return); + } + + return $return; + } + +} diff --git a/lib/file/file_exceptions.php b/lib/file/file_exceptions.php new file mode 100644 index 0000000000..ab58bb22f8 --- /dev/null +++ b/lib/file/file_exceptions.php @@ -0,0 +1,43 @@ +contextid = $contextid; + $a->filearea = $filearea; + $a->itemid = $itemid; + $a->filepath = $filepath; + $a->filename = $filename; + parent::__construct('localfilenotcreated', $a, $debuginfo); + } +} + +/** + * Table does not exist problem exception + */ +class file_access_exception extends file_exception { + function __construct($debuginfo=null) { + parent::__construct('nopermissions', NULL, $debuginfo); + } +} + +/** + * Hash file content problem + */ +class file_pool_content_exception extends file_exception { + function __construct($contenthash, $debuginfo=null) { + parent::__construct('hashpoolproblem', $contenthash, $debuginfo); + } +} diff --git a/lib/file/file_info.php b/lib/file/file_info.php new file mode 100644 index 0000000000..495fe2c326 --- /dev/null +++ b/lib/file/file_info.php @@ -0,0 +1,89 @@ +browser = $browser; + $this->context = $context; + } + + public abstract function get_params(); + public abstract function get_visible_name(); + public abstract function is_directory(); + public abstract function get_children(); + public abstract function get_parent(); + + + public function get_params_rawencoded() { + $params = $this->get_params(); + $encoded = array(); + $encoded[] = 'contextid='.$params['contextid']; + $encoded[] = 'filearea='.$params['filearea']; + $encoded[] = 'itemid='.(is_null($params['itemid']) ? -1 : $params['itemid']); + $encoded[] = 'filepath='.(is_null($params['filepath']) ? '' : rawurlencode($params['filepath'])); + $encoded[] = 'filename='.((is_null($params['filename']) or $params['filename'] === '.') ? '' : rawurlencode($params['filename'])); + + return $encoded; + } + + + + public function get_url($forcedownload=false, $https=false) { + return null; + } + + public function is_readable() { + return true; + } + + public function is_writable() { + return true; + } + + public function get_filesize() { + return null; + } + + public function get_mimetype() { + // TODO: add some custom mime icons for courses, categories?? + return null; + } + + public function get_timecreated() { + return null; + } + + public function get_timemodified() { + return null; + } + + public function create_directory($newdirname, $userid=null) { + return null; + } + + public function create_file_from_string($newfilename, $content, $userid=null) { + return null; + } + + public function create_file_from_pathname($newfilename, $pathname, $userid=null) { + return null; + } + + public function create_file_from_localfile($newfilename, $fid, $userid=null) { + return null; + } + + public function delete() { + return false; + } + +//TODO: following methods are not implemented yet ;-) + + //public abstract function copy(location params); + //public abstract function move(location params); + //public abstract function rename(new name); + //public abstract function unzip(location params); + //public abstract function zip(zip file, file info); +} diff --git a/lib/file/file_info_course.php b/lib/file/file_info_course.php new file mode 100644 index 0000000000..dd8bf79918 --- /dev/null +++ b/lib/file/file_info_course.php @@ -0,0 +1,61 @@ +course = $course; + } + + public function get_params() { + return array('contextid'=>$this->context->id, + 'filearea' =>null, + 'itemid' =>null, + 'filepath' =>null, + 'filename' =>null); + } + + public function get_visible_name() { + return ($this->course->id == SITEID) ? get_string('frontpage', 'admin') : format_string($this->course->fullname); + } + + public function is_writable() { + return false; + } + + public function is_directory() { + return true; + } + + public function get_children() { + $children = array(); + + if (has_capability('moodle/course:update', $this->context)) { + if ($child = $this->browser->get_file_info($this->context, 'course_intro', 0)) { + $children[] = $child; + } + } + + if (has_capability('moodle/site:backup', $this->context) or has_capability('moodle/site:restorep', $this->context)) { + if ($child = $this->browser->get_file_info($this->context, 'course_backup', 0)) { + $children[] = $child; + } + } + + if (has_capability('moodle/course:managefiles', $this->context)) { + if ($child = $this->browser->get_file_info($this->context, 'course_content', 0)) { + $children[] = $child; + } + } + + return $children; + } + + public function get_parent() { + $pcid = get_parent_contextid($this->context); + $parent = get_context_instance_by_id($pcid); + return $this->browser->get_file_info($parent); + } +} diff --git a/lib/file/file_info_coursecat.php b/lib/file/file_info_coursecat.php new file mode 100644 index 0000000000..15c2430f3e --- /dev/null +++ b/lib/file/file_info_coursecat.php @@ -0,0 +1,66 @@ +category = $category; + } + + public function get_params() { + return array('contextid'=>$this->context->id, + 'filearea' =>null, + 'itemid' =>null, + 'filepath' =>null, + 'filename' =>null); + } + + public function get_visible_name() { + return format_string($this->category->name); + } + + public function is_directory() { + return true; + } + + public function get_children() { + global $DB; + + $children = array(); + + if ($child = $this->browser->get_file_info($this->context, 'coursecat_intro', 0)) { + $children[] = $child; + } + + $course_cats = $DB->get_records('course_categories', array('parent'=>$this->category->id), 'sortorder'); + foreach ($course_cats as $category) { + $context = get_context_instance(CONTEXT_COURSECAT, $category->id); + if (!$category->visible and !has_capability('moodle/course:viewhiddencourses', $context)) { + continue; + } + if ($child = $this->browser->get_file_info($context)) { + $children[] = $child; + } + } + + $courses = $DB->get_records('course', array('category'=>$this->category->id), 'sortorder'); + foreach ($courses as $course) { + $context = get_context_instance(CONTEXT_COURSE, $course->id); + if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) { + continue; + } + if ($child = $this->browser->get_file_info($context)) { + $children[] = $child; + } + } + + return $children; + } + + public function get_parent() { + $cid = get_parent_contextid($this->context); + $parent = get_context_instance_by_id($cid); + return $this->browser->get_file_info($parent); + } +} diff --git a/lib/file/file_info_coursefile.php b/lib/file/file_info_coursefile.php new file mode 100644 index 0000000000..d255362833 --- /dev/null +++ b/lib/file/file_info_coursefile.php @@ -0,0 +1,38 @@ +wwwroot.'/file.php'; + parent::__construct($browser, $context, $localfile, $urlbase, 'Course files', false, true, true); // TODO: localise + } + + public function get_url($forcedownload=false, $https=false) { + global $CFG; + + if (!$this->is_readable()) { + return null; + } + + if ($this->lf->get_filename() === '.') { + return null; + } + + $filepath = $this->lf->get_filepath(); + $filename = $this->lf->get_filename(); + $courseid = $this->context->instanceid; + + $path = '/'.$courseid.$filepath.$filename; + + return $this->browser->encodepath($this->urlbase, $path, $forcedownload, $https); + } + + public function get_children() { + if ($this->lf->get_filename() !== '.') { + return array(); //not a dir + } + return $this->browser->build_coursefile_children($this->context, $this->lf->get_filepath()); + } + + +} diff --git a/lib/file/file_info_stored.php b/lib/file/file_info_stored.php new file mode 100644 index 0000000000..9c2a98f0d0 --- /dev/null +++ b/lib/file/file_info_stored.php @@ -0,0 +1,278 @@ +lf = $localfile; + $this->urlbase = $urlbase; + $this->areavisiblename = $areavisiblename; + $this->itemidused = $itemidused; + $this->readaccess = $readaccess; + $this->writeaccess = $writeaccess; + } + + public function get_params() { + return array('contextid'=>$this->context->id, + 'filearea' =>$this->lf->get_filearea(), + 'itemid' =>$this->lf->get_itemid(), + 'filepath' =>$this->lf->get_filepath(), + 'filename' =>$this->lf->get_filename()); + } + + public function get_visible_name() { + $filename = $this->lf->get_filename(); + $filepath = $this->lf->get_filepath(); + + if ($filename !== '.') { + return $filename; + + } else { + $dir = trim($filepath, '/'); + $dir = explode('/', $dir); + $dir = array_pop($dir); + if ($dir === '') { + if ($this->itemidused) { + return $this->itemid; + } else { + return $this->areavisiblename; + } + } else { + return $dir; + } + } + } + + public function get_url($forcedownload=false, $https=false) { + global $CFG; + + if (!$this->is_readable()) { + return null; + } + + if ($this->is_directory()) { + return null; + } + + $this->urlbase; + $contextid = $this->lf->get_contextid(); + $filearea = $this->lf->get_filearea(); + $filepath = $this->lf->get_filepath(); + $filename = $this->lf->get_filename(); + $itemid = $this->lf->get_itemid(); + + if ($this->itemidused) { + $path = '/'.$contextid.'/'.$filearea.'/'.$itemid.$filepath.$filename; + } else { + $path = '/'.$contextid.'/'.$filearea.$filepath.$filename; + } + return $this->browser->encodepath($this->urlbase, $path, $forcedownload, $https); + } + + public function is_readable() { + return $this->readaccess; + } + + public function is_writable() { + return $this->writeaccess; + } + + public function get_filesize() { + return $this->lf->get_filesize(); + } + + public function get_mimetype() { + // TODO: add some custom mime icons for courses, categories?? + return $this->lf->get_mimetype(); + } + + public function get_timecreated() { + return $this->lf->get_timecreated(); + } + + public function get_timemodified() { + return $this->lf->get_timemodified(); + } + + public function is_directory() { + if (!$this->lf) { + return true; + } + + return ($this->lf->get_filename() === '.'); + } + + public function get_children() { + if ($this->lf->get_filename() !== '.') { + return array(); //not a dir + } + return $this->browser->build_stored_file_children($this->context, $this->lf->get_filearea(), $this->lf->get_itemid(), $this->lf->get_filepath(), + $this->urlbase, $this->areavisiblename, $this->itemidused, $this->readaccess, $this->writeaccess); + } + + public function get_parent() { + if ($this->lf->get_filename() !== '.') { + return $this->browser->get_file_info($this->context, $this->lf->get_filearea(), $this->lf->get_itemid(), $this->lf->get_filepath(), '.'); + } + + if ($this->lf->get_filepath() === '/') { + if ($this->itemidused) { + return $this->browser->get_file_info($this->context, $this->lf->get_filearea(), $this->lf->get_itemid()); + } else { + return $this->browser->get_file_info($this->context, $this->lf->get_filearea()); + } + } + + $filepath = $this->lf->get_filepath(); + $filepath = trim($filepath, '/'); + $dirs = explode('/', $filepath); + array_pop($dirs); + $filepath = implode('/', $dirs); + $filepath = ($filepath === '') ? '/' : "/$filepath/"; + + return $this->browser->get_file_info($this->context, $this->lf->get_filearea(), $this->lf->get_itemid(), $filepath, '.'); + } + + public function create_directory($newdirname, $userid=null) { + if (!$this->is_writable() or $this->lf->get_filename() !== '.') { + return null; + } + + $newdirname = clean_param($newdirname, PARAM_FILE); + if ($newdirname === '') { + return null; + } + + $filepath = $this->lf->get_filepath().'/'.$newdirname.'/'; + + $fs = get_file_storage(); + + if ($file = $fs->create_directory($this->lf->get_contextid(), $this->lf->get_filearea(), $this->lf->get_itemid(), $filepath, $userid)) { + return $this->browser->get_file_info($this->context, $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename()); + } + return null; + } + + + public function create_file_from_string($newfilename, $content, $userid=null) { + if (!$this->is_writable() or $this->lf->get_filename() !== '.') { + return null; + } + + $newfilename = clean_param($newfilename, PARAM_FILE); + if ($newfilename === '') { + return null; + } + + $now = time(); + + $newrecord = new object(); + $newrecord->contextid = $this->lf->get_contextid(); + $newrecord->filearea = $this->lf->get_filearea(); + $newrecord->itemid = $this->lf->get_itemid(); + $newrecord->filepath = $this->lf->get_filepath(); + $newrecord->filename = $newfilename; + + $newrecord->timecreated = $now; + $newrecord->timemodified = $now; + $newrecord->mimetype = mimeinfo('type', $newfilename); + $newrecord->userid = $userid; + + $fs = get_file_storage(); + + if ($file = $fs->create_file_from_string($newrecord, $content)) { + return $this->browser->get_file_info($this->context, $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename()); + } + return null; + } + + public function create_file_from_pathname($newfilename, $pathname, $userid=null) { + if (!$this->is_writable() or $this->lf->get_filename() !== '.') { + return null; + } + + $newfilename = clean_param($newfilename, PARAM_FILE); + if ($newfilename === '') { + return null; + } + + $now = time(); + + $newrecord = new object(); + $newrecord->contextid = $this->lf->get_contextid(); + $newrecord->filearea = $this->lf->get_filearea(); + $newrecord->itemid = $this->lf->get_itemid(); + $newrecord->filepath = $this->lf->get_filepath(); + $newrecord->filename = $newfilename; + + $newrecord->timecreated = $now; + $newrecord->timemodified = $now; + $newrecord->mimetype = mimeinfo('type', $newfilename); + $newrecord->userid = $userid; + + $fs = get_file_storage(); + + if ($file = $fs->create_file_from_pathname($newrecord, $pathname)) { + return $this->browser->get_file_info($this->context, $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename()); + } + return null; + } + + public function create_file_from_localfile($newfilename, $fid, $userid=null) { + if (!$this->is_writable() or $this->lf->get_filename() !== '.') { + return null; + } + + $newfilename = clean_param($newfilename, PARAM_FILE); + if ($newfilename === '') { + return null; + } + + $now = time(); + + $newrecord = new object(); + $newrecord->contextid = $this->lf->get_contextid(); + $newrecord->filearea = $this->lf->get_filearea(); + $newrecord->itemid = $this->lf->get_itemid(); + $newrecord->filepath = $this->lf->get_filepath(); + $newrecord->filename = $newfilename; + + $newrecord->timecreated = $now; + $newrecord->timemodified = $now; + $newrecord->mimetype = mimeinfo('type', $newfilename); + $newrecord->userid = $userid; + + $fs = get_file_storage(); + + if ($file = $fs->create_file_from_localfile($newrecord, $fid)) { + return $this->browser->get_file_info($this->context, $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename()); + } + return null; + } + + public function delete() { + if (!$this->lf or !$this->is_writable()) { + return false; + } + + if ($this->is_directory()) { + $filepath = $this->lf->get_filepath(); + $fs = get_file_storage(); + $localfiles = $fs->get_area_files($this->context->id, $this->lf->get_filearea(), $this->lf->get_itemid(), ""); + foreach ($localfiles as $file) { + if (strpos($file->get_filepath(), $filepath) === 0) { + $file->delete(); + } + } + } + + return $this->lf->delete(); + } +} diff --git a/lib/file/file_info_system.php b/lib/file/file_info_system.php new file mode 100644 index 0000000000..315ab17ae4 --- /dev/null +++ b/lib/file/file_info_system.php @@ -0,0 +1,65 @@ +$this->context->id, + 'filearea' =>null, + 'itemid' =>null, + 'filepath' =>null, + 'filename' =>null); + } + + public function get_visible_name() { + return 'Root'; // TODO: fix & localise + } + + public function is_writable() { + return false; + } + + public function is_directory() { + return true; + } + + public function get_children() { + global $DB, $USER; + + $children = array(); + + if ($child = $this->browser->get_file_info(get_context_instance(CONTEXT_USER, $USER->id))) { + $children[] = $child; + } + + $course_cats = $DB->get_records('course_categories', array('parent'=>0), 'sortorder'); + foreach ($course_cats as $category) { + $context = get_context_instance(CONTEXT_COURSECAT, $category->id); + if (!$category->visible and !has_capability('moodle/course:viewhiddencourses', $context)) { + continue; + } + if ($child = $this->browser->get_file_info($context)) { + $children[] = $child; + } + } + + $courses = $DB->get_records('course', array('category'=>0), 'sortorder'); + foreach ($courses as $course) { + if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) { + continue; + } + $context = get_context_instance(CONTEXT_COURSE, $course->id); + if ($child = $this->browser->get_file_info($context)) { + $children[] = $child; + } + } + + return $children; + } + + public function get_parent() { + return null; + } +} diff --git a/lib/file/file_info_user.php b/lib/file/file_info_user.php new file mode 100644 index 0000000000..7af4e30d68 --- /dev/null +++ b/lib/file/file_info_user.php @@ -0,0 +1,47 @@ +instanceid; + + if ($userid == $USER->id) { + $this->user = $USER; + } else { + // if context exists user record should exist too ;-) + $this->user = $DB->get_record('user', array('id'=>$userid)); + } + } + + public function get_params() { + return array('contextid'=>$this->context->id, + 'filearea' =>null, + 'itemid' =>null, + 'filepath' =>null, + 'filename' =>null); + } + + public function get_visible_name() { + return fullname($this->user, true); + } + + public function is_directory() { + return true; + } + + public function get_children() { + global $USER, $CFG; + + // only current user for now + return array($this->browser->get_file_info(get_context_instance(CONTEXT_USER, $USER->id), 'user_private', 0)); + } + + public function get_parent() { + return $this->browser->get_file_info(get_context_instance(CONTEXT_SYSTEM)); + } +} diff --git a/lib/file/file_packer.php b/lib/file/file_packer.php new file mode 100644 index 0000000000..2b826f2d5f --- /dev/null +++ b/lib/file/file_packer.php @@ -0,0 +1,5 @@ +libdir/file/stored_file.php"); + +class file_storage { + private $filedir; + + /** + * Contructor + * @param string $filedir full path to pool directory + */ + public function __construct() { + global $CFG; + if (isset($CFG->filedir)) { + $this->filedir = $CFG->filedir; + } else { + $this->filedir = $CFG->dataroot.'/filedir'; + } + + // make sure the file pool directory exists + if (!is_dir($this->filedir)) { + if (!check_dir_exists($this->filedir, true, true)) { + throw new file_exception('localfilecannotcreatefiledirs'); // permission trouble + } + // place warning file in file pool root + $fp = fopen($this->filedir.'/warning.txt', 'w'); + fwrite($fp, '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.'); + fclose($fp); + unset($fp); + } + } + + /** + * Calculates sha1 hash of unique full path name information + * @param int $contextid + * @param string $filearea + * @param int $itemid + * @param string $filepath + * @param string $filename + * @return string + */ + public static function get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename) { + return sha1($contextid.$filearea.$itemid.$filepath.$filename); + } + + /** + * Does this file exist? + * @param int $contextid + * @param string $filearea + * @param int $itemid + * @param string $filepath + * @param string $filename + * @return bool + */ + public function file_exists($contextid, $filearea, $itemid, $filepath, $filename) { + $filepath = clean_param($filepath, PARAM_PATH); + $filename = clean_param($filename, PARAM_FILE); + + if ($filename === '') { + $filename = '.'; + } + + $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename); + return $this->file_exists_by_hash($pathnamehash); + } + + /** + * Does this file exist? + * @param string $pathnamehash + * @return bool + */ + public function file_exists_by_hash($pathnamehash) { + global $DB; + + return $DB->record_exists('files', array('pathnamehash'=>$pathnamehash)); + } + + /** + * Fetch file using local file id + * @param int $fileid + * @return mixed stored_file instance if exists, false if not + */ + public function get_file_by_id($fileid) { + global $DB; + + if ($file_record = $DB->get_record('files', array('id'=>$fileid))) { + return new stored_file($this, $file_record); + } else { + return false; + } + } + + /** + * Fetch file using local file full pathname hash + * @param string $pathnamehash + * @return mixed stored_file instance if exists, false if not + */ + public function get_file_by_hash($pathnamehash) { + global $DB; + + if ($file_record = $DB->get_record('files', array('pathnamehash'=>$pathnamehash))) { + return new stored_file($this, $file_record); + } else { + return false; + } + } + + /** + * Fetch file + * @param int $contextid + * @param string $filearea + * @param int $itemid + * @param string $filepath + * @param string $filename + * @return mixed stored_file instance if exists, false if not + */ + public function get_file($contextid, $filearea, $itemid, $filepath, $filename) { + global $DB; + + $filepath = clean_param($filepath, PARAM_PATH); + $filename = clean_param($filename, PARAM_FILE); + + if ($filename === '') { + $filename = '.'; + } + + $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, $filename); + return $this->get_file_by_hash($pathnamehash); + } + + /** + * Returns all area files (optionally limited by itemid) + * @param int $contextid + * @param string $filearea + * @param int $itemid (all files if not specified) + * @param string $sort + * @param bool $includedirs + * @return array of stored_files + */ + public function get_area_files($contextid, $filearea, $itemid=false, $sort="itemid, filepath, filename", $inludedirs=true) { + global $DB; + + $conditions = array('contextid'=>$contextid, 'filearea'=>$filearea); + if ($itemid !== false) { + $conditions['itemid'] = $itemid; + } + + $result = array(); + $file_records = $DB->get_records('files', $conditions, $sort); + foreach ($file_records as $file_record) { + if (!$inludedirs and $file_record->filename === '.') { + continue; + } + $result[] = new stored_file($this, $file_record); + } + return $result; + } + + /** + * Delete all area files (optionally limited by itemid) + * @param int $contextid + * @param string $filearea + * @param int $itemid (all files if not specified) + * @return success + */ + public function delete_area_files($contextid, $filearea, $itemid=false) { + global $DB; + + $conditions = array('contextid'=>$contextid, 'filearea'=>$filearea); + if ($itemid !== false) { + $conditions['itemid'] = $itemid; + } + + $success = true; + + $file_records = $DB->get_records('files', $conditions); + foreach ($file_records as $file_record) { + $stored_file = new stored_file($this, $file_record); + $success = $stored_file->delete() && $success; + } + + return $success; + } + + /** + * Recursively creates director + * @param int $contextid + * @param string $filearea + * @param int $itemid + * @param string $filepath + * @param string $filename + * @return bool success + */ + public function create_directory($contextid, $filearea, $itemid, $filepath, $userid=null) { + global $DB; + + // validate all parameters, we do not want any rubbish stored in database, right? + if (!is_number($contextid) or $contextid < 1) { + throw new file_exception('localfileproblem', 'Invalid contextid'); + } + + $filearea = clean_param($filearea, PARAM_SAFEDIR); + if ($filearea === '') { + throw new file_exception('localfileproblem', 'Invalid filearea'); + } + + if (!is_number($itemid) or $itemid < 0) { + throw new file_exception('localfileproblem', 'Invalid itemid'); + } + + $filepath = clean_param($filepath, PARAM_PATH); + if (strpos($filepath, '/') !== 0 or strrpos($filepath, '/') !== strlen($filepath)-1) { + // path must start and end with '/' + throw new file_exception('localfileproblem', 'Invalid file path'); + } + + $pathnamehash = $this->get_pathname_hash($contextid, $filearea, $itemid, $filepath, '.'); + + if ($dir_info = $this->get_file_by_hash($pathnamehash)) { + return $dir_info; + } + + static $contenthash = null; + if (!$contenthash) { + $this->add_to_pool_string(''); + $contenthash = sha1(''); + } + + $now = time(); + + $dir_record = new object(); + $dir_record->contextid = $contextid; + $dir_record->filearea = $filearea; + $dir_record->itemid = $itemid; + $dir_record->filepath = $filepath; + $dir_record->filename = '.'; + $dir_record->contenthash = $contenthash; + $dir_record->filesize = 0; + + $dir_record->timecreated = $now; + $dir_record->timemodified = $now; + $dir_record->mimetype = null; + $dir_record->userid = $userid; + + $dir_record->pathnamehash = $pathnamehash; + + $DB->insert_record('files', $dir_record); + $dir_info = $this->get_file_by_hash($pathnamehash); + + if ($filepath !== '/') { + //recurse to parent dirs + $filepath = trim($filepath, '/'); + $filepath = explode('/', $filepath); + array_pop($filepath); + $filepath = implode('/', $filepath); + $filepath = ($filepath === '') ? '/' : "/$filepath/"; + $this->create_directory($contextid, $filearea, $itemid, $filepath, $userid); + } + + return $dir_info; + } + + /** + * Add new local file based on existing local file + * @param mixed $file_record object or array describing changes + * @param int $fid id of existing local file + * @return object stored_file instance + */ + public function create_file_from_localfile($file_record, $fid) { + global $DB; + + $file_record = (array)$file_record; // we support arrays too + unset($file_record['id']); + unset($file_record['filesize']); + unset($file_record['contenthash']); + + $now = time(); + + if ($newrecord = $DB->get_record('files', array('id'=>$fid))) { + throw new file_exception('localfileproblem', 'File does not exist'); + } + + unset($newrecord->id); + + foreach ($file_record as $key=>$value) { + // validate all parameters, we do not want any rubbish stored in database, right? + if ($key == 'contextid' and (!is_number($value) or $value < 1)) { + throw new file_exception('localfileproblem', 'Invalid contextid'); + } + + if ($key == 'filearea') { + $value = clean_param($value, PARAM_SAFEDIR); + if ($value === '') { + throw new file_exception('localfileproblem', 'Invalid filearea'); + } + } + + if ($key == 'itemid' and (!is_number($value) or $value < 0)) { + throw new file_exception('localfileproblem', 'Invalid itemid'); + } + + + if ($key == 'filepath') { + $value = clean_param($value, PARAM_PATH); + if (strpos($value, '/') !== 0 or strpos($value, '/') !== strlen($value)-1) { + // path must start and end with '/' + throw new file_exception('localfileproblem', 'Invalid file path'); + } + } + + if ($key == 'filename') { + $value = clean_param($value, PARAM_FILE); + if ($value === '') { + // path must start and end with '/' + throw new file_exception('localfileproblem', 'Invalid file name'); + } + } + + $newrecord->$key = $value; + } + + $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); + + try { + $newrecord->id = $DB->insert_record('files', $newrecord); + } catch (database_exception $e) { + $newrecord->id = false; + } + + if (!$newrecord->id) { + throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, + $newrecord->filepath, $newrecord->filename); + } + + $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); + + return new stored_file($this, $newrecord); + } + + /** + * Add new local file + * @param mixed $file_record object or array describing file + * @param string $path path to file or content of file + * @return object stored_file instance + */ + public function create_file_from_pathname($file_record, $pathname) { + global $DB; + + $file_record = (object)$file_record; // we support arrays too + + // validate all parameters, we do not want any rubbish stored in database, right? + if (!is_number($file_record->contextid) or $file_record->contextid < 1) { + throw new file_exception('localfileproblem', 'Invalid contextid'); + } + + $file_record->filearea = clean_param($file_record->filearea, PARAM_SAFEDIR); + if ($file_record->filearea === '') { + throw new file_exception('localfileproblem', 'Invalid filearea'); + } + + if (!is_number($file_record->itemid) or $file_record->itemid < 0) { + throw new file_exception('localfileproblem', 'Invalid itemid'); + } + + $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); + if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { + // path must start and end with '/' + throw new file_exception('localfileproblem', 'Invalid file path'); + } + + $file_record->filename = clean_param($file_record->filename, PARAM_FILE); + if ($file_record->filename === '') { + // path must start and end with '/' + throw new file_exception('localfileproblem', 'Invalid file name'); + } + + $now = time(); + + $newrecord = new object(); + + $newrecord->contextid = $file_record->contextid; + $newrecord->filearea = $file_record->filearea; + $newrecord->itemid = $file_record->itemid; + $newrecord->filepath = $file_record->filepath; + $newrecord->filename = $file_record->filename; + + $newrecord->timecreated = empty($file_record->timecreated) ? $now : $file_record->timecreated; + $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified; + $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; + $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; + + list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_to_pool_pathname($pathname); + + $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); + + try { + $newrecord->id = $DB->insert_record('files', $newrecord); + } catch (database_exception $e) { + $newrecord->id = false; + } + + if (!$newrecord->id) { + if ($newfile) { + $this->mark_delete_candidate($newrecord->contenthash); + } + throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, + $newrecord->filepath, $newrecord->filename); + } + + $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); + + return new stored_file($this, $newrecord); + } + + /** + * Add new local file + * @param mixed $file_record object or array describing file + * @param string $content content of file + * @return object stored_file instance + */ + public function create_file_from_string($file_record, $content) { + global $DB; + + $file_record = (object)$file_record; // we support arrays too + + // validate all parameters, we do not want any rubbish stored in database, right? + if (!is_number($file_record->contextid) or $file_record->contextid < 1) { + throw new file_exception('localfileproblem', 'Invalid contextid'); + } + + $file_record->filearea = clean_param($file_record->filearea, PARAM_SAFEDIR); + if ($file_record->filearea === '') { + throw new file_exception('localfileproblem', 'Invalid filearea'); + } + + if (!is_number($file_record->itemid) or $file_record->itemid < 0) { + throw new file_exception('localfileproblem', 'Invalid itemid'); + } + + $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH); + if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) { + // path must start and end with '/' + throw new file_exception('localfileproblem', 'Invalid file path'); + } + + $file_record->filename = clean_param($file_record->filename, PARAM_FILE); + if ($file_record->filename === '') { + // path must start and end with '/' + throw new file_exception('localfileproblem', 'Invalid file name'); + } + + $now = time(); + + $newrecord = new object(); + + $newrecord->contextid = $file_record->contextid; + $newrecord->filearea = $file_record->filearea; + $newrecord->itemid = $file_record->itemid; + $newrecord->filepath = $file_record->filepath; + $newrecord->filename = $file_record->filename; + + $newrecord->timecreated = empty($file_record->timecreated) ? $now : $file_record->timecreated; + $newrecord->timemodified = empty($file_record->timemodified) ? $now : $file_record->timemodified; + $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype; + $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid; + + list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_to_pool_string($content); + + $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename); + + try { + $newrecord->id = $DB->insert_record('files', $newrecord); + } catch (database_exception $e) { + $newrecord->id = false; + } + + if (!$newrecord->id) { + if ($newfile) { + $this->mark_delete_candidate($newrecord->contenthash); + } + throw new stored_file_creation_exception($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, + $newrecord->filepath, $newrecord->filename); + } + + $this->create_directory($newrecord->contextid, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); + + return new stored_file($this, $newrecord); + } + + /** + * Add file content to sha1 pool + * @param string $pathname path to file + * @param string sha1 hash of content if known (performance only) + * @return array(contenthash, filesize, newfile) + */ + public function add_to_pool_pathname($pathname, $contenthash=null) { + if (!is_readable($pathname)) { + throw new file_exception('localfilecannotread'); + } + + if (is_null($contenthash)) { + $contenthash = sha1_file($pathname); + } + + $filesize = filesize($pathname); + + $hashpath = $this->path_from_hash($contenthash); + $hashfile = "$hashpath/$contenthash"; + + if (file_exists($hashfile)) { + if (filesize($hashfile) !== $filesize) { + throw new file_pool_content_exception($contenthash); + } + $newfile = false; + + } else { + if (!check_dir_exists($hashpath, true, true)) { + throw new file_exception('localfilecannotcreatefiledirs'); // permission trouble + } + $newfile = true; + + $fs = fopen($pathname, 'rb'); + $fp = fopen($hashfile, 'wb'); + while(!feof($fs)) { + $buf = fread($fs, 65536); + if ($buf === false) { + throw new file_exception('localfilecannotread'); + } + fwrite($fp, $buf); + } + fclose($fp); + fclose($fs); + + if (filesize($hashfile) !== $filesize) { + @unlink($hashfile); + throw new file_pool_content_exception($contenthash); + } + } + + + return array($contenthash, $filesize, $newfile); + } + + /** + * Add string content to sha1 pool + * @param string $content file content - binary string + * @return array(contenthash, filesize, newfile) + */ + public function add_to_pool_string($content) { + $contenthash = sha1($content); + $filesize = strlen($content); // binary length + + $hashpath = $this->path_from_hash($contenthash); + $hashfile = "$hashpath/$contenthash"; + + + if (file_exists($hashfile)) { + if (filesize($hashfile) !== $filesize) { + throw new file_pool_content_exception($contenthash); + } + $newfile = false; + + } else { + if (!check_dir_exists($hashpath, true, true)) { + throw new file_exception('localfilecannotcreatefiledirs'); // permission trouble + } + $newfile = true; + + $fp = fopen($hashfile, 'wb'); + + fwrite($fp, $content); + fclose($fp); + + if (filesize($hashfile) !== $filesize) { + @unlink($hashfile); + throw new file_pool_content_exception($contenthash); + } + } + + return array($contenthash, $filesize, $newfile); + } + + /** + * Return path to file with given hash + * + * DO NOT USE - should be protected, but protected is dumb in PHP + * + * @param string $contenthash + * @return string expected file location + */ + public function path_from_hash($contenthash) { + $l1 = $contenthash[0].$contenthash[1]; + $l2 = $contenthash[2].$contenthash[3]; + $l3 = $contenthash[4].$contenthash[5]; + return "$this->filedir/$l1/$l2/$l3"; + } + + /** + * Marks pool file as candidate for deleting + * @param string $contenthash + */ + public function mark_delete_candidate($contenthash) { + global $DB; + + if ($DB->record_exists('files_cleanup', array('contenthash'=>$contenthash))) { + return; + } + $rec = new object(); + $rec->contenthash = $contenthash; + $DB->insert_record('files_cleanup', $rec); + } + + /** + * Cron cleanup job. + */ + public function cron() { + global $DB; + + //TODO: there is a small chance that reused files might be deleted + // if this function takes too long we should add some table locking here + + $sql = "SELECT 1 AS id, fc.contenthash + FROM {files_cleanup} fc + LEFT JOIN {files} f ON f.contenthash = fc.contenthash + WHERE f.id IS NULL"; + while ($hash = $DB->get_record_sql($sql, null, true)) { + $file = $this->path_from_hash($hash->contenthash).'/'.$hash->contenthash; + @unlink($file); + $DB->delete_records('files_cleanup', array('contenthash'=>$hash->contenthash)); + } + } +} diff --git a/lib/file/stored_file.php b/lib/file/stored_file.php new file mode 100644 index 0000000000..375809cc74 --- /dev/null +++ b/lib/file/stored_file.php @@ -0,0 +1,122 @@ +fs = $fs; + $this->file_record = clone($file_record); + } + + /** + * Is this a directory? + * @return bool + */ + public function is_directory() { + return $this->file_record->filename === '.'; + } + + /** + * Delete file + * @return success + */ + public function delete() { + global $DB; + $this->fs->mark_delete_candidate($this->file_record->contenthash); + return $DB->delete_records('files', array('id'=>$this->file_record->id)); + } + + /** + * Protected - devs must not gain direct access to this function + **/ + protected function get_content_file_location() { + // NOTE: do not make this public, we must not modify or delete the pool files directly! ;-) + $hashpath = $this->fs->path_from_hash($this->file_record->contenthash); + return $hashpath.'/'.$this->file_record->contenthash; + } + + /** + * Returns file handle - read only mode, no writing allowed into pool files! + * @return file handle + */ + public function get_content_file_handle() { + $path = $this->get_content_file_location(); + if (!is_readable($path)) { + throw new file_exception('localfilecannotread'); + } + return fopen($path, 'rb'); //binary reading only!! + } + + /** + * Dumps file content to page + * @return file handle + */ + public function readfile() { + $path = $this->get_content_file_location(); + if (!is_readable($path)) { + throw new file_exception('localfilecannotread'); + } + readfile($path); + } + + /** + * Returns file content as string + * @return string content + */ + public function get_content() { + $path = $this->get_content_file_location(); + if (!is_readable($path)) { + throw new file_exception('localfilecannotread'); + } + return file_get_contents($this->get_content_file_location()); + } + + public function get_contextid() { + return $this->file_record->contextid; + } + + public function get_filearea() { + return $this->file_record->filearea; + } + + public function get_itemid() { + return $this->file_record->itemid; + } + + public function get_filepath() { + return $this->file_record->filepath; + } + + public function get_filename() { + return $this->file_record->filename; + } + + public function get_userid() { + return $this->file_record->userid; + } + + public function get_filesize() { + return $this->file_record->filesize; + } + + public function get_mimetype() { + return $this->file_record->mimetype; + } + + public function get_timecreated() { + return $this->file_record->timecreated; + } + + public function get_timemodified() { + return $this->file_record->timemodified; + } +} diff --git a/lib/filelib.php b/lib/filelib.php index cca046444c..8d64540ee4 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -2,10 +2,14 @@ define('BYTESERVING_BOUNDARY', 's1k2o3d4a5k6s7'); //unique string constant +require_once("$CFG->libdir/file/file_exceptions.php"); +require_once("$CFG->libdir/file/file_storage.php"); +require_once("$CFG->libdir/file/file_browser.php"); + function get_file_url($path, $options=null, $type='coursefile') { global $CFG; - $path = str_replace('//', '/', $path); + $path = str_replace('//', '/', $path); $path = trim($path, '/'); // no leading and trailing slashes // type of file @@ -309,7 +313,7 @@ function download_file_content($url, $headers=null, $postdata=null, $fullrespons * Unknown types should use the 'xxx' entry which includes defaults. */ function get_mimetypes_array() { - return array ( + static $mimearray = array ( 'xxx' => array ('type'=>'document/unknown', 'icon'=>'unknown.gif'), '3gp' => array ('type'=>'video/quicktime', 'icon'=>'video.gif'), 'ai' => array ('type'=>'application/postscript', 'icon'=>'image.gif'), @@ -464,6 +468,7 @@ function get_mimetypes_array() { 'xsl' => array ('type'=>'text/xml', 'icon'=>'xml.gif'), 'zip' => array ('type'=>'application/zip', 'icon'=>'zip.gif') ); + return $mimearray; } /** @@ -476,10 +481,7 @@ function get_mimetypes_array() { * @return string Requested piece of information from array */ function mimeinfo($element, $filename) { - static $mimeinfo = null; - if (is_null($mimeinfo)) { - $mimeinfo = get_mimetypes_array(); - } + $mimeinfo = get_mimetypes_array(); if (eregi('\.([a-z0-9]+)$', $filename, $match)) { if (isset($mimeinfo[strtolower($match[1])][$element])) { @@ -500,8 +502,7 @@ function mimeinfo($element, $filename) { * @return string Requested piece of information from array */ function mimeinfo_from_type($element, $mimetype) { - static $mimeinfo; - $mimeinfo=get_mimetypes_array(); + $mimeinfo = get_mimetypes_array(); foreach($mimeinfo as $values) { if($values['type']==$mimetype) { @@ -521,8 +522,7 @@ function mimeinfo_from_type($element, $mimetype) { * @return string Requested piece of information from array */ function mimeinfo_from_icon($element, $icon) { - static $mimeinfo; - $mimeinfo=get_mimetypes_array(); + $mimeinfo = get_mimetypes_array(); if (preg_match("/\/(.*)/", $icon, $matches)) { $icon = $matches[1]; @@ -639,6 +639,8 @@ function send_temp_file_finished($path) { function send_file($path, $filename, $lifetime=86400 , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='') { global $CFG, $COURSE, $SESSION; + session_write_close(); // unlock session during fileserving + // Use given MIME type if specified, otherwise guess it using mimeinfo. // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O // only Firefox saves all files locally before opening when content-disposition: attachment stated @@ -727,7 +729,8 @@ function send_file($path, $filename, $lifetime=86400 , $filter=0, $pathisstring= $ranges = false; } if ($ranges) { - byteserving_send_file($path, $mimetype, $ranges); + $handle = fopen($filename, 'rb'); + byteserving_send_file($handle, $mimetype, $ranges, $filesize); } } } else { @@ -815,6 +818,177 @@ function send_file($path, $filename, $lifetime=86400 , $filter=0, $pathisstring= die; //no more chars to output!!! } +/** + * Handles the sending of file data to the user's browser, including support for + * byteranges etc. + * @param object $stored_file local file object + * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours) + * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only + * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin + * @param string $filename Override filename + * @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename + */ +function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, $filename=null) { + global $CFG, $COURSE, $SESSION; + + session_write_close(); // unlock session during fileserving + + // Use given MIME type if specified, otherwise guess it using mimeinfo. + // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O + // only Firefox saves all files locally before opening when content-disposition: attachment stated + $filename = is_null($filename) ? $stored_file->get_filename() : $filename; + $isFF = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested + $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' : + ($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename)); + $lastmodified = $stored_file->get_timemodified(); + $filesize = $stored_file->get_filesize(); + + //IE compatibiltiy HACK! + if (ini_get('zlib.output_compression')) { + ini_set('zlib.output_compression', 'Off'); + } + + //try to disable automatic sid rewrite in cookieless mode + @ini_set("session.use_trans_sid", "false"); + + //do not put '@' before the next header to detect incorrect moodle configurations, + //error should be better than "weird" empty lines for admins/users + //TODO: should we remove all those @ before the header()? Are all of the values supported on all servers? + header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT'); + + // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup + if (check_browser_version('MSIE')) { + $filename = rawurlencode($filename); + } + + if ($forcedownload) { + @header('Content-Disposition: attachment; filename="'.$filename.'"'); + } else { + @header('Content-Disposition: inline; filename="'.$filename.'"'); + } + + if ($lifetime > 0) { + @header('Cache-Control: max-age='.$lifetime); + @header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); + @header('Pragma: '); + + if (empty($CFG->disablebyteserving) && $mimetype != 'text/plain' && $mimetype != 'text/html') { + + @header('Accept-Ranges: bytes'); + + if (!empty($_SERVER['HTTP_RANGE']) && strpos($_SERVER['HTTP_RANGE'],'bytes=') !== FALSE) { + // byteserving stuff - for acrobat reader and download accelerators + // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 + // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php + $ranges = false; + if (preg_match_all('/(\d*)-(\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) { + foreach ($ranges as $key=>$value) { + if ($ranges[$key][1] == '') { + //suffix case + $ranges[$key][1] = $filesize - $ranges[$key][2]; + $ranges[$key][2] = $filesize - 1; + } else if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) { + //fix range length + $ranges[$key][2] = $filesize - 1; + } + if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) { + //invalid byte-range ==> ignore header + $ranges = false; + break; + } + //prepare multipart header + $ranges[$key][0] = "\r\n--".BYTESERVING_BOUNDARY."\r\nContent-Type: $mimetype\r\n"; + $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/$filesize\r\n\r\n"; + } + } else { + $ranges = false; + } + if ($ranges) { + byteserving_send_file($stored_file->get_content_file_handle(), $mimetype, $ranges, $filesize); + } + } + } else { + /// Do not byteserve (disabled, strings, text and html files). + @header('Accept-Ranges: none'); + } + } else { // Do not cache files in proxies and browsers + if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431 + @header('Cache-Control: max-age=10'); + @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT'); + @header('Pragma: '); + } else { //normal http - prevent caching at all cost + @header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0'); + @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT'); + @header('Pragma: no-cache'); + } + @header('Accept-Ranges: none'); // Do not allow byteserving when caching disabled + } + + if (empty($filter)) { + $filtered = false; + if ($mimetype == 'text/html' && !empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) { + //cookieless mode - rewrite links + @header('Content-Type: text/html'); + $text = $stored_file->get_content(); + $text = $SESSION->sid_ob_rewrite($text); + $filesize = strlen($text); + $filtered = true; + } else if ($mimetype == 'text/plain') { + @header('Content-Type: Text/plain; charset=utf-8'); //add encoding + } else { + @header('Content-Type: '.$mimetype); + } + @header('Content-Length: '.$filesize); + while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite + if ($filtered) { + echo $text; + } else { + $stored_file->readfile(); + } + + } else { // Try to put the file through filters + if ($mimetype == 'text/html') { + $options = new object(); + $options->noclean = true; + $options->nocache = true; // temporary workaround for MDL-5136 + $text = $stored_file->get_content(); + $text = file_modify_html_header($text); + $output = format_text($text, FORMAT_HTML, $options, $COURSE->id); + if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) { + //cookieless mode - rewrite links + $output = $SESSION->sid_ob_rewrite($output); + } + + @header('Content-Length: '.strlen($output)); + @header('Content-Type: text/html'); + while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite + echo $output; + // only filter text if filter all files is selected + } else if (($mimetype == 'text/plain') and ($filter == 1)) { + $options = new object(); + $options->newlines = false; + $options->noclean = true; + $text = $stored_file->get_content(); + $output = '
'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'
'; + if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) { + //cookieless mode - rewrite links + $output = $SESSION->sid_ob_rewrite($output); + } + + @header('Content-Length: '.strlen($output)); + @header('Content-Type: text/html; charset=utf-8'); //add encoding + while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite + echo $output; + } else { // Just send it out raw + @header('Content-Length: '.$filesize); + @header('Content-Type: '.$mimetype); + while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite + $stored_file->readfile(); + } + } + die; //no more chars to output!!! +} + function get_records_csv($file, $table) { global $CFG, $DB; @@ -984,9 +1158,8 @@ function readfile_chunked($filename, $retbytes=true) { /** * Send requested byterange of file. */ -function byteserving_send_file($filename, $mimetype, $ranges) { +function byteserving_send_file($handle, $mimetype, $ranges, $filesize) { $chunksize = 1*(1024*1024); // 1MB chunks - must be less than 2MB! - $handle = fopen($filename, 'rb'); if ($handle === false) { die; } @@ -994,7 +1167,7 @@ function byteserving_send_file($filename, $mimetype, $ranges) { $length = $ranges[0][2] - $ranges[0][1] + 1; @header('HTTP/1.1 206 Partial content'); @header('Content-Length: '.$length); - @header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.filesize($filename)); + @header('Content-Range: bytes '.$ranges[0][1].'-'.$ranges[0][2].'/'.$filesize); @header('Content-Type: '.$mimetype); while (@ob_end_flush()); //flush the buffers - save memory and disable sid rewrite $buffer = ''; @@ -1356,7 +1529,7 @@ class curl { * Download multiple files in parallel * $c = new curl; * $c->download(array( - * array('url'=>'http://localhost/', 'file'=>fopen('a', 'wb')), + * array('url'=>'http://localhost/', 'file'=>fopen('a', 'wb')), * array('url'=>'http://localhost/20/', 'file'=>fopen('b', 'wb')) * )); */ diff --git a/lib/formslib.php b/lib/formslib.php index bc00cf0b3e..6d5c8ecf22 100644 --- a/lib/formslib.php +++ b/lib/formslib.php @@ -26,7 +26,7 @@ require_once 'HTML/QuickForm.php'; require_once 'HTML/QuickForm/DHTMLRulesTableless.php'; require_once 'HTML/QuickForm/Renderer/Tableless.php'; -require_once $CFG->libdir.'/uploadlib.php'; +require_once $CFG->libdir.'/uploadlib.php'; // TODO: remove /** * Callback called when PEAR throws an error @@ -52,30 +52,24 @@ if ($CFG->debug >= DEBUG_ALL){ * You will write your own definition() method which performs the form set up. */ class moodleform { - var $_formname; // form name + protected $_formname; // form name /** * quickform object definition * * @var MoodleQuickForm */ - var $_form; + protected $_form; /** * globals workaround * * @var array */ - var $_customdata; - /** - * file upload manager - * - * @var upload_manager - */ - var $_upload_manager; // + protected $_customdata; /** * definition_after_data executed flag * @var definition_finalized */ - var $_definition_finalized = false; + protected $_definition_finalized = false; /** * The constructor function calls the abstract function definition() and it will then @@ -112,7 +106,6 @@ class moodleform { if (!$editable){ $this->_form->hardFreeze(); } - $this->set_upload_manager(new upload_manager()); $this->definition(); @@ -197,6 +190,8 @@ class moodleform { * Internal method. Validates all uploaded files. */ function _validate_files(&$files) { + global $CFG, $COURSE; + $files = array(); if (empty($_FILES)) { @@ -204,33 +199,106 @@ class moodleform { // note: server side rules do not work for files - use custom verification in validate() instead return true; } - $errors = array(); - $mform =& $this->_form; - // check the files - $status = $this->_upload_manager->preprocess_files(); + $errors = array(); + $filenames = array(); // now check that we really want each file foreach ($_FILES as $elname=>$file) { - if ($mform->elementExists($elname) and $mform->getElementType($elname)=='file') { - $required = $mform->isElementRequired($elname); - if (!empty($this->_upload_manager->files[$elname]['uploadlog']) and empty($this->_upload_manager->files[$elname]['clear'])) { - if (!$required and $file['error'] == UPLOAD_ERR_NO_FILE) { - // file not uploaded and not required - ignore it - continue; - } - $errors[$elname] = $this->_upload_manager->files[$elname]['uploadlog']; + $required = $this->_form->isElementRequired($elname); - } else if (!empty($this->_upload_manager->files[$elname]['clear'])) { - $files[$elname] = $this->_upload_manager->files[$elname]['tmp_name']; + if ($file['error'] == 4 and $file['size'] == 0) { + if ($required) { + $errors[$elname] = get_string('required'); } - } else { - print_error('cannotuploadfile'); + unset($_FILES[$elname]); + continue; + } + + if ($file['error'] > 0) { + switch ($file['error']) { + case 1: // UPLOAD_ERR_INI_SIZE + $errmessage = get_string('uploadserverlimit'); + break; + + case 2: // UPLOAD_ERR_FORM_SIZE + $errmessage = get_string('uploadformlimit'); + break; + + case 3: // UPLOAD_ERR_PARTIAL + $errmessage = get_string('uploadpartialfile'); + break; + + case 4: // UPLOAD_ERR_NO_FILE + $errmessage = get_string('uploadnofilefound'); + break; + + // Note: there is no error with a value of 5 + + case 6: // UPLOAD_ERR_NO_TMP_DIR + $errmessage = get_string('uploadnotempdir'); + break; + + case 7: // UPLOAD_ERR_CANT_WRITE + $errmessage = get_string('uploadcantwrite'); + break; + + case 8: // UPLOAD_ERR_EXTENSION + $errmessage = get_string('uploadextension'); + break; + + default: + $errmessage = get_string('uploadproblem', $file['name']); + } + $errors[$elname] = $errmessage; + unset($_FILES[$elname]); + continue; + } + + if (!is_uploaded_file($file['tmp_name'])) { + // TODO: improve error message + $errors[$elname] = get_string('error'); + unset($_FILES[$elname]); + continue; + } + + if (!$this->_form->elementExists($elname) or !$this->_form->getElementType($elname)=='file') { + // hmm, this file was not requested + unset($_FILES[$elname]); + continue; + } + +/* + // TODO: rethink the file scanning + if ($CFG->runclamonupload) { + if (!clam_scan_moodle_file($_FILES[$elname], $COURSE)) { + $errors[$elname] = $_FILES[$elname]['uploadlog']; + unset($_FILES[$elname]); + continue; + } + } +*/ + $filename = clean_param($_FILES[$elname]['name'], PARAM_FILE); + if ($filename === '') { + // TODO: improve error message - wrong chars + $errors[$elname] = get_string('error'); + unset($_FILES[$elname]); + continue; } + if (in_array($filename, $filenames)) { + // TODO: improve error message - duplicate name + $errors[$elname] = get_string('error'); + unset($_FILES[$elname]); + continue; + } + $filenames[] = $filename; + $_FILES[$elname]['name'] = $filename; + + $files[$elname] = $_FILES[$elname]['tmp_name']; } // return errors if found - if ($status and 0 == count($errors)){ + if (count($errors) == 0){ return true; } else { @@ -256,19 +324,8 @@ class moodleform { $this->_form->setDefaults($default_values); } - /** - * Set custom upload manager. - * Must be used BEFORE creating of file element! - * - * @param object $um - custom upload manager - */ function set_upload_manager($um=false) { - if ($um === false) { - $um = new upload_manager(); - } - $this->_upload_manager = $um; - - $this->_form->setMaxFileSize($um->config->maxbytes); + debugging('Not used anymore, please fix code!'); } /** @@ -370,7 +427,7 @@ class moodleform { /** * Return submitted data if properly submitted or returns NULL if validation fails or * if there is no submitted data. - * + * * note: $slashed param removed * * @return object submitted data; NULL if not valid or not submitted @@ -417,60 +474,147 @@ class moodleform { /** * Save verified uploaded files into directory. Upload process can be customised from definition() - * method by creating instance of upload manager and storing it in $this->_upload_form - * - * @param string $destination where to store uploaded files - * @return bool success + * NOTE: please use save_stored_file() or save_file() */ function save_files($destination) { - if ($this->is_submitted() and $this->is_validated()) { - return $this->_upload_manager->save_files($destination); - } + debugging('Not used anymore, please fix code! Use save_stored_file() or save_file() instead'); return false; } /** - * If we're only handling one file (if inputname was given in the constructor) - * this will return the (possibly changed) filename of the file. + * Returns name of uploaded file. + * @param string $elname, first element if null * @return mixed false in case of failure, string if ok */ - function get_new_filename() { - return $this->_upload_manager->get_new_filename(); + function get_new_filename($elname=null) { + if (!$this->is_submitted() or !$this->is_validated()) { + return false; + } + + if (is_null($elname)) { + if (empty($_FILES)) { + return false; + } + reset($_FILES); + $elname = key($_FILES); + } + if (!isset($_FILES[$elname])) { + return false; + } + + return $_FILES[$elname]['name']; } /** - * Get content of uploaded file. - * @param $element name of file upload element - * @return mixed false in case of failure, string if ok + * Save file to standard filesystem + * @param string $elname name of element + * @param string $pathname full path name of file + * @param bool $override override file if exists + * @return bool success */ - function get_file_content($elname) { + function save_file($elname, $pathname, $override=false) { if (!$this->is_submitted() or !$this->is_validated()) { return false; } - if (!$this->_form->elementExists($elname)) { + if (!isset($_FILES[$elname])) { return false; } - if (empty($this->_upload_manager->files[$elname]['clear'])) { + if (file_exists($pathname)) { + if ($override) { + if (!@unlink($pathname)) { + return false; + } + } else { + return false; + } + } + if (!$temp = @fopen($_FILES[$elname]['tmp_name'], "rb")) { + return false; + } + if (!$file = @fopen($pathname, "wb")) { + return false; + } + + while (!feof($temp)) { + $data = fread($temp, 65536); + fwrite($file, $data); + } + fclose($file); + fclose($temp); + + return true; + } + + /** + * Save file to local filesystem pool + * @param string $elname name of element + * @param int $contextid + * @param string $filearea + * @param string $filepath + * @param string $filename - use specified filename, if not specified name of uploaded file used + * @param bool $override override file if exists + * @param int $userid + * @return mixed stored_file object or false if error; may throw exception if duplicate found + */ + function save_stored_file($elname, $contextid, $filearea, $itemid, $filepath, $filename=null, $override=false, $userid=null) { + if (!$this->is_submitted() or !$this->is_validated()) { return false; - } + } - if (empty($this->_upload_manager->files[$elname]['tmp_name'])) { + if (!isset($_FILES[$elname])) { return false; } - $data = ""; - $file = @fopen($this->_upload_manager->files[$elname]['tmp_name'], "rb"); - if ($file) { - while (!feof($file)) { - $data .= fread($file, 1024); // TODO: do we really have to do this? + $filename = is_null($filename) ? $_FILES[$elname]['name'] : $filename; + + $fs = get_file_storage(); + + if ($file = $fs->get_file($contextid, $filearea, $itemid, $filepath, $filename)) { + if ($override) { + $file->delete(); + } else { + return false; } - fclose($file); - return $data; - } else { + } + + $file_record = new object(); + $file_record->contextid = $contextid; + $file_record->filearea = $filearea; + $file_record->itemid = $itemid; + $file_record->filepath = $filepath; + $file_record->filename = $filename; + $file_record->userid = $userid; + + return $fs->create_file_from_pathname($file_record, $_FILES[$elname]['tmp_name']); + } + + /** + * Get content of uploaded file. + * @param $element name of file upload element + * @return mixed false in case of failure, string if ok + */ + function get_file_content($elname) { + if (!$this->is_submitted() or !$this->is_validated()) { + return false; + } + + if (!isset($_FILES[$elname])) { return false; } + + if (!$file = @fopen($_FILES[$elname]['tmp_name'], "rb")) { + return false; + } + + $data = ''; + while (!feof($file)) { + $data .= fread($file, 4048); + } + fclose($file); + + return $data; } /** @@ -625,7 +769,7 @@ class moodleform { * @param array $attributes associative array of HTML attributes * @param int $originalValue The original general state of the checkboxes before the user first clicks this element */ - function add_checkbox_controller($groupid, $buttontext, $attributes, $originalValue = 0) { + function add_checkbox_controller($groupid, $buttontext, $attributes, $originalValue = 0) { global $CFG; if (empty($text)) { $text = get_string('selectallornone', 'form'); @@ -642,7 +786,7 @@ class moodleform { $mform->addElement('hidden', "checkbox_controller$groupid"); $mform->setConstants(array("checkbox_controller$groupid" => $new_select_value)); - + // Locate all checkboxes for this group and set their value, IF the optional param was given if (!is_null($select_value)) { foreach ($this->_form->_elements as $element) { @@ -654,7 +798,7 @@ class moodleform { $checkbox_controller_name = 'nosubmit_checkbox_controller' . $groupid; $mform->registerNoSubmitButton($checkbox_controller_name); - + // Prepare Javascript for submit element $js = "\n//\n"; - + require_once("$CFG->libdir/form/submitlink.php"); $submitlink = new MoodleQuickForm_submitlink($checkbox_controller_name, $attributes); $submitlink->_js = $js; $submitlink->_onclick = "html_quickform_toggle_checkboxes($groupid); return false;"; - $mform->addElement($submitlink); + $mform->addElement($submitlink); $mform->setDefault($checkbox_controller_name, $text); } @@ -1702,7 +1846,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{ } function renderElement(&$element, $required, $error){ - //manipulate id of all elements before rendering + //manipulate id of all elements before rendering if (!is_null($element->getAttribute('id'))) { $id = $element->getAttribute('id'); } else { @@ -1715,7 +1859,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{ } //adding stuff to place holders in template - //check if this is a group element first + //check if this is a group element first if (($this->_inGroup) and !empty($this->_groupElementTemplate)) { // so it gets substitutions for *each* element $html = $this->_groupTemplates[$element->getName()]; @@ -1753,8 +1897,8 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{ } elseif (!isset($this->_templates[$element->getName()])) { $this->_templates[$element->getName()] = $html; - } - + } + parent::renderElement($element, $required, $error); } @@ -1814,7 +1958,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{ document.write("'.addslashes_js($button_js).'") //]]> '; // the extra div should fix xhtml validation - + $header_html = str_replace('{button}', $button, $header_html); } else { $header_html = str_replace('{button}', '', $header_html); diff --git a/lib/gdlib.php b/lib/gdlib.php index 24c95afbf5..7bb5b8b5e9 100644 --- a/lib/gdlib.php +++ b/lib/gdlib.php @@ -128,26 +128,25 @@ function create_profile_image_destination($id, $dir='user') { * it and saves it in the right place to be a "user" or "group" image. * * @param int $id user or group id - * @param object $uploadmanager object referencing the image + * @param object $userform with imagefile upload field * @param string $dir type of entity - groups, user, ... * @return boolean success */ -function save_profile_image($id, $uploadmanager, $dir='user') { - - if (!$uploadmanager) { - return false; - } +function save_profile_image($id, $userform, $dir='user') { $destination = create_profile_image_destination($id, $dir); if ($destination === false) { return false; } - if (!$uploadmanager->save_files($destination)) { + $filename = $userform->get_new_filename('imagefile'); + $pathname = $destination.'/'.$filename; + + if (!$userform->save_file('imagefile', $pathname, true)) { return false; } - return process_profile_image($uploadmanager->get_new_filepath(), $destination); + return process_profile_image($pathname, $destination); } /** diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 1bb2c9a651..ea3a299cea 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -4471,6 +4471,45 @@ function email_welcome_message_to_user($course, $user=NULL) { /// FILE HANDLING ///////////////////////////////////////////// +/** + * Returns local file storage instance + * @return object file_storage + */ +function get_file_storage() { + global $CFG; + + static $fs = null; + + if ($fs) { + return $fs; + } + + require_once("$CFG->libdir/filelib.php"); + + $fs = new file_storage(); + + return $fs; +} + +/** + * Returns local file storage instance + * @return object file_storage + */ +function get_file_browser() { + global $CFG; + + static $fb = null; + + if ($fb) { + return $fb; + } + + require_once("$CFG->libdir/filelib.php"); + + $fb = new file_browser(); + + return $fb; +} /** * Makes an upload directory for a particular module. @@ -7776,35 +7815,11 @@ function check_dir_exists($dir, $create=false, $recursive=false) { $status = true; - if(!is_dir($dir)) { + if (!is_dir($dir)) { if (!$create) { $status = false; } else { - umask(0000); - if ($recursive) { - /// PHP 5.0 has recursive mkdir parameter, but 4.x does not :-( - $dir = str_replace('\\', '/', $dir); //windows compatibility - /// We are going to make it recursive under $CFG->dataroot only - /// (will help sites running open_basedir security and others) - $dir = str_replace($CFG->dataroot . '/', '', $dir); - $dirs = explode('/', $dir); /// Extract path parts - /// Iterate over each part with start point $CFG->dataroot - $dir = $CFG->dataroot . '/'; - foreach ($dirs as $part) { - if ($part == '') { - continue; - } - $dir .= $part.'/'; - if (!is_dir($dir)) { - if (!mkdir($dir, $CFG->directorypermissions)) { - $status = false; - break; - } - } - } - } else { - $status = mkdir($dir, $CFG->directorypermissions); - } + $status = mkdir($dir, $CFG->directorypermissions, $recursive); } } return $status; diff --git a/lib/weblib.php b/lib/weblib.php index 751e26904b..d4f74fa4da 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -7142,7 +7142,7 @@ class progress_bar { private $lastcall; private $time_start; private $minimum_time = 2; //min time between updates. - function __construct($html_id = 'pid', $width = 100, $autostart = false){ + function __construct($html_id = 'pid', $width = 500, $autostart = false){ $this->html_id = $html_id; $this->clr = new stdClass; $this->clr->done = 'green'; diff --git a/mod/assignment/db/upgrade.php b/mod/assignment/db/upgrade.php index 6e9982500c..914b399363 100644 --- a/mod/assignment/db/upgrade.php +++ b/mod/assignment/db/upgrade.php @@ -35,7 +35,125 @@ function xmldb_assignment_upgrade($oldversion=0) { $DB->set_debug(true); upgrade_mod_savepoint($result, 2007101511, 'assignment'); } - + + if ($result && $oldversion < 2008073000) { + + ///////////////////////////////////// + /// new file storage upgrade code /// + ///////////////////////////////////// + + $fs = get_file_storage(); + + $sql = "SELECT s.id, s.userid, s.teacher, s.assignment, a.course + FROM {assignment_submissions} s + JOIN {assignment} a ON a.id = s.assignment + ORDER BY a.course, s.assignment"; + + $count = $DB->count_records_sql($sql); + + $lastcourse = 0; + $lastassignment = 0; + + if ($rs = $DB->get_recordset_sql($sql)) { + + $pbar = new progress_bar('migrateassignmentfiles', 500, true); + + $olddebug = $DB->get_debug(); +// $DB->set_debug(false); // lower debug level, there might be many files + $i = 0; + foreach ($rs as $submission) { + $i++; + $basepath = "$CFG->dataroot/$submission->course/$CFG->moddata/assignment/$submission->assignment/$submission->userid/"; + if (!file_exists($basepath)) { + //no files + continue; + } + $context = get_context_instance(CONTEXT_MODULE, $submission->assignment); + + // migrate submitted files fisrt + $path = $basepath; + $filearea = 'assignment_submission'; + $items = new DirectoryIterator($path); + foreach ($items as $item) { + if (!$item->isFile()) { + continue; + } + if (!$item->isReadable()) { + notify(" File not readable, skipping: ".$path.$item->getFilename()); + continue; + } + $filename = clean_param($item->getFilename(), PARAM_FILE); + if ($filename === '') { + continue; + } + if (!$fs->file_exists($context->id, $filearea, '0', '/', $filename)) { + $file_record = array('contextid'=>$context->id, 'filearea'=>$filearea, 'itemid'=>$submission->userid, 'filepath'=>'/', 'filename'=>$filename, 'userid'=>$submission->userid); + if ($fs->create_file_from_pathname($file_record, $path.$item->getFilename())) { + unlink($path.$item->getFilename()); + } + } + } + unset($items); //release file handles + + // migrate teacher response files + $path = $basepath.'responses/'; + if (file_exists($path)) { + $filearea = 'assignment_response'; + $items = new DirectoryIterator($path); + foreach ($items as $item) { + if (!$item->isFile()) { + continue; + } + $filename = clean_param($item->getFilename(), PARAM_FILE); + if ($filename === '') { + continue; + } + if (!$fs->file_exists($context->id, $filearea, '0', '/', $filename)) { + $file_record = array('contextid'=>$context->id, 'filearea'=>$filearea, 'itemid'=>$submission->userid, 'filepath'=>'/', 'filename'=>$filename, + 'timecreated'=>$item->getCTime(), 'timemodified'=>$item->getMTime()); + if ($submission->teacher) { + $file_record['userid'] = $submission->teacher; + } + if ($fs->create_file_from_pathname($file_record, $path.$item->getFilename())) { + unlink($path.$item->getFilename()); + } + } + } + unset($items); //release file handles + @rmdir("$CFG->dataroot/$submission->course/$CFG->moddata/assignment/$submission->assignment/$submission->userid/responses/"); + } + + @rmdir("$CFG->dataroot/$submission->course/$CFG->moddata/assignment/$submission->assignment/$submission->userid/"); + + if ($lastassignment and $lastassignment != $submission->assignment) { + @rmdir("$CFG->dataroot/$lastcourse/$CFG->moddata/assignment/$lastassignment"); + } + + if ($lastcourse and $lastcourse != $submission->course) { + @rmdir("$CFG->dataroot/$lastcourse/$CFG->moddata/assignment"); + @rmdir("$CFG->dataroot/$lastcourse/$CFG->moddata"); + @rmdir("$CFG->dataroot/$lastcourse"); + } + $lastsubmission = $submission->assignment; + $lastcourse = $submission->course; + + $pbar->update($i, $count, "Migrated assignment submissions - $i/$count."); + } + $DB->set_debug($olddebug); // reset debug level + $rs->close(); + + // cleanup after the last submission + if ($lastcourse) { + @rmdir("$CFG->dataroot/$lastcourse/$CFG->moddata/assignment/$lastassignment"); + @rmdir("$CFG->dataroot/$lastcourse/$CFG->moddata/assignment"); + @rmdir("$CFG->dataroot/$lastcourse/$CFG->moddata"); + @rmdir("$CFG->dataroot/$lastcourse"); + } + } + + upgrade_mod_savepoint($result, 2008073000, 'assignment'); + } + return $result; } diff --git a/mod/assignment/lib.php b/mod/assignment/lib.php index 30cdb12e91..99f1365005 100644 --- a/mod/assignment/lib.php +++ b/mod/assignment/lib.php @@ -6,6 +6,7 @@ */ require_once($CFG->libdir.'/eventslib.php'); +require_once($CFG->libdir.'/formslib.php'); DEFINE ('ASSIGNMENT_COUNT_WORDS', 1); DEFINE ('ASSIGNMENT_COUNT_LETTERS', 2); @@ -1600,6 +1601,11 @@ class assignment_base { } } + function send_file($filearea, $args) { + debugging('plugin does not implement file sending', DEBUG_DEVELOPER); + return false; + } + /** * Returns a list of teachers that should be grading given submission */ @@ -1692,34 +1698,34 @@ class assignment_base { $userid = $USER->id; } - $filearea = $this->file_area_name($userid); - $output = ''; - if ($basedir = $this->file_area($userid)) { - if ($files = get_directory_list($basedir)) { - require_once($CFG->libdir.'/filelib.php'); - $p = array( - 'userid' => $userid, - 'assignmentid' => $this->cm->id, - ); - foreach ($files as $key => $file) { - - $icon = mimeinfo('icon', $file); - $ffurl = get_file_url("$filearea/$file", array('forcedownload'=>1)); - - $output .= ''.$icon.''. - ''.$file.''; - if ($this->portfolio_exportable() && true) { // @todo replace with capability check - $p['file'] = $file; - $output .= portfolio_add_button('assignment_portfolio_caller', $p, null, false, true); - } - $output .= '
'; - } - if ($this->portfolio_exportable() && true) { //@todo replace with check capability - unset($p['file']);// for all files - $output .= '
' . portfolio_add_button('assignment_portfolio_caller', $p, null, true, true); + $fs = get_file_storage(); + $browser = get_file_browser(); + + $found = false; + + if ($files = $fs->get_area_files($this->context->id, 'assignment_submission', $userid, "timemodified", false)) { + $p = array( + 'userid' => $userid, + 'assignmentid' => $this->cm->id, + ); + foreach ($files as $file) { + $filename = $file->get_filename(); + $found = true; + $mimetype = $file->get_mimetype(); + $icon = mimeinfo_from_type('icon', $mimetype); + $path = $browser->encodepath($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/assignment_submission/'.$userid.'/'.$filename); + $output .= ''.$icon.''.s($filename).''; + if ($this->portfolio_exportable() && true) { // @todo replace with capability check + $p['file'] = $file; + $output .= portfolio_add_button('assignment_portfolio_caller', $p, null, false, true); } + $output .= '
'; + } + if ($this->portfolio_exportable() && true) { //@todo replace with check capability + unset($p['file']);// for all files + $output .= '
' . portfolio_add_button('assignment_portfolio_caller', $p, null, true, true); } } @@ -1738,38 +1744,9 @@ class assignment_base { * @return int */ function count_user_files($userid) { - global $CFG; - - $filearea = $this->file_area_name($userid); - - if ( is_dir($CFG->dataroot.'/'.$filearea) && $basedir = $this->file_area($userid)) { - if ($files = get_directory_list($basedir)) { - return count($files); - } - } - return 0; - } - - /** - * Creates a directory file name, suitable for make_upload_directory() - * - * @param $userid int The user id - * @return string path to file area - */ - function file_area_name($userid) { - global $CFG; - - return $this->course->id.'/'.$CFG->moddata.'/assignment/'.$this->assignment->id.'/'.$userid; - } - - /** - * Makes an upload directory - * - * @param $userid int The user id - * @return string path to file area. - */ - function file_area($userid) { - return make_upload_directory( $this->file_area_name($userid) ); + $fs = get_file_storage(); + $files = $fs->get_area_files($this->context->id, 'assignment_submission', $userid, "id", false); + return count($files); } /** @@ -1829,12 +1806,14 @@ class assignment_base { */ function user_complete($user) { if ($submission = $this->get_submission($user->id)) { - if ($basedir = $this->file_area($user->id)) { - if ($files = get_directory_list($basedir)) { - $countfiles = count($files)." ".get_string("uploadedfiles", "assignment"); - foreach ($files as $file) { - $countfiles .= "; $file"; - } + + $fs = get_file_storage(); + $browser = get_file_browser(); + + if ($files = $fs->get_area_files($this->context->id, 'assignment_submission', $user->id, "timemodified", false)) { + $countfiles = count($files)." ".get_string("uploadedfiles", "assignment"); + foreach ($files as $file) { + $countfiles .= "; ".$file->get_filename(); } } @@ -1962,6 +1941,27 @@ class assignment_base { } } ////// End of the assignment_base class +class mod_assignment_upload_file_form extends moodleform { + function definition() { + $mform = $this->_form; + $instance = $this->_customdata; + + //TODO: improve upload size checking + $mform->setMaxFileSize($instance->assignment->maxbytes); + + // visible elements + $mform->addElement('file', 'newfile', get_string('uploadafile')); + + // hidden params + $mform->addElement('hidden', 'id', $instance->cm->id); + $mform->setType('id', PARAM_INT); + $mform->addElement('hidden', 'action', 'uploadfile'); + $mform->setType('action', PARAM_ALPHA); + + // buttons + $this->add_action_buttons(false, get_string('uploadthisfile')); + } +} /// OTHER STANDARD FUNCTIONS //////////////////////////////////////////////////////// @@ -2347,6 +2347,22 @@ function assignment_get_participants($assignmentid) { return ($students); } +function assignment_pluginfile($course, $cminfo, $context, $filearea, $args) { + global $CFG, $DB; + + if (!$assignment = $DB->get_record('assignment', array('id'=>$cminfo->instance))) { + return false; + } + if (!$cm = get_coursemodule_from_instance('assignment', $assignment->id, $course->id)) { + return false; + } + + require_once($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php'); + $assignmentclass = 'assignment_'.$assignment->assignmenttype; + $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course); + + return $assignmentinstance->send_file($filearea, $args); +} /** * Checks if a scale is being used by an assignment * @@ -3143,6 +3159,7 @@ class assignment_portfolio_caller extends portfolio_module_caller_base { if (is_callable(array($this->assignment, 'portfolio_prepare_package'))) { return $this->assignment->portfolio_prepare_package($tempdir); } +error('TODO: covert'); // default... $filearea = $CFG->dataroot . '/' . $this->assignment->file_area_name($this->userid); //@todo penny this is a dreadful thing to have to call (replace with files api anyway) @@ -3158,6 +3175,8 @@ class assignment_portfolio_caller extends portfolio_module_caller_base { if (is_callable(array($this->assignment, 'portfolio_get_sha1'))) { return $this->assignment->portfolio_get_sha1(); } + +error('TODO: covert'); // default ... $filearea = $CFG->dataroot . '/' . $this->assignment->file_area_name($this->userid); if ($this->file) { diff --git a/mod/assignment/type/upload/assignment.class.php b/mod/assignment/type/upload/assignment.class.php index 1d68566c33..a2d0b6f03d 100644 --- a/mod/assignment/type/upload/assignment.class.php +++ b/mod/assignment/type/upload/assignment.class.php @@ -1,5 +1,4 @@ libdir.'/formslib.php'); require_once($CFG->libdir . '/portfoliolib.php'); require_once($CFG->dirroot . '/mod/assignment/lib.php'); @@ -157,29 +156,14 @@ class assignment_upload extends assignment_base { $submission = $this->get_submission($USER->id); - $struploadafile = get_string('uploadafile'); - $maxbytes = $this->assignment->maxbytes == 0 ? $this->course->maxbytes : $this->assignment->maxbytes; - $strmaxsize = get_string('maxsize', '', display_size($maxbytes)); - if ($this->is_finalized($submission)) { // no uploading return; } if ($this->can_upload_file($submission)) { - echo '
'; - echo '
'; - echo '
'; - echo "

$struploadafile ($strmaxsize)

"; - echo ''; - echo ''; - require_once($CFG->libdir.'/uploadlib.php'); - upload_print_form_fragment(1,array('newfile'),null,false,null,0,$this->assignment->maxbytes,false); - echo ''; - echo '
'; - echo '
'; - echo '
'; - echo '
'; + $mform = new mod_assignment_upload_file_form('upload.php', $this); + $mform->display(); } } @@ -249,21 +233,15 @@ class assignment_upload extends assignment_base { $offset = optional_param('offset', 0, PARAM_INT); $forcerefresh = optional_param('forcerefresh', 0, PARAM_BOOL); + $mform = new mod_assignment_upload_response_form("$CFG->wwwroot/mod/assignment/upload.php", $this); + + $mform->set_data(array('id'=>$this->cm->id, 'offset'=>$offset, 'forcerefresh'=>$forcerefresh, 'userid'=>$submission->userid, 'mode'=>$mode)); + $output = get_string('responsefiles', 'assignment').': '; - $output .= '
wwwroot/mod/assignment/upload.php\">"; - $output .= '
'; - $output .= ''; - $output .= ''; - $output .= ''; - $output .= ''; - $output .= ''; - require_once($CFG->libdir.'/uploadlib.php'); - $output .= upload_print_form_fragment(1,array('newfile'),null,false,null,0,0,true); - $output .= ''; - $output .= '
'; - $output .= '
'; + ob_start(); + $mform->display(); + $output = ob_get_clean(); if ($forcerefresh) { $output .= $this->update_main_listing($submission); @@ -285,30 +263,35 @@ class assignment_upload extends assignment_base { function print_student_answer($userid, $return=false){ global $CFG; - $filearea = $this->file_area_name($userid); $submission = $this->get_submission($userid); $output = ''; - if ($basedir = $this->file_area($userid)) { - if ($this->drafts_tracked() and $this->isopen() and !$this->is_finalized($submission)) { - $output .= ''.get_string('draft', 'assignment').': '; - } + if ($this->drafts_tracked() and $this->isopen() and !$this->is_finalized($submission)) { + $output .= ''.get_string('draft', 'assignment').': '; + } - if ($this->notes_allowed() and !empty($submission->data1)) { - $output .= link_to_popup_window ('/mod/assignment/type/upload/notes.php?id='.$this->cm->id.'&userid='.$userid, - 'notes'.$userid, get_string('notes', 'assignment'), 500, 780, get_string('notes', 'assignment'), 'none', true, 'notesbutton'.$userid); - $output .= ' '; - } + if ($this->notes_allowed() and !empty($submission->data1)) { + $output .= link_to_popup_window ('/mod/assignment/type/upload/notes.php?id='.$this->cm->id.'&userid='.$userid, + 'notes'.$userid, get_string('notes', 'assignment'), 500, 780, get_string('notes', 'assignment'), 'none', true, 'notesbutton'.$userid); + $output .= ' '; + } - if ($files = get_directory_list($basedir, 'responses')) { - require_once($CFG->libdir.'/filelib.php'); - foreach ($files as $key => $file) { - $icon = mimeinfo('icon', $file); - $ffurl = get_file_url("$filearea/$file"); - $output .= ''.$icon.''.$file.' '; - } + $fs = get_file_storage(); + $browser = get_file_browser(); + + if ($files = $fs->get_area_files($this->context->id, 'assignment_submission', $userid, "timemodified", false)) { + + foreach ($files as $file) { + $filename = $file->get_filename(); + $found = true; + $mimetype = $file->get_mimetype(); + $icon = mimeinfo_from_type('icon', $mimetype); + $path = $browser->encodepath($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/assignment_submission/'.$userid.'/'.$filename); + $output .= ''.$icon.''.s($filename).' '; + } + } $output = '
'.$output.'
'; $output .= '
'; @@ -337,8 +320,6 @@ class assignment_upload extends assignment_base { $userid = $USER->id; } - $filearea = $this->file_area_name($userid); - $output = ''; $submission = $this->get_submission($userid); @@ -357,36 +338,37 @@ class assignment_upload extends assignment_base { } - if ($basedir = $this->file_area($userid)) { - if ($files = get_directory_list($basedir, 'responses')) { - require_once($CFG->libdir.'/filelib.php'); - $p = array( - 'userid' => $userid, - 'assignmentid' => $this->cm->id, - ); - foreach ($files as $key => $file) { - - $icon = mimeinfo('icon', $file); - $ffurl = get_file_url("$filearea/$file"); - - $output .= ''.$icon.''.$file.''; - - if ($candelete) { - $delurl = "$CFG->wwwroot/mod/assignment/delete.php?id={$this->cm->id}&file=$file&userid={$submission->userid}&mode=$mode&offset=$offset"; - - $output .= ' ' - .' '; - } - if (true) { // @todo penny replace with capability check - $p['file'] = $file; - $output .= portfolio_add_button('assignment_portfolio_caller', $p, '/mod/assignment/lib.php', false, true); - } - $output .= '
'; + $fs = get_file_storage(); + $browser = get_file_browser(); + + if ($files = $fs->get_area_files($this->context->id, 'assignment_submission', $userid, "timemodified", false)) { + $p = array( + 'userid' => $userid, + 'assignmentid' => $this->cm->id, + ); + foreach ($files as $file) { + $filename = $file->get_filename(); + $mimetype = $file->get_mimetype(); + $icon = mimeinfo_from_type('icon', $mimetype); + $path = $browser->encodepath($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/assignment_submission/'.$userid.'/'.$filename); + $output .= ''.$icon.''.s($filename).''; + + if ($candelete) { + $delurl = "$CFG->wwwroot/mod/assignment/delete.php?id={$this->cm->id}&file=".rawurlencode($filename)."&userid={$submission->userid}&mode=$mode&offset=$offset"; + + $output .= ' ' + .' '; } - if (true) { //@todo penny replace with check capability - unset($p['file']);// for all files - $output .= '
' . portfolio_add_button('assignment_portfolio_caller', $p, '/mod/assignment/lib.php', true, true); + + if (true) { // @todo penny replace with capability check + $p['file'] = $filename; + $output .= portfolio_add_button('assignment_portfolio_caller', $p, '/mod/assignment/lib.php', false, true); } + $output .= '
'; + } + if (true) { //@todo penny replace with check capability + unset($p['file']);// for all files + $output .= '
' . portfolio_add_button('assignment_portfolio_caller', $p, '/mod/assignment/lib.php', true, true); } } @@ -414,40 +396,35 @@ class assignment_upload extends assignment_base { $mode = optional_param('mode', '', PARAM_ALPHA); $offset = optional_param('offset', 0, PARAM_INT); - $filearea = $this->file_area_name($userid).'/responses'; - $output = ''; $candelete = $this->can_manage_responsefiles(); $strdelete = get_string('delete'); - if ($basedir = $this->file_area($userid)) { - $basedir .= '/responses'; + $fs = get_file_storage(); + $browser = get_file_browser(); - if ($files = get_directory_list($basedir)) { - require_once($CFG->libdir.'/filelib.php'); - foreach ($files as $key => $file) { + if ($files = $fs->get_area_files($this->context->id, 'assignment_response', $userid, "timemodified", false)) { + foreach ($files as $file) { + $filename = $file->get_filename(); + $found = true; + $mimetype = $file->get_mimetype(); + $icon = mimeinfo_from_type('icon', $mimetype); + $path = $browser->encodepath($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/assignment_response/'.$userid.'/'.$filename); - $icon = mimeinfo('icon', $file); + $output .= ''.$icon.''.$filename.''; - $ffurl = get_file_url("$filearea/$file"); + if ($candelete) { + $delurl = "$CFG->wwwroot/mod/assignment/delete.php?id={$this->cm->id}&file=".rawurlencode($filename)."&userid=$userid&mode=$mode&offset=$offset&action=response"; - $output .= ''.$icon.''.$file.''; - - if ($candelete) { - $delurl = "$CFG->wwwroot/mod/assignment/delete.php?id={$this->cm->id}&file=$file&userid=$userid&mode=$mode&offset=$offset&action=response"; - - $output .= ' ' - .' '; - } - - $output .= ' '; + $output .= ' ' + .' '; } - } + $output .= ' '; + } $output = '
'.$output.'
'; - } if ($return) { @@ -549,7 +526,7 @@ class assignment_upload extends assignment_base { } function upload_responsefile() { - global $CFG; + global $CFG, $USER; $userid = required_param('userid', PARAM_INT); $mode = required_param('mode', PARAM_ALPHA); @@ -557,31 +534,28 @@ class assignment_upload extends assignment_base { $returnurl = "submissions.php?id={$this->cm->id}&userid=$userid&mode=$mode&offset=$offset"; - if (data_submitted() and $this->can_manage_responsefiles()) { - $dir = $this->file_area_name($userid).'/responses'; - check_dir_exists($CFG->dataroot.'/'.$dir, true, true); - - require_once($CFG->dirroot.'/lib/uploadlib.php'); - $um = new upload_manager('newfile',false,true,$this->course,false,0,true); - - if (!$um->process_file_uploads($dir)) { - print_header(get_string('upload')); - notify(get_string('uploaderror', 'assignment')); - echo $um->get_errors(); - print_continue($returnurl); - print_footer('none'); - die; + $mform = new mod_assignment_upload_response_form(null, $this); + if ($mform->get_data() and $this->can_manage_responsefiles()) { + $fs = get_file_storage(); + $filename = $mform->get_new_filename('newfile'); + if ($filename !== false) { + if (!$fs->file_exists($this->context->id, 'assignment_response', $userid, '/', $filename)) { + if ($file = $mform->save_stored_file('newfile', $this->context->id, 'assignment_response', $userid, '/', $filename, false, $USER->id)) { + redirect($returnurl); + } + } } } - redirect($returnurl); + print_header(get_string('upload')); + notify(get_string('uploaderror', 'assignment')); + print_continue($returnurl); + print_footer('none'); + die; } function upload_file() { global $CFG, $USER, $DB; - $mode = optional_param('mode', '', PARAM_ALPHA); - $offset = optional_param('offset', 0, PARAM_INT); - $returnurl = 'view.php?id='.$this->cm->id; $filecount = $this->count_user_files($USER->id); @@ -595,44 +569,71 @@ class assignment_upload extends assignment_base { die; } - $dir = $this->file_area_name($USER->id); - check_dir_exists($CFG->dataroot.'/'.$dir, true, true); // better to create now so that student submissions do not block it later - - require_once($CFG->dirroot.'/lib/uploadlib.php'); - $um = new upload_manager('newfile',false,true,$this->course,false,$this->assignment->maxbytes,true); - - if ($um->process_file_uploads($dir)) { - $submission = $this->get_submission($USER->id, true); //create new submission if needed - $updated = new object(); - $updated->id = $submission->id; - $updated->timemodified = time(); - - if ($DB->update_record('assignment_submissions', $updated)) { - add_to_log($this->course->id, 'assignment', 'upload', - 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id); - $submission = $this->get_submission($USER->id); - $this->update_grade($submission); - if (!$this->drafts_tracked()) { - $this->email_teachers($submission); + $mform = new mod_assignment_upload_file_form('upload.php', $this); + if ($mform->get_data()) { + $fs = get_file_storage(); + $filename = $mform->get_new_filename('newfile'); + if ($filename !== false) { + if (!$fs->file_exists($this->context->id, 'assignment_submission', $USER->id, '/', $filename)) { + if ($file = $mform->save_stored_file('newfile', $this->context->id, 'assignment_submission', $USER->id, '/', $filename, false, $USER->id)) { + $submission = $this->get_submission($USER->id, true); //create new submission if needed + $submission->timemodified = time(); + if ($DB->update_record('assignment_submissions', $submission)) { + add_to_log($this->course->id, 'assignment', 'upload', + 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id); + $this->update_grade($submission); + if (!$this->drafts_tracked()) { + $this->email_teachers($submission); + } + redirect('view.php?id='.$this->cm->id); + } else { + $file->delete(); + } + } } - } else { - $new_filename = $um->get_new_filename(); - $this->view_header(get_string('upload')); - notify(get_string('uploadnotregistered', 'assignment', $new_filename)); - print_continue($returnurl); - $this->view_footer(); - die; } - redirect('view.php?id='.$this->cm->id); } + $this->view_header(get_string('upload')); notify(get_string('uploaderror', 'assignment')); - echo $um->get_errors(); print_continue($returnurl); $this->view_footer(); die; } + function send_file($filearea, $args) { + global $CFG, $DB, $USER; + require_once($CFG->libdir.'/filelib.php'); + + require_login($this->course, false, $this->cm); + + $userid = (int)array_shift($args); + $relativepath = '/'.implode('/', $args); + $fullpath = $this->context->id.$filearea.$userid.$relativepath; + + $fs = get_file_storage(); + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + return false; + } + + if ($filearea === 'assignment_submission') { + if ($USER->id != $userid and !has_capability('mod/assignment:grade', $this->context)) { + return false; + } + + } else if ($filearea === 'assignment_response') { + if ($USER->id != $userid and !has_capability('mod/assignment:grade', $this->context)) { + return false; + } + + } else { + return false; + } + + send_stored_file($file, 0, 0, true); // downlaod MUST be forced - security! + } + function finalize() { global $USER, $DB; @@ -777,10 +778,9 @@ class assignment_upload extends assignment_base { die; } - $dir = $this->file_area_name($userid).'/responses'; - $filepath = $CFG->dataroot.'/'.$dir.'/'.$file; - if (file_exists($filepath)) { - if (@unlink($filepath)) { + $fs = get_file_storage(); + if ($file = $fs->get_file($this->context->id, 'assignment_submission', $userid, '/', $file)) { + if ($file->delete()) { redirect($returnurl); } } @@ -824,7 +824,6 @@ class assignment_upload extends assignment_base { $this->view_footer(); die; } - $dir = $this->file_area_name($userid); if (!data_submitted() or !$confirm) { $optionsyes = array ('id'=>$this->cm->id, 'file'=>$file, 'userid'=>$userid, 'confirm'=>1, 'sesskey'=>sesskey(), 'mode'=>$mode, 'offset'=>$offset); @@ -843,16 +842,13 @@ class assignment_upload extends assignment_base { die; } - $filepath = $CFG->dataroot.'/'.$dir.'/'.$file; - if (file_exists($filepath)) { - if (@unlink($filepath)) { - $updated = new object(); - $updated->id = $submission->id; - $updated->timemodified = time(); - if ($DB->update_record('assignment_submissions', $updated)) { + $fs = get_file_storage(); + if ($file = $fs->get_file($this->context->id, 'assignment_submission', $userid, '/', $file)) { + if ($file->delete()) { + $submission->timemodified = time(); + if ($DB->update_record('assignment_submissions', $submission)) { add_to_log($this->course->id, 'assignment', 'upload', //TODO: add delete action to log 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id); - $submission = $this->get_submission($userid); $this->update_grade($submission); } redirect($returnurl); @@ -997,37 +993,10 @@ class assignment_upload extends assignment_base { return (boolean)$this->assignment->var2; } - /** - * Count the files uploaded by a given user - * - * @param $userid int The user id - * @return int - */ - function count_user_files($userid) { - global $CFG; - - $filearea = $this->file_area_name($userid); - - if ( is_dir($CFG->dataroot.'/'.$filearea) && $basedir = $this->file_area($userid)) { - if ($files = get_directory_list($basedir, 'responses')) { - return count($files); - } - } - return 0; - } - function count_responsefiles($userid) { - global $CFG; - - $filearea = $this->file_area_name($userid).'/responses'; - - if ( is_dir($CFG->dataroot.'/'.$filearea) && $basedir = $this->file_area($userid)) { - $basedir .= '/responses'; - if ($files = get_directory_list($basedir)) { - return count($files); - } - } - return 0; + $fs = get_file_storage(); + $files = $fs->get_area_files($this->context->id, 'assignment_response', $userid, "id", false); + return count($files); } function setup_elements(&$mform) { @@ -1078,7 +1047,7 @@ class assignment_upload extends assignment_base { class mod_assignment_upload_notes_form extends moodleform { function definition() { - $mform =& $this->_form; + $mform = $this->_form; // visible elements $mform->addElement('htmleditor', 'text', get_string('notes', 'assignment'), array('cols'=>85, 'rows'=>30)); @@ -1089,13 +1058,40 @@ class mod_assignment_upload_notes_form extends moodleform { $mform->addElement('hidden', 'id', 0); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'action', 'savenotes'); - $mform->setType('id', PARAM_ALPHA); + $mform->setType('action', PARAM_ALPHA); // buttons $this->add_action_buttons(); } } +class mod_assignment_upload_response_form extends moodleform { + function definition() { + $mform = $this->_form; + $instance = $this->_customdata; + + // visible elements + $mform->addElement('file', 'newfile', get_string('uploadafile')); + + // hidden params + $mform->addElement('hidden', 'id', $instance->cm->id); + $mform->setType('id', PARAM_INT); + $mform->addElement('hidden', 'action', 'uploadresponse'); + $mform->setType('action', PARAM_ALPHA); + $mform->addElement('hidden', 'mode'); + $mform->setType('mode', PARAM_ALPHA); + $mform->addElement('hidden', 'offset'); + $mform->setType('offset', PARAM_INT); + $mform->addElement('hidden', 'forcerefresh'); + $mform->setType('forcerefresh', PARAM_INT); + $mform->addElement('hidden', 'userid'); + $mform->setType('userid', PARAM_INT); + + // buttons + $this->add_action_buttons(false, get_string('uploadthisfile')); + } +} + ?> diff --git a/mod/assignment/type/uploadsingle/assignment.class.php b/mod/assignment/type/uploadsingle/assignment.class.php index 886117b587..96d6ee1ae0 100644 --- a/mod/assignment/type/uploadsingle/assignment.class.php +++ b/mod/assignment/type/uploadsingle/assignment.class.php @@ -8,25 +8,20 @@ class assignment_uploadsingle extends assignment_base { function print_student_answer($userid, $return=false){ - global $CFG, $USER; + global $CFG, $USER; - $filearea = $this->file_area_name($userid); + $fs = get_file_storage(); + $browser = get_file_browser(); - $output = ''; + if ($files = $fs->get_area_files($this->context->id, 'assignment_submission', $userid, "timemodified", false)) { - if ($basedir = $this->file_area($userid)) { - if ($files = get_directory_list($basedir)) { - require_once($CFG->libdir.'/filelib.php'); - foreach ($files as $key => $file) { - - $icon = mimeinfo('icon', $file); - $ffurl = get_file_url("$filearea/$file"); - - //died right here - //require_once($ffurl); - $output = ''.$icon.''. - ''.$file.'
'; - } + foreach ($files as $file) { + $filename = $file->get_filename(); + $found = true; + $mimetype = $file->get_mimetype(); + $icon = mimeinfo_from_type('icon', $mimetype); + $path = $browser->encodepath($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/assignment_submission/'.$userid.'/'.$filename); + $output .= ''.$icon.''.s($filename).'
'; } } @@ -74,24 +69,8 @@ class assignment_uploadsingle extends assignment_base { function view_upload_form() { - global $CFG; - $struploadafile = get_string("uploadafile"); - - $maxbytes = $this->assignment->maxbytes == 0 ? $this->course->maxbytes : $this->assignment->maxbytes; - $strmaxsize = get_string('maxsize', '', display_size($maxbytes)); - - echo '
'; - echo '
wwwroot/mod/assignment/upload.php\">"; - echo '
'; - echo "

$struploadafile ($strmaxsize)

"; - echo ''; - require_once($CFG->libdir.'/uploadlib.php'); - upload_print_form_fragment(1,array('newfile'),false,null,0,$this->assignment->maxbytes,false); - echo ''; - echo '
'; - echo '
'; - echo '
'; + $mform = new mod_assignment_upload_file_form('upload.php', $this); + $mform->display(); } @@ -112,46 +91,32 @@ class assignment_uploadsingle extends assignment_base { } } - $dir = $this->file_area_name($USER->id); - - require_once($CFG->dirroot.'/lib/uploadlib.php'); - $um = new upload_manager('newfile',true,false,$this->course,false,$this->assignment->maxbytes); - if ($um->process_file_uploads($dir)) { - $newfile_name = $um->get_new_filename(); - if ($submission) { - $submission->timemodified = time(); - $submission->numfiles = 1; - $submission->submissioncomment = $submission->submissioncomment; - unset($submission->data1); // Don't need to update this. - unset($submission->data2); // Don't need to update this. - if ($DB->update_record("assignment_submissions", $submission)) { - add_to_log($this->course->id, 'assignment', 'upload', - 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id); - $submission = $this->get_submission($USER->id); - $this->update_grade($submission); - $this->email_teachers($submission); - print_heading(get_string('uploadedfile')); - } else { - notify(get_string("uploadfailnoupdate", "assignment")); - } - } else { - $newsubmission = $this->prepare_new_submission($USER->id); - $newsubmission->timemodified = time(); - $newsubmission->numfiles = 1; - if ($DB->insert_record('assignment_submissions', $newsubmission)) { - add_to_log($this->course->id, 'assignment', 'upload', - 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id); - $submission = $this->get_submission($USER->id); - $this->update_grade($submission); - $this->email_teachers($newsubmission); - print_heading(get_string('uploadedfile')); - } else { - notify(get_string("uploadnotregistered", "assignment", $newfile_name) ); + $mform = new mod_assignment_upload_file_form('upload.php', $this); + if ($mform->get_data()) { + $fs = get_file_storage(); + $filename = $mform->get_new_filename('newfile'); + if ($filename !== false) { + $fs->delete_area_files($this->context->id, 'assignment_submission', $USER->id); + if ($file = $mform->save_stored_file('newfile', $this->context->id, 'assignment_submission', $USER->id, '/', $filename, false, $USER->id)) { + $submission = $this->get_submission($USER->id, true); //create new submission if needed + $submission->timemodified = time(); + $submission->numfiles = 1; + if ($DB->update_record('assignment_submissions', $submission)) { + add_to_log($this->course->id, 'assignment', 'upload', + 'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id); + $this->update_grade($submission); + $this->email_teachers($submission); + print_heading(get_string('uploadedfile')); + redirect('view.php?id='.$this->cm->id); + } else { + notify(get_string("uploadnotregistered", "assignment", $newfile_name) ); + $file->delete(); + } } } + } else { + notify(get_string("uploaderror", "assignment")); //submitting not allowed! } - } else { - notify(get_string("uploaderror", "assignment")); //submitting not allowed! } print_continue('view.php?id='.$this->cm->id); @@ -183,6 +148,34 @@ class assignment_uploadsingle extends assignment_base { return true; } + function send_file($filearea, $args) { + global $CFG, $DB, $USER; + require_once($CFG->libdir.'/filelib.php'); + + require_login($this->course, false, $this->cm); + + $userid = (int)array_shift($args); + $relativepath = '/'.implode('/', $args); + $fullpath = $this->context->id.$filearea.$userid.$relativepath; + + $fs = get_file_storage(); + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + return false; + } + + if ($filearea === 'assignment_submission') { + if ($USER->id != $userid and !has_capability('mod/assignment:grade', $this->context)) { + return false; + } + + } else { + return false; + } + + send_stored_file($file, 0, 0, true); // downlaod MUST be forced - security! + } + } ?> diff --git a/mod/assignment/upload.php b/mod/assignment/upload.php index 2b4059c73c..d937f18905 100644 --- a/mod/assignment/upload.php +++ b/mod/assignment/upload.php @@ -33,7 +33,7 @@ require_login($course->id, false, $cm); /// Load up the required assignment code - require($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php'); + require_once($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php'); $assignmentclass = 'assignment_'.$assignment->assignmenttype; $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course); diff --git a/mod/assignment/version.php b/mod/assignment/version.php index dab1a0569e..2d169bc0eb 100644 --- a/mod/assignment/version.php +++ b/mod/assignment/version.php @@ -5,7 +5,7 @@ // This fragment is called by /admin/index.php //////////////////////////////////////////////////////////////////////////////// -$module->version = 2008072401; +$module->version = 2008073000; $module->requires = 2008072401; // Requires this Moodle version $module->cron = 60; diff --git a/pluginfile.php b/pluginfile.php new file mode 100644 index 0000000000..21962a55d5 --- /dev/null +++ b/pluginfile.php @@ -0,0 +1,181 @@ +contextlevel == CONTEXT_SYSTEM) { + if ($filearea === 'blog') { + + if (empty($CFG->bloglevel)) { + print_error('siteblogdisable', 'blog'); + } + if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) { + require_login(); + if (isguestuser()) { + print_error('noguest'); + } + if ($CFG->bloglevel == BLOG_USER_LEVEL) { + if ($USER->id != $entry->userid) { + not_found(); + } + } + } + $entryid = (int)array_shift($args); + if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) { + not_found(); + } + if ('publishstate' === 'public') { + if ($CFG->forcelogin) { + require_login(); + } + + } else if ('publishstate' === 'site') { + require_login(); + //ok + } else if ('publishstate' === 'draft') { + require_login(); + if ($USER->id != $entry->userid) { + not_found(); + } + } + + //TODO: implement shared course and shared group access + + $relativepath = '/'.implode('/', $args); + $fullpath = $context->id.'blog'.$entryid.$relativepath; + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + not_found(); + } + + send_stored_file($file, 10*60, 0, true); // downlaod MUST be forced - security! + + } else { + not_found(); + } + + + } else if ($context->contextlevel == CONTEXT_USER) { + not_found(); + + + } else if ($context->contextlevel == CONTEXT_COURSECAT) { + if ($filearea !== 'intro') { + not_found(); + } + + if ($CFG->forcelogin) { + // no login necessary - unless login forced everywhere + require_login(); + } + + $relativepath = '/'.implode('/', $args); + $fullpath = $context->id.'intro0'.$relativepath; + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->get_filename() == '.') { + not_found(); + } + + session_write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + + } else if ($context->contextlevel == CONTEXT_COURSE) { + if ($filearea !== 'intro' and $filearea !== 'backup') { + not_found(); + } + + if (!$course = $DB->get_record('course', array('id'=>$context->instanceid))) { + print_error('invalidcourseid'); + } + + if ($filearea === 'backup') { + require_login($course); + require_capability('moodle/site:backupdownload', $context); + } else { + if ($CFG->forcelogin) { + require_login(); + } + } + + $relativepath = '/'.implode('/', $args); + $fullpath = $context->id.'intro0'.$relativepath; + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + not_found(); + } + + session_write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + + } else if ($context->contextlevel == CONTEXT_MODULE) { + + if (!$coursecontext = get_context_instance_by_id(get_parent_contextid($context))) { + not_found(); + } + + if (!$course = $DB->get_record('course', array('id'=>$coursecontext->instanceid))) { + not_found(); + } + $modinfo = get_fast_modinfo($course); + if (empty($modinfo->cms[$context->instanceid])) { + not_found(); + } + + $cminfo = $modinfo->cms[$context->instanceid]; + $modname = $cminfo->modname; + $libfile = "$CFG->dirroot/mod/$modname/lib.php"; + if (file_exists($libfile)) { + require_once($libfile); + $filefunction = $modname.'_pluginfile'; + if (function_exists($filefunction)) { + if ($filefunction($course, $cminfo, $context, $filearea, $args) !== false) { + die; + } + } + } + not_found(); + + } else if ($context->contextlevel == CONTEXT_BLOCK) { + //not supported yet + not_found(); + + + } else { + not_found(); + } + + + function not_found() { + global $CFG; + header('HTTP/1.0 404 not found'); + print_error('filenotfound', 'error', $CFG->wwwroot.'/'); //this is not displayed on IIS?? + } diff --git a/user/edit_form.php b/user/edit_form.php index 0f08437d0f..8d849aa668 100644 --- a/user/edit_form.php +++ b/user/edit_form.php @@ -9,7 +9,6 @@ class user_edit_form extends moodleform { global $CFG, $COURSE; $mform =& $this->_form; - $this->set_upload_manager(new upload_manager('imagefile', false, false, null, false, 0, true, true, false)); //Accessibility: "Required" is bad legend text. $strgeneral = get_string('general'); $strrequired = get_string('required'); diff --git a/user/editadvanced_form.php b/user/editadvanced_form.php index e0c9123f63..2d16192f53 100644 --- a/user/editadvanced_form.php +++ b/user/editadvanced_form.php @@ -9,7 +9,6 @@ class user_editadvanced_form extends moodleform { global $USER, $CFG, $COURSE; $mform =& $this->_form; - $this->set_upload_manager(new upload_manager('imagefile', false, false, null, false, 0, true, true, false)); //Accessibility: "Required" is bad legend text. $strgeneral = get_string('general'); $strrequired = get_string('required'); diff --git a/user/editlib.php b/user/editlib.php index 1c97f65d2c..ba31678658 100644 --- a/user/editlib.php +++ b/user/editlib.php @@ -33,15 +33,17 @@ function useredit_update_user_preference($usernew) { } } -function useredit_update_picture(&$usernew, &$userform) { +function useredit_update_picture(&$usernew, $userform) { global $CFG, $DB; if (isset($usernew->deletepicture) and $usernew->deletepicture) { $location = make_user_directory($usernew->id, true); @remove_dir($location); $DB->set_field('user', 'picture', 0, array('id'=>$usernew->id)); - } else if ($usernew->picture = save_profile_image($usernew->id, $userform->get_um(), 'user')) { - $DB->set_field('user', 'picture', 1, array('id'=>$usernew->id)); + + } else if ($userform->get_new_filename('imagefile')) { + $usernew->picture = (int)save_profile_image($usernew->id, $userform, 'user', 'imagefile'); + $DB->set_field('user', 'picture', $usernew->picture, array('id'=>$usernew->id)); } } diff --git a/userfile.php b/userfile.php new file mode 100644 index 0000000000..368dcdcd5d --- /dev/null +++ b/userfile.php @@ -0,0 +1,71 @@ +contextlevel != CONTEXT_USER) { + print_error('invalidarguments'); + } + + $userid = $context->instanceid; + if ($USER->id != $userid) { + print_error('invaliduserid'); + } + + switch ($filearea) { + case 'private': $itemid = 0; $forcedownload = true; break; + case 'draft' : $itemid = (int)array_shift($args); break; + default: not_found(); + } + + $relativepath = '/'.implode('/', $args); + + + $fs = get_file_storage(); + + $fullpath = $context->id.$filearea.$itemid.$relativepath; + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->get_filename() == '.') { + not_found(); + } + + // ======================================== + // finally send the file + // ======================================== + session_write_close(); // unlock session during fileserving + send_stored_file($file, 0, false, $forcedownload); + + function not_found() { + global $CFG; + header('HTTP/1.0 404 not found'); + print_error('filenotfound', 'error', $CFG->wwwroot.'/'); //this is not displayed on IIS?? + } diff --git a/version.php b/version.php index f55f818d73..933a743fb7 100644 --- a/version.php +++ b/version.php @@ -6,7 +6,7 @@ // This is compared against the values stored in the database to determine // whether upgrades should be performed (see lib/db/*.php) - $version = 2008073104; // YYYYMMDD = date of the last version bump + $version = 2008073114; // YYYYMMDD = date of the last version bump // XX = daily increments $release = '2.0 dev (Build: 20080731)'; // Human-friendly version name