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] [Versions 400 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  namespace core_backup;
  18  
  19  use backup;
  20  use backup_controller;
  21  
  22  defined('MOODLE_INTERNAL') || die();
  23  
  24  global $CFG;
  25  require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
  26  require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
  27  require_once($CFG->libdir . '/completionlib.php');
  28  
  29  /**
  30   * Asyncronhous backup tests.
  31   *
  32   * @package    core_backup
  33   * @copyright  2018 Matt Porritt <mattp@catalyst-au.net>
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class async_backup_test extends \advanced_testcase {
  37  
  38      /**
  39       * Tests the asynchronous backup.
  40       */
  41      public function test_async_backup() {
  42          global $CFG, $DB, $USER;
  43  
  44          $this->resetAfterTest(true);
  45          $this->setAdminUser();
  46          $CFG->enableavailability = true;
  47          $CFG->enablecompletion = true;
  48  
  49          // Create a course with some availability data set.
  50          $generator = $this->getDataGenerator();
  51          $course = $generator->create_course(
  52                  array('format' => 'topics', 'numsections' => 3,
  53                          'enablecompletion' => COMPLETION_ENABLED),
  54                  array('createsections' => true));
  55          $forum = $generator->create_module('forum', array(
  56                  'course' => $course->id));
  57          $forum2 = $generator->create_module('forum', array(
  58                  'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL));
  59  
  60          // Create a teacher user to call the backup.
  61          $teacher = $generator->create_user();
  62          $generator->enrol_user($teacher->id, $course->id, 'editingteacher');
  63  
  64          // We need a grade, easiest is to add an assignment.
  65          $assignrow = $generator->create_module('assign', array(
  66                  'course' => $course->id));
  67          $assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
  68          $item = $assign->get_grade_item();
  69  
  70          // Make a test grouping as well.
  71          $grouping = $generator->create_grouping(array('courseid' => $course->id,
  72                  'name' => 'Grouping!'));
  73  
  74          $availability = '{"op":"|","show":false,"c":[' .
  75                  '{"type":"completion","cm":' . $forum2->cmid .',"e":1},' .
  76                  '{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' .
  77                  '{"type":"grouping","id":' . $grouping->id . '}' .
  78                  ']}';
  79          $DB->set_field('course_modules', 'availability', $availability, array(
  80                  'id' => $forum->cmid));
  81          $DB->set_field('course_sections', 'availability', $availability, array(
  82                  'course' => $course->id, 'section' => 1));
  83  
  84          // Enable logging.
  85          $this->preventResetByRollback();
  86          set_config('enabled_stores', 'logstore_standard', 'tool_log');
  87          set_config('buffersize', 0, 'logstore_standard');
  88          get_log_manager(true);
  89  
  90          // Case 1: Make a course backup without users.
  91          $this->setUser($teacher->id);
  92  
  93          // Make the backup controller for an async backup.
  94          $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
  95                  backup::INTERACTIVE_YES, backup::MODE_ASYNC, $USER->id);
  96          $bc->finish_ui();
  97          $backupid = $bc->get_backupid();
  98          $bc->destroy();
  99  
 100          $prebackuprec = $DB->get_record('backup_controllers', array('backupid' => $backupid));
 101  
 102          // Check the initial backup controller was created correctly.
 103          $this->assertEquals(backup::STATUS_AWAITING, $prebackuprec->status);
 104          $this->assertEquals(2, $prebackuprec->execution);
 105  
 106          // Create the adhoc task.
 107          $asynctask = new \core\task\asynchronous_backup_task();
 108          $asynctask->set_blocking(false);
 109          $asynctask->set_custom_data(['backupid' => $backupid]);
 110          $asynctask->set_userid($USER->id);
 111          \core\task\manager::queue_adhoc_task($asynctask);
 112  
 113          // We are expecting trace output during this test.
 114          $this->expectOutputRegex("/$backupid/");
 115  
 116          // Execute adhoc task.
 117          $now = time();
 118          $task = \core\task\manager::get_next_adhoc_task($now);
 119          $this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
 120          $task->execute();
 121          \core\task\manager::adhoc_task_complete($task);
 122  
 123          $postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
 124  
 125          // Check backup was created successfully.
 126          $this->assertEquals(backup::STATUS_FINISHED_OK, $postbackuprec->status);
 127          $this->assertEquals(1.0, $postbackuprec->progress);
 128          $this->assertEquals($teacher->id, $postbackuprec->userid);
 129  
 130          // Check that the backupid was logged correctly.
 131          $logrec = $DB->get_record('logstore_standard_log', ['userid' => $postbackuprec->userid,
 132                  'target' => 'course_backup'], '*', MUST_EXIST);
 133          $otherdata = json_decode($logrec->other);
 134          $this->assertEquals($backupid, $otherdata->backupid);
 135  
 136          // Check backup was stored in correct area.
 137          $usercontextid = $DB->get_field('context', 'id', ['contextlevel' => CONTEXT_USER, 'instanceid' => $teacher->id]);
 138          $this->assertEquals(1, $DB->count_records('files', ['contextid' => $usercontextid,
 139                  'component' => 'user', 'filearea' => 'backup', 'filename' => 'backup.mbz']));
 140  
 141          // Case 2: Make a second backup with users and not anonymised.
 142          $this->setAdminUser();
 143          $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
 144                  backup::INTERACTIVE_YES, backup::MODE_ASYNC, $USER->id);
 145          $bc->get_plan()->get_setting('users')->set_status(\backup_setting::NOT_LOCKED);
 146          $bc->get_plan()->get_setting('users')->set_value(true);
 147          $bc->get_plan()->get_setting('anonymize')->set_value(false);
 148          $bc->finish_ui();
 149          $backupid = $bc->get_backupid();
 150          $bc->destroy();
 151  
 152          // Create the adhoc task.
 153          $asynctask = new \core\task\asynchronous_backup_task();
 154          $asynctask->set_blocking(false);
 155          $asynctask->set_custom_data(['backupid' => $backupid]);
 156          \core\task\manager::queue_adhoc_task($asynctask);
 157  
 158          // Execute adhoc task.
 159          $now = time();
 160          $task = \core\task\manager::get_next_adhoc_task($now);
 161          $task->execute();
 162          \core\task\manager::adhoc_task_complete($task);
 163  
 164          $postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
 165  
 166          // Check backup was created successfully.
 167          $this->assertEquals(backup::STATUS_FINISHED_OK, $postbackuprec->status);
 168          $this->assertEquals(1.0, $postbackuprec->progress);
 169          $this->assertEquals($USER->id, $postbackuprec->userid);
 170  
 171          // Check that the backupid was logged correctly.
 172          $logrec = $DB->get_record('logstore_standard_log', ['userid' => $postbackuprec->userid,
 173                  'target' => 'course_backup'], '*', MUST_EXIST);
 174          $otherdata = json_decode($logrec->other);
 175          $this->assertEquals($backupid, $otherdata->backupid);
 176  
 177          // Check backup was stored in correct area.
 178          $coursecontextid = $DB->get_field('context', 'id', ['contextlevel' => CONTEXT_COURSE, 'instanceid' => $course->id]);
 179          $this->assertEquals(1, $DB->count_records('files', ['contextid' => $coursecontextid,
 180                  'component' => 'backup', 'filearea' => 'course', 'filename' => 'backup.mbz']));
 181      }
 182  
 183      /**
 184       * Tests the asynchronous backup will resolve in duplicate cases.
 185       */
 186      public function test_complete_async_backup() {
 187          global $CFG, $DB, $USER;
 188  
 189          $this->resetAfterTest(true);
 190          $this->setAdminUser();
 191          $CFG->enableavailability = true;
 192          $CFG->enablecompletion = true;
 193  
 194          // Create a course with some availability data set.
 195          $generator = $this->getDataGenerator();
 196          $course = $generator->create_course(
 197                  array('format' => 'topics', 'numsections' => 3,
 198                          'enablecompletion' => COMPLETION_ENABLED),
 199                  array('createsections' => true));
 200          $forum = $generator->create_module('forum', array(
 201                  'course' => $course->id));
 202          $forum2 = $generator->create_module('forum', array(
 203                  'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL));
 204  
 205          // We need a grade, easiest is to add an assignment.
 206          $assignrow = $generator->create_module('assign', array(
 207                  'course' => $course->id));
 208          $assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
 209          $item = $assign->get_grade_item();
 210  
 211          // Make a test grouping as well.
 212          $grouping = $generator->create_grouping(array('courseid' => $course->id,
 213                  'name' => 'Grouping!'));
 214  
 215          $availability = '{"op":"|","show":false,"c":[' .
 216                  '{"type":"completion","cm":' . $forum2->cmid .',"e":1},' .
 217                  '{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' .
 218                  '{"type":"grouping","id":' . $grouping->id . '}' .
 219                  ']}';
 220          $DB->set_field('course_modules', 'availability', $availability, array(
 221                  'id' => $forum->cmid));
 222          $DB->set_field('course_sections', 'availability', $availability, array(
 223                  'course' => $course->id, 'section' => 1));
 224  
 225          // Make the backup controller for an async backup.
 226          $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
 227                  backup::INTERACTIVE_YES, backup::MODE_ASYNC, $USER->id);
 228          $bc->finish_ui();
 229          $backupid = $bc->get_backupid();
 230          $bc->destroy();
 231  
 232          // Now hack the record to remove the controller and set the status fields to complete.
 233          // This emulates a duplicate run for an already finished controller.
 234          $id = $DB->get_field('backup_controllers', 'id', ['backupid' => $backupid]);
 235          $data = [
 236              'id' => $id,
 237              'controller' => '',
 238              'progress' => 1.0,
 239              'status' => backup::STATUS_FINISHED_OK
 240          ];
 241          $DB->update_record('backup_controllers', $data);
 242  
 243          // Now queue an adhoc task and check it handles and completes gracefully.
 244          $asynctask = new \core\task\asynchronous_backup_task();
 245          $asynctask->set_blocking(false);
 246          $asynctask->set_custom_data(array('backupid' => $backupid));
 247          \core\task\manager::queue_adhoc_task($asynctask);
 248  
 249          // We are expecting a specific message output during this test.
 250          $this->expectOutputRegex('/invalid controller/');
 251  
 252          // Execute adhoc task.
 253          $now = time();
 254          $task = \core\task\manager::get_next_adhoc_task($now);
 255          $this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
 256          $task->execute();
 257          \core\task\manager::adhoc_task_complete($task);
 258  
 259          // Check the task record is removed.
 260          $this->assertEquals(0, $DB->count_records('task_adhoc'));
 261      }
 262  }