Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   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   * Recycle bin tests.
  19   *
  20   * @package    tool_recyclebin
  21   * @copyright  2015 University of Kent
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  /**
  28   * Recycle bin course tests.
  29   *
  30   * @package    tool_recyclebin
  31   * @copyright  2015 University of Kent
  32   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33   */
  34  class tool_recyclebin_course_bin_tests extends advanced_testcase {
  35  
  36      /**
  37       * @var stdClass $course
  38       */
  39      protected $course;
  40  
  41      /**
  42       * @var stdClass the quiz record
  43       */
  44      protected $quiz;
  45  
  46      /**
  47       * Setup for each test.
  48       */
  49      protected function setUp(): void {
  50          $this->resetAfterTest(true);
  51          $this->setAdminUser();
  52  
  53          // We want the course bin to be enabled.
  54          set_config('coursebinenable', 1, 'tool_recyclebin');
  55  
  56          $this->course = $this->getDataGenerator()->create_course();
  57          $this->quiz = $this->getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(array(
  58              'course' => $this->course->id, 'grade' => 100.0, 'sumgrades' => 1
  59          ));
  60      }
  61  
  62      /**
  63       * Check that our hook is called when an activity is deleted.
  64       */
  65      public function test_pre_course_module_delete_hook() {
  66          global $DB;
  67  
  68          // Should have nothing in the recycle bin.
  69          $this->assertEquals(0, $DB->count_records('tool_recyclebin_course'));
  70  
  71          // Delete the course module.
  72          course_delete_module($this->quiz->cmid);
  73  
  74          // Now, run the course module deletion adhoc task.
  75          phpunit_util::run_all_adhoc_tasks();
  76  
  77          // Check the course module is now in the recycle bin.
  78          $this->assertEquals(1, $DB->count_records('tool_recyclebin_course'));
  79  
  80          // Try with the API.
  81          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
  82          $this->assertEquals(1, count($recyclebin->get_items()));
  83      }
  84  
  85      /**
  86       * Test that we can restore recycle bin items.
  87       */
  88      public function test_restore() {
  89          global $DB;
  90  
  91          $startcount = $DB->count_records('course_modules');
  92  
  93          // Delete the course module.
  94          course_delete_module($this->quiz->cmid);
  95  
  96          // Try restoring.
  97          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
  98          foreach ($recyclebin->get_items() as $item) {
  99              $recyclebin->restore_item($item);
 100          }
 101  
 102          // Check that it was restored and removed from the recycle bin.
 103          $this->assertEquals($startcount, $DB->count_records('course_modules'));
 104          $this->assertEquals(0, count($recyclebin->get_items()));
 105      }
 106  
 107      /**
 108       * Test that we can delete recycle bin items.
 109       */
 110      public function test_delete() {
 111          global $DB;
 112  
 113          $startcount = $DB->count_records('course_modules');
 114  
 115          // Delete the course module.
 116          course_delete_module($this->quiz->cmid);
 117  
 118          // Now, run the course module deletion adhoc task.
 119          phpunit_util::run_all_adhoc_tasks();
 120  
 121          // Try purging.
 122          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
 123          foreach ($recyclebin->get_items() as $item) {
 124              $recyclebin->delete_item($item);
 125          }
 126  
 127          // Item was deleted, so no course module was restored.
 128          $this->assertEquals($startcount - 1, $DB->count_records('course_modules'));
 129          $this->assertEquals(0, count($recyclebin->get_items()));
 130      }
 131  
 132      /**
 133       * Test the cleanup task.
 134       */
 135      public function test_cleanup_task() {
 136          global $DB;
 137  
 138          set_config('coursebinexpiry', WEEKSECS, 'tool_recyclebin');
 139  
 140          // Delete the quiz.
 141          course_delete_module($this->quiz->cmid);
 142  
 143          // Now, run the course module deletion adhoc task.
 144          phpunit_util::run_all_adhoc_tasks();
 145  
 146          // Set deleted date to the distant past.
 147          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
 148          foreach ($recyclebin->get_items() as $item) {
 149              $item->timecreated = time() - WEEKSECS;
 150              $DB->update_record('tool_recyclebin_course', $item);
 151          }
 152  
 153          // Create another module we are going to delete, but not alter the time it was placed in the recycle bin.
 154          $book = $this->getDataGenerator()->get_plugin_generator('mod_book')->create_instance(array(
 155              'course' => $this->course->id));
 156  
 157          course_delete_module($book->cmid);
 158  
 159          // Now, run the course module deletion adhoc task.
 160          phpunit_util::run_all_adhoc_tasks();
 161  
 162          // Should have 2 items now.
 163          $this->assertEquals(2, count($recyclebin->get_items()));
 164  
 165          // Execute cleanup task.
 166          $this->expectOutputRegex("/\[tool_recyclebin\] Deleting item '\d+' from the course recycle bin/");
 167          $task = new \tool_recyclebin\task\cleanup_course_bin();
 168          $task->execute();
 169  
 170          // Should only have the book as it was not due to be deleted.
 171          $items = $recyclebin->get_items();
 172          $this->assertEquals(1, count($items));
 173          $deletedbook = reset($items);
 174          $this->assertEquals($book->name, $deletedbook->name);
 175      }
 176  
 177      /**
 178       * Provider for test_coursemodule_restore_with_userdata() and test_coursemodule_restore_without_userdata()
 179       *
 180       * Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for
 181       * direct usage with set_config()
 182       */
 183      public function recycle_bin_settings_provider() {
 184          return [
 185              'backup/backup_auto_storage moodle' => [[
 186                  (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0],
 187              ]],
 188  
 189              'backup/backup_auto_storage external' => [[
 190                  (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1],
 191                  (object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
 192              ]],
 193  
 194              'backup/backup_auto_storage mixed' => [[
 195                  (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
 196                  (object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
 197              ]],
 198          ];
 199      }
 200  
 201      /**
 202       * Tests that user data is restored when module is restored.
 203       *
 204       * @dataProvider recycle_bin_settings_provider
 205       * @param array $settings array of plugin, name, value stdClass().
 206       */
 207      public function test_coursemodule_restore_with_userdata($settings) {
 208          // Force configuration changes from provider.
 209          foreach ($settings as $setting) {
 210              // Need to create a directory for backup_auto_destination.
 211              if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
 212                  $setting->value = make_request_directory();
 213              }
 214              set_config($setting->name, $setting->value, $setting->plugin);
 215          }
 216  
 217          $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
 218          $this->setUser($student);
 219  
 220          set_config('backup_auto_users', true, 'backup');
 221          $this->create_quiz_attempt($this->quiz, $student);
 222  
 223          // Delete quiz.
 224          $cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
 225          course_delete_module($cm->id);
 226          phpunit_util::run_all_adhoc_tasks();
 227          $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
 228          $this->assertEquals(0, count($quizzes));
 229  
 230          // Restore quiz.
 231          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
 232          foreach ($recyclebin->get_items() as $item) {
 233              $recyclebin->restore_item($item);
 234          }
 235          $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
 236          $this->assertEquals(1, count($quizzes));
 237          $cm = array_pop($quizzes);
 238  
 239          // Check if user quiz attempt data is restored.
 240          $attempts = quiz_get_user_attempts($cm->instance, $student->id);
 241          $this->assertEquals(1, count($attempts));
 242          $attempt = array_pop($attempts);
 243          $attemptobj = quiz_attempt::create($attempt->id);
 244          $this->assertEquals($student->id, $attemptobj->get_userid());
 245          $this->assertEquals(true, $attemptobj->is_finished());
 246      }
 247  
 248      /**
 249       * Tests that user data is not restored when module is restored.
 250       *
 251       * @dataProvider recycle_bin_settings_provider
 252       * @param array $settings array of plugin, name, value stdClass().
 253       */
 254      public function test_coursemodule_restore_without_userdata($settings) {
 255          // Force configuration changes from provider.
 256          foreach ($settings as $setting) {
 257              // Need to create a directory for backup_auto_destination.
 258              if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
 259                  $setting->value = make_request_directory();
 260              }
 261              set_config($setting->name, $setting->value, $setting->plugin);
 262          }
 263  
 264          $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
 265          $this->setUser($student);
 266  
 267          set_config('backup_auto_users', false, 'backup');
 268          $this->create_quiz_attempt($this->quiz, $student);
 269  
 270          // Delete quiz.
 271          $cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
 272          course_delete_module($cm->id);
 273          phpunit_util::run_all_adhoc_tasks();
 274          $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
 275          $this->assertEquals(0, count($quizzes));
 276  
 277          // Restore quiz.
 278          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
 279          foreach ($recyclebin->get_items() as $item) {
 280              $recyclebin->restore_item($item);
 281          }
 282          $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
 283          $this->assertEquals(1, count($quizzes));
 284          $cm = array_pop($quizzes);
 285  
 286          // Check if user quiz attempt data is restored.
 287          $attempts = quiz_get_user_attempts($cm->instance, $student->id);
 288          $this->assertEquals(0, count($attempts));
 289      }
 290  
 291      /**
 292       * Add a question to quiz and create a quiz attempt.
 293       * @param \stdClass $quiz Quiz
 294       * @param \stdClass $student User
 295       * @throws coding_exception
 296       * @throws moodle_exception
 297       */
 298      private function create_quiz_attempt($quiz, $student) {
 299          // Add Question.
 300          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
 301          $cat = $questiongenerator->create_question_category();
 302          $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
 303          quiz_add_quiz_question($numq->id, $quiz);
 304  
 305          // Create quiz attempt.
 306          $quizobj = quiz::create($quiz->id, $student->id);
 307          $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
 308          $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
 309          $timenow = time();
 310          $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id);
 311          quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
 312          quiz_attempt_save_started($quizobj, $quba, $attempt);
 313          $attemptobj = quiz_attempt::create($attempt->id);
 314          $tosubmit = array(1 => array('answer' => '0'));
 315          $attemptobj->process_submitted_actions($timenow, false, $tosubmit);
 316          $attemptobj = quiz_attempt::create($attempt->id);
 317          $attemptobj->process_finish($timenow, false);
 318      }
 319  }