/** * Prepare file reference information * * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned) * @return string file reference, ready to be stored */ public function get_file_reference($source) { if ($source && $this->has_moodle_files()) { $params = @json_decode(base64_decode($source), true); if (!is_array($params) || empty($params['contextid'])) { throw new repository_exception('invalidparams', 'repository'); } $params = array('component' => empty($params['component']) ? '' : clean_param($params['component'], PARAM_COMPONENT), 'filearea' => empty($params['filearea']) ? '' : clean_param($params['filearea'], PARAM_AREA), 'itemid' => empty($params['itemid']) ? 0 : clean_param($params['itemid'], PARAM_INT), 'filename' => empty($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE), 'filepath' => empty($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH), 'contextid' => clean_param($params['contextid'], PARAM_INT)); // Check if context exists. if (!context::instance_by_id($params['contextid'], IGNORE_MISSING)) { throw new repository_exception('invalidparams', 'repository'); } return file_storage::pack_reference($params); } return $source; }
/** * What to do when this step is executed. */ protected function define_execution() { global $DB; $this->log('processing file aliases queue', backup::LOG_DEBUG); $fs = get_file_storage(); // Load the queue. $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $this->get_restoreid(), 'itemname' => 'file_aliases_queue'), '', 'info'); // Iterate over aliases in the queue. foreach ($rs as $record) { $info = backup_controller_dbops::decode_backup_temp_info($record->info); // Try to pick a repository instance that should serve the alias. $repository = $this->choose_repository($info); if (is_null($repository)) { $this->notify_failure($info, 'unable to find a matching repository instance'); continue; } if ($info->oldfile->repositorytype === 'local' or $info->oldfile->repositorytype === 'coursefiles') { // Aliases to Server files and Legacy course files may refer to a file // contained in the backup file or to some existing file (if we are on the // same site). try { $reference = file_storage::unpack_reference($info->oldfile->reference); } catch (Exception $e) { $this->notify_failure($info, 'invalid reference field format'); continue; } // Let's see if the referred source file was also included in the backup. $candidates = $DB->get_recordset('backup_files_temp', array('backupid' => $this->get_restoreid(), 'contextid' => $reference['contextid'], 'component' => $reference['component'], 'filearea' => $reference['filearea'], 'itemid' => $reference['itemid']), '', 'info, newcontextid, newitemid'); $source = null; foreach ($candidates as $candidate) { $candidateinfo = backup_controller_dbops::decode_backup_temp_info($candidate->info); if ($candidateinfo->filename === $reference['filename'] and $candidateinfo->filepath === $reference['filepath'] and !is_null($candidate->newcontextid) and !is_null($candidate->newitemid)) { $source = $candidateinfo; $source->contextid = $candidate->newcontextid; $source->itemid = $candidate->newitemid; break; } } $candidates->close(); if ($source) { // We have an alias that refers to another file also included in // the backup. Let us change the reference field so that it refers // to the restored copy of the original file. $reference = file_storage::pack_reference($source); // Send the new alias to the filepool. $fs->create_file_from_reference($info->newfile, $repository->id, $reference); $this->notify_success($info); continue; } else { // This is a reference to some moodle file that was not contained in the backup // file. If we are restoring to the same site, keep the reference untouched // and restore the alias as is if the referenced file exists. if ($this->task->is_samesite()) { if ($fs->file_exists($reference['contextid'], $reference['component'], $reference['filearea'], $reference['itemid'], $reference['filepath'], $reference['filename'])) { $reference = file_storage::pack_reference($reference); $fs->create_file_from_reference($info->newfile, $repository->id, $reference); $this->notify_success($info); continue; } else { $this->notify_failure($info, 'referenced file not found'); continue; } // If we are at other site, we can't restore this alias. } else { $this->notify_failure($info, 'referenced file not included'); continue; } } } else { if ($info->oldfile->repositorytype === 'user') { if ($this->task->is_samesite()) { // For aliases to user Private files at the same site, we have a chance to check // if the referenced file still exists. try { $reference = file_storage::unpack_reference($info->oldfile->reference); } catch (Exception $e) { $this->notify_failure($info, 'invalid reference field format'); continue; } if ($fs->file_exists($reference['contextid'], $reference['component'], $reference['filearea'], $reference['itemid'], $reference['filepath'], $reference['filename'])) { $reference = file_storage::pack_reference($reference); $fs->create_file_from_reference($info->newfile, $repository->id, $reference); $this->notify_success($info); continue; } else { $this->notify_failure($info, 'referenced file not found'); continue; } // If we are at other site, we can't restore this alias. } else { $this->notify_failure($info, 'restoring at another site'); continue; } } else { // This is a reference to some external file such as in boxnet or dropbox. // If we are restoring to the same site, keep the reference untouched and // restore the alias as is. if ($this->task->is_samesite()) { $fs->create_file_from_reference($info->newfile, $repository->id, $info->oldfile->reference); $this->notify_success($info); continue; // If we are at other site, we can't restore this alias. } else { $this->notify_failure($info, 'restoring at another site'); continue; } } } } $rs->close(); }
/** * Initialise a draft file area from a real one by copying the files. A draft * area will be created if one does not already exist. Normally you should * get $draftitemid by calling file_get_submitted_draft_itemid('elementname'); * * @category files * @global stdClass $CFG * @global stdClass $USER * @param int $draftitemid the id of the draft area to use, or 0 to create a new one, in which case this parameter is updated. * @param int $contextid This parameter and the next two identify the file area to copy files from. * @param string $component * @param string $filearea helps indentify the file area. * @param int $itemid helps identify the file area. Can be null if there are no files yet. * @param array $options text and file options ('subdirs'=>false, 'forcehttps'=>false) * @param string $text some html content that needs to have embedded links rewritten to point to the draft area. * @return string|null returns string if $text was passed in, the rewritten $text is returned. Otherwise NULL. */ function file_prepare_draft_area(&$draftitemid, $contextid, $component, $filearea, $itemid, array $options = null, $text = null) { global $CFG, $USER, $CFG; $options = (array) $options; if (!isset($options['subdirs'])) { $options['subdirs'] = false; } if (!isset($options['forcehttps'])) { $options['forcehttps'] = false; } $usercontext = context_user::instance($USER->id); $fs = get_file_storage(); if (empty($draftitemid)) { // create a new area and copy existing files into $draftitemid = file_get_unused_draft_itemid(); $file_record = array('contextid' => $usercontext->id, 'component' => 'user', 'filearea' => 'draft', 'itemid' => $draftitemid); if (!is_null($itemid) and $files = $fs->get_area_files($contextid, $component, $filearea, $itemid)) { foreach ($files as $file) { if ($file->is_directory() and $file->get_filepath() === '/') { // we need a way to mark the age of each draft area, // by not copying the root dir we force it to be created automatically with current timestamp continue; } if (!$options['subdirs'] and ($file->is_directory() or $file->get_filepath() !== '/')) { continue; } $draftfile = $fs->create_file_from_storedfile($file_record, $file); // XXX: This is a hack for file manager (MDL-28666) // 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)) { // at this point there should not be any draftfile links yet, // because this is a new text from database that should still contain the @@pluginfile@@ links // this happens when developers forget to post process the text $text = str_replace("\"{$CFG->httpswwwroot}/draftfile.php", "\"{$CFG->httpswwwroot}/brokenfile.php#", $text); } } else { // nothing to do } if (is_null($text)) { return null; } // relink embedded files - editor can not handle @@PLUGINFILE@@ ! return file_rewrite_pluginfile_urls($text, 'draftfile.php', $usercontext->id, 'user', 'draft', $draftitemid, $options); }
/** * Prepare file reference information * * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned) * @return string file reference, ready to be stored */ public function get_file_reference($source) { if ($source && $this->has_moodle_files()) { $params = @json_decode(base64_decode($source), true); if (!$params && !in_array($this->get_typename(), array('recent', 'user', 'local', 'coursefiles'))) { // IMPORTANT! Since default format for moodle files was changed in the minor release as a security fix // we maintain an old code here in order not to break 3rd party repositories that deal // with moodle files. Repositories are strongly encouraged to be upgraded, see MDL-45616. // In Moodle 2.8 this fallback will be removed. $params = file_storage::unpack_reference($source, true); return file_storage::pack_reference($params); } if (!is_array($params) || empty($params['contextid'])) { throw new repository_exception('invalidparams', 'repository'); } $params = array( 'component' => empty($params['component']) ? '' : clean_param($params['component'], PARAM_COMPONENT), 'filearea' => empty($params['filearea']) ? '' : clean_param($params['filearea'], PARAM_AREA), 'itemid' => empty($params['itemid']) ? 0 : clean_param($params['itemid'], PARAM_INT), 'filename' => empty($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE), 'filepath' => empty($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH), 'contextid' => clean_param($params['contextid'], PARAM_INT) ); // Check if context exists. if (!context::instance_by_id($params['contextid'], IGNORE_MISSING)) { throw new repository_exception('invalidparams', 'repository'); } return file_storage::pack_reference($params); } return $source; }
public function test_search_references() { $user = $this->setup_three_private_files(); $fs = get_file_storage(); $repos = repository::get_instances(array('type' => 'user')); $repo = reset($repos); $alias1 = array('contextid' => $user->ctxid, 'component' => 'user', 'filearea' => 'private', 'itemid' => 0, 'filepath' => '/aliases/', 'filename' => 'alias-to-1.txt'); $alias2 = array('contextid' => $user->ctxid, 'component' => 'user', 'filearea' => 'private', 'itemid' => 0, 'filepath' => '/aliases/', 'filename' => 'another-alias-to-1.txt'); $reference = file_storage::pack_reference(array('contextid' => $user->ctxid, 'component' => 'user', 'filearea' => 'private', 'itemid' => 0, 'filepath' => '/', 'filename' => '1.txt')); // There are no aliases now. $result = $fs->search_references($reference); $this->assertEquals(array(), $result); $result = $fs->search_references_count($reference); $this->assertSame($result, 0); // Create two aliases and make sure they are returned. $fs->create_file_from_reference($alias1, $repo->id, $reference); $fs->create_file_from_reference($alias2, $repo->id, $reference); $result = $fs->search_references($reference); $this->assertTrue(is_array($result)); $this->assertEquals(count($result), 2); foreach ($result as $alias) { $this->assertTrue($alias instanceof stored_file); } $result = $fs->search_references_count($reference); $this->assertSame($result, 2); // The method can't be used for references to files outside the filepool. $exceptionthrown = false; try { $fs->search_references('http://dl.dropbox.com/download/1234567/naked-dougiamas.jpg'); } catch (file_reference_exception $e) { $exceptionthrown = true; } $this->assertTrue($exceptionthrown); $exceptionthrown = false; try { $fs->search_references_count('http://dl.dropbox.com/download/1234567/naked-dougiamas.jpg'); } catch (file_reference_exception $e) { $exceptionthrown = true; } $this->assertTrue($exceptionthrown); }
/** * Prepare file reference information * * @param string $source * @return string file referece */ public function get_file_reference($source) { if ($this->has_moodle_files() && $this->supported_returntypes() & FILE_REFERENCE) { $params = file_storage::unpack_reference($source); if (!is_array($params)) { throw new repository_exception('invalidparams', 'repository'); } return file_storage::pack_reference($params); } return $source; }
/** * 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); }
/** * 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); }
/** * Determine if dir path exists or not in repository * * @param string $dirpath * @param stdclass $repository * @param boolean $encodepath * @param array $params * @return boolean true if dir path exists in repository, false otherwise */ function hotpot_pluginfile_dirpath_exists($dirpath, $repository, $encodepath, $params) { $dirs = explode('/', $dirpath); foreach ($dirs as $i => $dir) { $dirpath = implode('/', array_slice($dirs, 0, $i)); if ($encodepath) { $params['filepath'] = '/' . $dirpath . ($dirpath == '' ? '' : '/'); $params['filename'] = '.'; // "." signifies a directory $dirpath = file_storage::pack_reference($params); } $exists = false; $listing = $repository->get_listing($dirpath); foreach ($listing['list'] as $file) { if (empty($file['source'])) { if ($file['title'] == $dir) { $exists = true; break; } } } if (!$exists) { return false; } } // all dirs in path exist - success !! return true; }
function xmldb_hotpot_locate_externalfile($contextid, $component, $filearea, $itemid, $filepath, $filename) { global $CFG, $DB; if (!class_exists('repository')) { return false; // Moodle <= 2.2 has no repositories } static $repositories = null; if ($repositories === null) { $exclude_types = array('recent', 'upload', 'user', 'areafiles'); $repositories = repository::get_instances(); foreach (array_keys($repositories) as $id) { if (method_exists($repositories[$id], 'get_typename')) { $type = $repositories[$id]->get_typename(); } else { $type = $repositories[$id]->options['type']; } if (in_array($type, $exclude_types)) { unset($repositories[$id]); } } // ensure upgraderunning is set if (empty($CFG->upgraderunning)) { $CFG->upgraderunning = null; } } // get file storage $fs = get_file_storage(); // the following types repository use encoded params $encoded_types = array('user', 'areafiles', 'coursefiles'); foreach ($repositories as $id => $repository) { // "filesystem" path is in plain text, others are encoded if (method_exists($repositories[$id], 'get_typename')) { $type = $repositories[$id]->get_typename(); } else { $type = $repositories[$id]->options['type']; } $encodepath = in_array($type, $encoded_types); // save $root_path, because it may get messed up by // $repository->get_listing($path), if $path is non-existant if (method_exists($repository, 'get_rootpath')) { $root_path = $repository->get_rootpath(); } else { if (isset($repository->root_path)) { $root_path = $repository->root_path; } else { $root_path = false; } } // get repository type switch (true) { case isset($repository->options['type']): $type = $repository->options['type']; break; case isset($repository->instance->typeid): $type = repository::get_type_by_id($repository->instance->typeid); $type = $type->get_typename(); break; default: $type = ''; // shouldn't happen !! } $path = $filepath; $source = trim($filepath . $filename, '/'); // setup $params for path encoding, if necessary $params = array(); if ($encodepath) { $listing = $repository->get_listing(); switch (true) { case isset($listing['list'][0]['source']): $param = 'source'; break; // file // file case isset($listing['list'][0]['path']): $param = 'path'; break; // dir // dir default: return false; // shouldn't happen !! } $params = file_storage::unpack_reference($listing['list'][0][$param], true); $params['filepath'] = '/' . $path . ($path == '' ? '' : '/'); $params['filename'] = '.'; // "." signifies a directory $path = file_storage::pack_reference($params); } // reset $repository->root_path (filesystem repository only) if ($root_path) { $repository->root_path = $root_path; } // unset upgraderunning because it can cause get_listing() to fail $upgraderunning = $CFG->upgraderunning; $CFG->upgraderunning = null; // Note: we use "@" to suppress warnings in case $path does not exist $listing = @$repository->get_listing($path); // restore upgraderunning flag $CFG->upgraderunning = $upgraderunning; // check each file to see if it is the one we want foreach ($listing['list'] as $file) { switch (true) { case isset($file['source']): $param = 'source'; break; // file // file case isset($file['path']): $param = 'path'; break; // dir // dir default: continue; // shouldn't happen !! } if ($encodepath) { $file[$param] = file_storage::unpack_reference($file[$param]); $file[$param] = trim($file[$param]['filepath'], '/') . '/' . $file[$param]['filename']; } if ($file[$param] == $source) { if ($encodepath) { $params['filename'] = $filename; $source = file_storage::pack_reference($params); } $file_record = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'sortorder' => 0, 'itemid' => 0, 'filepath' => $filepath, 'filename' => $filename); if ($file = $fs->create_file_from_reference($file_record, $id, $source)) { return $file; } break; // try another repository } } } // external file not found (or found but not created) return false; }
/** * Gets main file in a file area * * if the main file is a link from an external repository * look for the target file in the main file's repository * Note: this functionality only exists in Moodle 2.3+ * * @param stdclass $context * @param string $component 'mod_hotpot' * @param string $filearea 'sourcefile', 'entrytext' or 'exittext' * @param string $filepath despite the name, this is a dir path with leading and trailing "/" * @param string $filename * @return stdclass if external file found, false otherwise */ function hotpot_pluginfile_externalfile($context, $component, $filearea, $filepath, $filename) { // get file storage $fs = get_file_storage(); // get main file for this $component/$filearea // typically this will be the HotPot quiz file $mainfile = hotpot_pluginfile_mainfile($context, $component, $filearea); // get repository - cautiously :-) if (!$mainfile) { return false; // no main file - shouldn't happen !! } if (!method_exists($mainfile, 'get_repository_id')) { return false; // no file linking in Moodle 2.0 - 2.2 } if (!($repositoryid = $mainfile->get_repository_id())) { return false; // $mainfile is not from an external repository } if (!($repository = repository::get_repository_by_id($repositoryid, $context))) { return false; // $repository is not accessible in this context - shouldn't happen !! } // get repository type switch (true) { case isset($repository->options['type']): $type = $repository->options['type']; break; case isset($repository->instance->typeid): $type = repository::get_type_by_id($repository->instance->typeid); $type = $type->get_typename(); break; default: $type = ''; // shouldn't happen !! } // set paths (within repository) to required file // how we do this depends on the repository $typename // "filesystem" path is in plain text, others are encoded $mainreference = $mainfile->get_reference(); switch ($type) { case 'filesystem': $maindirname = dirname($mainreference); $encodepath = false; break; case 'coursefiles': case 'user': $params = file_storage::unpack_reference($mainreference, true); $maindirname = $params['filepath']; $encodepath = true; break; default: echo 'unknown repository type in hotpot_pluginfile_externalfile(): ' . $type; die; } // remove leading and trailing "/" from dir names $maindirname = trim($maindirname, '/'); $dirname = trim($filepath, '/'); // assume path to target dir is same as path to main dir $path = explode('/', $maindirname); // traverse back up folder hierarchy if necessary $count = count(explode('/', $dirname)); array_splice($path, -$count); // reconstruct expected dir path for source file if ($dirname) { $path[] = $dirname; } $source = $path; $source[] = $filename; $source = implode('/', $source); $path = implode('/', $path); // filepaths in the repository to search for the file $paths = array(); // add to the list of possible paths $paths[$path] = $source; if ($dirname) { $paths[$dirname] = $dirname . '/' . $filename; } if ($maindirname) { $paths[$maindirname] = $maindirname . '/' . $filename; } if ($maindirname && $dirname) { $paths[$maindirname . '/' . $dirname] = $maindirname . '/' . $dirname . '/' . $filename; $paths[$dirname . '/' . $maindirname] = $dirname . '/' . $maindirname . '/' . $filename; } // add leading and trailing "/" to dir names $dirname = $dirname == '' ? '/' : '/' . $dirname . '/'; $maindirname = $maindirname == '' ? '/' : '/' . $maindirname . '/'; // locate $dirname within $maindirname // typically it will be absent or occur just once, // but it could possibly occur several times $search = '/' . preg_quote($dirname, '/') . '/i'; if (preg_match_all($search, $maindirname, $matches, PREG_OFFSET_CAPTURE)) { $i_max = count($matches[0]); for ($i = 0; $i < $i_max; $i++) { list($match, $start) = $matches[0][$i]; $path = substr($maindirname, 0, $start) . $match; $path = trim($path, '/'); // e.g. hp6.2/html_files $paths[$path] = $path . '/' . $filename; } } // setup $params for path encoding, if necessary $params = array(); if ($encodepath) { $listing = $repository->get_listing(); switch (true) { case isset($listing['list'][0]['source']): $param = 'source'; break; // file // file case isset($listing['list'][0]['path']): $param = 'path'; break; // dir // dir default: return false; // shouldn't happen !! } $params = $listing['list'][0][$param]; $params = json_decode(base64_decode($params), true); } foreach ($paths as $path => $source) { if (!hotpot_pluginfile_dirpath_exists($path, $repository, $type, $encodepath, $params)) { continue; } if ($encodepath) { $params['filepath'] = '/' . $path . ($path == '' ? '' : '/'); $params['filename'] = '.'; // "." signifies a directory $path = base64_encode(json_encode($params)); } $listing = $repository->get_listing($path); foreach ($listing['list'] as $file) { switch (true) { case isset($file['source']): $param = 'source'; break; // file // file case isset($file['path']): $param = 'path'; break; // dir // dir default: continue; // shouldn't happen !! } if ($encodepath) { $file[$param] = json_decode(base64_decode($file[$param]), true); $file[$param] = trim($file[$param]['filepath'], '/') . '/' . $file[$param]['filename']; } if ($file[$param] == $source) { if ($encodepath) { $params['filename'] = $filename; $source = file_storage::pack_reference($params); } $file_record = array('contextid' => $context->id, 'component' => $component, 'filearea' => $filearea, 'sortorder' => 0, 'itemid' => 0, 'filepath' => $filepath, 'filename' => $filename); if ($file = $fs->create_file_from_reference($file_record, $repositoryid, $source)) { return $file; } break; // couldn't create file, so give up and try a different $path } } } // external file not found (or found but not created) return false; }