[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/backup/util/plan/ -> restore_structure_step.class.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1