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.
   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 qbank_comment;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  global $CFG;
  22  require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
  23  require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
  24  require_once($CFG->dirroot. '/comment/lib.php');
  25  
  26  /**
  27   * Question comment backup and restore unit tests.
  28   *
  29   * @package    qbank_comment
  30   * @copyright  2021 Catalyst IT Australia Pty Ltd
  31   * @author     Matt Porritt <mattp@catalyst-au.net>
  32   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33   */
  34  class backup_test extends \advanced_testcase {
  35  
  36      /**
  37       * @var array Data object for generating a question.
  38       */
  39      protected $question1data;
  40  
  41      /**
  42       * @var array Data object for generating a question.
  43       */
  44      protected $question2data;
  45  
  46      /**
  47       * @var component_generator_base Question Generator.
  48       */
  49      protected $qgen;
  50  
  51      /**
  52       * @var core_course_category Course category.
  53       */
  54      protected $category;
  55  
  56      /**
  57       * @var stdClass Course object.
  58       */
  59      protected $course;
  60  
  61      /**
  62       * Set up
  63       */
  64      protected function setUp(): void {
  65          parent::setUp();
  66          $this->resetAfterTest();
  67          $this->setAdminUser();
  68  
  69          // Set up custom fields.
  70          $data = new \stdClass();
  71          $data->component = 'qbank_comment';
  72          $data->area = 'question';
  73  
  74          // Question initial set up.
  75          $this->category = $this->getDataGenerator()->create_category();
  76          $this->course = $this->getDataGenerator()->create_course(['category' => $this->category->id]);
  77          $context = \context_coursecat::instance($this->category->id);
  78          $this->qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
  79          $qcat = $this->qgen->create_question_category(['contextid' => $context->id]);
  80  
  81          $this->question1data = ['category' => $qcat->id, 'idnumber' => 'q1'];
  82          $this->question2data = ['category' => $qcat->id, 'idnumber' => 'q2'];
  83      }
  84  
  85      /**
  86       * Makes a backup of the course.
  87       *
  88       * @param \stdClass $course The course object.
  89       * @return string Unique identifier for this backup.
  90       */
  91      protected function backup_course(\stdClass $course): string {
  92          global $CFG, $USER;
  93  
  94          // Turn off file logging, otherwise it can't delete the file (Windows).
  95          $CFG->backup_file_logger_level = \backup::LOG_NONE;
  96  
  97          // Do backup with default settings. MODE_IMPORT means it will just
  98          // create the directory and not zip it.
  99          $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id,
 100                  \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, \backup::MODE_IMPORT,
 101                  $USER->id);
 102          $backupid = $bc->get_backupid();
 103          $bc->execute_plan();
 104          $bc->destroy();
 105  
 106          return $backupid;
 107      }
 108  
 109      /**
 110       * Restores a backup that has been made earlier.
 111       *
 112       * @param string $backupid The unique identifier of the backup.
 113       * @param string $fullname Full name of the new course that is going to be created.
 114       * @param string $shortname Short name of the new course that is going to be created.
 115       * @param int $categoryid The course category the backup is going to be restored in.
 116       * @return int The new course id.
 117       */
 118      protected function restore_course(string $backupid, string $fullname, string $shortname, int $categoryid): int {
 119          global $CFG, $USER;
 120  
 121          // Turn off file logging, otherwise it can't delete the file (Windows).
 122          $CFG->backup_file_logger_level = \backup::LOG_NONE;
 123  
 124          // Do restore to new course with default settings.
 125          $newcourseid = \restore_dbops::create_new_course($fullname, $shortname, $categoryid);
 126          $rc = new \restore_controller($backupid, $newcourseid,
 127                  \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id,
 128                  \backup::TARGET_NEW_COURSE);
 129  
 130          $rc->execute_precheck();
 131          $rc->execute_plan();
 132          $rc->destroy();
 133  
 134          return $newcourseid;
 135      }
 136  
 137      /**
 138       * Test comments attached to questions persist
 139       * across the backup and restore process.
 140       */
 141      public function test_backup_restore() {
 142          global $DB, $CFG;
 143          require_once($CFG->dirroot . '/comment/lib.php');
 144          $this->resetAfterTest();
 145          $this->setAdminUser();
 146  
 147          $courseshortname = $this->course->shortname;
 148          $coursefullname = $this->course->fullname;
 149  
 150          // Create 2 questions.
 151          $question1 = $this->qgen->create_question('shortanswer', null, $this->question1data);
 152          $question2 = $this->qgen->create_question('shortanswer', null, $this->question2data);
 153  
 154          // Add comments to the questions.
 155          $args = new \stdClass;
 156          $args->context = \context_system::instance();
 157          $args->course = $this->course;
 158          $args->area = 'question';
 159          $args->itemid = $question1->id;
 160          $args->component = 'qbank_comment';
 161          $args->linktext = get_string('commentheader', 'qbank_comment');
 162          $args->notoggle = true;
 163          $args->autostart = true;
 164          $args->displaycancel = false;
 165  
 166          // Two comments for question 1.
 167          $commentobj1 = new \comment($args);
 168          $commentobj1->add('new \comment for question 1 _ 1');
 169          $comment1 = $commentobj1->add('new \comment for question 1 _ 2');
 170  
 171          // One comment for question 2.
 172          $args->itemid = $question2->id;
 173          $commentobj2 = new \comment($args);
 174          $comment2 = $commentobj2->add('new \comment for question 2');
 175  
 176          // Create a quiz and the questions to that.
 177          $quiz = $this->getDataGenerator()->create_module(
 178                  'quiz', ['course' => $this->course->id, 'name' => 'restored_quiz']);
 179          quiz_add_quiz_question($question1->id, $quiz);
 180          quiz_add_quiz_question($question2->id, $quiz);
 181  
 182          // Backup the course.
 183          $backupid = $this->backup_course($this->course);
 184  
 185          // Now delete everything.
 186          delete_course($this->course, false);
 187          question_delete_question($question1->id);
 188          question_delete_question($question2->id);
 189  
 190          // Check the comment data for the questions has also gone.
 191          $DB->record_exists('comments', ['id' => $comment1->id]);
 192          $this->assertFalse($DB->record_exists('comments', ['id' => $comment1->id]));
 193          $this->assertFalse($DB->record_exists('comments', ['id' => $comment2->id]));
 194  
 195          // Restore the backup we had made earlier into a new course.
 196          $newcategory = $this->getDataGenerator()->create_category();
 197          $this->restore_course($backupid, $coursefullname, $courseshortname . '_2', $newcategory->id);
 198  
 199          // The questions and their associated comments should have been restored.
 200          $sql =
 201              'SELECT q.*
 202                 FROM {question} q
 203                 JOIN {question_versions} qv ON qv.questionid = q.id
 204                 JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
 205                 WHERE qbe.idnumber = ?';
 206          $newquestion1 = $DB->get_record_sql($sql, ['idnumber' => 'q1']);
 207          $args->itemid = $newquestion1->id;
 208          $commentobj = new \comment($args);
 209          $this->assertEquals($commentobj->count(), 2);
 210  
 211          $newquestion2 = $DB->get_record_sql($sql, ['idnumber' => 'q2']);
 212          $args->itemid = $newquestion2->id;
 213          $commentobj = new \comment($args);
 214          $this->assertEquals($commentobj->count(), 1);
 215      }
 216  }