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 400 and 403] [Versions 401 and 403] [Versions 402 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 qbank_bulkmove;
  18  
  19  use core_question\local\bank\question_edit_contexts;
  20  
  21  defined('MOODLE_INTERNAL') || die();
  22  
  23  global $CFG;
  24  require_once($CFG->dirroot . '/question/editlib.php');
  25  
  26  /**
  27   * Bulk move helper tests.
  28   *
  29   * @package    qbank_bulkmove
  30   * @copyright  2021 Catalyst IT Australia Pty Ltd
  31   * @author     Safat Shahin <safatshahin@catalyst-au.net>
  32   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33   * @coversDefaultClass \qbank_bulkmove\helper
  34   */
  35  class helper_test extends \advanced_testcase {
  36  
  37      /**
  38       * @var false|object|\stdClass|null $cat
  39       */
  40      protected $cat;
  41  
  42      /**
  43       * @var \stdClass $questiondata1
  44       */
  45      protected $questiondata1;
  46  
  47      /**
  48       * @var \stdClass $questiondata2
  49       */
  50      protected $questiondata2;
  51  
  52      /**
  53       * @var bool|\context|\context_course $context
  54       */
  55      protected $context;
  56  
  57      /**
  58       * @var \core_question\local\bank\question_edit_contexts $contexts
  59       */
  60      protected $contexts;
  61  
  62      /**
  63       * @var \stdClass $course
  64       */
  65      protected $course;
  66  
  67      /**
  68       * @var array $rawdata
  69       */
  70      protected $rawdata;
  71  
  72      /**
  73       * @var object $secondcategory
  74       */
  75      protected $secondcategory;
  76  
  77      /**
  78       * Setup the test.
  79       */
  80      protected function helper_setup(): void {
  81          $this->resetAfterTest();
  82          $this->setAdminUser();
  83          $generator = $this->getDataGenerator();
  84          /** @var \core_question_generator $questiongenerator */
  85          $questiongenerator = $generator->get_plugin_generator('core_question');
  86  
  87          // Create a course.
  88          $this->course = $generator->create_course();
  89          $this->context = \context_course::instance($this->course->id);
  90  
  91          // Create a question in the default category.
  92          $this->contexts = new question_edit_contexts($this->context);
  93          $this->cat = question_make_default_categories($this->contexts->all());
  94          $this->questiondata1 = $questiongenerator->create_question('numerical', null,
  95              ['name' => 'Example question', 'category' => $this->cat->id]);
  96  
  97          // Create a second category to move questions.
  98          $this->secondcategory = $questiongenerator->create_question_category(['contextid' => $this->context->id,
  99              'parent' => $this->cat->id]);
 100  
 101          // Ensure the question is not in the cache.
 102          $cache = \cache::make('core', 'questiondata');
 103          $cache->delete($this->questiondata1->id);
 104  
 105          $this->questiondata2 = $questiongenerator->create_question('numerical', null,
 106              ['name' => 'Example question second', 'category' => $this->cat->id]);
 107  
 108          // Ensure the question is not in the cache.
 109          $cache = \cache::make('core', 'questiondata');
 110          $cache->delete($this->questiondata2->id);
 111  
 112          // Posted raw data.
 113          $this->rawdata = [
 114              'courseid' => $this->course->id,
 115              'cat' => "{$this->cat->id},{$this->context->id}",
 116              'qpage' => '0',
 117              "q{$this->questiondata1->id}" => '1',
 118              "q{$this->questiondata2->id}" => '1',
 119              'move' => 'Move to'
 120          ];
 121      }
 122  
 123      /**
 124       * Count how many questions in the list belong to the given category.
 125       *
 126       * @param string $categoryid a category id
 127       * @param array $questionids list of question ids
 128       * @return int
 129       */
 130      private function count_category_questions(string $categoryid, array $questionids): int {
 131          global $DB;
 132          $this->assertNotEmpty($questionids);
 133          list($insql, $inparams) = $DB->get_in_or_equal($questionids, SQL_PARAMS_NAMED);
 134          $sql = "SELECT COUNT(q.id)
 135                    FROM {question} q
 136                    JOIN {question_versions} qv ON qv.questionid = q.id
 137                    JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
 138                    JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
 139                   WHERE qc.id = :categoryid
 140                     AND q.id $insql";
 141  
 142          return $DB->count_records_sql($sql, array_merge(['categoryid' => $categoryid], $inparams));
 143      }
 144  
 145      /**
 146       * Assert that the given category contains following questions
 147       *
 148       * @param string $categoryid a category id
 149       * @param array $questionids list of question ids
 150       * @return void
 151       */
 152      protected function assert_category_contains_questions(string $categoryid, array $questionids) {
 153          // The category need to contain all the questions.
 154          $this->assertEquals(count($questionids), $this->count_category_questions($categoryid, $questionids));
 155      }
 156  
 157      /**
 158       * Assert that the given category does not contain following questions
 159       *
 160       * @param string $categoryid a category id
 161       * @param array $questionids list of question ids
 162       * @return void
 163       */
 164      protected function assert_category_does_not_contain_questions(string $categoryid, array $questionids) {
 165          // The category does not contain any question.
 166          $this->assertEquals(0, $this->count_category_questions($categoryid, $questionids));
 167      }
 168  
 169      /**
 170       * Test bulk move of questions.
 171       *
 172       * @covers ::bulk_move_questions
 173       */
 174      public function test_bulk_move_questions() {
 175          global $DB;
 176          $this->helper_setup();
 177  
 178          // Get the processed question ids.
 179          $questionlist = $this->process_question_ids_test();
 180          $questionids = array_map('intval', explode(',', $questionlist));
 181  
 182          // Verify that the questions are available in the current view.
 183          $this->assert_category_contains_questions($this->cat->id, $questionids);
 184          helper::bulk_move_questions($questionlist, $this->secondcategory);
 185  
 186          // Verify the questions are not in the current category.
 187          $this->assert_category_does_not_contain_questions($this->cat->id, $questionids);
 188  
 189          // Verify the questions are in the new category.
 190          $this->assert_category_contains_questions($this->secondcategory->id, $questionids);
 191      }
 192  
 193      /**
 194       * Test the question processing and return the question list.
 195       *
 196       * @return mixed
 197       * @covers ::process_question_ids
 198       */
 199      protected function process_question_ids_test() {
 200          // Test the raw data processing.
 201          list($questionids, $questionlist) = helper::process_question_ids($this->rawdata);
 202          $this->assertEquals([$this->questiondata1->id, $this->questiondata2->id], $questionids);
 203          $this->assertEquals("{$this->questiondata1->id},{$this->questiondata2->id}", $questionlist);
 204          return $questionlist;
 205      }
 206  
 207      /**
 208       * Test the question displaydata.
 209       *
 210       * @covers ::get_displaydata
 211       */
 212      public function test_get_displaydata() {
 213          $this->helper_setup();
 214          $coursecontext = \context_course::instance($this->course->id);
 215          $contexts = new question_edit_contexts($coursecontext);
 216          $addcontexts = $contexts->having_cap('moodle/question:add');
 217          $url = new \moodle_url('/question/bank/bulkmove/move.php');
 218          $displaydata = \qbank_bulkmove\helper::get_displaydata($addcontexts, $url, $url);
 219          $this->assertStringContainsString('Test question category 1', $displaydata['categorydropdown']);
 220          $this->assertStringContainsString('Default for Category 1', $displaydata['categorydropdown']);
 221          $this->assertEquals($url, $displaydata ['moveurl']);
 222          $this->assertEquals($url, $displaydata ['returnurl']);
 223      }
 224  }