01436f7539
This patch adds better core support for copying courses. There is now a simplified and dedicated UI for copying courses. This can be accessed from the course context menu or course management screens. All backups are done asynchronously and there can be multiple copies of a course in flight at once.
382 lines
13 KiB
PHP
382 lines
13 KiB
PHP
<?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;
|
|
}
|
|
|
|
/**
|
|
* 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->type,
|
|
'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 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) {
|
|
global $DB;
|
|
|
|
$tabledata = array();
|
|
|
|
// Get relevant backup ids based on context instance id.
|
|
$select = 'itemid = :itemid AND 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;
|
|
}
|
|
|
|
return $tabledata;
|
|
}
|
|
|
|
/**
|
|
* Get the course name of the resource being restored.
|
|
*
|
|
* @param \context $context The Moodle context for the restores.
|
|
* @return string $coursename The full name of the course.
|
|
*/
|
|
public static function get_restore_name(\context $context) {
|
|
global $DB;
|
|
$instanceid = $context->instanceid;
|
|
|
|
if ($context->contextlevel == CONTEXT_MODULE) {
|
|
// For modules get the course name and module name.
|
|
$cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
|
|
$coursename = $DB->get_field('course', 'fullname', array('id' => $cm->course));
|
|
$itemname = $coursename . ' - ' . $cm->name;
|
|
} else {
|
|
$itemname = $DB->get_field('course', 'fullname', array('id' => $context->instanceid));
|
|
|
|
}
|
|
|
|
return $itemname;
|
|
}
|
|
|
|
/**
|
|
* Get all the current in progress async restores for a user.
|
|
*
|
|
* @param int $userid Moodle user id.
|
|
* @return array $restores List of current restores in progress.
|
|
*/
|
|
public static function get_async_restores($userid) {
|
|
global $DB;
|
|
|
|
$select = 'userid = ? AND execution = ? AND status < ? AND status > ? AND operation = ?';
|
|
$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;
|
|
}
|
|
|
|
}
|
|
|