<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Helper functions for asynchronous backups and restores.
*
* @package core
* @copyright 2019 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/user/lib.php');
/**
* Helper functions for asynchronous backups and restores.
*
* @package core
* @copyright 2019 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class async_helper {
/**
* @var string $type The type of async operation.
*/
protected $type = 'backup';
/**
* @var string $backupid The id of the backup or restore.
*/
protected $backupid;
/**
* @var object $user The user who created the backup record.
*/
protected $user;
/**
* @var object $backuprec The backup controller record from the database.
*/
protected $backuprec;
/**
* Class constructor.
*
* @param string $type The type of async operation.
* @param string $id The id of the backup or restore.
*/
public function __construct($type, $id) {
$this->type = $type;
$this->backupid = $id;
$this->backuprec = self::get_backup_record($id);
$this->user = $this->get_user();
}
/**
* Given a backup id return a the record from the database.
* We use this method rather than 'load_controller' as the controller may
* not exist if this backup/restore has completed.
*
* @param int $id The backup id to get.
* @return object $backuprec The backup controller record.
*/
static public function get_backup_record($id) {
global $DB;
$backuprec = $DB->get_record('backup_controllers', array('backupid' => $id), '*', MUST_EXIST);
return $backuprec;
}
/**
* Given a user id return a user object.
*
* @return object $user The limited user record.
*/
private function get_user() {
$userid = $this->backuprec->userid;
$user = core_user::get_user($userid, '*', MUST_EXIST);
return $user;
}
/**
* Return appropriate description for current async operation {@see async_helper::type}
*
* @return string
*/
private function get_operation_description(): string {
$operations = [
'backup' => new lang_string('backup'),
'copy' => new lang_string('copycourse'),
'restore' => new lang_string('restore'),
];
return (string) ($operations[$this->type] ?? $this->type);
}
/**
* Callback for preg_replace_callback.
* Replaces message placeholders with real values.
*
* @param array $matches The match array from from preg_replace_callback.
* @return string $match The replaced string.
*/
private function lookup_message_variables($matches) {
$options = array(
'operation' => $this->get_operation_description(),
'backupid' => $this->backupid,
'user_username' => $this->user->username,
'user_email' => $this->user->email,
'user_firstname' => $this->user->firstname,
'user_lastname' => $this->user->lastname,
'link' => $this->get_resource_link(),
);
$match = $options[$matches[1]] ?? $matches[1];
return $match;
}
/**
* Get the link to the resource that is being backuped or restored.
*
* @return moodle_url $url The link to the resource.
*/
private function get_resource_link() {
// Get activity context only for backups.
if ($this->backuprec->type == 'activity' && $this->type == 'backup') {
$context = context_module::instance($this->backuprec->itemid);
} else { // Course or Section which have the same context getter.
$context = context_course::instance($this->backuprec->itemid);
}
// Generate link based on operation type.
if ($this->type == 'backup') {
// For backups simply generate link to restore file area UI.
$url = new moodle_url('/backup/restorefile.php', array('contextid' => $context->id));
} else {
// For restore generate link to the item itself.
$url = $context->get_url();
}
return $url;
}
/**
* Sends a confirmation message for an aynchronous process.
*
* @return int $messageid The id of the sent message.
*/
public function send_message() {
global $USER;
$subjectraw = get_config('backup', 'backup_async_message_subject');
$subjecttext = preg_replace_callback(
'/\{([-_A-Za-z0-9]+)\}/u',
array('async_helper', 'lookup_message_variables'),
$subjectraw);
$messageraw = get_config('backup', 'backup_async_message');
$messagehtml = preg_replace_callback(
'/\{([-_A-Za-z0-9]+)\}/u',
array('async_helper', 'lookup_message_variables'),
$messageraw);
$messagetext = html_to_text($messagehtml);
$message = new \core\message\message();
$message->component = 'moodle';
$message->name = 'asyncbackupnotification';
$message->userfrom = $USER;
$message->userto = $this->user;
$message->subject = $subjecttext;
$message->fullmessage = $messagetext;
$message->fullmessageformat = FORMAT_HTML;
$message->fullmessagehtml = $messagehtml;
$message->smallmessage = '';
$message->notification = '1';
$messageid = message_send($message);
return $messageid;
}
/**
* Check if asynchronous backup and restore mode is
* enabled at system level.
*
* @return bool $async True if async mode enabled false otherwise.
*/
static public function is_async_enabled() {
global $CFG;
$async = false;
if (!empty($CFG->enableasyncbackup)) {
$async = true;
}
return $async;
}
/**
* Check if there is a pending async operation for given details.
*
* @param int $id The item id to check in the backup record.
* @param string $type The type of operation: course, activity or section.
* @param string $operation Operation backup or restore.
* @return boolean $asyncpedning Is there a pending async operation.
*/
public static function is_async_pending($id, $type, $operation) {
global $DB, $USER, $CFG;
$asyncpending = false;
// Only check for pending async operations if async mode is enabled.
require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
require_once($CFG->dirroot . '/backup/backup.class.php');
$select = 'userid = ? AND itemid = ? AND type = ? AND operation = ? AND execution = ? AND status < ? AND status > ?';
$params = array(
$USER->id,
$id,
$type,
$operation,
backup::EXECUTION_DELAYED,
backup::STATUS_FINISHED_ERR,
backup::STATUS_NEED_PRECHECK
);
$asyncrecord= $DB->get_record_select('backup_controllers', $select, $params);
if ((self::is_async_enabled() && $asyncrecord) || ($asyncrecord && $asyncrecord->purpose == backup::MODE_COPY)) {
$asyncpending = true;
}
return $asyncpending;
}
/**
* Get the size, url and restore url for a backup file.
*
* @param string $filename The name of the file to get info for.
* @param string $filearea The file area for the file.
* @param int $contextid The context ID of the file.
* @return array $results The result array containing the size, url and restore url of the file.
*/
public static function get_backup_file_info($filename, $filearea, $contextid) {
$fs = get_file_storage();
$file = $fs->get_file($contextid, 'backup', $filearea, 0, '/', $filename);
$filesize = display_size ($file->get_filesize());
$fileurl = moodle_url::make_pluginfile_url(
$file->get_contextid(),
$file->get_component(),
$file->get_filearea(),
null,
$file->get_filepath(),
$file->get_filename(),
true
);
$params = array();
$params['action'] = 'choosebackupfile';
$params['filename'] = $file->get_filename();
$params['filepath'] = $file->get_filepath();
$params['component'] = $file->get_component();
$params['filearea'] = $file->get_filearea();
$params['filecontextid'] = $file->get_contextid();
$params['contextid'] = $contextid;
$params['itemid'] = $file->get_itemid();
$restoreurl = new moodle_url('/backup/restorefile.php', $params);
$filesize = display_size ($file->get_filesize());
$results = array(
'filesize' => $filesize,
'fileurl' => $fileurl->out(false),
'restoreurl' => $restoreurl->out(false));
return $results;
}
/**
* Get the url of a restored backup item based on the backup ID.
*
* @param string $backupid The backup ID to get the restore location url.
* @return array $urlarray The restored item URL as an array.
*/
public static function get_restore_url($backupid) {
global $DB;
$backupitemid = $DB->get_field('backup_controllers', 'itemid', array('backupid' => $backupid), MUST_EXIST);
$newcontext = context_course::instance($backupitemid);
$restoreurl = $newcontext->get_url()->out();
$urlarray = array('restoreurl' => $restoreurl);
return $urlarray;
}
/**
* 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',
];
> $sort = 'timecreated DESC';
> $fields = 'id, backupid, status, timecreated';
$backups = $DB->get_records_select('backup_controllers', $select, $params, 'timecreated DESC', 'id, backupid, timecreated');
>
foreach ($backups as $backup) {
> if ($filearea == 'backup') {
$bc = \backup_controller::load_controller($backup->backupid); // Get the backup controller.
> // Get relevant backup ids based on user id.
$filename = $bc->get_plan()->get_setting('filename')->get_value();
> $params['userid'] = $instanceid;
$timecreated = $backup->timecreated;
> $select = 'userid = :userid AND ' . $select;
$status = $renderer->get_status_display($bc->get_status(), $bc->get_backupid());
> $records = $DB->get_records_select($table, $select, $params, $sort, $fields);
$bc->destroy();
> foreach ($records as $record) {
> $bc = \backup_controller::load_controller($record->backupid);
$tablerow = array($filename, userdate($timecreated), '-', '-', '-', $status);
>
$tabledata[] = $tablerow;
> // 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.
return $tabledata;
> if ($hasusers && !$isannon) {
}
> continue;
> }
/**
>
* Get the course name of the resource being restored.
> $record->filename = $bc->get_plan()->get_setting('filename')->get_value();
*
> $bc->destroy();
* @param \context $context The Moodle context for the restores.
> array_push($backups, $record);
* @return string $coursename The full name of the course.
> }
*/
> } else {
public static function get_restore_name(\context $context) {
> if ($filearea == 'course' || $filearea == 'activity') {
global $DB;
> // Get relevant backup ids based on context instance id.
$instanceid = $context->instanceid;
> $params['itemid'] = $instanceid;
> $select = 'itemid = :itemid AND ' . $select;
if ($context->contextlevel == CONTEXT_MODULE) {
> $records = $DB->get_records_select($table, $select, $params, $sort, $fields);
// For modules get the course name and module name.
> foreach ($records as $record) {
$cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
> $bc = \backup_controller::load_controller($record->backupid);
$coursename = $DB->get_field('course', 'fullname', array('id' => $cm->course));
>
$itemname = $coursename . ' - ' . $cm->name;
> // Get useful info to render async status in correct area.
} else {
> list($hasusers, $isannon) = self::get_userdata_backup_settings($bc);
$itemname = $DB->get_field('course', 'fullname', array('id' => $context->instanceid));
> // Backup has no user or is anonymised -> don't show it in course/activity backup file area.
> if (!$hasusers || $isannon) {
}
> continue;
> }
< $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());
> $record->filename = $bc->get_plan()->get_setting('filename')->get_value();
* @param int $userid Moodle user id.
> array_push($backups, $record);
* @return array $restores List of current restores in progress.
> }
*/
> }
public static function get_async_restores($userid) {
> }
< $tablerow = array($filename, userdate($timecreated), '-', '-', '-', $status);
< $tabledata[] = $tablerow;
> return $backups;
< return $tabledata;
> /**
> * 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];
$params = array($userid, backup::EXECUTION_DELAYED, backup::STATUS_FINISHED_ERR, backup::STATUS_NEED_PRECHECK, 'restore');
$restores = $DB->get_records_select(
'backup_controllers',
$select,
$params,
'timecreated DESC',
'id, backupid, status, itemid, timecreated');
return $restores;
}
}