<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Data provider tests.
 *
 * @package    core_grades
 * @category   test
 * @copyright  2018 Frédéric Massart
 * @author     Frédéric Massart <fred@branchup.tech>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();
global $CFG;

use core_privacy\tests\provider_testcase;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_grades\privacy\provider;

require_once($CFG->libdir . '/gradelib.php');

/**
 * Data provider testcase class.
 *
 * @package    core_grades
 * @category   test
 * @copyright  2018 Frédéric Massart
 * @author     Frédéric Massart <fred@branchup.tech>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class core_grades_privacy_testcase extends provider_testcase {

    public function setUp() {
        global $PAGE;
        $this->resetAfterTest();
        $PAGE->get_renderer('core');
    }

    public function test_get_contexts_for_userid_gradebook_edits() {
        $dg = $this->getDataGenerator();

        $c1 = $dg->create_course();
        $c2 = $dg->create_course();

        $u1 = $dg->create_user();
        $u2 = $dg->create_user();
        $u3 = $dg->create_user();
        $u4 = $dg->create_user();
        $u5 = $dg->create_user();
        $u6 = $dg->create_user();
        $u7 = $dg->create_user();
        $u8 = $dg->create_user();
        $u9 = $dg->create_user();
        $u10 = $dg->create_user();
        $u11 = $dg->create_user();

        $sysctx = context_system::instance();
        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);

        // Create some stuff.
        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gc1a = new grade_category($dg->create_grade_category(['courseid' => $c1->id]), false);
        $gc1b = new grade_category($dg->create_grade_category(['courseid' => $c1->id]), false);
        $gc2a = new grade_category($dg->create_grade_category(['courseid' => $c2->id]), false);
        $go2 = new grade_outcome($dg->create_grade_outcome(['courseid' => $c2->id, 'shortname' => 'go2',
            'fullname' => 'go2']), false);

        // Nothing as of now.
        foreach ([$u1, $u2, $u3, $u4, $u5, $u6, $u7, $u8, $u9, $u10, $u11] as $u) {
            $contexts = array_flip(provider::get_contexts_for_userid($u->id)->get_contextids());
            $this->assertEmpty($contexts);
        }

        $go0 = new grade_outcome(['shortname' => 'go0', 'fullname' => 'go0', 'usermodified' => $u1->id]);
        $go0->insert();
        $go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u11->id]);
        $go1->insert();

        // Create scales.
        $s1 = new grade_scale(['name' => 's1', 'scale' => 'a,b', 'userid' => $u7->id, 'courseid' => 0, 'description' => '']);
        $s1->insert();
        $s2 = new grade_scale(['name' => 's2', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c1->id, 'description' => '']);
        $s2->insert();

        // User 2 creates history.
        $this->setUser($u2);
        $go0->shortname .= ' edited';
        $go0->update();
        $gc1a->fullname .= ' edited';
        $gc1a->update();

        // User 3 creates history.
        $this->setUser($u3);
        $go1->shortname .= ' edited';
        $go1->update();
        $gc2a->fullname .= ' a';
        $gc2a->update();

        // User 4 updates an outcome in course (creates history).
        $this->setUser($u4);
        $go2->shortname .= ' edited';
        $go2->update();

        // User 5 updates an item.
        $this->setUser($u5);
        $gi1a->itemname .= ' edited';
        $gi1a->update();

        // User 6 creates history.
        $this->setUser($u6);
        $gi2a->delete();

        // User 9 creates history.
        $this->setUser($u9);
        $s1->name .= ' edited';
        $s1->update();

        // Assert contexts.
        $contexts = array_flip(provider::get_contexts_for_userid($u1->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($sysctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u11->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u2->id)->get_contextids());
        $this->assertCount(2, $contexts);
        $this->assertArrayHasKey($sysctx->id, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u3->id)->get_contextids());
        $this->assertCount(2, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $this->assertArrayHasKey($c2ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u4->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c2ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u5->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u6->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c2ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u7->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($sysctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u8->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u9->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($sysctx->id, $contexts);

        // User 10 creates history.
        $this->setUser($u10);
        $s2->delete();

        $contexts = array_flip(provider::get_contexts_for_userid($u10->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
    }

    public function test_get_contexts_for_userid_grades_and_history() {
        $dg = $this->getDataGenerator();

        $c1 = $dg->create_course();
        $c2 = $dg->create_course();

        $u1 = $dg->create_user();
        $u2 = $dg->create_user();
        $u3 = $dg->create_user();
        $u4 = $dg->create_user();
        $u5 = $dg->create_user();
        $u6 = $dg->create_user();

        $sysctx = context_system::instance();
        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);

        // Create some stuff.
        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);

        // Nothing as of now.
        foreach ([$u1, $u2, $u3, $u4, $u5, $u6] as $u) {
            $contexts = array_flip(provider::get_contexts_for_userid($u->id)->get_contextids());
            $this->assertEmpty($contexts);
        }

        // User 1 is graded in course 1.
        $gi1a->update_final_grade($u1->id, 1, 'test');

        // User 2 is graded in course 2.
        $gi2a->update_final_grade($u2->id, 10, 'test');

        // User 3 is set as modifier.
        $gi1a->update_final_grade($u1->id, 1, 'test', '', FORMAT_MOODLE, $u3->id);

        // User 4 is set as modifier, and creates history..
        $this->setUser($u4);
        $gi1a->update_final_grade($u2->id, 1, 'test');

        // User 5 creates history, user 6 is the known modifier, and we delete the item.
        $this->setUser($u5);
        $gi2b->update_final_grade($u2->id, 1, 'test', '', FORMAT_PLAIN, $u6->id);
        $gi2b->delete();

        // Assert contexts.
        $contexts = array_flip(provider::get_contexts_for_userid($u1->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u2->id)->get_contextids());
        $this->assertCount(3, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $this->assertArrayHasKey($c2ctx->id, $contexts);
        $this->assertArrayHasKey(context_user::instance($u2->id)->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u3->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u4->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey($c1ctx->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u5->id)->get_contextids());
        $this->assertCount(2, $contexts);
        $this->assertArrayHasKey($c2ctx->id, $contexts);
        $this->assertArrayHasKey(context_user::instance($u2->id)->id, $contexts);
        $contexts = array_flip(provider::get_contexts_for_userid($u6->id)->get_contextids());
        $this->assertCount(1, $contexts);
        $this->assertArrayHasKey(context_user::instance($u2->id)->id, $contexts);
    }

    /**
     * Test that the appropriate user IDs are returned for a given context.
     */
    public function test_get_users_in_context_gradebook_edits() {
        $dg = $this->getDataGenerator();

        $c1 = $dg->create_course();
        $c2 = $dg->create_course();

        $u1 = $dg->create_user();
        $u2 = $dg->create_user();
        $u3 = $dg->create_user();
        $u4 = $dg->create_user();
        $u5 = $dg->create_user();
        $u6 = $dg->create_user();
        $u7 = $dg->create_user();
        $u8 = $dg->create_user();
        $u9 = $dg->create_user();
        $u10 = $dg->create_user();
        $u11 = $dg->create_user();

        $sysctx = context_system::instance();
        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);

        // Create some stuff.
        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gc1a = new grade_category($dg->create_grade_category(['courseid' => $c1->id]), false);
        $gc1b = new grade_category($dg->create_grade_category(['courseid' => $c1->id]), false);
        $gc2a = new grade_category($dg->create_grade_category(['courseid' => $c2->id]), false);
        $go2 = new grade_outcome($dg->create_grade_outcome(['courseid' => $c2->id, 'shortname' => 'go2',
            'fullname' => 'go2']), false);

        $go0 = new grade_outcome(['shortname' => 'go0', 'fullname' => 'go0', 'usermodified' => $u1->id]);
        $go0->insert();
        $go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u11->id]);
        $go1->insert();

        // Create scales.
        $s1 = new grade_scale(['name' => 's1', 'scale' => 'a,b', 'userid' => $u7->id, 'courseid' => 0, 'description' => '']);
        $s1->insert();
        $s2 = new grade_scale(['name' => 's2', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c1->id, 'description' => '']);
        $s2->insert();

        // User 2 creates history.
        $this->setUser($u2);
        $go0->shortname .= ' edited';
        $go0->update();
        $gc1a->fullname .= ' edited';
        $gc1a->update();

        // User 3 creates history.
        $this->setUser($u3);
        $go1->shortname .= ' edited';
        $go1->update();
        $gc2a->fullname .= ' a';
        $gc2a->update();

        // User 4 updates an outcome in course (creates history).
        $this->setUser($u4);
        $go2->shortname .= ' edited';
        $go2->update();

        // User 5 updates an item.
        $this->setUser($u5);
        $gi1a->itemname .= ' edited';
        $gi1a->update();

        // User 6 creates history.
        $this->setUser($u6);
        $gi2a->delete();

        // User 9 creates history.
        $this->setUser($u9);
        $s1->name .= ' edited';
        $s1->update();

        $userlist = new \core_privacy\local\request\userlist($sysctx, 'core_grades');
        provider::get_users_in_context($userlist);
        $systemcontextuserids = [$u1->id, $u2->id, $u7->id, $u9->id];
        $this->assertEquals($systemcontextuserids, $userlist->get_userids());

        $userlist = new \core_privacy\local\request\userlist($c1ctx, 'core_grades');
        provider::get_users_in_context($userlist);
        $course1userids = [$u11->id, $u3->id, $u8->id, $u5->id, $u2->id];
        $this->assertEquals($course1userids, $userlist->get_userids());

        $userlist = new \core_privacy\local\request\userlist($c2ctx, 'core_grades');
        provider::get_users_in_context($userlist);
        $course2userids = [$u4->id, $u6->id, $u3->id];
        $this->assertEquals($course2userids, $userlist->get_userids());
    }

    /**
     * Test that the appropriate user IDs are returned for a given context.
     */
    public function test_get_users_in_context_grades_and_history() {
        $dg = $this->getDataGenerator();

        $c1 = $dg->create_course();
        $c2 = $dg->create_course();

        $u1 = $dg->create_user();
        $u2 = $dg->create_user();
        $u3 = $dg->create_user();
        $u4 = $dg->create_user();
        $u5 = $dg->create_user();
        $u6 = $dg->create_user();

        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);
        $u2ctx = context_user::instance($u2->id);

        // Create some stuff.
        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);

        // User 1 is graded in course 1.
        $gi1a->update_final_grade($u1->id, 1, 'test');

        // User 2 is graded in course 2.
        $gi2a->update_final_grade($u2->id, 10, 'test');

        // User 3 is set as modifier.
        $gi1a->update_final_grade($u1->id, 1, 'test', '', FORMAT_MOODLE, $u3->id);

        // User 4 is set as modifier, and creates history..
        $this->setUser($u4);
        $gi1a->update_final_grade($u2->id, 1, 'test');

        // User 5 creates history, user 6 is the known modifier, and we delete the item.
        $this->setUser($u5);
        $gi2b->update_final_grade($u2->id, 1, 'test', '', FORMAT_PLAIN, $u6->id);
        $gi2b->delete();

        $userlist = new \core_privacy\local\request\userlist($c1ctx, 'core_grades');
        provider::get_users_in_context($userlist);
        $course1userids = [$u1->id, $u2->id, $u3->id, $u4->id];
        $this->assertEquals($course1userids, $userlist->get_userids(), '', 0.0, 10, true);

        $userlist = new \core_privacy\local\request\userlist($c2ctx, 'core_grades');
        provider::get_users_in_context($userlist);
        $course2userids = [$u5->id, $u2->id];
        $this->assertEquals($course2userids, $userlist->get_userids(), '', 0.0, 10, true);

        $userlist = new \core_privacy\local\request\userlist($u2ctx, 'core_grades');
        provider::get_users_in_context($userlist);
        $this->assertEquals([$u2->id], $userlist->get_userids());
    }

    public function test_delete_data_for_all_users_in_context() {
        global $DB;
        $dg = $this->getDataGenerator();

        $c1 = $dg->create_course();
        $c2 = $dg->create_course();
        $u1 = $dg->create_user();
        $u2 = $dg->create_user();
        $u1ctx = context_user::instance($u1->id);
        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);

        // Create some stuff.
        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);

        $gi1a->update_final_grade($u1->id, 1, 'test');
        $gi1a->update_final_grade($u2->id, 1, 'test');
        $gi1b->update_final_grade($u1->id, 1, 'test');
        $gi2a->update_final_grade($u1->id, 1, 'test');
        $gi2a->update_final_grade($u2->id, 1, 'test');
        $gi2b->update_final_grade($u1->id, 1, 'test');
        $gi2b->update_final_grade($u2->id, 1, 'test');
        $gi2b->delete();

        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));

        provider::delete_data_for_all_users_in_context($c1ctx);
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));

        provider::delete_data_for_all_users_in_context($u1ctx);
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertFalse($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));

        provider::delete_data_for_all_users_in_context($c2ctx);
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
    }

    public function test_delete_data_for_user() {
        global $DB;
        $dg = $this->getDataGenerator();

        $c1 = $dg->create_course();
        $c2 = $dg->create_course();
        $u1 = $dg->create_user();
        $u2 = $dg->create_user();
        $u1ctx = context_user::instance($u1->id);
        $u2ctx = context_user::instance($u2->id);
        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);

        // Create some stuff.
        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);

        $gi1a->update_final_grade($u1->id, 1, 'test');
        $gi1a->update_final_grade($u2->id, 1, 'test');
        $gi1b->update_final_grade($u1->id, 1, 'test');
        $gi2a->update_final_grade($u1->id, 1, 'test');
        $gi2a->update_final_grade($u2->id, 1, 'test');
        $gi2b->update_final_grade($u1->id, 1, 'test');
        $gi2b->update_final_grade($u2->id, 1, 'test');
        $gi2b->delete();

        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));

        provider::delete_data_for_user(new approved_contextlist($u1, 'core_grades', [$c1ctx->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));

        provider::delete_data_for_user(new approved_contextlist($u1, 'core_grades', [$u1ctx->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertFalse($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));

        provider::delete_data_for_user(new approved_contextlist($u1, 'core_grades', [$u2ctx->id, $c2ctx->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertFalse($DB->record_exists('grade_grades_history', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
        $this->assertTrue($DB->record_exists('grade_grades_history', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
    }

    /**
     * Test deleting multiple users for a context works.
     */
    public function test_delete_data_for_users() {
        global $DB;
        $dg = $this->getDataGenerator();

        $c1 = $dg->create_course();
        $c2 = $dg->create_course();
        $u1 = $dg->create_user();
        $u2 = $dg->create_user();
        $u3 = $dg->create_user();
        $u4 = $dg->create_user();
        $u1ctx = context_user::instance($u1->id);
        $u2ctx = context_user::instance($u2->id);
        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);

        // Create some stuff.
        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);

        $gi1a->update_final_grade($u1->id, 1, 'test');
        $gi1a->update_final_grade($u2->id, 1, 'test');
        $gi1a->update_final_grade($u3->id, 1, 'test');
        $gi1b->update_final_grade($u1->id, 1, 'test');
        $gi1b->update_final_grade($u4->id, 1, 'test');
        $gi2a->update_final_grade($u1->id, 1, 'test');
        $gi2a->update_final_grade($u2->id, 1, 'test');
        $gi2a->update_final_grade($u4->id, 1, 'test');
        $gi2b->update_final_grade($u1->id, 1, 'test');
        $gi2b->update_final_grade($u2->id, 1, 'test');
        $gi2b->update_final_grade($u3->id, 1, 'test');
        $gi2b->delete();

        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u3->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi2a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2b->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2b->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u3->id, 'itemid' => $gi2b->id]));

        $userlist = new \core_privacy\local\request\approved_userlist($c1ctx, 'core_grades', [$u1->id, $u2->id]);
        provider::delete_data_for_users($userlist);

        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u3->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi2a->id]));

        $userlist = new \core_privacy\local\request\approved_userlist($c2ctx, 'core_grades', [$u2->id, $u4->id]);
        provider::delete_data_for_users($userlist);

        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi1a->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u3->id, 'itemid' => $gi1a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi1b->id]));
        $this->assertTrue($DB->record_exists('grade_grades', ['userid' => $u1->id, 'itemid' => $gi2a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u2->id, 'itemid' => $gi2a->id]));
        $this->assertFalse($DB->record_exists('grade_grades', ['userid' => $u4->id, 'itemid' => $gi2a->id]));
    }

    public function test_export_data_for_user_about_grades_and_history() {
        global $DB;
        $dg = $this->getDataGenerator();

        $c1 = $dg->create_course();
        $c2 = $dg->create_course();

        // Users being graded.
        $ug1 = $dg->create_user();
        $ug2 = $dg->create_user();
        $ug3 = $dg->create_user();
        // Users performing actions.
        $ua1 = $dg->create_user();
        $ua2 = $dg->create_user();
        $ua3 = $dg->create_user();

        $ug1ctx = context_user::instance($ug1->id);
        $ug2ctx = context_user::instance($ug2->id);
        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);

        $rootpath = [get_string('grades', 'core_grades')];
        $relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);

        // Create the course minimal stuff.
        grade_category::fetch_course_category($c1->id);
        $ci1 = grade_item::fetch_course_item($c1->id);
        grade_category::fetch_course_category($c2->id);
        $ci2 = grade_item::fetch_course_item($c2->id);

        // Create data that will sit in the user context because we will delete the grate item.
        $gi1 = new grade_item($dg->create_grade_item(['courseid' => $c1->id, 'aggregationcoef2' => 1]), false);
        $gi1->update_final_grade($ug1->id, 100, 'test', 'Well done!', FORMAT_PLAIN, $ua2->id);
        $gi1->update_final_grade($ug1->id, 1, 'test', 'Hi', FORMAT_PLAIN, $ua2->id);
        $gi1->update_final_grade($ug3->id, 12, 'test', 'Hello', FORMAT_PLAIN, $ua2->id);

        // Create another set for another user.
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gi2a->update_final_grade($ug1->id, 15, 'test', '', FORMAT_PLAIN, $ua2->id);
        $gi2b = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gi2b->update_final_grade($ug1->id, 30, 'test', 'Well played!', FORMAT_PLAIN, $ua2->id);

        // Export action user 1 everywhere.
        provider::export_user_data(new approved_contextlist($ua1, 'core_grades', [$ug1ctx->id, $ug2ctx->id,
            $c1ctx->id, $c2ctx->id]));
        $this->assert_context_has_no_data($ug1ctx);
        $this->assert_context_has_no_data($ug2ctx);
        $this->assert_context_has_no_data($c1ctx);
        $this->assert_context_has_no_data($c2ctx);

        // Export action user 2 in course 1.
        writer::reset();
        provider::export_user_data(new approved_contextlist($ua2, 'core_grades', [$c1ctx->id]));
        $this->assert_context_has_no_data($ug1ctx);
        $this->assert_context_has_no_data($ug2ctx);
        $this->assert_context_has_no_data($c2ctx);
        $data = writer::with_context($c1ctx)->get_data($rootpath);
        $this->assertEmpty($data);

        // Here we are testing the export of grades that we've changed.
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'grades');
        $this->assertCount(2, $data->grades);
        $this->assertEquals($gi1->get_name(), $data->grades[0]['item']);
        $this->assertEquals(1, $data->grades[0]['grade']);
        $this->assertEquals('Hi', $data->grades[0]['feedback']);
        $this->assertEquals(transform::yesno(true), $data->grades[0]['created_or_modified_by_you']);
        $this->assertEquals($gi1->get_name(), $data->grades[1]['item']);
        $this->assertEquals(12, $data->grades[1]['grade']);
        $this->assertEquals('Hello', $data->grades[1]['feedback']);
        $this->assertEquals(transform::yesno(true), $data->grades[1]['created_or_modified_by_you']);

        // Here we are testing the export of history of grades that we've changed.
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'grades_history');
        $this->assertCount(3, $data->modified_records);
        $grade = $data->modified_records[0];
        $this->assertEquals($ug1->id, $grade['userid']);
        $this->assertEquals($gi1->get_name(), $grade['item']);
        $this->assertEquals(100, $grade['grade']);
        $this->assertEquals('Well done!', $grade['feedback']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(true), $grade['author_of_change_was_you']);
        $grade = $data->modified_records[1];
        $this->assertEquals($ug1->id, $grade['userid']);
        $this->assertEquals($gi1->get_name(), $grade['item']);
        $this->assertEquals(1, $grade['grade']);
        $this->assertEquals('Hi', $grade['feedback']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(true), $grade['author_of_change_was_you']);
        $grade = $data->modified_records[2];
        $this->assertEquals($ug3->id, $grade['userid']);
        $this->assertEquals($gi1->get_name(), $grade['item']);
        $this->assertEquals(12, $grade['grade']);
        $this->assertEquals('Hello', $grade['feedback']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(true), $grade['author_of_change_was_you']);

        // Create a history record with logged user.
        $this->setUser($ua3);
        $gi1->update_final_grade($ug3->id, 50, 'test', '...', FORMAT_PLAIN, $ua2->id);
        writer::reset();
        provider::export_user_data(new approved_contextlist($ua3, 'core_grades', [$c1ctx->id]));
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'grades_history');
        $this->assertCount(1, $data->modified_records);
        $grade = $data->modified_records[0];
        $this->assertEquals($ug3->id, $grade['userid']);
        $this->assertEquals($gi1->get_name(), $grade['item']);
        $this->assertEquals(50, $grade['grade']);
        $this->assertEquals('...', $grade['feedback']);
        $this->assertEquals(transform::yesno(true), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(false), $grade['author_of_change_was_you']);

        // Test that we export our own grades.
        writer::reset();
        provider::export_user_data(new approved_contextlist($ug1, 'core_grades', [$c1ctx->id]));
        $data = writer::with_context($c1ctx)->get_data($rootpath);
        $this->assert_context_has_no_data($c2ctx);
        $this->assertCount(2, $data->grades);
        $grade = $data->grades[0];
        $this->assertEquals($ci1->get_name(), $grade['item']);
        $this->assertEquals(1, $grade['grade']);
        $grade = $data->grades[1];
        $this->assertEquals($gi1->get_name(), $grade['item']);
        $this->assertEquals(1, $grade['grade']);
        $this->assertEquals('Hi', $grade['feedback']);

        // Test that we export our own grades in two courses.
        writer::reset();
        provider::export_user_data(new approved_contextlist($ug1, 'core_grades', [$ug1ctx->id, $c1ctx->id, $c2ctx->id]));
        $this->assert_context_has_no_data($ug1ctx);
        $data = writer::with_context($c1ctx)->get_data($rootpath);
        $this->assertCount(2, $data->grades);
        $grade = $data->grades[0];
        $this->assertEquals($ci1->get_name(), $grade['item']);
        $this->assertEquals(1, $grade['grade']);
        $grade = $data->grades[1];
        $this->assertEquals($gi1->get_name(), $grade['item']);
        $this->assertEquals(1, $grade['grade']);
        $this->assertEquals('Hi', $grade['feedback']);

        $data = writer::with_context($c2ctx)->get_data($rootpath);
        $this->assertCount(3, $data->grades);
        $grade = $data->grades[0];
        $this->assertEquals($ci2->get_name(), $grade['item']);
        $grade = $data->grades[1];
        $this->assertEquals($gi2a->get_name(), $grade['item']);
        $this->assertEquals(15, $grade['grade']);
        $this->assertEquals('', $grade['feedback']);
        $grade = $data->grades[2];
        $this->assertEquals($gi2b->get_name(), $grade['item']);
        $this->assertEquals(30, $grade['grade']);
        $this->assertEquals('Well played!', $grade['feedback']);

        // Delete a grade item.
        $this->setUser($ua3);
        $gi1->delete();

        // Now, we should find history of grades in our own context.
        writer::reset();
        provider::export_user_data(new approved_contextlist($ug1, 'core_grades', [$ug1ctx->id, $c1ctx->id, $c2ctx->id]));
        $data = writer::with_context($c1ctx)->get_data($rootpath);
        $this->assertCount(1, $data->grades);
        $this->assertEquals($ci1->get_name(), $data->grades[0]['item']);
        $data = writer::with_context($c2ctx)->get_data($rootpath);
        $this->assertCount(3, $data->grades);
        $data = writer::with_context($ug1ctx)->get_related_data($rootpath, 'history');
        $this->assertCount(3, $data->grades);
        $grade = $data->grades[0];
        $this->assertEquals(get_string('privacy:request:unknowndeletedgradeitem', 'core_grades'), $grade['name']);
        $this->assertEquals(100, $grade['grade']);
        $this->assertEquals('Well done!', $grade['feedback']);
        $this->assertEquals(transform::yesno(true), $grade['graded_user_was_you']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(false), $grade['author_of_change_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactioninsert', 'core_grades'), $grade['action']);
        $grade = $data->grades[1];
        $this->assertEquals(get_string('privacy:request:unknowndeletedgradeitem', 'core_grades'), $grade['name']);
        $this->assertEquals(1, $grade['grade']);
        $this->assertEquals('Hi', $grade['feedback']);
        $this->assertEquals(transform::yesno(true), $grade['graded_user_was_you']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(false), $grade['author_of_change_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactionupdate', 'core_grades'), $grade['action']);
        $grade = $data->grades[2];
        $this->assertEquals(get_string('privacy:request:unknowndeletedgradeitem', 'core_grades'), $grade['name']);
        $this->assertEquals(1, $grade['grade']);
        $this->assertEquals('Hi', $grade['feedback']);
        $this->assertEquals(transform::yesno(true), $grade['graded_user_was_you']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(false), $grade['author_of_change_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'), $grade['action']);

        // The action user 3 should have a record of the deletion in the user's context.
        writer::reset();
        provider::export_user_data(new approved_contextlist($ua3, 'core_grades', [$ug1ctx->id]));
        $data = writer::with_context($ug1ctx)->get_related_data($rootpath, 'history');
        $this->assertCount(1, $data->grades);
        $grade = $data->grades[0];
        $this->assertEquals(get_string('privacy:request:unknowndeletedgradeitem', 'core_grades'), $grade['name']);
        $this->assertEquals(1, $grade['grade']);
        $this->assertEquals('Hi', $grade['feedback']);
        $this->assertEquals(transform::yesno(true), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(false), $grade['author_of_change_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'), $grade['action']);

        // The action user 2 should have a record of their edits in the user's context.
        writer::reset();
        provider::export_user_data(new approved_contextlist($ua2, 'core_grades', [$ug1ctx->id]));
        $data = writer::with_context($ug1ctx)->get_related_data($rootpath, 'history');
        $this->assertCount(3, $data->grades);
        $grade = $data->grades[0];
        $this->assertEquals(get_string('privacy:request:unknowndeletedgradeitem', 'core_grades'), $grade['name']);
        $this->assertEquals(100, $grade['grade']);
        $this->assertEquals('Well done!', $grade['feedback']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(true), $grade['author_of_change_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactioninsert', 'core_grades'), $grade['action']);
        $grade = $data->grades[1];
        $this->assertEquals(get_string('privacy:request:unknowndeletedgradeitem', 'core_grades'), $grade['name']);
        $this->assertEquals(1, $grade['grade']);
        $this->assertEquals('Hi', $grade['feedback']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(true), $grade['author_of_change_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactionupdate', 'core_grades'), $grade['action']);
        $grade = $data->grades[2];
        $this->assertEquals(get_string('privacy:request:unknowndeletedgradeitem', 'core_grades'), $grade['name']);
        $this->assertEquals(1, $grade['grade']);
        $this->assertEquals('Hi', $grade['feedback']);
        $this->assertEquals(transform::yesno(false), $grade['logged_in_user_was_you']);
        $this->assertEquals(transform::yesno(true), $grade['author_of_change_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'), $grade['action']);
    }

    public function test_export_data_for_user_with_scale() {
        global $DB;
        $dg = $this->getDataGenerator();
        $c1 = $dg->create_course();
        $scale = $dg->create_scale(['scale' => 'Awesome,OK,Reasonable,Bad']);
        $u1 = $dg->create_user();
        $u2 = $dg->create_user();

        $u1ctx = context_user::instance($u1->id);
        $c1ctx = context_course::instance($c1->id);

        $rootpath = [get_string('grades', 'core_grades')];

        // Create another set for another user.
        $gi1 = new grade_item($dg->create_grade_item(['courseid' => $c1->id, 'scaleid' => $scale->id]), false);
        $gi1->update_final_grade($u1->id, 1, 'test', '', FORMAT_PLAIN, $u2->id);
        $gi2 = new grade_item($dg->create_grade_item(['courseid' => $c1->id, 'scaleid' => $scale->id]), false);
        $gi2->update_final_grade($u1->id, 3, 'test', '', FORMAT_PLAIN, $u2->id);

        // Export user's data.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u1, 'core_grades', [$c1ctx->id]));
        $data = writer::with_context($c1ctx)->get_data($rootpath);
        $this->assertCount(3, $data->grades);
        $this->assertEquals(grade_item::fetch_course_item($c1->id)->get_name(), $data->grades[0]['item']);
        $this->assertEquals($gi1->get_name(), $data->grades[1]['item']);
        $this->assertEquals(1, $data->grades[1]['grade']);
        $this->assertEquals('Awesome', $data->grades[1]['grade_formatted']);
        $this->assertEquals($gi2->get_name(), $data->grades[2]['item']);
        $this->assertEquals(3, $data->grades[2]['grade']);
        $this->assertEquals('Reasonable', $data->grades[2]['grade_formatted']);
    }

    public function test_export_data_for_user_about_gradebook_edits() {
        global $DB;
        $dg = $this->getDataGenerator();
        $c1 = $dg->create_course();
        $c2 = $dg->create_course();
        $u1 = $dg->create_user();
        $u2 = $dg->create_user();
        $u3 = $dg->create_user();
        $u4 = $dg->create_user();
        $u5 = $dg->create_user();
        $u6 = $dg->create_user();
        $u7 = $dg->create_user();
        $u8 = $dg->create_user();
        $u9 = $dg->create_user();
        $u10 = $dg->create_user();

        $sysctx = context_system::instance();
        $u1ctx = context_user::instance($u1->id);
        $u2ctx = context_user::instance($u2->id);
        $u3ctx = context_user::instance($u3->id);
        $u4ctx = context_user::instance($u4->id);
        $u5ctx = context_user::instance($u5->id);
        $u6ctx = context_user::instance($u6->id);
        $c1ctx = context_course::instance($c1->id);
        $c2ctx = context_course::instance($c2->id);

        $rootpath = [get_string('grades', 'core_grades')];
        $relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);
        $allcontexts = [$sysctx->id, $c1ctx->id, $c2ctx->id, $u1ctx->id, $u2ctx->id, $u3ctx->id, $u4ctx->id,
            $u5ctx->id, $u6ctx->id];
        $updateactionstr = get_string('privacy:request:historyactionupdate', 'core_grades');

        // Create some stuff.
        $gi1a = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi1b = new grade_item($dg->create_grade_item(['courseid' => $c1->id]), false);
        $gi2a = new grade_item($dg->create_grade_item(['courseid' => $c2->id]), false);
        $gc1a = new grade_category($dg->create_grade_category(['courseid' => $c1->id]), false);
        $gc1b = new grade_category($dg->create_grade_category(['courseid' => $c1->id]), false);
        $gc2a = new grade_category($dg->create_grade_category(['courseid' => $c2->id]), false);
        $go2 = new grade_outcome($dg->create_grade_outcome(['courseid' => $c2->id, 'shortname' => 'go2',
            'fullname' => 'go2']), false);

        $go0 = new grade_outcome(['shortname' => 'go0', 'fullname' => 'go0', 'usermodified' => $u1->id]);
        $go0->insert();
        $go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u1->id]);
        $go1->insert();

        // Create scales.
        $s1 = new grade_scale(['name' => 's1', 'scale' => 'a,b', 'userid' => $u7->id, 'courseid' => 0, 'description' => '']);
        $s1->insert();
        $s2 = new grade_scale(['name' => 's2', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c1->id, 'description' => '']);
        $s2->insert();
        $s3 = new grade_scale(['name' => 's3', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c2->id, 'description' => '']);
        $s3->insert();

        // User 2 creates history.
        $this->setUser($u2);
        $go0->shortname .= ' edited';
        $go0->update();
        $gc1a->fullname .= ' edited';
        $gc1a->update();

        // User 3 creates history.
        $this->setUser($u3);
        $go1->shortname .= ' edited';
        $go1->update();
        $gc2a->fullname .= ' a';
        $gc2a->update();

        // User 4 updates an outcome in course (creates history).
        $this->setUser($u4);
        $go2->shortname .= ' edited';
        $go2->update();

        // User 5 updates an item.
        $this->setUser($u5);
        $gi1a->itemname .= ' edited';
        $gi1a->update();

        // User 6 creates history.
        $this->setUser($u6);
        $gi2a->delete();

        // User 9 creates history.
        $this->setUser($u9);
        $s1->name .= ' edited';
        $s1->update();

        // User 10 creates history.
        $this->setUser($u10);
        $s3->delete();

        $this->setAdminUser();

        // Export data for u1.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u1, 'core_grades', $allcontexts));
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'outcomes');
        $this->assertCount(1, $data->outcomes);
        $this->assertEquals($go0->shortname, $data->outcomes[0]['shortname']);
        $this->assertEquals($go0->fullname, $data->outcomes[0]['fullname']);
        $this->assertEquals(transform::yesno(true), $data->outcomes[0]['created_or_modified_by_you']);
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'outcomes');
        $this->assertCount(1, $data->outcomes);
        $this->assertEquals($go1->shortname, $data->outcomes[0]['shortname']);
        $this->assertEquals($go1->fullname, $data->outcomes[0]['fullname']);
        $this->assertEquals(transform::yesno(true), $data->outcomes[0]['created_or_modified_by_you']);
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'outcomes_history');
        $this->assertEmpty($data);
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'outcomes_history');
        $this->assertEmpty($data);

        // Export data for u2.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u2, 'core_grades', $allcontexts));
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'outcomes');
        $this->assertEmpty($data);
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'outcomes');
        $this->assertEmpty($data);
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'outcomes_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($go0->shortname, $data->modified_records[0]['shortname']);
        $this->assertEquals($go0->fullname, $data->modified_records[0]['fullname']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']);
        $this->assertEquals($updateactionstr, $data->modified_records[0]['action']);

        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'categories_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($gc1a->fullname, $data->modified_records[0]['name']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']);
        $this->assertEquals($updateactionstr, $data->modified_records[0]['action']);

        // Export data for u3.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u3, 'core_grades', $allcontexts));
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'outcomes_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($go1->shortname, $data->modified_records[0]['shortname']);
        $this->assertEquals($go1->fullname, $data->modified_records[0]['fullname']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']);
        $this->assertEquals($updateactionstr, $data->modified_records[0]['action']);

        $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'categories_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($gc2a->fullname, $data->modified_records[0]['name']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']);
        $this->assertEquals($updateactionstr, $data->modified_records[0]['action']);

        // Export data for u4.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u4, 'core_grades', $allcontexts));
        $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'outcomes_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($go2->shortname, $data->modified_records[0]['shortname']);
        $this->assertEquals($go2->fullname, $data->modified_records[0]['fullname']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']);
        $this->assertEquals($updateactionstr, $data->modified_records[0]['action']);

        // Export data for u5.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u5, 'core_grades', $allcontexts));
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'items_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($gi1a->itemname, $data->modified_records[0]['name']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']);
        $this->assertEquals($updateactionstr, $data->modified_records[0]['action']);

        // Export data for u6.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u6, 'core_grades', $allcontexts));
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'items_history');
        $this->assertEmpty($data);
        $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'items_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($gi2a->itemname, $data->modified_records[0]['name']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'),
            $data->modified_records[0]['action']);

        // Export data for u7.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u7, 'core_grades', $allcontexts));
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertEmpty($data);
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertCount(1, $data->scales);
        $this->assertEquals($s1->name, $data->scales[0]['name']);
        $this->assertEquals(transform::yesno(true), $data->scales[0]['created_or_modified_by_you']);

        // Export data for u8.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u8, 'core_grades', $allcontexts));
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertEmpty($data);
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertCount(1, $data->scales);
        $this->assertEquals($s2->name, $data->scales[0]['name']);
        $this->assertEquals(transform::yesno(true), $data->scales[0]['created_or_modified_by_you']);
        $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales_history');
        $this->assertCount(2, $data->modified_records);
        $this->assertEquals($s3->name, $data->modified_records[0]['name']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_change_was_you']);
        $this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_action_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactioninsert', 'core_grades'),
            $data->modified_records[0]['action']);
        $this->assertEquals($s3->name, $data->modified_records[1]['name']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[1]['author_of_change_was_you']);
        $this->assertEquals(transform::yesno(false), $data->modified_records[1]['author_of_action_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'),
            $data->modified_records[1]['action']);

        // Export data for u9.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u9, 'core_grades', $allcontexts));
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertEmpty($data);
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertEmpty($data);
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($s1->name, $data->modified_records[0]['name']);
        $this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_change_was_you']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_action_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactionupdate', 'core_grades'),
            $data->modified_records[0]['action']);

        // Export data for u10.
        writer::reset();
        provider::export_user_data(new approved_contextlist($u10, 'core_grades', $allcontexts));
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertEmpty($data);
        $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertEmpty($data);
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales');
        $this->assertEmpty($data);
        $data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales_history');
        $this->assertEmpty($data);
        $data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales_history');
        $this->assertEmpty($data);
        $data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales_history');
        $this->assertCount(1, $data->modified_records);
        $this->assertEquals($s3->name, $data->modified_records[0]['name']);
        $this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_change_was_you']);
        $this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_action_was_you']);
        $this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'),
            $data->modified_records[0]['action']);
    }

    /**
     * Assert there is no grade data in the context.
     *
     * @param context $context The context.
     * @return void
     */
    protected function assert_context_has_no_data(context $context) {
        $rootpath = [get_string('grades', 'core_grades')];
        $relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);

        $data = writer::with_context($context)->get_data($rootpath);
        $this->assertEmpty($data);

        $data = writer::with_context($context)->get_related_data($rootpath, 'history');
        $this->assertEmpty($data);

        $files = ['categories_history', 'items_history', 'outcomes', 'outcomes_history', 'grades', 'grades_history', 'history'];
        foreach ($files as $file) {
            $data = writer::with_context($context)->get_related_data($relatedtomepath, $file);
            $this->assertEmpty($data);
        }
    }
}
