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