[ Index ]

PHP Cross Reference of moodle-2.8

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       * Launch all the after_execute methods present in all the processing objects
 299       *
 300       * This method will launch all the after_execute methods that can be defined
 301       * both in restore_plugin and restore_structure_step classes
 302       *
 303       * For restore_plugin classes the name of the method to be executed will be
 304       * "after_execute_" + connection point (as far as can be multiple connection
 305       * points in the same class)
 306       *
 307       * For restore_structure_step classes is will be, simply, "after_execute". Note
 308       * that this is executed *after* the plugin ones
 309       */
 310      protected function launch_after_execute_methods() {
 311          $alreadylaunched = array(); // To avoid multiple executions
 312          foreach ($this->pathelements as $key => $pathelement) {
 313              // Get the processing object
 314              $pobject = $pathelement->get_processing_object();
 315              // Skip null processors (child of grouped ones for sure)
 316              if (is_null($pobject)) {
 317                  continue;
 318              }
 319              // Skip restore structure step processors (this)
 320              if ($pobject instanceof restore_structure_step) {
 321                  continue;
 322              }
 323              // Skip already launched processing objects
 324              if (in_array($pobject, $alreadylaunched, true)) {
 325                  continue;
 326              }
 327              // Add processing object to array of launched ones
 328              $alreadylaunched[] = $pobject;
 329              // If the processing object has support for
 330              // launching after_execute methods, use it
 331              if (method_exists($pobject, 'launch_after_execute_methods')) {
 332                  $pobject->launch_after_execute_methods();
 333              }
 334          }
 335          // Finally execute own (restore_structure_step) after_execute method
 336          $this->after_execute();
 337  
 338      }
 339  
 340      /**
 341       * Launch all the after_restore methods present in all the processing objects
 342       *
 343       * This method will launch all the after_restore methods that can be defined
 344       * both in restore_plugin class
 345       *
 346       * For restore_plugin classes the name of the method to be executed will be
 347       * "after_restore_" + connection point (as far as can be multiple connection
 348       * points in the same class)
 349       */
 350      public function launch_after_restore_methods() {
 351          $alreadylaunched = array(); // To avoid multiple executions
 352          foreach ($this->pathelements as $pathelement) {
 353              // Get the processing object
 354              $pobject = $pathelement->get_processing_object();
 355              // Skip null processors (child of grouped ones for sure)
 356              if (is_null($pobject)) {
 357                  continue;
 358              }
 359              // Skip restore structure step processors (this)
 360              if ($pobject instanceof restore_structure_step) {
 361                  continue;
 362              }
 363              // Skip already launched processing objects
 364              if (in_array($pobject, $alreadylaunched, true)) {
 365                  continue;
 366              }
 367              // Add processing object to array of launched ones
 368              $alreadylaunched[] = $pobject;
 369              // If the processing object has support for
 370              // launching after_restore methods, use it
 371              if (method_exists($pobject, 'launch_after_restore_methods')) {
 372                  $pobject->launch_after_restore_methods();
 373              }
 374          }
 375          // Finally execute own (restore_structure_step) after_restore method
 376          $this->after_restore();
 377      }
 378  
 379      /**
 380       * This method will be executed after the whole structure step have been processed
 381       *
 382       * After execution method for code needed to be executed after the whole structure
 383       * has been processed. Useful for cleaning tasks, files process and others. Simply
 384       * overwrite in in your steps if needed
 385       */
 386      protected function after_execute() {
 387          // do nothing by default
 388      }
 389  
 390      /**
 391       * This method will be executed after the rest of the restore has been processed.
 392       *
 393       * Use if you need to update IDs based on things which are restored after this
 394       * step has completed.
 395       */
 396      protected function after_restore() {
 397          // do nothing by default
 398      }
 399  
 400      /**
 401       * Prepare the pathelements for processing, looking for duplicates, applying
 402       * processing objects and other adjustments
 403       */
 404      protected function prepare_pathelements($elementsarr) {
 405  
 406          // First iteration, push them to new array, indexed by name
 407          // detecting duplicates in names or paths
 408          $names = array();
 409          $paths = array();
 410          foreach($elementsarr as $element) {
 411              if (!$element instanceof restore_path_element) {
 412                  throw new restore_step_exception('restore_path_element_wrong_class', get_class($element));
 413              }
 414              if (array_key_exists($element->get_name(), $names)) {
 415                  throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name());
 416              }
 417              if (array_key_exists($element->get_path(), $paths)) {
 418                  throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path());
 419              }
 420              $names[$element->get_name()] = true;
 421              $paths[$element->get_path()] = $element;
 422          }
 423          // Now, for each element not having one processing object, if
 424          // not child of grouped element, assign $this (the step itself) as processing element
 425          // Note method must exist or we'll get one @restore_path_element_exception
 426          foreach($paths as $key => $pelement) {
 427              if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $paths)) {
 428                  $paths[$key]->set_processing_object($this);
 429              }
 430              // Populate $elementsoldid and $elementsoldid based on available pathelements
 431              $this->elementsoldid[$pelement->get_name()] = null;
 432              $this->elementsnewid[$pelement->get_name()] = null;
 433          }
 434          // Done, add them to pathelements (dupes by key - path - are discarded)
 435          $this->pathelements = array_merge($this->pathelements, $paths);
 436      }
 437  
 438      /**
 439       * Given one pathelement, return true if grouped parent was found
 440       */
 441      protected function grouped_parent_exists($pelement, $elements) {
 442          foreach ($elements as $element) {
 443              if ($pelement->get_path() == $element->get_path()) {
 444                  continue; // Don't compare against itself
 445              }
 446              // If element is grouped and parent of pelement, return true
 447              if ($element->is_grouped() and strpos($pelement->get_path() .  '/', $element->get_path()) === 0) {
 448                  return true;
 449              }
 450          }
 451          return false; // no grouped parent found
 452      }
 453  
 454      /**
 455       * To conditionally decide if one step will be executed or no
 456       *
 457       * For steps needing to be executed conditionally, based in dynamic
 458       * conditions (at execution time vs at declaration time) you must
 459       * override this function. It will return true if the step must be
 460       * executed and false if not
 461       */
 462      protected function execute_condition() {
 463          return true;
 464      }
 465  
 466      /**
 467       * Function that will return the structure to be processed by this restore_step.
 468       * Must return one array of @restore_path_element elements
 469       */
 470      abstract protected function define_structure();
 471  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1