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