[ 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-plan 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 * Abstract class defining the needed stuff to restore one xml file 27 * 28 * TODO: Finish phpdocs 29 */ 30 abstract class restore_structure_step extends restore_step { 31 32 protected $filename; // Name of the file to be parsed 33 protected $contentprocessor; // xml parser processor being used 34 // (need it here, apart from parser 35 // thanks to serialized data to process - 36 // say thanks to blocks!) 37 protected $pathelements; // Array of pathelements to process 38 protected $elementsoldid; // Array to store last oldid used on each element 39 protected $elementsnewid; // Array to store last newid used on each element 40 41 protected $pathlock; // Path currently locking processing of children 42 43 const SKIP_ALL_CHILDREN = -991399; // To instruct the dispatcher about to ignore 44 // all children below path processor returning it 45 46 /** 47 * Constructor - instantiates one object of this class 48 */ 49 public function __construct($name, $filename, $task = null) { 50 if (!is_null($task) && !($task instanceof restore_task)) { 51 throw new restore_step_exception('wrong_restore_task_specified'); 52 } 53 $this->filename = $filename; 54 $this->contentprocessor = null; 55 $this->pathelements = array(); 56 $this->elementsoldid = array(); 57 $this->elementsnewid = array(); 58 $this->pathlock = null; 59 parent::__construct($name, $task); 60 } 61 62 final public function execute() { 63 64 if (!$this->execute_condition()) { // Check any condition to execute this 65 return; 66 } 67 68 $fullpath = $this->task->get_taskbasepath(); 69 70 // We MUST have one fullpath here, else, error 71 if (empty($fullpath)) { 72 throw new restore_step_exception('restore_structure_step_undefined_fullpath'); 73 } 74 75 // Append the filename to the fullpath 76 $fullpath = rtrim($fullpath, '/') . '/' . $this->filename; 77 78 // And it MUST exist 79 if (!file_exists($fullpath)) { // Shouldn't happen ever, but... 80 throw new restore_step_exception('missing_moodle_backup_xml_file', $fullpath); 81 } 82 83 // Get restore_path elements array adapting and preparing it for processing 84 $structure = $this->define_structure(); 85 if (!is_array($structure)) { 86 throw new restore_step_exception('restore_step_structure_not_array', $this->get_name()); 87 } 88 $this->prepare_pathelements($structure); 89 90 // Create parser and processor 91 $xmlparser = new progressive_parser(); 92 $xmlparser->set_file($fullpath); 93 $xmlprocessor = new restore_structure_parser_processor($this->task->get_courseid(), $this); 94 $this->contentprocessor = $xmlprocessor; // Save the reference to the contentprocessor 95 // as far as we are going to need it out 96 // from parser (blame serialized data!) 97 $xmlparser->set_processor($xmlprocessor); 98 99 // Add pathelements to processor 100 foreach ($this->pathelements as $element) { 101 $xmlprocessor->add_path($element->get_path(), $element->is_grouped()); 102 } 103 104 // Set up progress tracking. 105 $progress = $this->get_task()->get_progress(); 106 $progress->start_progress($this->get_name(), \core\progress\base::INDETERMINATE); 107 $xmlparser->set_progress($progress); 108 109 // And process it, dispatch to target methods in step will start automatically 110 $xmlparser->process(); 111 112 // Have finished, launch the after_execute method of all the processing objects 113 $this->launch_after_execute_methods(); 114 $progress->end_progress(); 115 } 116 117 /** 118 * Receive one chunk of information form the xml parser processor and 119 * dispatch it, following the naming rules 120 */ 121 final public function process($data) { 122 if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen 123 throw new restore_step_exception('restore_structure_step_missing_path', $data['path']); 124 } 125 $element = $this->pathelements[$data['path']]; 126 $object = $element->get_processing_object(); 127 $method = $element->get_processing_method(); 128 $rdata = null; 129 if (empty($object)) { // No processing object defined 130 throw new restore_step_exception('restore_structure_step_missing_pobject', $object); 131 } 132 // Release the lock if we aren't anymore within children of it 133 if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) { 134 $this->pathlock = null; 135 } 136 if (is_null($this->pathlock)) { // Only dispatch if there isn't any lock 137 $rdata = $object->$method($data['tags']); // Dispatch to proper object/method 138 } 139 140 // If the dispatched method returns SKIP_ALL_CHILDREN, we grab current path in order to 141 // lock dispatching to any children 142 if ($rdata === self::SKIP_ALL_CHILDREN) { 143 // Check we haven't any previous lock 144 if (!is_null($this->pathlock)) { 145 throw new restore_step_exception('restore_structure_step_already_skipping', $data['path']); 146 } 147 // Set the lock 148 $this->pathlock = $data['path'] . '/'; // Lock everything below current path 149 150 // Continue with normal processing of return values 151 } else if ($rdata !== null) { // If the method has returned any info, set element data to it 152 $element->set_data($rdata); 153 } else { // Else, put the original parsed data 154 $element->set_data($data); 155 } 156 } 157 158 /** 159 * To send ids pairs to backup_ids_table and to store them into paths 160 * 161 * This method will send the given itemname and old/new ids to the 162 * backup_ids_temp table, and, at the same time, will save the new id 163 * into the corresponding restore_path_element for easier access 164 * by children. Also will inject the known old context id for the task 165 * in case it's going to be used for restoring files later 166 */ 167 public function set_mapping($itemname, $oldid, $newid, $restorefiles = false, $filesctxid = null, $parentid = null) { 168 if ($restorefiles && $parentid) { 169 throw new restore_step_exception('set_mapping_cannot_specify_both_restorefiles_and_parentitemid'); 170 } 171 // If we haven't specified one context for the files, use the task one 172 if (is_null($filesctxid)) { 173 $parentitemid = $restorefiles ? $this->task->get_old_contextid() : null; 174 } else { // Use the specified one 175 $parentitemid = $restorefiles ? $filesctxid : null; 176 } 177 // We have passed one explicit parentid, apply it 178 $parentitemid = !is_null($parentid) ? $parentid : $parentitemid; 179 180 // Let's call the low level one 181 restore_dbops::set_backup_ids_record($this->get_restoreid(), $itemname, $oldid, $newid, $parentitemid); 182 // Now, if the itemname matches any pathelement->name, store the latest $newid 183 if (array_key_exists($itemname, $this->elementsoldid)) { // If present in $this->elementsoldid, is valid, put both ids 184 $this->elementsoldid[$itemname] = $oldid; 185 $this->elementsnewid[$itemname] = $newid; 186 } 187 } 188 189 /** 190 * Returns the latest (parent) old id mapped by one pathelement 191 */ 192 public function get_old_parentid($itemname) { 193 return array_key_exists($itemname, $this->elementsoldid) ? $this->elementsoldid[$itemname] : null; 194 } 195 196 /** 197 * Returns the latest (parent) new id mapped by one pathelement 198 */ 199 public function get_new_parentid($itemname) { 200 return array_key_exists($itemname, $this->elementsnewid) ? $this->elementsnewid[$itemname] : null; 201 } 202 203 /** 204 * Return the new id of a mapping for the given itemname 205 * 206 * @param string $itemname the type of item 207 * @param int $oldid the item ID from the backup 208 * @param mixed $ifnotfound what to return if $oldid wasnt found. Defaults to false 209 */ 210 public function get_mappingid($itemname, $oldid, $ifnotfound = false) { 211 $mapping = $this->get_mapping($itemname, $oldid); 212 return $mapping ? $mapping->newitemid : $ifnotfound; 213 } 214 215 /** 216 * Return the complete mapping from the given itemname, itemid 217 */ 218 public function get_mapping($itemname, $oldid) { 219 return restore_dbops::get_backup_ids_record($this->get_restoreid(), $itemname, $oldid); 220 } 221 222 /** 223 * Add all the existing file, given their component and filearea and one backup_ids itemname to match with 224 */ 225 public function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) { 226 // If the current progress object is set up and ready to receive 227 // indeterminate progress, then use it, otherwise don't. (This check is 228 // just in case this function is ever called from somewhere not within 229 // the execute() method here, which does set up progress like this.) 230 $progress = $this->get_task()->get_progress(); 231 if (!$progress->is_in_progress_section() || 232 $progress->get_current_max() !== \core\progress\base::INDETERMINATE) { 233 $progress = null; 234 } 235 236 $filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid; 237 $results = restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, 238 $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid, null, false, 239 $progress); 240 $resultstoadd = array(); 241 foreach ($results as $result) { 242 $this->log($result->message, $result->level); 243 $resultstoadd[$result->code] = true; 244 } 245 $this->task->add_result($resultstoadd); 246 } 247 248 /** 249 * As far as restore structure steps are implementing restore_plugin stuff, they need to 250 * have the parent task available for wrapping purposes (get course/context....) 251 * @return restore_task|null 252 */ 253 public function get_task() { 254 return $this->task; 255 } 256 257 // Protected API starts here 258 259 /** 260 * Add plugin structure to any element in the structure restore tree 261 * 262 * @param string $plugintype type of plugin as defined by core_component::get_plugin_types() 263 * @param restore_path_element $element element in the structure restore tree that 264 * we are going to add plugin information to 265 */ 266 protected function add_plugin_structure($plugintype, $element) { 267 268 global $CFG; 269 270 // Check the requested plugintype is a valid one 271 if (!array_key_exists($plugintype, core_component::get_plugin_types($plugintype))) { 272 throw new restore_step_exception('incorrect_plugin_type', $plugintype); 273 } 274 275 // Get all the restore path elements, looking across all the plugin dirs 276 $pluginsdirs = core_component::get_plugin_list($plugintype); 277 foreach ($pluginsdirs as $name => $pluginsdir) { 278 // We need to add also backup plugin classes on restore, they may contain 279 // some stuff used both in backup & restore 280 $backupclassname = 'backup_' . $plugintype . '_' . $name . '_plugin'; 281 $backupfile = $pluginsdir . '/backup/moodle2/' . $backupclassname . '.class.php'; 282 if (file_exists($backupfile)) { 283 require_once($backupfile); 284 } 285 // Now add restore plugin classes and prepare stuff 286 $restoreclassname = 'restore_' . $plugintype . '_' . $name . '_plugin'; 287 $restorefile = $pluginsdir . '/backup/moodle2/' . $restoreclassname . '.class.php'; 288 if (file_exists($restorefile)) { 289 require_once($restorefile); 290 $restoreplugin = new $restoreclassname($plugintype, $name, $this); 291 // Add plugin paths to the step 292 $this->prepare_pathelements($restoreplugin->define_plugin_structure($element)); 293 } 294 } 295 } 296 297 /** 298 * Add subplugin structure for a given plugin to any element in the structure restore tree 299 * 300 * This method allows the injection of subplugins (of a specific plugin) parsing and proccessing 301 * to any element in the restore structure. 302 * 303 * NOTE: Initially subplugins were only available for activities (mod), so only the 304 * {@link restore_activity_structure_step} class had support for them, always 305 * looking for /mod/modulenanme subplugins. This new method is a generalization of the 306 * existing one for activities, supporting all subplugins injecting information everywhere. 307 * 308 * @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.php. 309 * @param restore_path_element $element element in the structure restore tree that 310 * we are going to add subplugin information to. 311 * @param string $plugintype type of the plugin. 312 * @param string $pluginname name of the plugin. 313 * @return void 314 */ 315 protected function add_subplugin_structure($subplugintype, $element, $plugintype = null, $pluginname = null) { 316 global $CFG; 317 // This global declaration is required, because where we do require_once($backupfile); 318 // That file may in turn try to do require_once($CFG->dirroot ...). 319 // That worked in the past, we should keep it working. 320 321 // Verify if this is a BC call for an activity restore. See NOTE above for this special case. 322 if ($plugintype === null and $pluginname === null) { 323 $plugintype = 'mod'; 324 $pluginname = $this->task->get_modulename(); 325 // TODO: Once all the calls have been changed to add both not null plugintype and pluginname, add a debugging here. 326 } 327 328 // Check the requested plugintype is a valid one. 329 if (!array_key_exists($plugintype, core_component::get_plugin_types())) { 330 throw new restore_step_exception('incorrect_plugin_type', $plugintype); 331 } 332 333 // Check the requested pluginname, for the specified plugintype, is a valid one. 334 if (!array_key_exists($pluginname, core_component::get_plugin_list($plugintype))) { 335 throw new restore_step_exception('incorrect_plugin_name', array($plugintype, $pluginname)); 336 } 337 338 // Check the requested subplugintype is a valid one. 339 $subpluginsfile = core_component::get_component_directory($plugintype . '_' . $pluginname) . '/db/subplugins.php'; 340 if (!file_exists($subpluginsfile)) { 341 throw new restore_step_exception('plugin_missing_subplugins_php_file', array($plugintype, $pluginname)); 342 } 343 include($subpluginsfile); 344 if (!array_key_exists($subplugintype, $subplugins)) { 345 throw new restore_step_exception('incorrect_subplugin_type', $subplugintype); 346 } 347 348 // Every subplugin optionally can have a common/parent subplugin 349 // class for shared stuff. 350 $parentclass = 'restore_' . $plugintype . '_' . $pluginname . '_' . $subplugintype . '_subplugin'; 351 $parentfile = core_component::get_component_directory($plugintype . '_' . $pluginname) . 352 '/backup/moodle2/' . $parentclass . '.class.php'; 353 if (file_exists($parentfile)) { 354 require_once($parentfile); 355 } 356 357 // Get all the restore path elements, looking across all the subplugin dirs. 358 $subpluginsdirs = core_component::get_plugin_list($subplugintype); 359 foreach ($subpluginsdirs as $name => $subpluginsdir) { 360 $classname = 'restore_' . $subplugintype . '_' . $name . '_subplugin'; 361 $restorefile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php'; 362 if (file_exists($restorefile)) { 363 require_once($restorefile); 364 $restoresubplugin = new $classname($subplugintype, $name, $this); 365 // Add subplugin paths to the step. 366 $this->prepare_pathelements($restoresubplugin->define_subplugin_structure($element)); 367 } 368 } 369 } 370 371 /** 372 * Launch all the after_execute methods present in all the processing objects 373 * 374 * This method will launch all the after_execute methods that can be defined 375 * both in restore_plugin and restore_structure_step classes 376 * 377 * For restore_plugin classes the name of the method to be executed will be 378 * "after_execute_" + connection point (as far as can be multiple connection 379 * points in the same class) 380 * 381 * For restore_structure_step classes is will be, simply, "after_execute". Note 382 * that this is executed *after* the plugin ones 383 */ 384 protected function launch_after_execute_methods() { 385 $alreadylaunched = array(); // To avoid multiple executions 386 foreach ($this->pathelements as $key => $pathelement) { 387 // Get the processing object 388 $pobject = $pathelement->get_processing_object(); 389 // Skip null processors (child of grouped ones for sure) 390 if (is_null($pobject)) { 391 continue; 392 } 393 // Skip restore structure step processors (this) 394 if ($pobject instanceof restore_structure_step) { 395 continue; 396 } 397 // Skip already launched processing objects 398 if (in_array($pobject, $alreadylaunched, true)) { 399 continue; 400 } 401 // Add processing object to array of launched ones 402 $alreadylaunched[] = $pobject; 403 // If the processing object has support for 404 // launching after_execute methods, use it 405 if (method_exists($pobject, 'launch_after_execute_methods')) { 406 $pobject->launch_after_execute_methods(); 407 } 408 } 409 // Finally execute own (restore_structure_step) after_execute method 410 $this->after_execute(); 411 412 } 413 414 /** 415 * Launch all the after_restore methods present in all the processing objects 416 * 417 * This method will launch all the after_restore methods that can be defined 418 * both in restore_plugin class 419 * 420 * For restore_plugin classes the name of the method to be executed will be 421 * "after_restore_" + connection point (as far as can be multiple connection 422 * points in the same class) 423 */ 424 public function launch_after_restore_methods() { 425 $alreadylaunched = array(); // To avoid multiple executions 426 foreach ($this->pathelements as $pathelement) { 427 // Get the processing object 428 $pobject = $pathelement->get_processing_object(); 429 // Skip null processors (child of grouped ones for sure) 430 if (is_null($pobject)) { 431 continue; 432 } 433 // Skip restore structure step processors (this) 434 if ($pobject instanceof restore_structure_step) { 435 continue; 436 } 437 // Skip already launched processing objects 438 if (in_array($pobject, $alreadylaunched, true)) { 439 continue; 440 } 441 // Add processing object to array of launched ones 442 $alreadylaunched[] = $pobject; 443 // If the processing object has support for 444 // launching after_restore methods, use it 445 if (method_exists($pobject, 'launch_after_restore_methods')) { 446 $pobject->launch_after_restore_methods(); 447 } 448 } 449 // Finally execute own (restore_structure_step) after_restore method 450 $this->after_restore(); 451 } 452 453 /** 454 * This method will be executed after the whole structure step have been processed 455 * 456 * After execution method for code needed to be executed after the whole structure 457 * has been processed. Useful for cleaning tasks, files process and others. Simply 458 * overwrite in in your steps if needed 459 */ 460 protected function after_execute() { 461 // do nothing by default 462 } 463 464 /** 465 * This method will be executed after the rest of the restore has been processed. 466 * 467 * Use if you need to update IDs based on things which are restored after this 468 * step has completed. 469 */ 470 protected function after_restore() { 471 // do nothing by default 472 } 473 474 /** 475 * Prepare the pathelements for processing, looking for duplicates, applying 476 * processing objects and other adjustments 477 */ 478 protected function prepare_pathelements($elementsarr) { 479 480 // First iteration, push them to new array, indexed by name 481 // detecting duplicates in names or paths 482 $names = array(); 483 $paths = array(); 484 foreach($elementsarr as $element) { 485 if (!$element instanceof restore_path_element) { 486 throw new restore_step_exception('restore_path_element_wrong_class', get_class($element)); 487 } 488 if (array_key_exists($element->get_name(), $names)) { 489 throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name()); 490 } 491 if (array_key_exists($element->get_path(), $paths)) { 492 throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path()); 493 } 494 $names[$element->get_name()] = true; 495 $paths[$element->get_path()] = $element; 496 } 497 // Now, for each element not having one processing object, if 498 // not child of grouped element, assign $this (the step itself) as processing element 499 // Note method must exist or we'll get one @restore_path_element_exception 500 foreach($paths as $key => $pelement) { 501 if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $paths)) { 502 $paths[$key]->set_processing_object($this); 503 } 504 // Populate $elementsoldid and $elementsoldid based on available pathelements 505 $this->elementsoldid[$pelement->get_name()] = null; 506 $this->elementsnewid[$pelement->get_name()] = null; 507 } 508 // Done, add them to pathelements (dupes by key - path - are discarded) 509 $this->pathelements = array_merge($this->pathelements, $paths); 510 } 511 512 /** 513 * Given one pathelement, return true if grouped parent was found 514 */ 515 protected function grouped_parent_exists($pelement, $elements) { 516 foreach ($elements as $element) { 517 if ($pelement->get_path() == $element->get_path()) { 518 continue; // Don't compare against itself 519 } 520 // If element is grouped and parent of pelement, return true 521 if ($element->is_grouped() and strpos($pelement->get_path() . '/', $element->get_path()) === 0) { 522 return true; 523 } 524 } 525 return false; // no grouped parent found 526 } 527 528 /** 529 * To conditionally decide if one step will be executed or no 530 * 531 * For steps needing to be executed conditionally, based in dynamic 532 * conditions (at execution time vs at declaration time) you must 533 * override this function. It will return true if the step must be 534 * executed and false if not 535 */ 536 protected function execute_condition() { 537 return true; 538 } 539 540 /** 541 * Function that will return the structure to be processed by this restore_step. 542 * Must return one array of @restore_path_element elements 543 */ 544 abstract protected function define_structure(); 545 }
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 |