Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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