Merge branch 'wip-MDL-61937-master' of git://github.com/marinaglancy/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Fri, 4 May 2018 03:29:24 +0000 (11:29 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Fri, 4 May 2018 03:29:24 +0000 (11:29 +0800)
competency/classes/external/performance_helper.php
competency/classes/privacy/provider.php [new file with mode: 0644]
competency/tests/privacy_test.php [new file with mode: 0644]
lang/en/competency.php

index cfe04c6..6d39f74 100644 (file)
@@ -26,6 +26,7 @@ namespace core_competency\external;
 defined('MOODLE_INTERNAL') || die();
 
 use core_competency\competency;
+use core_competency\competency_framework;
 
 /**
  * Performance helper class.
@@ -108,4 +109,15 @@ class performance_helper {
         return $this->scales[$scaleid];
     }
 
+    /**
+     * Ingest a framework to avoid additional fetching.
+     *
+     * @param competency_framework $framework The framework.
+     * @return void
+     */
+    public function ingest_framework(competency_framework $framework) {
+        $id = $framework->get('id');
+        $this->frameworks[$id] = $framework;
+    }
+
 }
diff --git a/competency/classes/privacy/provider.php b/competency/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..1fe4e89
--- /dev/null
@@ -0,0 +1,1981 @@
+<?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.
+ *
+ * @package    core_competency
+ * @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
+ */
+
+namespace core_competency\privacy;
+defined('MOODLE_INTERNAL') || die();
+
+use context;
+use context_course;
+use context_helper;
+use context_module;
+use context_system;
+use context_user;
+use moodle_recordset;
+use core_competency\api;
+use core_competency\competency;
+use core_competency\competency_framework;
+use core_competency\course_competency;
+use core_competency\course_competency_settings;
+use core_competency\course_module_competency;
+use core_competency\evidence;
+use core_competency\plan;
+use core_competency\plan_competency;
+use core_competency\related_competency;
+use core_competency\template;
+use core_competency\template_cohort;
+use core_competency\template_competency;
+use core_competency\user_competency;
+use core_competency\user_competency_course;
+use core_competency\user_competency_plan;
+use core_competency\user_evidence;
+use core_competency\user_evidence_competency;
+use core_competency\external\performance_helper;
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\contextlist;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\transform;
+use core_privacy\local\request\writer;
+
+/**
+ * Data provider class.
+ *
+ * @package    core_competency
+ * @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 provider implements
+    \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\subsystem\provider {
+
+    /**
+     * Returns metadata.
+     *
+     * @param collection $collection The initialised collection to add items to.
+     * @return collection A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $collection) : collection {
+
+        // Tables not related to users aside from the editing information.
+        $collection->add_database_table('competency', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency');
+
+        $collection->add_database_table('competency_coursecompsetting', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_coursecompsetting');
+
+        $collection->add_database_table('competency_framework', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_framework');
+
+        $collection->add_database_table('competency_coursecomp', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_coursecomp');
+
+        $collection->add_database_table('competency_template', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_template');
+
+        $collection->add_database_table('competency_templatecomp', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_templatecomp');
+
+        $collection->add_database_table('competency_templatecohort', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_templatecohort');
+
+        $collection->add_database_table('competency_relatedcomp', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_relatedcomp');
+
+        $collection->add_database_table('competency_modulecomp', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_modulecomp');
+
+        // Tables containing user data.
+        $collection->add_database_table('competency_plan', [
+            'name' => 'privacy:metadata:plan:name',
+            'description' => 'privacy:metadata:plan:description',
+            'userid' => 'privacy:metadata:plan:userid',
+            'status' => 'privacy:metadata:plan:status',
+            'duedate' => 'privacy:metadata:plan:duedate',
+            'reviewerid' => 'privacy:metadata:plan:reviewerid',
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_plan');
+
+        $collection->add_database_table('competency_usercomp', [
+            'userid' => 'privacy:metadata:usercomp:userid',
+            'status' => 'privacy:metadata:usercomp:status',
+            'reviewerid' => 'privacy:metadata:usercomp:reviewerid',
+            'proficiency' => 'privacy:metadata:usercomp:proficiency',
+            'grade' => 'privacy:metadata:usercomp:grade',
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_usercomp');
+
+        $collection->add_database_table('competency_usercompcourse', [
+            'userid' => 'privacy:metadata:usercomp:userid',
+            'proficiency' => 'privacy:metadata:usercomp:proficiency',
+            'grade' => 'privacy:metadata:usercomp:grade',
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_usercompcourse');
+
+        $collection->add_database_table('competency_usercompplan', [
+            'userid' => 'privacy:metadata:usercomp:userid',
+            'proficiency' => 'privacy:metadata:usercomp:proficiency',
+            'grade' => 'privacy:metadata:usercomp:grade',
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_usercompplan');
+
+        $collection->add_database_table('competency_plancomp', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_plancomp');
+
+        $collection->add_database_table('competency_evidence', [
+            'action' => 'privacy:metadata:evidence:action',
+            'actionuserid' => 'privacy:metadata:evidence:actionuserid',
+            'descidentifier' => 'privacy:metadata:evidence:descidentifier',
+            'desccomponent' => 'privacy:metadata:evidence:desccomponent',
+            'desca' => 'privacy:metadata:evidence:desca',
+            'url' => 'privacy:metadata:evidence:url',
+            'grade' => 'privacy:metadata:evidence:grade',
+            'note' => 'privacy:metadata:evidence:note',
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_evidence');
+
+        $collection->add_database_table('competency_userevidence', [
+            'name' => 'privacy:metadata:userevidence:name',
+            'description' => 'privacy:metadata:userevidence:description',
+            'url' => 'privacy:metadata:userevidence:url',
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_userevidence');
+
+        $collection->add_database_table('competency_userevidencecomp', [
+            'timecreated' => 'privacy:metadata:timecreated',
+            'timemodified' => 'privacy:metadata:timemodified',
+            'usermodified' => 'privacy:metadata:usermodified',
+        ], 'privacy:metadata:competency_userevidencecomp');
+
+        // Comments can be left on learning plans and competencies.
+        $collection->link_subsystem('core_comments', 'privacy:metadata:core_comments');
+
+        return $collection;
+    }
+
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param int $userid The user to search.
+     * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
+     */
+    public static function get_contexts_for_userid(int $userid) : contextlist {
+        global $DB;
+        $contextlist = new \core_privacy\local\request\contextlist();
+
+        // Find the contexts of the frameworks, and related data, modified by the user.
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {context} ctx
+               JOIN {" . competency_framework::TABLE . "} cf
+                 ON cf.contextid = ctx.id
+          LEFT JOIN {" . competency::TABLE . "} c
+                 ON c.competencyframeworkid = cf.id
+          LEFT JOIN {" . related_competency::TABLE . "} cr
+                 ON cr.competencyid = c.id
+              WHERE cf.usermodified = :userid1
+                 OR c.usermodified = :userid2
+                 OR cr.usermodified = :userid3";
+        $params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid];
+        $contextlist->add_from_sql($sql, $params);
+
+        // Find the contexts of the templates, and related data, modified by the user.
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {context} ctx
+               JOIN {" . template::TABLE . "} tpl
+                 ON tpl.contextid = ctx.id
+          LEFT JOIN {" . template_cohort::TABLE . "} tch
+                 ON tch.templateid = tpl.id
+          LEFT JOIN {" . template_competency::TABLE . "} tc
+                 ON tc.templateid = tpl.id
+              WHERE tpl.usermodified = :userid1
+                 OR tch.usermodified = :userid2
+                 OR tc.usermodified = :userid3";
+        $params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid];
+        $contextlist->add_from_sql($sql, $params);
+
+        // Find the possible course contexts.
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {" . course_competency::TABLE . "} cc
+               JOIN {context} ctx
+                 ON ctx.instanceid = cc.courseid
+                AND ctx.contextlevel = :courselevel
+              WHERE cc.usermodified = :userid";
+        $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
+        $contextlist->add_from_sql($sql, $params);
+
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {" . course_competency_settings::TABLE . "} ccs
+               JOIN {context} ctx
+                 ON ctx.instanceid = ccs.courseid
+                AND ctx.contextlevel = :courselevel
+              WHERE ccs.usermodified = :userid";
+        $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
+        $contextlist->add_from_sql($sql, $params);
+
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {" . user_competency_course::TABLE . "} ucc
+               JOIN {context} ctx
+                 ON ctx.instanceid = ucc.courseid
+                AND ctx.contextlevel = :courselevel
+              WHERE ucc.usermodified = :userid";
+        $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
+        $contextlist->add_from_sql($sql, $params);
+
+        // Find the possible module contexts.
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {" . course_module_competency::TABLE . "} cmc
+               JOIN {context} ctx
+                 ON ctx.instanceid = cmc.cmid
+                AND ctx.contextlevel = :modulelevel
+              WHERE cmc.usermodified = :userid";
+        $params = ['modulelevel' => CONTEXT_MODULE, 'userid' => $userid];
+        $contextlist->add_from_sql($sql, $params);
+
+        // Add user contexts through usermodified/reviewing of plan related data.
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {" . plan::TABLE . "} p
+               JOIN {context} ctx
+                 ON ctx.instanceid = p.userid
+                AND ctx.contextlevel = :userlevel
+          LEFT JOIN {" . plan_competency::TABLE . "} pc
+                 ON pc.planid = p.id
+          LEFT JOIN {" . user_competency_plan::TABLE . "} upc
+                 ON upc.planid = p.id
+              WHERE p.usermodified = :userid1
+                 OR p.reviewerid = :userid2
+                 OR pc.usermodified = :userid3
+                 OR upc.usermodified = :userid4";
+        $params = [
+            'userlevel' => CONTEXT_USER,
+            'userid1' => $userid,
+            'userid2' => $userid,
+            'userid3' => $userid,
+            'userid4' => $userid,
+        ];
+        $contextlist->add_from_sql($sql, $params);
+
+        // Add user contexts through usermodified/reviewing of competency data.
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {context} ctx
+          LEFT JOIN {" . user_competency::TABLE . "} uc
+                 ON uc.userid = ctx.instanceid
+                AND ctx.contextlevel = :userlevel1
+          LEFT JOIN {" . evidence::TABLE . "} e
+                 ON e.usercompetencyid = uc.id
+          LEFT JOIN {" . user_evidence::TABLE . "} ue
+                 ON ue.userid = ctx.instanceid
+                AND ctx.contextlevel = :userlevel2
+          LEFT JOIN {" . user_evidence_competency::TABLE . "} uec
+                 ON uec.userevidenceid = ue.id
+              WHERE uc.usermodified = :userid1
+                 OR uc.reviewerid = :userid2
+                 OR e.usermodified = :userid3
+                 OR e.actionuserid = :userid4
+                 OR ue.usermodified = :userid5
+                 OR uec.usermodified = :userid6";
+        $params = [
+            'userlevel1' => CONTEXT_USER,
+            'userlevel2' => CONTEXT_USER,
+            'userid1' => $userid,
+            'userid2' => $userid,
+            'userid3' => $userid,
+            'userid4' => $userid,
+            'userid5' => $userid,
+            'userid6' => $userid,
+        ];
+        $contextlist->add_from_sql($sql, $params);
+
+        // Now, the easy part, we fetch the user context for user plans and competencies.
+        // We also fetch the course context for the state of competencies for the user in courses.
+        $sql = "
+             SELECT DISTINCT ctx.id
+               FROM {context} ctx
+          LEFT JOIN {" . plan::TABLE . "} p
+                 ON p.userid = ctx.instanceid
+                AND ctx.contextlevel = :userlevel1
+          LEFT JOIN {" . user_competency::TABLE . "} uc
+                 ON uc.userid = ctx.instanceid
+                AND ctx.contextlevel = :userlevel2
+          LEFT JOIN {" . user_evidence::TABLE . "} ue
+                 ON ue.userid = ctx.instanceid
+                AND ctx.contextlevel = :userlevel3
+          LEFT JOIN {" . user_competency_course::TABLE . "} ucc
+                 ON ucc.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel
+              WHERE p.userid = :userid1
+                 OR uc.userid = :userid2
+                 OR ue.userid = :userid3
+                 OR ucc.userid = :userid4";
+        $params = [
+            'userlevel1' => CONTEXT_USER,
+            'userlevel2' => CONTEXT_USER,
+            'userlevel3' => CONTEXT_USER,
+            'courselevel' => CONTEXT_COURSE,
+            'userid1' => $userid,
+            'userid2' => $userid,
+            'userid3' => $userid,
+            'userid4' => $userid,
+        ];
+        $contextlist->add_from_sql($sql, $params);
+
+        // Include the user contexts in which the user commented.
+        $sql = "
+            SELECT ctx.id
+              FROM {context} ctx
+              JOIN {comments} c
+                ON c.contextid = ctx.id
+             WHERE c.component = :component
+               AND c.commentarea IN (:planarea, :usercomparea)
+               AND c.userid = :userid";
+        $params = [
+            'component' => 'competency',    // Must not be core_competency.
+            'planarea' => 'plan',
+            'usercomparea' => 'user_competency',
+            'userid' => $userid
+        ];
+        $contextlist->add_from_sql($sql, $params);
+
+        return $contextlist;
+    }
+
+    /**
+     * Export all user data for the specified user, in the specified contexts.
+     *
+     * @param approved_contextlist $contextlist The approved contexts to export information for.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        $user = $contextlist->get_user();
+        $userid = $user->id;
+
+        // Re-arrange the contexts by context level.
+        $groupedcontexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
+            $contextlevel = $context->contextlevel;
+            if (!in_array($contextlevel, [CONTEXT_USER, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_SYSTEM, CONTEXT_COURSECAT])) {
+                return $carry;
+            }
+            $carry[$contextlevel][] = $context;
+            return $carry;
+        }, [
+            CONTEXT_COURSE => [],
+            CONTEXT_COURSECAT => [],
+            CONTEXT_MODULE => [],
+            CONTEXT_SYSTEM => [],
+            CONTEXT_USER => [],
+        ]);
+
+        // Process module contexts.
+        static::export_user_data_in_module_contexts($userid, $groupedcontexts[CONTEXT_MODULE]);
+
+        // Process course contexts.
+        static::export_user_data_in_course_contexts($userid, $groupedcontexts[CONTEXT_COURSE]);
+
+        // Process course categories context.
+        static::export_user_data_in_category_contexts($userid, $groupedcontexts[CONTEXT_COURSECAT]);
+
+        // Process system context.
+        if (!empty($groupedcontexts[CONTEXT_SYSTEM])) {
+            static::export_user_data_in_system_context($userid);
+        }
+
+        // Process user contexts.
+        static::export_user_data_in_user_contexts($userid, $groupedcontexts[CONTEXT_USER]);
+    }
+
+    /**
+     * Delete all data for all users in the specified context.
+     *
+     * @param context $context The specific context to delete data for.
+     */
+    public static function delete_data_for_all_users_in_context(context $context) {
+        global $DB;
+
+        switch ($context->contextlevel) {
+            case CONTEXT_USER:
+                $userid = $context->instanceid;
+                static::delete_user_evidence_of_prior_learning($userid);
+                static::delete_user_plans($userid);
+                static::delete_user_competencies($userid);
+                break;
+
+            case CONTEXT_COURSE:
+                $courseid = $context->instanceid;
+                $DB->delete_records(user_competency_course::TABLE, ['courseid' => $courseid]);
+                break;
+        }
+    }
+
+    /**
+     * Delete all user data for the specified user, in the specified contexts.
+     *
+     * Here we only delete the private data of user, not whether they modified, are reviewing,
+     * or are associated with the record on at a second level. Only data directly linked to the
+     * user will be affected.
+     *
+     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
+     */
+    public static function delete_data_for_user(approved_contextlist $contextlist) {
+        $user = $contextlist->get_user();
+        $userid = $user->id;
+
+        foreach ($contextlist as $context) {
+            switch ($context->contextlevel) {
+                case CONTEXT_USER:
+                    if ($context->instanceid != $userid) {
+                        // Only delete the user's information when they requested their context to be deleted. We
+                        // do not take any action on other user's contexts because we don't own the data there.
+                        continue;
+                    }
+                    static::delete_user_evidence_of_prior_learning($userid);
+                    static::delete_user_plans($userid);
+                    static::delete_user_competencies($userid);
+                    break;
+
+                case CONTEXT_COURSE:
+                    static::delete_user_competencies_in_course($userid, $context->instanceid);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Delete evidence of prior learning.
+     *
+     * @param int $userid The user ID.
+     * @return void
+     */
+    protected static function delete_user_evidence_of_prior_learning($userid) {
+        global $DB;
+
+        $usercontext = context_user::instance($userid);
+        $ueids = $DB->get_fieldset_select(user_evidence::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
+        if (empty($ueids)) {
+            return;
+        }
+        list($insql, $inparams) = $DB->get_in_or_equal($ueids, SQL_PARAMS_NAMED);
+
+        // Delete competencies associated with user evidence.
+        $DB->delete_records_select(user_evidence_competency::TABLE, "userevidenceid $insql", $inparams);
+
+        // Delete the user evidence.
+        $DB->delete_records_select(user_evidence::TABLE, "id $insql", $inparams);
+
+        // Delete the user evidence files.
+        $fs = get_file_storage();
+        $fs->delete_area_files($usercontext->id, 'core_competency', 'userevidence');
+    }
+
+    /**
+     * User plans.
+     *
+     * @param int $userid The user ID.
+     * @return void
+     */
+    protected static function delete_user_plans($userid) {
+        global $DB;
+        $usercontext = context_user::instance($userid);
+
+        // Remove all the comments made on plans.
+        \core_comment\privacy\provider::delete_comments_for_all_users($usercontext, 'competency', 'plan');
+
+        // Find the user plan IDs.
+        $planids = $DB->get_fieldset_select(plan::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
+        if (empty($planids)) {
+            return;
+        }
+        list($insql, $inparams) = $DB->get_in_or_equal($planids, SQL_PARAMS_NAMED);
+
+        // Delete all the competencies proficiency in the plans.
+        $DB->delete_records_select(user_competency_plan::TABLE, "planid $insql", $inparams);
+
+        // Delete all the competencies in the plans.
+        $DB->delete_records_select(plan_competency::TABLE, "planid $insql", $inparams);
+
+        // Delete all the plans.
+        $DB->delete_records_select(plan::TABLE, "id $insql", $inparams);
+    }
+
+    /**
+     * Delete user competency data.
+     *
+     * @param int $userid The user ID.
+     * @return void
+     */
+    protected static function delete_user_competencies($userid) {
+        global $DB;
+        $usercontext = context_user::instance($userid);
+
+        // Remove all the comments made on user competencies.
+        \core_comment\privacy\provider::delete_comments_for_all_users($usercontext, 'competency', 'user_competency');
+
+        // Find the user competency IDs.
+        $ucids = $DB->get_fieldset_select(user_competency::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
+        if (empty($ucids)) {
+            return;
+        }
+        list($insql, $inparams) = $DB->get_in_or_equal($ucids, SQL_PARAMS_NAMED);
+
+        // Delete all the evidence associated with competencies.
+        $DB->delete_records_select(evidence::TABLE, "usercompetencyid $insql", $inparams);
+
+        // Delete all the record of competency.
+        $DB->delete_records_select(user_competency::TABLE, "id $insql", $inparams);
+    }
+
+    /**
+     * Delete the record of competencies for a user in a course.
+     *
+     * @param int $userid The user ID.
+     * @param int $courseid The course ID.
+     * @return void
+     */
+    protected static function delete_user_competencies_in_course($userid, $courseid) {
+        global $DB;
+        $DB->delete_records(user_competency_course::TABLE, ['userid' => $userid, 'courseid' => $courseid]);
+    }
+
+    /**
+     * Export the user data in user context.
+     *
+     * @param int $userid The user ID.
+     * @param array $contexts The contexts.
+     * @return void
+     */
+    protected static function export_user_data_in_user_contexts($userid, array $contexts) {
+        global $DB;
+
+        $mycontext = context_user::instance($userid);
+        $contextids = array_map(function($context) {
+            return $context->id;
+        }, $contexts);
+        $exportowncontext = in_array($mycontext->id, $contextids);
+        $othercontexts = array_filter($contextids, function($contextid) use ($mycontext) {
+            return $contextid != $mycontext->id;
+        });
+
+        if ($exportowncontext) {
+            static::export_user_data_learning_plans($mycontext);
+            static::export_user_data_competencies($mycontext);
+            static::export_user_data_user_evidence($mycontext);
+        }
+
+        foreach ($othercontexts as $contextid) {
+            static::export_user_data_learning_plans_related_to_me($userid, context::instance_by_id($contextid));
+            static::export_user_data_competencies_related_to_me($userid, context::instance_by_id($contextid));
+            static::export_user_data_user_evidence_related_to_me($userid, context::instance_by_id($contextid));
+        }
+    }
+
+    /**
+     * Export the user data in systen context.
+     *
+     * @param int $userid The user ID.
+     * @return void
+     */
+    protected static function export_user_data_in_system_context($userid) {
+        static::export_user_data_frameworks_in_context($userid, context_system::instance());
+        static::export_user_data_templates_in_context($userid, context_system::instance());
+    }
+
+    /**
+     * Export the user data in category contexts.
+     *
+     * @param int $userid The user ID.
+     * @param array $contexts The contexts.
+     * @return void
+     */
+    protected static function export_user_data_in_category_contexts($userid, array $contexts) {
+        $contexts = array_filter($contexts, function($context) {
+            return $context->contextlevel == CONTEXT_COURSECAT;
+        });
+        if (empty($contexts)) {
+            return;
+        }
+
+        foreach ($contexts as $context) {
+            static::export_user_data_frameworks_in_context($userid, $context);
+            static::export_user_data_templates_in_context($userid, $context);
+        }
+    }
+
+    /**
+     * Export the user data in course contexts.
+     *
+     * @param int $userid The user whose data we're exporting.
+     * @param array $contexts A list of contexts.
+     * @return void
+     */
+    protected static function export_user_data_in_course_contexts($userid, array $contexts) {
+        global $DB;
+
+        $contexts = array_filter($contexts, function($context) {
+            return $context->contextlevel == CONTEXT_COURSE;
+        });
+        if (empty($contexts)) {
+            return;
+        }
+
+        $helper = new performance_helper();
+        $path = [get_string('competencies', 'core_competency')];
+        $courseids = array_map(function($context) {
+            return $context->instanceid;
+        }, $contexts);
+
+        // Fetch all the records of competency proficiency in the course.
+        $ffields = competency_framework::get_sql_fields('f', 'f_');
+        $compfields = competency::get_sql_fields('c', 'c_');
+        $uccfields = user_competency_course::get_sql_fields('ucc', 'ucc_');
+        $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
+        list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
+        $sql = "
+            SELECT $ffields, $compfields, $uccfields, $ctxfields
+              FROM {" . user_competency_course::TABLE . "} ucc
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = ucc.competencyid
+              JOIN {" . competency_framework::TABLE . "} f
+                ON f.id = c.competencyframeworkid
+              JOIN {context} ctx
+                ON ctx.id = f.contextid
+             WHERE ucc.userid = :userid
+               AND ucc.courseid $insql
+          ORDER BY ucc.courseid, c.id";
+        $params = array_merge($inparams, ['userid' => $userid]);
+
+        // Export data.
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        static::recordset_loop_and_export($recordset, 'ucc_courseid', [], function($carry, $record) use ($helper) {
+            context_helper::preload_from_record($record);
+            $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $ucc = new user_competency_course(null, user_competency_course::extract_record($record, 'ucc_'));
+            $helper->ingest_framework($framework);
+
+            $carry[] = array_merge(static::transform_competency_brief($competency), [
+                'rating' => [
+                    'rating' => static::transform_competency_grade($competency, $ucc->get('grade'), $helper),
+                    'proficient' => static::transform_proficiency($ucc->get('proficiency')),
+                    'timecreated' => transform::datetime($ucc->get('timecreated')),
+                    'timemodified' => transform::datetime($ucc->get('timemodified')),
+                ]
+            ]);
+            return $carry;
+
+        }, function($courseid, $data) use ($path) {
+            $context = context_course::instance($courseid);
+            writer::with_context($context)->export_data($path, (object) ['ratings' => $data]);
+        });
+
+        // Export usermodified data.
+        static::export_user_data_in_course_contexts_associations($userid, $courseids, $path);
+        static::export_user_data_in_course_contexts_settings($userid, $courseids, $path);
+        static::export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path, $helper);
+    }
+
+    /**
+     * Export the ratings given in a course.
+     *
+     * @param int $userid The user ID.
+     * @param array $courseids The course IDs.
+     * @param array $path The root path.
+     * @param performance_helper $helper The performance helper.
+     * @return void
+     */
+    protected static function export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path,
+            performance_helper $helper) {
+        global $DB;
+
+        // Fetch all the records of competency proficiency in the course.
+        $ffields = competency_framework::get_sql_fields('f', 'f_');
+        $compfields = competency::get_sql_fields('c', 'c_');
+        $uccfields = user_competency_course::get_sql_fields('ucc', 'ucc_');
+        $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
+        list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
+        $sql = "
+            SELECT $ffields, $compfields, $uccfields, $ctxfields
+              FROM {" . user_competency_course::TABLE . "} ucc
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = ucc.competencyid
+              JOIN {" . competency_framework::TABLE . "} f
+                ON f.id = c.competencyframeworkid
+              JOIN {context} ctx
+                ON ctx.id = f.contextid
+             WHERE ucc.usermodified = :userid
+               AND ucc.courseid $insql
+          ORDER BY ucc.courseid";
+        $params = array_merge($inparams, ['userid' => $userid]);
+
+        // Export the data.
+        static::recordset_loop_and_export($DB->get_recordset_sql($sql, $params), 'ucc_courseid', [],
+            function($carry, $record) use ($helper) {
+                context_helper::preload_from_record($record);
+
+                $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
+                $competency = new competency(null, competency::extract_record($record, 'c_'));
+                $ucc = new user_competency_course(null, user_competency_course::extract_record($record, 'ucc_'));
+                $helper->ingest_framework($framework);
+
+                $carry[] = array_merge(static::transform_competency_brief($competency), [
+                    'rating' => [
+                        'userid' => transform::user($ucc->get('userid')),
+                        'rating' => static::transform_competency_grade($competency, $ucc->get('grade'), $helper),
+                        'proficient' => static::transform_proficiency($ucc->get('proficiency')),
+                        'timemodified' => transform::datetime($ucc->get('timemodified')),
+                    ]
+                ]);
+                return $carry;
+
+            }, function($courseid, $data) use ($path) {
+                $context = context_course::instance($courseid);
+                writer::with_context($context)->export_related_data($path, 'rated_by_me', (object) [
+                    'ratings' => $data
+                ]);
+            }
+        );
+    }
+
+    /**
+     * Export user data in course contexts related to linked competencies.
+     *
+     * @param int $userid The user ID.
+     * @param array $courseids The course IDs.
+     * @param array $path The root path to export at.
+     * @return void
+     */
+    protected static function export_user_data_in_course_contexts_associations($userid, $courseids, $path) {
+        global $DB;
+
+        // Fetch all the courses with associations we created or modified.
+        $compfields = competency::get_sql_fields('c', 'c_');
+        $ccfields = course_competency::get_sql_fields('cc', 'cc_');
+        $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
+        list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
+        $sql = "
+            SELECT $compfields, $ccfields, $ctxfields
+              FROM {" . course_competency::TABLE . "} cc
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = cc.competencyid
+              JOIN {" . competency_framework::TABLE . "} f
+                ON f.id = c.competencyframeworkid
+              JOIN {context} ctx
+                ON ctx.id = f.contextid
+             WHERE cc.usermodified = :userid
+               AND cc.courseid $insql
+          ORDER BY cc.courseid, c.id";
+        $params = array_merge($inparams, ['userid' => $userid]);
+        $recordset = $DB->get_recordset_sql($sql, $params);
+
+        // Export the data.
+        static::recordset_loop_and_export($recordset, 'cc_courseid', [], function($carry, $record) {
+            context_helper::preload_from_record($record);
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $cc = new course_competency(null, course_competency::extract_record($record, 'cc_'));
+            $carry[] = array_merge(static::transform_competency_brief($competency), [
+                'timemodified' => transform::datetime($cc->get('timemodified')),
+                'created_or_modified_by_you' => transform::yesno(true)
+            ]);
+            return $carry;
+
+        }, function($courseid, $data) use ($path, $userid, $DB) {
+            $context = context_course::instance($courseid);
+            writer::with_context($context)->export_related_data($path, 'associations', (object) ['competencies' => $data]);
+        });
+    }
+
+    /**
+     * Export user data in course contexts related to course settings.
+     *
+     * @param int $userid The user ID.
+     * @param array $courseids The course IDs.
+     * @param array $path The root path to export at.
+     * @return void
+     */
+    protected static function export_user_data_in_course_contexts_settings($userid, $courseids, $path) {
+        global $DB;
+
+        // Fetch all the courses with associations we created or modified.
+        $ccsfields = course_competency_settings::get_sql_fields('ccs', 'ccs_');
+        list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
+        $sql = "
+            SELECT $ccsfields
+              FROM {" . course_competency_settings::TABLE . "} ccs
+             WHERE ccs.usermodified = :userid
+               AND ccs.courseid $insql
+          ORDER BY ccs.courseid";
+        $params = array_merge($inparams, ['userid' => $userid]);
+        $recordset = $DB->get_recordset_sql($sql, $params);
+
+        // Export the data.
+        static::recordset_loop_and_export($recordset, 'ccs_courseid', [], function($carry, $record) {
+            $ccs = new course_competency_settings(null, course_competency_settings::extract_record($record, 'ccs_'));
+            return [
+                'timemodified' => transform::datetime($ccs->get('timemodified')),
+                'created_or_modified_by_you' => transform::yesno(true)
+            ];
+        }, function($courseid, $data) use ($path, $userid, $DB) {
+            $context = context_course::instance($courseid);
+            writer::with_context($context)->export_related_data($path, 'settings', (object) $data);
+        });
+    }
+
+    /**
+     * Export the user data in module contexts.
+     *
+     * @param int $userid The user whose data we're exporting.
+     * @param array $contexts A list of contexts.
+     * @return void
+     */
+    protected static function export_user_data_in_module_contexts($userid, array $contexts) {
+        global $DB;
+
+        $contexts = array_filter($contexts, function($context) {
+            return $context->contextlevel == CONTEXT_MODULE;
+        });
+        if (empty($contexts)) {
+            return;
+        }
+
+        $path = [get_string('competencies', 'core_competency')];
+        $cmids = array_map(function($context) {
+            return $context->instanceid;
+        }, $contexts);
+
+        // Fetch all the modules with associations we created or modified.
+        $compfields = competency::get_sql_fields('c', 'c_');
+        $cmcfields = course_module_competency::get_sql_fields('cmc', 'cmc_');
+        $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
+        list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
+        $sql = "
+            SELECT $compfields, $cmcfields, $ctxfields
+              FROM {" . course_module_competency::TABLE . "} cmc
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = cmc.competencyid
+              JOIN {" . competency_framework::TABLE . "} f
+                ON f.id = c.competencyframeworkid
+              JOIN {context} ctx
+                ON ctx.id = f.contextid
+             WHERE cmc.usermodified = :userid
+               AND cmc.cmid $insql
+          ORDER BY cmc.cmid";
+        $params = array_merge($inparams, ['userid' => $userid]);
+
+        // Export the data.
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        static::recordset_loop_and_export($recordset, 'cmc_cmid', [], function($carry, $record) {
+            context_helper::preload_from_record($record);
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $cmc = new course_module_competency(null, course_module_competency::extract_record($record, 'cmc_'));
+            $carry[] = array_merge(static::transform_competency_brief($competency), [
+                'timecreated' => transform::datetime($cmc->get('timecreated')),
+                'timemodified' => transform::datetime($cmc->get('timemodified')),
+                'created_or_modified_by_you' => transform::yesno(true)
+            ]);
+            return $carry;
+
+        }, function($cmid, $data) use ($path) {
+            $context = context_module::instance($cmid);
+            writer::with_context($context)->export_data($path, (object) ['associations' => $data]);
+        });
+    }
+
+    /**
+     * Export a user's competencies.
+     *
+     * @param context_user $context The context of the user requesting the export.
+     * @return void
+     */
+    protected static function export_user_data_competencies(context_user $context) {
+        global $DB;
+
+        $userid = $context->instanceid;
+        $path = [get_string('competencies', 'core_competency'), get_string('competencies', 'core_competency')];
+        $helper = new performance_helper();
+        $cfields = competency::get_sql_fields('c', 'c_');
+        $ucfields = user_competency::get_sql_fields('uc', 'uc_');
+        $efields = evidence::get_sql_fields('e', 'e_');
+
+        $makecomppath = function($competencyid, $data) use ($path) {
+            return array_merge($path, [$data['name'] . ' (' . $competencyid . ')']);
+        };
+
+        $sql = "
+            SELECT $cfields, $ucfields, $efields
+              FROM {" . user_competency::TABLE . "} uc
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = uc.competencyid
+         LEFT JOIN {" . evidence::TABLE . "} e
+                ON uc.id = e.usercompetencyid
+             WHERE uc.userid = :userid
+          ORDER BY c.id, e.timecreated DESC, e.id DESC";
+        $params = ['userid' => $userid];
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        static::recordset_loop_and_export($recordset, 'c_id', null, function($carry, $record)
+                use ($context, $userid, $helper, $makecomppath) {
+
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+
+            if ($carry === null) {
+                $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
+                $carry = array_merge(static::transform_competency_brief($competency), [
+                    'rating' => static::transform_user_competency($userid, $uc, $competency, $helper),
+                    'evidence' => []
+                ]);
+                \core_comment\privacy\provider::export_comments($context, 'competency', 'user_competency',
+                    $uc->get('id'), $makecomppath($competency->get('id'), $carry), false);
+            }
+
+            // There is an evidence in this record.
+            if (!empty($record->e_id)) {
+                $evidence = new evidence(null, evidence::extract_record($record, 'e_'));
+                $carry['evidence'][] = static::transform_evidence($userid, $evidence, $competency, $helper);
+            }
+
+            return $carry;
+
+        }, function($competencyid, $data) use ($makecomppath, $context) {
+            writer::with_context($context)->export_data($makecomppath($competencyid, $data), (object) $data);
+        });
+    }
+
+    /**
+     * Export a user's learning plans.
+     *
+     * @param context_user $context The context of the user requesting the export.
+     * @return void
+     */
+    protected static function export_user_data_learning_plans(context_user $context) {
+        global $DB;
+
+        $userid = $context->instanceid;
+        $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:plans', 'core_competency')];
+        $helper = new performance_helper();
+        $pfields = plan::get_sql_fields('p', 'p_');
+        $pcfields = plan_competency::get_sql_fields('pc', 'pc_');
+        $cfields = competency::get_sql_fields('c', 'c_');
+        $ucfields = user_competency::get_sql_fields('uc', 'uc_');
+        $ucpfields = user_competency_plan::get_sql_fields('ucp', 'ucp_');
+
+        // The user's learning plans.
+        $sql = "
+            SELECT $pfields, $pcfields, $cfields, $ucfields, $ucpfields
+              FROM {" . plan::TABLE . "} p
+         LEFT JOIN {" . plan_competency::TABLE . "} pc
+                ON p.id = pc.planid
+               AND p.templateid IS NULL
+               AND p.status != :complete1
+         LEFT JOIN {" . template_competency::TABLE . "} tc
+                ON tc.templateid = p.templateid
+               AND p.templateid IS NOT NULL
+               AND p.status != :complete2
+         LEFT JOIN {" . user_competency_plan::TABLE . "} ucp
+                ON ucp.planid = p.id
+               AND p.status = :complete3
+         LEFT JOIN {" . competency::TABLE . "} c
+                ON c.id = pc.competencyid
+                OR c.id = tc.competencyid
+                OR c.id = ucp.competencyid
+         LEFT JOIN {" . user_competency::TABLE . "} uc
+                ON uc.userid = p.userid
+               AND (uc.competencyid = pc.competencyid OR uc.competencyid = tc.competencyid)
+             WHERE p.userid = :userid
+          ORDER BY p.id, c.id";
+        $params = [
+            'userid' => $userid,
+            'complete1' => plan::STATUS_COMPLETE,
+            'complete2' => plan::STATUS_COMPLETE,
+            'complete3' => plan::STATUS_COMPLETE,
+        ];
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        static::recordset_loop_and_export($recordset, 'p_id', null, function($carry, $record) use ($userid, $helper, $context) {
+            $iscomplete = $record->p_status == plan::STATUS_COMPLETE;
+
+            if ($carry === null) {
+                $plan = new plan(null, plan::extract_record($record, 'p_'));
+                $options = ['context' => $context];
+                $carry = [
+                    'name' => format_string($plan->get('name'), true, $options),
+                    'description' => format_text($plan->get('description'), $plan->get('descriptionformat'), $options),
+                    'status' => $plan->get_statusname(),
+                    'duedate' => $plan->get('duedate') ? transform::datetime($plan->get('duedate')) : '-',
+                    'reviewerid' => $plan->get('reviewerid') ? transform::user($plan->get('reviewerid')) : '-',
+                    'timecreated' => transform::datetime($plan->get('timecreated')),
+                    'timemodified' => transform::datetime($plan->get('timemodified')),
+                    'competencies' => [],
+                ];
+            }
+
+            // The plan is empty.
+            if (empty($record->c_id)) {
+                return $carry;
+            }
+
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $rating = null;
+
+            if ($iscomplete) {
+                // When the plan is complete, we should always found the user_competency_plan.
+                $ucp = new user_competency_plan(null, user_competency_plan::extract_record($record, 'ucp_'));
+                $rating = static::transform_user_competency($userid, $ucp, $competency, $helper);
+
+            } else if (!empty($record->uc_id)) {
+                // When the plan is complete, there are still records of user_competency but we do not
+                // export them here, we export them as part of the competencies structure. The reason why
+                // we try to get the user_competency when the plan is not complete is to give the most accurate
+                // representation of the plan as possible.
+                $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
+                $rating = static::transform_user_competency($userid, $uc, $competency, $helper);
+            }
+
+            $carry['competencies'][] = array_merge(static::transform_competency_brief($competency), ['rating' => $rating]);
+            return $carry;
+
+        }, function($planid, $data) use ($context, $path) {
+            $planpath = array_merge($path, [$data['name'] . ' (' . $planid . ')']);
+            \core_comment\privacy\provider::export_comments($context, 'competency', 'plan', $planid, $planpath, false);
+            writer::with_context($context)->export_data($planpath, (object) $data);
+        });
+    }
+
+    /**
+     * Export a user's data related to learning plans.
+     *
+     * @param int $userid The user ID we're exporting for.
+     * @param context_user $context The context of the user in which we're gathering data.
+     * @return void
+     */
+    protected static function export_user_data_learning_plans_related_to_me($userid, context_user $context) {
+        global $DB;
+
+        $path = [
+            get_string('competencies', 'core_competency'),
+            get_string('privacy:path:relatedtome', 'core_competency'),
+            get_string('privacy:path:plans', 'core_competency'),
+        ];
+        $plans = [];
+        $helper = new performance_helper();
+        $pfields = plan::get_sql_fields('p', 'p_');
+        $pcfields = plan_competency::get_sql_fields('pc', 'pc_');
+        $cfields = competency::get_sql_fields('c', 'c_');
+        $ucpfields = user_competency_plan::get_sql_fields('ucp', 'ucp_');
+
+        // Function to initialise a plan record.
+        $initplan = function($record) use ($context, $userid, &$plans) {
+            $plan = new plan(null, plan::extract_record($record, 'p_'));
+            $options = ['context' => $context];
+            $plans[$plan->get('id')] = [
+                'name' => format_string($plan->get('name'), true, $options),
+                'reviewer_is_you' => transform::yesno($plan->get('reviewerid') == $userid),
+                'timecreated' => transform::datetime($plan->get('timecreated')),
+                'timemodified' => transform::datetime($plan->get('timemodified')),
+                'created_or_modified_by_you' => transform::yesno($plan->get('usermodified') == $userid),
+                'competencies' => [],
+            ];
+        };
+
+        $initcompetency = function($record, $planid) use (&$plans) {
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $plans[$planid]['competencies'][$competency->get('id')] = static::transform_competency_brief($competency);
+        };
+
+        // Look for associations that were created.
+        $sql = "
+            SELECT $pfields, $pcfields, $cfields
+              FROM {" . plan_competency::TABLE . "} pc
+              JOIN {" . plan::TABLE . "} p
+                ON p.id = pc.planid
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = pc.competencyid
+             WHERE p.userid = :targetuserid
+               AND pc.usermodified = :userid
+          ORDER BY p.id, c.id";
+        $params = [
+            'targetuserid' => $context->instanceid,
+            'userid' => $userid,
+        ];
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $planid = $record->p_id;
+            if (!isset($plans[$planid])) {
+                $initplan($record);
+            }
+
+            $initcompetency($record, $planid);
+            $pc = new plan_competency(null, plan_competency::extract_record($record, 'pc_'));
+            $plans[$planid]['competencies'][$pc->get('competencyid')] = array_merge(
+                $plans[$planid]['competencies'][$pc->get('competencyid')], [
+                    'timemodified' => $pc->get('timemodified') ? transform::datetime($pc->get('timemodified')) : '-',
+                    'timecreated' => $pc->get('timecreated') ? transform::datetime($pc->get('timecreated')) : '-',
+                    'created_or_modified_by_you' => transform::yesno($pc->get('usermodified') == $userid),
+                ]
+            );
+        }
+        $recordset->close();
+
+        // Look for final grades that were given.
+        $sql = "
+            SELECT $pfields, $ucpfields, $cfields
+              FROM {" . user_competency_plan::TABLE . "} ucp
+              JOIN {" . plan::TABLE . "} p
+                ON p.id = ucp.planid
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = ucp.competencyid
+             WHERE p.userid = :targetuserid
+               AND ucp.usermodified = :userid
+          ORDER BY p.id, c.id";
+        $params = [
+            'targetuserid' => $context->instanceid,
+            'userid' => $userid,
+        ];
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $planid = $record->p_id;
+            $competencyid = $record->c_id;
+
+            if (!isset($plans[$planid])) {
+                $initplan($record);
+            }
+
+            if (!isset($plans[$planid]['competencies'][$competencyid])) {
+                $initcompetency($record, $planid);
+            }
+
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $ucp = new user_competency_plan(null, user_competency_plan::extract_record($record, 'ucp_'));
+            $plans[$planid]['competencies'][$competencyid]['rating'] = static::transform_user_competency($userid, $ucp,
+                $competency, $helper);
+        }
+        $recordset->close();
+
+        // Find the plans that were modified or reviewed.
+        $insql = " > 0";
+        $inparams = [];
+        if (!empty($plans)) {
+            list($insql, $inparams) = $DB->get_in_or_equal(array_keys($plans), SQL_PARAMS_NAMED, 'param', false);
+        }
+        $sql = "
+            SELECT $pfields
+              FROM {" . plan::TABLE . "} p
+         LEFT JOIN {comments} c
+                ON c.contextid = :contextid
+               AND c.commentarea = :planarea
+               AND c.component = :competency
+               AND c.itemid = p.id
+             WHERE p.userid = :targetuserid
+               AND (p.usermodified = :userid1
+                OR p.reviewerid = :userid2
+                OR c.userid = :userid3)
+               AND p.id $insql
+          ORDER BY p.id";
+        $params = array_merge($inparams, [
+            'targetuserid' => $context->instanceid,
+            'userid1' => $userid,
+            'userid2' => $userid,
+            'userid3' => $userid,
+            'contextid' => $context->id,
+            'planarea' => 'plan',
+            'competency' => 'competency'
+        ]);
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $planid = $record->p_id;
+            if (!isset($plans[$planid])) {
+                $initplan($record);
+            }
+        }
+        $recordset->close();
+
+        // Export each plan on its own.
+        foreach ($plans as $planid => $plan) {
+            $planpath = array_merge($path, ["{$plan['name']} ({$planid})"]);
+            $plan['competencies'] = array_values($plan['competencies']);    // Drop the keys.
+
+            writer::with_context($context)->export_data($planpath, (object) $plan);
+            \core_comment\privacy\provider::export_comments($context, 'competency', 'plan', $planid, $planpath, true);
+        }
+    }
+
+    /**
+     * Export a user's data related to competencies.
+     *
+     * @param int $userid The user ID we're exporting for.
+     * @param context_user $context The context of the user in which we're gathering data.
+     * @return void
+     */
+    protected static function export_user_data_competencies_related_to_me($userid, context_user $context) {
+        global $DB;
+
+        $path = [
+            get_string('competencies', 'core_competency'),
+            get_string('privacy:path:relatedtome', 'core_competency'),
+            get_string('competencies', 'core_competency'),
+        ];
+        $competencies = [];
+        $helper = new performance_helper();
+        $cfields = competency::get_sql_fields('c', 'c_');
+        $ucfields = user_competency::get_sql_fields('uc', 'uc_');
+        $efields = evidence::get_sql_fields('e', 'e_');
+
+        $initcompetency = function($record) use (&$competencies) {
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $competencies[$competency->get('id')] = array_merge(static::transform_competency_brief($competency), [
+                'evidence' => []
+            ]);
+        };
+
+        $initusercomp = function($competency, $record) use (&$competencies, $userid, $helper) {
+            $competencyid = $competency->get('id');
+            $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
+            $competencies[$competencyid]['uc_id'] = $uc->get('id');
+            $competencies[$competencyid]['rating'] = static::transform_user_competency($userid, $uc, $competency, $helper);
+        };
+
+        // Look for evidence.
+        $sql = "
+            SELECT $efields, $ucfields, $cfields
+              FROM {" . evidence::TABLE . "} e
+              JOIN {" . user_competency::TABLE . "} uc
+                ON uc.id = e.usercompetencyid
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = uc.competencyid
+             WHERE uc.userid = :targetuserid
+               AND (e.usermodified = :userid1
+                OR e.actionuserid = :userid2)
+          ORDER BY c.id, e.id";
+        $params = [
+            'targetuserid' => $context->instanceid,
+            'userid1' => $userid,
+            'userid2' => $userid,
+        ];
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $competencyid = $record->c_id;
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+
+            if (!isset($competencies[$competencyid])) {
+                $initcompetency($record);
+            }
+
+            if (!array_key_exists('rating', $competencies[$competencyid])) {
+                $competencies[$competencyid]['rating'] = null;
+                if ($record->uc_reviewerid == $userid || $record->uc_usermodified == $userid) {
+                    $initusercomp($competency, $record);
+                }
+            }
+
+            $evidence = new evidence(null, evidence::extract_record($record, 'e_'));
+            $competencies[$competencyid]['evidence'][] = static::transform_evidence($userid, $evidence, $competency, $helper);
+        }
+        $recordset->close();
+
+        // Look for user competency we modified and didn't catch.
+        $insql = ' > 0';
+        $inparams = [];
+        if (!empty($competencies)) {
+            list($insql, $inparams) = $DB->get_in_or_equal(array_keys($competencies), SQL_PARAMS_NAMED, 'param', false);
+        }
+        $sql = "
+            SELECT $ucfields, $cfields
+              FROM {" . user_competency::TABLE . "} uc
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = uc.competencyid
+         LEFT JOIN {comments} cmt
+                ON cmt.contextid = :contextid
+               AND cmt.commentarea = :ucarea
+               AND cmt.component = :competency
+               AND cmt.itemid = uc.id
+             WHERE uc.userid = :targetuserid
+               AND (uc.usermodified = :userid1
+                OR uc.reviewerid = :userid2
+                OR cmt.userid = :userid3)
+               AND uc.competencyid $insql
+          ORDER BY c.id, uc.id";
+        $params = array_merge($inparams, [
+            'targetuserid' => $context->instanceid,
+            'userid1' => $userid,
+            'userid2' => $userid,
+            'userid3' => $userid,
+            'contextid' => $context->id,
+            'ucarea' => 'user_competency',
+            'competency' => 'competency',
+        ]);
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            if (!isset($competencies[$competency->get('id')])) {
+                $initcompetency($record);
+                $initusercomp($competency, $record);
+            }
+        }
+        $recordset->close();
+
+        // Export each competency on its own.
+        foreach ($competencies as $competencyid => $competency) {
+            $comppath = array_merge($path, ["{$competency['name']} ({$competencyid})"]);
+            $ucid = isset($competency['uc_id']) ? $competency['uc_id'] : null;
+            unset($competency['uc_id']);
+
+            // Send to writer.
+            writer::with_context($context)->export_data($comppath, (object) $competency);
+            if ($ucid) {
+                \core_comment\privacy\provider::export_comments($context, 'competency', 'user_competency', $ucid, $comppath, true);
+            }
+        }
+    }
+
+    /**
+     * Export a user's data related to evidence of prior learning.
+     *
+     * @param int $userid The user ID we're exporting for.
+     * @param context_user $context The context of the user in which we're gathering data.
+     * @return void
+     */
+    protected static function export_user_data_user_evidence_related_to_me($userid, context_user $context) {
+        global $DB;
+
+        $path = [
+            get_string('competencies', 'core_competency'),
+            get_string('privacy:path:relatedtome', 'core_competency'),
+            get_string('privacy:path:userevidence', 'core_competency'),
+        ];
+        $evidence = [];
+        $helper = new performance_helper();
+        $cfields = competency::get_sql_fields('c', 'c_');
+        $uecfields = user_evidence_competency::get_sql_fields('uec', 'uec_');
+        $uefields = user_evidence::get_sql_fields('ue', 'ue_');
+
+        $initevidence = function($record) use (&$evidence, $userid) {
+            $ue = new user_evidence(null, user_evidence::extract_record($record, 'ue_'));
+            $evidence[$ue->get('id')] = static::transform_user_evidence($userid, $ue);
+        };
+
+        // Look for evidence.
+        $sql = "
+            SELECT $uefields, $uecfields, $cfields
+              FROM {" . user_evidence_competency::TABLE . "} uec
+              JOIN {" . user_evidence::TABLE . "} ue
+                ON ue.id = uec.userevidenceid
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = uec.competencyid
+             WHERE ue.userid = :targetuserid
+               AND uec.usermodified = :userid
+          ORDER BY ue.id, c.id";
+        $params = [
+            'targetuserid' => $context->instanceid,
+            'userid' => $userid,
+        ];
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $ueid = $record->ue_id;
+            if (!isset($evidence[$ueid])) {
+                $initevidence($record);
+            }
+
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $uec = new user_evidence_competency(null, user_evidence_competency::extract_record($record, 'uec_'));
+            $evidence[$ueid]['competencies'][] = array_merge(static::transform_competency_brief($competency), [
+                'timemodified' => $uec->get('timemodified') ? transform::datetime($uec->get('timemodified')) : '-',
+                'timecreated' => $uec->get('timecreated') ? transform::datetime($uec->get('timecreated')) : '-',
+                'created_or_modified_by_you' => transform::yesno($uec->get('usermodified'))
+            ]);
+        }
+        $recordset->close();
+
+        // Look for user evidence we modified or reviewed and didn't catch.
+        $insql = ' > 0';
+        $inparams = [];
+        if (!empty($evidence)) {
+            list($insql, $inparams) = $DB->get_in_or_equal(array_keys($evidence), SQL_PARAMS_NAMED, 'param', false);
+        }
+        $sql = "
+            SELECT $uefields
+              FROM {" . user_evidence::TABLE . "} ue
+             WHERE ue.userid = :targetuserid
+               AND ue.usermodified = :userid
+               AND ue.id $insql
+          ORDER BY ue.id";
+        $params = array_merge($inparams, [
+            'targetuserid' => $context->instanceid,
+            'userid' => $userid,
+        ]);
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $initevidence($record);
+        }
+        $recordset->close();
+
+        // Export files, then content.
+        foreach ($evidence as $ueid => $data) {
+            $uepath = array_merge($path, ["{$data['name']} ({$ueid})"]);
+            writer::with_context($context)->export_area_files($uepath, 'core_competency', 'userevidence', $ueid);
+            writer::with_context($context)->export_data($uepath, (object) $data);
+        }
+    }
+
+    /**
+     * Export the evidence of prior learning of a user.
+     *
+     * @param context_user $context The context of the user we're exporting for.
+     * @return void
+     */
+    protected static function export_user_data_user_evidence(context_user $context) {
+        global $DB;
+
+        $userid = $context->instanceid;
+        $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:userevidence', 'core_competency')];
+        $uefields = user_evidence::get_sql_fields('ue', 'ue_');
+        $cfields = competency::get_sql_fields('c', 'c_');
+
+        $sql = "
+            SELECT $uefields, $cfields
+              FROM {" . user_evidence::TABLE . "} ue
+         LEFT JOIN {" . user_evidence_competency::TABLE . "} uec
+                ON uec.userevidenceid = ue.id
+         LEFT JOIN {" . competency::TABLE . "} c
+                ON c.id = uec.competencyid
+             WHERE ue.userid = :userid
+          ORDER BY ue.id";
+        $params = ['userid' => $userid];
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        static::recordset_loop_and_export($recordset, 'ue_id', null, function($carry, $record) use ($userid, $context){
+            if ($carry === null) {
+                $ue = new user_evidence(null, user_evidence::extract_record($record, 'ue_'));
+                $carry = static::transform_user_evidence($userid, $ue);
+            }
+
+            if (!empty($record->c_id)) {
+                $competency = new competency(null, competency::extract_record($record, 'c_'));
+                $carry['competencies'][] = static::transform_competency_brief($competency);
+            }
+
+            return $carry;
+        }, function($ueid, $data) use ($context, $path) {
+            $finalpath = array_merge($path, [$data['name'] . ' (' . $ueid . ')']);
+            writer::with_context($context)->export_area_files($finalpath, 'core_competency', 'userevidence', $ueid);
+            writer::with_context($context)->export_data($finalpath, (object) $data);
+        });
+    }
+
+    /**
+     * Export the user data related to frameworks in context.
+     *
+     * @param int $userid The user ID.
+     * @param context $context The context.
+     * @return void
+     */
+    protected static function export_user_data_frameworks_in_context($userid, context $context) {
+        global $DB;
+
+        $ffields = competency_framework::get_sql_fields('f', 'f_');
+        $cfields = competency::get_sql_fields('c', 'c_');
+        $c2fields = competency::get_sql_fields('c2', 'c2_');
+        $rcfields = related_competency::get_sql_fields('rc', 'rc_');
+
+        $frameworks = [];
+        $initframework = function($record) use (&$frameworks, $userid) {
+            $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
+            $frameworks[$framework->get('id')] = array_merge(static::transform_framework_brief($framework), [
+                'timemodified' => transform::datetime($framework->get('timemodified')),
+                'created_or_modified_by_you' => transform::yesno($framework->get('usermodified') == $userid),
+                'competencies' => []
+            ]);
+        };
+        $initcompetency = function($record, $prefix) use (&$frameworks, $userid) {
+            $competency = new competency(null, competency::extract_record($record, $prefix));
+            $frameworks[$competency->get('competencyframeworkid')]['competencies'][$competency->get('id')] = array_merge(
+                static::transform_competency_brief($competency),
+                [
+                    'timemodified' => transform::datetime($competency->get('timemodified')),
+                    'created_or_modified_by_you' => transform::yesno($competency->get('usermodified') == $userid),
+                    'related_competencies' => []
+                ]
+            );
+        };
+
+        // Start by looking for related competencies.
+        $sql = "
+            SELECT $ffields, $cfields, $c2fields, $rcfields
+              FROM {" . related_competency::TABLE . "} rc
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = rc.competencyid
+              JOIN {" . competency::TABLE . "} c2
+                ON c2.id = rc.relatedcompetencyid
+              JOIN {" . competency_framework::TABLE . "} f
+                ON f.id = c.competencyframeworkid
+             WHERE rc.usermodified = :userid
+               AND f.contextid = :contextid
+          ORDER BY rc.id, c.id";
+        $params = ['userid' => $userid, 'contextid' => $context->id];
+
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $frameworkid = $record->f_id;
+            $comp1id = $record->c_id;
+            $comp2id = $record->c2_id;
+
+            if (!isset($frameworks[$frameworkid])) {
+                $initframework($record);
+            }
+
+            foreach (['c_', 'c2_'] as $key) {
+                $competencyid = $record->{$key . 'id'};
+                if (!isset($frameworks[$frameworkid]['competencies'][$competencyid])) {
+                    $initcompetency($record, $key);
+                }
+            }
+
+            $relcomp = new related_competency(null, related_competency::extract_record($record, 'rc_'));
+            foreach (['c_' => 'c2_', 'c2_' => 'c_'] as $key => $relatedkey) {
+                $competencyid = $record->{$key . 'id'};
+                $competency = new competency(null, competency::extract_record($record, $relatedkey));
+                $frameworks[$frameworkid]['competencies'][$competencyid]['related_competencies'][] = [
+                    'name' => $competency->get('shortname'),
+                    'idnumber' => $competency->get('idnumber'),
+                    'timemodified' => transform::datetime($relcomp->get('timemodified')),
+                    'created_or_modified_by_you' => transform::yesno($relcomp->get('usermodified') == $userid),
+                ];
+            }
+        }
+        $recordset->close();
+
+        // Now look for competencies, but skip the ones we've already seen.
+        $competencyids = array_reduce($frameworks, function($carry, $framework) {
+            return array_merge($carry, array_keys($framework['competencies']));
+        }, []);
+        $insql = ' IS NOT NULL';
+        $inparams = [];
+        if (!empty($competencyids)) {
+            list($insql, $inparams) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED, 'param', false);
+        }
+        $sql = "
+            SELECT $ffields, $cfields
+              FROM {" . competency::TABLE . "} c
+              JOIN {" . competency_framework::TABLE . "} f
+                ON f.id = c.competencyframeworkid
+             WHERE c.usermodified = :userid
+               AND f.contextid = :contextid
+               AND c.id $insql
+          ORDER BY c.id";
+        $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $frameworkid = $record->f_id;
+            if (!isset($frameworks[$frameworkid])) {
+                $initframework($record);
+            }
+            $initcompetency($record, 'c_');
+        }
+        $recordset->close();
+
+        // Now look for frameworks, but skip the ones we've already seen.
+        $frameworkids = array_keys($frameworks);
+        $insql = ' IS NOT NULL';
+        $inparams = [];
+        if (!empty($frameworkids)) {
+            list($insql, $inparams) = $DB->get_in_or_equal($frameworkids, SQL_PARAMS_NAMED, 'param', false);
+        }
+        $sql = "
+            SELECT $ffields
+              FROM {" . competency_framework::TABLE . "} f
+             WHERE f.usermodified = :userid
+               AND f.contextid = :contextid
+               AND f.id $insql
+          ORDER BY f.id";
+        $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            context_helper::preload_from_record($record);
+            $initframework($record);
+        }
+        $recordset->close();
+
+        // Export all the things!
+        writer::with_context($context)->export_related_data(
+            [get_string('competencies', 'core_competency')],
+            'frameworks',
+            (object) [
+                // Drop the temporary IDs.
+                'frameworks' => array_reduce($frameworks, function($carry, $item) {
+                    $item['competencies'] = array_values($item['competencies']);
+                    $carry[] = $item;
+                    return $carry;
+                }, [])
+            ]
+        );
+    }
+
+    /**
+     * Export the user data related to templates in contexts.
+     *
+     * @param int $userid The user ID.
+     * @param context $context The context.
+     * @return void
+     */
+    protected static function export_user_data_templates_in_context($userid, context $context) {
+        global $DB;
+
+        $tfields = template::get_sql_fields('t', 't_');
+        $cfields = competency::get_sql_fields('c', 'c_');
+        $tcfields = template_competency::get_sql_fields('tc', 'tc_');
+        $tchfields = template_cohort::get_sql_fields('tch', 'tch_');
+
+        $templates = [];
+        $inittemplate = function($record) use (&$templates, $userid) {
+            $template = new template(null, template::extract_record($record, 't_'));
+            $templates[$template->get('id')] = array_merge(static::transform_template_brief($template), [
+                'timemodified' => transform::datetime($template->get('timemodified')),
+                'created_or_modified_by_you' => transform::yesno($template->get('usermodified') == $userid),
+                'competencies' => [],
+                'cohorts' => []
+            ]);
+        };
+
+        // Find the template competencies.
+        $sql = "
+            SELECT $tfields, $cfields, $tcfields
+              FROM {" . template_competency::TABLE . "} tc
+              JOIN {" . template::TABLE . "} t
+                ON t.id = tc.templateid
+              JOIN {" . competency::TABLE . "} c
+                ON c.id = tc.competencyid
+             WHERE t.contextid = :contextid
+               AND tc.usermodified = :userid
+          ORDER BY t.id, tc.id";
+        $params = ['userid' => $userid, 'contextid' => $context->id];
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $templateid = $record->t_id;
+            if (!isset($templates[$templateid])) {
+                $inittemplate($record);
+            }
+            $tplcomp = new template_competency(null, template_competency::extract_record($record, 'tc_'));
+            $competency = new competency(null, competency::extract_record($record, 'c_'));
+            $templates[$templateid]['competencies'][] = array_merge(
+                static::transform_competency_brief($competency),
+                [
+                    'timemodified' => transform::datetime($tplcomp->get('timemodified')),
+                    'created_or_modified_by_you' => transform::yesno($tplcomp->get('usermodified') == $userid)
+                ]
+            );
+        }
+        $recordset->close();
+
+        // Find the template cohorts.
+        $sql = "
+            SELECT $tfields, $tchfields, c.name AS cohortname
+              FROM {" . template_cohort::TABLE . "} tch
+              JOIN {" . template::TABLE . "} t
+                ON t.id = tch.templateid
+              JOIN {cohort} c
+                ON c.id = tch.cohortid
+             WHERE t.contextid = :contextid
+               AND tch.usermodified = :userid
+          ORDER BY t.id, tch.id";
+        $params = ['userid' => $userid, 'contextid' => $context->id];
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $templateid = $record->t_id;
+            if (!isset($templates[$templateid])) {
+                $inittemplate($record);
+            }
+            $tplcohort = new template_cohort(null, template_cohort::extract_record($record, 'tch_'));
+            $templates[$templateid]['cohorts'][] = [
+                'name' => $record->cohortname,
+                'timemodified' => transform::datetime($tplcohort->get('timemodified')),
+                'created_or_modified_by_you' => transform::yesno($tplcohort->get('usermodified') == $userid)
+            ];
+        }
+        $recordset->close();
+
+        // Find the modified templates which we haven't been found yet.
+        $templateids = array_keys($templates);
+        $insql = "IS NOT NULL";
+        $inparams = [];
+        if (!empty($templateids)) {
+            list($insql, $inparams) = $DB->get_in_or_equal($templateids, SQL_PARAMS_NAMED, 'param', false);
+        }
+        $sql = "
+            SELECT $tfields
+              FROM {" . template::TABLE . "} t
+             WHERE t.contextid = :contextid
+               AND t.usermodified = :userid
+               AND t.id $insql
+          ORDER BY t.id";
+        $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
+        $recordset = $DB->get_recordset_sql($sql, $params);
+        foreach ($recordset as $record) {
+            $inittemplate($record);
+        }
+        $recordset->close();
+
+        // Export all the things!
+        writer::with_context($context)->export_related_data([get_string('competencies', 'core_competency')],
+            'templates', (object) ['templates' => array_values($templates)]);
+    }
+
+    /**
+     * Transform a competency into a brief description.
+     *
+     * @param competency $competency The competency.
+     * @return array
+     */
+    protected static function transform_competency_brief(competency $competency) {
+        global $OUTPUT;
+        $exporter = new \core_competency\external\competency_exporter($competency, ['context' => $competency->get_context()]);
+        $data = $exporter->export($OUTPUT);
+        return [
+            'idnumber' => $data->idnumber,
+            'name' => $data->shortname,
+            'description' => $data->description
+        ];
+    }
+
+    /**
+     * Transform a competency rating.
+     *
+     * @param competency $competency The competency.
+     * @param int $grade The grade.
+     * @param performance_helper $helper The performance helper.
+     * @return string
+     */
+    protected static function transform_competency_grade(competency $competency, $grade, performance_helper $helper) {
+        if ($grade === null) {
+            return '-';
+        }
+        $scale = $helper->get_scale_from_competency($competency);
+        return $scale->scale_items[$grade - 1];
+    }
+
+    /**
+     * Transform an evidence.
+     *
+     * @param int $userid The user ID we are exporting for.
+     * @param evidence $evidence The evidence.
+     * @param competency $competency The associated competency.
+     * @param performance_helper $helper The performance helper.
+     * @return array
+     */
+    protected static function transform_evidence($userid, evidence $evidence, competency $competency, performance_helper $helper) {
+        $action = $evidence->get('action');
+        $actiontxt = '?';
+        if ($action == evidence::ACTION_LOG) {
+            $actiontxt = get_string('privacy:evidence:action:log', 'core_competency');
+        } else if ($action == evidence::ACTION_COMPLETE) {
+            $actiontxt = get_string('privacy:evidence:action:complete', 'core_competency');
+        } else if ($action == evidence::ACTION_OVERRIDE) {
+            $actiontxt = get_string('privacy:evidence:action:override', 'core_competency');
+        }
+
+        $actionuserid = $evidence->get('actionuserid');
+
+        return [
+            'action' => $actiontxt,
+            'actionuserid' => $actionuserid ? transform::user($actionuserid) : '-',
+            'acting_user_is_you' => transform::yesno($userid == $actionuserid),
+            'description' => (string) $evidence->get_description(),
+            'url' => $evidence->get('url'),
+            'grade' => static::transform_competency_grade($competency, $evidence->get('grade'), $helper),
+            'note' => $evidence->get('note'),
+            'timecreated' => transform::datetime($evidence->get('timecreated')),
+            'timemodified' => transform::datetime($evidence->get('timemodified')),
+            'created_or_modified_by_you' => transform::yesno($userid == $evidence->get('usermodified'))
+        ];
+    }
+
+    /**
+     * Transform a framework into a brief description.
+     *
+     * @param competency_framework $framework The framework.
+     * @return array
+     */
+    protected static function transform_framework_brief(competency_framework $framework) {
+        global $OUTPUT;
+        $exporter = new \core_competency\external\competency_framework_exporter($framework);
+        $data = $exporter->export($OUTPUT);
+        return [
+            'name' => $data->shortname,
+            'idnumber' => $data->idnumber,
+            'description' => $data->description
+        ];
+    }
+
+    /**
+     * Transform a template into a brief description.
+     *
+     * @param template $template The Template.
+     * @return array
+     */
+    protected static function transform_template_brief(template $template) {
+        global $OUTPUT;
+        $exporter = new \core_competency\external\template_exporter($template);
+        $data = $exporter->export($OUTPUT);
+        return [
+            'name' => $data->shortname,
+            'description' => $data->description
+        ];
+    }
+
+    /**
+     * Transform proficiency.
+     *
+     * @param null|bool $proficiency The proficiency.
+     * @return string
+     */
+    protected static function transform_proficiency($proficiency) {
+        return $proficiency !== null ? transform::yesno($proficiency) : '-';
+    }
+
+    /**
+     * Transform user competency.
+     *
+     * @param int $userid The user ID we are exporting for.
+     * @param user_competency|user_competency_plan|user_competency_course $uc The user competency.
+     * @param competency $competency The associated competency.
+     * @param performance_helper $helper The performance helper.
+     * @return array
+     */
+    protected static function transform_user_competency($userid, $uc, competency $competency, performance_helper $helper) {
+        $data = [
+            'proficient' => static::transform_proficiency($uc->get('proficiency')),
+            'rating' => static::transform_competency_grade($competency, $uc->get('grade'), $helper),
+            'timemodified' => $uc->get('timemodified') ? transform::datetime($uc->get('timemodified')) : '-',
+            'timecreated' => $uc->get('timecreated') ? transform::datetime($uc->get('timecreated')) : '-',
+            'created_or_modified_by_you' => transform::yesno($uc->get('usermodified') == $userid),
+        ];
+
+        if ($uc instanceof user_competency) {
+            $reviewer = $uc->get('reviewerid');
+            $data['status'] = (string) user_competency::get_status_name($uc->get('status'));
+            $data['reviewerid'] = $reviewer ? transform::user($reviewer) : '-';
+            $data['reviewer_is_you'] = transform::yesno($reviewer == $userid);
+        }
+
+        return $data;
+    }
+
+    /**
+     * Transform a user evidence.
+     *
+     * @param int $userid The user we are exporting for.
+     * @param user_evidence $ue The evidence of prior learning.
+     * @return array
+     */
+    protected static function transform_user_evidence($userid, user_evidence $ue) {
+        $options = ['context' => $ue->get_context()];
+        return [
+            'name' => format_string($ue->get('name'), true, $options),
+            'description' => format_text($ue->get('description'), $ue->get('descriptionformat'), $options),
+            'url' => $ue->get('url'),
+            'timecreated' => $ue->get('timecreated') ? transform::datetime($ue->get('timecreated')) : '-',
+            'timemodified' => $ue->get('timemodified') ? transform::datetime($ue->get('timemodified')) : '-',
+            'created_or_modified_by_you' => transform::yesno($ue->get('usermodified') == $userid),
+            'competencies' => []
+        ];
+    }
+
+    /**
+     * Loop and export from a recordset.
+     *
+     * @param moodle_recordset $recordset The recordset.
+     * @param string $splitkey The record key to determine when to export.
+     * @param mixed $initial The initial data to reduce from.
+     * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
+     * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
+     * @return void
+     */
+    protected static function recordset_loop_and_export(moodle_recordset $recordset, $splitkey, $initial,
+            callable $reducer, callable $export) {
+
+        $data = $initial;
+        $lastid = null;
+
+        foreach ($recordset as $record) {
+            if ($lastid && $record->{$splitkey} != $lastid) {
+                $export($lastid, $data);
+                $data = $initial;
+            }
+            $data = $reducer($data, $record);
+            $lastid = $record->{$splitkey};
+        }
+        $recordset->close();
+
+        if (!empty($lastid)) {
+            $export($lastid, $data);
+        }
+    }
+}
diff --git a/competency/tests/privacy_test.php b/competency/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..b07db9a
--- /dev/null
@@ -0,0 +1,2086 @@
+<?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_competency
+ * @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, $DB;
+
+use core_privacy\tests\provider_testcase;
+use core_privacy\local\request\contextlist;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\transform;
+use core_privacy\local\request\writer;
+use core_competency\api;
+use core_competency\privacy\provider;
+
+/**
+ * Data provider testcase class.
+ *
+ * @package    core_competency
+ * @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_competency_privacy_testcase extends provider_testcase {
+
+    public function setUp() {
+        global $PAGE;
+        $this->resetAfterTest();
+
+        // We need this or exporters (core_competency\external\exporter) do not receive the right renderer.
+        $PAGE->get_renderer('core');
+    }
+
+    public function test_get_contexts_for_userid_with_usermodified_for_framework() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $cat1 = $dg->create_category();
+        $cat2 = $dg->create_category();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+
+        $sysctx = context_system::instance();
+        $cat1ctx = context_coursecat::instance($cat1->id);
+        $cat2ctx = context_coursecat::instance($cat2->id);
+
+        // Test recovery through framework context.
+        $this->setUser($u1);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []);
+        $f1 = $ccg->create_framework();
+        $contextlist = provider::get_contexts_for_userid($u1->id);
+        $this->assert_contextlist($contextlist, [$sysctx]);
+        $f2 = $ccg->create_framework(['contextid' => $cat1ctx->id]);
+        $contextlist = provider::get_contexts_for_userid($u1->id);
+        $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]);
+
+        // Test recovery of category context alone.
+        $this->setUser($u2);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $ccg->create_framework(['contextid' => $cat2ctx->id]);
+        $contextlist = provider::get_contexts_for_userid($u2->id);
+        $this->assert_contextlist($contextlist, [$cat2ctx]);
+
+        // Test recovery through competency.
+        $this->setUser($u3);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $c1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]);
+        $c2 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]);
+        $c3 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]);
+        $contextlist = provider::get_contexts_for_userid($u3->id);
+        $this->assert_contextlist($contextlist, [$sysctx]);
+        $c4 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]);
+        $c5 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]);
+        $c6 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]);
+        $contextlist = provider::get_contexts_for_userid($u3->id);
+        $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]);
+
+        // Test recovery through related competency.
+        $this->setUser($u4);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+        $cr = $ccg->create_related_competency(['competencyid' => $c1->get('id'), 'relatedcompetencyid' => $c2->get('id')]);
+        $contextlist = provider::get_contexts_for_userid($u4->id);
+        $this->assert_contextlist($contextlist, [$sysctx]);
+        $cr = $ccg->create_related_competency(['competencyid' => $c4->get('id'), 'relatedcompetencyid' => $c5->get('id')]);
+        $contextlist = provider::get_contexts_for_userid($u4->id);
+        $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]);
+    }
+
+    public function test_get_contexts_for_userid_with_usermodified_for_template() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $cat1 = $dg->create_category();
+        $cat2 = $dg->create_category();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+        $cohort = $dg->create_cohort();
+
+        $sysctx = context_system::instance();
+        $cat1ctx = context_coursecat::instance($cat1->id);
+        $cat2ctx = context_coursecat::instance($cat2->id);
+
+        $f1 = $ccg->create_framework();
+        $f2 = $ccg->create_framework(['contextid' => $cat1ctx->id]);
+        $f3 = $ccg->create_framework(['contextid' => $cat2ctx->id]);
+        $cs = [];
+
+        foreach ([$f1, $f2, $f3] as $f) {
+            $cs[$f->get('id')] = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        }
+
+        // Test recovery through template context.
+        $this->setUser($u1);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []);
+        $t1 = $ccg->create_template();
+        $contextlist = provider::get_contexts_for_userid($u1->id);
+        $this->assert_contextlist($contextlist, [$sysctx]);
+        $t2 = $ccg->create_template(['contextid' => $cat1ctx->id]);
+        $contextlist = provider::get_contexts_for_userid($u1->id);
+        $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]);
+
+        // Test recovery of category context alone.
+        $this->setUser($u2);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $ccg->create_template(['contextid' => $cat2ctx->id]);
+        $contextlist = provider::get_contexts_for_userid($u2->id);
+        $this->assert_contextlist($contextlist, [$cat2ctx]);
+
+        // Test recovery through template competency.
+        $this->setUser($u3);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $c1 = $ccg->create_template_competency(['competencyid' => $cs[$f1->get('id')]->get('id'), 'templateid' => $t1->get('id')]);
+        $contextlist = provider::get_contexts_for_userid($u3->id);
+        $this->assert_contextlist($contextlist, [$sysctx]);
+        $c4 = $ccg->create_template_competency(['competencyid' => $cs[$f2->get('id')]->get('id'), 'templateid' => $t2->get('id')]);
+        $contextlist = provider::get_contexts_for_userid($u3->id);
+        $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]);
+
+        // Test recovery through template cohort.
+        $this->setUser($u4);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+        $c1 = $ccg->create_template_cohort(['cohortid' => $cohort->id, 'templateid' => $t1->get('id')]);
+        $contextlist = provider::get_contexts_for_userid($u4->id);
+        $this->assert_contextlist($contextlist, [$sysctx]);
+        $c4 = $ccg->create_template_cohort(['cohortid' => $cohort->id, 'templateid' => $t2->get('id')]);
+        $contextlist = provider::get_contexts_for_userid($u4->id);
+        $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]);
+    }
+
+    public function test_get_contexts_for_userid_with_usermodified_for_course() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+        $u0 = $dg->create_user();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+        $c1ctx = context_course::instance($c1->id);
+        $c2ctx = context_course::instance($c2->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $this->setUser($u1);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $ccg->create_course_competency(['courseid' => $c1->id, 'competencyid' => $comp1->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+
+        $this->setUser($u2);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $ccg->create_course_competency(['courseid' => $c2->id, 'competencyid' => $comp2->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$c2ctx]);
+        $ccg->create_course_competency(['courseid' => $c1->id, 'competencyid' => $comp2->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$c1ctx, $c2ctx]);
+
+        $this->setUser($u3);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $ccs = new \core_competency\course_competency_settings(null, (object) ['courseid' => $c1->id]);
+        $ccs->create();
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$c1ctx, $c2ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]);
+
+        $this->setUser($u4);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+        $ccg->create_user_competency_course(['courseid' => $c2->id, 'userid' => $u0->id, 'competencyid' => $comp1->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$c1ctx, $c2ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), [$c2ctx]);
+    }
+
+    public function test_get_contexts_for_userid_with_usermodified_for_module() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+        $c1 = $dg->create_course();
+        $m1 = $dg->create_module('choice', ['course' => $c1]);
+        $m2 = $dg->create_module('choice', ['course' => $c1]);
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $m1ctx = context_module::instance($m1->cmid);
+        $m2ctx = context_module::instance($m2->cmid);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $this->setUser($u1);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $ccg->create_course_module_competency(['cmid' => $m1->cmid, 'competencyid' => $comp1->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$m1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+
+        $this->setUser($u2);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$m1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $ccg->create_course_module_competency(['cmid' => $m2->cmid, 'competencyid' => $comp2->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$m1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$m2ctx]);
+        $ccg->create_course_module_competency(['cmid' => $m1->cmid, 'competencyid' => $comp2->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$m1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$m1ctx, $m2ctx]);
+    }
+
+    public function test_get_contexts_for_userid_with_usermodified_for_plan() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+        $u0 = $dg->create_user();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u0ctx = context_user::instance($u0->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $this->setUser($u1);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $plan = $ccg->create_plan(['userid' => $u0->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+
+        $this->setUser($u2);
+        $ccg->create_plan_competency(['planid' => $plan->get('id'), 'competencyid' => $comp1->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+
+        $this->setUser($u3);
+        $ccg->create_user_competency_plan(['planid' => $plan->get('id'), 'competencyid' => $comp1->get('id'),
+            'userid' => $u0->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$u0ctx]);
+    }
+
+    public function test_get_contexts_for_userid_with_usermodified_for_competency_data() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+        $u0 = $dg->create_user();
+        $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();
+        $u0ctx = context_user::instance($u0->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $this->setUser($u1);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), []);
+        $uc = $ccg->create_user_competency(['userid' => $u0->id, 'competencyid' => $comp1->get('id'),
+            'reviewerid' => $u6->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), [$u0ctx]);
+
+        $this->setUser($u2);
+        $e = $ccg->create_evidence(['usercompetencyid' => $uc->get('id'), 'actionuserid' => $u5->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), [$u0ctx]);
+
+        $this->setUser($u3);
+        $ccg->create_user_evidence(['userid' => $u0->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), [$u0ctx]);
+
+        $this->setUser($u4);
+        $ccg->create_user_evidence(['userid' => $u0->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), [$u0ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), [$u0ctx]);
+
+        // Comment on competency.
+        $this->allow_anyone_to_comment_anywhere();
+        $this->assert_contextlist(provider::get_contexts_for_userid($u7->id), []);
+        $this->setUser($u7);
+        $comments = $uc->get_comment_object();
+        $comments->add('Hello there!');
+        $this->assert_contextlist(provider::get_contexts_for_userid($u7->id), [$u0ctx]);
+
+        // Comment on plan.
+        $this->assert_contextlist(provider::get_contexts_for_userid($u8->id), []);
+        $this->setUser($u8);
+        $plan = $ccg->create_plan(['userid' => $u0->id]);
+        $comments = $plan->get_comment_object();
+        $comments->add('Hi, planet!');
+        $this->assert_contextlist(provider::get_contexts_for_userid($u8->id), [$u0ctx]);
+    }
+
+    public function test_get_contexts_for_userid_with_actual_data_and_actual_data_is_goooood() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+        $c1 = $dg->create_course();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+
+        $c1ctx = context_course::instance($c1->id);
+        $u1ctx = context_user::instance($u1->id);
+        $u2ctx = context_user::instance($u2->id);
+        $u3ctx = context_user::instance($u3->id);
+        $u4ctx = context_user::instance($u4->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+
+        $ccg->create_plan(['userid' => $u1->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+
+        $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp1->get('id')]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u2ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+
+        $ccg->create_user_competency_course(['userid' => $u3->id, 'competencyid' => $comp1->get('id'), 'courseid' => $c1->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u2ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []);
+
+        $ccg->create_user_evidence(['userid' => $u4->id]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u2ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]);
+        $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), [$u4ctx]);
+    }
+
+    public function test_delete_data_for_user() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+
+        $c1ctx = context_course::instance($c1->id);
+        $u1ctx = context_user::instance($u1->id);
+        $u2ctx = context_user::instance($u2->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]);
+        $ue1b = $ccg->create_user_evidence(['userid' => $u1->id]);
+        $ue2 = $ccg->create_user_evidence(['userid' => $u2->id]);
+        $uec1a = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'),
+            'competencyid' => $comp1->get('id')]);
+        $uec1b = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'),
+            'competencyid' => $comp2->get('id')]);
+        $uec2 = $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'),
+            'competencyid' => $comp1->get('id')]);
+
+        $p1a = $ccg->create_plan(['userid' => $u1->id]);
+        $p1b = $ccg->create_plan(['userid' => $u1->id]);
+        $p2 = $ccg->create_plan(['userid' => $u2->id]);
+        $pc1a = $ccg->create_plan_competency(['planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]);
+        $pc1b = $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]);
+        $pc2 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ucp1a = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1a->get('id'),
+            'competencyid' => $comp1->get('id')]);
+        $ucp1b = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1b->get('id'),
+            'competencyid' => $comp2->get('id')]);
+        $ucp2 = $ccg->create_user_competency_plan(['userid' => $u2->id, 'planid' => $p2->get('id'),
+            'competencyid' => $comp1->get('id')]);
+
+        $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]);
+        $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp2->get('id')]);
+        $uc2 = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp2->get('id')]);
+        $e1a = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id')]);
+        $e1b = $ccg->create_evidence(['usercompetencyid' => $uc1b->get('id')]);
+        $e2 = $ccg->create_evidence(['usercompetencyid' => $uc2->get('id')]);
+
+        $ucc1a = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c1->id,
+            'competencyid' => $comp1->get('id')]);
+        $ucc1b = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c2->id,
+            'competencyid' => $comp1->get('id')]);
+        $ucc2 = $ccg->create_user_competency_course(['userid' => $u2->id, 'courseid' => $c1->id,
+            'competencyid' => $comp1->get('id')]);
+
+        // User 1 comments on both plans.
+        $this->allow_anyone_to_comment_anywhere();
+        $this->setUser($u1);
+        $p1a->get_comment_object()->add('Hi...');
+        $p1a->get_comment_object()->add('mister');
+        $p2->get_comment_object()->add('Ahoy!');
+
+        // User 2 comments on both competencies.
+        $this->setUser($u2);
+        $uc1a->get_comment_object()->add('Hi, too!');
+        $uc1a->get_comment_object()->add('How are you?');
+        $uc2->get_comment_object()->add('Ahoy, too!');
+
+        $p1acommentobj = $p1a->get_comment_object();
+        $p2commentobj = $p2->get_comment_object();
+        $uc1acommentobj = $uc1a->get_comment_object();
+        $uc2commentobj = $uc2->get_comment_object();
+
+        $this->setAdminUser();
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1b->get('id')));
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1b->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1b->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p2->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1b->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1b->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1b->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1b->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id')));
+        $this->assert_has_comments($p1acommentobj);
+        $this->assertEquals(2, $this->get_comments_count($p1acommentobj, $u1->id));
+        $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u2->id));
+        $this->assert_has_comments($p2commentobj);
+        $this->assertEquals(1, $this->get_comments_count($p2commentobj, $u1->id));
+        $this->assertEquals(0, $this->get_comments_count($p2commentobj, $u2->id));
+        $this->assert_has_comments($uc1acommentobj);
+        $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u1->id));
+        $this->assertEquals(2, $this->get_comments_count($uc1acommentobj, $u2->id));
+        $this->assert_has_comments($uc2commentobj);
+        $this->assertEquals(0, $this->get_comments_count($uc2commentobj, $u1->id));
+        $this->assertEquals(1, $this->get_comments_count($uc2commentobj, $u2->id));
+
+        // Deleting user context only.
+        $appctx = new approved_contextlist($u1, 'core_competency', [$u1ctx->id]);
+        provider::delete_data_for_user($appctx);
+
+        $this->assertFalse(\core_competency\user_evidence::record_exists($ue1a->get('id')));
+        $this->assertFalse(\core_competency\user_evidence::record_exists($ue1b->get('id')));
+        $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1a->get('id')));
+        $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1b->get('id')));
+        $this->assertFalse(\core_competency\plan::record_exists($p1a->get('id')));
+        $this->assertFalse(\core_competency\plan::record_exists($p1b->get('id')));
+        $this->assertFalse(\core_competency\plan_competency::record_exists($pc1a->get('id')));
+        $this->assertFalse(\core_competency\plan_competency::record_exists($pc1b->get('id')));
+        $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1a->get('id')));
+        $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1b->get('id')));
+        $this->assertFalse(\core_competency\user_competency::record_exists($uc1a->get('id')));
+        $this->assertFalse(\core_competency\user_competency::record_exists($uc1b->get('id')));
+        $this->assertFalse(\core_competency\evidence::record_exists($e1a->get('id')));
+        $this->assertFalse(\core_competency\evidence::record_exists($e1b->get('id')));
+
+        $this->assert_has_no_comments($p1acommentobj);
+        $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u1->id));
+        $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u2->id));
+        $this->assert_has_no_comments($uc1acommentobj);
+        $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u1->id));
+        $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u2->id));
+
+        // This should not have been affected.
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id')));
+
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p2->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id')));
+        $this->assert_has_comments($p2commentobj);
+        $this->assertEquals(1, $this->get_comments_count($p2commentobj, $u1->id));
+        $this->assertEquals(0, $this->get_comments_count($p2commentobj, $u2->id));
+        $this->assert_has_comments($uc2commentobj);
+        $this->assertEquals(0, $this->get_comments_count($uc2commentobj, $u1->id));
+        $this->assertEquals(1, $this->get_comments_count($uc2commentobj, $u2->id));
+
+        // Deleting course context as well.
+        $appctx = new approved_contextlist($u1, 'core_competency', [$u1ctx->id, $c1ctx->id]);
+        provider::delete_data_for_user($appctx);
+
+        $this->assertFalse(\core_competency\user_competency_course::record_exists($ucc1a->get('id')));
+
+        // The rest belongs to another course, or the other user.
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id')));
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p2->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id')));
+    }
+
+    public function test_delete_data_for_user_with_other_user_context() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+
+        $u1ctx = context_user::instance($u1->id);
+        $u2ctx = context_user::instance($u2->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        // Create a bunch of data for user 1.
+        $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]);
+        $uec1a = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'),
+            'competencyid' => $comp1->get('id')]);
+        $p1a = $ccg->create_plan(['userid' => $u1->id]);
+        $pc1a = $ccg->create_plan_competency(['planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ucp1a = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1a->get('id'),
+            'competencyid' => $comp1->get('id')]);
+        $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]);
+        $e1a = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id')]);
+
+        $p2a = $ccg->create_plan(['userid' => $u2->id]);
+
+        // User 2 comments.
+        $this->allow_anyone_to_comment_anywhere();
+        $this->setUser($u2);
+        $p1a->get_comment_object()->add('Hi...');
+        $p2a->get_comment_object()->add('Hi, hi!');
+        $uc1a->get_comment_object()->add('Hi, too!');
+
+        // Confirm state.
+        $this->setAdminUser();
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id')));
+        $this->assert_has_comments($p1a->get_comment_object());
+        $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id));
+        $this->assert_has_comments($p2a->get_comment_object());
+        $this->assertEquals(1, $this->get_comments_count($p2a->get_comment_object(), $u2->id));
+        $this->assert_has_comments($uc1a->get_comment_object());
+        $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id));
+
+        $this->assertTrue(\core_competency\plan::record_exists($p2a->get('id')));
+
+        // Delete for user 2, but we pass u1 context.
+        provider::delete_data_for_user(new approved_contextlist($u2, 'core_competency', [$u1ctx->id]));
+
+        // Nothing should have happened.
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id')));
+        $this->assert_has_comments($p1a->get_comment_object());
+        $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id));
+        $this->assert_has_comments($p2a->get_comment_object());
+        $this->assertEquals(1, $this->get_comments_count($p2a->get_comment_object(), $u2->id));
+        $this->assert_has_comments($uc1a->get_comment_object());
+        $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id));
+
+        $this->assertTrue(\core_competency\plan::record_exists($p2a->get('id')));
+
+        // Delete for user 2, but we pass u1 and u2 context.
+        $p2acommentobj = $p2a->get_comment_object();
+        provider::delete_data_for_user(new approved_contextlist($u2, 'core_competency', [$u1ctx->id, $u2ctx->id]));
+
+        // The plan got deleted.
+        $this->assertFalse(\core_competency\plan::record_exists($p2a->get('id')));
+        $this->assert_has_no_comments($p2acommentobj);
+
+        // Nothing should have happened for u1.
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id')));
+        $this->assert_has_comments($p1a->get_comment_object());
+        $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id));
+        $this->assert_has_comments($uc1a->get_comment_object());
+        $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id));
+    }
+
+    public function test_delete_data_for_all_users_in_context() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+
+        $c1ctx = context_course::instance($c1->id);
+        $u1ctx = context_user::instance($u1->id);
+        $u2ctx = context_user::instance($u2->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]);
+        $ue1b = $ccg->create_user_evidence(['userid' => $u1->id]);
+        $ue2 = $ccg->create_user_evidence(['userid' => $u2->id]);
+        $uec1a = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'),
+            'competencyid' => $comp1->get('id')]);
+        $uec1b = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'),
+            'competencyid' => $comp2->get('id')]);
+        $uec2 = $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'),
+            'competencyid' => $comp1->get('id')]);
+
+        $p1a = $ccg->create_plan(['userid' => $u1->id]);
+        $p1b = $ccg->create_plan(['userid' => $u1->id]);
+        $p2 = $ccg->create_plan(['userid' => $u2->id]);
+        $pc1a = $ccg->create_plan_competency(['planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]);
+        $pc1b = $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]);
+        $pc2 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ucp1a = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1a->get('id'),
+            'competencyid' => $comp1->get('id')]);
+        $ucp1b = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1b->get('id'),
+            'competencyid' => $comp2->get('id')]);
+        $ucp2 = $ccg->create_user_competency_plan(['userid' => $u2->id, 'planid' => $p2->get('id'),
+            'competencyid' => $comp1->get('id')]);
+
+        $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]);
+        $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp2->get('id')]);
+        $uc2 = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp2->get('id')]);
+        $e1a = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id')]);
+        $e1b = $ccg->create_evidence(['usercompetencyid' => $uc1b->get('id')]);
+        $e2 = $ccg->create_evidence(['usercompetencyid' => $uc2->get('id')]);
+
+        $ucc1a = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c1->id,
+            'competencyid' => $comp1->get('id')]);
+        $ucc1b = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c2->id,
+            'competencyid' => $comp1->get('id')]);
+        $ucc2 = $ccg->create_user_competency_course(['userid' => $u2->id, 'courseid' => $c1->id,
+            'competencyid' => $comp1->get('id')]);
+
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1b->get('id')));
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1b->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1b->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p2->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1b->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1b->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1b->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1b->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id')));
+
+        // Deleting the course 1 context.
+        provider::delete_data_for_all_users_in_context($c1ctx);
+        $this->assertFalse(\core_competency\user_competency_course::record_exists($ucc1a->get('id')));
+        $this->assertFalse(\core_competency\user_competency_course::record_exists($ucc2->get('id')));
+
+        // Not affected.
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue1b->get('id')));
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1b->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p1b->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p2->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc1b->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1b->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc1b->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e1b->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id')));
+
+        // Deleting the user 1 context.
+        provider::delete_data_for_all_users_in_context($u1ctx);
+        $this->assertFalse(\core_competency\user_evidence::record_exists($ue1a->get('id')));
+        $this->assertFalse(\core_competency\user_evidence::record_exists($ue1b->get('id')));
+        $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1a->get('id')));
+        $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1b->get('id')));
+        $this->assertFalse(\core_competency\plan::record_exists($p1a->get('id')));
+        $this->assertFalse(\core_competency\plan::record_exists($p1b->get('id')));
+        $this->assertFalse(\core_competency\plan_competency::record_exists($pc1a->get('id')));
+        $this->assertFalse(\core_competency\plan_competency::record_exists($pc1b->get('id')));
+        $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1a->get('id')));
+        $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1b->get('id')));
+        $this->assertFalse(\core_competency\user_competency::record_exists($uc1a->get('id')));
+        $this->assertFalse(\core_competency\user_competency::record_exists($uc1b->get('id')));
+        $this->assertFalse(\core_competency\evidence::record_exists($e1a->get('id')));
+        $this->assertFalse(\core_competency\evidence::record_exists($e1b->get('id')));
+
+        // Not affected.
+        $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id')));
+        $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id')));
+        $this->assertTrue(\core_competency\plan::record_exists($p2->get('id')));
+        $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id')));
+        $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id')));
+        $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id')));
+        $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id')));
+    }
+
+    public function test_export_data_for_user_in_module_context_where_usermodified_matches() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $c1 = $dg->create_course();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $m1 = $dg->create_module('page', ['course' => $c1]);
+        $m2 = $dg->create_module('page', ['course' => $c1]);
+
+        $m1ctx = context_module::instance($m1->cmid);
+        $m2ctx = context_module::instance($m2->cmid);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $ccg->create_course_module_competency(['competencyid' => $comp3->get('id'), 'cmid' => $m1->cmid]);
+
+        $this->setUser($u1);
+        $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $m1->cmid]);
+        $ccg->create_course_module_competency(['competencyid' => $comp2->get('id'), 'cmid' => $m2->cmid]);
+
+        $this->setUser($u2);
+        $ccg->create_course_module_competency(['competencyid' => $comp3->get('id'), 'cmid' => $m2->cmid]);
+
+        // Export.
+        $this->setAdminUser();
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$m1ctx->id]));
+
+        // Check exported context 1.
+        $data = writer::with_context($m1ctx)->get_data([get_string('competencies', 'core_competency')]);
+        $this->assertCount(1, $data->associations);
+        $this->assertEquals(transform::yesno(true), $data->associations[0]['created_or_modified_by_you']);
+
+        // Check exported context 2.
+        $data = writer::with_context($m2ctx)->get_data([get_string('competencies', 'core_competency')]);
+        $this->assertEmpty($data);
+
+        // Export both contexts.
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$m1ctx->id, $m2ctx->id]));
+
+        // Check exported context 1.
+        $data = writer::with_context($m1ctx)->get_data([get_string('competencies', 'core_competency')]);
+        $this->assertCount(1, $data->associations);
+        $this->assertEquals($comp1->get('shortname'), $data->associations[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->associations[0]['created_or_modified_by_you']);
+
+        // Check exported context 2.
+        $data = writer::with_context($m2ctx)->get_data([get_string('competencies', 'core_competency')]);
+        $this->assertCount(1, $data->associations);
+        $this->assertEquals($comp2->get('shortname'), $data->associations[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->associations[0]['created_or_modified_by_you']);
+    }
+
+    public function test_export_data_for_user_in_course_context_where_usermodified_matches() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+        $u0 = $dg->create_user();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+
+        $c1ctx = context_course::instance($c1->id);
+        $c2ctx = context_course::instance($c2->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp4 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $ccg->create_course_competency(['competencyid' => $comp3->get('id'), 'courseid' => $c1->id]);
+        $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c1->id, 'userid' => $u0->id]);
+
+        $this->setUser($u1);
+        $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id]);
+        $ccg->create_course_competency(['competencyid' => $comp4->get('id'), 'courseid' => $c1->id]);
+        $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id]);
+        $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id, 'userid' => $u0->id]);
+        $ccg->create_user_competency_course(['competencyid' => $comp4->get('id'), 'courseid' => $c1->id, 'userid' => $u0->id]);
+        $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id, 'userid' => $u0->id]);
+        $ccs = new \core_competency\course_competency_settings(null, (object) ['courseid' => $c1->id]);
+        $ccs->create();
+
+        $this->setUser($u2);
+        $ccg->create_course_competency(['competencyid' => $comp3->get('id'), 'courseid' => $c2->id]);
+        $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c2->id, 'userid' => $u0->id]);
+        $ccs = new \core_competency\course_competency_settings(null, (object) ['courseid' => $c2->id]);
+        $ccs->create();
+
+        // Export.
+        $this->setAdminUser();
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$c1ctx->id]));
+
+        // Check exported context 1.
+        $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'associations');
+        $this->assertCount(2, $data->competencies);
+        $this->assertEquals($comp1->get('shortname'), $data->competencies[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->competencies[0]['created_or_modified_by_you']);
+        $this->assertEquals($comp4->get('shortname'), $data->competencies[1]['name']);
+        $this->assertEquals(transform::yesno(true), $data->competencies[1]['created_or_modified_by_you']);
+        $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'settings');
+        $this->assertEquals(transform::yesno(true), $data->created_or_modified_by_you);
+        $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'rated_by_me');
+        $this->assertCount(2, $data->ratings);
+        $this->assertEquals($comp1->get('shortname'), $data->ratings[0]['name']);
+        $this->assertEquals($comp4->get('shortname'), $data->ratings[1]['name']);
+
+        // Check exported context 2.
+        $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'associations');
+        $this->assertEmpty($data);
+        $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'settings');
+        $this->assertEmpty($data);
+        $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'rated_by_me');
+        $this->assertEmpty($data);
+
+        // Export both contexts.
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$c1ctx->id, $c2ctx->id]));
+
+        // Check exported context 1.
+        $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'associations');
+        $this->assertCount(2, $data->competencies);
+        $this->assertEquals($comp1->get('shortname'), $data->competencies[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->competencies[0]['created_or_modified_by_you']);
+        $this->assertEquals($comp4->get('shortname'), $data->competencies[1]['name']);
+        $this->assertEquals(transform::yesno(true), $data->competencies[1]['created_or_modified_by_you']);
+        $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'settings');
+        $this->assertEquals(transform::yesno(true), $data->created_or_modified_by_you);
+        $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'rated_by_me');
+        $this->assertCount(2, $data->ratings);
+        $this->assertEquals($comp1->get('shortname'), $data->ratings[0]['name']);
+        $this->assertEquals($comp4->get('shortname'), $data->ratings[1]['name']);
+
+        // Check exported context 2.
+        $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'associations');
+        $this->assertCount(1, $data->competencies);
+        $this->assertEquals($comp2->get('shortname'), $data->competencies[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->competencies[0]['created_or_modified_by_you']);
+        $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'settings');
+        $this->assertEmpty($data);
+        $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'rated_by_me');
+        $this->assertCount(1, $data->ratings);
+        $this->assertEquals($comp2->get('shortname'), $data->ratings[0]['name']);
+    }
+
+    public function test_export_data_for_user_in_course_context_with_real_data() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $c1 = $dg->create_course();
+        $c2 = $dg->create_course();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+
+        $c1ctx = context_course::instance($c1->id);
+        $c2ctx = context_course::instance($c2->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id,
+            'userid' => $u1->id, 'grade' => 1, 'proficiency' => true]);
+        $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id,
+            'userid' => $u1->id, 'grade' => 2, 'proficiency' => false]);
+        $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id,
+            'userid' => $u1->id, 'grade' => 3, 'proficiency' => false]);
+        $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c2->id,
+            'userid' => $u1->id]);
+
+        $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c1->id, 'userid' => $u2->id]);
+        $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c2->id, 'userid' => $u2->id]);
+
+        // Export user 1, in course 1.
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$c1ctx->id]));
+
+        // Check course 1.
+        $data = writer::with_context($c1ctx)->get_data([get_string('competencies', 'core_competency')]);
+        $this->assertCount(2, $data->ratings);
+        $this->assertEquals($comp1->get('shortname'), $data->ratings[0]['name']);
+        $this->assertEquals('A', $data->ratings[0]['rating']['rating']);
+        $this->assertEquals(transform::yesno(true), $data->ratings[0]['rating']['proficient']);
+        $this->assertEquals($comp2->get('shortname'), $data->ratings[1]['name']);
+        $this->assertEquals('B', $data->ratings[1]['rating']['rating']);
+        $this->assertEquals(transform::yesno(false), $data->ratings[1]['rating']['proficient']);
+
+        // Check course 2.
+        $data = writer::with_context($c2ctx)->get_data([get_string('competencies', 'core_competency')]);
+        $this->assertEmpty($data);
+
+        // Export user 1, in course 2.
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$c2ctx->id]));
+        $data = writer::with_context($c2ctx)->get_data([get_string('competencies', 'core_competency')]);
+        $this->assertCount(2, $data->ratings);
+        $this->assertEquals($comp2->get('shortname'), $data->ratings[0]['name']);
+        $this->assertEquals('C', $data->ratings[0]['rating']['rating']);
+        $this->assertEquals(transform::yesno(false), $data->ratings[0]['rating']['proficient']);
+        $this->assertEquals($comp3->get('shortname'), $data->ratings[1]['name']);
+        $this->assertEquals('-', $data->ratings[1]['rating']['rating']);
+        $this->assertEquals('-', $data->ratings[1]['rating']['proficient']);
+    }
+
+    public function test_export_data_for_user_in_system_and_category_contexts() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $c1 = $dg->create_cohort();
+        $c2 = $dg->create_cohort();
+        $cat1 = $dg->create_category();
+        $cat2 = $dg->create_category();
+
+        $cat1ctx = context_coursecat::instance($cat1->id);
+        $cat2ctx = context_coursecat::instance($cat2->id);
+        $sysctx = context_system::instance();
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+        $u2 = $dg->create_user();
+
+        $this->setUser($u1);
+        $f1 = $ccg->create_framework();
+        $f1bis = $ccg->create_framework();
+        $f2 = $ccg->create_framework(['contextid' => $cat1ctx->id]);
+        $c2a = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]);
+        $c2b = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]);
+
+        $t1 = $ccg->create_template();
+        $t2 = $ccg->create_template(['contextid' => $cat1ctx->id]);
+        $tc2a = $ccg->create_template_competency(['templateid' => $t2->get('id'), 'competencyid' => $c2a->get('id')]);
+        $tch2 = $ccg->create_template_cohort(['templateid' => $t2->get('id'), 'cohortid' => $c1->id]);
+
+        $this->setUser($u2);
+        $f3 = $ccg->create_framework(['contextid' => $cat2ctx->id]);
+        $c1a = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]);
+        $c1b = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]);
+        $c3a = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]);
+        $c3b = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]);
+        $c3c = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]);
+        $c3d = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]);
+        $rc1 = $ccg->create_related_competency(['competencyid' => $c2a->get('id'), 'relatedcompetencyid' => $c2b->get('id')]);
+
+        $t3 = $ccg->create_template(['contextid' => $cat2ctx->id]);
+        $tch1 = $ccg->create_template_cohort(['templateid' => $t1->get('id'), 'cohortid' => $c2->id]);
+        $tc1a = $ccg->create_template_competency(['templateid' => $t1->get('id'), 'competencyid' => $c1a->get('id')]);
+        $tc1b = $ccg->create_template_competency(['templateid' => $t1->get('id'), 'competencyid' => $c2a->get('id')]);
+        $tc3a = $ccg->create_template_competency(['templateid' => $t3->get('id'), 'competencyid' => $c3a->get('id')]);
+
+        $this->setUser($u1);
+        $rc2 = $ccg->create_related_competency(['competencyid' => $c3a->get('id'), 'relatedcompetencyid' => $c3b->get('id')]);
+        $rc3 = $ccg->create_related_competency(['competencyid' => $c3a->get('id'), 'relatedcompetencyid' => $c3c->get('id')]);
+
+        $this->setAdminUser();
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$sysctx->id, $cat1ctx->id, $cat2ctx->id]));
+
+        // Check frameworks for u1 in system.
+        $data = writer::with_context($sysctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks');
+        $this->assertCount(2, $data->frameworks);
+        $this->assertEquals($f1->get('shortname'), $data->frameworks[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->frameworks[0]['created_or_modified_by_you']);
+        $this->assertEquals($f1bis->get('shortname'), $data->frameworks[1]['name']);
+        $this->assertEquals(transform::yesno(true), $data->frameworks[1]['created_or_modified_by_you']);
+        $this->assertEmpty($data->frameworks[0]['competencies']);
+        $this->assertEmpty($data->frameworks[1]['competencies']);
+
+        // Check templates for u1 in system.
+        $data = writer::with_context($sysctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates');
+        $this->assertCount(1, $data->templates);
+        $this->assertEquals($t1->get('shortname'), $data->templates[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->templates[0]['created_or_modified_by_you']);
+        $this->assertEmpty($data->templates[0]['competencies']);
+        $this->assertEmpty($data->templates[0]['cohorts']);
+
+        // Check frameworks for u1 in cat1.
+        $data = writer::with_context($cat1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks');
+        $this->assertCount(1, $data->frameworks);
+        $this->assertEquals($f2->get('shortname'), $data->frameworks[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->frameworks[0]['created_or_modified_by_you']);
+        $this->assertCount(2, $data->frameworks[0]['competencies']);
+        $this->assertEquals($c2a->get('shortname'), $data->frameworks[0]['competencies'][0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->frameworks[0]['competencies'][0]['created_or_modified_by_you']);
+        $this->assertEquals($c2b->get('shortname'), $data->frameworks[0]['competencies'][1]['name']);
+        $this->assertEquals(transform::yesno(true), $data->frameworks[0]['competencies'][1]['created_or_modified_by_you']);
+
+        // Check templates for u1 in cat1.
+        $data = writer::with_context($cat1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates');
+        $this->assertCount(1, $data->templates);
+        $this->assertEquals($t2->get('shortname'), $data->templates[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->templates[0]['created_or_modified_by_you']);
+        $this->assertCount(1, $data->templates[0]['competencies']);
+        $this->assertEquals($c2a->get('shortname'), $data->templates[0]['competencies'][0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->templates[0]['competencies'][0]['created_or_modified_by_you']);
+        $this->assertCount(1, $data->templates[0]['cohorts']);
+        $this->assertEquals($c1->name, $data->templates[0]['cohorts'][0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->templates[0]['cohorts'][0]['created_or_modified_by_you']);
+
+        // Check frameworks for u1 in cat2.
+        $data = writer::with_context($cat2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks');
+        $this->assertCount(1, $data->frameworks);
+        $this->assertEquals($f3->get('shortname'), $data->frameworks[0]['name']);
+        $this->assertEquals(transform::yesno(false), $data->frameworks[0]['created_or_modified_by_you']);
+        $this->assertCount(3, $data->frameworks[0]['competencies']);
+        $competency = $data->frameworks[0]['competencies'][0];
+        $this->assertEquals($c3a->get('shortname'), $competency['name']);
+        $this->assertEquals(transform::yesno(false), $competency['created_or_modified_by_you']);
+        $this->assertCount(2, $competency['related_competencies']);
+        $this->assertEquals($c3b->get('shortname'), $competency['related_competencies'][0]['name']);
+        $this->assertEquals(transform::yesno(true), $competency['related_competencies'][0]['created_or_modified_by_you']);
+        $this->assertEquals($c3c->get('shortname'), $competency['related_competencies'][1]['name']);
+        $this->assertEquals(transform::yesno(true), $competency['related_competencies'][1]['created_or_modified_by_you']);
+        $competency = $data->frameworks[0]['competencies'][1];
+        $this->assertEquals($c3b->get('shortname'), $competency['name']);
+        $this->assertCount(1, $competency['related_competencies']);
+        $competency = $data->frameworks[0]['competencies'][2];
+        $this->assertEquals($c3c->get('shortname'), $competency['name']);
+        $this->assertCount(1, $competency['related_competencies']);
+
+        // Check templates for u1 in cat2.
+        $data = writer::with_context($cat2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates');
+        $this->assertEmpty($data->templates);
+
+        provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$sysctx->id, $cat1ctx->id, $cat2ctx->id]));
+
+        // Check frameworks for u2 in system.
+        $data = writer::with_context($sysctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks');
+        $this->assertCount(1, $data->frameworks);
+        $this->assertEquals($f1->get('shortname'), $data->frameworks[0]['name']);
+        $this->assertEquals(transform::yesno(false), $data->frameworks[0]['created_or_modified_by_you']);
+        $this->assertCount(2, $data->frameworks[0]['competencies']);
+        $competency = $data->frameworks[0]['competencies'][0];
+        $this->assertEquals($c1a->get('shortname'), $competency['name']);
+        $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']);
+        $competency = $data->frameworks[0]['competencies'][1];
+        $this->assertEquals($c1b->get('shortname'), $competency['name']);
+        $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']);
+
+        // Check templates for u2 in system.
+        $data = writer::with_context($sysctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates');
+        $this->assertCount(1, $data->templates);
+        $this->assertEquals($t1->get('shortname'), $data->templates[0]['name']);
+        $this->assertEquals(transform::yesno(false), $data->templates[0]['created_or_modified_by_you']);
+        $this->assertCount(2, $data->templates[0]['competencies']);
+        $competency = $data->templates[0]['competencies'][0];
+        $this->assertEquals($c1a->get('shortname'), $competency['name']);
+        $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']);
+        $competency = $data->templates[0]['competencies'][1];
+        $this->assertEquals($c2a->get('shortname'), $competency['name']);
+        $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']);
+        $this->assertCount(1, $data->templates[0]['cohorts']);
+        $this->assertEquals($c2->name, $data->templates[0]['cohorts'][0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->templates[0]['cohorts'][0]['created_or_modified_by_you']);
+
+        // Check frameworks for u2 in cat1.
+        $data = writer::with_context($cat1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks');
+        $this->assertCount(1, $data->frameworks);
+        $this->assertEquals(transform::yesno(false), $data->frameworks[0]['created_or_modified_by_you']);
+        $this->assertCount(2, $data->frameworks[0]['competencies']);
+        $competency = $data->frameworks[0]['competencies'][0];
+        $this->assertEquals($c2a->get('shortname'), $competency['name']);
+        $this->assertEquals(transform::yesno(false), $competency['created_or_modified_by_you']);
+        $this->assertCount(1, $competency['related_competencies']);
+        $this->assertEquals($c2b->get('shortname'), $competency['related_competencies'][0]['name']);
+        $this->assertEquals(transform::yesno(true), $competency['related_competencies'][0]['created_or_modified_by_you']);
+
+        // Check templates for u2 in system.
+        $data = writer::with_context($cat2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates');
+        $this->assertCount(1, $data->templates);
+        $this->assertEquals($t3->get('shortname'), $data->templates[0]['name']);
+        $this->assertEquals(transform::yesno(true), $data->templates[0]['created_or_modified_by_you']);
+        $this->assertCount(1, $data->templates[0]['competencies']);
+        $competency = $data->templates[0]['competencies'][0];
+        $this->assertEquals($c3a->get('shortname'), $competency['name']);
+        $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']);
+    }
+
+    public function test_export_data_for_user_with_related_learning_plans() {
+        global $DB;
+
+        $path = [
+            get_string('competencies', 'core_competency'),
+            get_string('privacy:path:relatedtome', 'core_competency'),
+            get_string('privacy:path:plans', 'core_competency'),
+        ];
+        $yes = transform::yesno(true);
+        $no = transform::yesno(false);
+
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $u0 = $dg->create_user();
+        $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();
+
+        $dg->role_assign($DB->get_field('role', 'id', ['archetype' => 'manager'], IGNORE_MULTIPLE), $u6->id);
+        $u0ctx = context_user::instance($u0->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp4 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $t = $ccg->create_template();
+        $tc1 = $ccg->create_template_competency(['competencyid' => $comp1->get('id'), 'templateid' => $t->get('id')]);
+        $tc2 = $ccg->create_template_competency(['competencyid' => $comp2->get('id'), 'templateid' => $t->get('id')]);
+        $tc3 = $ccg->create_template_competency(['competencyid' => $comp3->get('id'), 'templateid' => $t->get('id')]);
+        $tc4 = $ccg->create_template_competency(['competencyid' => $comp4->get('id'), 'templateid' => $t->get('id')]);
+
+        $this->setUser($u1);
+        $p1 = $ccg->create_plan(['templateid' => $t->get('id'), 'userid' => $u0->id]);
+
+        $this->setUser($u2);
+        $p2 = $ccg->create_plan(['userid' => $u0->id, 'reviewerid' => $u7->id]);
+
+        $this->setUser($u3);
+        $p1c1 = $ccg->create_plan_competency(['planid' => $p1->get('id'), 'competencyid' => $comp1->get('id')]);
+        $p2c2 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp2->get('id')]);
+        $p2c3 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp3->get('id')]);
+
+        $this->setUser($u4);
+        $uc1 = $ccg->create_user_competency(['competencyid' => $comp1->get('id'), 'userid' => $u0->id, 'grade' => 1,
+            'proficiency' => true]);
+        $uc2 = $ccg->create_user_competency(['competencyid' => $comp2->get('id'), 'userid' => $u0->id, 'grade' => 2,
+            'proficiency' => false]);
+        $uc3 = $ccg->create_user_competency(['competencyid' => $comp3->get('id'), 'userid' => $u0->id]);
+        $uc4 = $ccg->create_user_competency(['competencyid' => $comp4->get('id'), 'userid' => $u0->id, 'reviewerid' => $u5->id]);
+
+        $this->setUser($u5);
+        $p3 = $ccg->create_plan(['userid' => $u0->id]);
+        $p3c1 = $ccg->create_plan_competency(['planid' => $p3->get('id'), 'competencyid' => $comp1->get('id')]);
+        $p3c3 = $ccg->create_plan_competency(['planid' => $p3->get('id'), 'competencyid' => $comp3->get('id')]);
+
+        // Add comments on plan.
+        $this->allow_anyone_to_comment_anywhere();
+        $this->setUser($u0);
+        $p1->get_comment_object()->add('Hello.');
+        $this->setUser($u8);
+        $p1->get_comment_object()->add('Hi.');
+
+        // Export data for user 1.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u0ctx->id]));
+        $planpath = array_merge($path, ["{$p1->get('name')} ({$p1->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($planpath);
+        $this->assertEquals($p1->get('name'), $data->name);
+        $this->assertEquals($yes, $data->created_or_modified_by_you);
+
+        // Export data for user 2.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u0ctx->id]));
+        $planpath = array_merge($path, ["{$p2->get('name')} ({$p2->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($planpath);
+        $this->assertEquals($p2->get('name'), $data->name);
+        $this->assertEquals($yes, $data->created_or_modified_by_you);
+
+        // Export data for user 3.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u3, 'core_competency', [$u0ctx->id]));
+        $planpath = array_merge($path, ["{$p1->get('name')} ({$p1->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($planpath);
+        $this->assertEquals($p1->get('name'), $data->name);
+        $this->assertEquals($no, $data->created_or_modified_by_you);
+        $this->assertCount(1, $data->competencies);
+        $this->assertEquals($comp1->get('shortname'), $data->competencies[0]['name']);
+        $this->assertEquals($yes, $data->competencies[0]['created_or_modified_by_you']);
+
+        $planpath = array_merge($path, ["{$p2->get('name')} ({$p2->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($planpath);
+        $this->assertEquals($p2->get('name'), $data->name);
+        $this->assertEquals($no, $data->created_or_modified_by_you);
+        $competencies = $data->competencies;
+        $this->assertCount(2, $competencies);
+        $this->assertEquals($comp2->get('shortname'), $competencies[0]['name']);
+        $this->assertEquals($yes, $competencies[0]['created_or_modified_by_you']);
+        $this->assertEquals($comp3->get('shortname'), $competencies[1]['name']);
+        $this->assertEquals($yes, $competencies[1]['created_or_modified_by_you']);
+
+        // Export data for user 4.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u4, 'core_competency', [$u0ctx->id]));
+        foreach ([$p1, $p2, $p3] as $plan) {
+            $planpath = array_merge($path, ["{$p2->get('name')} ({$p2->get('id')})"]);
+            $data = writer::with_context($u0ctx)->get_data($planpath);
+            $this->assertEmpty($data);
+        }
+
+        // Export data for user 5.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u5, 'core_competency', [$u0ctx->id]));
+        $planpath = array_merge($path, ["{$p3->get('name')} ({$p3->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($planpath);
+        $this->assertEquals($p3->get('name'), $data->name);
+        $this->assertEquals($yes, $data->created_or_modified_by_you);
+        $this->assertCount(2, $data->competencies);
+        $competency = $data->competencies[0];
+        $this->assertEquals($comp1->get('shortname'), $competency['name']);
+        $this->assertEquals($yes, $competency['created_or_modified_by_you']);
+        $competency = $data->competencies[1];
+        $this->assertEquals($comp3->get('shortname'), $competency['name']);
+        $this->assertEquals($yes, $competency['created_or_modified_by_you']);
+
+        // Do some stuff.
+        $this->setUser($u6);
+        api::complete_plan($p3);
+
+        // Export data for user 6.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u6, 'core_competency', [$u0ctx->id]));
+        $planpath = array_merge($path, ["{$p3->get('name')} ({$p3->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($planpath);
+        $this->assertEquals($p3->get('name'), $data->name);
+        $this->assertEquals($yes, $data->created_or_modified_by_you);
+        $this->assertCount(2, $data->competencies);
+        $competency = $data->competencies[0];
+        $this->assertEquals($comp1->get('shortname'), $competency['name']);
+        $this->assertArrayNotHasKey('created_or_modified_by_you', $competency);
+        $this->assertEquals('A', $competency['rating']['rating']);
+        $this->assertEquals($yes, $competency['rating']['created_or_modified_by_you']);
+        $competency = $data->competencies[1];
+        $this->assertEquals($comp3->get('shortname'), $competency['name']);
+        $this->assertArrayNotHasKey('created_or_modified_by_you', $competency);
+        $this->assertEquals('-', $competency['rating']['rating']);
+        $this->assertEquals($yes, $competency['rating']['created_or_modified_by_you']);
+
+        // Export data for user 7.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u7, 'core_competency', [$u0ctx->id]));
+        $planpath = array_merge($path, ["{$p2->get('name')} ({$p2->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($planpath);
+        $this->assertEquals($p2->get('name'), $data->name);
+        $this->assertEquals($no, $data->created_or_modified_by_you);
+        $this->assertEquals($yes, $data->reviewer_is_you);
+
+        // Export data for user 8.
+        writer::reset();
+        $this->setUser($u8);
+        provider::export_user_data(new approved_contextlist($u8, 'core_competency', [$u0ctx->id]));
+        $planpath = array_merge($path, ["{$p1->get('name')} ({$p1->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($planpath);
+        $this->assertEquals($p1->get('name'), $data->name);
+        $this->assertEquals($no, $data->created_or_modified_by_you);
+        $this->assertEquals($no, $data->reviewer_is_you);
+        $commentspath = array_merge($planpath,  [get_string('commentsubcontext', 'core_comment')]);
+        $data = writer::with_context($u0ctx)->get_data($commentspath);
+        $this->assert_exported_comments(['Hi.'], $data->comments);
+    }
+
+    public function test_export_data_for_user_with_related_competencies() {
+        $path = [
+            get_string('competencies', 'core_competency'),
+            get_string('privacy:path:relatedtome', 'core_competency'),
+            get_string('competencies', 'core_competency'),
+        ];
+        $yes = transform::yesno(true);
+        $no = transform::yesno(false);
+        $makecomppath = function($comp) use ($path) {
+            return array_merge($path, ["{$comp->get('shortname')} ({$comp->get('id')})"]);
+        };
+
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $u0 = $dg->create_user();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+        $u5 = $dg->create_user();
+
+        $u0ctx = context_user::instance($u0->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp4 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $this->setUser($u1);
+        api::add_evidence($u0->id, $comp1->get('id'), $u0ctx, \core_competency\evidence::ACTION_LOG,
+            'privacy:metadata:competency_evidence', 'core_competency');
+        api::add_evidence($u0->id, $comp1->get('id'), $u0ctx, \core_competency\evidence::ACTION_LOG,
+            'privacy:metadata:competency_evidence', 'core_competency');
+        api::add_evidence($u0->id, $comp2->get('id'), $u0ctx, \core_competency\evidence::ACTION_LOG,
+            'privacy:metadata:competency_evidence', 'core_competency');
+
+        $this->setUser($u2);
+        api::add_evidence($u0->id, $comp1->get('id'), $u0ctx, \core_competency\evidence::ACTION_COMPLETE,
+            'privacy:metadata:competency_evidence', 'core_competency', null, false, null, null, $u3->id);
+
+        $this->setUser($u3);
+        api::add_evidence($u0->id, $comp2->get('id'), $u0ctx, \core_competency\evidence::ACTION_OVERRIDE,
+            'privacy:metadata:competency_evidence', 'core_competency', null, false, null, 1, $u4->id, 'Ze note');
+
+        $this->setUser($u4);
+        $uc3 = $ccg->create_user_competency(['userid' => $u0->id, 'competencyid' => $comp3->get('id')]);
+        $uc4 = $ccg->create_user_competency(['userid' => $u0->id, 'competencyid' => $comp4->get('id'), 'reviewerid' => $u2->id]);
+
+        $this->allow_anyone_to_comment_anywhere();
+        $this->setUser($u0);
+        $uc3->get_comment_object()->add('...');
+        $this->setUser($u5);
+        $uc3->get_comment_object()->add('Hello!');
+        $uc3->get_comment_object()->add('It\'s me...');
+
+        // Export data for user 1.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u0ctx->id]));
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp1));
+        $competency = (array) $data;
+        $this->assertEquals($comp1->get('shortname'), $competency['name']);
+        $evidence = $competency['evidence'];
+        $this->assertCount(2, $evidence);
+        $this->assertEquals(get_string('privacy:evidence:action:log', 'core_competency'), $evidence[0]['action']);
+        $this->assertEquals('-', $evidence[0]['actionuserid']);
+        $this->assertEquals($no, $evidence[0]['acting_user_is_you']);
+        $this->assertEquals($yes, $evidence[0]['created_or_modified_by_you']);
+        $this->assertEquals(get_string('privacy:evidence:action:log', 'core_competency'), $evidence[1]['action']);
+        $this->assertEquals('-', $evidence[1]['actionuserid']);
+        $this->assertEquals($no, $evidence[1]['acting_user_is_you']);
+        $this->assertEquals($yes, $evidence[1]['created_or_modified_by_you']);
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp2));
+        $competency = (array) $data;
+        $this->assertEquals($comp2->get('shortname'), $competency['name']);
+        $evidence = $competency['evidence'];
+        $this->assertCount(1, $evidence);
+        $this->assertEquals(get_string('privacy:evidence:action:log', 'core_competency'), $evidence[0]['action']);
+        $this->assertEquals('-', $evidence[0]['actionuserid']);
+        $this->assertEquals($no, $evidence[0]['acting_user_is_you']);
+        $this->assertEquals($yes, $evidence[0]['created_or_modified_by_you']);
+
+        // Export data for user 2.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u0ctx->id]));
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp1));
+        $competency = (array) $data;
+        $this->assertEquals($comp1->get('shortname'), $competency['name']);
+        $evidence = $competency['evidence'];
+        $this->assertCount(1, $evidence);
+        $this->assertEquals(get_string('privacy:evidence:action:complete', 'core_competency'), $evidence[0]['action']);
+        $this->assertEquals($u3->id, $evidence[0]['actionuserid']);
+        $this->assertEquals($no, $evidence[0]['acting_user_is_you']);
+        $this->assertEquals($yes, $evidence[0]['created_or_modified_by_you']);
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp4));
+        $competency = (array) $data;
+        $this->assertEquals($comp4->get('shortname'), $competency['name']);
+        $this->assertCount(0, $competency['evidence']);
+        $this->assertEquals($yes, $competency['rating']['reviewer_is_you']);
+        $this->assertEquals($no, $competency['rating']['created_or_modified_by_you']);
+
+        // Export data for user 3.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u3, 'core_competency', [$u0ctx->id]));
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp1));
+        $competency = (array) $data;
+        $this->assertEquals($comp1->get('shortname'), $competency['name']);
+        $evidence = $competency['evidence'];
+        $this->assertCount(1, $evidence);
+        $this->assertEquals($u3->id, $evidence[0]['actionuserid']);
+        $this->assertEquals($yes, $evidence[0]['acting_user_is_you']);
+        $this->assertEquals($no, $evidence[0]['created_or_modified_by_you']);
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp2));
+        $competency = (array) $data;
+        $this->assertEquals($comp2->get('shortname'), $competency['name']);
+        $evidence = $competency['evidence'];
+        $this->assertCount(1, $evidence);
+        $this->assertEquals(get_string('privacy:evidence:action:override', 'core_competency'), $evidence[0]['action']);
+        $this->assertEquals($u4->id, $evidence[0]['actionuserid']);
+        $this->assertEquals($no, $evidence[0]['acting_user_is_you']);
+        $this->assertEquals($yes, $evidence[0]['created_or_modified_by_you']);
+
+        // Export data for user 4.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u4, 'core_competency', [$u0ctx->id]));
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp2));
+        $competency = (array) $data;
+        $this->assertEquals($comp2->get('shortname'), $competency['name']);
+        $this->assertNull($competency['rating']);
+        $this->assertCount(1, $competency['evidence']);
+        $evidence = $competency['evidence'][0];
+        $this->assertEquals($u4->id, $evidence['actionuserid']);
+        $this->assertEquals($yes, $evidence['acting_user_is_you']);
+        $this->assertEquals($no, $evidence['created_or_modified_by_you']);
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp3));
+        $competency = (array) $data;
+        $this->assertEquals($comp3->get('shortname'), $competency['name']);
+        $this->assertEquals($no, $competency['rating']['reviewer_is_you']);
+        $this->assertEquals($yes, $competency['rating']['created_or_modified_by_you']);
+        $this->assertEmpty($competency['evidence']);
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp4));
+        $competency = (array) $data;
+        $this->assertEquals($comp4->get('shortname'), $competency['name']);
+        $this->assertEquals($no, $competency['rating']['reviewer_is_you']);
+        $this->assertEquals($yes, $competency['rating']['created_or_modified_by_you']);
+        $this->assertEmpty($competency['evidence']);
+
+        // Export data for user 5.
+        $this->setUser($u5);
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u5, 'core_competency', [$u0ctx->id]));
+        $data = writer::with_context($u0ctx)->get_data($makecomppath($comp3));
+        $competency = (array) $data;
+        $this->assertEquals($comp3->get('shortname'), $competency['name']);
+        $data = writer::with_context($u0ctx)->get_data(array_merge($makecomppath($comp3),
+            [get_string('commentsubcontext', 'core_comment')]));
+        $this->assert_exported_comments(['Hello!', 'It\'s me...'], $data->comments);
+    }
+
+    public function test_export_data_for_user_with_related_user_evidence() {
+        $path = [
+            get_string('competencies', 'core_competency'),
+            get_string('privacy:path:relatedtome', 'core_competency'),
+            get_string('privacy:path:userevidence', 'core_competency')
+        ];
+        $yes = transform::yesno(true);
+        $no = transform::yesno(false);
+
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $u0 = $dg->create_user();
+        $u0b = $dg->create_user();
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u4 = $dg->create_user();
+
+        $u0ctx = context_user::instance($u0->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $this->setUser($u0);
+        $ue0 = $ccg->create_user_evidence(['userid' => $u0->id]);
+
+        $this->setUser($u1);
+        $ue1 = $ccg->create_user_evidence(['userid' => $u0->id]);
+        $ue1b = $ccg->create_user_evidence(['userid' => $u0b->id]);
+
+        $this->setUser($u2);
+        $ccg->create_user_evidence_competency(['userevidenceid' => $ue1->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ue2 = $ccg->create_user_evidence(['userid' => $u0->id]);
+        $ue2b = $ccg->create_user_evidence(['userid' => $u0b->id]);
+
+        $this->setUser($u3);
+        $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'), 'competencyid' => $comp2->get('id')]);
+        $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'), 'competencyid' => $comp3->get('id')]);
+
+        // Export for user 1.
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u0ctx->id]));
+        $uepath = array_merge($path, ["{$ue1->get('name')} ({$ue1->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($uepath);
+        $this->assertEquals($ue1->get('name'), $data->name);
+        $this->assertEquals($yes, $data->created_or_modified_by_you);
+        $this->assertEmpty($data->competencies);
+
+        // Export for user 2.
+        provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u0ctx->id]));
+        $uepath = array_merge($path, ["{$ue1->get('name')} ({$ue1->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($uepath);
+        $this->assertEquals($ue1->get('name'), $data->name);
+        $this->assertEquals($no, $data->created_or_modified_by_you);
+        $this->assertCount(1, $data->competencies);
+        $competency = $data->competencies[0];
+        $this->assertEquals($comp1->get('shortname'), $competency['name']);
+        $this->assertEquals($yes, $competency['created_or_modified_by_you']);
+
+        $uepath = array_merge($path, ["{$ue2->get('name')} ({$ue2->get('id')})"]);
+        $data = writer::with_context($u0ctx)->get_data($uepath);
+        $this->assertEquals($ue2->get('name'), $data->name);
+        $this->assertEquals($yes, $data->created_or_modified_by_you);
+        $this->assertEmpty($data->competencies);
+
+        // Export for user 3.
+        provider::export_user_data(new approved_contextlist($u3, 'core_competency', [$u0ctx->id]));
+        $uepath = array_merge($path, ["{$ue2->get('name')} ({$ue2->get('id')})"]);
+        $evidence = writer::with_context($u0ctx)->get_data($uepath);
+        $this->assertEquals($ue2->get('name'), $evidence->name);
+        $this->assertEquals($no, $evidence->created_or_modified_by_you);
+        $this->assertCount(2, $evidence->competencies);
+        $competency = $evidence->competencies[0];
+        $this->assertEquals($comp2->get('shortname'), $competency['name']);
+        $this->assertEquals($yes, $competency['created_or_modified_by_you']);
+        $competency = $evidence->competencies[1];
+        $this->assertEquals($comp3->get('shortname'), $competency['name']);
+        $this->assertEquals($yes, $competency['created_or_modified_by_you']);
+    }
+
+    public function test_export_data_for_user_about_their_learning_plans() {
+        $this->setAdminUser();
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+        $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:plans', 'core_competency')];
+        $yes = transform::yesno(true);
+        $no = transform::yesno(false);
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u1ctx = context_user::instance($u1->id);
+        $u2ctx = context_user::instance($u2->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp4 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $t = $ccg->create_template();
+        $tc2 = $ccg->create_template_competency(['competencyid' => $comp2->get('id'), 'templateid' => $t->get('id')]);
+        $tc3 = $ccg->create_template_competency(['competencyid' => $comp3->get('id'), 'templateid' => $t->get('id')]);
+        $tc4 = $ccg->create_template_competency(['competencyid' => $comp4->get('id'), 'templateid' => $t->get('id')]);
+
+        $p1a = $ccg->create_plan(['userid' => $u1->id, 'templateid' => $t->get('id'),
+            'status' => \core_competency\plan::STATUS_WAITING_FOR_REVIEW]);
+        $p1b = $ccg->create_plan(['userid' => $u1->id]);
+        $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]);
+        $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp4->get('id')]);
+        $p1c = $ccg->create_plan(['userid' => $u1->id]);
+        $ccg->create_plan_competency(['planid' => $p1c->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ccg->create_plan_competency(['planid' => $p1c->get('id'), 'competencyid' => $comp3->get('id')]);
+        $ccg->create_plan_competency(['planid' => $p1c->get('id'), 'competencyid' => $comp4->get('id')]);
+        $p1d = $ccg->create_plan(['userid' => $u1->id]);
+
+        $p2a = $ccg->create_plan(['userid' => $u2->id]);
+        $ccg->create_plan_competency(['planid' => $p2a->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ccg->create_plan_competency(['planid' => $p2a->get('id'), 'competencyid' => $comp2->get('id')]);
+
+        $uc1a = $ccg->create_user_competency(['competencyid' => $comp1->get('id'), 'userid' => $u1->id,
+            'grade' => 2, 'proficiency' => false]);
+        $uc1b = $ccg->create_user_competency(['competencyid' => $comp2->get('id'), 'userid' => $u1->id,
+            'grade' => 3, 'proficiency' => false]);
+        $uc1c = $ccg->create_user_competency(['competencyid' => $comp3->get('id'), 'userid' => $u1->id]);
+
+        // Add comments on plan.
+        $this->allow_anyone_to_comment_anywhere();
+        $this->setUser($u1);
+        $p1a->get_comment_object()->add('Hello.');
+        $p1a->get_comment_object()->add('It\'s me.');
+        $this->setUser($u3);
+        $p1a->get_comment_object()->add('After all these years...');
+
+        // Complete the plan to create archiving, and modify the user competency again.
+        api::complete_plan($p1c);
+        $uc1a->set('grade', 1);
+        $uc1a->set('proficiency', true);
+        $uc1a->update();
+
+        // Export user data in both contexts.
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u1ctx->id, $u2ctx->id]));
+
+        // This plan is based off a template.
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($p1a->get('name'), $data->name);
+        $this->assertEquals($p1a->get_statusname(), $data->status);
+        $this->assertCount(3, $data->competencies);
+        $comp = $data->competencies[0];
+        $this->assertEquals($comp2->get('shortname'), $comp['name']);
+        $this->assertEquals('C', $comp['rating']['rating']);
+        $comp = $data->competencies[1];
+        $this->assertEquals($comp3->get('shortname'), $comp['name']);
+        $this->assertEquals('-', $comp['rating']['rating']);
+        $comp = $data->competencies[2];
+        $this->assertEquals($comp4->get('shortname'), $comp['name']);
+        $this->assertNull($comp['rating']['rating']);
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})",
+            get_string('commentsubcontext', 'core_comment')]));
+        $this->assert_exported_comments(['Hello.', 'It\'s me.', 'After all these years...'], $data->comments);
+
+        // This plan is manually created.
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1b->get('name')} ({$p1b->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($p1b->get('name'), $data->name);
+        $this->assertCount(3, $data->competencies);
+        $comp = $data->competencies[0];
+        $this->assertEquals($comp1->get('shortname'), $comp['name']);
+        $this->assertEquals('A', $comp['rating']['rating']);
+        $comp = $data->competencies[1];
+        $this->assertEquals($comp2->get('shortname'), $comp['name']);
+        $this->assertEquals('C', $comp['rating']['rating']);
+        $comp = $data->competencies[2];
+        $this->assertEquals($comp4->get('shortname'), $comp['name']);
+        $this->assertNull($comp['rating']['rating']);
+
+        // This plan is complete.
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1c->get('name')} ({$p1c->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($p1c->get('name'), $data->name);
+        $this->assertCount(3, $data->competencies);
+        $comp = $data->competencies[0];
+        $this->assertEquals($comp1->get('shortname'), $comp['name']);
+        $this->assertEquals('B', $comp['rating']['rating']);
+        $comp = $data->competencies[1];
+        $this->assertEquals($comp3->get('shortname'), $comp['name']);
+        $this->assertEquals('-', $comp['rating']['rating']);
+        $comp = $data->competencies[2];
+        $this->assertEquals($comp4->get('shortname'), $comp['name']);
+        $this->assertEquals('-', $comp['rating']['rating']);
+
+        // This plan is empty.
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1d->get('name')} ({$p1d->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($p1d->get('name'), $data->name);
+        $this->assertEquals($p1d->get_statusname(), $data->status);
+        $this->assertEmpty($data->competencies);
+
+        // Confirm that we do not get export what we shouldn't.
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p2a->get('name')} ({$p2a->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1b->get('name')} ({$p1b->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1c->get('name')} ({$p1c->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p2a->get('name')} ({$p2a->get('id')})"]));
+        $this->assertEmpty($data);
+
+        // Export for user 2.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u1ctx->id, $u2ctx->id]));
+
+        // Validate the basic plan.
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p2a->get('name')} ({$p2a->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($p2a->get('name'), $data->name);
+        $this->assertCount(2, $data->competencies);
+        $comp = $data->competencies[0];
+        $this->assertEquals($comp1->get('shortname'), $comp['name']);
+        $this->assertNull($comp['rating']);
+        $comp = $data->competencies[1];
+        $this->assertEquals($comp2->get('shortname'), $comp['name']);
+        $this->assertNull($comp['rating']);
+
+        // Confirm that we do not get export what we shouldn't.
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1b->get('name')} ({$p1b->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1c->get('name')} ({$p1c->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1b->get('name')} ({$p1b->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1c->get('name')} ({$p1c->get('id')})"]));
+        $this->assertEmpty($data);
+    }
+
+    public function test_export_data_for_user_about_their_competencies() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+        $path = [get_string('competencies', 'core_competency'), get_string('competencies', 'core_competency')];
+        $no = transform::yesno(false);
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+        $u1ctx = context_user::instance($u1->id);
+        $u2ctx = context_user::instance($u2->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]);
+        $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp2->get('id'),
+            'grade' => 2, 'proficiency' => false]);
+        $uc1c = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp3->get('id')]);
+        $e1a1 = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id'),
+            'action' => \core_competency\evidence::ACTION_COMPLETE, 'grade' => 1]);
+        $e1a2 = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id'), 'note' => 'Not too bad']);
+        $e1b1 = $ccg->create_evidence(['usercompetencyid' => $uc1b->get('id'), 'url' => 'https://example.com']);
+
+        $uc2a = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp1->get('id')]);
+        $uc2b = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp2->get('id')]);
+        $e2a1 = $ccg->create_evidence(['usercompetencyid' => $uc2b->get('id'), 'note' => 'A']);
+        $e2a2 = $ccg->create_evidence(['usercompetencyid' => $uc2b->get('id'), 'note' => 'B']);
+        $e2a3 = $ccg->create_evidence(['usercompetencyid' => $uc2b->get('id'), 'note' => 'C']);
+
+        // Add comments on competency.
+        $this->allow_anyone_to_comment_anywhere();
+        $this->setUser($u1);
+        $uc1a->get_comment_object()->add('Hello.');
+        $uc1a->get_comment_object()->add('It\'s me.');
+        $this->setUser($u3);
+        $uc1a->get_comment_object()->add('After all these years...');
+
+        // Export for user 1 in both contexts.
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u1ctx->id, $u2ctx->id]));
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($comp1->get('shortname'), $data->name);
+        $this->assertEquals('-', $data->rating['rating']);
+        $this->assertCount(2, $data->evidence);
+        $this->assertEquals(get_string('privacy:evidence:action:complete', 'core_competency'), $data->evidence[1]['action']);
+        $this->assertEquals('Not too bad', $data->evidence[0]['note']);
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})",
+            get_string('commentsubcontext', 'core_comment')]));
+        $this->assert_exported_comments(['Hello.', 'It\'s me.', 'After all these years...'], $data->comments);
+
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp2->get('shortname')} ({$comp2->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($comp2->get('shortname'), $data->name);
+        $this->assertEquals('B', $data->rating['rating']);
+        $this->assertEquals($no, $data->rating['proficient']);
+        $this->assertCount(1, $data->evidence);
+        $this->assertEquals('https://example.com', $data->evidence[0]['url']);
+
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp3->get('shortname')} ({$comp3->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($comp3->get('shortname'), $data->name);
+        $this->assertEquals('-', $data->rating['rating']);
+        $this->assertEquals('-', $data->rating['proficient']);
+        $this->assertEmpty($data->evidence);
+
+        // We don't know anything about user 2.
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp2->get('shortname')} ({$comp2->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp3->get('shortname')} ({$comp3->get('id')})"]));
+        $this->assertEmpty($data);
+
+        // Export for user 2 in both contexts.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u1ctx->id, $u2ctx->id]));
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($comp1->get('shortname'), $data->name);
+        $this->assertEquals('-', $data->rating['rating']);
+        $this->assertCount(0, $data->evidence);
+
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp2->get('shortname')} ({$comp2->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($comp2->get('shortname'), $data->name);
+        $this->assertEquals('-', $data->rating['rating']);
+        $this->assertCount(3, $data->evidence);
+        $this->assertEquals('C', $data->evidence[0]['note']);
+        $this->assertEquals('B', $data->evidence[1]['note']);
+        $this->assertEquals('A', $data->evidence[2]['note']);
+
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp3->get('shortname')} ({$comp3->get('id')})"]));
+        $this->assertEmpty($data);
+
+        // We don't know anything about user 1.
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp2->get('shortname')} ({$comp2->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp3->get('shortname')} ({$comp3->get('id')})"]));
+        $this->assertEmpty($data);
+    }
+
+    public function test_export_data_for_user_about_their_user_evidence() {
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+        $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:userevidence', 'core_competency')];
+
+        $u1 = $dg->create_user();
+        $u2 = $dg->create_user();
+        $u3 = $dg->create_user();
+
+        $u1ctx = context_user::instance($u1->id);
+        $u2ctx = context_user::instance($u2->id);
+        $u3ctx = context_user::instance($u3->id);
+
+        $f = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+        $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]);
+
+        $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]);
+        $ue1b = $ccg->create_user_evidence(['userid' => $u1->id]);
+        $ue2a = $ccg->create_user_evidence(['userid' => $u2->id]);
+        $ue3a = $ccg->create_user_evidence(['userid' => $u3->id]);
+        $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp1->get('id')]);
+        $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp2->get('id')]);
+        $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'), 'competencyid' => $comp2->get('id')]);
+        $ccg->create_user_evidence_competency(['userevidenceid' => $ue2a->get('id'), 'competencyid' => $comp2->get('id')]);
+
+        // Export for user 1 in two contexts to make sure.
+        provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u1ctx->id, $u2ctx->id]));
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue1a->get('name')} ({$ue1a->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($ue1a->get('name'), $data->name);
+        $this->assertCount(2, $data->competencies);
+        $this->assertEquals($comp1->get('shortname'), $data->competencies[0]['name']);
+        $this->assertEquals($comp2->get('shortname'), $data->competencies[1]['name']);
+
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue1b->get('name')} ({$ue1b->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($ue1b->get('name'), $data->name);
+        $this->assertCount(1, $data->competencies);
+        $this->assertEquals($comp2->get('shortname'), $data->competencies[0]['name']);
+
+        // We should not have access to other's info.
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue2a->get('name')} ({$ue2a->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$ue2a->get('name')} ({$ue2a->get('id')})"]));
+        $this->assertEmpty($data);
+
+        // Export for user 2 in two contexts to make sure.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u2ctx->id, $u1ctx->id]));
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$ue2a->get('name')} ({$ue2a->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($ue2a->get('name'), $data->name);
+        $this->assertCount(1, $data->competencies);
+        $this->assertEquals($comp2->get('shortname'), $data->competencies[0]['name']);
+
+        // We should not have access to other's info.
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue1a->get('name')} ({$ue1a->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$ue1a->get('name')} ({$ue1a->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue1b->get('name')} ({$ue1b->get('id')})"]));
+        $this->assertEmpty($data);
+        $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$ue1b->get('name')} ({$ue1b->get('id')})"]));
+        $this->assertEmpty($data);
+
+        // Export for user 3.
+        writer::reset();
+        provider::export_user_data(new approved_contextlist($u3, 'core_competency', [$u3ctx->id]));
+        $data = writer::with_context($u3ctx)->get_data(array_merge($path, ["{$ue3a->get('name')} ({$ue3a->get('id')})"]));
+        $this->assertNotEmpty($data);
+        $this->assertEquals($ue3a->get('name'), $data->name);
+        $this->assertCount(0, $data->competencies);
+    }
+
+    /**
+     * Helps testing comments on plans.
+     *
+     * @return void
+     */
+    protected function allow_anyone_to_comment_anywhere() {
+        global $DB;
+        $roleid = $DB->get_field('role', 'id', ['archetype' => 'user'], MUST_EXIST);
+        assign_capability('moodle/competency:plancomment', CAP_ALLOW, $roleid, SYSCONTEXTID, true);
+        assign_capability('moodle/competency:planmanage', CAP_ALLOW, $roleid, SYSCONTEXTID, true);
+        assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $roleid, SYSCONTEXTID, true);
+        assign_capability('moodle/competency:usercompetencycomment', CAP_ALLOW, $roleid, SYSCONTEXTID, true);
+        assign_capability('moodle/competency:usercompetencyview', CAP_ALLOW, $roleid, SYSCONTEXTID, true);
+        accesslib_clear_all_caches_for_unit_testing();
+    }
+
+    /**
+     * Assert the content of a contextlist.
+     *
+     * @param contextlist $contextlist The list.
+     * @param array $expectedcontextsorids The expected content.
+     * @return void
+     */
+    protected function assert_contextlist(contextlist $contextlist, $expectedcontextsorids) {
+        $contextids = array_unique($contextlist->get_contextids());
+        $expectedids = array_unique(array_map(function($item) {
+            return $item instanceof context ? $item->id : $id;
+        }, $expectedcontextsorids));
+        $this->assert_array_match($contextids, $expectedids);
+    }
+
+    /**
+     * Assert that array match.
+     *
+     * @param array $array1 The first one.
+     * @param array $array2 The second one.
+     * @return void
+     */
+    protected function assert_array_match($array1, $array2) {
+        $array1 = (array) (object) $array1;
+        $array2 = (array) (object) $array2;
+        sort($array1);
+        sort($array2);
+        $this->assertEquals($array1, $array2);
+    }
+
+    /**
+     * Assert the content of exported comments.
+     *
+     * @param array $expected The content of the comments.
+     * @param array $comments The exported comments.
+     * @return void
+     */
+    protected function assert_exported_comments($expected, $comments) {
+        $this->assertCount(count($expected), $comments);
+        $contents = array_map(function($comment) {
+            return strip_tags($comment->content);
+        }, $comments);
+        $this->assert_array_match($expected, $contents);
+    }
+
+    /**
+     * Assert that a comment object has comments.
+     *
+     * @param \comment $comment The comment object.
+     * @return void
+     */
+    protected function assert_has_comments(\comment $comment) {
+        global $DB;
+        $this->assertTrue($DB->record_exists('comments', [
+            'contextid' => $comment->get_context()->id,
+            'component' => $comment->get_component(),
+            'commentarea' => $comment->get_commentarea(),
+            'itemid' => $comment->get_itemid()
+        ]));
+    }
+
+    /**
+     * Assert that a comment object does not have any comments.
+     *
+     * @param \comment $comment The comment object.
+     * @return void
+     */
+    protected function assert_has_no_comments(\comment $comment) {
+        global $DB;
+        $this->assertFalse($DB->record_exists('comments', [
+            'contextid' => $comment->get_context()->id,
+            'component' => $comment->get_component(),
+            'commentarea' => $comment->get_commentarea(),
+            'itemid' => $comment->get_itemid()
+        ]));
+    }
+
+    /**
+     * Get the count of comments.
+     *
+     * @param \comment $comment The comment object.
+     * @param int $userid The user ID.
+     * @return int
+     */
+    protected function get_comments_count(\comment $comment, $userid = null) {
+        global $DB;
+        $params = [
+            'contextid' => $comment->get_context()->id,
+            'component' => $comment->get_component(),
+            'commentarea' => $comment->get_commentarea(),
+            'itemid' => $comment->get_itemid(),
+        ];
+        if ($userid) {
+            $params['userid'] = $userid;
+        }
+        return $DB->count_records('comments', $params);
+    }
+}
index b7ffe88..a3f66c3 100644 (file)
@@ -113,6 +113,55 @@ $string['planstatusdraft'] = 'Draft';
 $string['planstatusinreview'] = 'In review';
 $string['planstatuswaitingforreview'] = 'Waiting for review';
 $string['pointsrequiredaremet'] = 'Points required are met';
+$string['privacy:evidence:action:complete'] = 'Complete competency if unrated';
+$string['privacy:evidence:action:log'] = 'Log action';
+$string['privacy:evidence:action:override'] = 'Override competency rating';
+$string['privacy:metadata:competency'] = "A record of the competencies";
+$string['privacy:metadata:competency_coursecomp'] = 'A record of the competencies linked to a course';
+$string['privacy:metadata:competency_coursecompsetting'] = 'A record of the competency settings in a course';
+$string['privacy:metadata:competency_evidence'] = 'A record of the evidence affecting a competency\'s state';
+$string['privacy:metadata:competency_framework'] = 'A record of the competency frameworks';
+$string['privacy:metadata:competency_modulecomp'] = 'A record of the competencies linked to a module';
+$string['privacy:metadata:competency_plan'] = 'A record of the learning plans';
+$string['privacy:metadata:competency_plancomp'] = 'A record of the competencies in a learning plan';
+$string['privacy:metadata:competency_relatedcomp'] = 'A record of the relationship between competencies';
+$string['privacy:metadata:competency_template'] = 'A record of the learning plan templates';
+$string['privacy:metadata:competency_templatecohort'] = 'A record of the cohorts associated with a learning plan template';
+$string['privacy:metadata:competency_templatecomp'] = 'A record of the competencies in a learning plan template';
+$string['privacy:metadata:competency_usercomp'] = 'A record of a user\'s state of competencies';
+$string['privacy:metadata:competency_usercompcourse'] = 'A record of a user\'s state of competencies in a course';
+$string['privacy:metadata:competency_usercompplan'] = 'A record of the state of competencies in a learning plan';
+$string['privacy:metadata:competency_userevidence'] = 'A record of the evidence of prior learning';
+$string['privacy:metadata:competency_userevidencecomp'] = 'A record of the competencies associated with evidence of prior learning';
+$string['privacy:metadata:core_comments'] = 'Comments made on learning plans and competencies';
+$string['privacy:metadata:evidence:action'] = 'The type of action taken with the evidence';
+$string['privacy:metadata:evidence:actionuserid'] = 'The user performing the action';
+$string['privacy:metadata:evidence:desca'] = 'The optional parameters of the translatable evidence description';
+$string['privacy:metadata:evidence:desccomponent'] = 'The component of the translatable evidence description';
+$string['privacy:metadata:evidence:descidentifier'] = 'An identifier of the translatable evidence description';
+$string['privacy:metadata:evidence:grade'] = 'The grade associted with the evidence';
+$string['privacy:metadata:evidence:note'] = 'A non-localised note attached to the evidence';
+$string['privacy:metadata:evidence:url'] = 'A URL associated with the evidence';
+$string['privacy:metadata:plan:description'] = 'The description of the learning plan';
+$string['privacy:metadata:plan:duedate'] = 'The due date of the learning plan';
+$string['privacy:metadata:plan:name'] = 'The name of the learning plan';
+$string['privacy:metadata:plan:reviewerid'] = 'The ID of the reviewer of the learning plan';
+$string['privacy:metadata:plan:status'] = 'The status of the learning palan';
+$string['privacy:metadata:plan:userid'] = 'The ID of the user whose learning plan it is';
+$string['privacy:metadata:timecreated'] = 'The date at which the record was created';
+$string['privacy:metadata:timemodified'] = 'The date at which the record was edited';
+$string['privacy:metadata:usercomp:grade'] = 'The grade given for the competency';
+$string['privacy:metadata:usercomp:proficiency'] = 'Whether proficiency is achieved';
+$string['privacy:metadata:usercomp:reviewerid'] = 'The ID of the reviewer';
+$string['privacy:metadata:usercomp:status'] = 'The status of the competency';
+$string['privacy:metadata:usercomp:userid'] = 'The ID of the user whose competency it is';
+$string['privacy:metadata:userevidence:description'] = 'The description of the evidence';
+$string['privacy:metadata:userevidence:name'] = 'The name of the evidence of prior learning';
+$string['privacy:metadata:userevidence:url'] = 'A URL associated with the evidence';
+$string['privacy:metadata:usermodified'] = 'The user who created or modified the record';
+$string['privacy:path:plans'] = 'Learning plans';
+$string['privacy:path:relatedtome'] = 'Related to me';
+$string['privacy:path:userevidence'] = 'Evidence of prior learning';
 $string['pushcourseratingstouserplans'] = 'Push course ratings to individual learning plans';
 $string['pushcourseratingstouserplans_desc'] = 'Default value for the course setting to update individual learning plans when course competencies are rated.';
 $string['syncplanscohorts'] = 'Sync plans from learning plan template cohorts';