mirror of
https://github.com/moodle/moodle.git
synced 2025-01-19 06:18:28 +01:00
MDL-33430 Add process_file_aliases_queue restore step to the final task
This execution step tries to actually restore all aliases that were stashed in the file aliases queue.
This commit is contained in:
parent
83b2cc562d
commit
2c1426743a
@ -83,9 +83,15 @@ class restore_final_task extends restore_task {
|
||||
// executing it to perform some final adjustments of information
|
||||
// not available when the task was executed.
|
||||
// This step is always the last one performing modifications on restored information
|
||||
// Don't add any new step after it. Only cache rebuild and clean are allowed.
|
||||
// Don't add any new step after it. Only aliases queue, cache rebuild and clean are allowed.
|
||||
$this->add_step(new restore_execute_after_restore('executing_after_restore'));
|
||||
|
||||
// All files were sent to the filepool by now. We need to process
|
||||
// the aliases yet as they were not actually created but stashed for us instead.
|
||||
// We execute this step after executing_after_restore so that there can't be no
|
||||
// more files sent to the filepool after this.
|
||||
$this->add_step(new restore_process_file_aliases_queue('process_file_aliases_queue'));
|
||||
|
||||
// Rebuild course cache to see results, whoah!
|
||||
$this->add_step(new restore_rebuild_course_cache('rebuild_course_cache'));
|
||||
|
||||
|
@ -3093,6 +3093,281 @@ class restore_create_question_files extends restore_execution_step {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to restore aliases and references to external files.
|
||||
*
|
||||
* The queue of these files was prepared for us in {@link restore_dbops::send_files_to_pool()}.
|
||||
* We expect that all regular (non-alias) files have already been restored. Make sure
|
||||
* there is no restore step executed after this one that would call send_files_to_pool() again.
|
||||
*
|
||||
* You may notice we have hardcoded support for Server files, Legacy course files
|
||||
* and user Private files here at the moment. This could be eventually replaced with a set of
|
||||
* callbacks in the future if needed.
|
||||
*
|
||||
* @copyright 2012 David Mudrak <david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class restore_process_file_aliases_queue extends restore_execution_step {
|
||||
|
||||
/**
|
||||
* What to do when this step is executed.
|
||||
*/
|
||||
protected function define_execution() {
|
||||
global $DB;
|
||||
|
||||
$this->log('processing file aliases queue', backup::LOG_INFO);
|
||||
|
||||
$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 = unserialize(base64_decode($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 = unserialize(base64_decode($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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the repository instance that should handle the alias.
|
||||
*
|
||||
* At the same site, we can rely on repository instance id and we just
|
||||
* check it still exists. On other site, try to find matching Server files or
|
||||
* Legacy course files repository instance. Return null if no matching
|
||||
* repository instance can be found.
|
||||
*
|
||||
* @param stdClass $info
|
||||
* @return repository|null
|
||||
*/
|
||||
private function choose_repository(stdClass $info) {
|
||||
global $DB, $CFG;
|
||||
require_once($CFG->dirroot.'/repository/lib.php');
|
||||
|
||||
if ($this->task->is_samesite()) {
|
||||
// We can rely on repository instance id.
|
||||
try {
|
||||
return repository::get_repository_by_id($info->oldfile->repositoryid, SYSCONTEXTID);
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We can rely on repository type only.
|
||||
if (empty($info->oldfile->repositorytype)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Both Server files and Legacy course files repositories have a single
|
||||
// instance at the system context to use. Let us try to find it.
|
||||
if ($info->oldfile->repositorytype === 'local' or $info->oldfile->repositorytype === 'coursefiles') {
|
||||
$sql = "SELECT ri.id
|
||||
FROM {repository} r
|
||||
JOIN {repository_instances} ri ON ri.typeid = r.id
|
||||
WHERE r.type = ? AND ri.contextid = ?";
|
||||
$ris = $DB->get_records_sql($sql, array($info->oldfile->repositorytype, SYSCONTEXTID));
|
||||
if (empty($ris)) {
|
||||
return null;
|
||||
}
|
||||
$repoid = reset(array_keys($ris));
|
||||
try {
|
||||
return repository::get_repository_by_id($repoid, SYSCONTEXTID);
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the user know that the given alias was successfully restored
|
||||
*
|
||||
* @param stdClass $info
|
||||
*/
|
||||
private function notify_success(stdClass $info) {
|
||||
$filedesc = $this->describe_alias($info);
|
||||
$this->log('successfully restored alias', backup::LOG_DEBUG, $filedesc, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the user know that the given alias can't be restored
|
||||
*
|
||||
* @param stdClass $info
|
||||
* @param string $reason detailed reason to be logged
|
||||
*/
|
||||
private function notify_failure(stdClass $info, $reason = '') {
|
||||
$filedesc = $this->describe_alias($info);
|
||||
if ($reason) {
|
||||
$reason = ' ('.$reason.')';
|
||||
}
|
||||
$this->log('unable to restore alias'.$reason, backup::LOG_WARNING, $filedesc, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a human readable description of the alias file
|
||||
*
|
||||
* @param stdClass $info
|
||||
* @return string
|
||||
*/
|
||||
private function describe_alias(stdClass $info) {
|
||||
|
||||
$filedesc = $this->expected_alias_location($info->newfile);
|
||||
|
||||
if (!is_null($info->oldfile->source)) {
|
||||
$filedesc .= ' ('.$info->oldfile->source.')';
|
||||
}
|
||||
|
||||
return $filedesc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the expected location of a file
|
||||
*
|
||||
* Please note this may and may not work as a part of URL to pluginfile.php
|
||||
* (depends on how the given component/filearea deals with the itemid).
|
||||
*
|
||||
* @param stdClass $filerecord
|
||||
* @return string
|
||||
*/
|
||||
private function expected_alias_location($filerecord) {
|
||||
|
||||
$filedesc = '/'.$filerecord->contextid.'/'.$filerecord->component.'/'.$filerecord->filearea;
|
||||
if (!is_null($filerecord->itemid)) {
|
||||
$filedesc .= '/'.$filerecord->itemid;
|
||||
}
|
||||
$filedesc .= $filerecord->filepath.$filerecord->filename;
|
||||
|
||||
return $filedesc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Abstract structure step, to be used by all the activities using core questions stuff
|
||||
* (like the quiz module), to support qtype plugins, states and sessions
|
||||
|
Loading…
x
Reference in New Issue
Block a user