From 6723372514e0bf8b44b945b4a625ac43e08dc382 Mon Sep 17 00:00:00 2001 From: Dongsheng Cai Date: Sat, 12 May 2012 04:14:53 +0800 Subject: [PATCH] MDL-28666 Extends repository to support references 1. Allow repository to create references to external contents 2. Extends files API to handle file references 3. Generic file caching 4. Backup/restore file references 5. Download external contents if repository uninstalled 6. Allow filepicker to display iframe 7. PHPUnit test suits --- admin/repository.php | 73 +- admin/repositoryinstance.php | 93 ++- backup/backupfilesedit_form.php | 2 +- backup/moodle2/backup_stepslib.php | 19 +- backup/moodle2/restore_stepslib.php | 15 +- .../dbops/backup_controller_dbops.class.php | 21 + backup/util/dbops/restore_dbops.class.php | 14 +- .../util/helper/backup_file_manager.class.php | 4 + .../helper/backup_general_helper.class.php | 1 + backup/util/ui/backup_ui_stage.class.php | 3 + backup/util/ui/renderer.php | 10 + blocks/private_files/edit.php | 2 +- files/coursefilesedit_form.php | 2 +- lang/en/backup.php | 4 + lang/en/error.php | 1 + lang/en/repository.php | 8 +- lib/cronlib.php | 7 +- lib/db/install.xml | 27 +- lib/db/upgrade.php | 54 ++ lib/filelib.php | 288 +++++++- lib/filestorage/file_exceptions.php | 29 +- lib/filestorage/file_storage.php | 664 +++++++++++++---- lib/filestorage/stored_file.php | 319 ++++++++- lib/filestorage/tests/file_storage_test.php | 124 ++++ lib/form/editor.php | 4 +- lib/form/filemanager.php | 10 +- lib/form/filepicker.php | 10 +- lib/outputrequirementslib.php | 4 +- lib/tests/filelib_test.php | 81 ++- lib/tests/repositorylib_test.php | 57 -- mod/folder/edit.php | 2 +- mod/folder/mod_form.php | 2 +- mod/resource/mod_form.php | 4 +- mod/wiki/filesedit.php | 2 +- mod/workshop/submission.php | 2 +- repository/alfresco/lib.php | 19 +- repository/boxnet/lib.php | 94 ++- repository/coursefiles/lib.php | 123 +++- repository/dropbox/lib.php | 187 ++++- repository/dropbox/locallib.php | 20 + repository/filepicker.js | 91 ++- repository/filesystem/lib.php | 79 +- repository/flickr/lib.php | 17 +- repository/flickr_public/lib.php | 22 +- repository/googledocs/lib.php | 18 +- repository/lib.php | 672 ++++++++++++------ repository/local/lib.php | 16 +- repository/merlot/lib.php | 17 +- repository/picasa/lib.php | 15 +- repository/recent/lib.php | 16 +- repository/repository_ajax.php | 134 +++- repository/s3/lib.php | 19 +- repository/tests/repository_test.php | 61 ++ repository/upload/lib.php | 16 +- repository/url/lib.php | 20 +- repository/user/lib.php | 133 +++- repository/webdav/lib.php | 20 +- repository/wikimedia/lib.php | 17 +- repository/youtube/lib.php | 17 +- user/filesedit.php | 2 +- version.php | 2 +- 61 files changed, 3112 insertions(+), 697 deletions(-) delete mode 100644 lib/tests/repositorylib_test.php create mode 100644 repository/tests/repository_test.php diff --git a/admin/repository.php b/admin/repository.php index c3409a5b5a0..a3af2ddc588 100644 --- a/admin/repository.php +++ b/admin/repository.php @@ -1,12 +1,27 @@ . require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->dirroot . '/repository/lib.php'); require_once($CFG->libdir . '/adminlib.php'); -$repository = optional_param('repos', '', PARAM_FORMAT); -$action = optional_param('action', '', PARAM_ALPHA); -$sure = optional_param('sure', '', PARAM_ALPHA); +$repository = optional_param('repos', '', PARAM_ALPHANUMEXT); +$action = optional_param('action', '', PARAM_ACTION); +$sure = optional_param('sure', '', PARAM_ALPHA); +$downloadcontents = optional_param('downloadcontents', '', PARAM_ALPHA); $display = true; // fall through to normal display @@ -42,6 +57,10 @@ $configstr = get_string('manage', 'repository'); $return = true; +if (!empty($action)) { + require_sesskey(); +} + /** * Helper function that generates a moodle_url object * relevant to the repository @@ -152,10 +171,10 @@ if (($action == 'edit') || ($action == 'new')) { // Display instances list and creation form if ($action == 'edit') { - $instanceoptionnames = repository::static_function($repository, 'get_instance_option_names'); - if (!empty($instanceoptionnames)) { - repository::display_instances_list(get_context_instance(CONTEXT_SYSTEM), $repository); - } + $instanceoptionnames = repository::static_function($repository, 'get_instance_option_names'); + if (!empty($instanceoptionnames)) { + repository::display_instances_list(context_system::instance(), $repository); + } } } } else if ($action == 'show') { @@ -185,7 +204,14 @@ if (($action == 'edit') || ($action == 'new')) { if (!confirm_sesskey()) { print_error('confirmsesskeybad', '', $baseurl); } - if ($repositorytype->delete()) { + + if (!empty($downloadcontents) and $downloadcontents == 'yes') { + $downloadcontents = true; + } else { + $downloadcontents = false; + } + + if ($repositorytype->delete($downloadcontents)) { redirect($baseurl); } else { print_error('instancenotdeleted', 'repository', $baseurl); @@ -193,7 +219,34 @@ if (($action == 'edit') || ($action == 'new')) { exit; } else { echo $OUTPUT->header(); - echo $OUTPUT->confirm(get_string('confirmremove', 'repository', $repositorytype->get_readablename()), $sesskeyurl . '&action=delete&repos=' . $repository . '&sure=yes', $baseurl); + + $message = get_string('confirmremove', 'repository', $repositorytype->get_readablename()); + + $output = $OUTPUT->box_start('generalbox', 'notice'); + $output .= html_writer::tag('p', $message); + + $removeurl = new moodle_url($sesskeyurl); + $removeurl->params(array( + 'action' =>'delete', + 'repos' => $repository, + 'sure' => 'yes', + )); + + $removeanddownloadurl = new moodle_url($sesskeyurl); + $removeanddownloadurl->params(array( + 'action' =>'delete', + 'repos'=> $repository, + 'sure' => 'yes', + 'downloadcontents' => 'yes', + )); + + $output .= $OUTPUT->single_button($removeurl, get_string('continueuninstall', 'repository')); + $output .= $OUTPUT->single_button($removeanddownloadurl, get_string('continueuninstallanddownload', 'repository')); + $output .= $OUTPUT->single_button($baseurl, get_string('cancel')); + $output .= $OUTPUT->box_end(); + + echo $output; + $return = false; } } else if ($action == 'moveup') { @@ -255,7 +308,7 @@ if (($action == 'edit') || ($action == 'new')) { // Calculate number of instances in order to display them for the Moodle administrator if (!empty($instanceoptionnames)) { $params = array(); - $params['context'] = array(get_system_context()); + $params['context'] = array(context_system::instance()); $params['onlyvisible'] = false; $params['type'] = $typename; $admininstancenumber = count(repository::static_function($typename, 'get_instances', $params)); diff --git a/admin/repositoryinstance.php b/admin/repositoryinstance.php index e75193d0be4..ce903e843a9 100644 --- a/admin/repositoryinstance.php +++ b/admin/repositoryinstance.php @@ -1,18 +1,35 @@ . require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->dirroot . '/repository/lib.php'); require_once($CFG->libdir . '/adminlib.php'); +require_sesskey(); + // id of repository $edit = optional_param('edit', 0, PARAM_INT); -$new = optional_param('new', '', PARAM_FORMAT); +$new = optional_param('new', '', PARAM_PLUGIN); $hide = optional_param('hide', 0, PARAM_INT); $delete = optional_param('delete', 0, PARAM_INT); $sure = optional_param('sure', '', PARAM_ALPHA); $type = optional_param('type', '', PARAM_PLUGIN); +$downloadcontents = optional_param('downloadcontents', '', PARAM_ALPHA); -$context = get_context_instance(CONTEXT_SYSTEM); +$context = context_system::instance(); $pagename = 'repositorycontroller'; @@ -25,15 +42,19 @@ if ($edit){ } admin_externalpage_setup($pagename); -require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)); +require_capability('moodle/site:config', $context); + +$baseurl = new moodle_url("/$CFG->admin/repositoryinstance.php", array('sesskey'=>sesskey())); + +$parenturl = new moodle_url("/$CFG->admin/repository.php", array( + 'sesskey'=>sesskey(), + 'action'=>'edit', +)); -$sesskeyurl = "$CFG->wwwroot/$CFG->admin/repositoryinstance.php?sesskey=" . sesskey(); -$baseurl = "$CFG->wwwroot/$CFG->admin/repository.php?session=". sesskey() .'&action=edit&repos='; if ($new) { - $baseurl .= $new; -} -else { - $baseurl .= $type; + $parenturl->param('repos', $new); +} else { + $parenturl->param('repos', $type); } $return = true; @@ -48,7 +69,7 @@ if (!empty($edit) || !empty($new)) { $typeid = $instance->options['typeid']; } else { $plugin = $new; - $typeid = $new; + $typeid = null; $instance = null; } @@ -57,12 +78,9 @@ if (!empty($edit) || !empty($new)) { // end setup, begin output if ($mform->is_cancelled()){ - redirect($baseurl); + redirect($parenturl); exit; } else if ($fromform = $mform->get_data()){ - if (!confirm_sesskey()) { - print_error('confirmsesskeybad', '', $baseurl); - } if ($edit) { $settings = array(); $settings['name'] = $fromform->name; @@ -77,13 +95,13 @@ if (!empty($edit) || !empty($new)) { } $success = $instance->set_option($settings); } else { - $success = repository::static_function($plugin, 'create', $plugin, 0, get_system_context(), $fromform); + $success = repository::static_function($plugin, 'create', $plugin, 0, $context, $fromform); $data = data_submitted(); } if ($success) { - redirect($baseurl); + redirect($parenturl); } else { - print_error('instancenotsaved', 'repository', $baseurl); + print_error('instancenotsaved', 'repository', $parenturl); } exit; } else { @@ -95,9 +113,6 @@ if (!empty($edit) || !empty($new)) { $return = false; } } else if (!empty($hide)) { - if (!confirm_sesskey()) { - print_error('confirmsesskeybad', '', $baseurl); - } $instance = repository::get_type_by_typename($hide); $instance->hide(); $return = true; @@ -108,25 +123,43 @@ if (!empty($edit) || !empty($new)) { throw new repository_exception('readonlyinstance', 'repository'); } if ($sure) { - if (!confirm_sesskey()) { - print_error('confirmsesskeybad', '', $baseurl); - } - if ($instance->delete()) { - $deletedstr = get_string('instancedeleted', 'repository'); - redirect($baseurl, $deletedstr, 3); + if (!empty($downloadcontents) and $downloadcontents == 'yes') { + $downloadcontents = true; } else { - print_error('instancenotdeleted', 'repository', $baseurl); + $downloadcontents = false; + } + if ($instance->delete($downloadcontents)) { + $deletedstr = get_string('instancedeleted', 'repository'); + redirect($parenturl, $deletedstr, 3); + } else { + print_error('instancenotdeleted', 'repository', $parenturl); } exit; } echo $OUTPUT->header(); - echo $OUTPUT->confirm(get_string('confirmdelete', 'repository', $instance->name), "$sesskeyurl&type=$type'&delete=$delete'&sure=yes", "$CFG->wwwroot/$CFG->admin/repositoryinstance.php?session=". sesskey()); + echo $OUTPUT->box_start('generalbox', 'notice'); + $continueurl = new moodle_url($baseurl, array( + 'type' => $type, + 'delete' => $delete, + 'sure' => 'yes', + )); + $continueanddownloadurl = new moodle_url($continueurl, array( + 'downloadcontents' => 'yes' + )); + $message = get_string('confirmdelete', 'repository', $instance->name); + echo html_writer::tag('p', $message); + + echo $OUTPUT->single_button($continueurl, get_string('continueuninstall', 'repository')); + echo $OUTPUT->single_button($continueanddownloadurl, get_string('continueuninstallanddownload', 'repository')); + echo $OUTPUT->single_button($parenturl, get_string('cancel')); + + echo $OUTPUT->box_end(); + $return = false; } if (!empty($return)) { - - redirect($baseurl); + redirect($parenturl); } echo $OUTPUT->footer(); diff --git a/backup/backupfilesedit_form.php b/backup/backupfilesedit_form.php index 7906a080d1a..36a4030da07 100644 --- a/backup/backupfilesedit_form.php +++ b/backup/backupfilesedit_form.php @@ -27,7 +27,7 @@ class backup_files_edit_form extends moodleform { function definition() { $mform =& $this->_form; $contextid = $this->_customdata['contextid']; - $options = array('subdirs'=>0, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL); + $options = array('subdirs'=>0, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL | FILE_REFERENCE); $mform->addElement('filemanager', 'files_filemanager', get_string('files'), null, $options); $mform->addElement('hidden', 'contextid', $this->_customdata['contextid']); $mform->addElement('hidden', 'currentcontext', $this->_customdata['currentcontext']); diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index e7b6018cdf3..d1be602bc8f 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -1404,7 +1404,7 @@ class backup_final_files_structure_step extends backup_structure_step { 'contenthash', 'contextid', 'component', 'filearea', 'itemid', 'filepath', 'filename', 'userid', 'filesize', 'mimetype', 'status', 'timecreated', 'timemodified', - 'source', 'author', 'license', 'sortorder')); + 'source', 'author', 'license', 'sortorder', 'reference', 'repositoryid')); // Build the tree @@ -1412,9 +1412,12 @@ class backup_final_files_structure_step extends backup_structure_step { // Define sources - $file->set_source_sql("SELECT f.* + $file->set_source_sql("SELECT f.*, r.repositoryid, r.reference FROM {files} f - JOIN {backup_ids_temp} bi ON f.id = bi.itemid + JOIN {files_reference} r + ON r.id = f.referencefileid + JOIN {backup_ids_temp} bi + ON f.id = bi.itemid WHERE bi.backupid = ? AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID)); @@ -1442,6 +1445,8 @@ class backup_main_structure_step extends backup_structure_step { $info['backup_date'] = time(); $info['backup_uniqueid']= $this->get_backupid(); $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid()); + $info['include_file_references_to_external_content'] = + backup_controller_dbops::backup_includes_file_references($this->get_backupid()); $info['original_wwwroot']=$CFG->wwwroot; $info['original_site_identifier_hash'] = md5(get_site_identifier()); $info['original_course_id'] = $this->get_courseid(); @@ -1461,7 +1466,7 @@ class backup_main_structure_step extends backup_structure_step { $information = new backup_nested_element('information', null, array( 'name', 'moodle_version', 'moodle_release', 'backup_version', - 'backup_release', 'backup_date', 'mnet_remoteusers', 'original_wwwroot', + 'backup_release', 'backup_date', 'mnet_remoteusers', 'include_file_references_to_external_content', 'original_wwwroot', 'original_site_identifier_hash', 'original_course_id', 'original_course_fullname', 'original_course_shortname', 'original_course_startdate', 'original_course_contextid', 'original_system_contextid')); @@ -1584,8 +1589,12 @@ class backup_store_backup_file extends backup_execution_step { // Calculate the zip fullpath (in OS temp area it's always backup.mbz) $zipfile = $basepath . '/backup.mbz'; + $has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid()); // Perform storage and return it (TODO: shouldn't be array but proper result object) - return array('backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile)); + return array( + 'backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile), + 'include_file_references_to_external_content' => $has_file_references + ); } } diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index fda860c9cf1..4d610e5cafb 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -586,11 +586,23 @@ class restore_load_included_files extends restore_structure_step { return array($file); } - // Processing functions go here + /** + * Processing functions go here + * + * @param array $data one file record including repositoryid and reference + */ public function process_file($data) { $data = (object)$data; // handy + $isreference = !empty($data->repositoryid); + $issamesite = $this->task->is_samesite(); + + // If it's not samesite, we skip file refernces + if (!$issamesite && $isreference) { + return; + } + // load it if needed: // - it it is one of the annotated inforef files (course/section/activity/block) // - it is one "user", "group", "grouping", "grade", "question" or "qtype_xxxx" component file (that aren't sent to inforef ever) @@ -601,6 +613,7 @@ class restore_load_included_files extends restore_structure_step { $data->component == 'grouping' || $data->component == 'grade' || $data->component == 'question' || substr($data->component, 0, 5) == 'qtype'); if ($isfileref || $iscomponent) { + // Process files restore_dbops::set_backup_files_record($this->get_restoreid(), $data); } } diff --git a/backup/util/dbops/backup_controller_dbops.class.php b/backup/util/dbops/backup_controller_dbops.class.php index c77aeb6e4c4..ee98987a41d 100644 --- a/backup/util/dbops/backup_controller_dbops.class.php +++ b/backup/util/dbops/backup_controller_dbops.class.php @@ -409,6 +409,27 @@ abstract class backup_controller_dbops extends backup_dbops { return (int)(bool)$count; } + /** + * Given the backupid, detect if the backup contains references to external contents + * + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + * @return int + */ + public static function backup_includes_file_references($backupid) { + global $CFG, $DB; + + $sql = "SELECT count(r.repositoryid) + FROM {files} f + JOIN {files_reference} r + ON r.id = f.referencefileid + JOIN {backup_ids_temp} bi + ON f.id = bi.itemid + WHERE bi.backupid = ? + AND bi.itemname = 'filefinal'"; + $count = $DB->count_records_sql($sql, array($backupid)); + return (int)(bool)$count; + } + /** * Given the courseid, return some course related information we want to transport * diff --git a/backup/util/dbops/restore_dbops.class.php b/backup/util/dbops/restore_dbops.class.php index 25f2e533d1c..e50c6e607df 100644 --- a/backup/util/dbops/restore_dbops.class.php +++ b/backup/util/dbops/restore_dbops.class.php @@ -685,6 +685,9 @@ abstract class restore_dbops { $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $rec) { $file = (object)unserialize(base64_decode($rec->info)); + + $isreference = !empty($file->repositoryid); + // ignore root dirs (they are created automatically) if ($file->filepath == '/' && $file->filename == '.') { continue; @@ -697,10 +700,12 @@ abstract class restore_dbops { $fs->create_directory($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->userid); continue; } + // arrived here, file found // Find file in backup pool $backuppath = $basepath . backup_file_manager::get_backup_content_file_location($file->contenthash); - if (!file_exists($backuppath)) { + + if (!file_exists($backuppath) && !$isreference) { throw new restore_dbops_exception('file_not_found_in_pool', $file); } if (!$fs->file_exists($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->filename)) { @@ -717,7 +722,11 @@ abstract class restore_dbops { 'author' => $file->author, 'license' => $file->license, 'sortorder' => $file->sortorder); - $fs->create_file_from_pathname($file_record, $backuppath); + if ($isreference) { + $fs->create_file_from_reference($file_record, $file->repositoryid, $file->reference); + } else { + $fs->create_file_from_pathname($file_record, $backuppath); + } } } $rs->close(); @@ -1204,6 +1213,7 @@ abstract class restore_dbops { public static function set_backup_files_record($restoreid, $filerec) { global $DB; + // Store external files info in `info` field $filerec->info = base64_encode(serialize($filerec)); // Serialize the whole rec in info $filerec->backupid = $restoreid; $DB->insert_record('backup_files_temp', $filerec); diff --git a/backup/util/helper/backup_file_manager.class.php b/backup/util/helper/backup_file_manager.class.php index fb6477a6996..9941af7f4b6 100644 --- a/backup/util/helper/backup_file_manager.class.php +++ b/backup/util/helper/backup_file_manager.class.php @@ -73,6 +73,10 @@ class backup_file_manager { $fs = get_file_storage(); $file = $fs->get_file_instance($filerecorid); + // If the file is external file, skip copying. + if ($file->is_external_file()) { + return; + } // Calculate source and target paths (use same subdirs strategy for both) $targetfilepath = self::get_backup_storage_base_dir($backupid) . '/' . diff --git a/backup/util/helper/backup_general_helper.class.php b/backup/util/helper/backup_general_helper.class.php index 73b5d3b4014..b0eb2e237d2 100644 --- a/backup/util/helper/backup_general_helper.class.php +++ b/backup/util/helper/backup_general_helper.class.php @@ -145,6 +145,7 @@ abstract class backup_general_helper extends backup_helper { $info->original_course_startdate= $infoarr['original_course_startdate']; $info->original_course_contextid= $infoarr['original_course_contextid']; $info->original_system_contextid= $infoarr['original_system_contextid']; + $info->include_file_references_to_external_content = $infoarr['include_file_references_to_external_content']; $info->type = $infoarr['details']['detail'][0]['type']; $info->format = $infoarr['details']['detail'][0]['format']; $info->mode = $infoarr['details']['detail'][0]['mode']; diff --git a/backup/util/ui/backup_ui_stage.class.php b/backup/util/ui/backup_ui_stage.class.php index 2cc20455259..1ce9053dd52 100644 --- a/backup/util/ui/backup_ui_stage.class.php +++ b/backup/util/ui/backup_ui_stage.class.php @@ -470,6 +470,9 @@ class backup_ui_stage_complete extends backup_ui_stage_final { $output = ''; $output .= $renderer->box_start(); + if (!empty($this->results['include_file_references_to_external_content'])) { + $output .= $renderer->notification(get_string('filereferencesincluded', 'backup'), 'notifyproblem'); + } $output .= $renderer->notification(get_string('executionsuccess', 'backup'), 'notifysuccess'); $output .= $renderer->continue_button($restorerul); $output .= $renderer->box_end(); diff --git a/backup/util/ui/renderer.php b/backup/util/ui/renderer.php index 7afcdfd368a..9e0ab9dd4f9 100644 --- a/backup/util/ui/renderer.php +++ b/backup/util/ui/renderer.php @@ -92,6 +92,16 @@ class core_backup_renderer extends plugin_renderer_base { $html .= $this->backup_detail_pair(get_string('originalwwwroot', 'backup'), html_writer::tag('span', $details->original_wwwroot, array('class'=>'originalwwwroot')). html_writer::tag('span', '['.$details->original_site_identifier_hash.']', array('class'=>'sitehash sub-detail'))); + if (!empty($details->include_file_references_to_external_content)) { + $message = ''; + if (backup_general_helper::backup_is_samesite($details)) { + $message = $yestick . ' ' . get_string('filereferencessamesite', 'backup'); + } else { + $message = $notick . ' ' . get_string('filereferencesnotsamesite', 'backup'); + } + $html .= $this->backup_detail_pair(get_string('includefilereferences', 'backup'), $message); + } + $html .= html_writer::end_tag('div'); $html .= html_writer::start_tag('div', array('class'=>'backup-section settings-section')); diff --git a/blocks/private_files/edit.php b/blocks/private_files/edit.php index 5a7667bbd76..67b1e70d5bc 100644 --- a/blocks/private_files/edit.php +++ b/blocks/private_files/edit.php @@ -45,7 +45,7 @@ $PAGE->set_pagelayout('mydashboard'); $PAGE->set_pagetype('user-private-files'); $data = new stdClass(); -$options = array('subdirs'=>1, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL); +$options = array('subdirs'=>1, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>-1, 'accepted_types'=>'*'); file_prepare_standard_filemanager($data, 'files', $options, $context, 'user', 'private', 0); $mform = new block_private_files_form(null, array('data'=>$data, 'options'=>$options)); diff --git a/files/coursefilesedit_form.php b/files/coursefilesedit_form.php index e5498dcec18..fadd387413d 100644 --- a/files/coursefilesedit_form.php +++ b/files/coursefilesedit_form.php @@ -21,7 +21,7 @@ class coursefiles_edit_form extends moodleform { function definition() { $mform =& $this->_form; $contextid = $this->_customdata['contextid']; - $options = array('subdirs'=>1, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL); + $options = array('subdirs'=>1, 'maxfiles'=>-1, 'accepted_types'=>'*'); $mform->addElement('filemanager', 'files_filemanager', '', null, $options); $mform->addElement('hidden', 'contextid', $this->_customdata['contextid']); $this->set_data($this->_customdata['data']); diff --git a/lang/en/backup.php b/lang/en/backup.php index 3cb7ec138f9..fa809254ddb 100644 --- a/lang/en/backup.php +++ b/lang/en/backup.php @@ -117,6 +117,9 @@ $string['errorinvalidformat'] = 'Unknown backup format'; $string['errorinvalidformatinfo'] = 'The selected file is not a valid Moodle backup file and can\'t be restored.'; $string['executionsuccess'] = 'The backup file was successfully created.'; $string['filename'] = 'Filename'; +$string['filereferencesincluded'] = 'File references to external contents included in backup package, they won\'t work on other sites.'; +$string['filereferencessamesite'] = 'Backup is from the same site, file references can be restored'; +$string['filereferencesnotsamesite'] = 'Backup is from other site, file references cannot be restored'; $string['generalactivities'] = 'Include activities'; $string['generalanonymize'] = 'Anonymise information'; $string['generalbackdefaults'] = 'General backup defaults'; @@ -148,6 +151,7 @@ $string['includeditems'] = 'Included items:'; $string['includesection'] = 'Section {$a}'; $string['includeuserinfo'] = 'User data'; $string['loglifetime'] = 'Keep logs for'; +$string['includefilereferences'] = 'File references to external contents'; $string['locked'] = 'Locked'; $string['lockedbypermission'] = 'You don\'t have sufficient permissions to change this setting'; $string['lockedbyconfig'] = 'This setting has been locked by the default backup settings'; diff --git a/lang/en/error.php b/lang/en/error.php index 75fb0be028c..371cf469fd3 100644 --- a/lang/en/error.php +++ b/lang/en/error.php @@ -224,6 +224,7 @@ $string['errorsettinguserpref'] = 'Error setting user preference'; $string['errorunzippingfiles'] = 'Error unzipping files'; $string['expiredkey'] = 'Expired key'; $string['externalauthpassworderror'] = 'Non-empty password for external authentication'; +$string['externalfilenolocation'] = 'External file has no location path'; $string['failtoloadblocks'] = 'One or more blocks are registered in the database, but they all failed to load!'; $string['fieldrequired'] = '"{$a}" is a required field'; $string['fileexists'] = 'File exists'; diff --git a/lang/en/repository.php b/lang/en/repository.php index 0e84fcd4c64..b288e85f0f0 100644 --- a/lang/en/repository.php +++ b/lang/en/repository.php @@ -61,9 +61,11 @@ $string['commonrepositorysettings'] = 'Common repository settings'; $string['configallowexternallinks'] = 'This option enables all users to choose whether or not external media is copied into Moodle or not. If this is off then media is always copied into Moodle (this is usually best for overall data integrity and security). If this is on then users can choose each time they add media to a text.'; $string['configcacheexpire'] = 'The amount of time that file listings are cached locally (in seconds) when browsing external repositories.'; $string['configsaved'] = 'Configuration saved!'; -$string['confirmdelete'] = 'Are you sure you want to delete this repository - {$a}?'; +$string['confirmdelete'] = 'Are you sure you want to delete this repository - {$a}? If you choose "Continue and download", file references to external contents will be downloaded to moodle, but it could take long time to process.'; $string['confirmdeletefile'] = 'Are you sure you want to delete this file?'; -$string['confirmremove'] = 'Are you sure you want to remove this repository plugin, its options and all of its instances - {$a}?'; +$string['confirmremove'] = 'Are you sure you want to remove this repository plugin, its options and all of its instances - {$a}? If you choose "Continue and download", file references to external contents will be downloaded to moodle, but it could take long time to process.'; +$string['continueuninstall'] = 'Continue'; +$string['continueuninstallanddownload'] = 'Continue and download'; $string['copying'] = 'Copying'; $string['create'] = 'Create'; $string['createfolderfail'] = 'Fail to create this folder'; @@ -117,6 +119,7 @@ $string['invalidjson'] = 'Invalid JSON string'; $string['invalidplugin'] = 'Invalid repository {$a} plug-in'; $string['invalidfiletype'] = '{$a} filetype cannot be accepted.'; $string['invalidrepositoryid'] = 'Invalid repository ID'; +$string['invalidparams'] = 'Invalid parameters'; $string['isactive'] = 'Active?'; $string['keyword'] = 'Keyword'; $string['linkexternal'] = 'Link external'; @@ -179,6 +182,7 @@ $string['typenotvisible'] = 'Type not visible'; $string['upload'] = 'Upload this file'; $string['uploading'] = 'Uploading...'; $string['uploadsucc'] = 'The file has been uploaded successfully'; +$string['uselatestfile'] = 'Use latest file'; $string['usercontextrepositorydisabled'] = 'You cannot edit this repository in user context'; $string['usenonjsfilemanager'] = 'Open file manager in new window'; $string['usenonjsfilepicker'] = 'Open file picker in new window'; diff --git a/lib/cronlib.php b/lib/cronlib.php index b05e34f87c9..0e47a655c32 100644 --- a/lib/cronlib.php +++ b/lib/cronlib.php @@ -403,9 +403,7 @@ function cron_run() { cron_execute_plugin_type('format', 'course formats'); cron_execute_plugin_type('profilefield', 'profile fields'); cron_execute_plugin_type('webservice', 'webservices'); - // TODO: Repository lib.php files are messed up (include many other files, etc), so it is - // currently not possible to implement repository plugin cron using this infrastructure - // cron_execute_plugin_type('repository', 'repository plugins'); + cron_execute_plugin_type('repository', 'repository plugins'); cron_execute_plugin_type('qbehaviour', 'question behaviours'); cron_execute_plugin_type('qformat', 'question import/export formats'); cron_execute_plugin_type('qtype', 'question types'); @@ -462,6 +460,9 @@ function cron_run() { $fs = get_file_storage(); $fs->cron(); + mtrace("Clean up cached external files"); + // 1 week + cache_file::cleanup(array(), 60 * 60 * 24 * 7); mtrace("Cron script completed correctly"); diff --git a/lib/db/install.xml b/lib/db/install.xml index 1d83d84ead5..0463430e996 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -2326,7 +2326,7 @@ - +
@@ -2346,12 +2346,16 @@ - + + + + - + + @@ -2359,7 +2363,20 @@
- +
+ + + + + + + + + + + +
+ diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index b263a39e735..29af2df5f5d 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -592,5 +592,59 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2012051100.03); } + if ($oldversion < 2012052100.00) { + + // Define field referencefileid to be added to files + $table = new xmldb_table('files'); + + // Define field referencefileid to be added to files + $field = new xmldb_field('referencefileid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'sortorder'); + // Conditionally launch add field referencefileid + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field referencelastsync to be added to files + $field = new xmldb_field('referencelastsync', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'referencefileid'); + // Conditionally launch add field referencelastsync + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field referencelifetime to be added to files + $field = new xmldb_field('referencelastsync', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'referencefileid'); + // Conditionally launch add field referencelifetime + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $key = new xmldb_key('referencefileid', XMLDB_KEY_FOREIGN, array('referencefileid'), 'files_reference', array('id')); + // Launch add key referencefileid + $dbman->add_key($table, $key); + + // Define table files_reference to be created. + $table = new xmldb_table('files_reference'); + + // Adding fields to table files_reference. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('fileid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('repositoryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('lastsync', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('lifetime', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('reference', XMLDB_TYPE_TEXT, null, null, null, null, null); + + // Adding keys to table files_reference. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('repositoryid', XMLDB_KEY_FOREIGN, array('repositoryid'), 'repository_instances', array('id')); + + // Conditionally launch create table for files_reference + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Main savepoint reached + upgrade_main_savepoint(true, 2012052100.00); + } + return true; } diff --git a/lib/filelib.php b/lib/filelib.php index f10d8ca5d63..78af2871e18 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -374,7 +374,25 @@ function file_prepare_draft_area(&$draftitemid, $contextid, $component, $fileare if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) { continue; } - $fs->create_file_from_storedfile($file_record, $file); + $draftfile = $fs->create_file_from_storedfile($file_record, $file); + // XXX: This is a hack for file manager + // File manager needs to know the original file information before copying + // to draft area, so we append these information in mdl_files.source field + // {@link file_storage::search_references()} + // {@link file_storage::search_references_count()} + $sourcefield = $file->get_source(); + $newsourcefield = new stdClass; + $newsourcefield->source = $sourcefield; + $original = new stdClass; + $original->contextid = $contextid; + $original->component = $component; + $original->filearea = $filearea; + $original->itemid = $itemid; + $original->filename = $file->get_filename(); + $original->filepath = $file->get_filepath(); + $newsourcefield->original = file_storage::pack_reference($original); + $draftfile->set_source(serialize($newsourcefield)); + // End of file manager hack } } if (!is_null($text)) { @@ -631,6 +649,20 @@ function file_get_submitted_draft_itemid($elname) { return $param; } +/** + * Restore the original source field from draft files + * + * @param stored_file $storedfile This only works with draft files + * @return stored_file + */ +function file_restore_source_field_from_draft_file($storedfile) { + $source = unserialize($storedfile->get_source()); + if (!empty($source) && is_object($source)) { + $restoredsource = $source->source; + $storedfile->set_source($restoredsource); + } + return $storedfile; +} /** * Saves files from a draft file area to a real one (merging the list of files). * Can rewrite URLs in some content at the same time if desired. @@ -694,6 +726,16 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea if (!$file->is_directory()) { $filecount++; } + + if ($file->is_external_file()) { + $repoid = $file->get_repository_id(); + if (!empty($repoid)) { + $file_record['repositoryid'] = $repoid; + $file_record['reference'] = $file->get_reference(); + } + } + file_restore_source_field_from_draft_file($file); + $fs->create_file_from_storedfile($file_record, $file); } @@ -715,14 +757,40 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea $oldfile->delete(); continue; } + $newfile = $newhashes[$oldhash]; - if ($oldfile->get_contenthash() != $newfile->get_contenthash() or $oldfile->get_sortorder() != $newfile->get_sortorder() - or $oldfile->get_status() != $newfile->get_status() or $oldfile->get_license() != $newfile->get_license() - or $oldfile->get_author() != $newfile->get_author() or $oldfile->get_source() != $newfile->get_source()) { + // status changed, we delete old file, and create a new one + if ($oldfile->get_status() != $newfile->get_status()) { // file was changed, use updated with new timemodified data $oldfile->delete(); + // This file will be added later continue; } + + file_restore_source_field_from_draft_file($newfile); + // Replaced file content + if ($oldfile->get_contenthash() != $newfile->get_contenthash()) { + $oldfile->replace_content_with($newfile); + } + // Updated author + if ($oldfile->get_author() != $newfile->get_author()) { + $oldfile->set_author($newfile->get_author()); + } + // Updated license + if ($oldfile->get_license() != $newfile->get_license()) { + $oldfile->set_license($newfile->get_license()); + } + + // Updated file source + if ($oldfile->get_source() != $newfile->get_source()) { + $oldfile->set_source($newfile->get_source()); + } + + // Updated sort order + if ($oldfile->get_sortorder() != $newfile->get_sortorder()) { + $oldfile->set_sortorder($newfile->get_sortorder()); + } + // unchanged file or directory - we keep it as is unset($newhashes[$oldhash]); if (!$oldfile->is_directory()) { @@ -730,7 +798,7 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea } } - // now add new/changed files + // Add fresh file or the file which has changed status // the size and subdirectory tests are extra safety only, the UI should prevent it foreach ($newhashes as $file) { if (!$options['subdirs']) { @@ -749,6 +817,15 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea if (!$file->is_directory()) { $filecount++; } + + if ($file->is_external_file()) { + $repoid = $file->get_repository_id(); + if (!empty($repoid)) { + $file_record['repositoryid'] = $repoid; + $file_record['reference'] = $file->get_reference(); + } + } + $fs->create_file_from_storedfile($file_record, $file); } } @@ -2005,6 +2082,12 @@ function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownl } } + // handle external resource + if ($stored_file->is_external_file()) { + $stored_file->send_file($lifetime, $filter, $forcedownload, $options); + die; + } + if (!$stored_file or $stored_file->is_directory()) { // nothing to serve if ($dontdie) { @@ -2943,7 +3026,7 @@ class curl_cache { * @global stdClass $CFG * @param string $module which module is using curl_cache */ - function __construct($module = 'repository'){ + public function __construct($module = 'repository') { global $CFG; if (!empty($module)) { $this->dir = $CFG->cachedir.'/'.$module.'/'; @@ -2974,14 +3057,13 @@ class curl_cache { * @param mixed $param * @return bool|string */ - public function get($param){ + public function get($param) { global $CFG, $USER; $this->cleanup($this->ttl); $filename = 'u'.$USER->id.'_'.md5(serialize($param)); if(file_exists($this->dir.$filename)) { $lasttime = filemtime($this->dir.$filename); - if(time()-$lasttime > $this->ttl) - { + if (time()-$lasttime > $this->ttl) { return false; } else { $fp = fopen($this->dir.$filename, 'r'); @@ -3001,7 +3083,7 @@ class curl_cache { * @param mixed $param * @param mixed $val */ - public function set($param, $val){ + public function set($param, $val) { global $CFG, $USER; $filename = 'u'.$USER->id.'_'.md5(serialize($param)); $fp = fopen($this->dir.$filename, 'w'); @@ -3012,18 +3094,19 @@ class curl_cache { /** * Remove cache files * - * @param int $expire The number os seconds before expiry + * @param int $expire The number of seconds before expiry */ - public function cleanup($expire){ - if($dir = opendir($this->dir)){ + public function cleanup($expire) { + if ($dir = opendir($this->dir)) { while (false !== ($file = readdir($dir))) { if(!is_dir($file) && $file != '.' && $file != '..') { $lasttime = @filemtime($this->dir.$file); - if(time() - $lasttime > $expire){ + if (time() - $lasttime > $expire) { @unlink($this->dir.$file); } } } + closedir($dir); } } /** @@ -3032,12 +3115,12 @@ class curl_cache { * @global object $CFG * @global object $USER */ - public function refresh(){ + public function refresh() { global $CFG, $USER; - if($dir = opendir($this->dir)){ + if ($dir = opendir($this->dir)) { while (false !== ($file = readdir($dir))) { - if(!is_dir($file) && $file != '.' && $file != '..') { - if(strpos($file, 'u'.$USER->id.'_')!==false){ + if (!is_dir($file) && $file != '.' && $file != '..') { + if (strpos($file, 'u'.$USER->id.'_') !== false) { @unlink($this->dir.$file); } } @@ -3923,3 +4006,172 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } } + +/** + * Universe file cacheing class + * + * @package core_files + * @category files + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_file { + /** @var string */ + public $cachedir = ''; + + /** + * static method to create cache_file class instance + * + * @param array $options caching ooptions + */ + public static function get_instance($options = array()) { + return new cache_file($options); + } + + /** + * Constructor + * + * @param array $options + */ + private function __construct($options = array()) { + global $CFG; + + // Path to file caches. + if (isset($options['cachedir'])) { + $this->cachedir = $options['cachedir']; + } else { + $this->cachedir = $CFG->cachedir . '/filedir'; + } + + // Create cache directory. + if (!file_exists($this->cachedir)) { + mkdir($this->cachedir, $CFG->directorypermissions, true); + } + + // When use cache_file::get, it will check ttl. + if (isset($options['ttl']) && is_numeric($options['ttl'])) { + $this->ttl = $options['ttl']; + } else { + // One day. + $this->ttl = 60 * 60 * 24; + } + } + + /** + * Get cached file, false if file expires + * + * @param mixed $param + * @param array $options caching options + * @return bool|string + */ + public static function get($param, $options = array()) { + $instance = self::get_instance($options); + $filepath = $instance->generate_filepath($param); + if (file_exists($filepath)) { + $lasttime = filemtime($filepath); + if (time() - $lasttime > $instance->ttl) { + // Remove cache file. + unlink($filepath); + return false; + } else { + return $filepath; + } + } else { + return false; + } + } + + /** + * Static method to create cache from a file + * + * @param mixed $ref + * @param string $srcfile + * @param array $options + * @return string cached file path + */ + public static function create_from_file($ref, $srcfile, $options = array()) { + $instance = self::get_instance($options); + $cachedfilepath = $instance->generate_filepath($ref); + copy($srcfile, $cachedfilepath); + return $cachedfilepath; + } + + /** + * Static method to create cache from url + * + * @param mixed $ref file reference + * @param string $url file url + * @param array $options options + * @return string cached file path + */ + public static function create_from_url($ref, $url, $options = array()) { + global $CFG; + $instance = self::get_instance($options); + $cachedfilepath = $instance->generate_filepath($ref); + $fp = fopen($cachedfilepath, 'w'); + $curl = new curl; + $curl->download(array(array('url'=>$url, 'file'=>$fp))); + // Must close file handler. + fclose($fp); + return $cachedfilepath; + } + + /** + * Static method to create cache from string + * + * @param mixed $ref file reference + * @param string $url file url + * @param array $options options + * @return string cached file path + */ + public static function create_from_string($ref, $string, $options = array()) { + global $CFG; + $instance = self::get_instance($options); + $cachedfilepath = $instance->generate_filepath($ref); + $fp = fopen($cachedfilepath, 'w'); + fwrite($fp, $string); + // Must close file handler. + fclose($fp); + return $cachedfilepath; + } + + /** + * Build path to cache file + * + * @param mixed $ref + * @return string + */ + private function generate_filepath($ref) { + global $CFG; + $hash = sha1(serialize($ref)); + $l1 = $hash[0].$hash[1]; + $l2 = $hash[2].$hash[3]; + $dir = $this->cachedir . "/$l1/$l2"; + if (!file_exists($dir)) { + mkdir($dir, $CFG->directorypermissions, true); + } + return "$dir/$hash"; + } + + /** + * Remove cache files + * + * @param array $options options + * @param int $expire The number of seconds before expiry + */ + public static function cleanup($options = array(), $expire) { + global $CFG; + $instance = self::get_instance($options); + if ($dir = opendir($instance->cachedir)) { + while (($file = readdir($dir)) !== false) { + if (!is_dir($file) && $file != '.' && $file != '..') { + $lasttime = @filemtime($instance->cachedir . $file); + if(time() - $lasttime > $expire){ + @unlink($instance->cachedir . $file); + } + } + } + closedir($dir); + } + } +} diff --git a/lib/filestorage/file_exceptions.php b/lib/filestorage/file_exceptions.php index 93eef5b0164..68c01de2e39 100644 --- a/lib/filestorage/file_exceptions.php +++ b/lib/filestorage/file_exceptions.php @@ -65,7 +65,7 @@ class stored_file_creation_exception extends file_exception { * @param string $filename file name * @param string $debuginfo extra debug info */ - function __construct($contextid, $component, $filearea, $itemid, $filepath, $filename, $debuginfo = NULL) { + function __construct($contextid, $component, $filearea, $itemid, $filepath, $filename, $debuginfo = null) { $a = new stdClass(); $a->contextid = $contextid; $a->component = $component; @@ -91,8 +91,8 @@ class file_access_exception extends file_exception { * * @param string $debuginfo extra debug info */ - function __construct($debuginfo = NULL) { - parent::__construct('nopermissions', NULL, $debuginfo); + public function __construct($debuginfo = null) { + parent::__construct('nopermissions', null, $debuginfo); } } @@ -111,7 +111,28 @@ class file_pool_content_exception extends file_exception { * @param string $contenthash content hash * @param string $debuginfo extra debug info */ - function __construct($contenthash, $debuginfo = NULL) { + public function __construct($contenthash, $debuginfo = null) { parent::__construct('hashpoolproblem', $contenthash, $debuginfo); } } + +/** + * Exception related to external file support + * + * @package core_files + * @category files + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class external_file_exception extends file_exception { + /** + * Constructor + * + * @param string $errorcode error code + * @param stdClass $a extra information + * @param string $debuginfo extra debug info + */ + public function __construct($errorcode, $a = null, $debuginfo = null) { + parent::__construct($errorcode, '', '', $a, $debuginfo); + } +} diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index 9855a6d0973..55a6307f76a 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -145,11 +145,12 @@ class file_storage { /** * Create instance of file class from database record. * - * @param stdClass $file_record record from the files table + * @param stdClass $filerecord record from the files table * @return stored_file instance of file abstraction class */ - public function get_file_instance(stdClass $file_record) { - return new stored_file($this, $file_record, $this->filedir); + public function get_file_instance(stdClass $filerecord) { + $storedfile = new stored_file($this, $filerecord, $this->filedir); + return $storedfile; } /** @@ -271,8 +272,13 @@ class file_storage { public function get_file_by_id($fileid) { global $DB; - if ($file_record = $DB->get_record('files', array('id'=>$fileid))) { - return $this->get_file_instance($file_record); + $sql = "SELECT f.*, r.repositoryid, r.reference + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE f.id = ?"; + if ($filerecord = $DB->get_record_sql($sql, array($fileid))) { + return $this->get_file_instance($filerecord); } else { return false; } @@ -287,8 +293,13 @@ class file_storage { public function get_file_by_hash($pathnamehash) { global $DB; - if ($file_record = $DB->get_record('files', array('pathnamehash'=>$pathnamehash))) { - return $this->get_file_instance($file_record); + $sql = "SELECT f.*, r.repositoryid, r.reference + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE f.pathnamehash = ?"; + if ($filerecord = $DB->get_record_sql($sql, array($pathnamehash))) { + return $this->get_file_instance($filerecord); } else { return false; } @@ -351,6 +362,29 @@ class file_storage { return !$DB->record_exists_sql($sql, $params); } + /** + * Returns all files belonging to given repository + * + * @param int $repositoryid + * @param string $sort + */ + public function get_external_files($repositoryid, $sort = 'sortorder, itemid, filepath, filename') { + global $DB; + $sql = "SELECT f.*, r.repositoryid, r.reference + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE r.repositoryid = ? + ORDER BY $sort"; + + $result = array(); + $filerecords = $DB->get_records_sql($sql, array($repositoryid)); + foreach ($filerecords as $filerecord) { + $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord); + } + return $result; + } + /** * Returns all area files (optionally limited by itemid) * @@ -367,16 +401,29 @@ class file_storage { $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea); if ($itemid !== false) { + $itemidsql = ' AND f.itemid = :itemid '; $conditions['itemid'] = $itemid; + } else { + $itemidsql = ''; } + $sql = "SELECT f.*, r.repositoryid, r.reference + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE f.contextid = :contextid + AND f.component = :component + AND f.filearea = :filearea + $itemidsql + ORDER BY $sort"; + $result = array(); - $file_records = $DB->get_records('files', $conditions, $sort); - foreach ($file_records as $file_record) { - if (!$includedirs and $file_record->filename === '.') { + $filerecords = $DB->get_records_sql($sql, $conditions); + foreach ($filerecords as $filerecord) { + if (!$includedirs and $filerecord->filename === '.') { continue; } - $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); + $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord); } return $result; } @@ -468,12 +515,12 @@ class file_storage { $files = array(); $dirs = array(); - $file_records = $DB->get_records_sql($sql, $params); - foreach ($file_records as $file_record) { - if ($file_record->filename == '.') { - $dirs[$file_record->pathnamehash] = $this->get_file_instance($file_record); + $filerecords = $DB->get_records_sql($sql, $params); + foreach ($filerecords as $filerecord) { + if ($filerecord->filename == '.') { + $dirs[$filerecord->pathnamehash] = $this->get_file_instance($filerecord); } else { - $files[$file_record->pathnamehash] = $this->get_file_instance($file_record); + $files[$filerecord->pathnamehash] = $this->get_file_instance($filerecord); } } $result = array_merge($dirs, $files); @@ -493,12 +540,12 @@ class file_storage { AND id <> :dirid ORDER BY $sort"; $reqlevel = substr_count($filepath, '/') + 1; - $file_records = $DB->get_records_sql($sql, $params); - foreach ($file_records as $file_record) { - if (substr_count($file_record->filepath, '/') !== $reqlevel) { + $filerecords = $DB->get_records_sql($sql, $params); + foreach ($filerecords as $filerecord) { + if (substr_count($filerecord->filepath, '/') !== $reqlevel) { continue; } - $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); + $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord); } } @@ -508,9 +555,9 @@ class file_storage { AND filepath = :filepath AND filename <> '.' ORDER BY $sort"; - $file_records = $DB->get_records_sql($sql, $params); - foreach ($file_records as $file_record) { - $result[$file_record->pathnamehash] = $this->get_file_instance($file_record); + $filerecords = $DB->get_records_sql($sql, $params); + foreach ($filerecords as $filerecord) { + $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord); } } @@ -540,9 +587,9 @@ class file_storage { $conditions['itemid'] = $itemid; } - $file_records = $DB->get_records('files', $conditions); - foreach ($file_records as $file_record) { - $this->get_file_instance($file_record)->delete(); + $filerecords = $DB->get_records('files', $conditions); + foreach ($filerecords as $filerecord) { + $this->get_file_instance($filerecord)->delete(); } return true; // BC only @@ -572,11 +619,11 @@ class file_storage { $params['component'] = $component; $params['filearea'] = $filearea; - $file_records = $DB->get_recordset_select('files', $where, $params); - foreach ($file_records as $file_record) { - $this->get_file_instance($file_record)->delete(); + $filerecords = $DB->get_recordset_select('files', $where, $params); + foreach ($filerecords as $filerecord) { + $this->get_file_instance($filerecord)->delete(); } - $file_records->close(); + $filerecords->close(); } /** @@ -699,11 +746,11 @@ class file_storage { /** * Add new local file based on existing local file. * - * @param stdClass|array $file_record object or array describing changes + * @param stdClass|array $filerecord object or array describing changes * @param stored_file|int $fileorid id or stored_file instance of the existing local file * @return stored_file instance of newly created file */ - public function create_file_from_storedfile($file_record, $fileorid) { + public function create_file_from_storedfile($filerecord, $fileorid) { global $DB; if ($fileorid instanceof stored_file) { @@ -712,20 +759,26 @@ class file_storage { $fid = $fileorid; } - $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record! + $filerecord = (array)$filerecord; // We support arrays too, do not modify the submitted record! - unset($file_record['id']); - unset($file_record['filesize']); - unset($file_record['contenthash']); - unset($file_record['pathnamehash']); + unset($filerecord['id']); + unset($filerecord['filesize']); + unset($filerecord['contenthash']); + unset($filerecord['pathnamehash']); - if (!$newrecord = $DB->get_record('files', array('id'=>$fid))) { + $sql = "SELECT f.*, r.repositoryid, r.reference + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE f.id = ?"; + + if (!$newrecord = $DB->get_record_sql($sql, array($fid))) { throw new file_exception('storedfileproblem', 'File does not exist'); } unset($newrecord->id); - foreach ($file_record as $key=>$value) { + foreach ($filerecord 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('storedfileproblem', 'Invalid contextid'); @@ -776,6 +829,10 @@ class file_storage { } } + if ($key == 'referencefileid' or $key == 'referencelastsync' or $key == 'referencelifetime') { + $value = clean_param($value, PARAM_INT); + } + $newrecord->$key = $value; } @@ -790,6 +847,21 @@ class file_storage { return $this->get_file_instance($newrecord); } + if (!empty($newrecord->repositoryid)) { + try { + $referencerecord = new stdClass; + $referencerecord->repositoryid = $newrecord->repositoryid; + $referencerecord->reference = $newrecord->reference; + $referencerecord->lastsync = $newrecord->referencelastsync; + $referencerecord->lifetime = $newrecord->referencelifetime; + $referencerecord->id = $DB->insert_record('files_reference', $referencerecord); + } catch (dml_exception $e) { + throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, + $newrecord->filepath, $newrecord->filename, $e->debuginfo); + } + $newrecord->referencefileid = $referencerecord->id; + } + try { $newrecord->id = $DB->insert_record('files', $newrecord); } catch (dml_exception $e) { @@ -797,6 +869,7 @@ class file_storage { $newrecord->filepath, $newrecord->filename, $e->debuginfo); } + $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid); return $this->get_file_instance($newrecord); @@ -805,16 +878,16 @@ class file_storage { /** * Add new local file. * - * @param stdClass|array $file_record object or array describing file + * @param stdClass|array $filerecord object or array describing file * @param string $url the URL to the file * @param array $options {@link download_file_content()} options * @param bool $usetempfile use temporary file for download, may prevent out of memory problems * @return stored_file */ - public function create_file_from_url($file_record, $url, array $options = NULL, $usetempfile = false) { + public function create_file_from_url($filerecord, $url, array $options = null, $usetempfile = false) { - $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects - $file_record = (object)$file_record; // we support arrays too + $filerecord = (array)$filerecord; // Do not modify the submitted record, this cast unlinks objects. + $filerecord = (object)$filerecord; // We support arrays too. $headers = isset($options['headers']) ? $options['headers'] : null; $postdata = isset($options['postdata']) ? $options['postdata'] : null; @@ -824,13 +897,13 @@ class file_storage { $skipcertverify = isset($options['skipcertverify']) ? $options['skipcertverify'] : false; $calctimeout = isset($options['calctimeout']) ? $options['calctimeout'] : false; - if (!isset($file_record->filename)) { + if (!isset($filerecord->filename)) { $parts = explode('/', $url); $filename = array_pop($parts); - $file_record->filename = clean_param($filename, PARAM_FILE); + $filerecord->filename = clean_param($filename, PARAM_FILE); } - $source = !empty($file_record->source) ? $file_record->source : $url; - $file_record->source = clean_param($source, PARAM_URL); + $source = !empty($filerecord->source) ? $filerecord->source : $url; + $filerecord->source = clean_param($source, PARAM_URL); if ($usetempfile) { check_dir_exists($this->tempdir); @@ -840,7 +913,7 @@ class file_storage { throw new file_exception('storedfileproblem', 'Can not fetch file form URL'); } try { - $newfile = $this->create_file_from_pathname($file_record, $tmpfile); + $newfile = $this->create_file_from_pathname($filerecord, $tmpfile); @unlink($tmpfile); return $newfile; } catch (Exception $e) { @@ -853,104 +926,108 @@ class file_storage { if ($content === false) { throw new file_exception('storedfileproblem', 'Can not fetch file form URL'); } - return $this->create_file_from_string($file_record, $content); + return $this->create_file_from_string($filerecord, $content); } } /** * Add new local file. * - * @param stdClass|array $file_record object or array describing file + * @param stdClass|array $filerecord object or array describing file * @param string $pathname path to file or content of file * @return stored_file */ - public function create_file_from_pathname($file_record, $pathname) { + public function create_file_from_pathname($filerecord, $pathname) { global $DB; - $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects - $file_record = (object)$file_record; // we support arrays too + $filerecord = (array)$filerecord; // Do not modify the submitted record, this cast unlinks objects. + $filerecord = (object)$filerecord; // 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) { + if (!is_number($filerecord->contextid) or $filerecord->contextid < 1) { throw new file_exception('storedfileproblem', 'Invalid contextid'); } - $file_record->component = clean_param($file_record->component, PARAM_COMPONENT); - if (empty($file_record->component)) { + $filerecord->component = clean_param($filerecord->component, PARAM_COMPONENT); + if (empty($filerecord->component)) { throw new file_exception('storedfileproblem', 'Invalid component'); } - $file_record->filearea = clean_param($file_record->filearea, PARAM_AREA); - if (empty($file_record->filearea)) { + $filerecord->filearea = clean_param($filerecord->filearea, PARAM_AREA); + if (empty($filerecord->filearea)) { throw new file_exception('storedfileproblem', 'Invalid filearea'); } - if (!is_number($file_record->itemid) or $file_record->itemid < 0) { + if (!is_number($filerecord->itemid) or $filerecord->itemid < 0) { throw new file_exception('storedfileproblem', 'Invalid itemid'); } - if (!empty($file_record->sortorder)) { - if (!is_number($file_record->sortorder) or $file_record->sortorder < 0) { - $file_record->sortorder = 0; + if (!empty($filerecord->sortorder)) { + if (!is_number($filerecord->sortorder) or $filerecord->sortorder < 0) { + $filerecord->sortorder = 0; } } else { - $file_record->sortorder = 0; + $filerecord->sortorder = 0; } - $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) { + $filerecord->referencefileid = !isset($filerecord->referencefileid) ? 0 : $filerecord->referencefileid; + $filerecord->referencelastsync = !isset($filerecord->referencelastsync) ? 0 : $filerecord->referencelastsync; + $filerecord->referencelifetime = !isset($filerecord->referencelifetime) ? 0 : $filerecord->referencelifetime; + + $filerecord->filepath = clean_param($filerecord->filepath, PARAM_PATH); + if (strpos($filerecord->filepath, '/') !== 0 or strrpos($filerecord->filepath, '/') !== strlen($filerecord->filepath)-1) { // path must start and end with '/' throw new file_exception('storedfileproblem', 'Invalid file path'); } - $file_record->filename = clean_param($file_record->filename, PARAM_FILE); - if ($file_record->filename === '') { + $filerecord->filename = clean_param($filerecord->filename, PARAM_FILE); + if ($filerecord->filename === '') { // filename must not be empty throw new file_exception('storedfileproblem', 'Invalid file name'); } $now = time(); - if (isset($file_record->timecreated)) { - if (!is_number($file_record->timecreated)) { + if (isset($filerecord->timecreated)) { + if (!is_number($filerecord->timecreated)) { throw new file_exception('storedfileproblem', 'Invalid file timecreated'); } - if ($file_record->timecreated < 0) { + if ($filerecord->timecreated < 0) { //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) - $file_record->timecreated = 0; + $filerecord->timecreated = 0; } } else { - $file_record->timecreated = $now; + $filerecord->timecreated = $now; } - if (isset($file_record->timemodified)) { - if (!is_number($file_record->timemodified)) { + if (isset($filerecord->timemodified)) { + if (!is_number($filerecord->timemodified)) { throw new file_exception('storedfileproblem', 'Invalid file timemodified'); } - if ($file_record->timemodified < 0) { + if ($filerecord->timemodified < 0) { //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) - $file_record->timemodified = 0; + $filerecord->timemodified = 0; } } else { - $file_record->timemodified = $now; + $filerecord->timemodified = $now; } $newrecord = new stdClass(); - $newrecord->contextid = $file_record->contextid; - $newrecord->component = $file_record->component; - $newrecord->filearea = $file_record->filearea; - $newrecord->itemid = $file_record->itemid; - $newrecord->filepath = $file_record->filepath; - $newrecord->filename = $file_record->filename; + $newrecord->contextid = $filerecord->contextid; + $newrecord->component = $filerecord->component; + $newrecord->filearea = $filerecord->filearea; + $newrecord->itemid = $filerecord->itemid; + $newrecord->filepath = $filerecord->filepath; + $newrecord->filename = $filerecord->filename; - $newrecord->timecreated = $file_record->timecreated; - $newrecord->timemodified = $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; - $newrecord->source = empty($file_record->source) ? null : $file_record->source; - $newrecord->author = empty($file_record->author) ? null : $file_record->author; - $newrecord->license = empty($file_record->license) ? null : $file_record->license; - $newrecord->sortorder = $file_record->sortorder; + $newrecord->timecreated = $filerecord->timecreated; + $newrecord->timemodified = $filerecord->timemodified; + $newrecord->mimetype = empty($filerecord->mimetype) ? mimeinfo('type', $filerecord->filename) : $filerecord->mimetype; + $newrecord->userid = empty($filerecord->userid) ? null : $filerecord->userid; + $newrecord->source = empty($filerecord->source) ? null : $filerecord->source; + $newrecord->author = empty($filerecord->author) ? null : $filerecord->author; + $newrecord->license = empty($filerecord->license) ? null : $filerecord->license; + $newrecord->sortorder = $filerecord->sortorder; list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_file_to_pool($pathname); @@ -974,97 +1051,100 @@ class file_storage { /** * Add new local file. * - * @param stdClass|array $file_record object or array describing file + * @param stdClass|array $filerecord object or array describing file * @param string $content content of file * @return stored_file */ - public function create_file_from_string($file_record, $content) { + public function create_file_from_string($filerecord, $content) { global $DB; - $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects - $file_record = (object)$file_record; // we support arrays too + $filerecord = (array)$filerecord; // Do not modify the submitted record, this cast unlinks objects. + $filerecord = (object)$filerecord; // 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) { + if (!is_number($filerecord->contextid) or $filerecord->contextid < 1) { throw new file_exception('storedfileproblem', 'Invalid contextid'); } - $file_record->component = clean_param($file_record->component, PARAM_COMPONENT); - if (empty($file_record->component)) { + $filerecord->component = clean_param($filerecord->component, PARAM_COMPONENT); + if (empty($filerecord->component)) { throw new file_exception('storedfileproblem', 'Invalid component'); } - $file_record->filearea = clean_param($file_record->filearea, PARAM_AREA); - if (empty($file_record->filearea)) { + $filerecord->filearea = clean_param($filerecord->filearea, PARAM_AREA); + if (empty($filerecord->filearea)) { throw new file_exception('storedfileproblem', 'Invalid filearea'); } - if (!is_number($file_record->itemid) or $file_record->itemid < 0) { + if (!is_number($filerecord->itemid) or $filerecord->itemid < 0) { throw new file_exception('storedfileproblem', 'Invalid itemid'); } - if (!empty($file_record->sortorder)) { - if (!is_number($file_record->sortorder) or $file_record->sortorder < 0) { - $file_record->sortorder = 0; + if (!empty($filerecord->sortorder)) { + if (!is_number($filerecord->sortorder) or $filerecord->sortorder < 0) { + $filerecord->sortorder = 0; } } else { - $file_record->sortorder = 0; + $filerecord->sortorder = 0; } + $filerecord->referencefileid = !isset($filerecord->referencefileid) ? 0 : $filerecord->referencefileid; + $filerecord->referencelastsync = !isset($filerecord->referencelastsync) ? 0 : $filerecord->referencelastsync; + $filerecord->referencelifetime = !isset($filerecord->referencelifetime) ? 0 : $filerecord->referencelifetime; - $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) { + $filerecord->filepath = clean_param($filerecord->filepath, PARAM_PATH); + if (strpos($filerecord->filepath, '/') !== 0 or strrpos($filerecord->filepath, '/') !== strlen($filerecord->filepath)-1) { // path must start and end with '/' throw new file_exception('storedfileproblem', 'Invalid file path'); } - $file_record->filename = clean_param($file_record->filename, PARAM_FILE); - if ($file_record->filename === '') { + $filerecord->filename = clean_param($filerecord->filename, PARAM_FILE); + if ($filerecord->filename === '') { // path must start and end with '/' throw new file_exception('storedfileproblem', 'Invalid file name'); } $now = time(); - if (isset($file_record->timecreated)) { - if (!is_number($file_record->timecreated)) { + if (isset($filerecord->timecreated)) { + if (!is_number($filerecord->timecreated)) { throw new file_exception('storedfileproblem', 'Invalid file timecreated'); } - if ($file_record->timecreated < 0) { + if ($filerecord->timecreated < 0) { //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) - $file_record->timecreated = 0; + $filerecord->timecreated = 0; } } else { - $file_record->timecreated = $now; + $filerecord->timecreated = $now; } - if (isset($file_record->timemodified)) { - if (!is_number($file_record->timemodified)) { + if (isset($filerecord->timemodified)) { + if (!is_number($filerecord->timemodified)) { throw new file_exception('storedfileproblem', 'Invalid file timemodified'); } - if ($file_record->timemodified < 0) { + if ($filerecord->timemodified < 0) { //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) - $file_record->timemodified = 0; + $filerecord->timemodified = 0; } } else { - $file_record->timemodified = $now; + $filerecord->timemodified = $now; } $newrecord = new stdClass(); - $newrecord->contextid = $file_record->contextid; - $newrecord->component = $file_record->component; - $newrecord->filearea = $file_record->filearea; - $newrecord->itemid = $file_record->itemid; - $newrecord->filepath = $file_record->filepath; - $newrecord->filename = $file_record->filename; + $newrecord->contextid = $filerecord->contextid; + $newrecord->component = $filerecord->component; + $newrecord->filearea = $filerecord->filearea; + $newrecord->itemid = $filerecord->itemid; + $newrecord->filepath = $filerecord->filepath; + $newrecord->filename = $filerecord->filename; - $newrecord->timecreated = $file_record->timecreated; - $newrecord->timemodified = $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; - $newrecord->source = empty($file_record->source) ? null : $file_record->source; - $newrecord->author = empty($file_record->author) ? null : $file_record->author; - $newrecord->license = empty($file_record->license) ? null : $file_record->license; - $newrecord->sortorder = $file_record->sortorder; + $newrecord->timecreated = $filerecord->timecreated; + $newrecord->timemodified = $filerecord->timemodified; + $newrecord->mimetype = empty($filerecord->mimetype) ? mimeinfo('type', $filerecord->filename) : $filerecord->mimetype; + $newrecord->userid = empty($filerecord->userid) ? null : $filerecord->userid; + $newrecord->source = empty($filerecord->source) ? null : $filerecord->source; + $newrecord->author = empty($filerecord->author) ? null : $filerecord->author; + $newrecord->license = empty($filerecord->license) ? null : $filerecord->license; + $newrecord->sortorder = $filerecord->sortorder; list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content); @@ -1085,10 +1165,135 @@ class file_storage { return $this->get_file_instance($newrecord); } + /** + * Create a moodle file from file reference information + * + * @param stdClass $filerecord + * @param int $repositoryid + * @param string $reference + * @param array $options options for creating external file + * @return stored_file + */ + public function create_file_from_reference($filerecord, $repositoryid, $reference, $options = array()) { + global $DB; + + $filerecord = (array)$filerecord; // Do not modify the submitted record, this cast unlinks objects. + $filerecord = (object)$filerecord; // We support arrays too. + + // validate all parameters, we do not want any rubbish stored in database, right? + if (!is_number($filerecord->contextid) or $filerecord->contextid < 1) { + throw new file_exception('storedfileproblem', 'Invalid contextid'); + } + + $filerecord->component = clean_param($filerecord->component, PARAM_COMPONENT); + if (empty($filerecord->component)) { + throw new file_exception('storedfileproblem', 'Invalid component'); + } + + $filerecord->filearea = clean_param($filerecord->filearea, PARAM_AREA); + if (empty($filerecord->filearea)) { + throw new file_exception('storedfileproblem', 'Invalid filearea'); + } + + if (!is_number($filerecord->itemid) or $filerecord->itemid < 0) { + throw new file_exception('storedfileproblem', 'Invalid itemid'); + } + + if (!empty($filerecord->sortorder)) { + if (!is_number($filerecord->sortorder) or $filerecord->sortorder < 0) { + $filerecord->sortorder = 0; + } + } else { + $filerecord->sortorder = 0; + } + + $filerecord->referencefileid = empty($filerecord->referencefileid) ? 0 : $filerecord->referencefileid; + $filerecord->referencelastsync = empty($filerecord->referencelastsync) ? 0 : $filerecord->referencelastsync; + $filerecord->referencelifetime = empty($filerecord->referencelifetime) ? 0 : $filerecord->referencelifetime; + $filerecord->mimetype = empty($filerecord->mimetype) ? mimeinfo('type', $filerecord->filename) : $filerecord->mimetype; + $filerecord->userid = empty($filerecord->userid) ? null : $filerecord->userid; + $filerecord->source = empty($filerecord->source) ? null : $filerecord->source; + $filerecord->author = empty($filerecord->author) ? null : $filerecord->author; + $filerecord->license = empty($filerecord->license) ? null : $filerecord->license; + $filerecord->filepath = clean_param($filerecord->filepath, PARAM_PATH); + if (strpos($filerecord->filepath, '/') !== 0 or strrpos($filerecord->filepath, '/') !== strlen($filerecord->filepath)-1) { + // Path must start and end with '/'. + throw new file_exception('storedfileproblem', 'Invalid file path'); + } + + $filerecord->filename = clean_param($filerecord->filename, PARAM_FILE); + if ($filerecord->filename === '') { + // Path must start and end with '/'. + throw new file_exception('storedfileproblem', 'Invalid file name'); + } + + $now = time(); + if (isset($filerecord->timecreated)) { + if (!is_number($filerecord->timecreated)) { + throw new file_exception('storedfileproblem', 'Invalid file timecreated'); + } + if ($filerecord->timecreated < 0) { + // NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) + $filerecord->timecreated = 0; + } + } else { + $filerecord->timecreated = $now; + } + + if (isset($filerecord->timemodified)) { + if (!is_number($filerecord->timemodified)) { + throw new file_exception('storedfileproblem', 'Invalid file timemodified'); + } + if ($filerecord->timemodified < 0) { + // NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak) + $filerecord->timemodified = 0; + } + } else { + $filerecord->timemodified = $now; + } + + // Insert file reference record. + try { + $referencerecord = new stdClass; + $referencerecord->repositoryid = $repositoryid; + $referencerecord->reference = $reference; + $referencerecord->lastsync = $filerecord->referencelastsync; + $referencerecord->lifetime = $filerecord->referencelifetime; + $referencerecord->id = $DB->insert_record('files_reference', $referencerecord); + } catch (dml_exception $e) { + throw $e; + } + + $filerecord->referencefileid = $referencerecord->id; + + // External file doesn't have content in moodle. + // So we create an empty file for it. + list($filerecord->contenthash, $filerecord->filesize, $newfile) = $this->add_string_to_pool(null); + + $filerecord->pathnamehash = $this->get_pathname_hash($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid, $filerecord->filepath, $filerecord->filename); + + try { + $filerecord->id = $DB->insert_record('files', $filerecord); + } catch (dml_exception $e) { + if ($newfile) { + $this->deleted_file_cleanup($filerecord->contenthash); + } + throw new stored_file_creation_exception($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid, + $filerecord->filepath, $filerecord->filename, $e->debuginfo); + } + + $this->create_directory($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid, $filerecord->filepath, $filerecord->userid); + + // Adding repositoryid and reference to file record to create stored_file instance + $filerecord->repositoryid = $repositoryid; + $filerecord->reference = $reference; + return $this->get_file_instance($filerecord); + } + /** * Creates new image file from existing. * - * @param stdClass|array $file_record object or array describing new file + * @param stdClass|array $filerecord object or array describing new file * @param int|stored_file $fid file id or stored file object * @param int $newwidth in pixels * @param int $newheight in pixels @@ -1096,7 +1301,7 @@ class file_storage { * @param int $quality depending on image type 0-100 for jpeg, 0-9 (0 means no compression) for png * @return stored_file */ - public function convert_image($file_record, $fid, $newwidth = NULL, $newheight = NULL, $keepaspectratio = true, $quality = NULL) { + public function convert_image($filerecord, $fid, $newwidth = null, $newheight = null, $keepaspectratio = true, $quality = null) { if (!function_exists('imagecreatefromstring')) { //Most likely the GD php extension isn't installed //image conversion cannot succeed @@ -1107,9 +1312,9 @@ class file_storage { $fid = $fid->get_id(); } - $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record! + $filerecord = (array)$filerecord; // We support arrays too, do not modify the submitted record! - if (!$file = $this->get_file_by_id($fid)) { // make sure file really exists and we we correct data + if (!$file = $this->get_file_by_id($fid)) { // Make sure file really exists and we we correct data. throw new file_exception('storedfileproblem', 'File does not exist'); } @@ -1117,12 +1322,12 @@ class file_storage { throw new file_exception('storedfileproblem', 'File is not an image'); } - if (!isset($file_record['filename'])) { - $file_record['filename'] = $file->get_filename(); + if (!isset($filerecord['filename'])) { + $filerecord['filename'] = $file->get_filename(); } - if (!isset($file_record['mimetype'])) { - $file_record['mimetype'] = mimeinfo('type', $file_record['filename']); + if (!isset($filerecord['mimetype'])) { + $filerecord['mimetype'] = mimeinfo('type', $filerecord['filename']); } $width = $imageinfo['width']; @@ -1171,7 +1376,7 @@ class file_storage { } ob_start(); - switch ($file_record['mimetype']) { + switch ($filerecord['mimetype']) { case 'image/gif': imagegif($img); break; @@ -1201,7 +1406,7 @@ class file_storage { throw new file_exception('storedfileproblem', 'Can not convert image'); } - return $this->create_file_from_string($file_record, $content); + return $this->create_file_from_string($filerecord, $content); } /** @@ -1310,6 +1515,18 @@ class file_storage { return xsendfile("$hashpath/$contenthash"); } + /** + * Content exists + * + * @param string $contenthash + * @return bool + */ + public function content_exists($contenthash) { + $dir = $this->path_from_hash($contenthash); + $filepath = $dir . '/' . $contenthash; + return file_exists($filepath); + } + /** * Return path to file with given hash. * @@ -1408,6 +1625,167 @@ class file_storage { chmod($trashfile, $this->filepermissions); // fix permissions if needed } + /** + * When user referring to a moodle file, we build the reference field + * + * @param array $params + * @return string + */ + public static function pack_reference($params) { + $params = (array)$params; + $reference = array(); + $reference['contextid'] = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT); + $reference['component'] = is_null($params['component']) ? null : clean_param($params['component'], PARAM_COMPONENT); + $reference['itemid'] = is_null($params['itemid']) ? null : clean_param($params['itemid'], PARAM_INT); + $reference['filearea'] = is_null($params['filearea']) ? null : clean_param($params['filearea'], PARAM_AREA); + $reference['filepath'] = is_null($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH);; + $reference['filename'] = is_null($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE); + return base64_encode(serialize($reference)); + } + + /** + * Unpack reference field + * + * @param string $str + * @return array + */ + public static function unpack_reference($str) { + return unserialize(base64_decode($str)); + } + + /** + * Search references by providing reference content + * + * @param string $str + * @return array + */ + public function search_references($str) { + global $DB; + $sql = "SELECT f.*, r.repositoryid, r.reference + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE r.reference = ?"; + + $rs = $DB->get_recordset_sql($sql, array($str)); + $files = array(); + foreach ($rs as $filerecord) { + $file = $this->get_file_instance($filerecord); + if ($file->is_external_file()) { + $files[$filerecord->pathnamehash] = $file; + } + } + + return $files; + } + + /** + * Search references count by providing reference content + * + * @param string $str + * @return int + */ + public function search_references_count($str) { + global $DB; + $sql = "SELECT COUNT(f.id) + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE r.reference = ?"; + + $count = $DB->count_records_sql($sql, array($str)); + return $count; + } + + /** + * Return all files referring to provided stored_file instance + * This won't work for draft files + * + * @param stored_file $storedfile + * @return array + */ + public function get_references_by_storedfile($storedfile) { + global $DB; + + $params = array(); + $params['contextid'] = $storedfile->get_contextid(); + $params['component'] = $storedfile->get_component(); + $params['filearea'] = $storedfile->get_filearea(); + $params['itemid'] = $storedfile->get_itemid(); + $params['filename'] = $storedfile->get_filename(); + $params['filepath'] = $storedfile->get_filepath(); + $params['userid'] = $storedfile->get_userid(); + + $reference = self::pack_reference($params); + + $sql = "SELECT f.*, r.repositoryid, r.reference + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE r.reference = ?"; + + $rs = $DB->get_recordset_sql($sql, array($reference)); + $files = array(); + foreach ($rs as $filerecord) { + $file = $this->get_file_instance($filerecord); + if ($file->is_external_file()) { + $files[$filerecord->pathnamehash] = $file; + } + } + + return $files; + } + + /** + * Return the count files referring to provided stored_file instance + * This won't work for draft files + * + * @param stored_file $storedfile + * @return int + */ + public function get_references_count_by_storedfile($storedfile) { + global $DB; + + $params = array(); + $params['contextid'] = $storedfile->get_contextid(); + $params['component'] = $storedfile->get_component(); + $params['filearea'] = $storedfile->get_filearea(); + $params['itemid'] = $storedfile->get_itemid(); + $params['filename'] = $storedfile->get_filename(); + $params['filepath'] = $storedfile->get_filepath(); + $params['userid'] = $storedfile->get_userid(); + + $reference = self::pack_reference($params); + + $sql = "SELECT COUNT(f.id) + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE r.reference = ?"; + + $count = $DB->count_records_sql($sql, array($reference)); + return $count; + } + + /** + * Convert file alias to local file + * + * @param stored_file $storedfile a stored_file instances + * @return stored_file|bool stored_file or return false when fail + */ + public function import_external_file($storedfile) { + global $CFG; + require_once($CFG->dirroot.'/repository/lib.php'); + // sync external file + if (repository::sync_external_file($storedfile)) { + // Remove file references + $storedfile->delete_reference(); + return $storedfile; + } else { + return false; + } + } + /** * Cron cleanup job. */ diff --git a/lib/filestorage/stored_file.php b/lib/filestorage/stored_file.php index bc19c0ba78b..fabf9480c71 100644 --- a/lib/filestorage/stored_file.php +++ b/lib/filestorage/stored_file.php @@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die(); require_once("$CFG->libdir/filestorage/stored_file.php"); +require_once("$CFG->dirroot/repository/lib.php"); /** * Class representing local files stored in a sha1 file pool. @@ -46,6 +47,8 @@ class stored_file { private $file_record; /** @var string location of content files */ private $filedir; + /** @var repository repository plugin instance */ + public $repository; /** * Constructor, this constructor should be called ONLY from the file_storage class! @@ -55,9 +58,142 @@ class stored_file { * @param string $filedir location of file directory with sh1 named content files */ public function __construct(file_storage $fs, stdClass $file_record, $filedir) { + global $DB, $CFG; $this->fs = $fs; $this->file_record = clone($file_record); // prevent modifications $this->filedir = $filedir; // keep secret, do not expose! + + if (!empty($file_record->repositoryid)) { + $this->repository = repository::get_repository_by_id($file_record->repositoryid, SYSCONTEXTID); + if ($this->repository->supported_returntypes() & FILE_REFERENCE != FILE_REFERENCE) { + // Repository cannot do file reference. + throw new moodle_exception('error'); + } + } else { + $this->repository = null; + } + } + + /** + * Whether or not this is a external resource + * + * @return bool + */ + public function is_external_file() { + return !empty($this->repository); + } + + /** + * Update some file record fields + * NOTE: Must remain protected + * + * @param stdClass $dataobject + */ + protected function update($dataobject) { + global $DB; + $keys = array_keys((array)$this->file_record); + foreach ($dataobject as $field => $value) { + if (in_array($field, $keys)) { + if ($field == 'contextid' and (!is_number($value) or $value < 1)) { + throw new file_exception('storedfileproblem', 'Invalid contextid'); + } + + if ($field == 'component') { + $value = clean_param($value, PARAM_COMPONENT); + if (empty($value)) { + throw new file_exception('storedfileproblem', 'Invalid component'); + } + } + + if ($field == 'filearea') { + $value = clean_param($value, PARAM_AREA); + if (empty($value)) { + throw new file_exception('storedfileproblem', 'Invalid filearea'); + } + } + + if ($field == 'itemid' and (!is_number($value) or $value < 0)) { + throw new file_exception('storedfileproblem', 'Invalid itemid'); + } + + + if ($field == 'filepath') { + $value = clean_param($value, PARAM_PATH); + if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) { + // path must start and end with '/' + throw new file_exception('storedfileproblem', 'Invalid file path'); + } + } + + if ($field == 'filename') { + $value = clean_param($value, PARAM_FILE); + if ($value === '') { + throw new file_exception('storedfileproblem', 'Invalid file name'); + } + } + + if ($field === 'timecreated' or $field === 'timemodified') { + if (!is_number($value)) { + throw new file_exception('storedfileproblem', 'Invalid timestamp'); + } + if ($value < 0) { + $value = 0; + } + } + + if ($field == 'referencefileid' or $field == 'referencelastsync' or $field == 'referencelifetime') { + $value = clean_param($value, PARAM_INT); + } + + // adding the field + $this->file_record->$field = $value; + } else { + throw new coding_exception("Invalid field name, $field doesn't exist in file record"); + } + } + $DB->update_record('files', $this->file_record); + } + + /** + * Rename filename + * + * @param string $filepath file path + * @param string $filename file name + */ + public function rename($filepath, $filename) { + $filerecord = new stdClass; + $filerecord->filepath = $filepath; + $filerecord->filename = $filename; + // populate the pathname hash + $filerecord->pathnamehash = $this->fs->get_pathname_hash($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath, $filename); + $this->update($filerecord); + } + + /** + * Replace the content by providing another stored_file instance + * + * @param stored_file $storedfile + */ + public function replace_content_with(stored_file $storedfile) { + $contenthash = $storedfile->get_contenthash(); + $this->set_contenthash($contenthash); + } + + /** + * Delete file reference + * + */ + public function delete_reference() { + global $DB; + // Remove repository info. + $this->repository = null; + unset($this->file_record->repositoryid); + unset($this->file_record->reference); + unset($this->file_record->referencelastsync); + unset($this->file_record->referencelifetime); + + // Remove reference info from DB. + $DB->delete_records('files_reference', array('id'=>$this->file_record->referencefileid)); } /** @@ -83,7 +219,15 @@ class stored_file { */ public function delete() { global $DB; + // If other files referring to this file, we need convert them + if ($files = $this->fs->get_references_by_storedfile($this)) { + foreach ($files as $file) { + $this->fs->import_external_file($file); + } + } + // Now delete file records in DB $DB->delete_records('files', array('id'=>$this->file_record->id)); + $DB->delete_records('files_reference', array('id'=>$this->file_record->referencefileid)); // moves pool file to trash if content not needed any more $this->fs->deleted_file_cleanup($this->file_record->contenthash); return true; // BC only @@ -97,9 +241,12 @@ class stored_file { * @return string full path to pool file with file content **/ protected function get_content_file_location() { + $this->sync_external_file(); + // Detect is local file or not. $contenthash = $this->file_record->contenthash; $l1 = $contenthash[0].$contenthash[1]; $l2 = $contenthash[2].$contenthash[3]; + $path = "$this->filedir/$l1/$l2/$contenthash"; return "$this->filedir/$l1/$l2/$contenthash"; } @@ -128,7 +275,7 @@ class stored_file { throw new file_exception('storedfilecannotread', '', $path); } } - return fopen($path, 'rb'); //binary reading only!! + return fopen($path, 'rb'); // Binary reading only!! } /** @@ -300,7 +447,33 @@ class stored_file { } /** - * Returns context id of the file- + * Sync external files + * + * @return bool true if file content changed, false if not + */ + public function sync_external_file() { + global $CFG, $DB; + if (empty($this->file_record->referencefileid)) { + return false; + } + if (empty($this->file_record->referencelastsync) or ($this->file_record->referencelastsync + $this->file_record->referencelifetime < time())) { + require_once($CFG->dirroot.'/repository/lib.php'); + if (repository::sync_external_file($this)) { + $prevcontent = $this->file_record->contenthash; + $sql = "SELECT f.*, r.repositoryid, r.reference + FROM {files} f + LEFT JOIN {files_reference} r + ON f.referencefileid = r.id + WHERE f.id = ?"; + $this->file_record = $DB->get_record_sql($sql, array($this->file_record->id), MUST_EXIST); + return ($prevcontent !== $this->file_record->contenthash); + } + } + return false; + } + + /** + * Returns context id of the file * * @return int context id */ @@ -370,6 +543,7 @@ class stored_file { * @return int bytes */ public function get_filesize() { + $this->sync_external_file(); return $this->file_record->filesize; } @@ -397,9 +571,21 @@ class stored_file { * @return int */ public function get_timemodified() { + $this->sync_external_file(); return $this->file_record->timemodified; } + /** + * set timemodified + * + * @param int $timemodified + */ + public function set_timemodified($timemodified) { + $filerecord = new stdClass; + $filerecord->timemodified = $timemodified; + $this->update($filerecord); + } + /** * Returns file status flag. * @@ -424,9 +610,26 @@ class stored_file { * @return string */ public function get_contenthash() { + $this->sync_external_file(); return $this->file_record->contenthash; } + /** + * Set contenthash + * + * @param string $contenthash + */ + protected function set_contenthash($contenthash) { + // make sure the content exists in moodle file pool + if ($this->fs->content_exists($contenthash)) { + $filerecord = new stdClass; + $filerecord->contenthash = $contenthash; + $this->update($filerecord); + } else { + throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash); + } + } + /** * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext"). * @@ -445,6 +648,17 @@ class stored_file { return $this->file_record->license; } + /** + * Set license + * + * @param string $license license + */ + public function set_license($license) { + $filerecord = new stdClass; + $filerecord->license = $license; + $this->update($filerecord); + } + /** * Returns the author name of the file. * @@ -454,6 +668,17 @@ class stored_file { return $this->file_record->author; } + /** + * Set author + * + * @param string $author + */ + public function set_author($author) { + $filerecord = new stdClass; + $filerecord->author = $author; + $this->update($filerecord); + } + /** * Returns the source of the file, usually it is a url. * @@ -463,6 +688,18 @@ class stored_file { return $this->file_record->source; } + /** + * Set license + * + * @param string $license license + */ + public function set_source($source) { + $filerecord = new stdClass; + $filerecord->source = $source; + $this->update($filerecord); + } + + /** * Returns the sort order of file * @@ -471,4 +708,82 @@ class stored_file { public function get_sortorder() { return $this->file_record->sortorder; } + + /** + * Set file sort order + * + * @param int $sortorder + * @return int + */ + public function set_sortorder($sortorder) { + $filerecord = new stdClass; + $filerecord->sortorder = $sortorder; + $this->update($filerecord); + } + + /** + * Returns repository id + * + * @return int|null + */ + public function get_repository_id() { + if (!empty($this->repository)) { + return $this->repository->id; + } else { + return null; + } + } + + /** + * get reference file id + * @return int + */ + public function get_referencefileid() { + return $this->file_record->referencefileid; + } + + /** + * Get reference last sync time + * @return int + */ + public function get_referencelastsync() { + return $this->file_record->referencelastsync; + } + + /** + * Get reference last sync time + * @return int + */ + public function get_referencelifetime() { + return $this->file_record->referencelifetime; + } + /** + * Returns file reference + * + * @return string + */ + public function get_reference() { + return $this->file_record->reference; + } + + /** + * Get human readable file reference information + * + * @return string + */ + public function get_reference_details() { + return $this->repository->get_reference_details($this->get_reference()); + } + + /** + * Send file references + * + * @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 array $options additional options affecting the file serving + */ + public function send_file($lifetime, $filter, $forcedownload, $options) { + $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options); + } } diff --git a/lib/filestorage/tests/file_storage_test.php b/lib/filestorage/tests/file_storage_test.php index ba31f8043a9..d52533278c4 100644 --- a/lib/filestorage/tests/file_storage_test.php +++ b/lib/filestorage/tests/file_storage_test.php @@ -77,4 +77,128 @@ class filestoragelib_testcase extends advanced_testcase { $this->assertInstanceOf('stored_file', $previewtinyicon); $this->assertEquals('6b9864ae1536a8eeef54e097319175a8be12f07c', $previewtinyicon->get_filename()); } + + /** + * Make sure renaming is working + * + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + */ + public function test_file_renaming() { + global $CFG; + + $this->resetAfterTest(true); + $fs = get_file_storage(); + $syscontext = context_system::instance(); + $component = 'core'; + $filearea = 'unittest'; + $itemid = 0; + $filepath = '/'; + $filename = 'test.txt'; + + $filerecord = array( + 'contextid' => $syscontext->id, + 'component' => $component, + 'filearea' => $filearea, + 'itemid' => $itemid, + 'filepath' => $filepath, + 'filename' => $filename, + ); + + $originalfile = $fs->create_file_from_string($filerecord, 'Test content'); + $this->assertInstanceOf('stored_file', $originalfile); + $contenthash = $originalfile->get_contenthash(); + $newpath = '/test/'; + $newname = 'newtest.txt'; + $originalfile->rename($newpath, $newname); + + $file = $fs->get_file($syscontext->id, $component, $filearea, $itemid, $newpath, $newname); + $this->assertInstanceOf('stored_file', $file); + $this->assertEquals($contenthash, $file->get_contenthash()); + } + + /** + * Create file from reference tests + * + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + */ + public function test_create_file_from_reference() { + global $CFG, $DB; + + $this->resetAfterTest(true); + // create user + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $usercontext = context_user::instance($user->id); + $syscontext = context_system::instance(); + $USER = $DB->get_record('user', array('id'=>$user->id)); + + $fs = get_file_storage(); + + $repositorypluginname = 'user'; + // override repository permission + $capability = 'repository/' . $repositorypluginname . ':view'; + $allroles = $DB->get_records_menu('role', array(), 'id', 'archetype, id'); + assign_capability($capability, CAP_ALLOW, $allroles['guest'], $syscontext->id, true); + + + $args = array(); + $args['type'] = $repositorypluginname; + $repos = repository::get_instances($args); + $userrepository = reset($repos); + $this->assertInstanceOf('repository', $userrepository); + + $component = 'user'; + $filearea = 'private'; + $itemid = 0; + $filepath = '/'; + $filename = 'userfile.txt'; + + $filerecord = array( + 'contextid' => $usercontext->id, + 'component' => $component, + 'filearea' => $filearea, + 'itemid' => $itemid, + 'filepath' => $filepath, + 'filename' => $filename, + ); + + $content = 'Test content'; + $originalfile = $fs->create_file_from_string($filerecord, $content); + $this->assertInstanceOf('stored_file', $originalfile); + + $newfilerecord = array( + 'contextid' => $syscontext->id, + 'component' => 'core', + 'filearea' => 'phpunit', + 'itemid' => 0, + 'filepath' => $filepath, + 'filename' => $filename, + ); + $ref = $fs->pack_reference($filerecord); + $newstoredfile = $fs->create_file_from_reference($newfilerecord, $userrepository->id, $ref); + $this->assertInstanceOf('stored_file', $newstoredfile); + $this->assertEquals($userrepository->id, $newstoredfile->repository->id); + $this->assertEquals($originalfile->get_contenthash(), $newstoredfile->get_contenthash()); + $this->assertEquals($originalfile->get_filesize(), $newstoredfile->get_filesize()); + $this->assertRegExp('#' . $filename. '$#', $newstoredfile->get_reference_details()); + + // Test looking for references + $count = $fs->get_references_count_by_storedfile($originalfile); + $this->assertEquals(1, $count); + $files = $fs->get_references_by_storedfile($originalfile); + $file = reset($files); + $this->assertEquals($file, $newstoredfile); + + // Look for references by repository ID + $files = $fs->get_external_files($userrepository->id); + $file = reset($files); + $this->assertEquals($file, $newstoredfile); + + // Try convert reference to local file + $importedfile = $fs->import_external_file($newstoredfile); + $this->assertFalse($importedfile->is_external_file()); + $this->assertInstanceOf('stored_file', $importedfile); + // still readable? + $this->assertEquals($content, $importedfile->get_content()); + } } diff --git a/lib/form/editor.php b/lib/form/editor.php index a993ffd467a..fdbbaf9faad 100644 --- a/lib/form/editor.php +++ b/lib/form/editor.php @@ -303,7 +303,7 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element { $args = new stdClass(); // need these three to filter repositories list $args->accepted_types = array('image'); - $args->return_types = (FILE_INTERNAL | FILE_EXTERNAL); + $args->return_types = (FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE); $args->context = $ctx; $args->env = 'filepicker'; // advimage plugin @@ -407,4 +407,4 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element { return ''; } -} \ No newline at end of file +} diff --git a/lib/form/filemanager.php b/lib/form/filemanager.php index 213ecc675f6..beceb6317e4 100644 --- a/lib/form/filemanager.php +++ b/lib/form/filemanager.php @@ -45,7 +45,10 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element { public $_helpbutton = ''; /** @var array options provided to initalize filemanager */ - protected $_options = array('mainfile'=>'', 'subdirs'=>1, 'maxbytes'=>-1, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL); + // PHP doesn't support 'key' => $value1 | $value2 in class definition + // We cannot do $_options = array('return_types'=> FILE_INTERNAL | FILE_REFERENCE); + // So I have to set null here, and do it in constructor + protected $_options = array('mainfile'=>'', 'subdirs'=>1, 'maxbytes'=>-1, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=> null); /** * Constructor @@ -68,6 +71,9 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element { if (!empty($options['maxbytes'])) { $this->_options['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $options['maxbytes']); } + if (empty($options['return_types'])) { + $this->_options['return_types'] = (FILE_INTERNAL | FILE_REFERENCE); + } $this->_type = 'filemanager'; parent::HTML_QuickForm_element($elementName, $elementLabel, $attributes); } @@ -242,7 +248,7 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element { $options->subdirs = $this->_options['subdirs']; $options->target = $id; $options->accepted_types = $accepted_types; - $options->return_types = FILE_INTERNAL; + $options->return_types = $this->_options['return_types']; $options->context = $PAGE->context; $html = $this->_getTabs(); diff --git a/lib/form/filepicker.php b/lib/form/filepicker.php index 4436cebb016..09071fb3cea 100644 --- a/lib/form/filepicker.php +++ b/lib/form/filepicker.php @@ -45,7 +45,10 @@ class MoodleQuickForm_filepicker extends HTML_QuickForm_input { public $_helpbutton = ''; /** @var array options provided to initalize filemanager */ - protected $_options = array('maxbytes'=>0, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL); + // PHP doesn't support 'key' => $value1 | $value2 in class definition + // We cannot do $_options = array('return_types'=> FILE_INTERNAL | FILE_REFERENCE); + // So I have to set null here, and do it in constructor + protected $_options = array('maxbytes'=>0, 'accepted_types'=>'*', 'return_types'=>null); /** * Constructor @@ -65,6 +68,9 @@ class MoodleQuickForm_filepicker extends HTML_QuickForm_input { $this->_options[$name] = $value; } } + if (!empty($options['return_types'])) { + $this->_options['return_types'] = FILE_INTERNAL | FILE_REFERENCE; + } if (!empty($options['maxbytes'])) { $this->_options['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $options['maxbytes']); } @@ -137,7 +143,7 @@ class MoodleQuickForm_filepicker extends HTML_QuickForm_input { $args = new stdClass(); // need these three to filter repositories list $args->accepted_types = $this->_options['accepted_types']?$this->_options['accepted_types']:'*'; - $args->return_types = FILE_INTERNAL; + $args->return_types = FILE_INTERNAL | FILE_REFERENCE; $args->itemid = $draftitemid; $args->maxbytes = $this->_options['maxbytes']; $args->context = $PAGE->context; diff --git a/lib/outputrequirementslib.php b/lib/outputrequirementslib.php index 961f8eb8a5d..25711c7ae46 100644 --- a/lib/outputrequirementslib.php +++ b/lib/outputrequirementslib.php @@ -457,7 +457,7 @@ class page_requirements_manager { array('nofilesavailable', 'repository'), array('overwrite', 'repository'), array('renameto', 'repository'), array('fileexists', 'repository'), array('fileexistsdialogheader', 'repository'), array('fileexistsdialog_editor', 'repository'), - array('fileexistsdialog_filemanager', 'repository') + array('fileexistsdialog_filemanager', 'repository'), array('uselatestfile', 'repository') )); break; case 'core_comment': @@ -1277,4 +1277,4 @@ function js_reset_all_caches() { set_config('jsrev', empty($CFG->jsrev) ? 1 : $CFG->jsrev+1); fulldelete("$CFG->cachedir/js"); -} \ No newline at end of file +} diff --git a/lib/tests/filelib_test.php b/lib/tests/filelib_test.php index 5ef866a6a5c..667007e0b19 100644 --- a/lib/tests/filelib_test.php +++ b/lib/tests/filelib_test.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->libdir . '/filelib.php'); -class filelib_testcase extends basic_testcase { +class filelib_testcase extends advanced_testcase { public function test_format_postdata_for_curlcall() { //POST params with just simple types @@ -85,4 +85,83 @@ class filelib_testcase extends basic_testcase { $contents = download_file_content($testhtml); $this->assertEquals('47250a973d1b88d9445f94db4ef2c97a', md5($contents)); } + + /** + * Testing prepare draft area + * + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + public function test_prepare_draft_area() { + global $USER, $DB; + + $this->resetAfterTest(true); + + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $usercontext = context_user::instance($user->id); + $USER = $DB->get_record('user', array('id'=>$user->id)); + + + $fs = get_file_storage(); + $syscontext = context_system::instance(); + $component = 'core'; + $filearea = 'unittest'; + $itemid = 0; + $filepath = '/'; + $filename = 'test.txt'; + $sourcefield = 'Copyright stuff'; + + $filerecord = array( + 'contextid' => $syscontext->id, + 'component' => $component, + 'filearea' => $filearea, + 'itemid' => $itemid, + 'filepath' => $filepath, + 'filename' => $filename, + 'source' => $sourcefield, + ); + $ref = $fs->pack_reference($filerecord); + + $originalfile = $fs->create_file_from_string($filerecord, 'Test content'); + + $fileid = $originalfile->get_id(); + $this->assertInstanceOf('stored_file', $originalfile); + + $draftitemid = 0; + file_prepare_draft_area($draftitemid, $syscontext->id, $component, $filearea, $itemid); + + $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid); + $this->assertEquals(2, count($draftfiles)); + + $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filename); + $source = unserialize($draftfile->get_source()); + $this->assertEquals($ref, $source->original); + $this->assertEquals($sourcefield, $source->source); + + + // change some information + $author = 'Dongsheng Cai'; + $draftfile->set_author($author); + $newsourcefield = 'Get from flickr'; + $license = 'GPLv3'; + $draftfile->set_license($license); + // if you want to really just change source field, do this: + $source = unserialize($draftfile->get_source()); + $newsourcefield = 'From flickr'; + $source->source = $newsourcefield; + $draftfile->set_source(serialize($source)); + + // Save changed file + file_save_draft_area_files($draftitemid, $syscontext->id, $component, $filearea, $itemid); + + $file = $fs->get_file($syscontext->id, $component, $filearea, $itemid, $filepath, $filename); + + // Make sure it's the original file id + $this->assertEquals($fileid, $file->get_id()); + $this->assertInstanceOf('stored_file', $file); + $this->assertEquals($author, $file->get_author()); + $this->assertEquals($license, $file->get_license()); + $this->assertEquals($newsourcefield, $file->get_source()); + } } diff --git a/lib/tests/repositorylib_test.php b/lib/tests/repositorylib_test.php deleted file mode 100644 index 55ca73120d0..00000000000 --- a/lib/tests/repositorylib_test.php +++ /dev/null @@ -1,57 +0,0 @@ -. - -/** - * Unit tests for ../repositorylib.php. - * - * @package core_repository - * @category phpunit - * @author nicolasconnault@gmail.com - * @license http://www.gnu.org/copyleft/gpl.html GNU Public License - */ - -defined('MOODLE_INTERNAL') || die(); - -global $CFG; -require_once("$CFG->dirroot/repository/lib.php"); - - -class repositorylib_testcase extends advanced_testcase { - - public function test_initialise_filepicker() { - global $PAGE, $SITE; - - $this->resetAfterTest(true); - - $PAGE->set_url('/'); - $PAGE->set_course($SITE); - - $this->setAdminUser(); - - $args = new stdClass(); - $args->accepted_types = '*'; - $args->return_types = array(FILE_EXTERNAL); - $info = initialise_filepicker($args); - - $this->assertInstanceOf('stdClass', $info); - $this->assertObjectHasAttribute('defaultlicense', $info); - $this->assertObjectHasAttribute('licenses', $info); - $this->assertObjectHasAttribute('author', $info); - $this->assertObjectHasAttribute('externallink', $info); - $this->assertObjectHasAttribute('accepted_types', $info); - $this->assertObjectHasAttribute('return_types', $info); - } -} diff --git a/mod/folder/edit.php b/mod/folder/edit.php index 161745c70ef..918568575ba 100644 --- a/mod/folder/edit.php +++ b/mod/folder/edit.php @@ -48,7 +48,7 @@ $PAGE->set_activity_record($folder); $data = new stdClass(); $data->id = $cm->id; -$options = array('mainfile'=>true, 'subdirs'=>1, 'maxbytes'=>$CFG->maxbytes, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL); +$options = array('mainfile'=>true, 'subdirs'=>1, 'maxbytes'=>$CFG->maxbytes, 'maxfiles'=>-1, 'accepted_types'=>'*'); file_prepare_standard_filemanager($data, 'files', $options, $context, 'mod_folder', 'content', 0); $mform = new mod_folder_edit_form(null, array('data'=>$data, 'options'=>$options)); diff --git a/mod/folder/mod_form.php b/mod/folder/mod_form.php index 3fe54d53488..212cb125120 100644 --- a/mod/folder/mod_form.php +++ b/mod/folder/mod_form.php @@ -48,7 +48,7 @@ class mod_folder_mod_form extends moodleform_mod { //------------------------------------------------------- $mform->addElement('header', 'content', get_string('contentheader', 'folder')); - $mform->addElement('filemanager', 'files', get_string('files'), null, array('subdirs'=>1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL)); + $mform->addElement('filemanager', 'files', get_string('files'), null, array('subdirs'=>1, 'accepted_types'=>'*')); //------------------------------------------------------- $this->standard_coursemodule_elements(); diff --git a/mod/resource/mod_form.php b/mod/resource/mod_form.php index d0f5a28eaa5..01560dd471f 100644 --- a/mod/resource/mod_form.php +++ b/mod/resource/mod_form.php @@ -61,9 +61,9 @@ class mod_resource_mod_form extends moodleform_mod { $mform->addElement('header', 'contentsection', get_string('contentheader', 'resource')); $filemanager_options = array(); - // 3 == FILE_EXTERNAL & FILE_INTERNAL + // 3 == FILE_EXTERNAL | FILE_INTERNAL | FILE_REFERENCE // These two constant names are defined in repository/lib.php - $filemanager_options['return_types'] = 3; + $filemanager_options['return_types'] = 7; $filemanager_options['accepted_types'] = '*'; $filemanager_options['maxbytes'] = 0; $filemanager_options['maxfiles'] = -1; diff --git a/mod/wiki/filesedit.php b/mod/wiki/filesedit.php index 0982095e5ea..e70fe95ff28 100644 --- a/mod/wiki/filesedit.php +++ b/mod/wiki/filesedit.php @@ -78,7 +78,7 @@ $data = new stdClass(); $data->returnurl = $returnurl; $data->subwikiid = $subwiki->id; $maxbytes = get_max_upload_file_size($CFG->maxbytes, $COURSE->maxbytes); -$options = array('subdirs'=>0, 'maxbytes'=>$maxbytes, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL); +$options = array('subdirs'=>0, 'maxbytes'=>$maxbytes, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL | FILE_REFERENCE); file_prepare_standard_filemanager($data, 'files', $options, $context, 'mod_wiki', 'attachments', $subwiki->id); $mform = new mod_wiki_filesedit_form(null, array('data'=>$data, 'options'=>$options)); diff --git a/mod/workshop/submission.php b/mod/workshop/submission.php index eb10b8fd90d..efcff41f930 100644 --- a/mod/workshop/submission.php +++ b/mod/workshop/submission.php @@ -147,7 +147,7 @@ if ($edit) { 'context' => $workshop->context ); - $attachmentopts = array('subdirs' => true, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes); + $attachmentopts = array('subdirs' => true, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes, 'return_types' => FILE_INTERNAL); $submission = file_prepare_standard_editor($submission, 'content', $contentopts, $workshop->context, 'mod_workshop', 'submission_content', $submission->id); $submission = file_prepare_standard_filemanager($submission, 'attachment', $attachmentopts, $workshop->context, diff --git a/repository/alfresco/lib.php b/repository/alfresco/lib.php index 1bda70d5748..8b8e1d8716e 100644 --- a/repository/alfresco/lib.php +++ b/repository/alfresco/lib.php @@ -1,5 +1,4 @@ . +/** + * This plugin is used to access alfresco repository + * + * @since 2.0 + * @package repository_alfresco + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); + /** * repository_alfresco class * This is a class used to browse files from alfresco * * @since 2.0 - * @package repository - * @subpackage alfresco - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_alfresco + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - class repository_alfresco extends repository { private $ticket = null; private $user_session = null; @@ -292,4 +298,3 @@ class repository_alfresco extends repository { return (FILE_INTERNAL | FILE_EXTERNAL); } } - diff --git a/repository/boxnet/lib.php b/repository/boxnet/lib.php index 65a5575822b..3ff8f347e53 100644 --- a/repository/boxnet/lib.php +++ b/repository/boxnet/lib.php @@ -1,5 +1,4 @@ . -require_once($CFG->libdir.'/boxlib.php'); - /** - * repository_boxnet class - * This is a subclass of repository class + * This plugin is used to access box.net repository * * @since 2.0 - * @package repository - * @subpackage boxnet - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_boxnet + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once($CFG->libdir . '/boxlib.php'); + +/** + * repository_boxnet class implements box.net client + * + * @since 2.0 + * @package repository_boxnet + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class repository_boxnet extends repository { @@ -33,8 +38,9 @@ class repository_boxnet extends repository { /** * Constructor + * * @param int $repositoryid - * @param object $context + * @param stdClass $context * @param array $options */ public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { @@ -59,6 +65,7 @@ class repository_boxnet extends repository { /** * check if user logged + * * @return boolean */ public function check_login() { @@ -110,7 +117,6 @@ class repository_boxnet extends repository { /** * Search files from box.net * - * @global object $OUTPUT * @param string $search_text * @return mixed */ @@ -144,6 +150,7 @@ class repository_boxnet extends repository { * Get file listing * * @param string $path + * @param string $page * @return mixed */ public function get_listing($path = '/', $page = ''){ @@ -188,6 +195,7 @@ class repository_boxnet extends repository { /** * Names of the plugin settings + * * @return array */ public static function get_type_option_names() { @@ -204,7 +212,9 @@ class repository_boxnet extends repository { /** * Add Plugin settings input to Moodle form - * @param object $mform + * + * @param moodleform $mform + * @param string $classname */ public static function type_config_form($mform, $classname = 'repository') { global $CFG; @@ -235,12 +245,66 @@ class repository_boxnet extends repository { $mform->addElement('static', 'callbackurl', '', get_string('callbackurltext', 'repository_boxnet', $callbackurl)); } } + /** * Box.net supports file linking and copying - * @return string + * + * @return int */ public function supported_returntypes() { - return FILE_INTERNAL | FILE_EXTERNAL; + return FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE; + } + + /** + * Prepare file reference information + * + * @param string $source + * @return string file referece + */ + public function get_file_reference($source) { + // Box.net returns a url. + return $source; + } + + /** + * Get file from external repository by reference + * {@link repository::get_file_reference()} + * {@link repository::get_file()} + * + * @param stdClass $reference file reference db record + * @return stdClass|null|false + */ + public function get_file_by_reference($reference) { + $fileinfo = new stdClass; + $boxnetfile = $this->get_file($reference->reference); + $fileinfo->filepath = $boxnetfile['path']; + return $fileinfo; + } + + /** + * Return human readable reference information + * {@link stored_file::get_reference()} + * + * @param string $reference + * @return string|null + */ + public function get_reference_details($reference) { + // Indicate it's from box.net repository + secure URL + return $this->get_name() . ': ' . $reference; + } + + /** + * Repository method to serve file + * + * @param stored_file $storedfile + * @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 array $options additional options affecting the file serving + */ + public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) { + $ref = $storedfile->get_reference(); + // Let box.net serve the file. + header('Location: ' . $ref); } } - diff --git a/repository/coursefiles/lib.php b/repository/coursefiles/lib.php index 9a12e85ae11..6cf0622c3dc 100644 --- a/repository/coursefiles/lib.php +++ b/repository/coursefiles/lib.php @@ -1,5 +1,4 @@ . +/** + * This plugin is used to access coursefiles repository + * + * @since 2.0 + * @package repository_coursefiles + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); + /** * repository_coursefiles class is used to browse course files * * @since 2.0 - * @package repository - * @subpackage coursefiles - * @copyright 2010 Dongsheng Cai + * @package repository_coursefiles + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - class repository_coursefiles extends repository { /** * coursefiles plugin doesn't require login, so list all files + * * @return mixed */ public function print_login() { @@ -145,6 +153,7 @@ class repository_coursefiles extends repository { /** * Return is the instance is visible * (is the type visible ? is the context enable ?) + * * @return boolean */ public function is_visible() { @@ -167,8 +176,9 @@ class repository_coursefiles extends repository { } public function supported_returntypes() { - return (FILE_INTERNAL | FILE_EXTERNAL); + return (FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE); } + public static function get_type_option_names() { return array(); } @@ -181,4 +191,105 @@ class repository_coursefiles extends repository { public function has_moodle_files() { return true; } + + /** + * Unpack file info and pack it, mainly for data validation + * + * @param string $source + * @return string file referece + */ + public function get_file_reference($source) { + $params = unserialize(base64_decode($source)); + + if (!is_array($params)) { + throw new repository_exception('invalidparams', 'repository'); + } + + $filename = is_null($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE); + $filepath = is_null($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH);; + $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT); + + $reference = array(); + // hard coded filearea, component and itemid for security + $reference['component'] = 'course'; + $reference['filearea'] = 'legacy'; + $reference['itemid'] = 0; + $reference['contextid'] = $contextid; + $reference['filepath'] = $filepath; + $reference['filename'] = $filename; + + return file_storage::pack_reference($reference); + } + + /** + * Get file from external repository by reference + * {@link repository::get_file_reference()} + * {@link repository::get_file()} + * + * @param stdClass $reference file reference db record + * @return stdClass|null|false + */ + public function get_file_by_reference($reference) { + $ref = $reference->reference; + $params = file_storage::unpack_reference($ref); + if (!is_array($params)) { + throw new repository_exception('invalidparams', 'repository'); + } + $filename = is_null($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE); + $filepath = is_null($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH);; + $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT); + + // hard coded filearea, component and itemid for security + $storedfile = $fs->get_file($contextid, 'course', 'legacy', 0, $filepath, $filename); + + $fileinfo = new stdClass; + $fileinfo->contenthash = $storedfile->get_contenthash(); + $fileinfo->filesize = $storedfile->get_filesize(); + return $fileinfo; + } + + /** + * Return human readable reference information + * {@link stored_file::get_reference()} + * + * @param string $reference + * @return string|null + */ + public function get_reference_details($reference) { + $params = file_storage::unpack_reference($reference); + list($context, $course, $cm) = get_context_info_array($params['contextid']); + $coursename = ''; + if (!empty($course)) { + $coursename = '"' . format_string($course->shortname, true, array('context' => get_course_context($context))) . '" ' . get_string('courselegacyfiles'); + } else { + $coursename = get_string('courselegacyfiles'); + } + // Indicate this is from user private area + return $coursename . ': ' . $params['filepath'] . $params['filename']; + } + + /** + * Repository method to serve file + * + * @param stored_file $storedfile + * @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 array $options additional options affecting the file serving + */ + public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) { + $fs = get_file_storage(); + + $reference = $storedfile->get_reference(); + $params = file_storage::unpack_reference($reference); + + $filename = is_null($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE); + $filepath = is_null($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH);; + $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT); + + // hard coded file area and component for security + $srcfile = $fs->get_file($contextid, 'course', 'legacy', 0, $filepath, $filename); + + send_stored_file($srcfile, $lifetime, $filter, $forcedownload, $options); + } } diff --git a/repository/dropbox/lib.php b/repository/dropbox/lib.php index 2c1003c5c2c..659b91a8f11 100644 --- a/repository/dropbox/lib.php +++ b/repository/dropbox/lib.php @@ -1,5 +1,4 @@ . /** - * repository_dropbox class * This plugin is used to access user's dropbox files * * @since 2.0 - * @package repository - * @subpackage dropbox - * @copyright 2010 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_dropbox + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - +require_once($CFG->dirroot . '/repository/lib.php'); require_once(dirname(__FILE__).'/locallib.php'); class repository_dropbox extends repository { + /** @var dropbox the instance of dropbox client */ private $dropbox; + /** @var array files */ public $files; + /** @var bool flag of login status */ public $logged=false; + /** @var int cached file ttl */ + private $cachedfilettl = null; + /** * Constructor of dropbox plugin + * * @param int $repositoryid - * @param object $context + * @param stdClass $context * @param array $options */ public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { - global $SESSION, $CFG; + global $CFG; $options['page'] = optional_param('p', 1, PARAM_INT); parent::__construct($repositoryid, $context, $options); @@ -50,14 +53,25 @@ class repository_dropbox extends repository { $this->dropbox_key = $this->get_option('dropbox_key'); $this->dropbox_secret = $this->get_option('dropbox_secret'); - $this->access_key = get_user_preferences($this->setting.'_access_key', ''); - $this->access_secret = get_user_preferences($this->setting.'_access_secret', ''); + // one day + $this->cachedfilettl = 60 * 60 * 24; + + if (isset($options['access_key'])) { + $this->access_key = $options['access_key']; + } else { + $this->access_key = get_user_preferences($this->setting.'_access_key', ''); + } + if (isset($options['access_secret'])) { + $this->access_secret = $options['access_secret']; + } else { + $this->access_secret = get_user_preferences($this->setting.'_access_secret', ''); + } if (!empty($this->access_key) && !empty($this->access_secret)) { $this->logged = true; } - $this->callback = new moodle_url($CFG->wwwroot.'/repository/repository_callback.php', array( + $callbackurl = new moodle_url($CFG->wwwroot.'/repository/repository_callback.php', array( 'callback'=>'yes', 'repo_id'=>$repositoryid )); @@ -65,15 +79,35 @@ class repository_dropbox extends repository { $args = array( 'oauth_consumer_key'=>$this->dropbox_key, 'oauth_consumer_secret'=>$this->dropbox_secret, - 'oauth_callback' => $this->callback->out(false), + 'oauth_callback' => $callbackurl->out(false), 'api_root' => 'https://www.dropbox.com/1/oauth', ); $this->dropbox = new dropbox($args); } + /** + * Set access key + * + * @param string $access_key + */ + public function set_access_key($access_key) { + $this->access_key = $access_key; + } + + /** + * Set access secret + * + * @param string $access_secret + */ + public function set_access_secret($access_secret) { + $this->access_secret = $access_secret; + } + + /** * Check if moodle has got access token and secret + * * @return bool */ public function check_login() { @@ -82,6 +116,7 @@ class repository_dropbox extends repository { /** * Generate dropbox login url + * * @return array */ public function print_login() { @@ -102,6 +137,7 @@ class repository_dropbox extends repository { /** * Request access token + * * @return array */ public function callback() { @@ -114,6 +150,7 @@ class repository_dropbox extends repository { /** * Get dropbox files + * * @param string $path * @param int $page * @return array @@ -256,6 +293,7 @@ class repository_dropbox extends repository { } /** * Add Plugin settings input to Moodle form + * * @param object $mform */ public static function type_config_form($mform, $classname = 'repository') { @@ -284,6 +322,7 @@ class repository_dropbox extends repository { /** * Option names of dropbox plugin + * * @return array */ public static function get_type_option_names() { @@ -292,6 +331,7 @@ class repository_dropbox extends repository { /** * Dropbox plugin supports all kinds of files + * * @return array */ public function supported_filetypes() { @@ -300,9 +340,128 @@ class repository_dropbox extends repository { /** * User cannot use the external link to dropbox + * * @return int */ public function supported_returntypes() { - return FILE_INTERNAL; + return FILE_INTERNAL | FILE_REFERENCE; + } + + /** + * Prepare file reference information + * + * @param string $source + * @return string file referece + */ + public function get_file_reference($source) { + $reference = new stdClass; + $reference->path = $source; + $reference->access_key = get_user_preferences($this->setting.'_access_key', ''); + $reference->access_secret = get_user_preferences($this->setting.'_access_secret', ''); + return serialize($reference); + } + + /** + * Get file from external repository by reference + * {@link repository::get_file_reference()} + * {@link repository::get_file()} + * + * @param stdClass $reference file reference db record + * @return stdClass|null|false + */ + public function get_file_by_reference($reference) { + $reference = unserialize($reference->reference); + $cachedfilepath = cache_file::get($reference, array('ttl' => $this->cachedfilettl)); + if ($cachedfilepath === false) { + // Cache the file. + $this->set_access_key($reference->access_key); + $this->set_access_secret($reference->access_secret); + $path = $this->get_file($reference->path); + $cachedfilepath = cache_file::create_from_file($reference, $path['path']); + } + + $fileinfo = new stdClass; + $fileinfo->filepath = $cachedfilepath; + + return $fileinfo; + } + + /** + * Get file from external repository by reference + * {@link repository::get_file_reference()} + * {@link repository::get_file()} + * + * @param string $reference this reference is generated by + * repository::get_file_reference() + * @param stored_file $storedfile created file reference + */ + public function cache_file_by_reference($reference, $storedfile) { + $reference = unserialize($reference); + $path = $this->get_file($reference->path); + cache_file::create_from_file($reference, $path['path']); + } + + /** + * Return human readable reference information + * {@link stored_file::get_reference()} + * + * @param string $reference + * @return string|null + */ + public function get_reference_details($reference) { + $ref = unserialize($reference); + // Indicate this is from dropbox with path + return $this->get_name() . ': ' . $ref->path; + } + + /** + * Repository method to serve file + * + * @param stored_file $storedfile + * @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 array $options additional options affecting the file serving + */ + public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) { + $reference = unserialize($storedfile->get_reference()); + + $cachedfilepath = cache_file::get($reference, array('ttl' => $this->cachedfilettl)); + if ($cachedfilepath === false) { + // Cache the file. + $this->set_access_key($reference->access_key); + $this->set_access_secret($reference->access_secret); + $path = $this->get_file($reference->path); + $cachedfilepath = cache_file::create_from_file($reference, $path['path']); + } + + send_file($cachedfilepath, $storedfile->get_filename(), 'default' , $filter, false, $forcedownload); + } + + public function cron() { + $fs = get_file_storage(); + $files = $fs->get_external_files($this->id); + foreach ($files as $file) { + $reference = unserialize($file->get_reference()); + + $cachedfile = cache_file::get($reference); + if ($cachedfile === false) { + // Re-fetch resource. + $this->set_access_key($reference->access_key); + $this->set_access_secret($reference->access_secret); + $path = $this->get_file($reference->path); + cache_file::create_from_file($reference, $path['path']); + } + } + } +} + +/** + * Dropbox plugin cron task + */ +function repository_dropbox_cron() { + $instances = repository::get_instances(array('type'=>'dropbox')); + foreach ($instances as $instance) { + $instance->cron(); } } diff --git a/repository/dropbox/locallib.php b/repository/dropbox/locallib.php index a86899c41a3..2fb322ae247 100644 --- a/repository/dropbox/locallib.php +++ b/repository/dropbox/locallib.php @@ -71,6 +71,26 @@ class dropbox extends oauth_helper { return array('path'=>$saveas, 'url'=>$url); } + /** + * Get file url + * + * @param string $filepath file path + * @return string file url + */ + public function get_file_url($filepath) { + $info = pathinfo($filepath); + $dirname = $info['dirname']; + $basename = $info['basename']; + $filepath = $dirname . rawurlencode($basename); + if ($dirname != '/') { + $filepath = $dirname . '/' . $basename; + $filepath = str_replace("%2F", "/", rawurlencode($filepath)); + } + + $url = $this->dropbox_content_api.'/files/'.$this->mode.$filepath; + return $url; + } + public function set_mode($mode) { $this->mode = $mode; } diff --git a/repository/filepicker.js b/repository/filepicker.js index 540fd6383ef..3a82f7b55ee 100644 --- a/repository/filepicker.js +++ b/repository/filepicker.js @@ -46,6 +46,19 @@ M.core_filepicker = M.core_filepicker || {}; M.core_filepicker.instances = M.core_filepicker.instances || {}; M.core_filepicker.active_filepicker = null; +/** + * Set selected file info + * + * @parma object file info + */ +M.core_filepicker.select_file = function(file) { + var fileinfo = {}; + fileinfo['title'] = file.title; + fileinfo['source'] = file.reference; + fileinfo['thumbnail'] = file.thumbnail; + M.core_filepicker.active_filepicker.select_file(fileinfo); +} + /** * Init and show file picker */ @@ -375,13 +388,18 @@ M.core_filepicker.init = function(Y, options) { // it is not working well with search result this.view_as_icons(); } else { - this.viewbar.set('disabled', false); - if (this.viewmode == 1) { - this.view_as_icons(); - } else if (this.viewmode == 2) { - this.view_as_list(p); + if (this.objecttag) { + // handle cancle action for repo using iframe + this.create_object_container(this.objecttag); } else { - this.view_as_icons(); + this.viewbar.set('disabled', false); + if (this.viewmode == 1) { + this.view_as_icons(); + } else if (this.viewmode == 2) { + this.view_as_list(p); + } else { + this.view_as_icons(); + } } } }, @@ -594,6 +612,7 @@ M.core_filepicker.init = function(Y, options) { path.setStyle('display', 'none'); } var panel = Y.one('#panel-'+client_id); + panel.set('innerHTML', ''); var form_id = 'fp-rename-form-'+client_id; var html = '
'; html += '

'; @@ -603,16 +622,28 @@ M.core_filepicker.init = function(Y, options) { var le_checked = ''; var le_style = ''; - if (this.options.repositories[this.active_repo.id].return_types == 1) { - // support external links only - le_checked = 'checked'; - le_style = ' style="display:none;"'; - } else if(this.options.repositories[this.active_repo.id].return_types == 2) { - // support internal files only - le_style = ' style="display:none;"'; - } - if ((this.options.externallink && this.options.env == 'editor' && this.options.return_types != 1)) { - html += '
'; + var returntypes = this.options.repositories[this.active_repo.id].return_types; + + if (returntypes == 1) { + // support link only + html += ''; + } else if (returntypes == 4) { + html += ''; + } else { + if (((returntypes & 1) == 1) && ((this.options.return_types & 1) == 1)) { + // support external links + html += ''; + } + // form element support file reference and repository support it too + if (((returntypes & 4) == 4) && ((this.options.return_types & 4) == 4)) { + // file reference + html += ''; + } + + if (((returntypes & 4) != 4) && ((returntypes & 1) != 1)) { + html += ''; + html += ''; + } } if (!args.hasauthor) { @@ -673,7 +704,7 @@ M.core_filepicker.init = function(Y, options) { // in editor, images are stored in '/' only params.savepath = '/'; // when image or media button is clicked - if ( this.options.return_types != 1 ) { + if (this.options.return_types != 1) { var linkexternal = Y.one('#linkexternal-'+client_id); if (linkexternal && linkexternal.get('checked')) { params['linkexternal'] = 'yes'; @@ -688,6 +719,15 @@ M.core_filepicker.init = function(Y, options) { params['linkexternal'] = 'yes'; } + if (this.options.return_types != 4) { + var usefilereference = Y.one('#filereference-'+client_id); + if (usefilereference && usefilereference.get('checked')) { + params['usefilereference'] = 'yes'; + } + } else { + params['usefilereference'] = 'yes'; + } + this.wait('download', title); this.request({ action:'download', @@ -846,6 +886,7 @@ M.core_filepicker.init = function(Y, options) { parse_repository_options: function(data) { this.filelist = data.list?data.list:null; this.filepath = data.path?data.path:null; + this.objecttag = data.object?data.object:null; this.active_repo = {}; this.active_repo.issearchresult = Boolean(data.issearchresult); this.active_repo.dynload = data.dynload?data.dynload:false; @@ -1132,7 +1173,11 @@ M.core_filepicker.init = function(Y, options) { scope.parse_repository_options(obj); scope.create_upload_form(obj); - } else if (obj.iframe) { + } else if (obj.object) { + M.core_filepicker.active_filepicker = scope; + scope.viewbar.set('disabled', true); + scope.parse_repository_options(obj); + scope.create_object_container(obj.object); } else if (obj.list) { obj.issearchresult = false; @@ -1143,6 +1188,16 @@ M.core_filepicker.init = function(Y, options) { } }, true); }, + create_object_container: function(data) { + var client_id = this.options.client_id; + Y.one('#panel-'+client_id).set('innerHTML', ''); + var types = this.options.accepted_types; + var panel = Y.one('#panel-'+client_id); + var str = ''; + str += ''; + var container = Y.Node.create(str); + Y.one('#panel-'+client_id).appendChild(container); + }, create_upload_form: function(data) { var client_id = this.options.client_id; Y.one('#panel-'+client_id).set('innerHTML', ''); diff --git a/repository/filesystem/lib.php b/repository/filesystem/lib.php index 6133434411b..955b2785c0e 100644 --- a/repository/filesystem/lib.php +++ b/repository/filesystem/lib.php @@ -1,5 +1,4 @@ . +/** + * This plugin is used to access files on server file system + * + * @since 2.0 + * @package repository_filesystem + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once($CFG->libdir . '/filelib.php'); + /** * repository_filesystem class + * * Create a repository from your local filesystem * *NOTE* for security issue, we use a fixed repository path * which is %moodledata%/repository * - * @since 2.0 * @package repository - * @subpackage filesystem - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class repository_filesystem extends repository { + + /** + * Constructor + * + * @param int $repositoryid repository ID + * @param int $context context ID + * @param array $options + */ public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { global $CFG; parent::__construct($repositoryid, $context, $options); @@ -114,6 +130,7 @@ class repository_filesystem extends repository { public function global_search() { return false; } + /** * Return file path * @return array @@ -176,10 +193,6 @@ class repository_filesystem extends repository { } } - public function supported_returntypes() { - return FILE_INTERNAL; - } - public static function create($type, $userid, $context, $params, $readonly=0) { global $PAGE; if (has_capability('moodle/site:config', get_system_context())) { @@ -195,4 +208,52 @@ class repository_filesystem extends repository { } return $errors; } + + /** + * User cannot use the external link to dropbox + * + * @return int + */ + public function supported_returntypes() { + return FILE_INTERNAL | FILE_REFERENCE; + } + + /** + * Get file from external repository by reference + * {@link repository::get_file_reference()} + * {@link repository::get_file()} + * + * @param stdClass $reference file reference db record + * @return stdClass|null|false + */ + public function get_file_by_reference($reference) { + $ref = $reference->reference; + if ($ref{0} == '/') { + $filepath = $this->root_path.substr($ref, 1, strlen($ref)-1); + } else { + $filepath = $this->root_path.$ref; + } + $fileinfo = new stdClass; + $fileinfo->filepath = $filepath; + return $fileinfo; + } + + /** + * Repository method to serve file + * + * @param stored_file $storedfile + * @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 array $options additional options affecting the file serving + */ + public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) { + $reference = $storedfile->get_reference(); + if ($reference{0} == '/') { + $file = $this->root_path.substr($reference, 1, strlen($reference)-1); + } else { + $file = $this->root_path.$reference; + } + send_file($file, $storedfile->get_filename(), 'default' , $filter, false, $forcedownload); + } } diff --git a/repository/flickr/lib.php b/repository/flickr/lib.php index 7d5b6fdcd5c..b54ad931f27 100644 --- a/repository/flickr/lib.php +++ b/repository/flickr/lib.php @@ -1,5 +1,4 @@ . /** - * repository_flickr class - * This plugin is used to access user's private flickr repository + * This plugin is used to access flickr pictures * * @since 2.0 - * @package repository - * @subpackage flickr - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_flickr + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - +require_once($CFG->dirroot . '/repository/lib.php'); require_once($CFG->libdir.'/flickrlib.php'); /** + * This plugin is used to access user's private flickr repository * + * @since 2.0 + * @package repository_flickr + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class repository_flickr extends repository { private $flickr; diff --git a/repository/flickr_public/lib.php b/repository/flickr_public/lib.php index 2c91090247c..3b34b980b94 100644 --- a/repository/flickr_public/lib.php +++ b/repository/flickr_public/lib.php @@ -14,6 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * This plugin is used to access flickr pictures + * + * @since 2.0 + * @package repository_flickr_public + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once($CFG->libdir.'/flickrlib.php'); +require_once(dirname(__FILE__) . '/image.php'); + /** * repository_flickr_public class * This one is used to create public repository @@ -21,16 +33,10 @@ * flickr photos from this plugin * * @since 2.0 - * @package repository - * @subpackage flickr_public - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_flickr_public + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -require_once($CFG->libdir.'/flickrlib.php'); -require_once(dirname(__FILE__) . '/image.php'); - class repository_flickr_public extends repository { private $flickr; public $photos; diff --git a/repository/googledocs/lib.php b/repository/googledocs/lib.php index acb991dd003..833eee953b5 100644 --- a/repository/googledocs/lib.php +++ b/repository/googledocs/lib.php @@ -1,5 +1,4 @@ . +/** + * This plugin is used to access google docs + * + * @since 2.0 + * @package repository_googledocs + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once($CFG->libdir.'/googleapi.php'); + /** * Google Docs Plugin * * @since 2.0 - * @package repository - * @subpackage googledocs + * @package repository_googledocs * @copyright 2009 Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -require_once($CFG->libdir.'/googleapi.php'); - class repository_googledocs extends repository { private $subauthtoken = ''; diff --git a/repository/lib.php b/repository/lib.php index 7ddb8989f19..274e7c1a6cd 100644 --- a/repository/lib.php +++ b/repository/lib.php @@ -1,5 +1,4 @@ . - /** * This file contains classes used to manage the repository plugins in Moodle * and was introduced as part of the changes occuring in Moodle 2.0 * * @since 2.0 - * @package core - * @subpackage repository - * @copyright 2009 Dongsheng Cai - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package repository + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->libdir . '/filelib.php'); require_once($CFG->libdir . '/formslib.php'); -define('FILE_EXTERNAL', 1); -define('FILE_INTERNAL', 2); +define('FILE_EXTERNAL', 1); +define('FILE_INTERNAL', 2); +define('FILE_REFERENCE', 4); define('RENAME_SUFFIX', '_2'); /** @@ -51,8 +49,7 @@ define('RENAME_SUFFIX', '_2'); * - When you create a type for a plugin that can't have multiple instances, a * instance is automatically created. * - * @package moodlecore - * @subpackage repository + * @package repository * @copyright 2009 Jerome Mouneyrac * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -92,12 +89,13 @@ class repository_type { */ private $_sortorder; - /** + /** * Return if the instance is visible in a context - * TODO: check if the context visibility has been overwritten by the plugin creator + * + * @todo check if the context visibility has been overwritten by the plugin creator * (need to create special functions to be overvwritten in repository class) - * @param objet $context - context - * @return boolean + * @param stdClass $context context + * @return bool */ public function get_contextvisibility($context) { global $USER; @@ -118,11 +116,11 @@ class repository_type { /** * repository_type constructor - * @global object $CFG - * @param integer $typename + * + * @param int $typename * @param array $typeoptions - * @param boolean $visible - * @param integer $sortorder (don't really need set, it will be during create() call) + * @param bool $visible + * @param int $sortorder (don't really need set, it will be during create() call) */ public function __construct($typename = '', $typeoptions = array(), $visible = true, $sortorder = 0) { global $CFG; @@ -163,7 +161,8 @@ class repository_type { /** * Get the type name (no whitespace) * For a human readable name, use get_readablename() - * @return String the type name + * + * @return string the type name */ public function get_typename() { return $this->_typename; @@ -171,6 +170,7 @@ class repository_type { /** * Return a human readable and user-friendly type name + * * @return string user-friendly type name */ public function get_readablename() { @@ -179,6 +179,7 @@ class repository_type { /** * Return general options + * * @return array the general options */ public function get_options() { @@ -187,7 +188,8 @@ class repository_type { /** * Return visibility - * @return boolean + * + * @return bool */ public function get_visible() { return $this->_visible; @@ -195,7 +197,8 @@ class repository_type { /** * Return order / position of display in the file picker - * @return integer + * + * @return int */ public function get_sortorder() { return $this->_sortorder; @@ -203,10 +206,8 @@ class repository_type { /** * Create a repository type (the type name must not already exist) - * @param boolean throw exception? + * @param bool $silent throw exception? * @return mixed return int if create successfully, return false if - * any errors - * @global object $DB */ public function create($silent = false) { global $DB; @@ -279,8 +280,9 @@ class repository_type { /** * Update plugin options into the config_plugin table + * * @param array $options - * @return boolean + * @return bool */ public function update_options($options = null) { global $DB; @@ -314,9 +316,9 @@ class repository_type { * or with the visible value of this object * This function is private. * For public access, have a look to switch_and_update_visibility() - * @global object $DB - * @param boolean $visible - * @return boolean + * + * @param bool $visible + * @return bool */ private function update_visible($visible = null) { global $DB; @@ -336,9 +338,9 @@ class repository_type { * or with the sortorder value of this object * This function is private. * For public access, have a look to move_order() - * @global object $DB - * @param integer $sortorder - * @return boolean + * + * @param int $sortorder + * @return bool */ private function update_sortorder($sortorder = null) { global $DB; @@ -362,7 +364,7 @@ class repository_type { * 1. retrieve all types in an array. This array is sorted by sortorder, * and the array keys start from 0 to X (incremented by 1) * 2. switch sortorder values of this type and its adjacent type - * @global object $DB + * * @param string $move "up" or "down" */ public function move_order($move) { @@ -370,7 +372,7 @@ class repository_type { $types = repository::get_types(); // retrieve all types - /// retrieve this type into the returned array + // retrieve this type into the returned array $i = 0; while (!isset($indice) && $iget_typename() == $this->_typename) { @@ -379,7 +381,7 @@ class repository_type { $i++; } - /// retrieve adjacent indice + // retrieve adjacent indice switch ($move) { case "up": $adjacentindice = $indice - 1; @@ -403,9 +405,10 @@ class repository_type { /** * 1. Change visibility to the value chosen - * * 2. Update the type - * @return boolean + * + * @param bool $visible + * @return bool */ public function update_visibility($visible = null) { if (is_bool($visible)) { @@ -420,10 +423,11 @@ class repository_type { /** * Delete a repository_type (general options are removed from config_plugin * table, and all instances are deleted) - * @global object $DB - * @return boolean + * + * @param bool $downloadcontents download external contents if exist + * @return bool */ - public function delete() { + public function delete($downloadcontents = false) { global $DB; //delete all instances of this type @@ -433,7 +437,7 @@ class repository_type { $params['type'] = $this->_typename; $instances = repository::get_instances($params); foreach ($instances as $instance) { - $instance->delete(); + $instance->delete($downloadcontents); } //delete all general options @@ -441,53 +445,50 @@ class repository_type { set_config($name, null, $this->_typename); } - return $DB->delete_records('repository', array('type' => $this->_typename)); + try { + $DB->delete_records('repository', array('type' => $this->_typename)); + } catch (dml_exception $ex) { + return false; + } + return true; } } /** - * This is the base class of the repository class + * This is the base class of the repository class. * - * To use repository plugin, see: - * http://docs.moodle.org/dev/Repository_How_to_Create_Plugin - * class repository is an abstract class, some functions must be implemented in subclass. - * See an example: repository/boxnet/lib.php + * To create repository plugin, see: {@link http://docs.moodle.org/dev/Repository_plugins} + * See an example: {@link repository_boxnet} * - * A few notes: - * // for ajax file picker, this will print a json string to tell file picker - * // how to build a login form - * $repo->print_login(); - * // for ajax file picker, this will return a files list. - * $repo->get_listing(); - * // this function will be used for non-javascript version. - * $repo->print_listing(); - * // print a search box - * $repo->print_search(); - * - * @package moodlecore - * @subpackage repository - * @copyright 2009 Dongsheng Cai + * @package repository + * @category repository + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class repository { // $disabled can be set to true to disable a plugin by force // example: self::$disabled = true + /** @var bool force disable repository instance */ public $disabled = false; + /** @var int repository instance id */ public $id; - /** @var object current context */ + /** @var stdClass current context */ public $context; + /** @var array repository options */ public $options; + /** @var bool Whether or not the repository instance is editable */ public $readonly; + /** @var int return types */ public $returntypes; - /** @var object repository instance database record */ + /** @var stdClass repository instance database record */ public $instance; /** - * 1. Initialize context and options - * 2. Accept necessary parameters + * Constructor * - * @param integer $repositoryid repository instance id - * @param integer|object a context id or context object + * @param int $repositoryid repository instance id + * @param int|stdClass $context a context id or context object * @param array $options repository options + * @param int $readonly indicate this repo is readonly or not */ public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) { global $DB; @@ -502,6 +503,7 @@ abstract class repository { $this->options = array(); if (is_array($options)) { + // The get_option() method will get stored options in database. $options = array_merge($this->get_option(), $options); } else { $options = $this->get_option(); @@ -514,9 +516,41 @@ abstract class repository { $this->super_called = true; } + /** + * Get repository instance using repository id + * + * @param int $repositoryid repository ID + * @param stdClass|int $context context instance or context ID + * @return repository + */ + public static function get_repository_by_id($repositoryid, $context) { + 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'; + + if (!$record = $DB->get_record_sql($sql, array($repositoryid))) { + throw new repository_exception('invalidrepositoryid', 'repository'); + } else { + $type = $record->type; + if (file_exists($CFG->dirroot . "/repository/$type/lib.php")) { + require_once($CFG->dirroot . "/repository/$type/lib.php"); + $classname = 'repository_' . $type; + $contextid = $context; + if (is_object($context)) { + $contextid = $context->id; + } + $repository = new $classname($repositoryid, $contextid, array('type'=>$type)); + return $repository; + } else { + throw new moodle_exception('error'); + } + } + } + /** * Get a repository type object by a given type name. - * @global object $DB + * + * @static * @param string $typename the repository type name * @return repository_type|bool */ @@ -532,7 +566,8 @@ abstract class repository { /** * Get the repository type by a given repository type id. - * @global object $DB + * + * @static * @param int $id the type id * @return object */ @@ -549,9 +584,9 @@ abstract class repository { /** * Return all repository types ordered by sortorder field * first repository type in returnedarray[0], second repository type in returnedarray[1], ... - * @global object $DB - * @global object $CFG - * @param boolean $visible can return types by visiblity, return all types if null + * + * @static + * @param bool $visible can return types by visiblity, return all types if null * @return array Repository types */ public static function get_types($visible=null) { @@ -575,9 +610,11 @@ abstract class repository { /** * To check if the context id is valid - * @global object $USER + * + * @static * @param int $contextid - * @return boolean + * @param stdClass $instance + * @return bool */ public static function check_capability($contextid, $instance) { $context = get_context_instance_by_id($contextid); @@ -590,10 +627,11 @@ abstract class repository { /** * Check if file already exists in draft area * + * @static * @param int $itemid * @param string $filepath * @param string $filename - * @return boolean + * @return bool */ public static function draftfile_exists($itemid, $filepath, $filename) { global $USER; @@ -606,33 +644,25 @@ abstract class repository { } } - /** - * Does this repository used to browse moodle files? - * - * @return boolean - */ - public function has_moodle_files() { - return false; - } /** * This function is used to copy a moodle file to draft area * - * @global object $USER - * @global object $DB * @param string $encoded The metainfo of file, it is base64 encoded php serialized data - * @param string $draftitemid itemid - * @param string $new_filename The intended name of file + * @param int $draftitemid itemid * @param string $new_filepath the new path in draft area + * @param string $new_filename The intended name of file * @return array The information of file */ public function copy_to_area($encoded, $draftitemid, $new_filepath, $new_filename) { global $USER, $DB; + $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 copy_to_area'); + throw new coding_exception('Only repository used to browse moodle files can use repository::copy_to_area()'); } - $browser = get_file_browser(); + $params = unserialize(base64_decode($encoded)); $user_context = get_context_instance(CONTEXT_USER, $USER->id); @@ -677,6 +707,7 @@ abstract class repository { /** * Get unused filename by appending suffix * + * @static * @param int $itemid * @param string $filepath * @param string $filename @@ -694,6 +725,7 @@ abstract class repository { /** * Append a suffix to filename * + * @static * @param string $filename * @return string */ @@ -709,7 +741,9 @@ abstract class repository { /** * Return all types that you a user can create/edit and which are also visible * Note: Mostly used in order to know if at least one editable type can be set - * @param object $context the context for which we want the editable types + * + * @static + * @param stdClass $context the context for which we want the editable types * @return array types */ public static function get_editable_types($context = null) { @@ -733,10 +767,8 @@ abstract class repository { /** * Return repository instances - * @global object $DB - * @global object $CFG - * @global object $USER * + * @static * @param array $args Array containing the following keys: * currentcontext * context @@ -890,8 +922,8 @@ abstract class repository { /** * Get single repository instance - * @global object $DB - * @global object $CFG + * + * @static * @param integer $id repository id * @return object repository instance */ @@ -919,9 +951,10 @@ abstract class repository { /** * Call a static function. Any additional arguments than plugin and function will be passed through. - * @global object $CFG - * @param string $plugin - * @param string $function + * + * @static + * @param string $plugin repository plugin name + * @param string $function funciton name * @return mixed */ public static function static_function($plugin, $function) { @@ -964,7 +997,6 @@ abstract class repository { * @param string $thefile * @param string $filename name of the file * @param bool $deleteinfected - * @return void */ public static function antivir_scan_file($thefile, $filename, $deleteinfected) { global $CFG; @@ -1024,14 +1056,82 @@ abstract class repository { } } + /** + * Repository method to serve file + * + * @param stored_file $storedfile + * @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 array $options additional options affecting the file serving + */ + public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) { + } + + /** + * Return reference file life time + * + * @param string $ref + * @return int + */ + public function get_reference_file_lifetime($ref) { + // One day + return 60 * 60 * 24; + } + + /** + * Decide whether or not the file should be synced + * + * @param stored_file $storedfile + * @return bool + */ + public function sync_individual_file(stored_file $storedfile) { + return true; + } + + /** + * Return human readable reference information + * {@link stored_file::get_reference()} + * + * @param string $reference + * @return string|null + */ + public function get_reference_details($reference) { + return null; + } + + /** + * Cache file from external repository by reference + * {@link repository::get_file_reference()} + * {@link repository::get_file()} + * Invoked at MOODLE/repository/repository_ajax.php + * + * @param string $reference this reference is generated by + * repository::get_file_reference() + * @param stored_file $storedfile created file reference + */ + public function cache_file_by_reference($reference, $storedfile = null) { + } + + /** + * Get file from external repository by reference + * {@link repository::get_file_reference()} + * {@link repository::get_file()} + * + * @param stdClass $reference file reference db record + * @return stdClass|null|false + */ + public function get_file_by_reference($reference) { + return null; + } + /** * Move file from download folder to file pool using FILE API - * @global object $DB - * @global object $CFG - * @global object $USER - * @global object $OUTPUT + * + * @todo MDL-28637 + * @static * @param string $thefile file path in download folder - * @param object $record + * @param stdClass $record * @return array containing the following keys: * icon * file @@ -1044,29 +1144,13 @@ abstract class repository { // scan for viruses if possible, throws exception if problem found self::antivir_scan_file($thefile, $record->filename, empty($CFG->repository_no_delete)); //TODO: MDL-28637 this repository_no_delete is a bloody hack! - if ($record->filepath !== '/') { - $record->filepath = trim($record->filepath, '/'); - $record->filepath = '/'.$record->filepath.'/'; - } - $context = get_context_instance(CONTEXT_USER, $USER->id); - $now = time(); - - $record->contextid = $context->id; - $record->component = 'user'; - $record->filearea = 'draft'; - $record->timecreated = $now; - $record->timemodified = $now; - $record->userid = $USER->id; - $record->mimetype = mimeinfo('type', $thefile); - if(!is_numeric($record->itemid)) { - $record->itemid = 0; - } $fs = get_file_storage(); - if ($existingfile = $fs->get_file($context->id, $record->component, $record->filearea, $record->itemid, $record->filepath, $record->filename)) { + // If file name being used. + if (repository::draftfile_exists($record->itemid, $record->filepath, $record->filename)) { $draftitemid = $record->itemid; $new_filename = repository::get_unused_filename($draftitemid, $record->filepath, $record->filename); $old_filename = $record->filename; - // create a tmp file + // Create a tmp file. $record->filename = $new_filename; $newfile = $fs->create_file_from_pathname($record, $thefile); $event = array(); @@ -1099,16 +1183,16 @@ abstract class repository { } /** - * Builds a tree of files This function is - * then called recursively. + * Builds a tree of files This function is then called recursively. * - * @param $fileinfo an object returned by file_browser::get_file_info() - * @param $search searched string - * @param $dynamicmode bool no recursive call is done when in dynamic mode - * @param $list - the array containing the files under the passed $fileinfo + * @static + * @todo take $search into account, and respect a threshold for dynamic loading + * @param file_info $fileinfo an object returned by file_browser::get_file_info() + * @param string $search searched string + * @param bool $dynamicmode no recursive call is done when in dynamic mode + * @param array $list the array containing the files under the passed $fileinfo * @returns int the number of files found * - * todo: take $search into account, and respect a threshold for dynamic loading */ public static function build_tree($fileinfo, $search, $dynamicmode, &$list) { global $CFG, $OUTPUT; @@ -1184,13 +1268,11 @@ abstract class repository { return $filecount; } - /** * Display a repository instance list (with edit/delete/create links) - * @global object $CFG - * @global object $USER - * @global object $OUTPUT - * @param object $context the context for which we display the instance + * + * @static + * @param stdClass $context the context for which we display the instance * @param string $typename if set, we display only one type of instance */ public static function display_instances_list($context, $typename = null) { @@ -1205,7 +1287,6 @@ abstract class repository { } else { $baseurl = new moodle_url('/repository/manage_instances.php', array('contextid'=>$context->id, 'sesskey'=>sesskey())); } - $url = $baseurl; $namestr = get_string('name'); $pluginstr = get_string('plugin', 'repository'); @@ -1240,15 +1321,15 @@ abstract class repository { if ($type->get_contextvisibility($context)) { if (!$i->readonly) { - $url->param('type', $i->options['type']); - $url->param('edit', $i->id); - $settings .= html_writer::link($url, $settingsstr); + $settingurl = new moodle_url($baseurl); + $settingurl->param('type', $i->options['type']); + $settingurl->param('edit', $i->id); + $settings .= html_writer::link($settingurl, $settingsstr); - $url->remove_params('edit'); - $url->param('delete', $i->id); - $delete .= html_writer::link($url, $deletestr); - - $url->remove_params('type'); + $deleteurl = new moodle_url($baseurl); + $deleteurl->param('delete', $i->id); + $deleteurl->param('type', $i->options['type']); + $delete .= html_writer::link($deleteurl, $deletestr); } } @@ -1294,9 +1375,7 @@ abstract class repository { if (!empty($instanceoptionnames)) { //create a unique type of instance $addable = 1; $baseurl->param('new', $typename); - $instancehtml .= "
-

- "; + $output .= $OUTPUT->single_button($baseurl, get_string('createinstance', 'repository'), 'get'); $baseurl->remove_params('new'); } } @@ -1312,9 +1391,20 @@ abstract class repository { print($output); } + /** + * Prepare file reference information + * + * @param string $source + * @return string file referece + */ + public function get_file_reference($source) { + return $source; + } /** * Decide where to save the file, can be overwriten by subclass - * @param string filename + * + * @param string $filename file name + * @return file path */ public function prepare_file($filename) { global $CFG; @@ -1333,6 +1423,15 @@ abstract class repository { return $dir.$filename; } + /** + * Does this repository used to browse moodle files? + * + * @return bool + */ + public function has_moodle_files() { + return false; + } + /** * Return file URL, for most plugins, the parameter is the original * url, but some plugins use a file id, so we need this function to @@ -1346,14 +1445,11 @@ abstract class repository { } /** - * Download a file, this function can be overridden by - * subclass. + * Download a file, this function can be overridden by subclass. {@link curl} * - * @global object $CFG * @param string $url the url of file * @param string $filename save location * @return string the location of the file - * @see curl package */ public function get_file($url, $filename = '') { global $CFG; @@ -1361,6 +1457,8 @@ abstract class repository { $fp = fopen($path, 'w'); $c = new curl; $c->download(array(array('url'=>$url, 'file'=>$fp))); + // Close file handler. + fclose($fp); return array('path'=>$path, 'url'=>$url); } @@ -1368,7 +1466,7 @@ abstract class repository { * Return size of a file in bytes. * * @param string $source encoded and serialized data of file - * @return integer file size in bytes + * @return int file size in bytes */ public function get_file_size($source) { $browser = get_file_browser(); @@ -1392,7 +1490,8 @@ abstract class repository { /** * Return is the instance is visible * (is the type visible ? is the context enable ?) - * @return boolean + * + * @return bool */ public function is_visible() { $type = repository::get_type_by_id($this->options['typeid']); @@ -1410,7 +1509,7 @@ abstract class repository { /** * Return the name of this instance, can be overridden. - * @global object $DB + * * @return string */ public function get_name() { @@ -1423,7 +1522,8 @@ abstract class repository { } /** - * what kind of files will be in this repository? + * What kind of files will be in this repository? + * * @return array return '*' means this repository support any files, otherwise * return mimetypes of files, it can be an array */ @@ -1433,17 +1533,18 @@ abstract class repository { } /** - * does it return a file url or a item_id + * Does it return a file url or a item_id + * * @return string */ public function supported_returntypes() { - return (FILE_INTERNAL | FILE_EXTERNAL); + return (FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE); } /** * Provide repository instance information for Ajax - * @global object $CFG - * @return object + * + * @return stdClass */ final public function get_meta() { global $CFG, $OUTPUT; @@ -1461,13 +1562,13 @@ abstract class repository { /** * Create an instance for this plug-in - * @global object $CFG - * @global object $DB + * + * @static * @param string $type the type of the repository - * @param integer $userid the user id - * @param object $context the context + * @param int $userid the user id + * @param stdClass $context the context * @param array $params the options for this instance - * @param integer $readonly whether to create it readonly or not (defaults to not) + * @param int $readonly whether to create it readonly or not (defaults to not) * @return mixed */ public static function create($type, $userid, $context, $params, $readonly=0) { @@ -1512,21 +1613,29 @@ abstract class repository { /** * delete a repository instance - * @global object $DB - * @return mixed + * + * @param bool $downloadcontents + * @return bool */ - final public function delete() { + final public function delete($downloadcontents = false) { global $DB; - $DB->delete_records('repository_instances', array('id'=>$this->id)); - $DB->delete_records('repository_instance_config', array('instanceid'=>$this->id)); + if ($downloadcontents) { + $this->convert_references_to_local(); + } + try { + $DB->delete_records('repository_instances', array('id'=>$this->id)); + $DB->delete_records('repository_instance_config', array('instanceid'=>$this->id)); + } catch (dml_exception $ex) { + return false; + } return true; } /** * Hide/Show a repository - * @global object $DB + * * @param string $hide - * @return boolean + * @return bool */ final public function hide($hide = 'toggle') { global $DB; @@ -1552,9 +1661,9 @@ abstract class repository { /** * Save settings for repository instance * $repo->set_option(array('api_key'=>'f2188bde132', 'name'=>'dongsheng')); - * @global object $DB + * * @param array $options settings - * @return int Id of the record + * @return bool */ public function set_option($options = array()) { global $DB; @@ -1582,7 +1691,7 @@ abstract class repository { /** * Get settings for repository instance - * @global object $DB + * * @param string $config * @return array Settings */ @@ -1607,6 +1716,12 @@ abstract class repository { } } + /** + * Filter file listing to display specific types + * + * @param array $value + * @return bool + */ public function filter(&$value) { $pass = false; $accepted_types = optional_param_array('accepted_types', '', PARAM_RAW); @@ -1642,11 +1757,10 @@ abstract class repository { /** * Given a path, and perhaps a search, get a list of files. * - * See details on http://docs.moodle.org/dev/Repository_plugins + * See details on {@link http://docs.moodle.org/dev/Repository_plugins} * - * @param string $path, this parameter can - * a folder name, or a identification of folder - * @param string $page, the page number of file list + * @param string $path this parameter can a folder name, or a identification of folder + * @param string $page the page number of file list * @return array the list of files, including meta infomation, containing the following keys * manage, url to manage url * client_id @@ -1666,11 +1780,11 @@ abstract class repository { } /** - * Search files in repository - * When doing global search, $search_text will be used as - * keyword. + * Search files in repository. * - * @return mixed, see get_listing() + * @param string $search_text search key word + * @param int $page page + * @return mixed {@see repository::get_listing} */ public function search($search_text, $page = 0) { $list = array(); @@ -1691,7 +1805,7 @@ abstract class repository { /** * To check whether the user is logged in. * - * @return boolean + * @return bool */ public function check_login(){ return true; @@ -1700,6 +1814,8 @@ abstract class repository { /** * Show the login screen, if required + * + * @return string */ public function print_login(){ return $this->get_listing(); @@ -1707,7 +1823,8 @@ abstract class repository { /** * Show the search screen, if required - * @return null + * + * @return string */ public function print_search() { $str = ''; @@ -1727,7 +1844,8 @@ abstract class repository { /** * is it possible to do glboal search? - * @return boolean + * + * @return bool */ public function global_search() { return false; @@ -1735,7 +1853,8 @@ abstract class repository { /** * Defines operations that happen occasionally on cron - * @return boolean + * + * @return bool */ public function cron() { return true; @@ -1743,7 +1862,8 @@ abstract class repository { /** * function which is run when the type is created (moodle administrator add the plugin) - * @return boolean success or fail? + * + * @return bool success or fail? */ public static function plugin_init() { return true; @@ -1751,7 +1871,8 @@ abstract class repository { /** * Edit/Create Admin Settings Moodle form - * @param object $mform Moodle form (passed by reference) + * + * @param moodleform $mform Moodle form (passed by reference) * @param string $classname repository class name */ public static function type_config_form($mform, $classname = 'repository') { @@ -1767,9 +1888,11 @@ abstract class repository { /** * Validate Admin Settings Moodle form - * @param object $mform Moodle form (passed by reference) - * @param array array of ("fieldname"=>value) of submitted data - * @param array array of ("fieldname"=>errormessage) of errors + * + * @static + * @param moodleform $mform Moodle form (passed by reference) + * @param array $data array of ("fieldname"=>value) of submitted data + * @param array $errors array of ("fieldname"=>errormessage) of errors * @return array array of errors */ public static function type_form_validation($mform, $data, $errors) { @@ -1779,14 +1902,16 @@ abstract class repository { /** * Edit/Create Instance Settings Moodle form - * @param object $mform Moodle form (passed by reference) + * + * @param moodleform $mform Moodle form (passed by reference) */ public function instance_config_form($mform) { } /** - * Return names of the general options + * Return names of the general options. * By default: no general option name + * * @return array */ public static function get_type_option_names() { @@ -1794,18 +1919,34 @@ abstract class repository { } /** - * Return names of the instance options + * Return names of the instance options. * By default: no instance option name + * * @return array */ public static function get_instance_option_names() { return array(); } + /** + * Validate repository plugin instance form + * + * @param moodleform $mform moodle form + * @param array $data form data + * @param array $errors errors + * @return array errors + */ public static function instance_form_validation($mform, $data, $errors) { return $errors; } + /** + * Create a shorten filename + * + * @param string $str filename + * @param int $maxlength max file name length + * @return string short filename + */ public function get_short_filename($str, $maxlength) { if (textlib::strlen($str) >= $maxlength) { return trim(textlib::substr($str, 0, $maxlength)).'...'; @@ -1822,7 +1963,7 @@ abstract class repository { * @param string $filename * @param string $newfilepath * @param string $newfilename - * @return boolean + * @return bool */ public static function overwrite_existing_draftfile($itemid, $filepath, $filename, $newfilepath, $newfilename) { global $USER; @@ -1848,7 +1989,7 @@ abstract class repository { * @param int $draftitemid * @param string $filepath * @param string $filename - * @return boolean + * @return bool */ public static function delete_tempfile_from_draft($draftitemid, $filepath, $filename) { global $USER; @@ -1861,15 +2002,106 @@ abstract class repository { return false; } } + + /** + * Find all external files in this repo and import them + */ + public function convert_references_to_local() { + $fs = get_file_storage(); + $files = $fs->get_external_files($this->id); + foreach ($files as $storedfile) { + $fs->import_external_file($storedfile); + } + } + + + + /** + * Call to request proxy file sync with repository source. + * + * @param stored_file $file + * @return bool success + */ + public static function sync_external_file(stored_file $file) { + global $DB; + + $fs = get_file_storage(); + + if (!$reference = $DB->get_record('files_reference', array('id'=>$file->get_referencefileid()))) { + return false; + } + + if (!empty($reference->lastsync) and ($reference->lastsync + $reference->lifetime > time())) { + return false; + } + + if (!$repository = self::get_repository_by_id($reference->repositoryid, SYSCONTEXTID)) { + return false; + } + + if (!$repository->sync_individual_file($file)) { + return false; + } + + $fileinfo = $repository->get_file_by_reference($reference); + if ($fileinfo === null) { + // does not exist any more - set status to missing + $sql = "UPDATE {files} SET status = :missing WHERE referencefileid = :referencefileid"; + $params = array('referencefileid'=>$reference->id, 'missing'=>666); + $DB->execute($sql, $params); + //TODO: purge content from pool if we set some other content hash and it is no used any more + return true; + } else if ($fileinfo === false) { + // error + return false; + } + + $contenthash = null; + $filesize = null; + if (!empty($fileinfo->contenthash)) { + // contenthash returned, file already in moodle + $contenthash = $fileinfo->contenthash; + $filesize = $fileinfo->filesize; + } else if (!empty($fileinfo->filepath)) { + // File path returned + list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo->filepath); + } else if (!empty($fileinfo->handle) && is_resource($fileinfo->handle)) { + // File handle returned + $contents = ''; + while (!feof($fileinfo->handle)) { + $contents .= fread($handle, 8192); + } + fclose($fileinfo->handle); + list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($content); + } else if (isset($fileinfo->content)) { + // File content returned + list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($fileinfo->content); + } + + if (!isset($contenthash) or !isset($filesize)) { + return false; + } + + $now = time(); + // update files table + $sql = "UPDATE {files} SET contenthash = :contenthash, filesize = :filesize, referencelastsync = :now, referencelifetime = :lifetime, timemodified = :now2 WHERE referencefileid = :referencefileid AND contenthash <> :contenthash2"; + $params = array('contenthash'=>$contenthash, 'filesize'=>$filesize, 'now'=>$now, 'lifetime'=>$reference->lifetime, + 'now2'=>$now, 'referencefileid'=>$reference->id, 'contenthash2'=>$contenthash); + $DB->execute($sql, $params); + + $DB->set_field('files_reference', 'lastsync', $now, array('id'=>$reference->id)); + + return true; + } } /** * Exception class for repository api * * @since 2.0 - * @package moodlecore - * @subpackage repository - * @copyright 2009 Dongsheng Cai + * @package repository + * @category repository + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class repository_exception extends moodle_exception { @@ -1879,14 +2111,20 @@ class repository_exception extends moodle_exception { * This is a class used to define a repository instance form * * @since 2.0 - * @package moodlecore - * @subpackage repository - * @copyright 2009 Dongsheng Cai + * @package repository + * @category repository + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class repository_instance_form extends moodleform { + /** @var stdClass repository instance */ protected $instance; + /** @var string repository plugin type */ protected $plugin; + + /** + * Added defaults to moodle form + */ protected function add_defaults() { $mform =& $this->_form; $strrequired = get_string('required'); @@ -1906,6 +2144,9 @@ final class repository_instance_form extends moodleform { $mform->addRule('name', $strrequired, 'required', null, 'client'); } + /** + * Define moodle form elements + */ public function definition() { global $CFG; // type of plugin, string @@ -1952,6 +2193,13 @@ final class repository_instance_form extends moodleform { } } + /** + * Validate moodle form data + * + * @param array $data form data + * @param array $files files in form + * @return array errors + */ public function validation($data, $files) { global $DB; $errors = array(); @@ -1980,19 +2228,21 @@ final class repository_instance_form extends moodleform { * This is a class used to define a repository type setting form * * @since 2.0 - * @package moodlecore - * @subpackage repository - * @copyright 2009 Dongsheng Cai + * @package repository + * @category repository + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class repository_type_form extends moodleform { + /** @var stdClass repository instance */ protected $instance; + /** @var string repository plugin name */ protected $plugin; + /** @var string action */ protected $action; /** * Definition of the moodleform - * @global object $CFG */ public function definition() { global $CFG; @@ -2062,6 +2312,13 @@ final class repository_type_form extends moodleform { $this->add_action_buttons(true, get_string('save','repository')); } + /** + * Validate moodle form data + * + * @param array $data moodle form data + * @param array $files + * @return array errors + */ public function validation($data, $files) { $errors = array(); $plugin = $this->_customdata['plugin']; @@ -2081,7 +2338,7 @@ final class repository_type_form extends moodleform { /** * Generate all options needed by filepicker * - * @param array $args, including following keys + * @param array $args including following keys * context * accepted_types * return_types @@ -2163,6 +2420,7 @@ function initialise_filepicker($args) { } /** * Small function to walk an array to attach repository ID + * * @param array $value * @param string $key * @param int $id diff --git a/repository/local/lib.php b/repository/local/lib.php index 3f154382f53..f31317e184f 100644 --- a/repository/local/lib.php +++ b/repository/local/lib.php @@ -1,5 +1,4 @@ . /** - * repository_local class is used to browse moodle files + * This plugin is used to access local files * - * @since 2.0 + * @since 2.0 * @package repository_local - * @copyright 2009 Dongsheng Cai + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +require_once($CFG->dirroot . '/repository/lib.php'); +/** + * repository_local class is used to browse moodle files + * + * @since 2.0 + * @package repository_local + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class repository_local extends repository { /** * local plugin doesn't require login, so list all files diff --git a/repository/merlot/lib.php b/repository/merlot/lib.php index 91f2da7e57c..7f876431029 100644 --- a/repository/merlot/lib.php +++ b/repository/merlot/lib.php @@ -1,5 +1,4 @@ . +/** + * This plugin is used to access merlot files + * + * @since 2.0 + * @package repository_merlot + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); + /** * repository_merlot is used to search merlot.org in moodle * * @since 2.0 - * @package repository - * @subpackage merlot - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_merlot + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class repository_merlot extends repository { diff --git a/repository/picasa/lib.php b/repository/picasa/lib.php index bd43d848242..c1cbc3ef8cb 100644 --- a/repository/picasa/lib.php +++ b/repository/picasa/lib.php @@ -1,5 +1,4 @@ . +/** + * This plugin is used to access picasa pictures + * + * @since 2.0 + * @package repository_picasa + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once($CFG->libdir.'/googleapi.php'); + /** * Picasa Repository Plugin * @@ -25,9 +35,6 @@ * @author Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -require_once($CFG->libdir.'/googleapi.php'); - class repository_picasa extends repository { private $subauthtoken = ''; diff --git a/repository/recent/lib.php b/repository/recent/lib.php index 1f4299a531b..05f739b1189 100644 --- a/repository/recent/lib.php +++ b/repository/recent/lib.php @@ -15,16 +15,24 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * This plugin is used to access recent used files + * + * @since 2.0 + * @package repository_recent + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); + /** * repository_recent class is used to browse recent used files * * @since 2.0 - * @package repository - * @subpackage recent - * @copyright 2010 Dongsheng Cai + * @package repository_recent + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - define('DEFAULT_RECENT_FILES_NUM', 50); class repository_recent extends repository { diff --git a/repository/repository_ajax.php b/repository/repository_ajax.php index b7793c8b9b0..35a985cd319 100644 --- a/repository/repository_ajax.php +++ b/repository/repository_ajax.php @@ -1,5 +1,4 @@ + * @package repository + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -34,7 +32,7 @@ require_once(dirname(__FILE__).'/lib.php'); $err = new stdClass(); -/// Parameters +// Parameters $action = optional_param('action', '', PARAM_ALPHA); $repo_id = optional_param('repo_id', 0, PARAM_INT); // Repository ID $contextid = optional_param('ctx_id', SYSCONTEXTID, PARAM_INT); // Context ID @@ -51,6 +49,7 @@ $saveas_filename = optional_param('title', '', PARAM_FILE); // save as file $saveas_path = optional_param('savepath', '/', PARAM_PATH); // save as file path $search_text = optional_param('s', '', PARAM_CLEANHTML); $linkexternal = optional_param('linkexternal', '', PARAM_ALPHA); +$usefilereference = optional_param('usefilereference', '', PARAM_ALPHA); list($context, $course, $cm) = get_context_info_array($contextid); require_login($course, false, $cm); @@ -59,7 +58,7 @@ $PAGE->set_context($context); echo $OUTPUT->header(); // send headers @header('Content-type: text/html; charset=utf-8'); -// if uploaded file is larger than post_max_size (php.ini) setting, $_POST content will lost +// If uploaded file is larger than post_max_size (php.ini) setting, $_POST content will be empty. if (empty($_POST) && !empty($action)) { $err->error = get_string('errorpostmaxsize', 'repository'); die(json_encode($err)); @@ -70,7 +69,7 @@ if (!confirm_sesskey()) { die(json_encode($err)); } -/// Get repository instance information +// 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))) { @@ -80,7 +79,7 @@ if (!$repository = $DB->get_record_sql($sql, array($repo_id))) { $type = $repository->type; } -/// Check permissions +// Check permissions repository::check_capability($contextid, $repository); $moodle_maxbytes = get_max_upload_file_size(); @@ -89,7 +88,7 @@ if ($maxbytes == 0 || $maxbytes>=$moodle_maxbytes) { $maxbytes = $moodle_maxbytes; } -/// Wait as long as it takes for this script to finish +// Wait as long as it takes for this script to finish set_time_limit(0); // Early actions which need to be done before repository instances initialised @@ -125,13 +124,19 @@ switch ($action) { if (file_exists($CFG->dirroot.'/repository/'.$type.'/lib.php')) { require_once($CFG->dirroot.'/repository/'.$type.'/lib.php'); $classname = 'repository_' . $type; - $repo = new $classname($repo_id, $contextid, array('ajax'=>true, 'name'=>$repository->name, 'type'=>$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 +// These actions all occur on the currently active repository instance switch ($action) { case 'sign': case 'signin': @@ -201,45 +206,43 @@ switch ($action) { echo json_encode($info); die; } else { - // some repository plugins deal with moodle internal files, so we cannot use get_file + $fs = get_file_storage(); + // Some repository plugins are hosting moodle internal files, we cannot use get_file // method, so we use copy_to_area method // (local, user, coursefiles, recent) - if ($repo->has_moodle_files()) { + if ($repo->has_moodle_files() && ($usefilereference != 'yes')) { // check filesize against max allowed size $filesize = $repo->get_file_size($source); if (empty($filesize)) { - $err->error = get_string('filesizenull', 'repository'); - die(json_encode($err)); + $filesize = 0; } if (($maxbytes !== -1) && ($filesize > $maxbytes)) { throw new file_exception('maxbytes'); } + // If the moodle file is an alias to a file in external repository + // we copy this alias instead of create alias to alias + // {@link repository::copy_to_area()}. $fileinfo = $repo->copy_to_area($source, $itemid, $saveas_path, $saveas_filename); + if (!isset($fileinfo['event'])) { $fileinfo['file'] = $fileinfo['title']; } + echo json_encode($fileinfo); die; } - // Download file to moodle - $file = $repo->get_file($source, $saveas_filename); - if ($file['path'] === false) { - $err->error = get_string('cannotdownload', 'repository'); - die(json_encode($err)); - } - - // check if exceed maxbytes - if (($maxbytes!==-1) && (filesize($file['path']) > $maxbytes)) { - throw new file_exception('maxbytes'); - } + // Prepare file record. $record = new stdClass(); $record->filepath = $saveas_path; $record->filename = $saveas_filename; $record->component = 'user'; $record->filearea = 'draft'; - $record->itemid = $itemid; - + if (!is_numeric($itemid)) { + $record->itemid = 0; + } else { + $record->itemid = $itemid; + } if (!empty($file['license'])) { $record->license = $file['license']; } else { @@ -250,11 +253,78 @@ switch ($action) { } else { $record->author = $author; } - $record->source = !empty($file['url']) ? $file['url'] : ''; - $info = repository::move_to_filepool($file['path'], $record); - if (empty($info)) { - $info['e'] = get_string('error', 'moodle'); + if ($record->filepath !== '/') { + $record->filepath = trim($record->filepath, '/'); + $record->filepath = '/'.$record->filepath.'/'; + } + $usercontext = get_context_instance(CONTEXT_USER, $USER->id); + $now = time(); + $record->contextid = $usercontext->id; + $record->timecreated = $now; + $record->timemodified = $now; + $record->userid = $USER->id; + $record->mimetype = mimeinfo('type', $record->filename); + + + if ($usefilereference == 'yes') { + $reference = $repo->get_file_reference($source); + // get reference life time from repo + $record->referencelifetime = $repo->get_reference_file_lifetime($reference); + // Check if file exists. + if (repository::draftfile_exists($itemid, $saveas_path, $saveas_filename)) { + // File name being used, rename it. + $unused_filename = repository::get_unused_filename($itemid, $saveas_path, $saveas_filename); + $record->filename = $unused_filename; + // Create a file copy using unused filename. + $storedfile = $fs->create_file_from_reference($record, $repo_id, $reference); + + $event = array(); + $event['event'] = 'fileexists'; + $event['newfile'] = new stdClass; + $event['newfile']->filepath = $saveas_path; + $event['newfile']->filename = $unused_filename; + $event['newfile']->url = moodle_url::make_draftfile_url($itemid, $saveas_path, $unused_filename)->out(); + + $event['existingfile'] = new stdClass; + $event['existingfile']->filepath = $saveas_path; + $event['existingfile']->filename = $saveas_filename; + $event['existingfile']->url = moodle_url::make_draftfile_url($itemid, $saveas_path, $saveas_filename)->out();; + echo json_encode($event); + die; + } + $storedfile = $fs->create_file_from_reference($record, $repo_id, $reference); + // Repository plugin callback + // You can cache reository file in this callback + // or complete other tasks. + $repo->cache_file_by_reference($reference, $storedfile); + $info = array( + 'url'=>moodle_url::make_draftfile_url($storedfile->get_itemid(), $storedfile->get_filepath(), $storedfile->get_filename())->out(), + 'id'=>$storedfile->get_itemid(), + 'file'=>$storedfile->get_filename(), + 'icon' => $OUTPUT->pix_url(file_extension_icon($storedfile->get_filename(), 32))->out(), + ); + echo json_encode($info); + die; + } else { + // Download file to moodle. + $downloadedfile = $repo->get_file($source, $saveas_filename); + if ($downloadedfile['path'] === false) { + $err->error = get_string('cannotdownload', 'repository'); + die(json_encode($err)); + } + + // Check if exceed maxbytes. + if (($maxbytes!==-1) && (filesize($file['path']) > $maxbytes)) { + throw new file_exception('maxbytes'); + } + + $record->source = !empty($downloadedfile['url']) ? $downloadedfile['url'] : ''; + + $info = repository::move_to_filepool($downloadedfile['path'], $record); + if (empty($info)) { + $info['e'] = get_string('error', 'moodle'); + } } echo json_encode($info); die; diff --git a/repository/s3/lib.php b/repository/s3/lib.php index e0a8f328f7f..b74a87dbdfc 100644 --- a/repository/s3/lib.php +++ b/repository/s3/lib.php @@ -15,20 +15,25 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * This plugin is used to access s3 files + * + * @since 2.0 + * @package repository_s3 + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once('S3.php'); /** * This is a repository class used to browse Amazon S3 content. * * @since 2.0 - * @package repository - * @subpackage s3 - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_s3 + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -require_once('S3.php'); - class repository_s3 extends repository { /** diff --git a/repository/tests/repository_test.php b/repository/tests/repository_test.php new file mode 100644 index 00000000000..46eaf449264 --- /dev/null +++ b/repository/tests/repository_test.php @@ -0,0 +1,61 @@ +. + +/** + * Repository API unit tests + * + * @package repository + * @category phpunit + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once("$CFG->dirroot/repository/lib.php"); + +class repositorylib_testcase extends advanced_testcase { + + /** + * Installing repository tests + * + * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org} + */ + public function test_install_repository() { + global $CFG, $DB; + + $this->resetAfterTest(true); + + $syscontext = context_system::instance(); + $repositorypluginname = 'boxnet'; + // override repository permission + $capability = 'repository/' . $repositorypluginname . ':view'; + $allroles = $DB->get_records_menu('role', array(), 'id', 'archetype, id'); + assign_capability($capability, CAP_ALLOW, $allroles['guest'], $syscontext->id, true); + + $plugintype = new repository_type($repositorypluginname); + $pluginid = $plugintype->create(false); + $this->assertInternalType('int', $pluginid); + $args = array(); + $args['type'] = $repositorypluginname; + $repos = repository::get_instances($args); + $repository = reset($repos); + $this->assertInstanceOf('repository', $repository); + $info = $repository->get_meta(); + $this->assertEquals($repositorypluginname, $info->type); + } +} diff --git a/repository/upload/lib.php b/repository/upload/lib.php index 1b2bb9d85f7..cd5ac10f47b 100644 --- a/repository/upload/lib.php +++ b/repository/upload/lib.php @@ -15,14 +15,22 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * This plugin is used to upload files + * + * @since 2.0 + * @package repository_upload + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); + /** * A repository plugin to allow user uploading files * * @since 2.0 - * @package repository - * @subpackage upload - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_upload + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/repository/url/lib.php b/repository/url/lib.php index bc31e9531f7..16f2eb07ae5 100644 --- a/repository/url/lib.php +++ b/repository/url/lib.php @@ -15,20 +15,26 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * This plugin is used to access files by providing an url + * + * @since 2.0 + * @package repository_url + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once(dirname(__FILE__).'/locallib.php'); + /** * repository_url class * A subclass of repository, which is used to download a file from a specific url * * @since 2.0 - * @package repository - * @subpackage url - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_url + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -require_once(dirname(__FILE__).'/locallib.php'); - class repository_url extends repository { var $processedfiles = array(); diff --git a/repository/user/lib.php b/repository/user/lib.php index c86eb3cb02b..1f06ddd28ea 100644 --- a/repository/user/lib.php +++ b/repository/user/lib.php @@ -16,19 +16,28 @@ // along with Moodle. If not, see . /** - * repository_user class is used to browse user private files + * This plugin is used to access user's private files * * @since 2.0 - * @package repository - * @subpackage user - * @copyright 2010 Dongsheng Cai + * @package repository_user + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +require_once($CFG->dirroot . '/repository/lib.php'); +/** + * repository_user class is used to browse user private files + * + * @since 2.0 + * @package repository_user + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class repository_user extends repository { /** * user plugin doesn't require login + * * @return mixed */ public function print_login() { @@ -115,15 +124,6 @@ class repository_user extends repository { return $ret; } - /** - * User file don't support to link to external links - * - * @return int - */ - public function supported_returntypes() { - return FILE_INTERNAL; - } - /** * Does this repository used to browse moodle files? * @@ -132,4 +132,111 @@ class repository_user extends repository { public function has_moodle_files() { return true; } + + /** + * User cannot use the external link to dropbox + * + * @return int + */ + public function supported_returntypes() { + return FILE_INTERNAL | FILE_REFERENCE; + } + + + /** + * Prepare file reference information + * + * @param string $source + * @return string file referece + */ + public function get_file_reference($source) { + global $USER; + $params = unserialize(base64_decode($source)); + if (is_array($params)) { + $filepath = clean_param($params['filepath'], PARAM_PATH);; + $filename = clean_param($params['filename'], PARAM_FILE); + $contextid = clean_param($params['contextid'], PARAM_INT); + } + // We store all file parameters, so file api could + // find the refernces later. + $reference = array(); + $reference['contextid'] = $contextid; + $reference['component'] = 'user'; + $reference['filearea'] = 'private'; + $reference['itemid'] = 0; + $reference['filepath'] = $filepath; + $reference['filename'] = $filename; + + return file_storage::pack_reference($reference); + } + + /** + * Get file from external repository by reference + * {@link repository::get_file_reference()} + * {@link repository::get_file()} + * + * @param stdClass $reference file reference db record + * @return stdClass|null|false + */ + public function get_file_by_reference($reference) { + $fs = get_file_storage(); + $ref = $reference->reference; + $params = unserialize(base64_decode($ref)); + if (!is_array($params)) { + throw new repository_exception('invalidparams', 'repository'); + } + $filename = is_null($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE); + $filepath = is_null($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH);; + $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT); + + // hard coded component, filearea and item for security + $component = 'user'; + $filearea = 'private'; + $itemid = 0; + + $storedfile = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename); + + $fileinfo = new stdClass; + $fileinfo->contenthash = $storedfile->get_contenthash(); + $fileinfo->filesize = $storedfile->get_filesize(); + return $fileinfo; + } + + /** + * Return human readable reference information + * {@link stored_file::get_reference()} + * + * @param string $reference + * @return string|null + */ + public function get_reference_details($reference) { + $params = file_storage::unpack_reference($reference); + // Indicate this is from user private area + return $this->get_name() . ': ' . $params['filepath'] . $params['filename']; + } + + /** + * Repository method to serve file + * + * @param stored_file $storedfile + * @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 array $options additional options affecting the file serving + */ + public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) { + $reference = $storedfile->get_reference(); + $params = file_storage::unpack_reference($reference); + $filepath = clean_param($params['filepath'], PARAM_PATH);; + $filename = clean_param($params['filename'], PARAM_FILE); + $contextid = clean_param($params['contextid'], PARAM_INT); + $filearea = 'private'; + $component = 'user'; + $itemid = 0; + + $fs = get_file_storage(); + $storedfile = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename); + + send_stored_file($storedfile, $lifetime, $filter, $forcedownload, $options); + } } diff --git a/repository/webdav/lib.php b/repository/webdav/lib.php index ea4444d2fc5..bccb7f07063 100644 --- a/repository/webdav/lib.php +++ b/repository/webdav/lib.php @@ -15,19 +15,25 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * This plugin is used to access webdav files + * + * @since 2.0 + * @package repository_webdav + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once($CFG->libdir.'/webdavlib.php'); + /** * repository_webdav class * * @since 2.0 - * @package repository - * @subpackage webdav - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_webdav + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -require_once($CFG->libdir.'/webdavlib.php'); - class repository_webdav extends repository { public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { parent::__construct($repositoryid, $context, $options); diff --git a/repository/wikimedia/lib.php b/repository/wikimedia/lib.php index 8583bbab9c6..50d85562873 100644 --- a/repository/wikimedia/lib.php +++ b/repository/wikimedia/lib.php @@ -15,17 +15,24 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -require_once('wikimedia.php'); +/** + * This plugin is used to access wikimedia files + * + * @since 2.0 + * @package repository_wikimedia + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); +require_once(dirname(__FILE__) . '/wikimedia.php'); /** * repository_wikimedia class * This is a class used to browse images from wikimedia * * @since 2.0 - * @package repository - * @subpackage wikimedia - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_wikimedia + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/repository/youtube/lib.php b/repository/youtube/lib.php index 41151f9a3b4..7a7c695ed5b 100644 --- a/repository/youtube/lib.php +++ b/repository/youtube/lib.php @@ -1,5 +1,4 @@ . +/** + * This plugin is used to access youtube videos + * + * @since 2.0 + * @package repository_youtube + * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once($CFG->dirroot . '/repository/lib.php'); + /** * repository_youtube class * * @since 2.0 - * @package repository - * @subpackage youtube - * @copyright 2009 Dongsheng Cai - * @author Dongsheng Cai + * @package repository_youtube + * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/user/filesedit.php b/user/filesedit.php index 716456e569b..86eb52ee7ed 100644 --- a/user/filesedit.php +++ b/user/filesedit.php @@ -57,7 +57,7 @@ $PAGE->set_pagetype('user-files'); $data = new stdClass(); $data->returnurl = $returnurl; -$options = array('subdirs'=>1, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL); +$options = array('subdirs'=>1, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>-1, 'accepted_types'=>'*'); file_prepare_standard_filemanager($data, 'files', $options, $context, 'user', 'private', 0); $mform = new user_filesedit_form(null, array('data'=>$data, 'options'=>$options)); diff --git a/version.php b/version.php index 4dad272d864..2732db6eb73 100644 --- a/version.php +++ b/version.php @@ -30,7 +30,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2012051900.00; // YYYYMMDD = weekly release date of this DEV branch +$version = 2012052100.00; // YYYYMMDD = weekly release date of this DEV branch // RR = release increments - 00 in DEV branches // .XX = incremental changes
'+M.str.repository.linkexternal+'
'+M.str.repository.linkexternal+'
'+M.str.repository.uselatestfile+'