[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * @package moodlecore 20 * @subpackage backup-controller 21 * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 /** 26 * Class implementing the controller of any restore process 27 * 28 * This final class is in charge of controlling all the restore architecture, for any 29 * type of backup. 30 * 31 * TODO: Finish phpdocs 32 */ 33 class restore_controller extends base_controller { 34 35 protected $tempdir; // Directory under tempdir/backup awaiting restore 36 protected $restoreid; // Unique identificator for this restore 37 38 protected $courseid; // courseid where restore is going to happen 39 40 protected $type; // Type of backup (activity, section, course) 41 protected $format; // Format of backup (moodle, imscc) 42 protected $interactive; // yes/no 43 protected $mode; // Purpose of the backup (default settings) 44 protected $userid; // user id executing the restore 45 protected $operation; // Type of operation (backup/restore) 46 protected $target; // Restoring to new/existing/current_adding/_deleting 47 protected $samesite; // Are we restoring to the same site where the backup was generated 48 49 protected $status; // Current status of the controller (created, planned, configured...) 50 protected $precheck; // Results of the execution of restore prechecks 51 52 protected $info; // Information retrieved from backup contents 53 protected $plan; // Restore execution plan 54 55 protected $execution; // inmediate/delayed 56 protected $executiontime; // epoch time when we want the restore to be executed (requires cron to run) 57 58 protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses 59 60 /** @var int Number of restore_controllers that are currently executing */ 61 protected static $executing = 0; 62 63 /** 64 * Constructor. 65 * 66 * If you specify a progress monitor, this will be used to report progress 67 * while loading the plan, as well as for future use. (You can change it 68 * for a different one later using set_progress.) 69 * 70 * @param string $tempdir Directory under tempdir/backup awaiting restore 71 * @param int $courseid Course id where restore is going to happen 72 * @param bool $interactive backup::INTERACTIVE_YES[true] or backup::INTERACTIVE_NO[false] 73 * @param int $mode backup::MODE_[ GENERAL | HUB | IMPORT | SAMESITE ] 74 * @param int $userid 75 * @param int $target backup::TARGET_[ NEW_COURSE | CURRENT_ADDING | CURRENT_DELETING | EXISTING_ADDING | EXISTING_DELETING ] 76 * @param \core\progress\base $progress Optional progress monitor 77 */ 78 public function __construct($tempdir, $courseid, $interactive, $mode, $userid, $target, 79 \core\progress\base $progress = null) { 80 $this->tempdir = $tempdir; 81 $this->courseid = $courseid; 82 $this->interactive = $interactive; 83 $this->mode = $mode; 84 $this->userid = $userid; 85 $this->target = $target; 86 87 // Apply some defaults 88 $this->type = ''; 89 $this->format = backup::FORMAT_UNKNOWN; 90 $this->execution = backup::EXECUTION_INMEDIATE; 91 $this->operation = backup::OPERATION_RESTORE; 92 $this->executiontime = 0; 93 $this->samesite = false; 94 $this->checksum = ''; 95 $this->precheck = null; 96 97 // Apply current backup version and release if necessary 98 backup_controller_dbops::apply_version_and_release(); 99 100 // Check courseid is correct 101 restore_check::check_courseid($this->courseid); 102 103 // Check user is correct 104 restore_check::check_user($this->userid); 105 106 // Calculate unique $restoreid 107 $this->calculate_restoreid(); 108 109 // Default logger chain (based on interactive/execution) 110 $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid); 111 112 // By default there is no progress reporter unless you specify one so it 113 // can be used during loading of the plan. 114 if ($progress) { 115 $this->progress = $progress; 116 } else { 117 $this->progress = new \core\progress\none(); 118 } 119 $this->progress->start_progress('Constructing restore_controller'); 120 121 // Instantiate the output_controller singleton and active it if interactive and inmediate 122 $oc = output_controller::get_instance(); 123 if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) { 124 $oc->set_active(true); 125 } 126 127 $this->log('instantiating restore controller', backup::LOG_INFO, $this->restoreid); 128 129 // Set initial status 130 $this->set_status(backup::STATUS_CREATED); 131 132 // Calculate original restore format 133 $this->format = backup_general_helper::detect_backup_format($tempdir); 134 135 // If format is not moodle2, set to conversion needed 136 if ($this->format !== backup::FORMAT_MOODLE) { 137 $this->set_status(backup::STATUS_REQUIRE_CONV); 138 139 // Else, format is moodle2, load plan, apply security and set status based on interactivity 140 } else { 141 // Load plan 142 $this->load_plan(); 143 144 // Perform all initial security checks and apply (2nd param) them to settings automatically 145 restore_check::check_security($this, true); 146 147 if ($this->interactive == backup::INTERACTIVE_YES) { 148 $this->set_status(backup::STATUS_SETTING_UI); 149 } else { 150 $this->set_status(backup::STATUS_NEED_PRECHECK); 151 } 152 } 153 154 // Tell progress monitor that we finished loading. 155 $this->progress->end_progress(); 156 } 157 158 /** 159 * Clean structures used by the restore_controller 160 * 161 * This method clean various structures used by the restore_controller, 162 * destroying them in an ordered way, so their memory will be gc properly 163 * by PHP (mainly circular references). 164 * 165 * Note that, while it's not mandatory to execute this method, it's highly 166 * recommended to do so, specially in scripts performing multiple operations 167 * (like the automated backups) or the system will run out of memory after 168 * a few dozens of backups) 169 */ 170 public function destroy() { 171 // Only need to destroy circulars under the plan. Delegate to it. 172 $this->plan->destroy(); 173 // Loggers may have also chained references, destroy them. Also closing resources when needed. 174 $this->logger->destroy(); 175 } 176 177 public function finish_ui() { 178 if ($this->status != backup::STATUS_SETTING_UI) { 179 throw new restore_controller_exception('cannot_finish_ui_if_not_setting_ui'); 180 } 181 $this->set_status(backup::STATUS_NEED_PRECHECK); 182 } 183 184 public function process_ui_event() { 185 186 // Perform security checks throwing exceptions (2nd param) if something is wrong 187 restore_check::check_security($this, false); 188 } 189 190 public function set_status($status) { 191 // Note: never save_controller() with the object info after STATUS_EXECUTING or the whole controller, 192 // containing all the steps will be sent to DB. 100% (monster) useless. 193 $this->log('setting controller status to', backup::LOG_DEBUG, $status); 194 // TODO: Check it's a correct status. 195 $this->status = $status; 196 // Ensure that, once set to backup::STATUS_AWAITING | STATUS_NEED_PRECHECK, controller is stored in DB. 197 if ($status == backup::STATUS_AWAITING || $status == backup::STATUS_NEED_PRECHECK) { 198 $this->save_controller(); 199 $tbc = self::load_controller($this->restoreid); 200 $this->logger = $tbc->logger; // wakeup loggers 201 $tbc->plan->destroy(); // Clean plan controller structures, keeping logger alive. 202 203 } else if ($status == backup::STATUS_FINISHED_OK) { 204 // If the operation has ended without error (backup::STATUS_FINISHED_OK) 205 // proceed by cleaning the object from database. MDL-29262. 206 $this->save_controller(false, true); 207 } 208 } 209 210 public function set_execution($execution, $executiontime = 0) { 211 $this->log('setting controller execution', backup::LOG_DEBUG); 212 // TODO: Check valid execution mode 213 // TODO: Check time in future 214 // TODO: Check time = 0 if inmediate 215 $this->execution = $execution; 216 $this->executiontime = $executiontime; 217 218 // Default logger chain (based on interactive/execution) 219 $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid); 220 } 221 222 // checksumable interface methods 223 224 public function calculate_checksum() { 225 // Reset current checksum to take it out from calculations! 226 $this->checksum = ''; 227 // Init checksum 228 $tempchecksum = md5('tempdir-' . $this->tempdir . 229 'restoreid-' . $this->restoreid . 230 'courseid-' . $this->courseid . 231 'type-' . $this->type . 232 'format-' . $this->format . 233 'interactive-'. $this->interactive . 234 'mode-' . $this->mode . 235 'userid-' . $this->userid . 236 'target-' . $this->target . 237 'samesite-' . $this->samesite . 238 'operation-' . $this->operation . 239 'status-' . $this->status . 240 'precheck-' . backup_general_helper::array_checksum_recursive(array($this->precheck)) . 241 'execution-' . $this->execution . 242 'plan-' . backup_general_helper::array_checksum_recursive(array($this->plan)) . 243 'info-' . backup_general_helper::array_checksum_recursive(array($this->info)) . 244 'logger-' . backup_general_helper::array_checksum_recursive(array($this->logger))); 245 $this->log('calculating controller checksum', backup::LOG_DEBUG, $tempchecksum); 246 return $tempchecksum; 247 } 248 249 public function is_checksum_correct($checksum) { 250 return $this->checksum === $checksum; 251 } 252 253 public function get_tempdir() { 254 return $this->tempdir; 255 } 256 257 public function get_restoreid() { 258 return $this->restoreid; 259 } 260 261 public function get_type() { 262 return $this->type; 263 } 264 265 public function get_operation() { 266 return $this->operation; 267 } 268 269 public function get_courseid() { 270 return $this->courseid; 271 } 272 273 public function get_format() { 274 return $this->format; 275 } 276 277 public function get_interactive() { 278 return $this->interactive; 279 } 280 281 public function get_mode() { 282 return $this->mode; 283 } 284 285 public function get_userid() { 286 return $this->userid; 287 } 288 289 public function get_target() { 290 return $this->target; 291 } 292 293 public function is_samesite() { 294 return $this->samesite; 295 } 296 297 public function get_status() { 298 return $this->status; 299 } 300 301 public function get_execution() { 302 return $this->execution; 303 } 304 305 public function get_executiontime() { 306 return $this->executiontime; 307 } 308 309 /** 310 * Returns the restore plan 311 * @return restore_plan 312 */ 313 public function get_plan() { 314 return $this->plan; 315 } 316 317 public function get_info() { 318 return $this->info; 319 } 320 321 public function execute_plan() { 322 // Basic/initial prevention against time/memory limits 323 core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted 324 raise_memory_limit(MEMORY_EXTRA); 325 // If this is not a course restore, inform the plan we are not 326 // including all the activities for sure. This will affect any 327 // task/step executed conditionally to stop processing information 328 // for section and activity restore. MDL-28180. 329 if ($this->get_type() !== backup::TYPE_1COURSE) { 330 $this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG); 331 $this->plan->set_excluding_activities(); 332 } 333 self::$executing++; 334 try { 335 $this->plan->execute(); 336 } catch (Exception $e) { 337 self::$executing--; 338 throw $e; 339 } 340 self::$executing--; 341 } 342 343 /** 344 * Checks whether restore is currently executing. Certain parts of code that 345 * is called during restore, but not directly part of the restore system, may 346 * need to behave differently during restore (e.g. do not bother resetting a 347 * cache because we know it will be reset at end of operation). 348 * 349 * @return bool True if any restore is currently executing 350 */ 351 public static function is_executing() { 352 return self::$executing > 0; 353 } 354 355 /** 356 * Execute the restore prechecks to detect any problem before proceed with restore 357 * 358 * This function checks various parts of the restore (versions, users, roles...) 359 * returning true if everything was ok or false if any warning/error was detected. 360 * Any warning/error is returned by the get_precheck_results() method. 361 * Note: if any problem is found it will, automatically, drop all the restore temp 362 * tables as far as the next step is to inform about the warning/errors. If no problem 363 * is found, then default behaviour is to keep the temp tables so, in the same request 364 * restore will be executed, saving a lot of checks to be executed again. 365 * Note: If for any reason (UI to show after prechecks...) you want to force temp tables 366 * to be dropped always, you can pass true to the $droptemptablesafter parameter 367 */ 368 public function execute_precheck($droptemptablesafter = false) { 369 if (is_array($this->precheck)) { 370 throw new restore_controller_exception('precheck_alredy_executed', $this->status); 371 } 372 if ($this->status != backup::STATUS_NEED_PRECHECK) { 373 throw new restore_controller_exception('cannot_precheck_wrong_status', $this->status); 374 } 375 // Basic/initial prevention against time/memory limits 376 core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted 377 raise_memory_limit(MEMORY_EXTRA); 378 $this->precheck = restore_prechecks_helper::execute_prechecks($this, $droptemptablesafter); 379 if (!array_key_exists('errors', $this->precheck)) { // No errors, can be executed 380 $this->set_status(backup::STATUS_AWAITING); 381 } 382 if (empty($this->precheck)) { // No errors nor warnings, return true 383 return true; 384 } 385 return false; 386 } 387 388 public function get_results() { 389 return $this->plan->get_results(); 390 } 391 392 /** 393 * Returns true if the prechecks have been executed 394 * @return bool 395 */ 396 public function precheck_executed() { 397 return (is_array($this->precheck)); 398 } 399 400 public function get_precheck_results() { 401 if (!is_array($this->precheck)) { 402 throw new restore_controller_exception('precheck_not_executed'); 403 } 404 return $this->precheck; 405 } 406 407 /** 408 * Save controller information 409 * 410 * @param bool $includeobj to decide if the object itself must be updated (true) or no (false) 411 * @param bool $cleanobj to decide if the object itself must be cleaned (true) or no (false) 412 */ 413 public function save_controller($includeobj = true, $cleanobj = false) { 414 // Going to save controller to persistent storage, calculate checksum for later checks and save it 415 // TODO: flag the controller as NA. Any operation on it should be forbidden util loaded back 416 $this->log('saving controller to db', backup::LOG_DEBUG); 417 if ($includeobj ) { // Only calculate checksum if we are going to include the object. 418 $this->checksum = $this->calculate_checksum(); 419 } 420 restore_controller_dbops::save_controller($this, $this->checksum, $includeobj, $cleanobj); 421 } 422 423 public static function load_controller($restoreid) { 424 // Load controller from persistent storage 425 // TODO: flag the controller as available. Operations on it can continue 426 $controller = restore_controller_dbops::load_controller($restoreid); 427 $controller->log('loading controller from db', backup::LOG_DEBUG); 428 return $controller; 429 } 430 431 /** 432 * class method to provide pseudo random unique "correct" tempdir names 433 */ 434 public static function get_tempdir_name($courseid = 0, $userid = 0) { 435 // Current epoch time + courseid + userid + random bits 436 return md5(time() . '-' . $courseid . '-'. $userid . '-'. random_string(20)); 437 } 438 439 /** 440 * Converts from current format to backup::MOODLE format 441 */ 442 public function convert() { 443 global $CFG; 444 require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php'); 445 446 // Basic/initial prevention against time/memory limits 447 core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted 448 raise_memory_limit(MEMORY_EXTRA); 449 $this->progress->start_progress('Backup format conversion'); 450 451 if ($this->status != backup::STATUS_REQUIRE_CONV) { 452 throw new restore_controller_exception('cannot_convert_not_required_status'); 453 } 454 455 $this->log('backup format conversion required', backup::LOG_INFO); 456 457 // Run conversion to the proper format 458 if (!convert_helper::to_moodle2_format($this->get_tempdir(), $this->format, $this->get_logger())) { 459 // todo - unable to find the conversion path, what to do now? 460 // throwing the exception as a temporary solution 461 throw new restore_controller_exception('unable_to_find_conversion_path'); 462 } 463 464 $this->log('backup format conversion successful', backup::LOG_INFO); 465 466 // If no exceptions were thrown, then we are in the proper format 467 $this->format = backup::FORMAT_MOODLE; 468 469 // Load plan, apply security and set status based on interactivity 470 $this->load_plan(); 471 472 // Perform all initial security checks and apply (2nd param) them to settings automatically 473 restore_check::check_security($this, true); 474 475 if ($this->interactive == backup::INTERACTIVE_YES) { 476 $this->set_status(backup::STATUS_SETTING_UI); 477 } else { 478 $this->set_status(backup::STATUS_NEED_PRECHECK); 479 } 480 $this->progress->end_progress(); 481 } 482 483 // Protected API starts here 484 485 protected function calculate_restoreid() { 486 // Current epoch time + tempdir + courseid + interactive + mode + userid + target + operation + random bits 487 $this->restoreid = md5(time() . '-' . $this->tempdir . '-' . $this->courseid . '-'. $this->interactive . '-' . 488 $this->mode . '-' . $this->userid . '-'. $this->target . '-' . $this->operation . '-' . 489 random_string(20)); 490 } 491 492 protected function load_plan() { 493 // First of all, we need to introspect the moodle_backup.xml file 494 // in order to detect all the required stuff. So, create the 495 // monster $info structure where everything will be defined 496 $this->log('loading backup info', backup::LOG_DEBUG); 497 $this->info = backup_general_helper::get_backup_information($this->tempdir); 498 499 // Set the controller type to the one found in the information 500 $this->type = $this->info->type; 501 502 // Set the controller samesite flag as needed 503 $this->samesite = backup_general_helper::backup_is_samesite($this->info); 504 505 // Now we load the plan that will be configured following the 506 // information provided by the $info 507 $this->log('loading controller plan', backup::LOG_DEBUG); 508 $this->plan = new restore_plan($this); 509 $this->plan->build(); // Build plan for this controller 510 $this->set_status(backup::STATUS_PLANNED); 511 } 512 } 513 514 /* 515 * Exception class used by all the @restore_controller stuff 516 */ 517 class restore_controller_exception extends backup_exception { 518 519 public function __construct($errorcode, $a=NULL, $debuginfo=null) { 520 parent::__construct($errorcode, $a, $debuginfo); 521 } 522 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |