See Release Notes
Long Term Support Release
Differences Between: [Versions 311 and 401] [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 /** 18 * Privacy provider tests. 19 * 20 * @package core_question 21 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace core_question\privacy; 25 26 use core_privacy\local\metadata\collection; 27 use core_privacy\local\request\deletion_criteria; 28 use core_privacy\local\request\writer; 29 use core_question\privacy\provider; 30 31 defined('MOODLE_INTERNAL') || die(); 32 33 global $CFG; 34 require_once($CFG->libdir . '/xmlize.php'); 35 require_once (__DIR__ . '/../privacy_helper.php'); 36 require_once (__DIR__ . '/../../engine/tests/helpers.php'); 37 38 /** 39 * Privacy provider tests class. 40 * 41 * @package core_question 42 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 */ 45 class provider_test extends \core_privacy\tests\provider_testcase { 46 47 // Include the privacy helper which has assertions on it. 48 use \core_question_privacy_helper; 49 50 /** 51 * Prepare a question attempt. 52 * 53 * @return question_usage_by_activity 54 */ 55 protected function prepare_question_attempt() { 56 // Create a question with a usage from the current user. 57 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 58 $cat = $questiongenerator->create_question_category(); 59 $quba = \question_engine::make_questions_usage_by_activity('core_question_preview', \context_system::instance()); 60 $quba->set_preferred_behaviour('deferredfeedback'); 61 $questiondata = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); 62 $question = \question_bank::load_question($questiondata->id); 63 $quba->add_question($question); 64 $quba->start_all_questions(); 65 66 \question_engine::save_questions_usage_by_activity($quba); 67 68 return $quba; 69 } 70 71 /** 72 * Test that calling export_question_usage on a usage belonging to a 73 * different user does not export any data. 74 */ 75 public function test_export_question_usage_no_usage() { 76 $this->resetAfterTest(); 77 78 $quba = $this->prepare_question_attempt(); 79 80 // Create a question with a usage from the current user. 81 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 82 $cat = $questiongenerator->create_question_category(); 83 $quba = \question_engine::make_questions_usage_by_activity('core_question_preview', \context_system::instance()); 84 $quba->set_preferred_behaviour('deferredfeedback'); 85 $questiondata = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); 86 $question = \question_bank::load_question($questiondata->id); 87 $quba->add_question($question); 88 $quba->start_all_questions(); 89 90 \question_engine::save_questions_usage_by_activity($quba); 91 92 // Set the user. 93 $testuser = $this->getDataGenerator()->create_user(); 94 $this->setUser($testuser); 95 $context = $quba->get_owning_context(); 96 $options = new \question_display_options(); 97 98 provider::export_question_usage($testuser->id, $context, [], $quba->get_id(), $options, false); 99 $writer = writer::with_context($context); 100 101 $this->assertFalse($writer->has_any_data_in_any_context()); 102 } 103 104 /** 105 * Test that calling export_question_usage on a usage belonging to a 106 * different user but ignoring the user match 107 */ 108 public function test_export_question_usage_with_usage() { 109 $this->resetAfterTest(); 110 111 $quba = $this->prepare_question_attempt(); 112 113 // Create a question with a usage from the current user. 114 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 115 $cat = $questiongenerator->create_question_category(); 116 $quba = \question_engine::make_questions_usage_by_activity('core_question_preview', \context_system::instance()); 117 $quba->set_preferred_behaviour('deferredfeedback'); 118 119 $questiondata = $questiongenerator->create_question('truefalse', 'true', ['category' => $cat->id]); 120 $quba->add_question(\question_bank::load_question($questiondata->id)); 121 $questiondata = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 122 $quba->add_question(\question_bank::load_question($questiondata->id)); 123 124 // Set the user and answer the questions. 125 $testuser = $this->getDataGenerator()->create_user(); 126 $this->setUser($testuser); 127 128 $quba->start_all_questions(); 129 $quba->process_action(1, ['answer' => 1]); 130 $quba->process_action(2, ['answer' => 'cat']); 131 $quba->finish_all_questions(); 132 133 \question_engine::save_questions_usage_by_activity($quba); 134 135 $context = $quba->get_owning_context(); 136 137 // Export all questions for this attempt. 138 $options = new \question_display_options(); 139 provider::export_question_usage($testuser->id, $context, [], $quba->get_id(), $options, true); 140 $writer = writer::with_context($context); 141 142 $this->assertTrue($writer->has_any_data_in_any_context()); 143 $this->assertTrue($writer->has_any_data()); 144 145 $slots = $quba->get_slots(); 146 $this->assertCount(2, $slots); 147 148 foreach ($slots as $slotno) { 149 $data = $writer->get_data([get_string('questions', 'core_question'), $slotno]); 150 $this->assertNotNull($data); 151 $this->assert_question_slot_equals($quba, $slotno, $options, $data); 152 } 153 154 $this->assertEmpty($writer->get_data([get_string('questions', 'core_question'), $quba->next_slot_number()])); 155 156 // Disable some options and re-export. 157 writer::reset(); 158 $options = new \question_display_options(); 159 $options->hide_all_feedback(); 160 $options->flags = \question_display_options::HIDDEN; 161 $options->marks = \question_display_options::HIDDEN; 162 163 provider::export_question_usage($testuser->id, $context, [], $quba->get_id(), $options, true); 164 $writer = writer::with_context($context); 165 166 $this->assertTrue($writer->has_any_data_in_any_context()); 167 $this->assertTrue($writer->has_any_data()); 168 169 $slots = $quba->get_slots(); 170 $this->assertCount(2, $slots); 171 172 foreach ($slots as $slotno) { 173 $data = $writer->get_data([get_string('questions', 'core_question'), $slotno]); 174 $this->assertNotNull($data); 175 $this->assert_question_slot_equals($quba, $slotno, $options, $data); 176 } 177 178 $this->assertEmpty($writer->get_data([get_string('questions', 'core_question'), $quba->next_slot_number()])); 179 } 180 181 /** 182 * Test that questions owned by a user are exported and never deleted. 183 */ 184 public function test_question_owned_is_handled() { 185 global $DB; 186 $this->resetAfterTest(); 187 188 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 189 190 // Create the two test users. 191 $user = $this->getDataGenerator()->create_user(); 192 $otheruser = $this->getDataGenerator()->create_user(); 193 194 // Create one question as each user in diferent contexts. 195 $this->setUser($user); 196 $userdata = $questiongenerator->setup_course_and_questions(); 197 $expectedcontext = \context_course::instance($userdata[1]->id); 198 199 $this->setUser($otheruser); 200 $otheruserdata = $questiongenerator->setup_course_and_questions(); 201 $unexpectedcontext = \context_course::instance($otheruserdata[1]->id); 202 203 // And create another one where we'll update a question as the test user. 204 $moreotheruserdata = $questiongenerator->setup_course_and_questions(); 205 $otherexpectedcontext = \context_course::instance($moreotheruserdata[1]->id); 206 $morequestions = $moreotheruserdata[3]; 207 208 // Update the third set of questions. 209 $this->setUser($user); 210 211 foreach ($morequestions as $question) { 212 $questiongenerator->update_question($question); 213 } 214 215 // Run the get_contexts_for_userid as default user. 216 $this->setUser(); 217 218 // There should be two contexts returned - the first course, and the third. 219 $contextlist = provider::get_contexts_for_userid($user->id); 220 $this->assertCount(2, $contextlist); 221 222 $expectedcontexts = [ 223 $expectedcontext->id, 224 $otherexpectedcontext->id, 225 ]; 226 $this->assertEqualsCanonicalizing($expectedcontexts, $contextlist->get_contextids(), 'Contexts not equal'); 227 228 // Run the export_user_Data as the test user. 229 $this->setUser($user); 230 231 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( 232 \core_user::get_user($user->id), 233 'core_question', 234 $expectedcontexts 235 ); 236 provider::export_user_data($approvedcontextlist); 237 238 // There should be data for the user's question context. 239 $writer = writer::with_context($expectedcontext); 240 $this->assertTrue($writer->has_any_data()); 241 242 // And for the course we updated. 243 $otherwriter = writer::with_context($otherexpectedcontext); 244 $this->assertTrue($otherwriter->has_any_data()); 245 246 // But not for the other user's course. 247 $otherwriter = writer::with_context($unexpectedcontext); 248 $this->assertFalse($otherwriter->has_any_data()); 249 250 // The question data is exported as an XML export in custom files. 251 $writer = writer::with_context($expectedcontext); 252 $subcontext = [get_string('questionbank', 'core_question')]; 253 254 $exportfile = $writer->get_custom_file($subcontext, 'questions.xml'); 255 $this->assertNotEmpty($exportfile); 256 257 $xmlized = xmlize($exportfile); 258 $xmlquestions = $xmlized['quiz']['#']['question']; 259 260 $this->assertCount(2, $xmlquestions); 261 262 // Run the delete functions as default user. 263 $this->setUser(); 264 265 // Find out how many questions are in the question bank to start with. 266 $questioncount = $DB->count_records('question'); 267 268 // The delete functions should do nothing here. 269 270 // Delete for all users in context. 271 provider::delete_data_for_all_users_in_context($expectedcontext); 272 $this->assertEquals($questioncount, $DB->count_records('question')); 273 274 provider::delete_data_for_user($approvedcontextlist); 275 $this->assertEquals($questioncount, $DB->count_records('question')); 276 } 277 278 /** 279 * Deleting questions should only unset their created and modified user. 280 */ 281 public function test_question_delete_data_for_user_anonymised() { 282 global $DB; 283 $this->resetAfterTest(true); 284 285 $user = \core_user::get_user_by_username('admin'); 286 $otheruser = $this->getDataGenerator()->create_user(); 287 288 $course = $this->getDataGenerator()->create_course(); 289 $context = \context_course::instance($course->id); 290 $othercourse = $this->getDataGenerator()->create_course(); 291 $othercontext = \context_course::instance($othercourse->id); 292 293 // Create a couple of questions. 294 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 295 $cat = $questiongenerator->create_question_category([ 296 'contextid' => $context->id, 297 ]); 298 $othercat = $questiongenerator->create_question_category([ 299 'contextid' => $othercontext->id, 300 ]); 301 302 // Create questions: 303 // Q1 - Created by the UUT, Modified by UUT. 304 // Q2 - Created by the UUT, Modified by the other user. 305 // Q3 - Created by the other user, Modified by UUT 306 // Q4 - Created by the other user, Modified by the other user. 307 // Q5 - Created by the UUT, Modified by the UUT, but in a different context. 308 $this->setUser($user); 309 $q1 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 310 $q2 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 311 312 $this->setUser($otheruser); 313 // When we update a question, a new question/version is created. 314 $q2updated = $questiongenerator->update_question($q2); 315 $q3 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 316 $q4 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 317 318 $this->setUser($user); 319 // When we update a question, a new question/version is created. 320 $q3updated = $questiongenerator->update_question($q3); 321 $q5 = $questiongenerator->create_question('shortanswer', null, ['category' => $othercat->id]); 322 323 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( 324 $user, 325 'core_question', 326 [$context->id] 327 ); 328 329 // Find out how many questions are in the question bank to start with. 330 $questioncount = $DB->count_records('question'); 331 332 // Delete the data and check it is removed. 333 $this->setUser(); 334 provider::delete_data_for_user($approvedcontextlist); 335 336 $this->assertEquals($questioncount, $DB->count_records('question')); 337 338 $qrecord = $DB->get_record('question', ['id' => $q1->id]); 339 $this->assertEquals(0, $qrecord->createdby); 340 $this->assertEquals(0, $qrecord->modifiedby); 341 342 $qrecord = $DB->get_record('question', ['id' => $q2updated->id]); 343 $this->assertEquals($otheruser->id, $qrecord->createdby); 344 $this->assertEquals($otheruser->id, $qrecord->modifiedby); 345 346 $qrecord = $DB->get_record('question', ['id' => $q3updated->id]); 347 $this->assertEquals(0, $qrecord->createdby); 348 $this->assertEquals(0, $qrecord->modifiedby); 349 350 $qrecord = $DB->get_record('question', ['id' => $q4->id]); 351 $this->assertEquals($otheruser->id, $qrecord->createdby); 352 $this->assertEquals($otheruser->id, $qrecord->modifiedby); 353 354 $qrecord = $DB->get_record('question', ['id' => $q5->id]); 355 $this->assertEquals($user->id, $qrecord->createdby); 356 $this->assertEquals($user->id, $qrecord->modifiedby); 357 } 358 359 /** 360 * Deleting questions should only unset their created and modified user for all questions in a context. 361 */ 362 public function test_question_delete_data_for_all_users_in_context_anonymised() { 363 global $DB; 364 $this->resetAfterTest(true); 365 366 $user = \core_user::get_user_by_username('admin'); 367 $otheruser = $this->getDataGenerator()->create_user(); 368 369 $course = $this->getDataGenerator()->create_course(); 370 $context = \context_course::instance($course->id); 371 $othercourse = $this->getDataGenerator()->create_course(); 372 $othercontext = \context_course::instance($othercourse->id); 373 374 // Create a couple of questions. 375 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 376 $cat = $questiongenerator->create_question_category([ 377 'contextid' => $context->id, 378 ]); 379 $othercat = $questiongenerator->create_question_category([ 380 'contextid' => $othercontext->id, 381 ]); 382 383 // Create questions: 384 // Q1 - Created by the UUT, Modified by UUT. 385 // Q2 - Created by the UUT, Modified by the other user. 386 // Q3 - Created by the other user, Modified by UUT 387 // Q4 - Created by the other user, Modified by the other user. 388 // Q5 - Created by the UUT, Modified by the UUT, but in a different context. 389 $this->setUser($user); 390 $q1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); 391 $q2 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); 392 393 $this->setUser($otheruser); 394 $questiongenerator->update_question($q2); 395 $q3 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); 396 $q4 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); 397 398 $this->setUser($user); 399 $questiongenerator->update_question($q3); 400 $q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id)); 401 402 // Find out how many questions are in the question bank to start with. 403 $questioncount = $DB->count_records('question'); 404 405 // Delete the data and check it is removed. 406 $this->setUser(); 407 provider::delete_data_for_all_users_in_context($context); 408 409 $this->assertEquals($questioncount, $DB->count_records('question')); 410 411 $qrecord = $DB->get_record('question', ['id' => $q1->id]); 412 $this->assertEquals(0, $qrecord->createdby); 413 $this->assertEquals(0, $qrecord->modifiedby); 414 415 $qrecord = $DB->get_record('question', ['id' => $q2->id]); 416 $this->assertEquals(0, $qrecord->createdby); 417 $this->assertEquals(0, $qrecord->modifiedby); 418 419 $qrecord = $DB->get_record('question', ['id' => $q3->id]); 420 $this->assertEquals(0, $qrecord->createdby); 421 $this->assertEquals(0, $qrecord->modifiedby); 422 423 $qrecord = $DB->get_record('question', ['id' => $q4->id]); 424 $this->assertEquals(0, $qrecord->createdby); 425 $this->assertEquals(0, $qrecord->modifiedby); 426 427 $qrecord = $DB->get_record('question', ['id' => $q5->id]); 428 $this->assertEquals($user->id, $qrecord->createdby); 429 $this->assertEquals($user->id, $qrecord->modifiedby); 430 } 431 432 /** 433 * Test for provider::get_users_in_context(). 434 */ 435 public function test_get_users_in_context() { 436 $this->resetAfterTest(); 437 438 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 439 440 // Create three test users. 441 $user1 = $this->getDataGenerator()->create_user(); 442 $user2 = $this->getDataGenerator()->create_user(); 443 $user3 = $this->getDataGenerator()->create_user(); 444 445 // Create one question as each user in different contexts. 446 $this->setUser($user1); 447 $user1data = $questiongenerator->setup_course_and_questions(); 448 $this->setUser($user2); 449 $user2data = $questiongenerator->setup_course_and_questions(); 450 451 $course1context = \context_course::instance($user1data[1]->id); 452 $course1questions = $user1data[3]; 453 454 // Log in as user3 and update the questions in course1. 455 $this->setUser($user3); 456 457 foreach ($course1questions as $question) { 458 $questiongenerator->update_question($question); 459 } 460 461 $userlist = new \core_privacy\local\request\userlist($course1context, 'core_question'); 462 provider::get_users_in_context($userlist); 463 464 // User1 has created questions and user3 has edited them. 465 $this->assertCount(2, $userlist); 466 $this->assertEqualsCanonicalizing([$user1->id, $user3->id], $userlist->get_userids()); 467 } 468 469 /** 470 * Test for provider::delete_data_for_users(). 471 */ 472 public function test_delete_data_for_users() { 473 global $DB; 474 475 $this->resetAfterTest(); 476 477 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 478 479 // Create three test users. 480 $user1 = $this->getDataGenerator()->create_user(); 481 $user2 = $this->getDataGenerator()->create_user(); 482 $user3 = $this->getDataGenerator()->create_user(); 483 484 // Create one question as each user in different contexts. 485 $this->setUser($user1); 486 $course1data = $questiongenerator->setup_course_and_questions(); 487 $course1 = $course1data[1]; 488 $course1qcat = $course1data[2]; 489 $course1questions = $course1data[3]; 490 $course1context = \context_course::instance($course1->id); 491 492 // Log in as user2 and update the questions in course1. 493 $this->setUser($user2); 494 495 foreach ($course1questions as $question) { 496 $questiongenerator->update_question($question); 497 } 498 499 // Add 2 more questions to course1 by user3. 500 $this->setUser($user3); 501 $questiongenerator->create_question('shortanswer', null, ['category' => $course1qcat->id]); 502 $questiongenerator->create_question('shortanswer', null, ['category' => $course1qcat->id]); 503 504 // Now, log in as user1 again, and then create a new course and add questions to that. 505 $this->setUser($user1); 506 $questiongenerator->setup_course_and_questions(); 507 508 $approveduserlist = new \core_privacy\local\request\approved_userlist($course1context, 'core_question', 509 [$user1->id, $user2->id]); 510 provider::delete_data_for_users($approveduserlist); 511 512 // Now, there should be no question related to user1 or user2 in course1. 513 $this->assertEquals(0, 514 $DB->count_records_sql("SELECT COUNT(q.id) 515 FROM {question} q 516 JOIN {question_versions} qv ON qv.questionid = q.id 517 JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid 518 JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid 519 WHERE qc.contextid = ? 520 AND (q.createdby = ? OR q.modifiedby = ? OR q.createdby = ? OR q.modifiedby = ?)", 521 [$course1context->id, $user1->id, $user1->id, $user2->id, $user2->id]) 522 ); 523 524 // User3 data in course1 should not change. 525 $this->assertEquals(2, 526 $DB->count_records_sql("SELECT COUNT(q.id) 527 FROM {question} q 528 JOIN {question_versions} qv ON qv.questionid = q.id 529 JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid 530 JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid 531 WHERE qc.contextid = ? AND (q.createdby = ? OR q.modifiedby = ?)", 532 [$course1context->id, $user3->id, $user3->id]) 533 ); 534 535 // User1 has authored 2 questions in another course. 536 $this->assertEquals( 537 2, 538 $DB->count_records_select('question', "createdby = ? OR modifiedby = ?", [$user1->id, $user1->id]) 539 ); 540 } 541 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body