Merge branch 'MDL-64012' of https://github.com/timhunt/moodle
[moodle.git] / lib / tests / questionlib_test.php
blobced468bbfb253da9d734092ded18f6aab942312f
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.
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/>.
17 /**
18 * Unit tests for (some of) ../questionlib.php.
20 * @package core_question
21 * @category phpunit
22 * @copyright 2006 The Open University
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 use core_tag\output\tag;
28 defined('MOODLE_INTERNAL') || die();
30 global $CFG;
32 require_once($CFG->libdir . '/questionlib.php');
33 require_once($CFG->dirroot . '/mod/quiz/locallib.php');
35 // Get the necessary files to perform backup and restore.
36 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
37 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
39 /**
40 * Unit tests for (some of) ../questionlib.php.
42 * @copyright 2006 The Open University
43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45 class core_questionlib_testcase extends advanced_testcase {
47 /**
48 * Test set up.
50 * This is executed before running any test in this file.
52 public function setUp() {
53 $this->resetAfterTest();
56 /**
57 * Return true and false to test functions with feedback on and off.
59 * @return array Test data
61 public function provider_feedback() {
62 return array(
63 'Feedback test' => array(true),
64 'No feedback test' => array(false)
68 /**
69 * Setup a course, a quiz, a question category and a question for testing.
71 * @param string $type The type of question category to create.
72 * @return array The created data objects
74 public function setup_quiz_and_questions($type = 'module') {
75 // Create course category.
76 $category = $this->getDataGenerator()->create_category();
78 // Create course.
79 $course = $this->getDataGenerator()->create_course(array(
80 'numsections' => 5,
81 'category' => $category->id
82 ));
84 $options = array(
85 'course' => $course->id,
86 'duedate' => time(),
89 // Generate an assignment with due date (will generate a course event).
90 $quiz = $this->getDataGenerator()->create_module('quiz', $options);
92 $qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
94 switch ($type) {
95 case 'course':
96 $context = context_course::instance($course->id);
97 break;
99 case 'category':
100 $context = context_coursecat::instance($category->id);
101 break;
103 case 'system':
104 $context = context_system::instance();
105 break;
107 default:
108 $context = context_module::instance($quiz->cmid);
109 break;
112 $qcat = $qgen->create_question_category(array('contextid' => $context->id));
114 $questions = array(
115 $qgen->create_question('shortanswer', null, array('category' => $qcat->id)),
116 $qgen->create_question('shortanswer', null, array('category' => $qcat->id)),
119 quiz_add_quiz_question($questions[0]->id, $quiz);
121 return array($category, $course, $quiz, $qcat, $questions);
124 public function test_question_reorder_qtypes() {
125 $this->assertEquals(
126 array(0 => 't2', 1 => 't1', 2 => 't3'),
127 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't1', +1));
128 $this->assertEquals(
129 array(0 => 't1', 1 => 't2', 2 => 't3'),
130 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't1', -1));
131 $this->assertEquals(
132 array(0 => 't2', 1 => 't1', 2 => 't3'),
133 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't2', -1));
134 $this->assertEquals(
135 array(0 => 't1', 1 => 't2', 2 => 't3'),
136 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't3', +1));
137 $this->assertEquals(
138 array(0 => 't1', 1 => 't2', 2 => 't3'),
139 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 'missing', +1));
142 public function test_match_grade_options() {
143 $gradeoptions = question_bank::fraction_options_full();
145 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.3333333, 'error'));
146 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.333333, 'error'));
147 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.33333, 'error'));
148 $this->assertFalse(match_grade_options($gradeoptions, 0.3333, 'error'));
150 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.3333333, 'nearest'));
151 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.333333, 'nearest'));
152 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.33333, 'nearest'));
153 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.33, 'nearest'));
155 $this->assertEquals(-0.1428571, match_grade_options($gradeoptions, -0.15, 'nearest'));
159 * This function tests that the functions responsible for moving questions to
160 * different contexts also updates the tag instances associated with the questions.
162 public function test_altering_tag_instance_context() {
163 global $CFG, $DB;
165 // Set to admin user.
166 $this->setAdminUser();
168 // Create two course categories - we are going to delete one of these later and will expect
169 // all the questions belonging to the course in the deleted category to be moved.
170 $coursecat1 = $this->getDataGenerator()->create_category();
171 $coursecat2 = $this->getDataGenerator()->create_category();
173 // Create a couple of categories and questions.
174 $context1 = context_coursecat::instance($coursecat1->id);
175 $context2 = context_coursecat::instance($coursecat2->id);
176 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
177 $questioncat1 = $questiongenerator->create_question_category(array('contextid' =>
178 $context1->id));
179 $questioncat2 = $questiongenerator->create_question_category(array('contextid' =>
180 $context2->id));
181 $question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat1->id));
182 $question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat1->id));
183 $question3 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat2->id));
184 $question4 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat2->id));
186 // Now lets tag these questions.
187 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $context1, array('tag 1', 'tag 2'));
188 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $context1, array('tag 3', 'tag 4'));
189 core_tag_tag::set_item_tags('core_question', 'question', $question3->id, $context2, array('tag 5', 'tag 6'));
190 core_tag_tag::set_item_tags('core_question', 'question', $question4->id, $context2, array('tag 7', 'tag 8'));
192 // Test moving the questions to another category.
193 question_move_questions_to_category(array($question1->id, $question2->id), $questioncat2->id);
195 // Test that all tag_instances belong to one context.
196 $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question',
197 'contextid' => $questioncat2->contextid)));
199 // Test moving them back.
200 question_move_questions_to_category(array($question1->id, $question2->id), $questioncat1->id);
202 // Test that all tag_instances are now reset to how they were initially.
203 $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question',
204 'contextid' => $questioncat1->contextid)));
205 $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question',
206 'contextid' => $questioncat2->contextid)));
208 // Now test moving a whole question category to another context.
209 question_move_category_to_context($questioncat1->id, $questioncat1->contextid, $questioncat2->contextid);
211 // Test that all tag_instances belong to one context.
212 $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question',
213 'contextid' => $questioncat2->contextid)));
215 // Now test moving them back.
216 question_move_category_to_context($questioncat1->id, $questioncat2->contextid,
217 context_coursecat::instance($coursecat1->id)->id);
219 // Test that all tag_instances are now reset to how they were initially.
220 $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question',
221 'contextid' => $questioncat1->contextid)));
222 $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question',
223 'contextid' => $questioncat2->contextid)));
225 // Now we want to test deleting the course category and moving the questions to another category.
226 question_delete_course_category($coursecat1, $coursecat2, false);
228 // Test that all tag_instances belong to one context.
229 $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question',
230 'contextid' => $questioncat2->contextid)));
232 // Create a course.
233 $course = $this->getDataGenerator()->create_course();
235 // Create some question categories and questions in this course.
236 $coursecontext = context_course::instance($course->id);
237 $questioncat = $questiongenerator->create_question_category(array('contextid' =>
238 $coursecontext->id));
239 $question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id));
240 $question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id));
242 // Add some tags to these questions.
243 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, array('tag 1', 'tag 2'));
244 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, array('tag 1', 'tag 2'));
246 // Create a course that we are going to restore the other course to.
247 $course2 = $this->getDataGenerator()->create_course();
249 // Create backup file and save it to the backup location.
250 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
251 backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2);
252 $bc->execute_plan();
253 $results = $bc->get_results();
254 $file = $results['backup_destination'];
255 $fp = get_file_packer('application/vnd.moodle.backup');
256 $filepath = $CFG->dataroot . '/temp/backup/test-restore-course';
257 $file->extract_to_pathname($fp, $filepath);
258 $bc->destroy();
260 // Now restore the course.
261 $rc = new restore_controller('test-restore-course', $course2->id, backup::INTERACTIVE_NO,
262 backup::MODE_GENERAL, 2, backup::TARGET_NEW_COURSE);
263 $rc->execute_precheck();
264 $rc->execute_plan();
266 // Get the created question category.
267 $restoredcategory = $DB->get_record_select('question_categories', 'contextid = ? AND parent <> 0',
268 array(context_course::instance($course2->id)->id), '*', MUST_EXIST);
270 // Check that there are two questions in the restored to course's context.
271 $this->assertEquals(2, $DB->count_records('question', array('category' => $restoredcategory->id)));
273 $rc->destroy();
277 * This function tests the question_category_delete_safe function.
279 public function test_question_category_delete_safe() {
280 global $DB;
281 $this->resetAfterTest(true);
282 $this->setAdminUser();
284 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions();
286 question_category_delete_safe($qcat);
288 // Verify category deleted.
289 $criteria = array('id' => $qcat->id);
290 $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
292 // Verify questions deleted or moved.
293 $criteria = array('category' => $qcat->id);
294 $this->assertEquals(0, $DB->count_records('question', $criteria));
296 // Verify question not deleted.
297 $criteria = array('id' => $questions[0]->id);
298 $this->assertEquals(1, $DB->count_records('question', $criteria));
302 * This function tests the question_delete_activity function.
304 * @param bool $feedback Whether to return feedback
305 * @dataProvider provider_feedback
307 public function test_question_delete_activity($feedback) {
308 global $DB;
309 $this->resetAfterTest(true);
310 $this->setAdminUser();
312 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions();
314 $cm = get_coursemodule_from_instance('quiz', $quiz->id);
315 // Test that the feedback works.
316 if ($feedback) {
317 $this->expectOutputRegex('|'.get_string('unusedcategorydeleted', 'question').'|');
319 question_delete_activity($cm, $feedback);
321 // Verify category deleted.
322 $criteria = array('id' => $qcat->id);
323 $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
325 // Verify questions deleted or moved.
326 $criteria = array('category' => $qcat->id);
327 $this->assertEquals(0, $DB->count_records('question', $criteria));
331 * This function tests the question_delete_context function.
333 public function test_question_delete_context() {
334 global $DB;
335 $this->resetAfterTest(true);
336 $this->setAdminUser();
338 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions();
340 // Get the module context id.
341 $result = question_delete_context($qcat->contextid);
343 // Verify category deleted.
344 $criteria = array('id' => $qcat->id);
345 $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
347 // Verify questions deleted or moved.
348 $criteria = array('category' => $qcat->id);
349 $this->assertEquals(0, $DB->count_records('question', $criteria));
351 // Test that the feedback works.
352 $expected[] = array('top', get_string('unusedcategorydeleted', 'question'));
353 $expected[] = array($qcat->name, get_string('unusedcategorydeleted', 'question'));
354 $this->assertEquals($expected, $result);
358 * This function tests the question_delete_course function.
360 * @param bool $feedback Whether to return feedback
361 * @dataProvider provider_feedback
363 public function test_question_delete_course($feedback) {
364 global $DB;
365 $this->resetAfterTest(true);
366 $this->setAdminUser();
368 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('course');
370 // Test that the feedback works.
371 if ($feedback) {
372 $this->expectOutputRegex('|'.get_string('unusedcategorydeleted', 'question').'|');
374 question_delete_course($course, $feedback);
376 // Verify category deleted.
377 $criteria = array('id' => $qcat->id);
378 $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
380 // Verify questions deleted or moved.
381 $criteria = array('category' => $qcat->id);
382 $this->assertEquals(0, $DB->count_records('question', $criteria));
386 * This function tests the question_delete_course_category function.
388 * @param bool $feedback Whether to return feedback
389 * @dataProvider provider_feedback
391 public function test_question_delete_course_category($feedback) {
392 global $DB;
393 $this->resetAfterTest(true);
394 $this->setAdminUser();
396 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
398 // Test that the feedback works.
399 if ($feedback) {
400 $this->expectOutputRegex('|'.get_string('unusedcategorydeleted', 'question').'|');
402 question_delete_course_category($category, 0, $feedback);
404 // Verify category deleted.
405 $criteria = array('id' => $qcat->id);
406 $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
408 // Verify questions deleted or moved.
409 $criteria = array('category' => $qcat->id);
410 $this->assertEquals(0, $DB->count_records('question', $criteria));
414 * This function tests the question_delete_course_category function when it is supposed to move question categories.
416 * @param bool $feedback Whether to return feedback
417 * @dataProvider provider_feedback
419 public function test_question_delete_course_category_move_qcats($feedback) {
420 global $DB;
421 $this->resetAfterTest(true);
422 $this->setAdminUser();
424 list($category1, $course1, $quiz1, $qcat1, $questions1) = $this->setup_quiz_and_questions('category');
425 list($category2, $course2, $quiz2, $qcat2, $questions2) = $this->setup_quiz_and_questions('category');
427 $questionsinqcat1 = count($questions1);
428 $questionsinqcat2 = count($questions2);
430 // Test that the feedback works.
431 if ($feedback) {
432 $a = new stdClass();
433 $a->oldplace = context::instance_by_id($qcat1->contextid)->get_context_name();
434 $a->newplace = context::instance_by_id($qcat2->contextid)->get_context_name();
435 $this->expectOutputRegex('|'.get_string('movedquestionsandcategories', 'question', $a).'|');
437 question_delete_course_category($category1, $category2, $feedback);
439 // Verify category not deleted.
440 $criteria = array('id' => $qcat1->id);
441 $this->assertEquals(1, $DB->count_records('question_categories', $criteria));
443 // Verify questions are moved.
444 $criteria = array('category' => $qcat1->id);
445 $params = array($qcat2->contextid);
446 $actualquestionscount = $DB->count_records_sql("SELECT COUNT(*)
447 FROM {question} q
448 JOIN {question_categories} qc ON q.category = qc.id
449 WHERE qc.contextid = ?", $params, $criteria);
450 $this->assertEquals($questionsinqcat1 + $questionsinqcat2, $actualquestionscount);
452 // Verify there is just a single top-level category.
453 $criteria = array('contextid' => $qcat2->contextid, 'parent' => 0);
454 $this->assertEquals(1, $DB->count_records('question_categories', $criteria));
456 // Verify there is no question category in previous context.
457 $criteria = array('contextid' => $qcat1->contextid);
458 $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
462 * This function tests the question_save_from_deletion function when it is supposed to make a new category and
463 * move question categories to that new category.
465 public function test_question_save_from_deletion() {
466 global $DB;
467 $this->resetAfterTest(true);
468 $this->setAdminUser();
470 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions();
472 $context = context::instance_by_id($qcat->contextid);
474 $newcat = question_save_from_deletion(array_column($questions, 'id'),
475 $context->get_parent_context()->id, $context->get_context_name());
477 // Verify that the newcat itself is not a tep level category.
478 $this->assertNotEquals(0, $newcat->parent);
480 // Verify there is just a single top-level category.
481 $this->assertEquals(1, $DB->count_records('question_categories', ['contextid' => $qcat->contextid, 'parent' => 0]));
484 public function test_question_remove_stale_questions_from_category() {
485 global $DB;
486 $this->resetAfterTest(true);
487 $this->setAdminUser();
489 $dg = $this->getDataGenerator();
490 $course = $dg->create_course();
491 $quiz = $dg->create_module('quiz', ['course' => $course->id]);
493 $qgen = $dg->get_plugin_generator('core_question');
494 $context = context_system::instance();
496 $qcat1 = $qgen->create_question_category(['contextid' => $context->id]);
497 $q1a = $qgen->create_question('shortanswer', null, ['category' => $qcat1->id]); // Will be hidden.
498 $DB->set_field('question', 'hidden', 1, ['id' => $q1a->id]);
500 $qcat2 = $qgen->create_question_category(['contextid' => $context->id]);
501 $q2a = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden.
502 $q2b = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden but used.
503 $DB->set_field('question', 'hidden', 1, ['id' => $q2a->id]);
504 $DB->set_field('question', 'hidden', 1, ['id' => $q2b->id]);
505 quiz_add_quiz_question($q2b->id, $quiz);
506 quiz_add_random_questions($quiz, 0, $qcat2->id, 1, false);
508 // We added one random question to the quiz and we expect the quiz to have only one random question.
509 $q2d = $DB->get_record_sql("SELECT q.*
510 FROM {question} q
511 JOIN {quiz_slots} s ON s.questionid = q.id
512 WHERE q.qtype = :qtype
513 AND s.quizid = :quizid",
514 array('qtype' => 'random', 'quizid' => $quiz->id), MUST_EXIST);
516 // The following 2 lines have to be after the quiz_add_random_questions() call above.
517 // Otherwise, quiz_add_random_questions() will to be "smart" and use them instead of creating a new "random" question.
518 $q1b = $qgen->create_question('random', null, ['category' => $qcat1->id]); // Will not be used.
519 $q2c = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will not be used.
521 $this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
522 $this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
524 // Non-existing category, nothing will happen.
525 question_remove_stale_questions_from_category(0);
526 $this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
527 $this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
529 // First category, should be empty afterwards.
530 question_remove_stale_questions_from_category($qcat1->id);
531 $this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id]));
532 $this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
533 $this->assertFalse($DB->record_exists('question', ['id' => $q1a->id]));
534 $this->assertFalse($DB->record_exists('question', ['id' => $q1b->id]));
536 // Second category, used questions should be left untouched.
537 question_remove_stale_questions_from_category($qcat2->id);
538 $this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id]));
539 $this->assertEquals(2, $DB->count_records('question', ['category' => $qcat2->id]));
540 $this->assertFalse($DB->record_exists('question', ['id' => $q2a->id]));
541 $this->assertTrue($DB->record_exists('question', ['id' => $q2b->id]));
542 $this->assertFalse($DB->record_exists('question', ['id' => $q2c->id]));
543 $this->assertTrue($DB->record_exists('question', ['id' => $q2d->id]));
547 * get_question_options should add the category object to the given question.
549 public function test_get_question_options_includes_category_object_single_question() {
550 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
551 $question = array_shift($questions);
553 get_question_options($question);
555 $this->assertEquals($qcat, $question->categoryobject);
559 * get_question_options should add the category object to all of the questions in
560 * the given list.
562 public function test_get_question_options_includes_category_object_multiple_questions() {
563 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
565 get_question_options($questions);
567 foreach ($questions as $question) {
568 $this->assertEquals($qcat, $question->categoryobject);
573 * get_question_options includes the tags for all questions in the list.
575 public function test_get_question_options_includes_question_tags() {
576 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
577 $question1 = $questions[0];
578 $question2 = $questions[1];
579 $qcontext = context::instance_by_id($qcat->contextid);
581 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo', 'bar']);
582 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['baz', 'bop']);
584 get_question_options($questions, true);
586 foreach ($questions as $question) {
587 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
588 $expectedtags = [];
589 $actualtags = $question->tags;
590 foreach ($tags as $tag) {
591 $expectedtags[$tag->id] = $tag->get_display_name();
594 // The question should have a tags property populated with each tag id
595 // and display name as a key vale pair.
596 $this->assertEquals($expectedtags, $actualtags);
598 $actualtagobjects = $question->tagobjects;
599 sort($tags);
600 sort($actualtagobjects);
602 // The question should have a full set of each tag object.
603 $this->assertEquals($tags, $actualtagobjects);
604 // The question should not have any course tags.
605 $this->assertEmpty($question->coursetagobjects);
610 * get_question_options includes the course tags for all questions in the list.
612 public function test_get_question_options_includes_course_tags() {
613 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
614 $question1 = $questions[0];
615 $question2 = $questions[1];
616 $coursecontext = context_course::instance($course->id);
618 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['foo', 'bar']);
619 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['baz', 'bop']);
621 get_question_options($questions, true);
623 foreach ($questions as $question) {
624 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
625 $expectedcoursetags = [];
626 $actualcoursetags = $question->coursetags;
627 foreach ($tags as $tag) {
628 $expectedcoursetags[$tag->id] = $tag->get_display_name();
631 // The question should have a coursetags property populated with each tag id
632 // and display name as a key vale pair.
633 $this->assertEquals($expectedcoursetags, $actualcoursetags);
635 $actualcoursetagobjects = $question->coursetagobjects;
636 sort($tags);
637 sort($actualcoursetagobjects);
639 // The question should have a full set of the course tag objects.
640 $this->assertEquals($tags, $actualcoursetagobjects);
641 // The question should not have any other tags.
642 $this->assertEmpty($question->tagobjects);
643 $this->assertEmpty($question->tags);
648 * get_question_options only categorises a tag as a course tag if it is in a
649 * course context that is different from the question context.
651 public function test_get_question_options_course_tags_in_course_question_context() {
652 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('course');
653 $question1 = $questions[0];
654 $question2 = $questions[1];
655 $coursecontext = context_course::instance($course->id);
657 // Create course level tags in the course context that matches the question
658 // course context.
659 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['foo', 'bar']);
660 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['baz', 'bop']);
662 get_question_options($questions, true);
664 foreach ($questions as $question) {
665 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
667 $actualtagobjects = $question->tagobjects;
668 sort($tags);
669 sort($actualtagobjects);
671 // The tags should not be considered course tags because they are in
672 // the same context as the question. That makes them question tags.
673 $this->assertEmpty($question->coursetagobjects);
674 // The course context tags should be returned in the regular tag object
675 // list.
676 $this->assertEquals($tags, $actualtagobjects);
681 * get_question_options includes the tags and course tags for all questions in the list
682 * if each question has course and question level tags.
684 public function test_get_question_options_includes_question_and_course_tags() {
685 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
686 $question1 = $questions[0];
687 $question2 = $questions[1];
688 $qcontext = context::instance_by_id($qcat->contextid);
689 $coursecontext = context_course::instance($course->id);
691 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo', 'bar']);
692 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['cfoo', 'cbar']);
693 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['baz', 'bop']);
694 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['cbaz', 'cbop']);
696 get_question_options($questions, true);
698 foreach ($questions as $question) {
699 $alltags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
700 $tags = array_filter($alltags, function($tag) use ($qcontext) {
701 return $tag->taginstancecontextid == $qcontext->id;
703 $coursetags = array_filter($alltags, function($tag) use ($coursecontext) {
704 return $tag->taginstancecontextid == $coursecontext->id;
707 $expectedtags = [];
708 $actualtags = $question->tags;
709 foreach ($tags as $tag) {
710 $expectedtags[$tag->id] = $tag->get_display_name();
713 // The question should have a tags property populated with each tag id
714 // and display name as a key vale pair.
715 $this->assertEquals($expectedtags, $actualtags);
717 $actualtagobjects = $question->tagobjects;
718 sort($tags);
719 sort($actualtagobjects);
720 // The question should have a full set of each tag object.
721 $this->assertEquals($tags, $actualtagobjects);
723 $actualcoursetagobjects = $question->coursetagobjects;
724 sort($coursetags);
725 sort($actualcoursetagobjects);
726 // The question should have a full set of course tag objects.
727 $this->assertEquals($coursetags, $actualcoursetagobjects);
732 * get_question_options should update the context id to the question category
733 * context id for any non-course context tag that isn't in the question category
734 * context.
736 public function test_get_question_options_normalises_question_tags() {
737 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
738 $question1 = $questions[0];
739 $question2 = $questions[1];
740 $qcontext = context::instance_by_id($qcat->contextid);
741 $systemcontext = context_system::instance();
743 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo', 'bar']);
744 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['baz', 'bop']);
746 $q1tags = core_tag_tag::get_item_tags('core_question', 'question', $question1->id);
747 $q2tags = core_tag_tag::get_item_tags('core_question', 'question', $question2->id);
748 $q1tag = array_shift($q1tags);
749 $q2tag = array_shift($q2tags);
751 // Change two of the tag instances to be a different (non-course) context to the
752 // question tag context. These tags should then be normalised back to the question
753 // tag context.
754 core_tag_tag::change_instances_context([$q1tag->taginstanceid, $q2tag->taginstanceid], $systemcontext);
756 get_question_options($questions, true);
758 foreach ($questions as $question) {
759 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
761 // The database should have been updated with the correct context id.
762 foreach ($tags as $tag) {
763 $this->assertEquals($qcontext->id, $tag->taginstancecontextid);
766 // The tag objects on the question should have been updated with the
767 // correct context id.
768 foreach ($question->tagobjects as $tag) {
769 $this->assertEquals($qcontext->id, $tag->taginstancecontextid);
775 * get_question_options if the question is a course level question then tags
776 * in that context should not be consdered course tags, they are question tags.
778 public function test_get_question_options_includes_course_context_question_tags() {
779 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('course');
780 $question1 = $questions[0];
781 $question2 = $questions[1];
782 $coursecontext = context_course::instance($course->id);
784 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['foo', 'bar']);
785 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['baz', 'bop']);
787 get_question_options($questions, true);
789 foreach ($questions as $question) {
790 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
791 // Tags in a course context that matches the question context should
792 // not be considered course tags.
793 $this->assertEmpty($question->coursetagobjects);
794 $this->assertEmpty($question->coursetags);
796 $actualtagobjects = $question->tagobjects;
797 sort($tags);
798 sort($actualtagobjects);
799 // The tags should be considered question tags not course tags.
800 $this->assertEquals($tags, $actualtagobjects);
805 * get_question_options should return tags from all course contexts by default.
807 public function test_get_question_options_includes_multiple_courses_tags() {
808 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
809 $question1 = $questions[0];
810 $question2 = $questions[1];
811 $coursecontext = context_course::instance($course->id);
812 // Create a sibling course.
813 $siblingcourse = $this->getDataGenerator()->create_course(['category' => $course->category]);
814 $siblingcoursecontext = context_course::instance($siblingcourse->id);
816 // Create course tags.
817 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['c1']);
818 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['c1']);
819 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $siblingcoursecontext, ['c2']);
820 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $siblingcoursecontext, ['c2']);
822 get_question_options($questions, true);
824 foreach ($questions as $question) {
825 $this->assertCount(2, $question->coursetagobjects);
827 foreach ($question->coursetagobjects as $tag) {
828 if ($tag->name == 'c1') {
829 $this->assertEquals($coursecontext->id, $tag->taginstancecontextid);
830 } else {
831 $this->assertEquals($siblingcoursecontext->id, $tag->taginstancecontextid);
838 * get_question_options should filter the course tags by the given list of courses.
840 public function test_get_question_options_includes_filter_course_tags() {
841 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
842 $question1 = $questions[0];
843 $question2 = $questions[1];
844 $coursecontext = context_course::instance($course->id);
845 // Create a sibling course.
846 $siblingcourse = $this->getDataGenerator()->create_course(['category' => $course->category]);
847 $siblingcoursecontext = context_course::instance($siblingcourse->id);
849 // Create course tags.
850 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['foo']);
851 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['bar']);
852 // Create sibling course tags. These should be filtered out.
853 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $siblingcoursecontext, ['filtered1']);
854 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $siblingcoursecontext, ['filtered2']);
856 // Ask to only receive course tags from $course (ignoring $siblingcourse tags).
857 get_question_options($questions, true, [$course]);
859 foreach ($questions as $question) {
860 foreach ($question->coursetagobjects as $tag) {
861 // We should only be seeing course tags from $course. The tags from
862 // $siblingcourse should have been filtered out.
863 $this->assertEquals($coursecontext->id, $tag->taginstancecontextid);
869 * question_move_question_tags_to_new_context should update all of the
870 * question tags contexts when they are moving down (from system to course
871 * category context).
873 public function test_question_move_question_tags_to_new_context_system_to_course_cat_qtags() {
874 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('system');
875 $question1 = $questions[0];
876 $question2 = $questions[1];
877 $qcontext = context::instance_by_id($qcat->contextid);
878 $newcontext = context_coursecat::instance($category->id);
880 foreach ($questions as $question) {
881 $question->contextid = $qcat->contextid;
884 // Create tags in the system context.
885 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo', 'bar']);
886 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo', 'bar']);
888 question_move_question_tags_to_new_context($questions, $newcontext);
890 foreach ($questions as $question) {
891 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
893 // All of the tags should have their context id set to the new context.
894 foreach ($tags as $tag) {
895 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
901 * question_move_question_tags_to_new_context should update all of the question tags
902 * contexts when they are moving down (from system to course category context)
903 * but leave any tags in the course context where they are.
905 public function test_question_move_question_tags_to_new_context_system_to_course_cat_qtags_and_course_tags() {
906 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('system');
907 $question1 = $questions[0];
908 $question2 = $questions[1];
909 $qcontext = context::instance_by_id($qcat->contextid);
910 $coursecontext = context_course::instance($course->id);
911 $newcontext = context_coursecat::instance($category->id);
913 foreach ($questions as $question) {
914 $question->contextid = $qcat->contextid;
917 // Create tags in the system context.
918 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
919 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
920 // Create tags in the course context.
921 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['ctag']);
922 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['ctag']);
924 question_move_question_tags_to_new_context($questions, $newcontext);
926 foreach ($questions as $question) {
927 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
929 foreach ($tags as $tag) {
930 if ($tag->name == 'ctag') {
931 // Course tags should remain in the course context.
932 $this->assertEquals($coursecontext->id, $tag->taginstancecontextid);
933 } else {
934 // Other tags should be updated.
935 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
942 * question_move_question_tags_to_new_context should update all of the question
943 * contexts tags when they are moving up (from course category to system context).
945 public function test_question_move_question_tags_to_new_context_course_cat_to_system_qtags() {
946 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
947 $question1 = $questions[0];
948 $question2 = $questions[1];
949 $qcontext = context::instance_by_id($qcat->contextid);
950 $newcontext = context_system::instance();
952 foreach ($questions as $question) {
953 $question->contextid = $qcat->contextid;
956 // Create tags in the course category context.
957 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo', 'bar']);
958 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo', 'bar']);
960 question_move_question_tags_to_new_context($questions, $newcontext);
962 foreach ($questions as $question) {
963 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
965 // All of the tags should have their context id set to the new context.
966 foreach ($tags as $tag) {
967 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
973 * question_move_question_tags_to_new_context should update all of the question
974 * tags contexts when they are moving up (from course category context to system
975 * context) but leave any tags in the course context where they are.
977 public function test_question_move_question_tags_to_new_context_course_cat_to_system_qtags_and_course_tags() {
978 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
979 $question1 = $questions[0];
980 $question2 = $questions[1];
981 $qcontext = context::instance_by_id($qcat->contextid);
982 $coursecontext = context_course::instance($course->id);
983 $newcontext = context_system::instance();
985 foreach ($questions as $question) {
986 $question->contextid = $qcat->contextid;
989 // Create tags in the system context.
990 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
991 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
992 // Create tags in the course context.
993 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['ctag']);
994 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['ctag']);
996 question_move_question_tags_to_new_context($questions, $newcontext);
998 foreach ($questions as $question) {
999 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1001 foreach ($tags as $tag) {
1002 if ($tag->name == 'ctag') {
1003 // Course tags should remain in the course context.
1004 $this->assertEquals($coursecontext->id, $tag->taginstancecontextid);
1005 } else {
1006 // Other tags should be updated.
1007 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1014 * question_move_question_tags_to_new_context should merge all tags into the course
1015 * context when moving down from course category context into course context.
1017 public function test_question_move_question_tags_to_new_context_course_cat_to_coures_qtags_and_course_tags() {
1018 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
1019 $question1 = $questions[0];
1020 $question2 = $questions[1];
1021 $qcontext = context::instance_by_id($qcat->contextid);
1022 $coursecontext = context_course::instance($course->id);
1023 $newcontext = $coursecontext;
1025 foreach ($questions as $question) {
1026 $question->contextid = $qcat->contextid;
1029 // Create tags in the system context.
1030 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1031 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1032 // Create tags in the course context.
1033 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['ctag']);
1034 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['ctag']);
1036 question_move_question_tags_to_new_context($questions, $newcontext);
1038 foreach ($questions as $question) {
1039 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1040 // Each question should have 2 tags.
1041 $this->assertCount(2, $tags);
1043 foreach ($tags as $tag) {
1044 // All tags should be updated to the course context and merged in.
1045 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1051 * question_move_question_tags_to_new_context should delete all of the tag
1052 * instances from sibling courses when moving the context of a question down
1053 * from a course category into a course context because the other courses will
1054 * no longer have access to the question.
1056 public function test_question_move_question_tags_to_new_context_remove_other_course_tags() {
1057 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
1058 // Create a sibling course.
1059 $siblingcourse = $this->getDataGenerator()->create_course(['category' => $course->category]);
1060 $question1 = $questions[0];
1061 $question2 = $questions[1];
1062 $qcontext = context::instance_by_id($qcat->contextid);
1063 $coursecontext = context_course::instance($course->id);
1064 $siblingcoursecontext = context_course::instance($siblingcourse->id);
1065 $newcontext = $coursecontext;
1067 foreach ($questions as $question) {
1068 $question->contextid = $qcat->contextid;
1071 // Create tags in the system context.
1072 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1073 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1074 // Create tags in the target course context.
1075 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['ctag']);
1076 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['ctag']);
1077 // Create tags in the sibling course context. These should be deleted as
1078 // part of the move.
1079 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $siblingcoursecontext, ['stag']);
1080 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $siblingcoursecontext, ['stag']);
1082 question_move_question_tags_to_new_context($questions, $newcontext);
1084 foreach ($questions as $question) {
1085 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1086 // Each question should have 2 tags, 'foo' and 'ctag'.
1087 $this->assertCount(2, $tags);
1089 foreach ($tags as $tag) {
1090 $tagname = $tag->name;
1091 // The 'stag' should have been deleted because it's in a sibling
1092 // course context.
1093 $this->assertContains($tagname, ['foo', 'ctag']);
1094 // All tags should be in the course context now.
1095 $this->assertEquals($coursecontext->id, $tag->taginstancecontextid);
1101 * question_move_question_tags_to_new_context should update all of the question
1102 * tags to be the course category context when moving the tags from a course
1103 * context to a course category context.
1105 public function test_question_move_question_tags_to_new_context_course_to_course_cat() {
1106 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('course');
1107 $question1 = $questions[0];
1108 $question2 = $questions[1];
1109 $qcontext = context::instance_by_id($qcat->contextid);
1110 // Moving up into the course category context.
1111 $newcontext = context_coursecat::instance($category->id);
1113 foreach ($questions as $question) {
1114 $question->contextid = $qcat->contextid;
1117 // Create tags in the course context.
1118 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1119 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1121 question_move_question_tags_to_new_context($questions, $newcontext);
1123 foreach ($questions as $question) {
1124 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1126 // All of the tags should have their context id set to the new context.
1127 foreach ($tags as $tag) {
1128 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1134 * question_move_question_tags_to_new_context should update all of the
1135 * question tags contexts when they are moving down (from system to course
1136 * category context).
1138 public function test_question_move_question_tags_to_new_context_orphaned_tag_contexts() {
1139 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('system');
1140 $question1 = $questions[0];
1141 $question2 = $questions[1];
1142 $othercategory = $this->getDataGenerator()->create_category();
1143 $qcontext = context::instance_by_id($qcat->contextid);
1144 $newcontext = context_coursecat::instance($category->id);
1145 $othercategorycontext = context_coursecat::instance($othercategory->id);
1147 foreach ($questions as $question) {
1148 $question->contextid = $qcat->contextid;
1151 // Create tags in the system context.
1152 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1153 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1154 // Create tags in the other course category context. These should be
1155 // update to the next context id because they represent erroneous data
1156 // from a time before context id was mandatory in the tag API.
1157 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $othercategorycontext, ['bar']);
1158 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $othercategorycontext, ['bar']);
1160 question_move_question_tags_to_new_context($questions, $newcontext);
1162 foreach ($questions as $question) {
1163 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1164 // Each question should have two tags, 'foo' and 'bar'.
1165 $this->assertCount(2, $tags);
1167 // All of the tags should have their context id set to the new context
1168 // (course category context).
1169 foreach ($tags as $tag) {
1170 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1176 * When moving from a course category context down into an activity context
1177 * all question context tags and course tags (where the course is a parent of
1178 * the activity) should move into the new context.
1180 public function test_question_move_question_tags_to_new_context_course_cat_to_activity_qtags_and_course_tags() {
1181 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
1182 $question1 = $questions[0];
1183 $question2 = $questions[1];
1184 $qcontext = context::instance_by_id($qcat->contextid);
1185 $coursecontext = context_course::instance($course->id);
1186 $newcontext = context_module::instance($quiz->cmid);
1188 foreach ($questions as $question) {
1189 $question->contextid = $qcat->contextid;
1192 // Create tags in the course category context.
1193 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1194 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1195 // Move the questions to the activity context which is a child context of
1196 // $coursecontext.
1197 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['ctag']);
1198 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['ctag']);
1200 question_move_question_tags_to_new_context($questions, $newcontext);
1202 foreach ($questions as $question) {
1203 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1204 // Each question should have 2 tags.
1205 $this->assertCount(2, $tags);
1207 foreach ($tags as $tag) {
1208 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1214 * When moving from a course category context down into an activity context
1215 * all question context tags and course tags (where the course is a parent of
1216 * the activity) should move into the new context. Tags in course contexts
1217 * that are not a parent of the activity context should be deleted.
1219 public function test_question_move_question_tags_to_new_context_course_cat_to_activity_orphaned_tags() {
1220 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
1221 $question1 = $questions[0];
1222 $question2 = $questions[1];
1223 $qcontext = context::instance_by_id($qcat->contextid);
1224 $coursecontext = context_course::instance($course->id);
1225 $newcontext = context_module::instance($quiz->cmid);
1226 $othercourse = $this->getDataGenerator()->create_course();
1227 $othercoursecontext = context_course::instance($othercourse->id);
1229 foreach ($questions as $question) {
1230 $question->contextid = $qcat->contextid;
1233 // Create tags in the course category context.
1234 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1235 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1236 // Create tags in the course context.
1237 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['ctag']);
1238 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['ctag']);
1239 // Create tags in the other course context. These should be deleted.
1240 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $othercoursecontext, ['delete']);
1241 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $othercoursecontext, ['delete']);
1243 // Move the questions to the activity context which is a child context of
1244 // $coursecontext.
1245 question_move_question_tags_to_new_context($questions, $newcontext);
1247 foreach ($questions as $question) {
1248 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1249 // Each question should have 2 tags.
1250 $this->assertCount(2, $tags);
1252 foreach ($tags as $tag) {
1253 // Make sure we don't have any 'delete' tags.
1254 $this->assertContains($tag->name, ['foo', 'ctag']);
1255 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1261 * When moving from a course context down into an activity context all of the
1262 * course tags should move into the activity context.
1264 public function test_question_move_question_tags_to_new_context_course_to_activity_qtags() {
1265 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('course');
1266 $question1 = $questions[0];
1267 $question2 = $questions[1];
1268 $qcontext = context::instance_by_id($qcat->contextid);
1269 $newcontext = context_module::instance($quiz->cmid);
1271 foreach ($questions as $question) {
1272 $question->contextid = $qcat->contextid;
1275 // Create tags in the course context.
1276 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1277 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1279 question_move_question_tags_to_new_context($questions, $newcontext);
1281 foreach ($questions as $question) {
1282 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1284 foreach ($tags as $tag) {
1285 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1291 * When moving from a course context down into an activity context all of the
1292 * course tags should move into the activity context.
1294 public function test_question_move_question_tags_to_new_context_activity_to_course_qtags() {
1295 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions();
1296 $question1 = $questions[0];
1297 $question2 = $questions[1];
1298 $qcontext = context::instance_by_id($qcat->contextid);
1299 $newcontext = context_course::instance($course->id);
1301 foreach ($questions as $question) {
1302 $question->contextid = $qcat->contextid;
1305 // Create tags in the activity context.
1306 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1307 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1309 question_move_question_tags_to_new_context($questions, $newcontext);
1311 foreach ($questions as $question) {
1312 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1314 foreach ($tags as $tag) {
1315 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1321 * question_move_question_tags_to_new_context should update all of the
1322 * question tags contexts when they are moving down (from system to course
1323 * category context).
1325 * Course tags within the new category context should remain while any course
1326 * tags in course contexts that can no longer access the question should be
1327 * deleted.
1329 public function test_question_move_question_tags_to_new_context_system_to_course_cat_with_orphaned_tags() {
1330 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('system');
1331 $question1 = $questions[0];
1332 $question2 = $questions[1];
1333 $othercategory = $this->getDataGenerator()->create_category();
1334 $othercourse = $this->getDataGenerator()->create_course(['category' => $othercategory->id]);
1335 $qcontext = context::instance_by_id($qcat->contextid);
1336 $newcontext = context_coursecat::instance($category->id);
1337 $othercategorycontext = context_coursecat::instance($othercategory->id);
1338 $coursecontext = context_course::instance($course->id);
1339 $othercoursecontext = context_course::instance($othercourse->id);
1341 foreach ($questions as $question) {
1342 $question->contextid = $qcat->contextid;
1345 // Create tags in the system context.
1346 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo']);
1347 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['foo']);
1348 // Create tags in the child course context of the new context.
1349 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['bar']);
1350 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['bar']);
1351 // Create tags in the other course context. These should be deleted when
1352 // the question moves to the new course category context because this
1353 // course belongs to a different category, which means it will no longer
1354 // have access to the question.
1355 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $othercoursecontext, ['delete']);
1356 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $othercoursecontext, ['delete']);
1358 question_move_question_tags_to_new_context($questions, $newcontext);
1360 foreach ($questions as $question) {
1361 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1362 // Each question should have two tags, 'foo' and 'bar'.
1363 $this->assertCount(2, $tags);
1365 // All of the tags should have their context id set to the new context
1366 // (course category context).
1367 foreach ($tags as $tag) {
1368 $this->assertContains($tag->name, ['foo', 'bar']);
1370 if ($tag->name == 'foo') {
1371 $this->assertEquals($newcontext->id, $tag->taginstancecontextid);
1372 } else {
1373 $this->assertEquals($coursecontext->id, $tag->taginstancecontextid);
1380 * question_sort_tags() includes the tags for all questions in the list.
1382 public function test_question_sort_tags_includes_question_tags() {
1384 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
1385 $question1 = $questions[0];
1386 $question2 = $questions[1];
1387 $qcontext = context::instance_by_id($qcat->contextid);
1389 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['foo', 'bar']);
1390 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['baz', 'bop']);
1392 foreach ($questions as $question) {
1393 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1394 $categorycontext = context::instance_by_id($qcat->contextid);
1395 $tagobjects = question_sort_tags($tags, $categorycontext);
1396 $expectedtags = [];
1397 $actualtags = $tagobjects->tags;
1398 foreach ($tagobjects->tagobjects as $tag) {
1399 $expectedtags[$tag->id] = $tag->name;
1402 // The question should have a tags property populated with each tag id
1403 // and display name as a key vale pair.
1404 $this->assertEquals($expectedtags, $actualtags);
1406 $actualtagobjects = $tagobjects->tagobjects;
1407 sort($tags);
1408 sort($actualtagobjects);
1410 // The question should have a full set of each tag object.
1411 $this->assertEquals($tags, $actualtagobjects);
1412 // The question should not have any course tags.
1413 $this->assertEmpty($tagobjects->coursetagobjects);
1418 * question_sort_tags() includes course tags for all questions in the list.
1420 public function test_question_sort_tags_includes_question_course_tags() {
1421 global $DB;
1423 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
1424 $question1 = $questions[0];
1425 $question2 = $questions[1];
1426 $coursecontext = context_course::instance($course->id);
1428 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['foo', 'bar']);
1429 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['baz', 'bop']);
1431 foreach ($questions as $question) {
1432 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1433 $tagobjects = question_sort_tags($tags, $qcat);
1435 $expectedtags = [];
1436 $actualtags = $tagobjects->coursetags;
1437 foreach ($actualtags as $coursetagid => $coursetagname) {
1438 $expectedtags[$coursetagid] = $coursetagname;
1441 // The question should have a tags property populated with each tag id
1442 // and display name as a key vale pair.
1443 $this->assertEquals($expectedtags, $actualtags);
1445 $actualtagobjects = $tagobjects->coursetagobjects;
1446 sort($tags);
1447 sort($actualtagobjects);
1449 // The question should have a full set of each tag object.
1450 $this->assertEquals($tags, $actualtagobjects);
1451 // The question should not have any course tags.
1452 $this->assertEmpty($tagobjects->tagobjects);
1457 * question_sort_tags() should return tags from all course contexts by default.
1459 public function test_question_sort_tags_includes_multiple_courses_tags() {
1460 global $DB;
1462 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
1463 $question1 = $questions[0];
1464 $question2 = $questions[1];
1465 $coursecontext = context_course::instance($course->id);
1466 // Create a sibling course.
1467 $siblingcourse = $this->getDataGenerator()->create_course(['category' => $course->category]);
1468 $siblingcoursecontext = context_course::instance($siblingcourse->id);
1470 // Create course tags.
1471 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['c1']);
1472 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['c1']);
1473 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $siblingcoursecontext, ['c2']);
1474 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $siblingcoursecontext, ['c2']);
1476 foreach ($questions as $question) {
1477 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1478 $tagobjects = question_sort_tags($tags, $qcat);
1479 $this->assertCount(2, $tagobjects->coursetagobjects);
1481 foreach ($tagobjects->coursetagobjects as $tag) {
1482 if ($tag->name == 'c1') {
1483 $this->assertEquals($coursecontext->id, $tag->taginstancecontextid);
1484 } else {
1485 $this->assertEquals($siblingcoursecontext->id, $tag->taginstancecontextid);
1492 * question_sort_tags() should filter the course tags by the given list of courses.
1494 public function test_question_sort_tags_includes_filter_course_tags() {
1495 global $DB;
1497 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category');
1498 $question1 = $questions[0];
1499 $question2 = $questions[1];
1500 $coursecontext = context_course::instance($course->id);
1501 // Create a sibling course.
1502 $siblingcourse = $this->getDataGenerator()->create_course(['category' => $course->category]);
1503 $siblingcoursecontext = context_course::instance($siblingcourse->id);
1505 // Create course tags.
1506 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['foo']);
1507 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['bar']);
1508 // Create sibling course tags. These should be filtered out.
1509 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $siblingcoursecontext, ['filtered1']);
1510 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $siblingcoursecontext, ['filtered2']);
1512 foreach ($questions as $question) {
1513 $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1514 $tagobjects = question_sort_tags($tags, $qcat, [$course]);
1515 foreach ($tagobjects->coursetagobjects as $tag) {
1517 // We should only be seeing course tags from $course. The tags from
1518 // $siblingcourse should have been filtered out.
1519 $this->assertEquals($coursecontext->id, $tag->taginstancecontextid);
1525 * Data provider for tests of question_has_capability_on_context and question_require_capability_on_context.
1527 * @return array
1529 public function question_capability_on_question_provider() {
1530 return [
1531 'Unrelated capability which is present' => [
1532 'capabilities' => [
1533 'moodle/question:config' => CAP_ALLOW,
1535 'testcapability' => 'config',
1536 'isowner' => true,
1537 'expect' => true,
1539 'Unrelated capability which is present (not owner)' => [
1540 'capabilities' => [
1541 'moodle/question:config' => CAP_ALLOW,
1543 'testcapability' => 'config',
1544 'isowner' => false,
1545 'expect' => true,
1547 'Unrelated capability which is not set' => [
1548 'capabilities' => [
1550 'testcapability' => 'config',
1551 'isowner' => true,
1552 'expect' => false,
1554 'Unrelated capability which is not set (not owner)' => [
1555 'capabilities' => [
1557 'testcapability' => 'config',
1558 'isowner' => false,
1559 'expect' => false,
1561 'Unrelated capability which is prevented' => [
1562 'capabilities' => [
1563 'moodle/question:config' => CAP_PREVENT,
1565 'testcapability' => 'config',
1566 'isowner' => true,
1567 'expect' => false,
1569 'Unrelated capability which is prevented (not owner)' => [
1570 'capabilities' => [
1571 'moodle/question:config' => CAP_PREVENT,
1573 'testcapability' => 'config',
1574 'isowner' => false,
1575 'expect' => false,
1577 'Related capability which is not set' => [
1578 'capabilities' => [
1580 'testcapability' => 'edit',
1581 'isowner' => true,
1582 'expect' => false,
1584 'Related capability which is not set (not owner)' => [
1585 'capabilities' => [
1587 'testcapability' => 'edit',
1588 'isowner' => false,
1589 'expect' => false,
1591 'Related capability which is allowed at all, unset at mine' => [
1592 'capabilities' => [
1593 'moodle/question:editall' => CAP_ALLOW,
1595 'testcapability' => 'edit',
1596 'isowner' => true,
1597 'expect' => true,
1599 'Related capability which is allowed at all, unset at mine (not owner)' => [
1600 'capabilities' => [
1601 'moodle/question:editall' => CAP_ALLOW,
1603 'testcapability' => 'edit',
1604 'isowner' => false,
1605 'expect' => true,
1607 'Related capability which is allowed at all, prevented at mine' => [
1608 'capabilities' => [
1609 'moodle/question:editall' => CAP_ALLOW,
1610 'moodle/question:editmine' => CAP_PREVENT,
1612 'testcapability' => 'edit',
1613 'isowner' => true,
1614 'expect' => true,
1616 'Related capability which is allowed at all, prevented at mine (not owner)' => [
1617 'capabilities' => [
1618 'moodle/question:editall' => CAP_ALLOW,
1619 'moodle/question:editmine' => CAP_PREVENT,
1621 'testcapability' => 'edit',
1622 'isowner' => false,
1623 'expect' => true,
1625 'Related capability which is unset all, allowed at mine' => [
1626 'capabilities' => [
1627 'moodle/question:editall' => CAP_PREVENT,
1628 'moodle/question:editmine' => CAP_ALLOW,
1630 'testcapability' => 'edit',
1631 'isowner' => true,
1632 'expect' => true,
1634 'Related capability which is unset all, allowed at mine (not owner)' => [
1635 'capabilities' => [
1636 'moodle/question:editall' => CAP_PREVENT,
1637 'moodle/question:editmine' => CAP_ALLOW,
1639 'testcapability' => 'edit',
1640 'isowner' => false,
1641 'expect' => false,
1647 * Tests for the deprecated question_has_capability_on function when passing a stdClass as parameter.
1649 * @dataProvider question_capability_on_question_provider
1650 * @param array $capabilities The capability assignments to set.
1651 * @param string $capability The capability to test
1652 * @param bool $isowner Whether the user to create the question should be the owner or not.
1653 * @param bool $expect The expected result.
1655 public function test_question_has_capability_on_using_stdclass($capabilities, $capability, $isowner, $expect) {
1656 $this->resetAfterTest();
1658 // Create the test data.
1659 $user = $this->getDataGenerator()->create_user();
1660 $otheruser = $this->getDataGenerator()->create_user();
1661 $roleid = $this->getDataGenerator()->create_role();
1662 $category = $this->getDataGenerator()->create_category();
1663 $context = context_coursecat::instance($category->id);
1665 // Assign the user to the role.
1666 role_assign($roleid, $user->id, $context->id);
1668 // Assign the capabilities to the role.
1669 foreach ($capabilities as $capname => $capvalue) {
1670 assign_capability($capname, $capvalue, $roleid, $context->id);
1673 $this->setUser($user);
1675 // The current fake question we make use of is always a stdClass and typically has no ID.
1676 $fakequestion = (object) [
1677 'contextid' => $context->id,
1680 if ($isowner) {
1681 $fakequestion->createdby = $user->id;
1682 } else {
1683 $fakequestion->createdby = $otheruser->id;
1686 $result = question_has_capability_on($fakequestion, $capability);
1687 $this->assertEquals($expect, $result);
1691 * Tests for the deprecated question_has_capability_on function when using question definition.
1693 * @dataProvider question_capability_on_question_provider
1694 * @param array $capabilities The capability assignments to set.
1695 * @param string $capability The capability to test
1696 * @param bool $isowner Whether the user to create the question should be the owner or not.
1697 * @param bool $expect The expected result.
1699 public function test_question_has_capability_on_using_question_definition($capabilities, $capability, $isowner, $expect) {
1700 $this->resetAfterTest();
1702 // Create the test data.
1703 $generator = $this->getDataGenerator();
1704 $questiongenerator = $generator->get_plugin_generator('core_question');
1705 $user = $generator->create_user();
1706 $otheruser = $generator->create_user();
1707 $roleid = $generator->create_role();
1708 $category = $generator->create_category();
1709 $context = context_coursecat::instance($category->id);
1710 $questioncat = $questiongenerator->create_question_category([
1711 'contextid' => $context->id,
1714 // Assign the user to the role.
1715 role_assign($roleid, $user->id, $context->id);
1717 // Assign the capabilities to the role.
1718 foreach ($capabilities as $capname => $capvalue) {
1719 assign_capability($capname, $capvalue, $roleid, $context->id);
1722 // Create the question.
1723 $qtype = 'truefalse';
1724 $overrides = [
1725 'category' => $questioncat->id,
1728 $question = $questiongenerator->create_question($qtype, null, $overrides);
1730 // The question generator does not support setting of the createdby for some reason.
1731 $question->createdby = ($isowner) ? $user->id : $otheruser->id;
1732 $fromform = test_question_maker::get_question_form_data($qtype, null);
1733 $fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
1734 question_bank::get_qtype($qtype)->save_question($question, $fromform);
1736 $this->setUser($user);
1737 $result = question_has_capability_on($question, $capability);
1738 $this->assertEquals($expect, $result);
1742 * Tests for the deprecated question_has_capability_on function when using a real question id.
1744 * @dataProvider question_capability_on_question_provider
1745 * @param array $capabilities The capability assignments to set.
1746 * @param string $capability The capability to test
1747 * @param bool $isowner Whether the user to create the question should be the owner or not.
1748 * @param bool $expect The expected result.
1750 public function test_question_has_capability_on_using_question_id($capabilities, $capability, $isowner, $expect) {
1751 $this->resetAfterTest();
1753 // Create the test data.
1754 $generator = $this->getDataGenerator();
1755 $questiongenerator = $generator->get_plugin_generator('core_question');
1756 $user = $generator->create_user();
1757 $otheruser = $generator->create_user();
1758 $roleid = $generator->create_role();
1759 $category = $generator->create_category();
1760 $context = context_coursecat::instance($category->id);
1761 $questioncat = $questiongenerator->create_question_category([
1762 'contextid' => $context->id,
1765 // Assign the user to the role.
1766 role_assign($roleid, $user->id, $context->id);
1768 // Assign the capabilities to the role.
1769 foreach ($capabilities as $capname => $capvalue) {
1770 assign_capability($capname, $capvalue, $roleid, $context->id);
1773 // Create the question.
1774 $qtype = 'truefalse';
1775 $overrides = [
1776 'category' => $questioncat->id,
1779 $question = $questiongenerator->create_question($qtype, null, $overrides);
1781 // The question generator does not support setting of the createdby for some reason.
1782 $question->createdby = ($isowner) ? $user->id : $otheruser->id;
1783 $fromform = test_question_maker::get_question_form_data($qtype, null);
1784 $fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
1785 question_bank::get_qtype($qtype)->save_question($question, $fromform);
1787 $this->setUser($user);
1788 $result = question_has_capability_on($question->id, $capability);
1789 $this->assertEquals($expect, $result);
1793 * Tests for the deprecated question_has_capability_on function when using a string as question id.
1795 * @dataProvider question_capability_on_question_provider
1796 * @param array $capabilities The capability assignments to set.
1797 * @param string $capability The capability to test
1798 * @param bool $isowner Whether the user to create the question should be the owner or not.
1799 * @param bool $expect The expected result.
1801 public function test_question_has_capability_on_using_question_string_id($capabilities, $capability, $isowner, $expect) {
1802 $this->resetAfterTest();
1804 // Create the test data.
1805 $generator = $this->getDataGenerator();
1806 $questiongenerator = $generator->get_plugin_generator('core_question');
1807 $user = $generator->create_user();
1808 $otheruser = $generator->create_user();
1809 $roleid = $generator->create_role();
1810 $category = $generator->create_category();
1811 $context = context_coursecat::instance($category->id);
1812 $questioncat = $questiongenerator->create_question_category([
1813 'contextid' => $context->id,
1816 // Assign the user to the role.
1817 role_assign($roleid, $user->id, $context->id);
1819 // Assign the capabilities to the role.
1820 foreach ($capabilities as $capname => $capvalue) {
1821 assign_capability($capname, $capvalue, $roleid, $context->id);
1824 // Create the question.
1825 $qtype = 'truefalse';
1826 $overrides = [
1827 'category' => $questioncat->id,
1830 $question = $questiongenerator->create_question($qtype, null, $overrides);
1832 // The question generator does not support setting of the createdby for some reason.
1833 $question->createdby = ($isowner) ? $user->id : $otheruser->id;
1834 $fromform = test_question_maker::get_question_form_data($qtype, null);
1835 $fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
1836 question_bank::get_qtype($qtype)->save_question($question, $fromform);
1838 $this->setUser($user);
1839 $result = question_has_capability_on((string) $question->id, $capability);
1840 $this->assertEquals($expect, $result);
1844 * Tests for the question_has_capability_on function when using a moved question.
1846 * @dataProvider question_capability_on_question_provider
1847 * @param array $capabilities The capability assignments to set.
1848 * @param string $capability The capability to test
1849 * @param bool $isowner Whether the user to create the question should be the owner or not.
1850 * @param bool $expect The expected result.
1852 public function test_question_has_capability_on_using_moved_question($capabilities, $capability, $isowner, $expect) {
1853 $this->resetAfterTest();
1855 // Create the test data.
1856 $generator = $this->getDataGenerator();
1857 $questiongenerator = $generator->get_plugin_generator('core_question');
1858 $user = $generator->create_user();
1859 $otheruser = $generator->create_user();
1860 $roleid = $generator->create_role();
1861 $category = $generator->create_category();
1862 $context = context_coursecat::instance($category->id);
1863 $questioncat = $questiongenerator->create_question_category([
1864 'contextid' => $context->id,
1867 $newcategory = $generator->create_category();
1868 $newcontext = context_coursecat::instance($newcategory->id);
1869 $newquestioncat = $questiongenerator->create_question_category([
1870 'contextid' => $newcontext->id,
1873 // Assign the user to the role in the _new_ context..
1874 role_assign($roleid, $user->id, $newcontext->id);
1876 // Assign the capabilities to the role in the _new_ context.
1877 foreach ($capabilities as $capname => $capvalue) {
1878 assign_capability($capname, $capvalue, $roleid, $newcontext->id);
1881 // Create the question.
1882 $qtype = 'truefalse';
1883 $overrides = [
1884 'category' => $questioncat->id,
1887 $question = $questiongenerator->create_question($qtype, null, $overrides);
1889 // The question generator does not support setting of the createdby for some reason.
1890 $question->createdby = ($isowner) ? $user->id : $otheruser->id;
1891 $fromform = test_question_maker::get_question_form_data($qtype, null);
1892 $fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
1893 question_bank::get_qtype($qtype)->save_question($question, $fromform);
1895 // Move the question.
1896 question_move_questions_to_category([$question->id], $newquestioncat->id);
1898 // Test that the capability is correct after the question has been moved.
1899 $this->setUser($user);
1900 $result = question_has_capability_on($question->id, $capability);
1901 $this->assertEquals($expect, $result);
1905 * Tests for the question_has_capability_on function when using a real question.
1907 * @dataProvider question_capability_on_question_provider
1908 * @param array $capabilities The capability assignments to set.
1909 * @param string $capability The capability to test
1910 * @param bool $isowner Whether the user to create the question should be the owner or not.
1911 * @param bool $expect The expected result.
1913 public function test_question_has_capability_on_using_question($capabilities, $capability, $isowner, $expect) {
1914 $this->resetAfterTest();
1916 // Create the test data.
1917 $generator = $this->getDataGenerator();
1918 $questiongenerator = $generator->get_plugin_generator('core_question');
1919 $user = $generator->create_user();
1920 $otheruser = $generator->create_user();
1921 $roleid = $generator->create_role();
1922 $category = $generator->create_category();
1923 $context = context_coursecat::instance($category->id);
1924 $questioncat = $questiongenerator->create_question_category([
1925 'contextid' => $context->id,
1928 // Assign the user to the role.
1929 role_assign($roleid, $user->id, $context->id);
1931 // Assign the capabilities to the role.
1932 foreach ($capabilities as $capname => $capvalue) {
1933 assign_capability($capname, $capvalue, $roleid, $context->id);
1936 // Create the question.
1937 $question = $questiongenerator->create_question('truefalse', null, [
1938 'category' => $questioncat->id,
1940 $question = question_bank::load_question_data($question->id);
1942 // The question generator does not support setting of the createdby for some reason.
1943 $question->createdby = ($isowner) ? $user->id : $otheruser->id;
1945 $this->setUser($user);
1946 $result = question_has_capability_on($question, $capability);
1947 $this->assertEquals($expect, $result);
1951 * Tests that question_has_capability_on throws an exception for wrong parameter types.
1953 public function test_question_has_capability_on_wrong_param_type() {
1954 // Create the test data.
1955 $generator = $this->getDataGenerator();
1956 $questiongenerator = $generator->get_plugin_generator('core_question');
1957 $user = $generator->create_user();
1959 $category = $generator->create_category();
1960 $context = context_coursecat::instance($category->id);
1961 $questioncat = $questiongenerator->create_question_category([
1962 'contextid' => $context->id,
1965 // Create the question.
1966 $question = $questiongenerator->create_question('truefalse', null, [
1967 'category' => $questioncat->id,
1969 $question = question_bank::load_question_data($question->id);
1971 // The question generator does not support setting of the createdby for some reason.
1972 $question->createdby = $user->id;
1974 $this->setUser($user);
1975 $result = question_has_capability_on((string)$question->id, 'tag');
1976 $this->assertFalse($result);
1978 $this->expectException('coding_exception');
1979 $this->expectExceptionMessage('$questionorid parameter needs to be an integer or an object.');
1980 question_has_capability_on('one', 'tag');
1984 * Test of question_categorylist_parents function.
1986 public function test_question_categorylist_parents() {
1987 $this->resetAfterTest();
1988 $generator = $this->getDataGenerator();
1989 $questiongenerator = $generator->get_plugin_generator('core_question');
1990 $category = $generator->create_category();
1991 $context = context_coursecat::instance($category->id);
1992 // Create a top category.
1993 $cat0 = question_get_top_category($context->id, true);
1994 // Add sub-categories.
1995 $cat1 = $questiongenerator->create_question_category(['parent' => $cat0->id]);
1996 $cat2 = $questiongenerator->create_question_category(['parent' => $cat1->id]);
1997 // Test the 'get parents' function.
1998 $parentcategories = question_categorylist_parents($cat2->id);
1999 $this->assertEquals($cat0->id, $parentcategories[0]);
2000 $this->assertEquals($cat1->id, $parentcategories[1]);
2001 $this->assertCount(2, $parentcategories);
2004 public function test_question_get_export_single_question_url() {
2005 $generator = $this->getDataGenerator();
2007 // Create a course and an activity.
2008 $course = $generator->create_course();
2009 $quiz = $generator->create_module('quiz', ['course' => $course->id]);
2011 // Create a question in each place.
2012 $questiongenerator = $generator->get_plugin_generator('core_question');
2013 $courseqcat = $questiongenerator->create_question_category(['contextid' => context_course::instance($course->id)->id]);
2014 $courseq = $questiongenerator->create_question('truefalse', null, ['category' => $courseqcat->id]);
2015 $quizqcat = $questiongenerator->create_question_category(['contextid' => context_module::instance($quiz->cmid)->id]);
2016 $quizq = $questiongenerator->create_question('truefalse', null, ['category' => $quizqcat->id]);
2017 $systemqcat = $questiongenerator->create_question_category();
2018 $systemq = $questiongenerator->create_question('truefalse', null, ['category' => $systemqcat->id]);
2020 // Verify some URLs.
2021 $this->assertEquals(new moodle_url('/question/exportone.php',
2022 ['id' => $courseq->id, 'courseid' => $course->id, 'sesskey' => sesskey()]),
2023 question_get_export_single_question_url(question_bank::load_question_data($courseq->id)));
2025 $this->assertEquals(new moodle_url('/question/exportone.php',
2026 ['id' => $quizq->id, 'cmid' => $quiz->cmid, 'sesskey' => sesskey()]),
2027 question_get_export_single_question_url(question_bank::load_question($quizq->id)));
2029 $this->assertEquals(new moodle_url('/question/exportone.php',
2030 ['id' => $systemq->id, 'courseid' => SITEID, 'sesskey' => sesskey()]),
2031 question_get_export_single_question_url(question_bank::load_question($systemq->id)));