Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * restore user interface stages
  19   *
  20   * This file contains the classes required to manage the stages that make up the
  21   * restore user interface.
  22   * These will be primarily operated a {@link restore_ui} instance.
  23   *
  24   * @package   core_backup
  25   * @copyright 2010 Sam Hemelryk
  26   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  
  29  /**
  30   * Abstract stage class
  31   *
  32   * This class should be extended by all restore stages (a requirement of many restore ui functions).
  33   * Each stage must then define two abstract methods
  34   *  - process : To process the stage
  35   *  - initialise_stage_form : To get a restore_moodleform instance for the stage
  36   *
  37   * @package   core_backup
  38   * @copyright 2010 Sam Hemelryk
  39   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  abstract class restore_ui_stage extends base_ui_stage {
  42      /**
  43       * Constructor
  44       * @param restore_ui $ui
  45       * @param array $params
  46       */
  47      public function __construct(restore_ui $ui, array $params = null) {
  48          $this->ui = $ui;
  49          $this->params = $params;
  50      }
  51      /**
  52       * The restore id from the restore controller
  53       * @return string
  54       */
  55      final public function get_restoreid() {
  56          return $this->get_uniqueid();
  57      }
  58  
  59      /**
  60       * This is an independent stage
  61       * @return int
  62       */
  63      final public function is_independent() {
  64          return false;
  65      }
  66  
  67      /**
  68       * No sub stages for this stage
  69       * @return false
  70       */
  71      public function has_sub_stages() {
  72          return false;
  73      }
  74  
  75      /**
  76       * The name of this stage
  77       * @return string
  78       */
  79      final public function get_name() {
  80          return get_string('restorestage'.$this->stage, 'backup');
  81      }
  82  
  83      /**
  84       * Returns true if this is the settings stage
  85       * @return bool
  86       */
  87      final public function is_first_stage() {
  88          return $this->stage == restore_ui::STAGE_SETTINGS;
  89      }
  90  }
  91  
  92  /**
  93   * Abstract class used to represent a restore stage that is indenependent.
  94   *
  95   * An independent stage is a judged to be so because it doesn't require, and has
  96   * no use for the restore controller.
  97   *
  98   * @package   core_backup
  99   * @copyright 2010 Sam Hemelryk
 100   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 101   */
 102  abstract class restore_ui_independent_stage {
 103      /**
 104       * @var \core\progress\base Optional progress reporter
 105       */
 106      private $progressreporter;
 107  
 108      /**
 109       * Constructs the restore stage.
 110       * @param int $contextid
 111       */
 112      abstract public function __construct($contextid);
 113  
 114      /**
 115       * Processes the current restore stage.
 116       * @return mixed
 117       */
 118      abstract public function process();
 119  
 120      /**
 121       * Displays this restore stage.
 122       * @param core_backup_renderer $renderer
 123       * @return mixed
 124       */
 125      abstract public function display(core_backup_renderer $renderer);
 126  
 127      /**
 128       * Returns the current restore stage.
 129       * @return int
 130       */
 131      abstract public function get_stage();
 132  
 133      /**
 134       * Gets the progress reporter object in use for this restore UI stage.
 135       *
 136       * IMPORTANT: This progress reporter is used only for UI progress that is
 137       * outside the restore controller. The restore controller has its own
 138       * progress reporter which is used for progress during the main restore.
 139       * Use the restore controller's progress reporter to report progress during
 140       * a restore operation, not this one.
 141       *
 142       * This extra reporter is necessary because on some restore UI screens,
 143       * there are long-running tasks even though there is no restore controller
 144       * in use. There is a similar function in restore_ui. but that class is not
 145       * used on some stages.
 146       *
 147       * @return \core\progress\none
 148       */
 149      public function get_progress_reporter() {
 150          if (!$this->progressreporter) {
 151              $this->progressreporter = new \core\progress\none();
 152          }
 153          return $this->progressreporter;
 154      }
 155  
 156      /**
 157       * Sets the progress reporter that will be returned by get_progress_reporter.
 158       *
 159       * @param \core\progress\base $progressreporter Progress reporter
 160       */
 161      public function set_progress_reporter(\core\progress\base $progressreporter) {
 162          $this->progressreporter = $progressreporter;
 163      }
 164  
 165      /**
 166       * Gets an array of progress bar items that can be displayed through the restore renderer.
 167       * @return array Array of items for the progress bar
 168       */
 169      public function get_progress_bar() {
 170          global $PAGE;
 171          $stage = restore_ui::STAGE_COMPLETE;
 172          $currentstage = $this->get_stage();
 173          $items = array();
 174          while ($stage > 0) {
 175              $classes = array('backup_stage');
 176              if (floor($stage / 2) == $currentstage) {
 177                  $classes[] = 'backup_stage_next';
 178              } else if ($stage == $currentstage) {
 179                  $classes[] = 'backup_stage_current';
 180              } else if ($stage < $currentstage) {
 181                  $classes[] = 'backup_stage_complete';
 182              }
 183              $item = array('text' => strlen(decbin($stage)).'. '.get_string('restorestage'.$stage, 'backup'), 'class' => join(' ', $classes));
 184              if ($stage < $currentstage && $currentstage < restore_ui::STAGE_COMPLETE) {
 185                  // By default you can't go back to independent stages, if that changes in the future uncomment the next line.
 186                  // $item['link'] = new moodle_url($PAGE->url, array('restore' => $this->get_restoreid(), 'stage' => $stage));
 187              }
 188              array_unshift($items, $item);
 189              $stage = floor($stage / 2);
 190          }
 191          return $items;
 192      }
 193  
 194      /**
 195       * Returns the restore stage name.
 196       * @return string
 197       */
 198      abstract public function get_stage_name();
 199  
 200      /**
 201       * Obviously true
 202       * @return true
 203       */
 204      final public function is_independent() {
 205          return true;
 206      }
 207  
 208      /**
 209       * Handles the destruction of this object.
 210       */
 211      public function destroy() {
 212          // Nothing to destroy here!.
 213      }
 214  }
 215  
 216  /**
 217   * The confirmation stage.
 218   *
 219   * This is the first stage, it is independent.
 220   *
 221   * @package   core_backup
 222   * @copyright 2010 Sam Hemelryk
 223   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 224   */
 225  class restore_ui_stage_confirm extends restore_ui_independent_stage implements file_progress {
 226  
 227      /**
 228       * The context ID.
 229       * @var int
 230       */
 231      protected $contextid;
 232  
 233      /**
 234       * The file name.
 235       * @var string
 236       */
 237      protected $filename = null;
 238  
 239      /**
 240       * The file path.
 241       * @var string
 242       */
 243      protected $filepath = null;
 244  
 245      /**
 246       * @var string Content hash of archive file to restore (if specified by hash)
 247       */
 248      protected $contenthash = null;
 249  
 250      /**
 251       * @var string Pathname hash of stored_file object to restore
 252       */
 253      protected $pathnamehash = null;
 254  
 255      /**
 256       * @var array
 257       */
 258      protected $details;
 259  
 260      /**
 261       * @var bool True if we have started reporting progress
 262       */
 263      protected $startedprogress = false;
 264  
 265      /**
 266       * Constructor
 267       * @param int $contextid
 268       * @throws coding_exception
 269       */
 270      public function __construct($contextid) {
 271          $this->contextid = $contextid;
 272          $this->filename = optional_param('filename', null, PARAM_FILE);
 273          if ($this->filename === null) {
 274              // Identify file object by its pathname hash.
 275              $this->pathnamehash = required_param('pathnamehash', PARAM_ALPHANUM);
 276  
 277              // The file content hash is also passed for security; users
 278              // cannot guess the content hash (unless they know the file contents),
 279              // so this guarantees that either the system generated this link or
 280              // else the user has access to the restore archive anyhow.
 281              $this->contenthash = required_param('contenthash', PARAM_ALPHANUM);
 282          }
 283      }
 284  
 285      /**
 286       * Processes this restore stage
 287       * @return bool
 288       * @throws restore_ui_exception
 289       */
 290      public function process() {
 291          $backuptempdir = make_backup_temp_directory('');
 292          if ($this->filename) {
 293              $archivepath = $backuptempdir . '/' . $this->filename;
 294              if (!file_exists($archivepath)) {
 295                  throw new restore_ui_exception('invalidrestorefile');
 296              }
 297              $outcome = $this->extract_file_to_dir($archivepath);
 298              if ($outcome) {
 299                  fulldelete($archivepath);
 300              }
 301          } else {
 302              $fs = get_file_storage();
 303              $storedfile = $fs->get_file_by_hash($this->pathnamehash);
 304              if (!$storedfile || $storedfile->get_contenthash() !== $this->contenthash) {
 305                  throw new restore_ui_exception('invalidrestorefile');
 306              }
 307              $outcome = $this->extract_file_to_dir($storedfile);
 308          }
 309          return $outcome;
 310      }
 311  
 312      /**
 313       * Extracts the file.
 314       *
 315       * @param string|stored_file $source Archive file to extract
 316       * @return bool
 317       */
 318      protected function extract_file_to_dir($source) {
 319          global $USER;
 320  
 321          $this->filepath = restore_controller::get_tempdir_name($this->contextid, $USER->id);
 322          $backuptempdir = make_backup_temp_directory('', false);
 323  
 324          $fb = get_file_packer('application/vnd.moodle.backup');
 325          $result = $fb->extract_to_pathname($source,
 326                  $backuptempdir . '/' . $this->filepath . '/', null, $this);
 327  
 328          // If any progress happened, end it.
 329          if ($this->startedprogress) {
 330              $this->get_progress_reporter()->end_progress();
 331          }
 332          return $result;
 333      }
 334  
 335      /**
 336       * Implementation for file_progress interface to display unzip progress.
 337       *
 338       * @param int $progress Current progress
 339       * @param int $max Max value
 340       */
 341      public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
 342          $reporter = $this->get_progress_reporter();
 343  
 344          // Start tracking progress if necessary.
 345          if (!$this->startedprogress) {
 346              $reporter->start_progress('extract_file_to_dir',
 347                      ($max == file_progress::INDETERMINATE) ? \core\progress\base::INDETERMINATE : $max);
 348              $this->startedprogress = true;
 349          }
 350  
 351          // Pass progress through to whatever handles it.
 352          $reporter->progress(
 353                  ($progress == file_progress::INDETERMINATE) ? \core\progress\base::INDETERMINATE : $progress);
 354      }
 355  
 356      /**
 357       * Renders the confirmation stage screen
 358       *
 359       * @param core_backup_renderer $renderer renderer instance to use
 360       * @return string HTML code
 361       */
 362      public function display(core_backup_renderer $renderer) {
 363  
 364          $prevstageurl = new moodle_url('/backup/restorefile.php', array('contextid' => $this->contextid));
 365          $nextstageurl = new moodle_url('/backup/restore.php', array(
 366              'contextid' => $this->contextid,
 367              'filepath'  => $this->filepath,
 368              'stage'     => restore_ui::STAGE_DESTINATION));
 369  
 370          $format = backup_general_helper::detect_backup_format($this->filepath);
 371  
 372          if ($format === backup::FORMAT_UNKNOWN) {
 373              // Unknown format - we can't do anything here.
 374              return $renderer->backup_details_unknown($prevstageurl);
 375  
 376          } else if ($format !== backup::FORMAT_MOODLE) {
 377              // Non-standard format to be converted.
 378              $details = array('format' => $format, 'type' => backup::TYPE_1COURSE); // todo type to be returned by a converter
 379              return $renderer->backup_details_nonstandard($nextstageurl, $details);
 380  
 381          } else {
 382              // Standard MBZ backup, let us get information from it and display.
 383              $this->details = backup_general_helper::get_backup_information($this->filepath);
 384              return $renderer->backup_details($this->details, $nextstageurl);
 385          }
 386      }
 387  
 388      /**
 389       * The restore stage name.
 390       * @return string
 391       * @throws coding_exception
 392       */
 393      public function get_stage_name() {
 394          return get_string('restorestage'.restore_ui::STAGE_CONFIRM, 'backup');
 395      }
 396  
 397      /**
 398       * The restore stage this class is for.
 399       * @return int
 400       */
 401      public function get_stage() {
 402          return restore_ui::STAGE_CONFIRM;
 403      }
 404  }
 405  
 406  /**
 407   * This is the destination stage.
 408   *
 409   * This stage is the second stage and is also independent
 410   *
 411   * @package   core_backup
 412   * @copyright 2010 Sam Hemelryk
 413   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 414   */
 415  class restore_ui_stage_destination extends restore_ui_independent_stage {
 416  
 417      /**
 418       * The context ID.
 419       * @var int
 420       */
 421      protected $contextid;
 422  
 423      /**
 424       * The backup file path.
 425       * @var mixed|null
 426       */
 427      protected $filepath = null;
 428  
 429      /**
 430       * The course ID.
 431       * @var null
 432       */
 433      protected $courseid = null;
 434  
 435      /**
 436       * The restore target. One of backup::TARGET_NEW
 437       * @var int
 438       */
 439      protected $target = backup::TARGET_NEW_COURSE;
 440  
 441      /**
 442       * The course search component.
 443       * @var null|restore_course_search
 444       */
 445      protected $coursesearch = null;
 446  
 447      /**
 448       * The category search component.
 449       * @var null|restore_category_search
 450       */
 451      protected $categorysearch = null;
 452  
 453      /**
 454       * Constructs the destination stage.
 455       * @param int $contextid
 456       * @throws coding_exception
 457       */
 458      public function __construct($contextid) {
 459          global $PAGE;
 460          $this->contextid = $contextid;
 461          $this->filepath = required_param('filepath', PARAM_ALPHANUM);
 462          $url = new moodle_url($PAGE->url, array(
 463              'filepath' => $this->filepath,
 464              'contextid' => $this->contextid,
 465              'stage' => restore_ui::STAGE_DESTINATION));
 466          // The context level can be course category, course or module. We need to make sure that we always use correct one.
 467          $context = context::instance_by_id($contextid);
 468          if ($context->contextlevel != CONTEXT_COURSE && $coursecontext = $context->get_course_context(false)) {
 469              $context = $coursecontext;
 470          }
 471          $this->coursesearch = new restore_course_search(array('url' => $url), $context->instanceid);
 472          $this->categorysearch = new restore_category_search(array('url' => $url));
 473      }
 474  
 475      /**
 476       * Processes the destination stage.
 477       * @return bool
 478       * @throws coding_exception
 479       * @throws restore_ui_exception
 480       */
 481      public function process() {
 482          global $DB;
 483          $filepathdir = make_backup_temp_directory($this->filepath, false);
 484          if (!file_exists($filepathdir) || !is_dir($filepathdir)) {
 485              throw new restore_ui_exception('invalidrestorepath');
 486          }
 487          if (optional_param('searchcourses', false, PARAM_BOOL)) {
 488              return false;
 489          }
 490          $this->target = optional_param('target', backup::TARGET_NEW_COURSE, PARAM_INT);
 491          $targetid = optional_param('targetid', null, PARAM_INT);
 492          if (!is_null($this->target) && !is_null($targetid) && confirm_sesskey()) {
 493              if ($this->target == backup::TARGET_NEW_COURSE) {
 494                  list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'), get_string('restoringcourseshortname', 'backup'));
 495                  $this->courseid = restore_dbops::create_new_course($fullname, $shortname, $targetid);
 496              } else {
 497                  $this->courseid = $targetid;
 498              }
 499              return ($DB->record_exists('course', array('id' => $this->courseid)));
 500          }
 501          return false;
 502      }
 503  
 504      /**
 505       * Renders the destination stage screen
 506       *
 507       * @param core_backup_renderer $renderer renderer instance to use
 508       * @return string HTML code
 509       */
 510      public function display(core_backup_renderer $renderer) {
 511  
 512          $format = backup_general_helper::detect_backup_format($this->filepath);
 513  
 514          if ($format === backup::FORMAT_MOODLE) {
 515              // Standard Moodle 2 format, let use get the type of the backup.
 516              $details = backup_general_helper::get_backup_information($this->filepath);
 517              if ($details->type === backup::TYPE_1COURSE) {
 518                  $wholecourse = true;
 519              } else {
 520                  $wholecourse = false;
 521              }
 522  
 523          } else {
 524              // Non-standard format to be converted. We assume it contains the
 525              // whole course for now. However, in the future there might be a callback
 526              // to the installed converters.
 527              $wholecourse = true;
 528          }
 529  
 530          $nextstageurl = new moodle_url('/backup/restore.php', array(
 531              'contextid' => $this->contextid,
 532              'filepath'  => $this->filepath,
 533              'stage'     => restore_ui::STAGE_SETTINGS));
 534          $context = context::instance_by_id($this->contextid);
 535  
 536          if ($context->contextlevel == CONTEXT_COURSE and has_capability('moodle/restore:restorecourse', $context)) {
 537              $currentcourse = $context->instanceid;
 538          } else {
 539              $currentcourse = false;
 540          }
 541  
 542          return $renderer->course_selector($nextstageurl, $wholecourse, $this->categorysearch, $this->coursesearch, $currentcourse);
 543      }
 544  
 545      /**
 546       * Returns the stage name.
 547       * @return string
 548       * @throws coding_exception
 549       */
 550      public function get_stage_name() {
 551          return get_string('restorestage'.restore_ui::STAGE_DESTINATION, 'backup');
 552      }
 553  
 554      /**
 555       * Returns the backup file path
 556       * @return mixed|null
 557       */
 558      public function get_filepath() {
 559          return $this->filepath;
 560      }
 561  
 562      /**
 563       * Returns the course id.
 564       * @return null
 565       */
 566      public function get_course_id() {
 567          return $this->courseid;
 568      }
 569  
 570      /**
 571       * Returns the current restore stage
 572       * @return int
 573       */
 574      public function get_stage() {
 575          return restore_ui::STAGE_DESTINATION;
 576      }
 577  
 578      /**
 579       * Returns the target for this restore.
 580       * One of backup::TARGET_*
 581       * @return int
 582       */
 583      public function get_target() {
 584          return $this->target;
 585      }
 586  }
 587  
 588  /**
 589   * This stage is the settings stage.
 590   *
 591   * This stage is the third stage, it is dependent on a restore controller and
 592   * is the first stage as such.
 593   *
 594   * @package   core_backup
 595   * @copyright 2010 Sam Hemelryk
 596   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 597   */
 598  class restore_ui_stage_settings extends restore_ui_stage {
 599      /**
 600       * Initial restore stage constructor
 601       * @param restore_ui $ui
 602       * @param array $params
 603       */
 604      public function __construct(restore_ui $ui, array $params = null) {
 605          $this->stage = restore_ui::STAGE_SETTINGS;
 606          parent::__construct($ui, $params);
 607      }
 608  
 609      /**
 610       * Process the settings stage.
 611       *
 612       * @param base_moodleform $form
 613       * @return bool|int
 614       */
 615      public function process(base_moodleform $form = null) {
 616          $form = $this->initialise_stage_form();
 617  
 618          if ($form->is_cancelled()) {
 619              $this->ui->cancel_process();
 620          }
 621  
 622          $data = $form->get_data();
 623          if ($data && confirm_sesskey()) {
 624              $tasks = $this->ui->get_tasks();
 625              $changes = 0;
 626              foreach ($tasks as &$task) {
 627                  // We are only interesting in the backup root task for this stage.
 628                  if ($task instanceof restore_root_task || $task instanceof restore_course_task) {
 629                      // Get all settings into a var so we can iterate by reference.
 630                      $settings = $task->get_settings();
 631                      foreach ($settings as &$setting) {
 632                          $name = $setting->get_ui_name();
 633                          if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
 634                              $setting->set_value($data->$name);
 635                              $changes++;
 636                          } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
 637                              $setting->set_value(0);
 638                              $changes++;
 639                          }
 640                      }
 641                  }
 642              }
 643              // Return the number of changes the user made.
 644              return $changes;
 645          } else {
 646              return false;
 647          }
 648      }
 649  
 650      /**
 651       * Initialise the stage form.
 652       *
 653       * @return backup_moodleform|base_moodleform|restore_settings_form
 654       * @throws coding_exception
 655       */
 656      protected function initialise_stage_form() {
 657          global $PAGE;
 658          if ($this->stageform === null) {
 659              $form = new restore_settings_form($this, $PAGE->url);
 660              // Store as a variable so we can iterate by reference.
 661              $tasks = $this->ui->get_tasks();
 662              $headingprinted = false;
 663              // Iterate all tasks by reference.
 664              foreach ($tasks as &$task) {
 665                  // For the initial stage we are only interested in the root settings.
 666                  if ($task instanceof restore_root_task) {
 667                      if (!$headingprinted) {
 668                          $form->add_heading('rootsettings', get_string('restorerootsettings', 'backup'));
 669                          $headingprinted = true;
 670                      }
 671                      $settings = $task->get_settings();
 672                      // First add all settings except the filename setting.
 673                      foreach ($settings as &$setting) {
 674                          if ($setting->get_name() == 'filename') {
 675                              continue;
 676                          }
 677                          $form->add_setting($setting, $task);
 678                      }
 679                      // Then add all dependencies.
 680                      foreach ($settings as &$setting) {
 681                          if ($setting->get_name() == 'filename') {
 682                              continue;
 683                          }
 684                          $form->add_dependencies($setting);
 685                      }
 686                  }
 687              }
 688              $this->stageform = $form;
 689          }
 690          // Return the form.
 691          return $this->stageform;
 692      }
 693  }
 694  
 695  /**
 696   * Schema stage of backup process
 697   *
 698   * During the schema stage the user is required to set the settings that relate
 699   * to the area that they are backing up as well as its children.
 700   *
 701   * @package   core_backup
 702   * @copyright 2010 Sam Hemelryk
 703   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 704   */
 705  class restore_ui_stage_schema extends restore_ui_stage {
 706      /**
 707       * @var int Maximum number of settings to add to form at once
 708       */
 709      const MAX_SETTINGS_BATCH = 1000;
 710  
 711      /**
 712       * Schema stage constructor
 713       * @param restore_ui $ui
 714       * @param array $params
 715       */
 716      public function __construct(restore_ui $ui, array $params = null) {
 717          $this->stage = restore_ui::STAGE_SCHEMA;
 718          parent::__construct($ui, $params);
 719      }
 720  
 721      /**
 722       * Processes the schema stage
 723       *
 724       * @param base_moodleform $form
 725       * @return int The number of changes the user made
 726       */
 727      public function process(base_moodleform $form = null) {
 728          $form = $this->initialise_stage_form();
 729          // Check it wasn't cancelled.
 730          if ($form->is_cancelled()) {
 731              $this->ui->cancel_process();
 732          }
 733  
 734          // Check it has been submit.
 735          $data = $form->get_data();
 736          if ($data && confirm_sesskey()) {
 737              // Get the tasks into a var so we can iterate by reference.
 738              $tasks = $this->ui->get_tasks();
 739              $changes = 0;
 740              // Iterate all tasks by reference.
 741              foreach ($tasks as &$task) {
 742                  // We are only interested in schema settings.
 743                  if (!($task instanceof restore_root_task)) {
 744                      // Store as a variable so we can iterate by reference.
 745                      $settings = $task->get_settings();
 746                      // Iterate by reference.
 747                      foreach ($settings as &$setting) {
 748                          $name = $setting->get_ui_name();
 749                          if (isset($data->$name) &&  $data->$name != $setting->get_value()) {
 750                              $setting->set_value($data->$name);
 751                              $changes++;
 752                          } else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
 753                              $setting->set_value(0);
 754                              $changes++;
 755                          }
 756                      }
 757                  }
 758              }
 759              // Return the number of changes the user made.
 760              return $changes;
 761          } else {
 762              return false;
 763          }
 764      }
 765  
 766      /**
 767       * Creates the backup_schema_form instance for this stage
 768       *
 769       * @return backup_schema_form
 770       */
 771      protected function initialise_stage_form() {
 772          global $PAGE;
 773          if ($this->stageform === null) {
 774              $form = new restore_schema_form($this, $PAGE->url);
 775              $tasks = $this->ui->get_tasks();
 776              $courseheading = false;
 777  
 778              // Track progress through each stage.
 779              $progress = $this->ui->get_progress_reporter();
 780              $progress->start_progress('Initialise schema stage form', 3);
 781  
 782              $progress->start_progress('', count($tasks));
 783              $done = 1;
 784              $allsettings = array();
 785              foreach ($tasks as $task) {
 786                  if (!($task instanceof restore_root_task)) {
 787                      if (!$courseheading) {
 788                          // If we haven't already display a course heading to group nicely.
 789                          $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
 790                          $courseheading = true;
 791                      }
 792                      // Put each setting into an array of settings to add. Adding
 793                      // a setting individually is a very slow operation, so we add.
 794                      // them all in a batch later on.
 795                      foreach ($task->get_settings() as $setting) {
 796                          $allsettings[] = array($setting, $task);
 797                      }
 798                  } else if ($this->ui->enforce_changed_dependencies()) {
 799                      // Only show these settings if dependencies changed them.
 800                      // Add a root settings heading to group nicely.
 801                      $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
 802                      // Iterate all settings and add them to the form as a fixed
 803                      // setting. We only want schema settings to be editable.
 804                      foreach ($task->get_settings() as $setting) {
 805                          if ($setting->get_name() != 'filename') {
 806                              $form->add_fixed_setting($setting, $task);
 807                          }
 808                      }
 809                  }
 810                  // Update progress.
 811                  $progress->progress($done++);
 812              }
 813              $progress->end_progress();
 814  
 815              // Add settings for tasks in batches of up to 1000. Adding settings
 816              // in larger batches improves performance, but if it takes too long,
 817              // we won't be able to update the progress bar so the backup might.
 818              // time out. 1000 is chosen to balance this.
 819              $numsettings = count($allsettings);
 820              $progress->start_progress('', ceil($numsettings / self::MAX_SETTINGS_BATCH));
 821              $start = 0;
 822              $done = 1;
 823              while ($start < $numsettings) {
 824                  $length = min(self::MAX_SETTINGS_BATCH, $numsettings - $start);
 825                  $form->add_settings(array_slice($allsettings, $start, $length));
 826                  $start += $length;
 827                  $progress->progress($done++);
 828              }
 829              $progress->end_progress();
 830  
 831              // Add the dependencies for all the settings.
 832              $progress->start_progress('', count($allsettings));
 833              $done = 1;
 834              foreach ($allsettings as $settingtask) {
 835                  $form->add_dependencies($settingtask[0]);
 836                  $progress->progress($done++);
 837              }
 838              $progress->end_progress();
 839  
 840              $progress->end_progress();
 841              $this->stageform = $form;
 842          }
 843          return $this->stageform;
 844      }
 845  }
 846  
 847  /**
 848   * Confirmation stage
 849   *
 850   * On this stage the user reviews the setting for the backup and can change the filename
 851   * of the file that will be generated.
 852   *
 853   * @package   core_backup
 854   * @copyright 2010 Sam Hemelryk
 855   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 856   */
 857  class restore_ui_stage_review extends restore_ui_stage {
 858  
 859      /**
 860       * Constructs the stage
 861       * @param restore_ui $ui
 862       * @param array $params
 863       */
 864      public function __construct($ui, array $params = null) {
 865          $this->stage = restore_ui::STAGE_REVIEW;
 866          parent::__construct($ui, $params);
 867      }
 868  
 869      /**
 870       * Processes the confirmation stage
 871       *
 872       * @param base_moodleform $form
 873       * @return int The number of changes the user made
 874       */
 875      public function process(base_moodleform $form = null) {
 876          $form = $this->initialise_stage_form();
 877          // Check it hasn't been cancelled.
 878          if ($form->is_cancelled()) {
 879              $this->ui->cancel_process();
 880          }
 881  
 882          $data = $form->get_data();
 883          if ($data && confirm_sesskey()) {
 884              return 0;
 885          } else {
 886              return false;
 887          }
 888      }
 889      /**
 890       * Creates the backup_confirmation_form instance this stage requires
 891       *
 892       * @return backup_confirmation_form
 893       */
 894      protected function initialise_stage_form() {
 895          global $PAGE;
 896          if ($this->stageform === null) {
 897              // Get the form.
 898              $form = new restore_review_form($this, $PAGE->url);
 899              $content = '';
 900              $courseheading = false;
 901  
 902              $progress = $this->ui->get_progress_reporter();
 903              $tasks = $this->ui->get_tasks();
 904              $progress->start_progress('initialise_stage_form', count($tasks));
 905              $done = 1;
 906              foreach ($tasks as $task) {
 907                  if ($task instanceof restore_root_task) {
 908                      // If its a backup root add a root settings heading to group nicely.
 909                      $form->add_heading('rootsettings', get_string('restorerootsettings', 'backup'));
 910                  } else if (!$courseheading) {
 911                      // We haven't already add a course heading.
 912                      $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
 913                      $courseheading = true;
 914                  }
 915                  // Iterate all settings, doesnt need to happen by reference.
 916                  foreach ($task->get_settings() as $setting) {
 917                      $form->add_fixed_setting($setting, $task);
 918                  }
 919                  // Update progress.
 920                  $progress->progress($done++);
 921              }
 922              $progress->end_progress();
 923              $this->stageform = $form;
 924          }
 925          return $this->stageform;
 926      }
 927  }
 928  
 929  /**
 930   * Final stage of backup
 931   *
 932   * This stage is special in that it is does not make use of a form. The reason for
 933   * this is the order of procession of backup at this stage.
 934   * The processesion is:
 935   * 1. The final stage will be intialise.
 936   * 2. The confirmation stage will be processed.
 937   * 3. The backup will be executed
 938   * 4. The complete stage will be loaded by execution
 939   * 5. The complete stage will be displayed
 940   *
 941   * This highlights that we neither need a form nor a display method for this stage
 942   * we simply need to process.
 943   *
 944   * @package   core_backup
 945   * @copyright 2010 Sam Hemelryk
 946   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 947   */
 948  class restore_ui_stage_process extends restore_ui_stage {
 949  
 950      /**
 951       * There is no substage required.
 952       */
 953      const SUBSTAGE_NONE = 0;
 954  
 955      /**
 956       * The prechecks substage is required/the current substage.
 957       */
 958      const SUBSTAGE_PRECHECKS = 2;
 959  
 960      /**
 961       * The current substage.
 962       * @var int
 963       */
 964      protected $substage = 0;
 965  
 966      /**
 967       * Constructs the final stage
 968       * @param base_ui $ui
 969       * @param array $params
 970       */
 971      public function __construct(base_ui $ui, array $params = null) {
 972          $this->stage = restore_ui::STAGE_PROCESS;
 973          parent::__construct($ui, $params);
 974      }
 975      /**
 976       * Processes the final stage.
 977       *
 978       * In this case it checks to see if there is a sub stage that we need to display
 979       * before execution, if there is we gear up to display the subpage, otherwise
 980       * we return true which will lead to execution of the restore and the loading
 981       * of the completed stage.
 982       *
 983       * @param base_moodleform $form
 984       */
 985      public function process(base_moodleform $form = null) {
 986          if (optional_param('cancel', false, PARAM_BOOL)) {
 987              redirect(new moodle_url('/course/view.php', array('id' => $this->get_ui()->get_controller()->get_courseid())));
 988          }
 989  
 990          // First decide whether a substage is needed.
 991          $rc = $this->ui->get_controller();
 992          if ($rc->get_status() == backup::STATUS_SETTING_UI) {
 993              $rc->finish_ui();
 994          }
 995          if ($rc->get_status() == backup::STATUS_NEED_PRECHECK) {
 996              if (!$rc->precheck_executed()) {
 997                  $rc->execute_precheck(true);
 998              }
 999              $results = $rc->get_precheck_results();
1000              if (!empty($results)) {
1001                  $this->substage = self::SUBSTAGE_PRECHECKS;
1002              }
1003          }
1004  
1005          $substage = optional_param('substage', null, PARAM_INT);
1006          if (empty($this->substage) && !empty($substage)) {
1007              $this->substage = $substage;
1008              // Now check whether that substage has already been submit.
1009              if ($this->substage == self::SUBSTAGE_PRECHECKS && optional_param('sesskey', null, PARAM_RAW) == sesskey()) {
1010                  $info = $rc->get_info();
1011                  if (!empty($info->role_mappings->mappings)) {
1012                      foreach ($info->role_mappings->mappings as $key => &$mapping) {
1013                          $mapping->targetroleid = optional_param('mapping'.$key, $mapping->targetroleid, PARAM_INT);
1014                      }
1015                      $info->role_mappings->modified = true;
1016                  }
1017                  // We've processed the substage now setting it back to none so we
1018                  // can move to the next stage.
1019                  $this->substage = self::SUBSTAGE_NONE;
1020              }
1021          }
1022  
1023          return empty($this->substage);
1024      }
1025      /**
1026       * should NEVER be called... throws an exception
1027       */
1028      protected function initialise_stage_form() {
1029          throw new backup_ui_exception('backup_ui_must_execute_first');
1030      }
1031  
1032      /**
1033       * Renders the process stage screen
1034       *
1035       * @throws restore_ui_exception
1036       * @param core_backup_renderer $renderer renderer instance to use
1037       * @return string HTML code
1038       */
1039      public function display(core_backup_renderer $renderer) {
1040          global $PAGE;
1041  
1042          $html = '';
1043          $haserrors = false;
1044          $url = new moodle_url($PAGE->url, array(
1045              'restore'   => $this->get_uniqueid(),
1046              'stage'     => restore_ui::STAGE_PROCESS,
1047              'substage'  => $this->substage,
1048              'sesskey'   => sesskey()));
1049          $html .= html_writer::start_tag('form', array(
1050              'action'    => $url->out_omit_querystring(),
1051              'class'     => 'backup-restore',
1052              'enctype'   => 'application/x-www-form-urlencoded', // Enforce compatibility with our max_input_vars hack.
1053              'method'    => 'post'));
1054          foreach ($url->params() as $name => $value) {
1055              $html .= html_writer::empty_tag('input', array(
1056                  'type'  => 'hidden',
1057                  'name'  => $name,
1058                  'value' => $value));
1059          }
1060          switch ($this->substage) {
1061              case self::SUBSTAGE_PRECHECKS :
1062                  $results = $this->ui->get_controller()->get_precheck_results();
1063                  $info = $this->ui->get_controller()->get_info();
1064                  $haserrors = (!empty($results['errors']));
1065                  $html .= $renderer->precheck_notices($results);
1066                  if (!empty($info->role_mappings->mappings)) {
1067                      $context = context_course::instance($this->ui->get_controller()->get_courseid());
1068                      $assignableroles = get_assignable_roles($context, ROLENAME_ALIAS, false);
1069  
1070                      // Get current role mappings.
1071                      $currentroles = role_fix_names(get_all_roles(), $context);
1072                      // Get backup role mappings.
1073                      $rolemappings = $info->role_mappings->mappings;
1074  
1075                      array_map(function($rolemapping) use ($currentroles) {
1076                          foreach ($currentroles as $role) {
1077                              // Find matching archetype to determine the backup's shortname for label display.
1078                              if ($rolemapping->archetype == $role->archetype) {
1079                                  $rolemapping->name = $rolemapping->shortname;
1080                                  break;
1081                              }
1082                          }
1083                          if ($rolemapping->name == null) {
1084                              $rolemapping->name = get_string('undefinedrolemapping', 'backup', $rolemapping->archetype);
1085                          }
1086                      }, $rolemappings);
1087  
1088                      $html .= $renderer->role_mappings($rolemappings, $assignableroles);
1089                  }
1090                  break;
1091              default:
1092                  throw new restore_ui_exception('backup_ui_must_execute_first');
1093          }
1094          $html .= $renderer->substage_buttons($haserrors);
1095          $html .= html_writer::end_tag('form');
1096  
1097          return $html;
1098      }
1099  
1100      /**
1101       * Returns true if this stage can have sub-stages.
1102       * @return bool|false
1103       */
1104      public function has_sub_stages() {
1105          return true;
1106      }
1107  }
1108  
1109  /**
1110   * This is the completed stage.
1111   *
1112   * Once this is displayed there is nothing more to do.
1113   *
1114   * @package   core_backup
1115   * @copyright 2010 Sam Hemelryk
1116   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1117   */
1118  class restore_ui_stage_complete extends restore_ui_stage_process {
1119  
1120      /**
1121       * The results of the backup execution
1122       * @var array
1123       */
1124      protected $results;
1125  
1126      /**
1127       * Constructs the complete backup stage
1128       * @param restore_ui $ui
1129       * @param array $params
1130       * @param array $results
1131       */
1132      public function __construct(restore_ui $ui, array $params = null, array $results = null) {
1133          $this->results = $results;
1134          parent::__construct($ui, $params);
1135          $this->stage = restore_ui::STAGE_COMPLETE;
1136      }
1137  
1138      /**
1139       * Displays the completed backup stage.
1140       *
1141       * Currently this just envolves redirecting to the file browser with an
1142       * appropriate message.
1143       *
1144       * @param core_backup_renderer $renderer
1145       * @return string HTML code to echo
1146       */
1147      public function display(core_backup_renderer $renderer) {
1148  
1149          $html  = '';
1150          if (!empty($this->results['file_aliases_restore_failures'])) {
1151              $html .= $renderer->box_start('generalbox filealiasesfailures');
1152              $html .= $renderer->heading_with_help(get_string('filealiasesrestorefailures', 'core_backup'),
1153                  'filealiasesrestorefailures', 'core_backup');
1154              $html .= $renderer->container(get_string('filealiasesrestorefailuresinfo', 'core_backup'));
1155              $html .= $renderer->container_start('aliaseslist');
1156              $html .= html_writer::start_tag('ul');
1157              foreach ($this->results['file_aliases_restore_failures'] as $alias) {
1158                  $html .= html_writer::tag('li', s($alias));
1159              }
1160              $html .= html_writer::end_tag('ul');
1161              $html .= $renderer->container_end();
1162              $html .= $renderer->box_end();
1163          }
1164          $html .= $renderer->box_start();
1165          if (array_key_exists('file_missing_in_backup', $this->results)) {
1166              $html .= $renderer->notification(get_string('restorefileweremissing', 'backup'), 'notifyproblem');
1167          }
1168          $html .= $renderer->notification(get_string('restoreexecutionsuccess', 'backup'), 'notifysuccess');
1169  
1170          $courseurl = course_get_url($this->get_ui()->get_controller()->get_courseid());
1171          $html .= $renderer->continue_button($courseurl, 'get');
1172  
1173          $html .= $renderer->box_end();
1174  
1175          return $html;
1176      }
1177  }