See Release Notes
Long Term Support Release
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
title
Description
Body
title
Description
Body
title
Description
Body
title
Body