See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 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 * Backup controller and related exception classes. 19 * 20 * @package core_backup 21 * @subpackage backup-controller 22 * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 /** 27 * Class implementing the controller of any backup process 28 * 29 * This final class is in charge of controlling all the backup architecture, for any 30 * type of backup. Based in type, format, interactivity and target, it stores the 31 * whole execution plan and settings that will be used later by the @backup_worker, 32 * applies all the defaults, performs all the security contraints and is in charge 33 * of handling the ui if necessary. Also logging strategy is defined here. 34 * 35 * Note the class is 100% neutral and usable for *any* backup. It just stores/requests 36 * all the needed information from other backup classes in order to have everything well 37 * structured in order to allow the @backup_worker classes to do their job. 38 * 39 * In other words, a mammoth class, but don't worry, practically everything is delegated/ 40 * aggregated!) 41 */ 42 class backup_controller extends base_controller { 43 /** @var string Unique identifier for this backup */ 44 protected $backupid; 45 46 /** 47 * Type of item that is being stored in the backup. 48 * 49 * Should be selected from one of the backup::TYPE_ constants 50 * for example backup::TYPE_1ACTIVITY 51 * 52 * @var string 53 */ 54 protected $type; 55 56 /** @var int Course/section/course_module id to backup */ 57 protected $id; 58 59 /** @var false|int The id of the course the backup belongs to, or false if no course. */ 60 protected $courseid; 61 62 /** 63 * Format of backup (moodle, imscc). 64 * 65 * Should be one of the backup::FORMAT_ constants. 66 * for example backup::FORMAT_MOODLE 67 * 68 * @var string 69 */ 70 protected $format; 71 72 /** 73 * Whether this backup will require user interaction. 74 * 75 * Should be one of backup::INTERACTIVE_YES or INTERACTIVE_NO 76 * 77 * @var bool 78 */ 79 protected $interactive; 80 81 /** 82 * Purpose of the backup (default settings) 83 * 84 * Should be one of the the backup::MODE_ constants, 85 * for example backup::MODE_GENERAL 86 * 87 * @var int 88 */ 89 protected $mode; 90 91 /** @var int The id of the user executing the backup. */ 92 protected $userid; 93 94 /** 95 * Type of operation (backup/restore) 96 * 97 * Should be selected from: backup::OPERATION_BACKUP or OPERATION_RESTORE 98 * 99 * @var string 100 */ 101 protected $operation; 102 103 /** 104 * Current status of the controller (created, planned, configured...) 105 * 106 * It should be one of the backup::STATUS_ constants, 107 * for example backup::STATUS_AWAITING. 108 * 109 * @var int 110 */ 111 protected $status; 112 113 /** @var backup_plan Backup execution plan. */ 114 protected $plan; 115 116 /** @var int Whether this backup includes files (1) or not (0). */ 117 protected $includefiles; 118 119 /** 120 * Immediate/delayed execution type. 121 * @var int 122 */ 123 protected $execution; 124 125 /** @var int Epoch time when we want the backup to be executed (requires cron to run). */ 126 protected $executiontime; 127 128 /** @var null Destination chain object (fs_moodle, fs_os, db, email...). */ 129 protected $destination; 130 131 /** @var string Cache {@see \checksumable} results for lighter {@see \backup_controller::is_checksum_correct()} uses. */ 132 protected $checksum; 133 134 /** 135 * The role ids to keep in a copy operation. 136 * @var array 137 */ 138 protected $keptroles = array(); 139 140 /** 141 * Constructor for the backup controller class. 142 * 143 * @param string $type Type of the backup; One of backup::TYPE_1COURSE, TYPE_1SECTION, TYPE_1ACTIVITY 144 * @param int $id The ID of the item to backup; e.g the course id 145 * @param string $format The backup format to use; Most likely backup::FORMAT_MOODLE 146 * @param bool $interactive Whether this backup will require user interaction; backup::INTERACTIVE_YES or INTERACTIVE_NO 147 * @param int $mode One of backup::MODE_GENERAL, MODE_IMPORT, MODE_SAMESITE, MODE_HUB, MODE_AUTOMATED 148 * @param int $userid The id of the user making the backup 149 * @param bool $releasesession Should release the session? backup::RELEASESESSION_YES or backup::RELEASESESSION_NO 150 */ 151 public function __construct($type, $id, $format, $interactive, $mode, $userid, $releasesession = backup::RELEASESESSION_NO) { 152 $this->type = $type; 153 $this->id = $id; 154 $this->courseid = backup_controller_dbops::get_courseid_from_type_id($this->type, $this->id); 155 $this->format = $format; 156 $this->interactive = $interactive; 157 $this->mode = $mode; 158 $this->userid = $userid; 159 $this->releasesession = $releasesession; 160 161 // Apply some defaults 162 $this->operation = backup::OPERATION_BACKUP; 163 $this->executiontime = 0; 164 $this->checksum = ''; 165 166 // Set execution based on backup mode. 167 if ($mode == backup::MODE_ASYNC || $mode == backup::MODE_COPY) { 168 $this->execution = backup::EXECUTION_DELAYED; 169 } else { 170 $this->execution = backup::EXECUTION_INMEDIATE; 171 } 172 173 // Apply current backup version and release if necessary 174 backup_controller_dbops::apply_version_and_release(); 175 176 // Check format and type are correct 177 backup_check::check_format_and_type($this->format, $this->type); 178 179 // Check id is correct 180 backup_check::check_id($this->type, $this->id); 181 182 // Check user is correct 183 backup_check::check_user($this->userid); 184 185 // Calculate unique $backupid 186 $this->calculate_backupid(); 187 188 // Default logger chain (based on interactive/execution) 189 $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->backupid); 190 191 // By default there is no progress reporter. Interfaces that wish to 192 // display progress must set it. 193 $this->progress = new \core\progress\none(); 194 195 // Instantiate the output_controller singleton and active it if interactive and immediate. 196 $oc = output_controller::get_instance(); 197 if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) { 198 $oc->set_active(true); 199 } 200 201 $this->log('instantiating backup controller', backup::LOG_INFO, $this->backupid); 202 203 // Default destination chain (based on type/mode/execution) 204 $this->destination = backup_factory::get_destination_chain($this->type, $this->id, $this->mode, $this->execution); 205 206 // Set initial status 207 $this->set_status(backup::STATUS_CREATED); 208 209 // Load plan (based on type/format) 210 $this->load_plan(); 211 212 // Apply all default settings (based on type/format/mode) 213 $this->apply_defaults(); 214 215 // Perform all initial security checks and apply (2nd param) them to settings automatically 216 backup_check::check_security($this, true); 217 218 // Set status based on interactivity 219 if ($this->interactive == backup::INTERACTIVE_YES) { 220 $this->set_status(backup::STATUS_SETTING_UI); 221 } else { 222 $this->set_status(backup::STATUS_AWAITING); 223 } 224 } 225 226 /** 227 * Clean structures used by the backup_controller 228 * 229 * This method clean various structures used by the backup_controller, 230 * destroying them in an ordered way, so their memory will be gc properly 231 * by PHP (mainly circular references). 232 * 233 * Note that, while it's not mandatory to execute this method, it's highly 234 * recommended to do so, specially in scripts performing multiple operations 235 * (like the automated backups) or the system will run out of memory after 236 * a few dozens of backups) 237 */ 238 public function destroy() { 239 // Only need to destroy circulars under the plan. Delegate to it. 240 $this->plan->destroy(); 241 // Loggers may have also chained references, destroy them. Also closing resources when needed. 242 $this->logger->destroy(); 243 } 244 245 /** 246 * Declare that all user interaction with the backup controller is complete. 247 * 248 * After this the backup controller is waiting for processing. 249 */ 250 public function finish_ui() { 251 if ($this->status != backup::STATUS_SETTING_UI) { 252 throw new backup_controller_exception('cannot_finish_ui_if_not_setting_ui'); 253 } 254 $this->set_status(backup::STATUS_AWAITING); 255 } 256 257 /** 258 * Validates the backup is valid after any user changes. 259 * 260 * A backup_controller_exception will be thrown if there is an issue. 261 */ 262 public function process_ui_event() { 263 264 // Perform security checks throwing exceptions (2nd param) if something is wrong 265 backup_check::check_security($this, false); 266 } 267 268 /** 269 * Sets the new status of the backup. 270 * 271 * @param int $status 272 */ 273 public function set_status($status) { 274 // Note: never save_controller() with the object info after STATUS_EXECUTING or the whole controller, 275 // containing all the steps will be sent to DB. 100% (monster) useless. 276 $this->log('setting controller status to', backup::LOG_DEBUG, $status); 277 // TODO: Check it's a correct status. 278 $this->status = $status; 279 // Ensure that, once set to backup::STATUS_AWAITING, controller is stored in DB. 280 // Also save if executing so we can better track progress. 281 if ($status == backup::STATUS_AWAITING || $status == backup::STATUS_EXECUTING) { 282 $this->save_controller(); 283 $tbc = self::load_controller($this->backupid); 284 $this->logger = $tbc->logger; // wakeup loggers 285 $tbc->plan->destroy(); // Clean plan controller structures, keeping logger alive. 286 287 } else if ($status == backup::STATUS_FINISHED_OK) { 288 // If the operation has ended without error (backup::STATUS_FINISHED_OK) 289 // proceed by cleaning the object from database. MDL-29262. 290 $this->save_controller(false, true); 291 } else if ($status == backup::STATUS_FINISHED_ERR) { 292 // If the operation has ended with an error save the controller 293 // preserving the object in the database. We may want it for debugging. 294 $this->save_controller(); 295 } 296 } 297 298 /** 299 * Sets if the backup will be processed immediately, or later. 300 * 301 * @param int $execution Use backup::EXECUTION_INMEDIATE or backup::EXECUTION_DELAYED 302 * @param int $executiontime The timestamp in the future when the task should be executed, or 0 for immediately. 303 */ 304 public function set_execution($execution, $executiontime = 0) { 305 $this->log('setting controller execution', backup::LOG_DEBUG); 306 // TODO: Check valid execution mode. 307 // TODO: Check time in future. 308 // TODO: Check time = 0 if immediate. 309 $this->execution = $execution; 310 $this->executiontime = $executiontime; 311 312 // Default destination chain (based on type/mode/execution) 313 $this->destination = backup_factory::get_destination_chain($this->type, $this->id, $this->mode, $this->execution); 314 315 // Default logger chain (based on interactive/execution) 316 $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->backupid); 317 } 318 319 // checksumable interface methods 320 321 public function calculate_checksum() { 322 // Reset current checksum to take it out from calculations! 323 $this->checksum = ''; 324 // Init checksum 325 $tempchecksum = md5('backupid-' . $this->backupid . 326 'type-' . $this->type . 327 'id-' . $this->id . 328 'format-' . $this->format . 329 'interactive-'. $this->interactive . 330 'mode-' . $this->mode . 331 'userid-' . $this->userid . 332 'operation-' . $this->operation . 333 'status-' . $this->status . 334 'execution-' . $this->execution . 335 'plan-' . backup_general_helper::array_checksum_recursive(array($this->plan)) . 336 'destination-'. backup_general_helper::array_checksum_recursive(array($this->destination)) . 337 'logger-' . backup_general_helper::array_checksum_recursive(array($this->logger))); 338 $this->log('calculating controller checksum', backup::LOG_DEBUG, $tempchecksum); 339 return $tempchecksum; 340 } 341 342 public function is_checksum_correct($checksum) { 343 return $this->checksum === $checksum; 344 } 345 346 /** 347 * Gets the unique identifier for this backup controller. 348 * 349 * @return string 350 */ 351 public function get_backupid() { 352 return $this->backupid; 353 } 354 355 /** 356 * Gets the type of backup to be performed. 357 * 358 * Use {@see \backup_controller::get_id()} to find the instance being backed up. 359 * 360 * @return string 361 */ 362 public function get_type() { 363 return $this->type; 364 } 365 366 /** 367 * Returns the current value of the include_files setting. 368 * This setting is intended to ensure that files are not included in 369 * generated backups. 370 * 371 * @return int Indicates whether files should be included in backups. 372 */ 373 public function get_include_files() { 374 return $this->includefiles; 375 } 376 377 /** 378 * Returns the default value for $this->includefiles before we consider any settings. 379 * 380 * @return bool 381 * @throws dml_exception 382 */ 383 protected function get_include_files_default() : bool { 384 // We normally include files. 385 $includefiles = true; 386 387 // In an import, we don't need to include files. 388 if ($this->get_mode() === backup::MODE_IMPORT) { 389 $includefiles = false; 390 } 391 392 // When a backup is intended for the same site, we don't need to include the files. 393 // Note, this setting is only used for duplication of an entire course. 394 if ($this->get_mode() === backup::MODE_SAMESITE || $this->get_mode() === backup::MODE_COPY) { 395 $includefiles = false; 396 } 397 398 // If backup is automated and we have set auto backup config to exclude 399 // files then set them to be excluded here. 400 $backupautofiles = (bool) get_config('backup', 'backup_auto_files'); 401 if ($this->get_mode() === backup::MODE_AUTOMATED && !$backupautofiles) { 402 $includefiles = false; 403 } 404 405 return $includefiles; 406 } 407 408 /** 409 * Gets if this is a backup or restore. 410 * 411 * @return string 412 */ 413 public function get_operation() { 414 return $this->operation; 415 } 416 417 /** 418 * Gets the instance id of the item being backed up. 419 * 420 * It's meaning is related to the type of backup {@see \backup_controller::get_type()}. 421 * 422 * @return int 423 */ 424 public function get_id() { 425 return $this->id; 426 } 427 428 /** 429 * Gets the course that the item being backed up is in. 430 * 431 * @return false|int 432 */ 433 public function get_courseid() { 434 return $this->courseid; 435 } 436 437 /** 438 * Gets the format the backup is stored in. 439 * 440 * @return string 441 */ 442 public function get_format() { 443 return $this->format; 444 } 445 446 /** 447 * Gets if user interaction is expected during the backup. 448 * 449 * @return bool 450 */ 451 public function get_interactive() { 452 return $this->interactive; 453 } 454 455 /** 456 * Gets the mode that the backup will be performed in. 457 * 458 * @return int 459 */ 460 public function get_mode() { 461 return $this->mode; 462 } 463 464 /** 465 * Get the id of the user who started the backup. 466 * 467 * @return int 468 */ 469 public function get_userid() { 470 return $this->userid; 471 } 472 473 /** 474 * Get the current status of the backup. 475 * 476 * @return int 477 */ 478 public function get_status() { 479 return $this->status; 480 } 481 482 /** 483 * Get if the backup will be executed immediately, or later. 484 * 485 * @return int 486 */ 487 public function get_execution() { 488 return $this->execution; 489 } 490 491 /** 492 * Get when the backup will be executed. 493 * 494 * @return int 0 means now, otherwise a Unix timestamp 495 */ 496 public function get_executiontime() { 497 return $this->executiontime; 498 } 499 500 /** 501 * Gets the plan that will be run during the backup. 502 * 503 * @return backup_plan 504 */ 505 public function get_plan() { 506 return $this->plan; 507 } 508 509 /** 510 * For debug only. Get a simple test display of all the settings. 511 * 512 * @return string 513 */ 514 public function debug_display_all_settings_values(): string { 515 return $this->get_plan()->debug_display_all_settings_values(); 516 } 517 518 /** 519 * Sets the user roles that should be kept in the destination course 520 * for a course copy operation. 521 * 522 * @param array $roleids 523 * @throws backup_controller_exception 524 */ 525 public function set_kept_roles(array $roleids): void { 526 // Only allow of keeping user roles when controller is in copy mode. 527 if ($this->mode != backup::MODE_COPY) { 528 throw new backup_controller_exception('cannot_set_keep_roles_wrong_mode'); 529 } 530 531 $this->keptroles = $roleids; 532 } 533 534 /** 535 * Executes the backup 536 * @return void Throws and exception of completes 537 */ 538 public function execute_plan() { 539 // Basic/initial prevention against time/memory limits 540 core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted 541 raise_memory_limit(MEMORY_EXTRA); 542 543 // Release the session so other tabs in the same session are not blocked. 544 if ($this->get_releasesession() === backup::RELEASESESSION_YES) { 545 \core\session\manager::write_close(); 546 } 547 548 // If the controller has decided that we can include files, then check the setting, otherwise do not include files. 549 if ($this->get_include_files()) { 550 $this->set_include_files((bool) $this->get_plan()->get_setting('files')->get_value()); 551 } 552 553 // If this is not a course backup, or single activity backup (e.g. duplicate) inform the plan we are not 554 // including all the activities for sure. This will affect any 555 // task/step executed conditionally to stop including information 556 // for section and activity backup. MDL-28180. 557 if ($this->get_type() !== backup::TYPE_1COURSE && $this->get_type() !== backup::TYPE_1ACTIVITY) { 558 $this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG); 559 $this->plan->set_excluding_activities(); 560 } 561 562 // Handle copy operation specific settings. 563 if ($this->mode == backup::MODE_COPY) { 564 $this->plan->set_kept_roles($this->keptroles); 565 } 566 567 return $this->plan->execute(); 568 } 569 570 /** 571 * Gets the results of the plan execution for this backup. 572 * 573 * @return array 574 */ 575 public function get_results() { 576 return $this->plan->get_results(); 577 } 578 579 /** 580 * Save controller information 581 * 582 * @param bool $includeobj to decide if the object itself must be updated (true) or no (false) 583 * @param bool $cleanobj to decide if the object itself must be cleaned (true) or no (false) 584 */ 585 public function save_controller($includeobj = true, $cleanobj = false) { 586 // Going to save controller to persistent storage, calculate checksum for later checks and save it. 587 // TODO: flag the controller as NA. Any operation on it should be forbidden until loaded back. 588 $this->log('saving controller to db', backup::LOG_DEBUG); 589 if ($includeobj ) { // Only calculate checksum if we are going to include the object. 590 $this->checksum = $this->calculate_checksum(); 591 } 592 backup_controller_dbops::save_controller($this, $this->checksum, $includeobj, $cleanobj); 593 } 594 595 /** 596 * Loads a backup controller from the database. 597 * 598 * @param string $backupid The id of the backup controller. 599 * @return \backup_controller 600 */ 601 public static function load_controller($backupid) { 602 // Load controller from persistent storage 603 // TODO: flag the controller as available. Operations on it can continue 604 $controller = backup_controller_dbops::load_controller($backupid); 605 $controller->log('loading controller from db', backup::LOG_DEBUG); 606 return $controller; 607 } 608 609 // Protected API starts here 610 611 /** 612 * Creates a unique id for this backup. 613 */ 614 protected function calculate_backupid() { 615 // Current epoch time + type + id + format + interactive + mode + userid + operation 616 // should be unique enough. Add one random part at the end 617 $this->backupid = md5(time() . '-' . $this->type . '-' . $this->id . '-' . $this->format . '-' . 618 $this->interactive . '-' . $this->mode . '-' . $this->userid . '-' . 619 $this->operation . '-' . random_string(20)); 620 } 621 622 /** 623 * Builds the plan for this backup job so that it may be executed. 624 */ 625 protected function load_plan() { 626 $this->log('loading controller plan', backup::LOG_DEBUG); 627 $this->plan = new backup_plan($this); 628 $this->plan->build(); // Build plan for this controller 629 $this->set_status(backup::STATUS_PLANNED); 630 } 631 632 /** 633 * Sets default values for the backup controller. 634 */ 635 protected function apply_defaults() { 636 $this->log('applying plan defaults', backup::LOG_DEBUG); 637 backup_controller_dbops::apply_config_defaults($this); 638 $this->set_status(backup::STATUS_CONFIGURED); 639 $this->set_include_files($this->get_include_files_default()); 640 } 641 642 /** 643 * Set the initial value for the include_files setting. 644 * 645 * @param bool $includefiles 646 * @see backup_controller::get_include_files for further information on the purpose of this setting. 647 */ 648 protected function set_include_files(bool $includefiles) { 649 $this->log("setting file inclusion to {$this->includefiles}", backup::LOG_DEBUG); 650 $this->includefiles = (int) $includefiles; 651 } 652 } 653 654 /** 655 * Exception class used by all the @backup_controller stuff 656 */ 657 class backup_controller_exception extends backup_exception { 658 659 public function __construct($errorcode, $a=NULL, $debuginfo=null) { 660 parent::__construct($errorcode, $a, $debuginfo); 661 } 662 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body