Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Helper functions for asynchronous backups and restores.
  19   *
  20   * @package    core
  21   * @copyright  2019 Matt Porritt <mattp@catalyst-au.net>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once($CFG->dirroot . '/user/lib.php');
  28  
  29  /**
  30   * Helper functions for asynchronous backups and restores.
  31   *
  32   * @package     core
  33   * @copyright   2019 Matt Porritt <mattp@catalyst-au.net>
  34   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class async_helper  {
  37  
  38      /**
  39       * @var string $type The type of async operation.
  40       */
  41      protected $type = 'backup';
  42  
  43      /**
  44       * @var string $backupid The id of the backup or restore.
  45       */
  46      protected $backupid;
  47  
  48      /**
  49       * @var object $user The user who created the backup record.
  50       */
  51      protected $user;
  52  
  53      /**
  54       * @var object $backuprec The backup controller record from the database.
  55       */
  56      protected $backuprec;
  57  
  58      /**
  59       * Class constructor.
  60       *
  61       * @param string $type The type of async operation.
  62       * @param string $id The id of the backup or restore.
  63       */
  64      public function __construct($type, $id) {
  65          $this->type = $type;
  66          $this->backupid = $id;
  67          $this->backuprec = self::get_backup_record($id);
  68          $this->user = $this->get_user();
  69      }
  70  
  71      /**
  72       * Given a backup id return a the record from the database.
  73       * We use this method rather than 'load_controller' as the controller may
  74       * not exist if this backup/restore has completed.
  75       *
  76       * @param int $id The backup id to get.
  77       * @return object $backuprec The backup controller record.
  78       */
  79      static public function get_backup_record($id) {
  80          global $DB;
  81  
  82          $backuprec = $DB->get_record('backup_controllers', array('backupid' => $id), '*', MUST_EXIST);
  83  
  84          return $backuprec;
  85      }
  86  
  87      /**
  88       * Given a user id return a user object.
  89       *
  90       * @return object $user The limited user record.
  91       */
  92      private function get_user() {
  93          $userid = $this->backuprec->userid;
  94          $user = core_user::get_user($userid, '*', MUST_EXIST);
  95  
  96          return $user;
  97      }
  98  
  99      /**
 100       * Return appropriate description for current async operation {@see async_helper::type}
 101       *
 102       * @return string
 103       */
 104      private function get_operation_description(): string {
 105          $operations = [
 106              'backup' => new lang_string('backup'),
 107              'copy' => new lang_string('copycourse'),
 108              'restore' => new lang_string('restore'),
 109          ];
 110  
 111          return (string) ($operations[$this->type] ?? $this->type);
 112      }
 113  
 114      /**
 115       * Callback for preg_replace_callback.
 116       * Replaces message placeholders with real values.
 117       *
 118       * @param array $matches The match array from from preg_replace_callback.
 119       * @return string $match The replaced string.
 120       */
 121      private function lookup_message_variables($matches) {
 122          $options = array(
 123                  'operation' => $this->get_operation_description(),
 124                  'backupid' => $this->backupid,
 125                  'user_username' => $this->user->username,
 126                  'user_email' => $this->user->email,
 127                  'user_firstname' => $this->user->firstname,
 128                  'user_lastname' => $this->user->lastname,
 129                  'link' => $this->get_resource_link(),
 130          );
 131  
 132          $match = $options[$matches[1]] ?? $matches[1];
 133  
 134          return $match;
 135      }
 136  
 137      /**
 138       * Get the link to the resource that is being backuped or restored.
 139       *
 140       * @return moodle_url $url The link to the resource.
 141       */
 142      private function get_resource_link() {
 143          // Get activity context only for backups.
 144          if ($this->backuprec->type == 'activity' && $this->type == 'backup') {
 145              $context = context_module::instance($this->backuprec->itemid);
 146          } else { // Course or Section which have the same context getter.
 147              $context = context_course::instance($this->backuprec->itemid);
 148          }
 149  
 150          // Generate link based on operation type.
 151          if ($this->type == 'backup') {
 152              // For backups simply generate link to restore file area UI.
 153              $url = new moodle_url('/backup/restorefile.php', array('contextid' => $context->id));
 154          } else {
 155              // For restore generate link to the item itself.
 156              $url = $context->get_url();
 157          }
 158  
 159          return $url;
 160      }
 161  
 162      /**
 163       * Sends a confirmation message for an aynchronous process.
 164       *
 165       * @return int $messageid The id of the sent message.
 166       */
 167      public function send_message() {
 168          global $USER;
 169  
 170          $subjectraw = get_config('backup', 'backup_async_message_subject');
 171          $subjecttext = preg_replace_callback(
 172                  '/\{([-_A-Za-z0-9]+)\}/u',
 173                  array('async_helper', 'lookup_message_variables'),
 174                  $subjectraw);
 175  
 176          $messageraw = get_config('backup', 'backup_async_message');
 177          $messagehtml = preg_replace_callback(
 178                  '/\{([-_A-Za-z0-9]+)\}/u',
 179                  array('async_helper', 'lookup_message_variables'),
 180                  $messageraw);
 181          $messagetext = html_to_text($messagehtml);
 182  
 183          $message = new \core\message\message();
 184          $message->component = 'moodle';
 185          $message->name = 'asyncbackupnotification';
 186          $message->userfrom          = $USER;
 187          $message->userto            = $this->user;
 188          $message->subject           = $subjecttext;
 189          $message->fullmessage       = $messagetext;
 190          $message->fullmessageformat = FORMAT_HTML;
 191          $message->fullmessagehtml   = $messagehtml;
 192          $message->smallmessage      = '';
 193          $message->notification      = '1';
 194  
 195          $messageid = message_send($message);
 196  
 197          return $messageid;
 198      }
 199  
 200      /**
 201       * Check if asynchronous backup and restore mode is
 202       * enabled at system level.
 203       *
 204       * @return bool $async True if async mode enabled false otherwise.
 205       */
 206      static public function is_async_enabled() {
 207          global $CFG;
 208  
 209          $async = false;
 210          if (!empty($CFG->enableasyncbackup)) {
 211              $async = true;
 212          }
 213  
 214          return $async;
 215      }
 216  
 217      /**
 218       * Check if there is a pending async operation for given details.
 219       *
 220       * @param int $id The item id to check in the backup record.
 221       * @param string $type The type of operation: course, activity or section.
 222       * @param string $operation Operation backup or restore.
 223       * @return boolean $asyncpedning Is there a pending async operation.
 224       */
 225      public static function is_async_pending($id, $type, $operation) {
 226          global $DB, $USER, $CFG;
 227          $asyncpending = false;
 228  
 229          // Only check for pending async operations if async mode is enabled.
 230          require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
 231          require_once($CFG->dirroot . '/backup/backup.class.php');
 232  
 233          $select = 'userid = ? AND itemid = ? AND type = ? AND operation = ? AND execution = ? AND status < ? AND status > ?';
 234          $params = array(
 235              $USER->id,
 236              $id,
 237              $type,
 238              $operation,
 239              backup::EXECUTION_DELAYED,
 240              backup::STATUS_FINISHED_ERR,
 241              backup::STATUS_NEED_PRECHECK
 242          );
 243  
 244          $asyncrecord= $DB->get_record_select('backup_controllers', $select, $params);
 245  
 246          if ((self::is_async_enabled() && $asyncrecord) || ($asyncrecord && $asyncrecord->purpose == backup::MODE_COPY)) {
 247              $asyncpending = true;
 248          }
 249          return $asyncpending;
 250      }
 251  
 252      /**
 253       * Get the size, url and restore url for a backup file.
 254       *
 255       * @param string $filename The name of the file to get info for.
 256       * @param string $filearea The file area for the file.
 257       * @param int $contextid The context ID of the file.
 258       * @return array $results The result array containing the size, url and restore url of the file.
 259       */
 260      public static function get_backup_file_info($filename, $filearea, $contextid) {
 261          $fs = get_file_storage();
 262          $file = $fs->get_file($contextid, 'backup', $filearea, 0, '/', $filename);
 263          $filesize = display_size ($file->get_filesize());
 264          $fileurl = moodle_url::make_pluginfile_url(
 265              $file->get_contextid(),
 266              $file->get_component(),
 267              $file->get_filearea(),
 268              null,
 269              $file->get_filepath(),
 270              $file->get_filename(),
 271              true
 272              );
 273  
 274          $params = array();
 275          $params['action'] = 'choosebackupfile';
 276          $params['filename'] = $file->get_filename();
 277          $params['filepath'] = $file->get_filepath();
 278          $params['component'] = $file->get_component();
 279          $params['filearea'] = $file->get_filearea();
 280          $params['filecontextid'] = $file->get_contextid();
 281          $params['contextid'] = $contextid;
 282          $params['itemid'] = $file->get_itemid();
 283          $restoreurl = new moodle_url('/backup/restorefile.php', $params);
 284          $filesize = display_size ($file->get_filesize());
 285  
 286          $results = array(
 287              'filesize' => $filesize,
 288              'fileurl' => $fileurl->out(false),
 289              'restoreurl' => $restoreurl->out(false));
 290  
 291          return $results;
 292      }
 293  
 294      /**
 295       * Get the url of a restored backup item based on the backup ID.
 296       *
 297       * @param string $backupid The backup ID to get the restore location url.
 298       * @return array $urlarray The restored item URL as an array.
 299       */
 300      public static function get_restore_url($backupid) {
 301          global $DB;
 302  
 303          $backupitemid = $DB->get_field('backup_controllers', 'itemid', array('backupid' => $backupid), MUST_EXIST);
 304          $newcontext = context_course::instance($backupitemid);
 305  
 306          $restoreurl = $newcontext->get_url()->out();
 307          $urlarray = array('restoreurl' => $restoreurl);
 308  
 309          return $urlarray;
 310      }
 311  
 312      /**
 313       * Get markup for in progress async backups,
 314       * to use in backup table UI.
 315       *
 316       * @param string $filearea The filearea to get backup data for.
 317       * @param integer $instanceid The context id to get backup data for.
 318       * @return array $tabledata the rows of table data.
 319       */
 320      public static function get_async_backups($filearea, $instanceid) {
 321          global $DB;
 322  
 323          $backups = [];
 324  
 325          $table = 'backup_controllers';
 326          $select = 'execution = :execution AND status < :status1 AND status > :status2 ' .
 327              'AND operation = :operation';
 328          $params = [
 329              'execution' => backup::EXECUTION_DELAYED,
 330              'status1' => backup::STATUS_FINISHED_ERR,
 331              'status2' => backup::STATUS_NEED_PRECHECK,
 332              'operation' => 'backup',
 333          ];
 334          $sort = 'timecreated DESC';
 335          $fields = 'id, backupid, status, timecreated';
 336  
 337          if ($filearea == 'backup') {
 338              // Get relevant backup ids based on user id.
 339              $params['userid'] = $instanceid;
 340              $select = 'userid = :userid AND ' . $select;
 341              $records = $DB->get_records_select($table, $select, $params, $sort, $fields);
 342              foreach ($records as $record) {
 343                  $bc = \backup_controller::load_controller($record->backupid);
 344  
 345                  // Get useful info to render async status in correct area.
 346                  list($hasusers, $isannon) = self::get_userdata_backup_settings($bc);
 347                  // Backup has users and is not anonymised -> don't show it in users backup file area.
 348                  if ($hasusers && !$isannon) {
 349                      continue;
 350                  }
 351  
 352                  $record->filename = $bc->get_plan()->get_setting('filename')->get_value();
 353                  $bc->destroy();
 354                  array_push($backups, $record);
 355              }
 356          } else {
 357              if ($filearea == 'course' || $filearea == 'activity') {
 358                  // Get relevant backup ids based on context instance id.
 359                  $params['itemid'] = $instanceid;
 360                  $select = 'itemid = :itemid AND ' . $select;
 361                  $records = $DB->get_records_select($table, $select, $params, $sort, $fields);
 362                  foreach ($records as $record) {
 363                      $bc = \backup_controller::load_controller($record->backupid);
 364  
 365                      // Get useful info to render async status in correct area.
 366                      list($hasusers, $isannon) = self::get_userdata_backup_settings($bc);
 367                      // Backup has no user or is anonymised -> don't show it in course/activity backup file area.
 368                      if (!$hasusers || $isannon) {
 369                          continue;
 370                      }
 371  
 372                      $record->filename = $bc->get_plan()->get_setting('filename')->get_value();
 373                      $bc->destroy();
 374                      array_push($backups, $record);
 375                  }
 376              }
 377          }
 378  
 379          return $backups;
 380      }
 381  
 382      /**
 383       * Get the user data settings for backups.
 384       *
 385       * @param \backup_controller $backupcontroller The backup controller object.
 386       * @return array Array of user data settings.
 387       */
 388      public static function get_userdata_backup_settings(\backup_controller $backupcontroller): array {
 389          $hasusers = (bool)$backupcontroller->get_plan()->get_setting('users')->get_value(); // Backup has users.
 390          $isannon = (bool)$backupcontroller->get_plan()->get_setting('anonymize')->get_value(); // Backup is anonymised.
 391          return [$hasusers, $isannon];
 392      }
 393  
 394      /**
 395       * Get the course name of the resource being restored.
 396       *
 397       * @param \context $context The Moodle context for the restores.
 398       * @return string $coursename The full name of the course.
 399       */
 400      public static function get_restore_name(\context $context) {
 401          global $DB;
 402          $instanceid = $context->instanceid;
 403  
 404          if ($context->contextlevel == CONTEXT_MODULE) {
 405              // For modules get the course name and module name.
 406              $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
 407              $coursename = $DB->get_field('course', 'fullname', array('id' => $cm->course));
 408              $itemname = $coursename . ' - ' . $cm->name;
 409          } else {
 410              $itemname = $DB->get_field('course', 'fullname', array('id' => $context->instanceid));
 411  
 412          }
 413  
 414          return $itemname;
 415      }
 416  
 417      /**
 418       * Get all the current in progress async restores for a user.
 419       *
 420       * @param int $userid Moodle user id.
 421       * @return array $restores List of current restores in progress.
 422       */
 423      public static function get_async_restores($userid) {
 424          global $DB;
 425  
 426          $select = 'userid = ? AND execution = ? AND status < ? AND status > ? AND operation = ?';
 427          $params = array($userid, backup::EXECUTION_DELAYED, backup::STATUS_FINISHED_ERR, backup::STATUS_NEED_PRECHECK, 'restore');
 428          $restores = $DB->get_records_select(
 429              'backup_controllers',
 430              $select,
 431              $params,
 432              'timecreated DESC',
 433              'id, backupid, status, itemid, timecreated');
 434  
 435              return $restores;
 436      }
 437  
 438  }
 439