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 401 and 402] [Versions 401 and 403]

   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              'restore/restore_general_users moodle' => [[
 192                  (object)['plugin' => 'restore', 'name' => 'restore_general_users', 'value' => 0],
 193                  (object)['plugin' => 'restore', 'name' => 'restore_general_groups', 'value' => 0],
 194              ]],
 195          ];
 196      }
 197  
 198      /**
 199       * Tests that user data is restored when module is restored.
 200       *
 201       * @dataProvider recycle_bin_settings_provider
 202       * @param array $settings array of plugin, name, value stdClass().
 203       */
 204      public function test_coursemodule_restore_with_userdata($settings) {
 205          // Force configuration changes from provider.
 206          foreach ($settings as $setting) {
 207              // Need to create a directory for backup_auto_destination.
 208              if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
 209                  $setting->value = make_request_directory();
 210              }
 211              set_config($setting->name, $setting->value, $setting->plugin);
 212          }
 213  
 214          $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
 215          $this->setUser($student);
 216  
 217          set_config('backup_auto_users', true, 'backup');
 218          $this->create_quiz_attempt($this->quiz, $student);
 219  
 220          // Delete quiz.
 221          $cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
 222          course_delete_module($cm->id);
 223          \phpunit_util::run_all_adhoc_tasks();
 224          $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
 225          $this->assertEquals(0, count($quizzes));
 226  
 227          // Restore quiz.
 228          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
 229          foreach ($recyclebin->get_items() as $item) {
 230              $recyclebin->restore_item($item);
 231          }
 232          $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
 233          $this->assertEquals(1, count($quizzes));
 234          $cm = array_pop($quizzes);
 235  
 236          // Check if user quiz attempt data is restored.
 237          $attempts = quiz_get_user_attempts($cm->instance, $student->id);
 238          $this->assertEquals(1, count($attempts));
 239          $attempt = array_pop($attempts);
 240          $attemptobj = \quiz_attempt::create($attempt->id);
 241          $this->assertEquals($student->id, $attemptobj->get_userid());
 242          $this->assertEquals(true, $attemptobj->is_finished());
 243      }
 244  
 245      /**
 246       * Test that the activity is NOT stored in bin when
 247       * in Automated backup setup settings "backup_auto_activities" is disabled.
 248       *
 249       * @dataProvider recycle_bin_settings_provider
 250       * @covers ::store_item
 251       */
 252      public function test_coursemodule_restore_with_activity_setting_disabled() {
 253  
 254          // Set the configuration to not include activities in the automated backup.
 255          set_config('backup_auto_activities', false, 'backup');
 256  
 257          // Delete the course module.
 258          course_delete_module($this->quiz->cmid);
 259  
 260          // Now, run the course module deletion adhoc task.
 261          \phpunit_util::run_all_adhoc_tasks();
 262  
 263          // Check there is no items in the recycle bin.
 264          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
 265          $this->assertEquals(0, count($recyclebin->get_items()));
 266      }
 267  
 268      /**
 269       * Tests that user data is not restored when module is restored.
 270       *
 271       * @dataProvider recycle_bin_settings_provider
 272       * @param array $settings array of plugin, name, value stdClass().
 273       */
 274      public function test_coursemodule_restore_without_userdata($settings) {
 275          // Force configuration changes from provider.
 276          foreach ($settings as $setting) {
 277              // Need to create a directory for backup_auto_destination.
 278              if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
 279                  $setting->value = make_request_directory();
 280              }
 281              set_config($setting->name, $setting->value, $setting->plugin);
 282          }
 283  
 284          $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
 285          $this->setUser($student);
 286  
 287          set_config('backup_auto_users', false, 'backup');
 288          $this->create_quiz_attempt($this->quiz, $student);
 289  
 290          // Delete quiz.
 291          $cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
 292          course_delete_module($cm->id);
 293          \phpunit_util::run_all_adhoc_tasks();
 294          $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
 295          $this->assertEquals(0, count($quizzes));
 296  
 297          // Restore quiz.
 298          $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
 299          foreach ($recyclebin->get_items() as $item) {
 300              $recyclebin->restore_item($item);
 301          }
 302          $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
 303          $this->assertEquals(1, count($quizzes));
 304          $cm = array_pop($quizzes);
 305  
 306          // Check if user quiz attempt data is restored.
 307          $attempts = quiz_get_user_attempts($cm->instance, $student->id);
 308          $this->assertEquals(0, count($attempts));
 309      }
 310  
 311      /**
 312       * Add a question to quiz and create a quiz attempt.
 313       * @param \stdClass $quiz Quiz
 314       * @param \stdClass $student User
 315       * @throws coding_exception
 316       * @throws moodle_exception
 317       */
 318      private function create_quiz_attempt($quiz, $student) {
 319          // Add Question.
 320          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
 321          $cat = $questiongenerator->create_question_category();
 322          $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
 323          quiz_add_quiz_question($numq->id, $quiz);
 324  
 325          // Create quiz attempt.
 326          $quizobj = \quiz::create($quiz->id, $student->id);
 327          $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
 328          $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
 329          $timenow = time();
 330          $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id);
 331          quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
 332          quiz_attempt_save_started($quizobj, $quba, $attempt);
 333          $attemptobj = \quiz_attempt::create($attempt->id);
 334          $tosubmit = array(1 => array('answer' => '0'));
 335          $attemptobj->process_submitted_actions($timenow, false, $tosubmit);
 336          $attemptobj = \quiz_attempt::create($attempt->id);
 337          $attemptobj->process_finish($timenow, false);
 338      }
 339  }