Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
<?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; } }