See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace qbank_tagquestion\external; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 global $CFG; 22 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 23 24 /** 25 * Question external functions tests. 26 * 27 * @package qbank_tagquestion 28 * @copyright 2016 Pau Ferrer <pau@moodle.com> 29 * @author 2021 Safat Shahin <safatshahin@catalyst-au.net> 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 class submit_tags_test extends \externallib_advanced_testcase { 33 34 /** 35 * Set up for every test 36 */ 37 public function setUp(): void { 38 global $DB; 39 $this->resetAfterTest(); 40 $this->setAdminUser(); 41 42 // Setup test data. 43 $this->course = $this->getDataGenerator()->create_course(); 44 45 // Create users. 46 $this->student = self::getDataGenerator()->create_user(); 47 48 // Users enrolments. 49 $this->studentrole = $DB->get_record('role', ['shortname' => 'student']); 50 $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual'); 51 } 52 53 /** 54 * submit_tags_form should throw an exception when the question id doesn't match 55 * a question. 56 */ 57 public function test_submit_tags_form_incorrect_question_id() { 58 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 59 list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions(); 60 $questioncontext = \context::instance_by_id($qcat->contextid); 61 $editingcontext = $questioncontext; 62 $question = $questions[0]; 63 // Generate an id for a question that doesn't exist. 64 $missingquestionid = $questions[1]->id * 2; 65 $question->id = $missingquestionid; 66 $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []); 67 68 // We should receive an exception if the question doesn't exist. 69 $this->expectException('moodle_exception'); 70 submit_tags::execute($missingquestionid, $editingcontext->id, $formdata); 71 } 72 73 /** 74 * submit_tags_form should throw an exception when the context id doesn't match 75 * a context. 76 */ 77 public function test_submit_tags_form_incorrect_context_id() { 78 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 79 list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions(); 80 $questioncontext = \context::instance_by_id($qcat->contextid); 81 $editingcontext = $questioncontext; 82 $question = $questions[0]; 83 // Generate an id for a context that doesn't exist. 84 $missingcontextid = $editingcontext->id * 200; 85 $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []); 86 87 // We should receive an exception if the question doesn't exist. 88 $this->expectException('moodle_exception'); 89 submit_tags::execute($question->id, $missingcontextid, $formdata); 90 } 91 92 /** 93 * submit_tags_form should return false when tags are disabled. 94 */ 95 public function test_submit_tags_form_tags_disabled() { 96 global $CFG; 97 98 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 99 list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions(); 100 $questioncontext = \context::instance_by_id($qcat->contextid); 101 $editingcontext = $questioncontext; 102 $question = $questions[0]; 103 $user = $this->create_user_can_tag($course); 104 $formdata = $this->generate_encoded_submit_tags_form_string($question, $qcat, $questioncontext, [], []); 105 106 $this->setUser($user); 107 $CFG->usetags = false; 108 $result = submit_tags::execute($question->id, $editingcontext->id, $formdata); 109 $CFG->usetags = true; 110 111 $this->assertFalse($result['status']); 112 } 113 114 /** 115 * submit_tags_form should return false if the user does not have any capability 116 * to tag the question. 117 */ 118 public function test_submit_tags_form_no_tag_permissions() { 119 global $DB; 120 121 $generator = $this->getDataGenerator(); 122 $user = $generator->create_user(); 123 $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); 124 $questiongenerator = $generator->get_plugin_generator('core_question'); 125 list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions(); 126 $questioncontext = \context::instance_by_id($qcat->contextid); 127 $editingcontext = $questioncontext; 128 $question = $questions[0]; 129 $formdata = $this->generate_encoded_submit_tags_form_string( 130 $question, 131 $qcat, 132 $questioncontext, 133 ['foo'], 134 ['bar'] 135 ); 136 137 // Prohibit all of the tag capabilities. 138 assign_capability('moodle/question:tagmine', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id); 139 assign_capability('moodle/question:tagall', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id); 140 141 $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual'); 142 $user->ignoresesskey = true; 143 $this->setUser($user); 144 145 $result = submit_tags::execute($question->id, $editingcontext->id, $formdata); 146 147 $this->assertFalse($result['status']); 148 } 149 150 /** 151 * submit_tags_form should return false if the user only has the capability to 152 * tag their own questions and the question is not theirs. 153 */ 154 public function test_submit_tags_form_tagmine_permission_non_owner_question() { 155 global $DB; 156 157 $generator = $this->getDataGenerator(); 158 $user = $generator->create_user(); 159 $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); 160 $questiongenerator = $generator->get_plugin_generator('core_question'); 161 list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions(); 162 $questioncontext = \context::instance_by_id($qcat->contextid); 163 $editingcontext = $questioncontext; 164 $question = $questions[0]; 165 $formdata = $this->generate_encoded_submit_tags_form_string( 166 $question, 167 $qcat, 168 $questioncontext, 169 ['foo'], 170 ['bar'] 171 ); 172 173 // Make sure the question isn't created by the user. 174 $question->createdby = $user->id + 1; 175 176 // Prohibit all of the tag capabilities. 177 assign_capability('moodle/question:tagmine', CAP_ALLOW, $teacherrole->id, $questioncontext->id); 178 assign_capability('moodle/question:tagall', CAP_PROHIBIT, $teacherrole->id, $questioncontext->id); 179 180 $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual'); 181 $user->ignoresesskey = true; 182 $this->setUser($user); 183 184 $result = submit_tags::execute($question->id, $editingcontext->id, $formdata); 185 186 $this->assertFalse($result['status']); 187 } 188 189 /** 190 * Data provided for the submit_tags_form test to check that course tags are 191 * only created in the correct editing and question context combinations. 192 * 193 * @return array Test cases 194 */ 195 public function get_submit_tags_form_testcases() { 196 return [ 197 'course - course' => [ 198 'editingcontext' => 'course', 199 'questioncontext' => 'course', 200 'questiontags' => ['foo'], 201 'coursetags' => ['bar'], 202 'expectcoursetags' => false 203 ], 204 'course - course - empty tags' => [ 205 'editingcontext' => 'course', 206 'questioncontext' => 'course', 207 'questiontags' => [], 208 'coursetags' => ['bar'], 209 'expectcoursetags' => false 210 ], 211 'course - course category' => [ 212 'editingcontext' => 'course', 213 'questioncontext' => 'category', 214 'questiontags' => ['foo'], 215 'coursetags' => ['bar'], 216 'expectcoursetags' => true 217 ], 218 'course - system' => [ 219 'editingcontext' => 'course', 220 'questioncontext' => 'system', 221 'questiontags' => ['foo'], 222 'coursetags' => ['bar'], 223 'expectcoursetags' => true 224 ], 225 'course category - course' => [ 226 'editingcontext' => 'category', 227 'questioncontext' => 'course', 228 'questiontags' => ['foo'], 229 'coursetags' => ['bar'], 230 'expectcoursetags' => false 231 ], 232 'course category - course category' => [ 233 'editingcontext' => 'category', 234 'questioncontext' => 'category', 235 'questiontags' => ['foo'], 236 'coursetags' => ['bar'], 237 'expectcoursetags' => false 238 ], 239 'course category - system' => [ 240 'editingcontext' => 'category', 241 'questioncontext' => 'system', 242 'questiontags' => ['foo'], 243 'coursetags' => ['bar'], 244 'expectcoursetags' => false 245 ], 246 'system - course' => [ 247 'editingcontext' => 'system', 248 'questioncontext' => 'course', 249 'questiontags' => ['foo'], 250 'coursetags' => ['bar'], 251 'expectcoursetags' => false 252 ], 253 'system - course category' => [ 254 'editingcontext' => 'system', 255 'questioncontext' => 'category', 256 'questiontags' => ['foo'], 257 'coursetags' => ['bar'], 258 'expectcoursetags' => false 259 ], 260 'system - system' => [ 261 'editingcontext' => 'system', 262 'questioncontext' => 'system', 263 'questiontags' => ['foo'], 264 'coursetags' => ['bar'], 265 'expectcoursetags' => false 266 ], 267 ]; 268 } 269 270 /** 271 * Tests that submit_tags_form only creates course tags when the correct combination 272 * of editing context and question context is provided. 273 * 274 * Course tags can only be set on a course category or system context question that 275 * is being editing in a course context. 276 * 277 * @dataProvider get_submit_tags_form_testcases() 278 * @param string $editingcontext The type of the context the question is being edited in 279 * @param string $questioncontext The type of the context the question belongs to 280 * @param string[] $questiontags The tag names to set as question tags 281 * @param string[] $coursetags The tag names to set as course tags 282 * @param bool $expectcoursetags If the given course tags should have been set or not 283 */ 284 public function test_submit_tags_form_context_combinations( 285 $editingcontext, 286 $questioncontext, 287 $questiontags, 288 $coursetags, 289 $expectcoursetags 290 ) { 291 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 292 list ($category, $course, $qcat, $questions) = $questiongenerator->setup_course_and_questions($questioncontext); 293 $coursecontext = \context_course::instance($course->id); 294 $questioncontext = \context::instance_by_id($qcat->contextid); 295 296 switch($editingcontext) { 297 case 'system': 298 $editingcontext = \context_system::instance(); 299 break; 300 301 case 'category': 302 $editingcontext = \context_coursecat::instance($category->id); 303 break; 304 305 default: 306 $editingcontext = \context_course::instance($course->id); 307 } 308 309 $user = $this->create_user_can_tag($course); 310 $question = $questions[0]; 311 $formdata = $this->generate_encoded_submit_tags_form_string( 312 $question, 313 $qcat, 314 $questioncontext, 315 $questiontags, // Question tags. 316 $coursetags // Course tags. 317 ); 318 319 $this->setUser($user); 320 321 $result = submit_tags::execute($question->id, $editingcontext->id, $formdata); 322 323 $this->assertTrue($result['status']); 324 325 $tagobjects = \core_tag_tag::get_item_tags('core_question', 'question', $question->id); 326 $coursetagobjects = []; 327 $questiontagobjects = []; 328 329 if ($expectcoursetags) { 330 // If the use case is expecting course tags to be created then split 331 // the tags into course tags and question tags and ensure we have 332 // the correct number of course tags. 333 334 while ($tagobject = array_shift($tagobjects)) { 335 if ($tagobject->taginstancecontextid == $questioncontext->id) { 336 $questiontagobjects[] = $tagobject; 337 } else if ($tagobject->taginstancecontextid == $coursecontext->id) { 338 $coursetagobjects[] = $tagobject; 339 } 340 } 341 342 $this->assertCount(count($coursetags), $coursetagobjects); 343 } else { 344 $questiontagobjects = $tagobjects; 345 } 346 347 // Ensure the expected number of question tags was created. 348 $this->assertCount(count($questiontags), $questiontagobjects); 349 350 foreach ($questiontagobjects as $tagobject) { 351 // If we have any question tags then make sure they are in the list 352 // of expected tags and have the correct context. 353 $this->assertContains($tagobject->name, $questiontags); 354 $this->assertEquals($questioncontext->id, $tagobject->taginstancecontextid); 355 } 356 357 foreach ($coursetagobjects as $tagobject) { 358 // If we have any course tags then make sure they are in the list 359 // of expected course tags and have the correct context. 360 $this->assertContains($tagobject->name, $coursetags); 361 $this->assertEquals($coursecontext->id, $tagobject->taginstancecontextid); 362 } 363 } 364 365 /** 366 * Build the encoded form data expected by the submit_tags_form external function. 367 * 368 * @param \stdClass $question The question record 369 * @param \stdClass $questioncategory The question category record 370 * @param \context $questioncontext Context for the question category 371 * @param array $tags A list of tag names for the question 372 * @param array $coursetags A list of course tag names for the question 373 * @return string HTML encoded string of the data 374 */ 375 protected function generate_encoded_submit_tags_form_string($question, $questioncategory, 376 $questioncontext, $tags = [], $coursetags = []) { 377 378 $data = [ 379 'id' => $question->id, 380 'categoryid' => $questioncategory->id, 381 'contextid' => $questioncontext->id, 382 'questionname' => $question->name, 383 'questioncategory' => $questioncategory->name, 384 'context' => $questioncontext->get_context_name(false), 385 'tags' => $tags, 386 'coursetags' => $coursetags 387 ]; 388 $data = \qbank_tagquestion\form\tags_form::mock_generate_submit_keys($data); 389 390 return http_build_query($data, '', '&'); 391 } 392 393 /** 394 * Create a user, enrol them in the course, and give them the capability to 395 * tag all questions in the system context. 396 * 397 * @param \stdClass $course The course record to enrol in 398 * @return \stdClass The user record 399 */ 400 protected function create_user_can_tag($course) { 401 global $DB; 402 403 $generator = $this->getDataGenerator(); 404 $user = $generator->create_user(); 405 $roleid = $generator->create_role(); 406 $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); 407 $systemcontext = \context_system::instance(); 408 409 $generator->role_assign($roleid, $user->id, $systemcontext->id); 410 $generator->enrol_user($user->id, $course->id, $teacherrole->id, 'manual'); 411 412 // Give the user global ability to tag questions. 413 assign_capability('moodle/question:tagall', CAP_ALLOW, $roleid, $systemcontext, true); 414 // Allow the user to submit form data. 415 $user->ignoresesskey = true; 416 417 return $user; 418 } 419 420 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body