Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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       * Callback for preg_replace_callback.
 101       * Replaces message placeholders with real values.
 102       *
 103       * @param array $matches The match array from from preg_replace_callback.
 104       * @return string $match The replaced string.
 105       */
 106      private function lookup_message_variables($matches) {
 107          $options = array(
 108                  'operation' => $this->type,
 109                  'backupid' => $this->backupid,
 110                  'user_username' => $this->user->username,
 111                  'user_email' => $this->user->email,
 112                  'user_firstname' => $this->user->firstname,
 113                  'user_lastname' => $this->user->lastname,
 114                  'link' => $this->get_resource_link(),
 115          );
 116  
 117          $match = $options[$matches[1]] ?? $matches[1];
 118  
 119          return $match;
 120      }
 121  
 122      /**
 123       * Get the link to the resource that is being backuped or restored.
 124       *
 125       * @return moodle_url $url The link to the resource.
 126       */
 127      private function get_resource_link() {
 128          // Get activity context only for backups.
 129          if ($this->backuprec->type == 'activity' && $this->type == 'backup') {
 130              $context = context_module::instance($this->backuprec->itemid);
 131          } else { // Course or Section which have the same context getter.
 132              $context = context_course::instance($this->backuprec->itemid);
 133          }
 134  
 135          // Generate link based on operation type.
 136          if ($this->type == 'backup') {
 137              // For backups simply generate link to restore file area UI.
 138              $url = new moodle_url('/backup/restorefile.php', array('contextid' => $context->id));
 139          } else {
 140              // For restore generate link to the item itself.
 141              $url = $context->get_url();
 142          }
 143  
 144          return $url;
 145      }
 146  
 147      /**
 148       * Sends a confirmation message for an aynchronous process.
 149       *
 150       * @return int $messageid The id of the sent message.
 151       */
 152      public function send_message() {
 153          global $USER;
 154  
 155          $subjectraw = get_config('backup', 'backup_async_message_subject');
 156          $subjecttext = preg_replace_callback(
 157                  '/\{([-_A-Za-z0-9]+)\}/u',
 158                  array('async_helper', 'lookup_message_variables'),
 159                  $subjectraw);
 160  
 161          $messageraw = get_config('backup', 'backup_async_message');
 162          $messagehtml = preg_replace_callback(
 163                  '/\{([-_A-Za-z0-9]+)\}/u',
 164                  array('async_helper', 'lookup_message_variables'),
 165                  $messageraw);
 166          $messagetext = html_to_text($messagehtml);
 167  
 168          $message = new \core\message\message();
 169          $message->component = 'moodle';
 170          $message->name = 'asyncbackupnotification';
 171          $message->userfrom          = $USER;
 172          $message->userto            = $this->user;
 173          $message->subject           = $subjecttext;
 174          $message->fullmessage       = $messagetext;
 175          $message->fullmessageformat = FORMAT_HTML;
 176          $message->fullmessagehtml   = $messagehtml;
 177          $message->smallmessage      = '';
 178          $message->notification      = '1';
 179  
 180          $messageid = message_send($message);
 181  
 182          return $messageid;
 183      }
 184  
 185      /**
 186       * Check if asynchronous backup and restore mode is
 187       * enabled at system level.
 188       *
 189       * @return bool $async True if async mode enabled false otherwise.
 190       */
 191      static public function is_async_enabled() {
 192          global $CFG;
 193  
 194          $async = false;
 195          if (!empty($CFG->enableasyncbackup)) {
 196              $async = true;
 197          }
 198  
 199          return $async;
 200      }
 201  
 202      /**
 203       * Check if there is a pending async operation for given details.
 204       *
 205       * @param int $id The item id to check in the backup record.
 206       * @param string $type The type of operation: course, activity or section.
 207       * @param string $operation Operation backup or restore.
 208       * @return boolean $asyncpedning Is there a pending async operation.
 209       */
 210      public static function is_async_pending($id, $type, $operation) {
 211          global $DB, $USER, $CFG;
 212          $asyncpending = false;
 213  
 214          // Only check for pending async operations if async mode is enabled.
 215          require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
 216          require_once($CFG->dirroot . '/backup/backup.class.php');
 217  
 218          $select = 'userid = ? AND itemid = ? AND type = ? AND operation = ? AND execution = ? AND status < ? AND status > ?';
 219          $params = array(
 220              $USER->id,
 221              $id,
 222              $type,
 223              $operation,
 224              backup::EXECUTION_DELAYED,
 225              backup::STATUS_FINISHED_ERR,
 226              backup::STATUS_NEED_PRECHECK
 227          );
 228  
 229          $asyncrecord= $DB->get_record_select('backup_controllers', $select, $params);
 230  
 231          if ((self::is_async_enabled() && $asyncrecord) || ($asyncrecord && $asyncrecord->purpose == backup::MODE_COPY)) {
 232              $asyncpending = true;
 233          }
 234          return $asyncpending;
 235      }
 236  
 237      /**
 238       * Get the size, url and restore url for a backup file.
 239       *
 240       * @param string $filename The name of the file to get info for.
 241       * @param string $filearea The file area for the file.
 242       * @param int $contextid The context ID of the file.
 243       * @return array $results The result array containing the size, url and restore url of the file.
 244       */
 245      public static function get_backup_file_info($filename, $filearea, $contextid) {
 246          $fs = get_file_storage();
 247          $file = $fs->get_file($contextid, 'backup', $filearea, 0, '/', $filename);
 248          $filesize = display_size ($file->get_filesize());
 249          $fileurl = moodle_url::make_pluginfile_url(
 250              $file->get_contextid(),
 251              $file->get_component(),
 252              $file->get_filearea(),
 253              null,
 254              $file->get_filepath(),
 255              $file->get_filename(),
 256              true
 257              );
 258  
 259          $params = array();
 260          $params['action'] = 'choosebackupfile';
 261          $params['filename'] = $file->get_filename();
 262          $params['filepath'] = $file->get_filepath();
 263          $params['component'] = $file->get_component();
 264          $params['filearea'] = $file->get_filearea();
 265          $params['filecontextid'] = $file->get_contextid();
 266          $params['contextid'] = $contextid;
 267          $params['itemid'] = $file->get_itemid();
 268          $restoreurl = new moodle_url('/backup/restorefile.php', $params);
 269          $filesize = display_size ($file->get_filesize());
 270  
 271          $results = array(
 272              'filesize' => $filesize,
 273              'fileurl' => $fileurl->out(false),
 274              'restoreurl' => $restoreurl->out(false));
 275  
 276          return $results;
 277      }
 278  
 279      /**
 280       * Get the url of a restored backup item based on the backup ID.
 281       *
 282       * @param string $backupid The backup ID to get the restore location url.
 283       * @return array $urlarray The restored item URL as an array.
 284       */
 285      public static function get_restore_url($backupid) {
 286          global $DB;
 287  
 288          $backupitemid = $DB->get_field('backup_controllers', 'itemid', array('backupid' => $backupid), MUST_EXIST);
 289          $newcontext = context_course::instance($backupitemid);
 290  
 291          $restoreurl = $newcontext->get_url()->out();
 292          $urlarray = array('restoreurl' => $restoreurl);
 293  
 294          return $urlarray;
 295      }
 296  
 297      /**
 298       * Get markup for in progress async backups,
 299       * to use in backup table UI.
 300       *
 301       * @param \core_backup_renderer $renderer The backup renderer object.
 302       * @param integer $instanceid The context id to get backup data for.
 303       * @return array $tabledata the rows of table data.
 304       */
 305      public static function get_async_backups($renderer, $instanceid) {
 306          global $DB;
 307  
 308          $tabledata = array();
 309  
 310          // Get relevant backup ids based on context instance id.
 311          $select = 'itemid = :itemid AND execution = :execution AND status < :status1 AND status > :status2 ' .
 312              'AND operation = :operation';
 313          $params = [
 314              'itemid' => $instanceid,
 315              'execution' => backup::EXECUTION_DELAYED,
 316              'status1' => backup::STATUS_FINISHED_ERR,
 317              'status2' => backup::STATUS_NEED_PRECHECK,
 318              'operation' => 'backup',
 319          ];
 320  
 321          $backups = $DB->get_records_select('backup_controllers', $select, $params, 'timecreated DESC', 'id, backupid, timecreated');
 322          foreach ($backups as $backup) {
 323              $bc = \backup_controller::load_controller($backup->backupid);  // Get the backup controller.
 324              $filename = $bc->get_plan()->get_setting('filename')->get_value();
 325              $timecreated = $backup->timecreated;
 326              $status = $renderer->get_status_display($bc->get_status(), $bc->get_backupid());
 327              $bc->destroy();
 328  
 329              $tablerow = array($filename, userdate($timecreated), '-', '-', '-', $status);
 330              $tabledata[] = $tablerow;
 331          }
 332  
 333          return $tabledata;
 334      }
 335  
 336      /**
 337       * Get the course name of the resource being restored.
 338       *
 339       * @param \context $context The Moodle context for the restores.
 340       * @return string $coursename The full name of the course.
 341       */
 342      public static function get_restore_name(\context $context) {
 343          global $DB;
 344          $instanceid = $context->instanceid;
 345  
 346          if ($context->contextlevel == CONTEXT_MODULE) {
 347              // For modules get the course name and module name.
 348              $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
 349              $coursename = $DB->get_field('course', 'fullname', array('id' => $cm->course));
 350              $itemname = $coursename . ' - ' . $cm->name;
 351          } else {
 352              $itemname = $DB->get_field('course', 'fullname', array('id' => $context->instanceid));
 353  
 354          }
 355  
 356          return $itemname;
 357      }
 358  
 359      /**
 360       * Get all the current in progress async restores for a user.
 361       *
 362       * @param int $userid Moodle user id.
 363       * @return array $restores List of current restores in progress.
 364       */
 365      public static function get_async_restores($userid) {
 366          global $DB;
 367  
 368          $select = 'userid = ? AND execution = ? AND status < ? AND status > ? AND operation = ?';
 369          $params = array($userid, backup::EXECUTION_DELAYED, backup::STATUS_FINISHED_ERR, backup::STATUS_NEED_PRECHECK, 'restore');
 370          $restores = $DB->get_records_select(
 371              'backup_controllers',
 372              $select,
 373              $params,
 374              'timecreated DESC',
 375              'id, backupid, status, itemid, timecreated');
 376  
 377              return $restores;
 378      }
 379  
 380  }
 381