diff --git a/repository/filepicker.php b/repository/filepicker.php index 20b67ca72c5..73970d60a28 100644 --- a/repository/filepicker.php +++ b/repository/filepicker.php @@ -59,6 +59,7 @@ $search_text = optional_param('s', '', PARAM_CLEANHTML); $maxfiles = optional_param('maxfiles', -1, PARAM_INT); // maxfiles $maxbytes = optional_param('maxbytes', 0, PARAM_INT); // maxbytes $subdirs = optional_param('subdirs', 0, PARAM_INT); // maxbytes +$accepted_types = optional_param_array('accepted_types', '*', PARAM_RAW); // the path to save files $savepath = optional_param('savepath', '/', PARAM_PATH); @@ -75,22 +76,16 @@ if (!$course = $DB->get_record('course', array('id'=>$courseid))) { } $PAGE->set_course($course); -// init repository plugin -$sql = 'SELECT i.name, i.typeid, r.type FROM {repository} r, {repository_instances} i '. - 'WHERE i.id=? AND i.typeid=r.id'; -if ($repository = $DB->get_record_sql($sql, array($repo_id))) { - $type = $repository->type; - if (file_exists($CFG->dirroot.'/repository/'.$type.'/lib.php')) { - require_once($CFG->dirroot.'/repository/'.$type.'/lib.php'); - $classname = 'repository_' . $type; - try { - $repo = new $classname($repo_id, $contextid, array('ajax'=>false, 'name'=>$repository->name, 'type'=>$type)); - } catch (repository_exception $e){ - print_error('pluginerror', 'repository'); - } - } else { - print_error('invalidplugin', 'repository'); - } +if ($repo_id) { + // Get repository instance information + $repooptions = array( + 'ajax' => false, + 'mimetypes' => $accepted_types + ); + $repo = repository::get_repository_by_id($repo_id, $contextid, $repooptions); + + // Check permissions + $repo->check_capability(); } $context = context::instance_by_id($contextid); @@ -285,40 +280,65 @@ case 'sign': break; case 'download': - $thefile = $repo->get_file($fileurl, $filename); - $filesize = filesize($thefile['path']); - if (($maxbytes!=-1) && ($filesize>$maxbytes)) { - print_error('maxbytes'); + // Check that user has permission to access this file + if (!$repo->file_is_accessible($fileurl)) { + print_error('storedfilecannotread'); } - if (!empty($thefile)) { - $record = new stdClass(); - $record->filepath = $savepath; - $record->filename = $filename; - $record->component = 'user'; - $record->filearea = 'draft'; - $record->itemid = $itemid; - $record->license = ''; - $record->author = ''; - $now = time(); - $record->timecreated = $now; - $record->timemodified = $now; - $record->userid = $USER->id; - $record->contextid = $user_context->id; - - $sourcefield = $repo->get_file_source_info($thefile['url']); - $record->source = repository::build_source_field($sourcefield); - try { - $info = repository::move_to_filepool($thefile['path'], $record); - redirect($home_url, get_string('downloadsucc', 'repository')); - } catch (moodle_exception $e) { - // inject target URL - $e->link = $PAGE->url->out(); - echo $OUTPUT->header(); // hack: we need the embedded header here, standard error printing would not use it - throw $e; + // If file is already a reference, set $fileurl = file source, $repo = file repository + // note that in this case user may not have permission to access the source file directly + // so no file_browser/file_info can be used below + if ($repo->has_moodle_files()) { + $file = repository::get_moodle_file($fileurl); + if ($file && $file->is_external_file()) { + $fileurl = $file->get_reference(); + $repo_id = $file->get_repository_id(); + $repo = repository::get_repository_by_id($repo_id, $contextid, $repooptions); } + } + + $record = new stdClass(); + $record->filepath = $savepath; + $record->filename = $filename; + $record->component = 'user'; + $record->filearea = 'draft'; + $record->itemid = $itemid; + $record->license = ''; + $record->author = ''; + + $now = time(); + $record->timecreated = $now; + $record->timemodified = $now; + $record->userid = $USER->id; + $record->contextid = $user_context->id; + $record->sortorder = 0; + + $sourcefield = $repo->get_file_source_info($fileurl); + $record->source = repository::build_source_field($sourcefield); + + if ($repo->has_moodle_files()) { + $fileinfo = $repo->copy_to_area($fileurl, $record, $maxbytes); + redirect($home_url, get_string('downloadsucc', 'repository')); } else { - print_error('cannotdownload', 'repository'); + $thefile = $repo->get_file($fileurl, $filename); + if (!empty($thefile['path'])) { + $filesize = filesize($thefile['path']); + if ($maxbytes != -1 && $filesize>$maxbytes) { + unlink($thefile['path']); + print_error('maxbytes'); + } + try { + $info = repository::move_to_filepool($thefile['path'], $record); + redirect($home_url, get_string('downloadsucc', 'repository')); + } catch (moodle_exception $e) { + // inject target URL + $e->link = $PAGE->url->out(); + echo $OUTPUT->header(); // hack: we need the embedded header here, standard error printing would not use it + throw $e; + } + } else { + print_error('cannotdownload', 'repository'); + } } break; diff --git a/repository/lib.php b/repository/lib.php index 62312099d58..c34dec75b7b 100644 --- a/repository/lib.php +++ b/repository/lib.php @@ -521,9 +521,10 @@ abstract class repository { * * @param int $repositoryid repository ID * @param stdClass|int $context context instance or context ID + * @param array $options additional repository options * @return repository */ - public static function get_repository_by_id($repositoryid, $context) { + public static function get_repository_by_id($repositoryid, $context, $options = array()) { global $CFG, $DB; $sql = 'SELECT i.name, i.typeid, r.type FROM {repository} r, {repository_instances} i WHERE i.id=? AND i.typeid=r.id'; @@ -539,10 +540,15 @@ abstract class repository { if (is_object($context)) { $contextid = $context->id; } - $repository = new $classname($repositoryid, $contextid, array('type'=>$type)); + $options['type'] = $type; + $options['typeid'] = $record->typeid; + if (empty($options['name'])) { + $options['name'] = $record->name; + } + $repository = new $classname($repositoryid, $contextid, $options); return $repository; } else { - throw new moodle_exception('error'); + throw new repository_exception('invalidplugin', 'repository'); } } } @@ -609,16 +615,16 @@ abstract class repository { } /** - * To check if the context id is valid + * Checks if user has a capability to view the current repository in current context * - * @static - * @param int $contextid - * @param stdClass $instance * @return bool */ - public static function check_capability($contextid, $instance) { - $context = get_context_instance_by_id($contextid); - $capability = has_capability('repository/'.$instance->type.':view', $context); + public final function check_capability() { + $capability = false; + if (preg_match("/^repository_(.*)$/", get_class($this), $matches)) { + $type = $matches[1]; + $capability = has_capability('repository/'.$type.':view', $this->context); + } if (!$capability) { throw new repository_exception('nopermissiontoaccess', 'repository'); } @@ -651,45 +657,60 @@ abstract class repository { * @return stored_file|null */ public static function get_moodle_file($source) { - $params = unserialize(base64_decode($source)); - if (empty($params) || !is_array($params)) { - return null; - } - foreach (array('contextid', 'itemid', 'filename', 'filepath', 'component') as $key) { - if (!array_key_exists($key, $params)) { - return null; - } - } - $contextid = clean_param($params['contextid'], PARAM_INT); - $component = clean_param($params['component'], PARAM_COMPONENT); - $filearea = clean_param($params['filearea'], PARAM_AREA); - $itemid = clean_param($params['itemid'], PARAM_INT); - $filepath = clean_param($params['filepath'], PARAM_PATH); - $filename = clean_param($params['filename'], PARAM_FILE); + $params = file_storage::unpack_reference($source, true); $fs = get_file_storage(); - return $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename); + return $fs->get_file($params['contextid'], $params['component'], $params['filearea'], + $params['itemid'], $params['filepath'], $params['filename']); } /** - * This function is used to copy a moodle file to draft area + * Repository method to make sure that user can access particular file. * - * @param string $encoded The metainfo of file, it is base64 encoded php serialized data + * This is checked when user tries to pick the file from repository to deal with + * potential parameter substitutions is request + * + * @param string $source + * @return bool whether the file is accessible by current user + */ + public function file_is_accessible($source) { + if ($this->has_moodle_files()) { + try { + $params = file_storage::unpack_reference($source, true); + } catch (file_reference_exception $e) { + return false; + } + $browser = get_file_browser(); + $context = context::instance_by_id($params['contextid']); + $file_info = $browser->get_file_info($context, $params['component'], $params['filearea'], + $params['itemid'], $params['filepath'], $params['filename']); + return !empty($file_info); + } + return true; + } + + /** + * This function is used to copy a moodle file to draft area. + * + * It DOES NOT check if the user is allowed to access this file because the actual file + * can be located in the area where user does not have access to but there is an alias + * to this file in the area where user CAN access it. + * {@link file_is_accessible} should be called for alias location before calling this function. + * + * @param string $source The metainfo of file, it is base64 encoded php serialized data * @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other * attributes of the new file * @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds * the limit, the file_exception is thrown. - * @return array The information of file + * @return array The information about the created file */ - public function copy_to_area($encoded, $filerecord, $maxbytes = -1) { + public function copy_to_area($source, $filerecord, $maxbytes = -1) { global $USER; $fs = get_file_storage(); - $browser = get_file_browser(); if ($this->has_moodle_files() == false) { throw new coding_exception('Only repository used to browse moodle files can use repository::copy_to_area()'); } - $params = unserialize(base64_decode($encoded)); $user_context = context_user::instance($USER->id); $filerecord = (array)$filerecord; @@ -701,17 +722,9 @@ abstract class repository { $new_filepath = $filerecord['filepath']; $new_filename = $filerecord['filename']; - $contextid = clean_param($params['contextid'], PARAM_INT); - $fileitemid = clean_param($params['itemid'], PARAM_INT); - $filename = clean_param($params['filename'], PARAM_FILE); - $filepath = clean_param($params['filepath'], PARAM_PATH);; - $filearea = clean_param($params['filearea'], PARAM_AREA); - $component = clean_param($params['component'], PARAM_COMPONENT); - - $context = get_context_instance_by_id($contextid); // the file needs to copied to draft area - $file_info = $browser->get_file_info($context, $component, $filearea, $fileitemid, $filepath, $filename); - if ($maxbytes !== -1 && $file_info->get_filesize() > $maxbytes) { + $stored_file = self::get_moodle_file($source); + if ($maxbytes != -1 && $stored_file->get_filesize() > $maxbytes) { throw new file_exception('maxbytes'); } @@ -719,7 +732,7 @@ abstract class repository { // create new file $unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename); $filerecord['filename'] = $unused_filename; - $file_info->copy_to_storage($filerecord); + $fs->create_file_from_storedfile($filerecord, $stored_file); $event = array(); $event['event'] = 'fileexists'; $event['newfile'] = new stdClass; @@ -729,17 +742,17 @@ abstract class repository { $event['existingfile'] = new stdClass; $event['existingfile']->filepath = $new_filepath; $event['existingfile']->filename = $new_filename; - $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();; + $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out(); return $event; } else { - $file_info->copy_to_storage($filerecord); + $fs->create_file_from_storedfile($filerecord, $stored_file); $info = array(); $info['itemid'] = $draftitemid; - $info['file'] = $new_filename; - $info['title'] = $new_filename; + $info['file'] = $new_filename; + $info['title'] = $new_filename; $info['contextid'] = $user_context->id; - $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();; - $info['filesize'] = $file_info->get_filesize(); + $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out(); + $info['filesize'] = $stored_file->get_filesize(); return $info; } } diff --git a/repository/recent/lib.php b/repository/recent/lib.php index 2c1acf23579..f1265c84add 100644 --- a/repository/recent/lib.php +++ b/repository/recent/lib.php @@ -177,91 +177,22 @@ class repository_recent extends repository { public function supported_returntypes() { return FILE_INTERNAL; } + /** - * This function overwrite the default implement to copying file using file_storage + * Repository method to make sure that user can access particular file. * - * @param string $encoded The information of file, it is base64 encoded php serialized data - * @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other - * attributes of the new file - * @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds - * the limit, the file_exception is thrown. - * @return array The information of file + * This is checked when user tries to pick the file from repository to deal with + * potential parameter substitutions is request + * + * @todo MDL-33805 remove this function when recent files are managed correctly + * + * @param string $source + * @return bool whether the file is accessible by current user */ - public function copy_to_area($encoded, $filerecord, $maxbytes = -1) { + public function file_is_accessible($source) { global $USER; - - $user_context = get_context_instance(CONTEXT_USER, $USER->id); - - $filerecord = (array)$filerecord; - // make sure the new file will be created in user draft area - $filerecord['component'] = 'user'; // make sure - $filerecord['filearea'] = 'draft'; // make sure - $filerecord['contextid'] = $user_context->id; - $filerecord['sortorder'] = 0; - $draftitemid = $filerecord['itemid']; - $new_filepath = $filerecord['filepath']; - $new_filename = $filerecord['filename']; - - $fs = get_file_storage(); - - $params = unserialize(base64_decode($encoded)); - - $contextid = clean_param($params['contextid'], PARAM_INT); - $fileitemid = clean_param($params['itemid'], PARAM_INT); - $filename = clean_param($params['filename'], PARAM_FILE); - $filepath = clean_param($params['filepath'], PARAM_PATH);; - $filearea = clean_param($params['filearea'], PARAM_AREA); - $component = clean_param($params['component'], PARAM_COMPONENT); - - // XXX: - // When user try to pick a file from other filearea, normally file api will use file browse to - // operate the files with capability check, but in some areas, users don't have permission to - // browse the files (for example, forum_attachment area). - // - // To get 'recent' plugin working, we need to use lower level file_stoarge class to bypass the - // capability check, we will use a better workaround to improve it. - // TODO MDL-33297 apply here - if ($stored_file = $fs->get_file($contextid, $component, $filearea, $fileitemid, $filepath, $filename)) { - // verify user id - if ($USER->id != $stored_file->get_userid()) { - throw new moodle_exception('errornotyourfile', 'repository'); - } - if ($maxbytes !== -1 && $stored_file->get_filesize() > $maxbytes) { - throw new file_exception('maxbytes'); - } - - // test if file already exists - if (repository::draftfile_exists($draftitemid, $new_filepath, $new_filename)) { - // create new file - $unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename); - $filerecord['filename'] = $unused_filename; - // create a tmp file - $fs->create_file_from_storedfile($filerecord, $stored_file); - $event = array(); - $event['event'] = 'fileexists'; - $event['newfile'] = new stdClass; - $event['newfile']->filepath = $new_filepath; - $event['newfile']->filename = $unused_filename; - $event['newfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $unused_filename)->out(); - $event['existingfile'] = new stdClass; - $event['existingfile']->filepath = $new_filepath; - $event['existingfile']->filename = $new_filename; - $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();; - return $event; - } else { - $fs->create_file_from_storedfile($filerecord, $stored_file); - $info = array(); - $info['title'] = $new_filename; - $info['file'] = $new_filename; - $info['itemid'] = $draftitemid; - $info['filesize'] = $stored_file->get_filesize(); - $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();; - $info['contextid'] = $user_context->id; - return $info; - } - } - return false; - + $file = self::get_moodle_file($source); + return (!empty($file) && $file->get_userid() == $USER->id); } /** diff --git a/repository/repository_ajax.php b/repository/repository_ajax.php index c9863c4037e..2452158490b 100644 --- a/repository/repository_ajax.php +++ b/repository/repository_ajax.php @@ -70,17 +70,14 @@ if (!confirm_sesskey()) { } // Get repository instance information -$sql = 'SELECT i.name, i.typeid, r.type FROM {repository} r, {repository_instances} i WHERE i.id=? AND i.typeid=r.id'; - -if (!$repository = $DB->get_record_sql($sql, array($repo_id))) { - $err->error = get_string('invalidrepositoryid', 'repository'); - die(json_encode($err)); -} else { - $type = $repository->type; -} +$repooptions = array( + 'ajax' => true, + 'mimetypes' => $accepted_types +); +$repo = repository::get_repository_by_id($repo_id, $contextid, $repooptions); // Check permissions -repository::check_capability($contextid, $repository); +$repo->check_capability(); $moodle_maxbytes = get_user_max_upload_file_size($context); // to prevent maxbytes greater than moodle maxbytes setting @@ -121,21 +118,6 @@ switch ($action) { break; } -if (file_exists($CFG->dirroot.'/repository/'.$type.'/lib.php')) { - require_once($CFG->dirroot.'/repository/'.$type.'/lib.php'); - $classname = 'repository_' . $type; - $repooptions = array( - 'ajax' => true, - 'name' => $repository->name, - 'type' => $type, - 'mimetypes' => $accepted_types - ); - $repo = new $classname($repo_id, $contextid, $repooptions); -} else { - $err->error = get_string('invalidplugin', 'repository', $type); - die(json_encode($err)); -} - // These actions all occur on the currently active repository instance switch ($action) { case 'sign': @@ -232,7 +214,14 @@ switch ($action) { $record->userid = $USER->id; $record->sortorder = 0; + // Check that user has permission to access this file + if (!$repo->file_is_accessible($source)) { + throw new file_exception('storedfilecannotread'); + } + // If file is already a reference, set $source = file source, $repo = file repository + // note that in this case user may not have permission to access the source file directly + // so no file_browser/file_info can be used below if ($repo->has_moodle_files()) { $file = repository::get_moodle_file($source); if ($file && $file->is_external_file()) { @@ -298,13 +287,13 @@ switch ($action) { } else { // Download file to moodle. $downloadedfile = $repo->get_file($source, $saveas_filename); - if ($downloadedfile['path'] === false) { + if (empty($downloadedfile['path'])) { $err->error = get_string('cannotdownload', 'repository'); die(json_encode($err)); } // Check if exceed maxbytes. - if (($maxbytes!==-1) && (filesize($downloadedfile['path']) > $maxbytes)) { + if ($maxbytes != -1 && filesize($downloadedfile['path']) > $maxbytes) { throw new file_exception('maxbytes'); }