Skip to content

Commit

Permalink
MDL-69983 backup: Save async backups in correct area.
Browse files Browse the repository at this point in the history
  • Loading branch information
Franziska Hübler authored and davewoloszyn committed Jun 30, 2023
1 parent b509652 commit 1da8cc1
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 41 deletions.
23 changes: 17 additions & 6 deletions backup/externallib.php
Expand Up @@ -132,6 +132,7 @@ public static function get_async_backup_links_backup_parameters() {
array(
'filename' => new external_value(PARAM_FILE, 'Backup filename', VALUE_REQUIRED, null, NULL_NOT_ALLOWED),
'contextid' => new external_value(PARAM_INT, 'Context id', VALUE_REQUIRED, null, NULL_NOT_ALLOWED),
'backupid' => new external_value(PARAM_ALPHANUMEXT, 'Backup id', VALUE_REQUIRED, null, NULL_NOT_ALLOWED),
)
);
}
Expand All @@ -142,9 +143,10 @@ public static function get_async_backup_links_backup_parameters() {
*
* @param string $filename The file name of the backup file.
* @param int $contextid The context the backup relates to.
* @param string $backupid The backup ID to get the backup settings.
* @since Moodle 3.7
*/
public static function get_async_backup_links_backup($filename, $contextid) {
public static function get_async_backup_links_backup($filename, $contextid, $backupid) {
// Release session lock.
\core\session\manager::write_close();

Expand All @@ -153,7 +155,8 @@ public static function get_async_backup_links_backup($filename, $contextid) {
self::get_async_backup_links_backup_parameters(),
array(
'filename' => $filename,
'contextid' => $contextid
'contextid' => $contextid,
'backupid' => $backupid,
)
);

Expand All @@ -162,10 +165,18 @@ public static function get_async_backup_links_backup($filename, $contextid) {
self::validate_context($context);
require_capability('moodle/backup:backupcourse', $context);

if ($cm) {
$filearea = 'activity';
} else {
$filearea = 'course';
// Backups without user info or with the anonymise functionality enabled are sent
// to user's "user_backup" file area.
$filearea = 'backup';
// Get useful info to render async status in correct area.
$bc = \backup_controller::load_controller($backupid);
list($hasusers, $isannon) = \async_helper::get_userdata_backup_settings($bc);
if ($hasusers && !$isannon) {
if ($cm) {
$filearea = 'activity';
} else {
$filearea = 'course';
}
}

$results = \async_helper::get_backup_file_info($filename, $filearea, $contextid);
Expand Down
48 changes: 47 additions & 1 deletion backup/tests/async_backup_test.php
Expand Up @@ -87,7 +87,7 @@ public function test_async_backup() {
set_config('buffersize', 0, 'logstore_standard');
get_log_manager(true);

// Start backup process.
// Case 1: Make a course backup without users.
$this->setUser($teacher->id);

// Make the backup controller for an async backup.
Expand Down Expand Up @@ -132,6 +132,52 @@ public function test_async_backup() {
'target' => 'course_backup'], '*', MUST_EXIST);
$otherdata = json_decode($logrec->other);
$this->assertEquals($backupid, $otherdata->backupid);

// Check backup was stored in correct area.
$usercontextid = $DB->get_field('context', 'id', ['contextlevel' => CONTEXT_USER, 'instanceid' => $teacher->id]);
$this->assertEquals(1, $DB->count_records('files', ['contextid' => $usercontextid,
'component' => 'user', 'filearea' => 'backup', 'filename' => 'backup.mbz']));

// Case 2: Make a second backup with users and not anonymised.
$this->setAdminUser();
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_ASYNC, $USER->id);
$bc->get_plan()->get_setting('users')->set_status(\backup_setting::NOT_LOCKED);
$bc->get_plan()->get_setting('users')->set_value(true);
$bc->get_plan()->get_setting('anonymize')->set_value(false);
$bc->finish_ui();
$backupid = $bc->get_backupid();
$bc->destroy();

// Create the adhoc task.
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_blocking(false);
$asynctask->set_custom_data(['backupid' => $backupid]);
\core\task\manager::queue_adhoc_task($asynctask);

// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$task->execute();
\core\task\manager::adhoc_task_complete($task);

$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);

// Check backup was created successfully.
$this->assertEquals(backup::STATUS_FINISHED_OK, $postbackuprec->status);
$this->assertEquals(1.0, $postbackuprec->progress);
$this->assertEquals($USER->id, $postbackuprec->userid);

// Check that the backupid was logged correctly.
$logrec = $DB->get_record('logstore_standard_log', ['userid' => $postbackuprec->userid,
'target' => 'course_backup'], '*', MUST_EXIST);
$otherdata = json_decode($logrec->other);
$this->assertEquals($backupid, $otherdata->backupid);

// Check backup was stored in correct area.
$coursecontextid = $DB->get_field('context', 'id', ['contextlevel' => CONTEXT_COURSE, 'instanceid' => $course->id]);
$this->assertEquals(1, $DB->count_records('files', ['contextid' => $coursecontextid,
'component' => 'backup', 'filearea' => 'course', 'filename' => 'backup.mbz']));
}

/**
Expand Down
79 changes: 61 additions & 18 deletions backup/util/helper/async_helper.class.php
Expand Up @@ -313,39 +313,82 @@ public static function get_restore_url($backupid) {
* Get markup for in progress async backups,
* to use in backup table UI.
*
* @param \core_backup_renderer $renderer The backup renderer object.
* @param string $filearea The filearea to get backup data for.
* @param integer $instanceid The context id to get backup data for.
* @return array $tabledata the rows of table data.
*/
public static function get_async_backups($renderer, $instanceid) {
public static function get_async_backups($filearea, $instanceid) {
global $DB;

$tabledata = array();
$backups = [];

// Get relevant backup ids based on context instance id.
$select = 'itemid = :itemid AND execution = :execution AND status < :status1 AND status > :status2 ' .
$table = 'backup_controllers';
$select = 'execution = :execution AND status < :status1 AND status > :status2 ' .
'AND operation = :operation';
$params = [
'itemid' => $instanceid,
'execution' => backup::EXECUTION_DELAYED,
'status1' => backup::STATUS_FINISHED_ERR,
'status2' => backup::STATUS_NEED_PRECHECK,
'operation' => 'backup',
];

$backups = $DB->get_records_select('backup_controllers', $select, $params, 'timecreated DESC', 'id, backupid, timecreated');
foreach ($backups as $backup) {
$bc = \backup_controller::load_controller($backup->backupid); // Get the backup controller.
$filename = $bc->get_plan()->get_setting('filename')->get_value();
$timecreated = $backup->timecreated;
$status = $renderer->get_status_display($bc->get_status(), $bc->get_backupid());
$bc->destroy();

$tablerow = array($filename, userdate($timecreated), '-', '-', '-', $status);
$tabledata[] = $tablerow;
$sort = 'timecreated DESC';
$fields = 'id, backupid, status, timecreated';

if ($filearea == 'backup') {
// Get relevant backup ids based on user id.
$params['userid'] = $instanceid;
$select = 'userid = :userid AND ' . $select;
$records = $DB->get_records_select($table, $select, $params, $sort, $fields);
foreach ($records as $record) {
$bc = \backup_controller::load_controller($record->backupid);

// Get useful info to render async status in correct area.
list($hasusers, $isannon) = self::get_userdata_backup_settings($bc);
// Backup has users and is not anonymised -> don't show it in users backup file area.
if ($hasusers && !$isannon) {
continue;
}

$record->filename = $bc->get_plan()->get_setting('filename')->get_value();
$bc->destroy();
array_push($backups, $record);
}
} else {
if ($filearea == 'course' || $filearea == 'activity') {
// Get relevant backup ids based on context instance id.
$params['itemid'] = $instanceid;
$select = 'itemid = :itemid AND ' . $select;
$records = $DB->get_records_select($table, $select, $params, $sort, $fields);
foreach ($records as $record) {
$bc = \backup_controller::load_controller($record->backupid);

// Get useful info to render async status in correct area.
list($hasusers, $isannon) = self::get_userdata_backup_settings($bc);
// Backup has no user or is anonymised -> don't show it in course/activity backup file area.
if (!$hasusers || $isannon) {
continue;
}

$record->filename = $bc->get_plan()->get_setting('filename')->get_value();
$bc->destroy();
array_push($backups, $record);
}
}
}

return $tabledata;
return $backups;
}

/**
* Get the user data settings for backups.
*
* @param \backup_controller $backupcontroller The backup controller object.
* @return array Array of user data settings.
*/
public static function get_userdata_backup_settings(\backup_controller $backupcontroller): array {
$hasusers = (bool)$backupcontroller->get_plan()->get_setting('users')->get_value(); // Backup has users.
$isannon = (bool)$backupcontroller->get_plan()->get_setting('anonymize')->get_value(); // Backup is anonymised.
return [$hasusers, $isannon];
}

/**
Expand Down
2 changes: 1 addition & 1 deletion backup/util/helper/backup_helper.class.php
Expand Up @@ -331,7 +331,7 @@ static public function store_backup_file($backupid, $filepath, \core\progress\ba
// enabled are sent to user's "user_backup"
// file area. Maintenance of such area is responsibility of
// the user via corresponding file manager frontend
if ($backupmode == backup::MODE_GENERAL && (!$hasusers || $isannon)) {
if (($backupmode == backup::MODE_GENERAL || $backupmode == backup::MODE_ASYNC) && (!$hasusers || $isannon)) {
$ctxid = context_user::instance($userid)->id;
$component = 'user';
$filearea = 'backup';
Expand Down
5 changes: 2 additions & 3 deletions backup/util/helper/tests/async_helper_test.php
Expand Up @@ -140,12 +140,11 @@ public function test_get_async_backups() {
unset($bc);

$coursecontext = \context_course::instance($course->id);
$renderer = $PAGE->get_renderer('core', 'backup');

$result = \async_helper::get_async_backups($renderer, $coursecontext->instanceid);
$result = \async_helper::get_async_backups('course', $coursecontext->instanceid);

$this->assertEquals(1, count($result));
$this->assertEquals('backup.mbz', $result[0][0]);
$this->assertEquals('backup.mbz', $result[0]->filename);
}

/**
Expand Down

0 comments on commit 1da8cc1

Please sign in to comment.