Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   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   * Backup and restore actions to help behat feature files writting.
  19   *
  20   * @package    core_backup
  21   * @category   test
  22   * @copyright  2013 David MonllaĆ³
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
  27  
  28  require_once (__DIR__ . '/../../../../../lib/behat/behat_base.php');
  29  require_once (__DIR__ . '/../../../../../lib/behat/behat_field_manager.php');
  30  require_once (__DIR__ . '/../../../../../lib/tests/behat/behat_navigation.php');
  31  require_once (__DIR__ . '/../../../../../lib/behat/form_field/behat_form_field.php');
  32  
  33  use Behat\Gherkin\Node\TableNode as TableNode,
  34      Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
  35      Behat\Mink\Exception\ExpectationException as ExpectationException;
  36  
  37  /**
  38   * Backup-related steps definitions.
  39   *
  40   * @package    core_backup
  41   * @category   test
  42   * @copyright  2013 David MonllaĆ³
  43   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  class behat_backup extends behat_base {
  46  
  47      /**
  48       * Backups the specified course using the provided options. If you are interested in restoring this backup would be
  49       * useful to provide a 'Filename' option.
  50       *
  51       * @Given /^I backup "(?P<course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
  52       * @param string $backupcourse
  53       * @param TableNode $options Backup options or false if no options provided
  54       */
  55      public function i_backup_course_using_this_options($backupcourse, $options = false) {
  56          // We can not use other steps here as we don't know where the provided data
  57          // table elements are used, and we need to catch exceptions contantly.
  58  
  59          // Navigate to the course backup page.
  60          $this->execute("behat_navigation::i_am_on_page_instance", [$backupcourse, 'backup']);
  61  
  62          // Initial settings.
  63          $this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
  64          $this->execute("behat_forms::press_button", get_string('backupstage1action', 'backup'));
  65  
  66          // Schema settings.
  67          $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
  68          $this->execute("behat_forms::press_button", get_string('backupstage2action', 'backup'));
  69  
  70          // Confirmation and review, backup filename can also be specified.
  71          $this->fill_backup_restore_form($this->get_step_options($options, "Confirmation"));
  72          $this->execute("behat_forms::press_button", get_string('backupstage4action', 'backup'));
  73  
  74          // Waiting for it to finish.
  75          $this->execute("behat_general::wait_until_the_page_is_ready");
  76  
  77          // Last backup continue button.
  78          $this->execute("behat_general::i_click_on", array(get_string('backupstage16action', 'backup'), 'button'));
  79      }
  80  
  81      /**
  82       * Performs a quick (one click) backup of a course.
  83       *
  84       * Please note that because you can't set settings with this there is no way to know what the filename
  85       * that was produced was. It contains a timestamp making it hard to find.
  86       *
  87       * @Given /^I perform a quick backup of course "(?P<course_fullname_string>(?:[^"]|\\")*)"$/
  88       * @param string $backupcourse
  89       */
  90      public function i_perform_a_quick_backup_of_course($backupcourse) {
  91          // We can not use other steps here as we don't know where the provided data
  92          // table elements are used, and we need to catch exceptions contantly.
  93  
  94          // Navigate to the course backup page.
  95          $this->execute("behat_navigation::i_am_on_page_instance", [$backupcourse, 'backup']);
  96  
  97          // Initial settings.
  98          $this->execute("behat_forms::press_button", get_string('jumptofinalstep', 'backup'));
  99  
 100          // Waiting for it to finish.
 101          $this->execute("behat_general::wait_until_the_page_is_ready");
 102  
 103          // Last backup continue button.
 104          $this->execute("behat_general::i_click_on", array(get_string('backupstage16action', 'backup'), 'button'));
 105      }
 106  
 107      /**
 108       * Imports the specified origin course into the other course using the provided options.
 109       *
 110       * Keeping it separatelly from backup & restore, it the number of
 111       * steps and duplicate code becomes bigger a common method should
 112       * be generalized.
 113       *
 114       * @Given /^I import "(?P<from_course_fullname_string>(?:[^"]|\\")*)" course into "(?P<to_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
 115       * @param string $fromcourse
 116       * @param string $tocourse
 117       * @param TableNode $options
 118       */
 119      public function i_import_course_into_course($fromcourse, $tocourse, $options = false) {
 120  
 121          // We can not use other steps here as we don't know where the provided data
 122          // table elements are used, and we need to catch exceptions contantly.
 123  
 124          // Navigate to the course import page.
 125          $this->execute("behat_navigation::i_am_on_page_instance", [$tocourse, 'import']);
 126  
 127          // Select the course.
 128          $fromcourse = behat_context_helper::escape($fromcourse);
 129          $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' ics-results ')]" .
 130              "/descendant::tr[contains(., $fromcourse)]" .
 131              "/descendant::input[@type='radio']";
 132          $this->execute('behat_forms::i_set_the_field_with_xpath_to', [$xpath, 1]);
 133  
 134          $this->execute("behat_forms::press_button", get_string('continue'));
 135  
 136          // Initial settings.
 137          $this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
 138          $this->execute("behat_forms::press_button", get_string('importbackupstage1action', 'backup'));
 139  
 140          // Schema settings.
 141          $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
 142          $this->execute("behat_forms::press_button", get_string('importbackupstage2action', 'backup'));
 143  
 144          // Run it.
 145          $this->execute("behat_forms::press_button", get_string('importbackupstage4action', 'backup'));
 146  
 147          // Wait to ensure restore is complete.
 148          $this->execute("behat_general::wait_until_the_page_is_ready");
 149  
 150          // Continue and redirect to 'to' course.
 151          $this->execute("behat_general::i_click_on", array(get_string('continue'), 'button'));
 152      }
 153  
 154      /**
 155       * Restores the backup into the specified course and the provided options.
 156       *
 157       * You should be in the 'Restore' page where the backup is.
 158       *
 159       * @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into "(?P<existing_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
 160       * @param string $backupfilename
 161       * @param string $existingcourse
 162       * @param TableNode $options Restore forms options or false if no options provided
 163       */
 164      public function i_restore_backup_into_course_using_this_options($backupfilename, $existingcourse, $options = false) {
 165  
 166          // Confirm restore.
 167          $this->select_backup($backupfilename);
 168  
 169          // The argument should be converted to an xpath literal.
 170          $existingcourse = behat_context_helper::escape($existingcourse);
 171  
 172          // Selecting the specified course (we can not call behat_forms::select_radio here as is in another behat subcontext).
 173          $radionodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
 174              "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' restore-course-search ')]" .
 175              "/descendant::tr[contains(., $existingcourse)]" .
 176              "/descendant::input[@type='radio']";
 177          $this->execute("behat_general::i_click_on", array($radionodexpath, 'xpath_element'));
 178  
 179          // Pressing the continue button of the restore into an existing course section.
 180          $continuenodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
 181              "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']";
 182          $this->execute("behat_general::i_click_on", array($continuenodexpath, 'xpath_element'));
 183  
 184          // Common restore process using provided key/value options.
 185          $this->process_restore($options);
 186      }
 187  
 188      /**
 189       * Restores the specified backup into a new course using the provided options.
 190       *
 191       * You should be in the 'Restore' page where the backup is.
 192       *
 193       * @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into a new course using this options:$/
 194       * @param string $backupfilename
 195       * @param TableNode $options Restore forms options or false if no options provided
 196       */
 197      public function i_restore_backup_into_a_new_course_using_this_options($backupfilename, $options = false) {
 198  
 199          // Confirm restore.
 200          $this->select_backup($backupfilename);
 201  
 202          // The first category in the list.
 203          $radionodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
 204              "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' restore-course-search ')]" .
 205              "/descendant::input[@type='radio']";
 206          $this->execute("behat_general::i_click_on", array($radionodexpath, 'xpath_element'));
 207  
 208          // Pressing the continue button of the restore into an existing course section.
 209          $continuenodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
 210              "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']";
 211          $this->execute("behat_general::i_click_on", array($continuenodexpath, 'xpath_element'));
 212  
 213          // Common restore process using provided key/value options.
 214          $this->process_restore($options);
 215      }
 216  
 217      /**
 218       * Merges the backup into the current course using the provided restore options.
 219       *
 220       * You should be in the 'Restore' page where the backup is.
 221       *
 222       * @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course using this options:$/
 223       * @param string $backupfilename
 224       * @param TableNode $options Restore forms options or false if no options provided
 225       */
 226      public function i_merge_backup_into_the_current_course($backupfilename, $options = false) {
 227  
 228          // Confirm restore.
 229          $this->select_backup($backupfilename);
 230  
 231          // Merge without deleting radio option.
 232          $radionodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
 233              "/descendant::input[@type='radio'][@name='target'][@value='1']";
 234          $this->execute("behat_general::i_click_on", array($radionodexpath, 'xpath_element'));
 235  
 236          // Pressing the continue button of the restore merging section.
 237          $continuenodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
 238              "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']";
 239          $this->execute("behat_general::i_click_on", array($continuenodexpath, 'xpath_element'));
 240  
 241          // Common restore process using provided key/value options.
 242          $this->process_restore($options);
 243      }
 244  
 245      /**
 246       * Merges the backup into the current course after deleting this contents, using the provided restore options.
 247       *
 248       * You should be in the 'Restore' page where the backup is.
 249       *
 250       * @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course after deleting it's contents using this options:$/
 251       * @param string $backupfilename
 252       * @param TableNode $options Restore forms options or false if no options provided
 253       */
 254      public function i_merge_backup_into_current_course_deleting_its_contents($backupfilename, $options = false) {
 255  
 256          // Confirm restore.
 257          $this->select_backup($backupfilename);
 258  
 259          // Delete contents radio option.
 260          $radionodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
 261              "/descendant::input[@type='radio'][@name='target'][@value='0']";
 262          $this->execute("behat_general::i_click_on", array($radionodexpath, 'xpath_element'));
 263  
 264          // Pressing the continue button of the restore merging section.
 265          $continuenodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
 266              "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']";
 267          $this->execute("behat_general::i_click_on", array($continuenodexpath, 'xpath_element'));
 268  
 269          // Common restore process using provided key/value options.
 270          $this->process_restore($options);
 271      }
 272  
 273      /**
 274       * Selects the backup to restore.
 275       *
 276       * @throws ExpectationException
 277       * @param string $backupfilename
 278       * @return void
 279       */
 280      protected function select_backup($backupfilename) {
 281  
 282          // Using xpath as there are other restore links before this one.
 283          $exception = new ExpectationException('The "' . $backupfilename . '" backup file can not be found in this page',
 284              $this->getSession());
 285  
 286          // The argument should be converted to an xpath literal.
 287          $backupfilename = behat_context_helper::escape($backupfilename);
 288  
 289          $xpath = "//tr[contains(., $backupfilename)]/descendant::a[contains(., '" . get_string('restore') . "')]";
 290          $restorelink = $this->find('xpath', $xpath, $exception);
 291          $restorelink->click();
 292  
 293          // Confirm the backup contents.
 294          $this->find_button(get_string('continue'))->press();
 295      }
 296  
 297      /**
 298       * Executes the common steps of all restore processes.
 299       *
 300       * @param TableNode $options The backup and restore options or false if no options provided
 301       * @return void
 302       */
 303      protected function process_restore($options) {
 304  
 305          // We can not use other steps here as we don't know where the provided data
 306          // table elements are used, and we need to catch exceptions contantly.
 307  
 308          // Settings.
 309          $this->fill_backup_restore_form($this->get_step_options($options, "Settings"));
 310          $this->execute("behat_forms::press_button", get_string('restorestage4action', 'backup'));
 311  
 312          // Schema.
 313          $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
 314          $this->execute("behat_forms::press_button", get_string('restorestage8action', 'backup'));
 315  
 316          // Review, no options here.
 317          $this->execute("behat_forms::press_button", get_string('restorestage16action', 'backup'));
 318  
 319          // Wait till the final button is visible.
 320          $this->execute("behat_general::wait_until_the_page_is_ready");
 321  
 322          // Last restore continue button, redirected to restore course after this.
 323          $this->execute("behat_general::i_click_on", array(get_string('restorestage32action', 'backup'), 'button'));
 324      }
 325  
 326      /**
 327       * Tries to fill the current page form elements with the provided options.
 328       *
 329       * This step is slow as it spins over each provided option, we are
 330       * not expected to have lots of provided options, anyways, is better
 331       * to be conservative and wait for the elements to appear rather than
 332       * to have false failures.
 333       *
 334       * @param TableNode $options The backup and restore options or false if no options provided
 335       * @return void
 336       */
 337      protected function fill_backup_restore_form($options) {
 338  
 339          // Nothing to fill if no options are provided.
 340          if (!$options) {
 341              return;
 342          }
 343  
 344          // If we find any of the provided options in the current form we should set the value.
 345          $datahash = $options->getRowsHash();
 346          foreach ($datahash as $locator => $value) {
 347              $field = behat_field_manager::get_form_field_from_label($locator, $this);
 348              $field->set_value($value);
 349          }
 350      }
 351  
 352      /**
 353       * Get the options specific to this step of the backup/restore process.
 354       *
 355       * @param TableNode $options The options table to filter
 356       * @param string $step The name of the step
 357       * @return TableNode The filtered options table
 358       * @throws ExpectationException
 359       */
 360      protected function get_step_options($options, $step) {
 361          // Nothing to fill if no options are provided.
 362          if (!$options) {
 363              return;
 364          }
 365  
 366          $rows = $options->getRows();
 367          $newrows = array();
 368          foreach ($rows as $k => $data) {
 369              if (count($data) !== 3) {
 370                  // Not enough information to guess the page.
 371                  throw new ExpectationException("The backup/restore step must be specified for all backup options",
 372                      $this->getSession());
 373              } else if ($data[0] == $step) {
 374                  unset($data[0]);
 375                  $newrows[] = $data;
 376              }
 377          }
 378          $pageoptions = new TableNode($newrows);
 379  
 380          return $pageoptions;
 381      }
 382  }