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