|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00033 class restore_controller extends backup implements loggable { 00034 00035 protected $tempdir; // Directory under tempdir/backup awaiting restore 00036 protected $restoreid; // Unique identificator for this restore 00037 00038 protected $courseid; // courseid where restore is going to happen 00039 00040 protected $type; // Type of backup (activity, section, course) 00041 protected $format; // Format of backup (moodle, imscc) 00042 protected $interactive; // yes/no 00043 protected $mode; // Purpose of the backup (default settings) 00044 protected $userid; // user id executing the restore 00045 protected $operation; // Type of operation (backup/restore) 00046 protected $target; // Restoring to new/existing/current_adding/_deleting 00047 protected $samesite; // Are we restoring to the same site where the backup was generated 00048 00049 protected $status; // Current status of the controller (created, planned, configured...) 00050 protected $precheck; // Results of the execution of restore prechecks 00051 00052 protected $info; // Information retrieved from backup contents 00053 protected $plan; // Restore execution plan 00054 00055 protected $execution; // inmediate/delayed 00056 protected $executiontime; // epoch time when we want the restore to be executed (requires cron to run) 00057 00058 protected $logger; // Logging chain object (moodle, inline, fs, db, syslog) 00059 00060 protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses 00061 00071 public function __construct($tempdir, $courseid, $interactive, $mode, $userid, $target){ 00072 $this->tempdir = $tempdir; 00073 $this->courseid = $courseid; 00074 $this->interactive = $interactive; 00075 $this->mode = $mode; 00076 $this->userid = $userid; 00077 $this->target = $target; 00078 00079 // Apply some defaults 00080 $this->type = ''; 00081 $this->format = backup::FORMAT_UNKNOWN; 00082 $this->execution = backup::EXECUTION_INMEDIATE; 00083 $this->operation = backup::OPERATION_RESTORE; 00084 $this->executiontime = 0; 00085 $this->samesite = false; 00086 $this->checksum = ''; 00087 $this->precheck = null; 00088 00089 // Apply current backup version and release if necessary 00090 backup_controller_dbops::apply_version_and_release(); 00091 00092 // Check courseid is correct 00093 restore_check::check_courseid($this->courseid); 00094 00095 // Check user is correct 00096 restore_check::check_user($this->userid); 00097 00098 // Calculate unique $restoreid 00099 $this->calculate_restoreid(); 00100 00101 // Default logger chain (based on interactive/execution) 00102 $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid); 00103 00104 // Instantiate the output_controller singleton and active it if interactive and inmediate 00105 $oc = output_controller::get_instance(); 00106 if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) { 00107 $oc->set_active(true); 00108 } 00109 00110 $this->log('instantiating restore controller', backup::LOG_INFO, $this->restoreid); 00111 00112 // Set initial status 00113 $this->set_status(backup::STATUS_CREATED); 00114 00115 // Calculate original restore format 00116 $this->format = backup_general_helper::detect_backup_format($tempdir); 00117 00118 // If format is not moodle2, set to conversion needed 00119 if ($this->format !== backup::FORMAT_MOODLE) { 00120 $this->set_status(backup::STATUS_REQUIRE_CONV); 00121 00122 // Else, format is moodle2, load plan, apply security and set status based on interactivity 00123 } else { 00124 // Load plan 00125 $this->load_plan(); 00126 00127 // Perform all initial security checks and apply (2nd param) them to settings automatically 00128 restore_check::check_security($this, true); 00129 00130 if ($this->interactive == backup::INTERACTIVE_YES) { 00131 $this->set_status(backup::STATUS_SETTING_UI); 00132 } else { 00133 $this->set_status(backup::STATUS_NEED_PRECHECK); 00134 } 00135 } 00136 } 00137 00150 public function destroy() { 00151 // Only need to destroy circulars under the plan. Delegate to it. 00152 $this->plan->destroy(); 00153 } 00154 00155 public function finish_ui() { 00156 if ($this->status != backup::STATUS_SETTING_UI) { 00157 throw new restore_controller_exception('cannot_finish_ui_if_not_setting_ui'); 00158 } 00159 $this->set_status(backup::STATUS_NEED_PRECHECK); 00160 } 00161 00162 public function process_ui_event() { 00163 00164 // Perform security checks throwing exceptions (2nd param) if something is wrong 00165 restore_check::check_security($this, false); 00166 } 00167 00168 public function set_status($status) { 00169 $this->log('setting controller status to', backup::LOG_DEBUG, $status); 00170 // TODO: Check it's a correct status 00171 $this->status = $status; 00172 // Ensure that, once set to backup::STATUS_AWAITING | STATUS_NEED_PRECHECK, controller is stored in DB 00173 // Note: never save_controller() after STATUS_EXECUTING or the whole controller, 00174 // containing all the steps will be sent to DB. 100% (monster) useless. 00175 if ($status == backup::STATUS_AWAITING || $status == backup::STATUS_NEED_PRECHECK) { 00176 $this->save_controller(); 00177 $tbc = self::load_controller($this->restoreid); 00178 $this->logger = $tbc->logger; // wakeup loggers 00179 $tbc->destroy(); // Clean temp controller structures 00180 } 00181 } 00182 00183 public function set_execution($execution, $executiontime = 0) { 00184 $this->log('setting controller execution', backup::LOG_DEBUG); 00185 // TODO: Check valid execution mode 00186 // TODO: Check time in future 00187 // TODO: Check time = 0 if inmediate 00188 $this->execution = $execution; 00189 $this->executiontime = $executiontime; 00190 00191 // Default logger chain (based on interactive/execution) 00192 $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid); 00193 } 00194 00195 // checksumable interface methods 00196 00197 public function calculate_checksum() { 00198 // Reset current checksum to take it out from calculations! 00199 $this->checksum = ''; 00200 // Init checksum 00201 $tempchecksum = md5('tempdir-' . $this->tempdir . 00202 'restoreid-' . $this->restoreid . 00203 'courseid-' . $this->courseid . 00204 'type-' . $this->type . 00205 'format-' . $this->format . 00206 'interactive-'. $this->interactive . 00207 'mode-' . $this->mode . 00208 'userid-' . $this->userid . 00209 'target-' . $this->target . 00210 'samesite-' . $this->samesite . 00211 'operation-' . $this->operation . 00212 'status-' . $this->status . 00213 'precheck-' . backup_general_helper::array_checksum_recursive(array($this->precheck)) . 00214 'execution-' . $this->execution . 00215 'plan-' . backup_general_helper::array_checksum_recursive(array($this->plan)) . 00216 'info-' . backup_general_helper::array_checksum_recursive(array($this->info)) . 00217 'logger-' . backup_general_helper::array_checksum_recursive(array($this->logger))); 00218 $this->log('calculating controller checksum', backup::LOG_DEBUG, $tempchecksum); 00219 return $tempchecksum; 00220 } 00221 00222 public function is_checksum_correct($checksum) { 00223 return $this->checksum === $checksum; 00224 } 00225 00226 public function get_tempdir() { 00227 return $this->tempdir; 00228 } 00229 00230 public function get_restoreid() { 00231 return $this->restoreid; 00232 } 00233 00234 public function get_type() { 00235 return $this->type; 00236 } 00237 00238 public function get_operation() { 00239 return $this->operation; 00240 } 00241 00242 public function get_courseid() { 00243 return $this->courseid; 00244 } 00245 00246 public function get_format() { 00247 return $this->format; 00248 } 00249 00250 public function get_interactive() { 00251 return $this->interactive; 00252 } 00253 00254 public function get_mode() { 00255 return $this->mode; 00256 } 00257 00258 public function get_userid() { 00259 return $this->userid; 00260 } 00261 00262 public function get_target() { 00263 return $this->target; 00264 } 00265 00266 public function is_samesite() { 00267 return $this->samesite; 00268 } 00269 00270 public function get_status() { 00271 return $this->status; 00272 } 00273 00274 public function get_execution() { 00275 return $this->execution; 00276 } 00277 00278 public function get_executiontime() { 00279 return $this->executiontime; 00280 } 00281 00286 public function get_plan() { 00287 return $this->plan; 00288 } 00289 00290 public function get_info() { 00291 return $this->info; 00292 } 00293 00294 public function get_logger() { 00295 return $this->logger; 00296 } 00297 00298 public function execute_plan() { 00299 // Basic/initial prevention against time/memory limits 00300 set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted 00301 raise_memory_limit(MEMORY_EXTRA); 00302 // If this is not a course restore, inform the plan we are not 00303 // including all the activities for sure. This will affect any 00304 // task/step executed conditionally to stop processing information 00305 // for section and activity restore. MDL-28180. 00306 if ($this->get_type() !== backup::TYPE_1COURSE) { 00307 $this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG); 00308 $this->plan->set_excluding_activities(); 00309 } 00310 return $this->plan->execute(); 00311 } 00312 00326 public function execute_precheck($droptemptablesafter = false) { 00327 if (is_array($this->precheck)) { 00328 throw new restore_controller_exception('precheck_alredy_executed', $this->status); 00329 } 00330 if ($this->status != backup::STATUS_NEED_PRECHECK) { 00331 throw new restore_controller_exception('cannot_precheck_wrong_status', $this->status); 00332 } 00333 $this->precheck = restore_prechecks_helper::execute_prechecks($this, $droptemptablesafter); 00334 if (!array_key_exists('errors', $this->precheck)) { // No errors, can be executed 00335 $this->set_status(backup::STATUS_AWAITING); 00336 } 00337 if (empty($this->precheck)) { // No errors nor warnings, return true 00338 return true; 00339 } 00340 return false; 00341 } 00342 00343 public function get_results() { 00344 return $this->plan->get_results(); 00345 } 00346 00351 public function precheck_executed() { 00352 return (is_array($this->precheck)); 00353 } 00354 00355 public function get_precheck_results() { 00356 if (!is_array($this->precheck)) { 00357 throw new restore_controller_exception('precheck_not_executed'); 00358 } 00359 return $this->precheck; 00360 } 00361 00362 public function log($message, $level, $a = null, $depth = null, $display = false) { 00363 backup_helper::log($message, $level, $a, $depth, $display, $this->logger); 00364 } 00365 00366 public function save_controller() { 00367 // Going to save controller to persistent storage, calculate checksum for later checks and save it 00368 // TODO: flag the controller as NA. Any operation on it should be forbidden util loaded back 00369 $this->log('saving controller to db', backup::LOG_DEBUG); 00370 $this->checksum = $this->calculate_checksum(); 00371 restore_controller_dbops::save_controller($this, $this->checksum); 00372 } 00373 00374 public static function load_controller($restoreid) { 00375 // Load controller from persistent storage 00376 // TODO: flag the controller as available. Operations on it can continue 00377 $controller = restore_controller_dbops::load_controller($restoreid); 00378 $controller->log('loading controller from db', backup::LOG_DEBUG); 00379 return $controller; 00380 } 00381 00385 public static function get_tempdir_name($courseid = 0, $userid = 0) { 00386 // Current epoch time + courseid + userid + random bits 00387 return md5(time() . '-' . $courseid . '-'. $userid . '-'. random_string(20)); 00388 } 00389 00393 public function convert() { 00394 global $CFG; 00395 require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php'); 00396 00397 // Basic/initial prevention against time/memory limits 00398 set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted 00399 raise_memory_limit(MEMORY_EXTRA); 00400 00401 if ($this->status != backup::STATUS_REQUIRE_CONV) { 00402 throw new restore_controller_exception('cannot_convert_not_required_status'); 00403 } 00404 00405 $this->log('backup format conversion required', backup::LOG_INFO); 00406 00407 // Run conversion to the proper format 00408 if (!convert_helper::to_moodle2_format($this->get_tempdir(), $this->format, $this->get_logger())) { 00409 // todo - unable to find the conversion path, what to do now? 00410 // throwing the exception as a temporary solution 00411 throw new restore_controller_exception('unable_to_find_conversion_path'); 00412 } 00413 00414 $this->log('backup format conversion successful', backup::LOG_INFO); 00415 00416 // If no exceptions were thrown, then we are in the proper format 00417 $this->format = backup::FORMAT_MOODLE; 00418 00419 // Load plan, apply security and set status based on interactivity 00420 $this->load_plan(); 00421 00422 // Perform all initial security checks and apply (2nd param) them to settings automatically 00423 restore_check::check_security($this, true); 00424 00425 if ($this->interactive == backup::INTERACTIVE_YES) { 00426 $this->set_status(backup::STATUS_SETTING_UI); 00427 } else { 00428 $this->set_status(backup::STATUS_NEED_PRECHECK); 00429 } 00430 } 00431 00432 // Protected API starts here 00433 00434 protected function calculate_restoreid() { 00435 // Current epoch time + tempdir + courseid + interactive + mode + userid + target + operation + random bits 00436 $this->restoreid = md5(time() . '-' . $this->tempdir . '-' . $this->courseid . '-'. $this->interactive . '-' . 00437 $this->mode . '-' . $this->userid . '-'. $this->target . '-' . $this->operation . '-' . 00438 random_string(20)); 00439 } 00440 00441 protected function load_plan() { 00442 // First of all, we need to introspect the moodle_backup.xml file 00443 // in order to detect all the required stuff. So, create the 00444 // monster $info structure where everything will be defined 00445 $this->log('loading backup info', backup::LOG_DEBUG); 00446 $this->info = backup_general_helper::get_backup_information($this->tempdir); 00447 00448 // Set the controller type to the one found in the information 00449 $this->type = $this->info->type; 00450 00451 // Set the controller samesite flag as needed 00452 $this->samesite = backup_general_helper::backup_is_samesite($this->info); 00453 00454 // Now we load the plan that will be configured following the 00455 // information provided by the $info 00456 $this->log('loading controller plan', backup::LOG_DEBUG); 00457 $this->plan = new restore_plan($this); 00458 $this->plan->build(); // Build plan for this controller 00459 $this->set_status(backup::STATUS_PLANNED); 00460 } 00461 } 00462 00463 /* 00464 * Exception class used by all the @restore_controller stuff 00465 */ 00466 class restore_controller_exception extends backup_exception { 00467 00468 public function __construct($errorcode, $a=NULL, $debuginfo=null) { 00469 parent::__construct($errorcode, $a, $debuginfo); 00470 } 00471 }