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.

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