| 1 | efrain | 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 $CFG->backuptempdir 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 |     /** @var restore_plan */
 | 
        
           |  |  | 54 |     protected $plan;   // Restore execution plan
 | 
        
           |  |  | 55 |   | 
        
           |  |  | 56 |     /**
 | 
        
           |  |  | 57 |      * Immediate/delayed execution type.
 | 
        
           |  |  | 58 |      * @var integer
 | 
        
           |  |  | 59 |      */
 | 
        
           |  |  | 60 |     protected $execution;
 | 
        
           |  |  | 61 |     protected $executiontime; // epoch time when we want the restore to be executed (requires cron to run)
 | 
        
           |  |  | 62 |   | 
        
           |  |  | 63 |     protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
 | 
        
           |  |  | 64 |   | 
        
           |  |  | 65 |     /** @var int Number of restore_controllers that are currently executing */
 | 
        
           |  |  | 66 |     protected static $executing = 0;
 | 
        
           |  |  | 67 |   | 
        
           |  |  | 68 |     /**
 | 
        
           |  |  | 69 |      * Holds the relevant destination information for course copy operations.
 | 
        
           |  |  | 70 |      *
 | 
        
           |  |  | 71 |      * @var \stdClass.
 | 
        
           |  |  | 72 |      */
 | 
        
           |  |  | 73 |     protected $copy;
 | 
        
           |  |  | 74 |   | 
        
           |  |  | 75 |     /**
 | 
        
           |  |  | 76 |      * Constructor.
 | 
        
           |  |  | 77 |      *
 | 
        
           |  |  | 78 |      * If you specify a progress monitor, this will be used to report progress
 | 
        
           |  |  | 79 |      * while loading the plan, as well as for future use. (You can change it
 | 
        
           |  |  | 80 |      * for a different one later using set_progress.)
 | 
        
           |  |  | 81 |      *
 | 
        
           |  |  | 82 |      * @param string $tempdir Directory under $CFG->backuptempdir awaiting restore
 | 
        
           |  |  | 83 |      * @param int $courseid Course id where restore is going to happen
 | 
        
           |  |  | 84 |      * @param bool $interactive backup::INTERACTIVE_YES[true] or backup::INTERACTIVE_NO[false]
 | 
        
           |  |  | 85 |      * @param int $mode backup::MODE_[ GENERAL | HUB | IMPORT | SAMESITE ]
 | 
        
           |  |  | 86 |      * @param int $userid
 | 
        
           |  |  | 87 |      * @param int $target backup::TARGET_[ NEW_COURSE | CURRENT_ADDING | CURRENT_DELETING | EXISTING_ADDING | EXISTING_DELETING ]
 | 
        
           |  |  | 88 |      * @param \core\progress\base $progress Optional progress monitor
 | 
        
           |  |  | 89 |      * @param \stdClass $copydata Course copy data, required when in MODE_COPY
 | 
        
           |  |  | 90 |      * @param bool $releasesession Should release the session? backup::RELEASESESSION_YES or backup::RELEASESESSION_NO
 | 
        
           |  |  | 91 |      */
 | 
        
           |  |  | 92 |     public function __construct($tempdir, $courseid, $interactive, $mode, $userid, $target,
 | 
        
           | 1441 | ariadna | 93 |             ?\core\progress\base $progress = null, $releasesession = backup::RELEASESESSION_NO, ?\stdClass $copydata = null) {
 | 
        
           | 1 | efrain | 94 |   | 
        
           |  |  | 95 |         if ($mode == backup::MODE_COPY && is_null($copydata)) {
 | 
        
           |  |  | 96 |             throw new restore_controller_exception('cannot_instantiate_missing_copydata');
 | 
        
           |  |  | 97 |         }
 | 
        
           |  |  | 98 |   | 
        
           |  |  | 99 |         $this->copy = $copydata;
 | 
        
           |  |  | 100 |         $this->tempdir = $tempdir;
 | 
        
           |  |  | 101 |         $this->courseid = $courseid;
 | 
        
           |  |  | 102 |         $this->interactive = $interactive;
 | 
        
           |  |  | 103 |         $this->mode = $mode;
 | 
        
           |  |  | 104 |         $this->userid = $userid;
 | 
        
           |  |  | 105 |         $this->target = $target;
 | 
        
           |  |  | 106 |         $this->releasesession = $releasesession;
 | 
        
           |  |  | 107 |   | 
        
           |  |  | 108 |         // Apply some defaults
 | 
        
           |  |  | 109 |         $this->type = '';
 | 
        
           |  |  | 110 |         $this->format = backup::FORMAT_UNKNOWN;
 | 
        
           |  |  | 111 |         $this->operation = backup::OPERATION_RESTORE;
 | 
        
           |  |  | 112 |         $this->executiontime = 0;
 | 
        
           |  |  | 113 |         $this->samesite = false;
 | 
        
           |  |  | 114 |         $this->checksum = '';
 | 
        
           |  |  | 115 |         $this->precheck = null;
 | 
        
           |  |  | 116 |   | 
        
           |  |  | 117 |         // Apply current backup version and release if necessary
 | 
        
           |  |  | 118 |         backup_controller_dbops::apply_version_and_release();
 | 
        
           |  |  | 119 |   | 
        
           |  |  | 120 |         // Check courseid is correct
 | 
        
           |  |  | 121 |         restore_check::check_courseid($this->courseid);
 | 
        
           |  |  | 122 |   | 
        
           |  |  | 123 |         // Check user is correct
 | 
        
           |  |  | 124 |         restore_check::check_user($this->userid);
 | 
        
           |  |  | 125 |   | 
        
           |  |  | 126 |         // Calculate unique $restoreid
 | 
        
           |  |  | 127 |         $this->calculate_restoreid();
 | 
        
           |  |  | 128 |   | 
        
           |  |  | 129 |         // Default logger chain (based on interactive/execution)
 | 
        
           |  |  | 130 |         $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid);
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |         // Set execution based on backup mode.
 | 
        
           |  |  | 133 |         if ($mode == backup::MODE_ASYNC || $mode == backup::MODE_COPY) {
 | 
        
           |  |  | 134 |             $this->execution = backup::EXECUTION_DELAYED;
 | 
        
           |  |  | 135 |         } else {
 | 
        
           |  |  | 136 |             $this->execution = backup::EXECUTION_INMEDIATE;
 | 
        
           |  |  | 137 |         }
 | 
        
           |  |  | 138 |   | 
        
           |  |  | 139 |         // By default there is no progress reporter unless you specify one so it
 | 
        
           |  |  | 140 |         // can be used during loading of the plan.
 | 
        
           |  |  | 141 |         if ($progress) {
 | 
        
           |  |  | 142 |             $this->progress = $progress;
 | 
        
           |  |  | 143 |         } else {
 | 
        
           |  |  | 144 |             $this->progress = new \core\progress\none();
 | 
        
           |  |  | 145 |         }
 | 
        
           |  |  | 146 |         $this->progress->start_progress('Constructing restore_controller');
 | 
        
           |  |  | 147 |   | 
        
           |  |  | 148 |         // Instantiate the output_controller singleton and active it if interactive and immediate.
 | 
        
           |  |  | 149 |         $oc = output_controller::get_instance();
 | 
        
           |  |  | 150 |         if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
 | 
        
           |  |  | 151 |             $oc->set_active(true);
 | 
        
           |  |  | 152 |         }
 | 
        
           |  |  | 153 |   | 
        
           |  |  | 154 |         $this->log('instantiating restore controller', backup::LOG_INFO, $this->restoreid);
 | 
        
           |  |  | 155 |   | 
        
           |  |  | 156 |         // Set initial status
 | 
        
           |  |  | 157 |         $this->set_status(backup::STATUS_CREATED);
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 |         // Calculate original restore format
 | 
        
           |  |  | 160 |         $this->format = backup_general_helper::detect_backup_format($tempdir);
 | 
        
           |  |  | 161 |   | 
        
           |  |  | 162 |         // If format is not moodle2, set to conversion needed
 | 
        
           |  |  | 163 |         if ($this->format !== backup::FORMAT_MOODLE) {
 | 
        
           |  |  | 164 |             $this->set_status(backup::STATUS_REQUIRE_CONV);
 | 
        
           |  |  | 165 |   | 
        
           |  |  | 166 |         // Else, format is moodle2, load plan, apply security and set status based on interactivity
 | 
        
           |  |  | 167 |         } else {
 | 
        
           |  |  | 168 |             // Load plan
 | 
        
           |  |  | 169 |             $this->load_plan();
 | 
        
           |  |  | 170 |   | 
        
           |  |  | 171 |             // Apply all default settings (based on type/format/mode).
 | 
        
           |  |  | 172 |             $this->apply_defaults();
 | 
        
           |  |  | 173 |   | 
        
           |  |  | 174 |             // Perform all initial security checks and apply (2nd param) them to settings automatically
 | 
        
           |  |  | 175 |             restore_check::check_security($this, true);
 | 
        
           |  |  | 176 |   | 
        
           |  |  | 177 |             if ($this->interactive == backup::INTERACTIVE_YES) {
 | 
        
           |  |  | 178 |                 $this->set_status(backup::STATUS_SETTING_UI);
 | 
        
           |  |  | 179 |             } else {
 | 
        
           |  |  | 180 |                 $this->set_status(backup::STATUS_NEED_PRECHECK);
 | 
        
           |  |  | 181 |             }
 | 
        
           |  |  | 182 |         }
 | 
        
           |  |  | 183 |   | 
        
           |  |  | 184 |         // Tell progress monitor that we finished loading.
 | 
        
           |  |  | 185 |         $this->progress->end_progress();
 | 
        
           |  |  | 186 |     }
 | 
        
           |  |  | 187 |   | 
        
           |  |  | 188 |     /**
 | 
        
           |  |  | 189 |      * Clean structures used by the restore_controller
 | 
        
           |  |  | 190 |      *
 | 
        
           |  |  | 191 |      * This method clean various structures used by the restore_controller,
 | 
        
           |  |  | 192 |      * destroying them in an ordered way, so their memory will be gc properly
 | 
        
           |  |  | 193 |      * by PHP (mainly circular references).
 | 
        
           |  |  | 194 |      *
 | 
        
           |  |  | 195 |      * Note that, while it's not mandatory to execute this method, it's highly
 | 
        
           |  |  | 196 |      * recommended to do so, specially in scripts performing multiple operations
 | 
        
           |  |  | 197 |      * (like the automated backups) or the system will run out of memory after
 | 
        
           |  |  | 198 |      * a few dozens of backups)
 | 
        
           |  |  | 199 |      */
 | 
        
           |  |  | 200 |     public function destroy() {
 | 
        
           |  |  | 201 |         // Only need to destroy circulars under the plan. Delegate to it.
 | 
        
           |  |  | 202 |         $this->plan->destroy();
 | 
        
           |  |  | 203 |         // Loggers may have also chained references, destroy them. Also closing resources when needed.
 | 
        
           |  |  | 204 |         $this->logger->destroy();
 | 
        
           |  |  | 205 |     }
 | 
        
           |  |  | 206 |   | 
        
           |  |  | 207 |     public function finish_ui() {
 | 
        
           |  |  | 208 |         if ($this->status != backup::STATUS_SETTING_UI) {
 | 
        
           |  |  | 209 |             throw new restore_controller_exception('cannot_finish_ui_if_not_setting_ui');
 | 
        
           |  |  | 210 |         }
 | 
        
           |  |  | 211 |         $this->set_status(backup::STATUS_NEED_PRECHECK);
 | 
        
           |  |  | 212 |     }
 | 
        
           |  |  | 213 |   | 
        
           |  |  | 214 |     public function process_ui_event() {
 | 
        
           |  |  | 215 |   | 
        
           |  |  | 216 |         // Perform security checks throwing exceptions (2nd param) if something is wrong
 | 
        
           |  |  | 217 |         restore_check::check_security($this, false);
 | 
        
           |  |  | 218 |     }
 | 
        
           |  |  | 219 |   | 
        
           |  |  | 220 |     public function set_status($status) {
 | 
        
           |  |  | 221 |         // Note: never save_controller() with the object info after STATUS_EXECUTING or the whole controller,
 | 
        
           |  |  | 222 |         // containing all the steps will be sent to DB. 100% (monster) useless.
 | 
        
           |  |  | 223 |         $this->log('setting controller status to', backup::LOG_DEBUG, $status);
 | 
        
           |  |  | 224 |         // TODO: Check it's a correct status.
 | 
        
           |  |  | 225 |         $this->status = $status;
 | 
        
           |  |  | 226 |         // Ensure that, once set to backup::STATUS_AWAITING | STATUS_NEED_PRECHECK, controller is stored in DB.
 | 
        
           |  |  | 227 |         // Also save if executing so we can better track progress.
 | 
        
           |  |  | 228 |         if ($status == backup::STATUS_AWAITING || $status == backup::STATUS_NEED_PRECHECK || $status == backup::STATUS_EXECUTING) {
 | 
        
           |  |  | 229 |             $this->save_controller();
 | 
        
           |  |  | 230 |             $tbc = self::load_controller($this->restoreid);
 | 
        
           |  |  | 231 |             $this->logger = $tbc->logger; // wakeup loggers
 | 
        
           |  |  | 232 |             $tbc->plan->destroy(); // Clean plan controller structures, keeping logger alive.
 | 
        
           |  |  | 233 |   | 
        
           |  |  | 234 |         } else if ($status == backup::STATUS_FINISHED_OK) {
 | 
        
           |  |  | 235 |             // If the operation has ended without error (backup::STATUS_FINISHED_OK)
 | 
        
           |  |  | 236 |             // proceed by cleaning the object from database. MDL-29262.
 | 
        
           |  |  | 237 |             $this->save_controller(false, true);
 | 
        
           |  |  | 238 |         } else if ($status == backup::STATUS_FINISHED_ERR) {
 | 
        
           |  |  | 239 |             // If the operation has ended with an error save the controller
 | 
        
           |  |  | 240 |             // preserving the object in the database. We may want it for debugging.
 | 
        
           |  |  | 241 |             $this->save_controller();
 | 
        
           |  |  | 242 |         }
 | 
        
           |  |  | 243 |     }
 | 
        
           |  |  | 244 |   | 
        
           |  |  | 245 |     public function set_execution($execution, $executiontime = 0) {
 | 
        
           |  |  | 246 |         $this->log('setting controller execution', backup::LOG_DEBUG);
 | 
        
           |  |  | 247 |         // TODO: Check valid execution mode.
 | 
        
           |  |  | 248 |         // TODO: Check time in future.
 | 
        
           |  |  | 249 |         // TODO: Check time = 0 if immediate.
 | 
        
           |  |  | 250 |         $this->execution = $execution;
 | 
        
           |  |  | 251 |         $this->executiontime = $executiontime;
 | 
        
           |  |  | 252 |   | 
        
           |  |  | 253 |         // Default logger chain (based on interactive/execution)
 | 
        
           |  |  | 254 |         $this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid);
 | 
        
           |  |  | 255 |     }
 | 
        
           |  |  | 256 |   | 
        
           |  |  | 257 | // checksumable interface methods
 | 
        
           |  |  | 258 |   | 
        
           |  |  | 259 |     public function calculate_checksum() {
 | 
        
           |  |  | 260 |         // Reset current checksum to take it out from calculations!
 | 
        
           |  |  | 261 |         $this->checksum = '';
 | 
        
           |  |  | 262 |         // Init checksum
 | 
        
           |  |  | 263 |         $tempchecksum = md5('tempdir-'    . $this->tempdir .
 | 
        
           |  |  | 264 |                             'restoreid-'  . $this->restoreid .
 | 
        
           |  |  | 265 |                             'courseid-'   . $this->courseid .
 | 
        
           |  |  | 266 |                             'type-'       . $this->type .
 | 
        
           |  |  | 267 |                             'format-'     . $this->format .
 | 
        
           |  |  | 268 |                             'interactive-'. $this->interactive .
 | 
        
           |  |  | 269 |                             'mode-'       . $this->mode .
 | 
        
           |  |  | 270 |                             'userid-'     . $this->userid .
 | 
        
           |  |  | 271 |                             'target-'     . $this->target .
 | 
        
           |  |  | 272 |                             'samesite-'   . $this->samesite .
 | 
        
           |  |  | 273 |                             'operation-'  . $this->operation .
 | 
        
           |  |  | 274 |                             'status-'     . $this->status .
 | 
        
           |  |  | 275 |                             'precheck-'   . backup_general_helper::array_checksum_recursive(array($this->precheck)) .
 | 
        
           |  |  | 276 |                             'execution-'  . $this->execution .
 | 
        
           |  |  | 277 |                             'plan-'       . backup_general_helper::array_checksum_recursive(array($this->plan)) .
 | 
        
           |  |  | 278 |                             'info-'       . backup_general_helper::array_checksum_recursive(array($this->info)) .
 | 
        
           |  |  | 279 |                             'logger-'     . backup_general_helper::array_checksum_recursive(array($this->logger)));
 | 
        
           |  |  | 280 |         $this->log('calculating controller checksum', backup::LOG_DEBUG, $tempchecksum);
 | 
        
           |  |  | 281 |         return $tempchecksum;
 | 
        
           |  |  | 282 |     }
 | 
        
           |  |  | 283 |   | 
        
           |  |  | 284 |     public function is_checksum_correct($checksum) {
 | 
        
           |  |  | 285 |         return $this->checksum === $checksum;
 | 
        
           |  |  | 286 |     }
 | 
        
           |  |  | 287 |   | 
        
           |  |  | 288 |     public function get_tempdir() {
 | 
        
           |  |  | 289 |         return $this->tempdir;
 | 
        
           |  |  | 290 |     }
 | 
        
           |  |  | 291 |   | 
        
           |  |  | 292 |     public function get_restoreid() {
 | 
        
           |  |  | 293 |         return $this->restoreid;
 | 
        
           |  |  | 294 |     }
 | 
        
           |  |  | 295 |   | 
        
           |  |  | 296 |     public function get_type() {
 | 
        
           |  |  | 297 |         return $this->type;
 | 
        
           |  |  | 298 |     }
 | 
        
           |  |  | 299 |   | 
        
           |  |  | 300 |     public function get_operation() {
 | 
        
           |  |  | 301 |         return $this->operation;
 | 
        
           |  |  | 302 |     }
 | 
        
           |  |  | 303 |   | 
        
           |  |  | 304 |     public function get_courseid() {
 | 
        
           |  |  | 305 |         return $this->courseid;
 | 
        
           |  |  | 306 |     }
 | 
        
           |  |  | 307 |   | 
        
           |  |  | 308 |     public function get_format() {
 | 
        
           |  |  | 309 |         return $this->format;
 | 
        
           |  |  | 310 |     }
 | 
        
           |  |  | 311 |   | 
        
           |  |  | 312 |     public function get_interactive() {
 | 
        
           |  |  | 313 |         return $this->interactive;
 | 
        
           |  |  | 314 |     }
 | 
        
           |  |  | 315 |   | 
        
           |  |  | 316 |     public function get_mode() {
 | 
        
           |  |  | 317 |         return $this->mode;
 | 
        
           |  |  | 318 |     }
 | 
        
           |  |  | 319 |   | 
        
           |  |  | 320 |     public function get_userid() {
 | 
        
           |  |  | 321 |         return $this->userid;
 | 
        
           |  |  | 322 |     }
 | 
        
           |  |  | 323 |   | 
        
           |  |  | 324 |     public function get_target() {
 | 
        
           |  |  | 325 |         return $this->target;
 | 
        
           |  |  | 326 |     }
 | 
        
           |  |  | 327 |   | 
        
           |  |  | 328 |     public function is_samesite() {
 | 
        
           |  |  | 329 |         return $this->samesite;
 | 
        
           |  |  | 330 |     }
 | 
        
           |  |  | 331 |   | 
        
           |  |  | 332 |     public function get_status() {
 | 
        
           |  |  | 333 |         return $this->status;
 | 
        
           |  |  | 334 |     }
 | 
        
           |  |  | 335 |   | 
        
           |  |  | 336 |     public function get_execution() {
 | 
        
           |  |  | 337 |         return $this->execution;
 | 
        
           |  |  | 338 |     }
 | 
        
           |  |  | 339 |   | 
        
           |  |  | 340 |     public function get_executiontime() {
 | 
        
           |  |  | 341 |         return $this->executiontime;
 | 
        
           |  |  | 342 |     }
 | 
        
           |  |  | 343 |   | 
        
           |  |  | 344 |     /**
 | 
        
           |  |  | 345 |      * Returns the restore plan
 | 
        
           |  |  | 346 |      * @return restore_plan
 | 
        
           |  |  | 347 |      */
 | 
        
           |  |  | 348 |     public function get_plan() {
 | 
        
           |  |  | 349 |         return $this->plan;
 | 
        
           |  |  | 350 |     }
 | 
        
           |  |  | 351 |     /**
 | 
        
           |  |  | 352 |      * Gets the value for the requested setting
 | 
        
           |  |  | 353 |      *
 | 
        
           |  |  | 354 |      * @param string $name
 | 
        
           |  |  | 355 |      * @param bool $default
 | 
        
           |  |  | 356 |      * @return mixed
 | 
        
           |  |  | 357 |      */
 | 
        
           |  |  | 358 |     public function get_setting_value($name, $default = false) {
 | 
        
           |  |  | 359 |         try {
 | 
        
           |  |  | 360 |             return $this->get_plan()->get_setting($name)->get_value();
 | 
        
           |  |  | 361 |         } catch (Exception $e) {
 | 
        
           |  |  | 362 |             debugging('Failed to find the setting: '.$name, DEBUG_DEVELOPER);
 | 
        
           |  |  | 363 |             return $default;
 | 
        
           |  |  | 364 |         }
 | 
        
           |  |  | 365 |     }
 | 
        
           |  |  | 366 |   | 
        
           |  |  | 367 |     /**
 | 
        
           |  |  | 368 |      * For debug only. Get a simple test display of all the settings.
 | 
        
           |  |  | 369 |      *
 | 
        
           |  |  | 370 |      * @return string
 | 
        
           |  |  | 371 |      */
 | 
        
           |  |  | 372 |     public function debug_display_all_settings_values(): string {
 | 
        
           |  |  | 373 |         return $this->get_plan()->debug_display_all_settings_values();
 | 
        
           |  |  | 374 |     }
 | 
        
           |  |  | 375 |   | 
        
           |  |  | 376 |     public function get_info() {
 | 
        
           |  |  | 377 |         return $this->info;
 | 
        
           |  |  | 378 |     }
 | 
        
           |  |  | 379 |   | 
        
           |  |  | 380 |     public function execute_plan() {
 | 
        
           |  |  | 381 |         // Basic/initial prevention against time/memory limits
 | 
        
           |  |  | 382 |         core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
 | 
        
           |  |  | 383 |         raise_memory_limit(MEMORY_EXTRA);
 | 
        
           |  |  | 384 |   | 
        
           |  |  | 385 |         // Release the session so other tabs in the same session are not blocked.
 | 
        
           |  |  | 386 |         if ($this->get_releasesession() === backup::RELEASESESSION_YES) {
 | 
        
           |  |  | 387 |             // Preemptively reset the navcache before closing, so it remains the same on shutdown.
 | 
        
           |  |  | 388 |             navigation_cache::destroy_volatile_caches();
 | 
        
           |  |  | 389 |   | 
        
           |  |  | 390 |             \core\session\manager::write_close();
 | 
        
           |  |  | 391 |         }
 | 
        
           |  |  | 392 |   | 
        
           |  |  | 393 |         // Do course cleanup precheck, if required. This was originally in restore_ui. Moved to handle async backup/restore.
 | 
        
           |  |  | 394 |         if ($this->get_target() == backup::TARGET_CURRENT_DELETING || $this->get_target() == backup::TARGET_EXISTING_DELETING) {
 | 
        
           |  |  | 395 |             $options = array();
 | 
        
           |  |  | 396 |             $options['keep_roles_and_enrolments'] = $this->get_setting_value('keep_roles_and_enrolments');
 | 
        
           |  |  | 397 |             $options['keep_groups_and_groupings'] = $this->get_setting_value('keep_groups_and_groupings');
 | 
        
           |  |  | 398 |             $options['userid'] = $this->userid;
 | 
        
           |  |  | 399 |             restore_dbops::delete_course_content($this->get_courseid(), $options);
 | 
        
           |  |  | 400 |         }
 | 
        
           |  |  | 401 |         // If this is not a course restore or single activity restore (e.g. duplicate), inform the plan we are not
 | 
        
           |  |  | 402 |         // including all the activities for sure. This will affect any
 | 
        
           |  |  | 403 |         // task/step executed conditionally to stop processing information
 | 
        
           |  |  | 404 |         // for section and activity restore. MDL-28180.
 | 
        
           |  |  | 405 |         if ($this->get_type() !== backup::TYPE_1COURSE && $this->get_type() !== backup::TYPE_1ACTIVITY) {
 | 
        
           |  |  | 406 |             $this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
 | 
        
           |  |  | 407 |             $this->plan->set_excluding_activities();
 | 
        
           |  |  | 408 |         }
 | 
        
           |  |  | 409 |         self::$executing++;
 | 
        
           |  |  | 410 |         try {
 | 
        
           |  |  | 411 |             $this->plan->execute();
 | 
        
           |  |  | 412 |         } catch (Exception $e) {
 | 
        
           |  |  | 413 |             self::$executing--;
 | 
        
           |  |  | 414 |             throw $e;
 | 
        
           |  |  | 415 |         }
 | 
        
           |  |  | 416 |         self::$executing--;
 | 
        
           |  |  | 417 |     }
 | 
        
           |  |  | 418 |   | 
        
           |  |  | 419 |     /**
 | 
        
           |  |  | 420 |      * Checks whether restore is currently executing. Certain parts of code that
 | 
        
           |  |  | 421 |      * is called during restore, but not directly part of the restore system, may
 | 
        
           |  |  | 422 |      * need to behave differently during restore (e.g. do not bother resetting a
 | 
        
           |  |  | 423 |      * cache because we know it will be reset at end of operation).
 | 
        
           |  |  | 424 |      *
 | 
        
           |  |  | 425 |      * @return bool True if any restore is currently executing
 | 
        
           |  |  | 426 |      */
 | 
        
           |  |  | 427 |     public static function is_executing() {
 | 
        
           |  |  | 428 |         return self::$executing > 0;
 | 
        
           |  |  | 429 |     }
 | 
        
           |  |  | 430 |   | 
        
           |  |  | 431 |     /**
 | 
        
           |  |  | 432 |      * Execute the restore prechecks to detect any problem before proceed with restore
 | 
        
           |  |  | 433 |      *
 | 
        
           |  |  | 434 |      * This function checks various parts of the restore (versions, users, roles...)
 | 
        
           |  |  | 435 |      * returning true if everything was ok or false if any warning/error was detected.
 | 
        
           |  |  | 436 |      * Any warning/error is returned by the get_precheck_results() method.
 | 
        
           |  |  | 437 |      * Note: if any problem is found it will, automatically, drop all the restore temp
 | 
        
           |  |  | 438 |      * tables as far as the next step is to inform about the warning/errors. If no problem
 | 
        
           |  |  | 439 |      * is found, then default behaviour is to keep the temp tables so, in the same request
 | 
        
           |  |  | 440 |      * restore will be executed, saving a lot of checks to be executed again.
 | 
        
           |  |  | 441 |      * Note: If for any reason (UI to show after prechecks...) you want to force temp tables
 | 
        
           |  |  | 442 |      * to be dropped always, you can pass true to the $droptemptablesafter parameter
 | 
        
           |  |  | 443 |      */
 | 
        
           |  |  | 444 |     public function execute_precheck($droptemptablesafter = false) {
 | 
        
           |  |  | 445 |         if (is_array($this->precheck)) {
 | 
        
           |  |  | 446 |             throw new restore_controller_exception('precheck_alredy_executed', $this->status);
 | 
        
           |  |  | 447 |         }
 | 
        
           |  |  | 448 |         if ($this->status != backup::STATUS_NEED_PRECHECK) {
 | 
        
           |  |  | 449 |             throw new restore_controller_exception('cannot_precheck_wrong_status', $this->status);
 | 
        
           |  |  | 450 |         }
 | 
        
           |  |  | 451 |         // Basic/initial prevention against time/memory limits
 | 
        
           |  |  | 452 |         core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
 | 
        
           |  |  | 453 |         raise_memory_limit(MEMORY_EXTRA);
 | 
        
           |  |  | 454 |         $this->precheck = restore_prechecks_helper::execute_prechecks($this, $droptemptablesafter);
 | 
        
           |  |  | 455 |         if (!array_key_exists('errors', $this->precheck)) { // No errors, can be executed
 | 
        
           |  |  | 456 |             $this->set_status(backup::STATUS_AWAITING);
 | 
        
           |  |  | 457 |         }
 | 
        
           |  |  | 458 |         if (empty($this->precheck)) { // No errors nor warnings, return true
 | 
        
           |  |  | 459 |             return true;
 | 
        
           |  |  | 460 |         }
 | 
        
           |  |  | 461 |         return false;
 | 
        
           |  |  | 462 |     }
 | 
        
           |  |  | 463 |   | 
        
           |  |  | 464 |     public function get_results() {
 | 
        
           |  |  | 465 |         return $this->plan->get_results();
 | 
        
           |  |  | 466 |     }
 | 
        
           |  |  | 467 |   | 
        
           |  |  | 468 |     /**
 | 
        
           |  |  | 469 |      * Returns true if the prechecks have been executed
 | 
        
           |  |  | 470 |      * @return bool
 | 
        
           |  |  | 471 |      */
 | 
        
           |  |  | 472 |     public function precheck_executed() {
 | 
        
           |  |  | 473 |         return (is_array($this->precheck));
 | 
        
           |  |  | 474 |     }
 | 
        
           |  |  | 475 |   | 
        
           |  |  | 476 |     public function get_precheck_results() {
 | 
        
           |  |  | 477 |         if (!is_array($this->precheck)) {
 | 
        
           |  |  | 478 |             throw new restore_controller_exception('precheck_not_executed');
 | 
        
           |  |  | 479 |         }
 | 
        
           |  |  | 480 |         return $this->precheck;
 | 
        
           |  |  | 481 |     }
 | 
        
           |  |  | 482 |   | 
        
           |  |  | 483 |     /**
 | 
        
           |  |  | 484 |      * Save controller information
 | 
        
           |  |  | 485 |      *
 | 
        
           |  |  | 486 |      * @param bool $includeobj to decide if the object itself must be updated (true) or no (false)
 | 
        
           |  |  | 487 |      * @param bool $cleanobj to decide if the object itself must be cleaned (true) or no (false)
 | 
        
           |  |  | 488 |      */
 | 
        
           |  |  | 489 |     public function save_controller($includeobj = true, $cleanobj = false) {
 | 
        
           |  |  | 490 |         // Going to save controller to persistent storage, calculate checksum for later checks and save it
 | 
        
           |  |  | 491 |         // TODO: flag the controller as NA. Any operation on it should be forbidden util loaded back
 | 
        
           |  |  | 492 |         $this->log('saving controller to db', backup::LOG_DEBUG);
 | 
        
           |  |  | 493 |         if ($includeobj ) {  // Only calculate checksum if we are going to include the object.
 | 
        
           |  |  | 494 |             $this->checksum = $this->calculate_checksum();
 | 
        
           |  |  | 495 |         }
 | 
        
           |  |  | 496 |         restore_controller_dbops::save_controller($this, $this->checksum, $includeobj, $cleanobj);
 | 
        
           |  |  | 497 |     }
 | 
        
           |  |  | 498 |   | 
        
           |  |  | 499 |     public static function load_controller($restoreid) {
 | 
        
           |  |  | 500 |         // Load controller from persistent storage
 | 
        
           |  |  | 501 |         // TODO: flag the controller as available. Operations on it can continue
 | 
        
           |  |  | 502 |         $controller = restore_controller_dbops::load_controller($restoreid);
 | 
        
           |  |  | 503 |         $controller->log('loading controller from db', backup::LOG_DEBUG);
 | 
        
           |  |  | 504 |         return $controller;
 | 
        
           |  |  | 505 |     }
 | 
        
           |  |  | 506 |   | 
        
           |  |  | 507 |     /**
 | 
        
           |  |  | 508 |      * class method to provide pseudo random unique "correct" tempdir names
 | 
        
           |  |  | 509 |      */
 | 
        
           |  |  | 510 |     public static function get_tempdir_name($courseid = 0, $userid = 0) {
 | 
        
           |  |  | 511 |         // Current epoch time + courseid + userid + random bits
 | 
        
           |  |  | 512 |         return md5(time() . '-' . $courseid . '-'. $userid . '-'. random_string(20));
 | 
        
           |  |  | 513 |     }
 | 
        
           |  |  | 514 |   | 
        
           |  |  | 515 |     /**
 | 
        
           |  |  | 516 |      * Converts from current format to backup::MOODLE format
 | 
        
           |  |  | 517 |      */
 | 
        
           |  |  | 518 |     public function convert() {
 | 
        
           |  |  | 519 |         global $CFG;
 | 
        
           |  |  | 520 |         require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php');
 | 
        
           |  |  | 521 |   | 
        
           |  |  | 522 |         // Basic/initial prevention against time/memory limits
 | 
        
           |  |  | 523 |         core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
 | 
        
           |  |  | 524 |         raise_memory_limit(MEMORY_EXTRA);
 | 
        
           |  |  | 525 |         $this->progress->start_progress('Backup format conversion');
 | 
        
           |  |  | 526 |   | 
        
           |  |  | 527 |         if ($this->status != backup::STATUS_REQUIRE_CONV) {
 | 
        
           |  |  | 528 |             throw new restore_controller_exception('cannot_convert_not_required_status');
 | 
        
           |  |  | 529 |         }
 | 
        
           |  |  | 530 |   | 
        
           |  |  | 531 |         $this->log('backup format conversion required', backup::LOG_INFO);
 | 
        
           |  |  | 532 |   | 
        
           |  |  | 533 |         // Run conversion to the proper format
 | 
        
           |  |  | 534 |         if (!convert_helper::to_moodle2_format($this->get_tempdir(), $this->format, $this->get_logger())) {
 | 
        
           |  |  | 535 |             // todo - unable to find the conversion path, what to do now?
 | 
        
           |  |  | 536 |             // throwing the exception as a temporary solution
 | 
        
           |  |  | 537 |             throw new restore_controller_exception('unable_to_find_conversion_path');
 | 
        
           |  |  | 538 |         }
 | 
        
           |  |  | 539 |   | 
        
           |  |  | 540 |         $this->log('backup format conversion successful', backup::LOG_INFO);
 | 
        
           |  |  | 541 |   | 
        
           |  |  | 542 |         // If no exceptions were thrown, then we are in the proper format
 | 
        
           |  |  | 543 |         $this->format = backup::FORMAT_MOODLE;
 | 
        
           |  |  | 544 |   | 
        
           |  |  | 545 |         // Load plan, apply security and set status based on interactivity
 | 
        
           |  |  | 546 |         $this->load_plan();
 | 
        
           |  |  | 547 |   | 
        
           |  |  | 548 |         // Perform all initial security checks and apply (2nd param) them to settings automatically
 | 
        
           |  |  | 549 |         restore_check::check_security($this, true);
 | 
        
           |  |  | 550 |   | 
        
           |  |  | 551 |         if ($this->interactive == backup::INTERACTIVE_YES) {
 | 
        
           |  |  | 552 |             $this->set_status(backup::STATUS_SETTING_UI);
 | 
        
           |  |  | 553 |         } else {
 | 
        
           |  |  | 554 |             $this->set_status(backup::STATUS_NEED_PRECHECK);
 | 
        
           |  |  | 555 |         }
 | 
        
           |  |  | 556 |         $this->progress->end_progress();
 | 
        
           |  |  | 557 |     }
 | 
        
           |  |  | 558 |   | 
        
           |  |  | 559 |     /**
 | 
        
           |  |  | 560 |      * Do the necessary copy preparation actions.
 | 
        
           |  |  | 561 |      * This method should only be called once the backup of a copy operation is completed.
 | 
        
           |  |  | 562 |      *
 | 
        
           |  |  | 563 |      * @throws restore_controller_exception
 | 
        
           |  |  | 564 |      */
 | 
        
           |  |  | 565 |     public function prepare_copy(): void {
 | 
        
           |  |  | 566 |         // Check that we are in the correct mode.
 | 
        
           |  |  | 567 |         if ($this->mode != backup::MODE_COPY) {
 | 
        
           |  |  | 568 |             throw new restore_controller_exception('cannot_prepare_copy_wrong_mode');
 | 
        
           |  |  | 569 |         }
 | 
        
           |  |  | 570 |   | 
        
           |  |  | 571 |         $this->progress->start_progress('Prepare Copy');
 | 
        
           |  |  | 572 |   | 
        
           |  |  | 573 |         // If no exceptions were thrown, then we are in the proper format.
 | 
        
           |  |  | 574 |         $this->format = backup::FORMAT_MOODLE;
 | 
        
           |  |  | 575 |   | 
        
           |  |  | 576 |         // Load plan, apply security and set status based on interactivity.
 | 
        
           |  |  | 577 |         $this->load_plan();
 | 
        
           |  |  | 578 |   | 
        
           |  |  | 579 |         $this->set_status(backup::STATUS_NEED_PRECHECK);
 | 
        
           |  |  | 580 |         $this->progress->end_progress();
 | 
        
           |  |  | 581 |     }
 | 
        
           |  |  | 582 |   | 
        
           |  |  | 583 |     /**
 | 
        
           |  |  | 584 |      * Get the course copy data.
 | 
        
           |  |  | 585 |      *
 | 
        
           |  |  | 586 |      * @return \stdClass
 | 
        
           |  |  | 587 |      */
 | 
        
           |  |  | 588 |     public function get_copy(): \stdClass {
 | 
        
           |  |  | 589 |         if ($this->mode != backup::MODE_COPY) {
 | 
        
           |  |  | 590 |             throw new restore_controller_exception('cannot_get_copy_wrong_mode');
 | 
        
           |  |  | 591 |         }
 | 
        
           |  |  | 592 |   | 
        
           |  |  | 593 |         return $this->copy;
 | 
        
           |  |  | 594 |     }
 | 
        
           |  |  | 595 |   | 
        
           |  |  | 596 | // Protected API starts here
 | 
        
           |  |  | 597 |   | 
        
           |  |  | 598 |     protected function calculate_restoreid() {
 | 
        
           |  |  | 599 |         // Current epoch time + tempdir + courseid + interactive + mode + userid + target + operation + random bits
 | 
        
           |  |  | 600 |         $this->restoreid = md5(time() . '-' . $this->tempdir . '-' . $this->courseid . '-'. $this->interactive . '-' .
 | 
        
           |  |  | 601 |                                $this->mode . '-' . $this->userid . '-'. $this->target . '-' . $this->operation . '-' .
 | 
        
           |  |  | 602 |                                random_string(20));
 | 
        
           |  |  | 603 |     }
 | 
        
           |  |  | 604 |   | 
        
           |  |  | 605 |     protected function load_plan() {
 | 
        
           |  |  | 606 |         // First of all, we need to introspect the moodle_backup.xml file
 | 
        
           |  |  | 607 |         // in order to detect all the required stuff. So, create the
 | 
        
           |  |  | 608 |         // monster $info structure where everything will be defined
 | 
        
           |  |  | 609 |         $this->log('loading backup info', backup::LOG_DEBUG);
 | 
        
           |  |  | 610 |         $this->info = backup_general_helper::get_backup_information($this->tempdir);
 | 
        
           |  |  | 611 |   | 
        
           |  |  | 612 |         // Set the controller type to the one found in the information
 | 
        
           |  |  | 613 |         $this->type = $this->info->type;
 | 
        
           |  |  | 614 |   | 
        
           |  |  | 615 |         // Set the controller samesite flag as needed
 | 
        
           |  |  | 616 |         $this->samesite = backup_general_helper::backup_is_samesite($this->info);
 | 
        
           |  |  | 617 |   | 
        
           |  |  | 618 |         // Now we load the plan that will be configured following the
 | 
        
           |  |  | 619 |         // information provided by the $info
 | 
        
           |  |  | 620 |         $this->log('loading controller plan', backup::LOG_DEBUG);
 | 
        
           |  |  | 621 |         $this->plan = new restore_plan($this);
 | 
        
           |  |  | 622 |         $this->plan->build(); // Build plan for this controller
 | 
        
           |  |  | 623 |         $this->set_status(backup::STATUS_PLANNED);
 | 
        
           |  |  | 624 |     }
 | 
        
           |  |  | 625 |   | 
        
           |  |  | 626 |     /**
 | 
        
           |  |  | 627 |      * Apply defaults from the global admin settings
 | 
        
           |  |  | 628 |      */
 | 
        
           |  |  | 629 |     protected function apply_defaults() {
 | 
        
           |  |  | 630 |         $this->log('applying restore defaults', backup::LOG_DEBUG);
 | 
        
           |  |  | 631 |         restore_controller_dbops::apply_config_defaults($this);
 | 
        
           |  |  | 632 |         $this->set_status(backup::STATUS_CONFIGURED);
 | 
        
           |  |  | 633 |     }
 | 
        
           |  |  | 634 | }
 | 
        
           |  |  | 635 |   | 
        
           |  |  | 636 | /*
 | 
        
           |  |  | 637 |  * Exception class used by all the @restore_controller stuff
 | 
        
           |  |  | 638 |  */
 | 
        
           |  |  | 639 | class restore_controller_exception extends backup_exception {
 | 
        
           |  |  | 640 |   | 
        
           |  |  | 641 |     public function __construct($errorcode, $a=NULL, $debuginfo=null) {
 | 
        
           |  |  | 642 |         parent::__construct($errorcode, $a, $debuginfo);
 | 
        
           |  |  | 643 |     }
 | 
        
           |  |  | 644 | }
 |