diff --git a/competency/classes/external/performance_helper.php b/competency/classes/external/performance_helper.php index cfe04c6692a..6d39f7405f9 100644 --- a/competency/classes/external/performance_helper.php +++ b/competency/classes/external/performance_helper.php @@ -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 index 00000000000..1fe4e8946d5 --- /dev/null +++ b/competency/classes/privacy/provider.php @@ -0,0 +1,1981 @@ +. + +/** + * Data provider. + * + * @package core_competency + * @copyright 2018 Frédéric Massart + * @author Frédéric Massart + * @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 + * @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 index 00000000000..b07db9ad943 --- /dev/null +++ b/competency/tests/privacy_test.php @@ -0,0 +1,2086 @@ +. + +/** + * Data provider tests. + * + * @package core_competency + * @category test + * @copyright 2018 Frédéric Massart + * @author Frédéric Massart + * @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 + * @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); + } +} diff --git a/lang/en/competency.php b/lang/en/competency.php index b7ffe885e9d..a3f66c39fa9 100644 --- a/lang/en/competency.php +++ b/lang/en/competency.php @@ -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';