Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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   * Provides the {@link mod_workshop_privacy_provider_testcase} class.
  19   *
  20   * @package     mod_workshop
  21   * @category    test
  22   * @copyright   2018 David Mudrák <david@moodle.com>
  23   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  
  30  use core_privacy\local\request\writer;
  31  
  32  /**
  33   * Unit tests for the privacy API implementation.
  34   *
  35   * @copyright 2018 David Mudrák <david@moodle.com>
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class mod_workshop_privacy_provider_testcase extends advanced_testcase {
  39  
  40      /** @var testing_data_generator */
  41      protected $generator;
  42  
  43      /** @var mod_workshop_generator */
  44      protected $workshopgenerator;
  45  
  46      /** @var stdClass */
  47      protected $course1;
  48  
  49      /** @var stdClass */
  50      protected $course2;
  51  
  52      /** @var stdClass */
  53      protected $student1;
  54  
  55      /** @var stdClass */
  56      protected $student2;
  57  
  58      /** @var stdClass */
  59      protected $student3;
  60  
  61      /** @var stdClass */
  62      protected $teacher4;
  63  
  64      /** @var stdClass first workshop in course1 */
  65      protected $workshop11;
  66  
  67      /** @var stdClass second workshop in course1 */
  68      protected $workshop12;
  69  
  70      /** @var stdClass first workshop in course2 */
  71      protected $workshop21;
  72  
  73      /** @var int ID of the submission in workshop11 by student1 */
  74      protected $submission111;
  75  
  76      /** @var int ID of the submission in workshop12 by student1 */
  77      protected $submission121;
  78  
  79      /** @var int ID of the submission in workshop12 by student2 */
  80      protected $submission122;
  81  
  82      /** @var int ID of the submission in workshop21 by student2 */
  83      protected $submission212;
  84  
  85      /** @var int ID of the assessment of submission111 by student1 */
  86      protected $assessment1111;
  87  
  88      /** @var int ID of the assessment of submission111 by student2 */
  89      protected $assessment1112;
  90  
  91      /** @var int ID of the assessment of submission111 by student3 */
  92      protected $assessment1113;
  93  
  94      /** @var int ID of the assessment of submission121 by student2 */
  95      protected $assessment1212;
  96  
  97      /** @var int ID of the assessment of submission212 by student1 */
  98      protected $assessment2121;
  99  
 100      /**
 101       * Set up the test environment.
 102       *
 103       * course1
 104       *  |
 105       *  +--workshop11 (first digit matches the course, second is incremental)
 106       *  |   |
 107       *  |   +--submission111 (first two digits match the workshop, last one matches the author)
 108       *  |       |
 109       *  |       +--assessment1111 (first three digits match the submission, last one matches the reviewer)
 110       *  |       +--assessment1112
 111       *  |       +--assessment1113
 112       *  |
 113       *  +--workshop12
 114       *      |
 115       *      +--submission121
 116       *      |   |
 117       *      |   +--assessment1212
 118       *      |
 119       *      +--submission122
 120       *
 121       *  etc.
 122       */
 123      protected function setUp() {
 124          global $DB;
 125          $this->resetAfterTest();
 126          $this->setAdminUser();
 127  
 128          $this->generator = $this->getDataGenerator();
 129          $this->workshopgenerator = $this->generator->get_plugin_generator('mod_workshop');
 130  
 131          $this->course1 = $this->generator->create_course();
 132          $this->course2 = $this->generator->create_course();
 133  
 134          $this->workshop11 = $this->generator->create_module('workshop', [
 135              'course' => $this->course1,
 136              'name' => 'Workshop11',
 137          ]);
 138          $DB->set_field('workshop', 'phase', 50, ['id' => $this->workshop11->id]);
 139  
 140          $this->workshop12 = $this->generator->create_module('workshop', ['course' => $this->course1]);
 141          $this->workshop21 = $this->generator->create_module('workshop', ['course' => $this->course2]);
 142  
 143          $this->student1 = $this->generator->create_user();
 144          $this->student2 = $this->generator->create_user();
 145          $this->student3 = $this->generator->create_user();
 146          $this->teacher4 = $this->generator->create_user();
 147  
 148          $this->submission111 = $this->workshopgenerator->create_submission($this->workshop11->id, $this->student1->id);
 149          $this->submission121 = $this->workshopgenerator->create_submission($this->workshop12->id, $this->student1->id,
 150              ['gradeoverby' => $this->teacher4->id]);
 151          $this->submission122 = $this->workshopgenerator->create_submission($this->workshop12->id, $this->student2->id);
 152          $this->submission212 = $this->workshopgenerator->create_submission($this->workshop21->id, $this->student2->id);
 153  
 154          $this->assessment1111 = $this->workshopgenerator->create_assessment($this->submission111, $this->student1->id, [
 155              'grade' => null,
 156          ]);
 157          $this->assessment1112 = $this->workshopgenerator->create_assessment($this->submission111, $this->student2->id, [
 158              'grade' => 92,
 159          ]);
 160          $this->assessment1113 = $this->workshopgenerator->create_assessment($this->submission111, $this->student3->id);
 161  
 162          $this->assessment1212 = $this->workshopgenerator->create_assessment($this->submission121, $this->student2->id, [
 163              'feedbackauthor' => 'This is what student 2 thinks about submission 121',
 164              'feedbackreviewer' => 'This is what the teacher thinks about this assessment',
 165          ]);
 166  
 167          $this->assessment2121 = $this->workshopgenerator->create_assessment($this->submission212, $this->student1->id, [
 168              'grade' => 68,
 169              'gradinggradeover' => 80,
 170              'gradinggradeoverby' => $this->teacher4->id,
 171              'feedbackauthor' => 'This is what student 1 thinks about submission 212',
 172              'feedbackreviewer' => 'This is what the teacher thinks about this assessment',
 173          ]);
 174      }
 175  
 176      /**
 177       * Test {@link \mod_workshop\privacy\provider::get_contexts_for_userid()} implementation.
 178       */
 179      public function test_get_contexts_for_userid() {
 180  
 181          $cm11 = get_coursemodule_from_instance('workshop', $this->workshop11->id);
 182          $cm12 = get_coursemodule_from_instance('workshop', $this->workshop12->id);
 183          $cm21 = get_coursemodule_from_instance('workshop', $this->workshop21->id);
 184  
 185          $context11 = context_module::instance($cm11->id);
 186          $context12 = context_module::instance($cm12->id);
 187          $context21 = context_module::instance($cm21->id);
 188  
 189          // Student1 has data in workshop11 (author + self reviewer), workshop12 (author) and workshop21 (reviewer).
 190          $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student1->id);
 191          $this->assertInstanceOf(\core_privacy\local\request\contextlist::class, $contextlist);
 192          $this->assertEquals([$context11->id, $context12->id, $context21->id], $contextlist->get_contextids(), '', 0.0, 10, true);
 193  
 194          // Student2 has data in workshop11 (reviewer), workshop12 (reviewer) and workshop21 (author).
 195          $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student2->id);
 196          $this->assertEquals([$context11->id, $context12->id, $context21->id], $contextlist->get_contextids(), '', 0.0, 10, true);
 197  
 198          // Student3 has data in workshop11 (reviewer).
 199          $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student3->id);
 200          $this->assertEquals([$context11->id], $contextlist->get_contextids(), '', 0.0, 10, true);
 201  
 202          // Teacher4 has data in workshop12 (gradeoverby) and workshop21 (gradinggradeoverby).
 203          $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->teacher4->id);
 204          $this->assertEquals([$context21->id, $context12->id], $contextlist->get_contextids(), '', 0.0, 10, true);
 205      }
 206  
 207      /**
 208       * Test {@link \mod_workshop\privacy\provider::get_users_in_context()} implementation.
 209       */
 210      public function test_get_users_in_context() {
 211  
 212          $cm11 = get_coursemodule_from_instance('workshop', $this->workshop11->id);
 213          $cm12 = get_coursemodule_from_instance('workshop', $this->workshop12->id);
 214          $cm21 = get_coursemodule_from_instance('workshop', $this->workshop21->id);
 215  
 216          $context11 = context_module::instance($cm11->id);
 217          $context12 = context_module::instance($cm12->id);
 218          $context21 = context_module::instance($cm21->id);
 219  
 220          // Users in the workshop11.
 221          $userlist11 = new \core_privacy\local\request\userlist($context11, 'mod_workshop');
 222          \mod_workshop\privacy\provider::get_users_in_context($userlist11);
 223          $expected11 = [
 224              $this->student1->id, // Student1 has data in workshop11 (author + self reviewer).
 225              $this->student2->id, // Student2 has data in workshop11 (reviewer).
 226              $this->student3->id, // Student3 has data in workshop11 (reviewer).
 227          ];
 228          $actual11 = $userlist11->get_userids();
 229          $this->assertEquals($expected11, $actual11, '', 0, 10, true);
 230  
 231          // Users in the workshop12.
 232          $userlist12 = new \core_privacy\local\request\userlist($context12, 'mod_workshop');
 233          \mod_workshop\privacy\provider::get_users_in_context($userlist12);
 234          $expected12 = [
 235              $this->student1->id, // Student1 has data in workshop12 (author).
 236              $this->student2->id, // Student2 has data in workshop12 (reviewer).
 237              $this->teacher4->id, // Teacher4 has data in workshop12 (gradeoverby).
 238          ];
 239          $actual12 = $userlist12->get_userids();
 240          $this->assertEquals($expected12, $actual12, '', 0, 10, true);
 241  
 242          // Users in the workshop21.
 243          $userlist21 = new \core_privacy\local\request\userlist($context21, 'mod_workshop');
 244          \mod_workshop\privacy\provider::get_users_in_context($userlist21);
 245          $expected21 = [
 246              $this->student1->id, // Student1 has data in workshop21 (reviewer).
 247              $this->student2->id, // Student2 has data in workshop21 (author).
 248              $this->teacher4->id, // Teacher4 has data in workshop21 (gradinggradeoverby).
 249          ];
 250          $actual21 = $userlist21->get_userids();
 251          $this->assertEquals($expected21, $actual21, '', 0, 10, true);
 252      }
 253  
 254      /**
 255       * Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation.
 256       */
 257      public function test_export_user_data_1() {
 258  
 259          $contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'mod_workshop', [
 260              \context_module::instance($this->workshop11->cmid)->id,
 261              \context_module::instance($this->workshop12->cmid)->id,
 262          ]);
 263  
 264          \mod_workshop\privacy\provider::export_user_data($contextlist);
 265  
 266          $writer = writer::with_context(\context_module::instance($this->workshop11->cmid));
 267  
 268          $workshop = $writer->get_data([]);
 269          $this->assertEquals('Workshop11', $workshop->name);
 270          $this->assertObjectHasAttribute('phase', $workshop);
 271  
 272          $mysubmission = $writer->get_data([
 273              get_string('mysubmission', 'mod_workshop'),
 274          ]);
 275  
 276          $mysubmissionselfassessmentwithoutgrade = $writer->get_data([
 277              get_string('mysubmission', 'mod_workshop'),
 278              get_string('assessments', 'mod_workshop'),
 279              $this->assessment1111,
 280          ]);
 281          $this->assertNull($mysubmissionselfassessmentwithoutgrade->grade);
 282          $this->assertEquals(get_string('yes'), $mysubmissionselfassessmentwithoutgrade->selfassessment);
 283  
 284          $mysubmissionassessmentwithgrade = $writer->get_data([
 285              get_string('mysubmission', 'mod_workshop'),
 286              get_string('assessments', 'mod_workshop'),
 287              $this->assessment1112,
 288          ]);
 289          $this->assertEquals(92, $mysubmissionassessmentwithgrade->grade);
 290          $this->assertEquals(get_string('no'), $mysubmissionassessmentwithgrade->selfassessment);
 291  
 292          $mysubmissionassessmentwithoutgrade = $writer->get_data([
 293              get_string('mysubmission', 'mod_workshop'),
 294              get_string('assessments', 'mod_workshop'),
 295              $this->assessment1113,
 296          ]);
 297          $this->assertEquals(null, $mysubmissionassessmentwithoutgrade->grade);
 298          $this->assertEquals(get_string('no'), $mysubmissionassessmentwithoutgrade->selfassessment);
 299  
 300          $myassessments = $writer->get_data([
 301              get_string('myassessments', 'mod_workshop'),
 302          ]);
 303          $this->assertEmpty($myassessments);
 304      }
 305  
 306      /**
 307       * Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation.
 308       */
 309      public function test_export_user_data_2() {
 310  
 311          $contextlist = new \core_privacy\local\request\approved_contextlist($this->student2, 'mod_workshop', [
 312              \context_module::instance($this->workshop11->cmid)->id,
 313          ]);
 314  
 315          \mod_workshop\privacy\provider::export_user_data($contextlist);
 316  
 317          $writer = writer::with_context(\context_module::instance($this->workshop11->cmid));
 318  
 319          $assessedsubmission = $writer->get_related_data([
 320              get_string('myassessments', 'mod_workshop'),
 321              $this->assessment1112,
 322          ], 'submission');
 323          $this->assertEquals(get_string('no'), $assessedsubmission->myownsubmission);
 324      }
 325  
 326      /**
 327       * Test {@link \mod_workshop\privacy\provider::delete_data_for_all_users_in_context()} implementation.
 328       */
 329      public function test_delete_data_for_all_users_in_context() {
 330          global $DB;
 331  
 332          $this->assertTrue($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));
 333  
 334          // Passing a non-module context does nothing.
 335          \mod_workshop\privacy\provider::delete_data_for_all_users_in_context(\context_course::instance($this->course1->id));
 336          $this->assertTrue($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));
 337  
 338          // Passing a workshop context removes all data.
 339          \mod_workshop\privacy\provider::delete_data_for_all_users_in_context(\context_module::instance($this->workshop11->cmid));
 340          $this->assertFalse($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id]));
 341      }
 342  
 343      /**
 344       * Test {@link \mod_workshop\privacy\provider::delete_data_for_user()} implementation.
 345       */
 346      public function test_delete_data_for_user() {
 347          global $DB;
 348  
 349          $student1submissions = $DB->get_records('workshop_submissions', [
 350              'workshopid' => $this->workshop12->id,
 351              'authorid' => $this->student1->id,
 352          ]);
 353  
 354          $student2submissions = $DB->get_records('workshop_submissions', [
 355              'workshopid' => $this->workshop12->id,
 356              'authorid' => $this->student2->id,
 357          ]);
 358  
 359          $this->assertNotEmpty($student1submissions);
 360          $this->assertNotEmpty($student2submissions);
 361  
 362          foreach ($student1submissions as $submission) {
 363              $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
 364          }
 365  
 366          foreach ($student2submissions as $submission) {
 367              $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
 368          }
 369  
 370          $contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'mod_workshop', [
 371              \context_module::instance($this->workshop12->cmid)->id,
 372              \context_module::instance($this->workshop21->cmid)->id,
 373          ]);
 374  
 375          \mod_workshop\privacy\provider::delete_data_for_user($contextlist);
 376  
 377          $student1submissions = $DB->get_records('workshop_submissions', [
 378              'workshopid' => $this->workshop12->id,
 379              'authorid' => $this->student1->id,
 380          ]);
 381  
 382          $student2submissions = $DB->get_records('workshop_submissions', [
 383              'workshopid' => $this->workshop12->id,
 384              'authorid' => $this->student2->id,
 385          ]);
 386  
 387          $this->assertNotEmpty($student1submissions);
 388          $this->assertNotEmpty($student2submissions);
 389  
 390          foreach ($student1submissions as $submission) {
 391              $this->assertEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
 392          }
 393  
 394          foreach ($student2submissions as $submission) {
 395              $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title);
 396          }
 397  
 398          $student1assessments = $DB->get_records('workshop_assessments', [
 399              'submissionid' => $this->submission212,
 400              'reviewerid' => $this->student1->id,
 401          ]);
 402          $this->assertNotEmpty($student1assessments);
 403  
 404          foreach ($student1assessments as $assessment) {
 405              // In Moodle, feedback is seen to belong to the recipient user.
 406              $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
 407              $this->assertEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);
 408              // We delete what we can without affecting others' grades.
 409              $this->assertEquals(68, $assessment->grade);
 410          }
 411  
 412          $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($student1submissions));
 413          $this->assertNotEmpty($assessments);
 414  
 415          foreach ($assessments as $assessment) {
 416              if ($assessment->reviewerid == $this->student1->id) {
 417                  $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
 418                  $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);
 419  
 420              } else {
 421                  $this->assertEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor);
 422                  $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer);
 423              }
 424          }
 425      }
 426  
 427      /**
 428       * Test {@link \mod_workshop\privacy\provider::delete_data_for_users()} implementation.
 429       */
 430      public function test_delete_data_for_users() {
 431          global $DB;
 432  
 433          // Student1 has submissions in two workshops.
 434          $this->assertFalse($this->is_submission_erased($this->submission111));
 435          $this->assertFalse($this->is_submission_erased($this->submission121));
 436  
 437          // Student1 has self-assessed one their submission.
 438          $this->assertFalse($this->is_given_assessment_erased($this->assessment1111));
 439          $this->assertFalse($this->is_received_assessment_erased($this->assessment1111));
 440  
 441          // Student2 and student3 peer-assessed student1's submission.
 442          $this->assertFalse($this->is_given_assessment_erased($this->assessment1112));
 443          $this->assertFalse($this->is_given_assessment_erased($this->assessment1113));
 444  
 445          // Delete data owned by student1 and student3 in the workshop11.
 446  
 447          $context11 = \context_module::instance($this->workshop11->cmid);
 448  
 449          $approveduserlist = new \core_privacy\local\request\approved_userlist($context11, 'mod_workshop', [
 450              $this->student1->id,
 451              $this->student3->id,
 452          ]);
 453          \mod_workshop\privacy\provider::delete_data_for_users($approveduserlist);
 454  
 455          // Student1's submission is erased in workshop11 but not in the other workshop12.
 456          $this->assertTrue($this->is_submission_erased($this->submission111));
 457          $this->assertFalse($this->is_submission_erased($this->submission121));
 458  
 459          // Student1's self-assessment is erased.
 460          $this->assertTrue($this->is_given_assessment_erased($this->assessment1111));
 461          $this->assertTrue($this->is_received_assessment_erased($this->assessment1111));
 462  
 463          // Student1's received peer-assessments are also erased because they are "owned" by the recipient of the assessment.
 464          $this->assertTrue($this->is_received_assessment_erased($this->assessment1112));
 465          $this->assertTrue($this->is_received_assessment_erased($this->assessment1113));
 466  
 467          // Student2's owned data in the given assessment are not erased.
 468          $this->assertFalse($this->is_given_assessment_erased($this->assessment1112));
 469  
 470          // Student3's owned data in the given assessment were erased because she/he was in the userlist.
 471          $this->assertTrue($this->is_given_assessment_erased($this->assessment1113));
 472  
 473          // Personal data in other contexts are not affected.
 474          $this->assertFalse($this->is_submission_erased($this->submission121));
 475          $this->assertFalse($this->is_given_assessment_erased($this->assessment2121));
 476          $this->assertFalse($this->is_received_assessment_erased($this->assessment2121));
 477      }
 478  
 479      /**
 480       * Check if the given submission has the author's personal data erased.
 481       *
 482       * @param int $submissionid Identifier of the submission.
 483       * @return boolean
 484       */
 485      protected function is_submission_erased(int $submissionid) {
 486          global $DB;
 487  
 488          $submission = $DB->get_record('workshop_submissions', ['id' => $submissionid], 'id, title, content', MUST_EXIST);
 489  
 490          $titledeleted = $submission->title === get_string('privacy:request:delete:title', 'mod_workshop');
 491          $contentdeleted = $submission->content === get_string('privacy:request:delete:content', 'mod_workshop');
 492  
 493          if ($titledeleted && $contentdeleted) {
 494              return true;
 495  
 496          } else {
 497              return false;
 498          }
 499      }
 500  
 501      /**
 502       * Check is the received assessment has recipient's (author's) personal data erased.
 503       *
 504       * @param int $assessmentid Identifier of the assessment.
 505       * @return boolean
 506       */
 507      protected function is_received_assessment_erased(int $assessmentid) {
 508          global $DB;
 509  
 510          $assessment = $DB->get_record('workshop_assessments', ['id' => $assessmentid], 'id, feedbackauthor', MUST_EXIST);
 511  
 512          if ($assessment->feedbackauthor === get_string('privacy:request:delete:content', 'mod_workshop')) {
 513              return true;
 514  
 515          } else {
 516              return false;
 517          }
 518      }
 519  
 520      /**
 521       * Check is the given assessment has reviewer's personal data erased.
 522       *
 523       * @param int $assessmentid Identifier of the assessment.
 524       * @return boolean
 525       */
 526      protected function is_given_assessment_erased(int $assessmentid) {
 527          global $DB;
 528  
 529          $assessment = $DB->get_record('workshop_assessments', ['id' => $assessmentid], 'id, feedbackreviewer', MUST_EXIST);
 530  
 531          if ($assessment->feedbackreviewer === get_string('privacy:request:delete:content', 'mod_workshop')) {
 532              return true;
 533  
 534          } else {
 535              return false;
 536          }
 537      }
 538  }