<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>.< /** < * Data provider. < * < * @package core_competency < * @copyright 2018 Frédéric Massart < * @author Frédéric Massart <fred@branchup.tech> < * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later < */ <namespace core_competency\privacy;< defined('MOODLE_INTERNAL') || die();use context; use context_course; use context_helper; use context_module; use context_system; use context_user; use moodle_recordset; use core_competency\api; use core_competency\competency; use core_competency\competency_framework; use core_competency\course_competency; use core_competency\course_competency_settings; use core_competency\course_module_competency; use core_competency\evidence; use core_competency\plan; use core_competency\plan_competency; use core_competency\related_competency; use core_competency\template; use core_competency\template_cohort; use core_competency\template_competency; use core_competency\user_competency; use core_competency\user_competency_course; use core_competency\user_competency_plan; use core_competency\user_evidence; use core_competency\user_evidence_competency; use core_competency\external\performance_helper; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_userlist; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\userlist; use core_privacy\local\request\writer; /** * Data provider class. * * @package core_competency * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_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_comment', '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 AND tch.usermodified = :userid2 LEFT JOIN {" . template_competency::TABLE . "} tc ON tc.templateid = tpl.id AND tc.usermodified = :userid3 WHERE tpl.usermodified = :userid1 OR tch.id IS NOT NULL OR tc.id IS NOT NULL"; $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 AND pc.usermodified = :userid3 LEFT JOIN {" . user_competency_plan::TABLE . "} upc ON upc.planid = p.id AND upc.usermodified = :userid4 WHERE p.usermodified = :userid1 OR p.reviewerid = :userid2 OR pc.id IS NOT NULL OR upc.id IS NOT NULL"; $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 AND (e.usermodified = :userid3 OR e.actionuserid = :userid4) LEFT JOIN {" . user_evidence::TABLE . "} ue ON ue.userid = ctx.instanceid AND ctx.contextlevel = :userlevel2 AND ue.usermodified = :userid5 LEFT JOIN {" . user_evidence_competency::TABLE . "} uec ON uec.userevidenceid = ue.id AND uec.usermodified = :userid6 WHERE uc.usermodified = :userid1 OR uc.reviewerid = :userid2 OR e.id IS NOT NULL OR ue.id IS NOT NULL OR uec.id IS NOT NULL"; $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 AND uc.userid = :userid2 LEFT JOIN {" . user_evidence::TABLE . "} ue ON ue.userid = ctx.instanceid AND ctx.contextlevel = :userlevel3 AND ue.userid = :userid3 LEFT JOIN {" . user_competency_course::TABLE . "} ucc ON ucc.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel AND ucc.userid = :userid4 WHERE p.userid = :userid1 OR uc.id IS NOT NULL OR ue.id IS NOT NULL OR ucc.id IS NOT NULL"; $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; } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); $params = ['contextid' => $context->id]; // Add users who have modified the frameworks and related data in the context. $sql = " SELECT cf.usermodified FROM {" . competency_framework::TABLE . "} cf WHERE cf.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT c.usermodified FROM {" . competency_framework::TABLE . "} cf JOIN {" . competency::TABLE . "} c ON c.competencyframeworkid = cf.id WHERE cf.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT cr.usermodified FROM {" . competency_framework::TABLE . "} cf JOIN {" . competency::TABLE . "} c ON c.competencyframeworkid = cf.id JOIN {" . related_competency::TABLE . "} cr ON cr.competencyid = c.id WHERE cf.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); // Add users who have modified the templates and related data in the context. $sql = " SELECT tpl.usermodified FROM {" . template::TABLE . "} tpl WHERE tpl.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT tch.usermodified FROM {" . template::TABLE . "} tpl JOIN {" . template_cohort::TABLE . "} tch ON tch.templateid = tpl.id WHERE tpl.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT tc.usermodified FROM {" . template::TABLE . "} tpl JOIN {" . template_competency::TABLE . "} tc ON tc.templateid = tpl.id WHERE tpl.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); // Add users if userlist is in course context. if (is_a($context, \context_course::class)) { $params = ['courseid' => $context->instanceid]; $sql = " SELECT cc.usermodified FROM {" . course_competency::TABLE . "} cc WHERE cc.courseid = :courseid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT ccs.usermodified FROM {" . course_competency_settings::TABLE . "} ccs WHERE ccs.courseid = :courseid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT ucc.userid, ucc.usermodified FROM {" . user_competency_course::TABLE . "} ucc WHERE ucc.courseid = :courseid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('usermodified', $sql, $params); } else if (is_a($context, \context_module::class)) { // Add users if userlist is in module context. $params = ['moduleid' => $context->instanceid]; $sql = " SELECT cmc.usermodified FROM {" . course_module_competency::TABLE . "} cmc WHERE cmc.cmid = :moduleid"; $userlist->add_from_sql('usermodified', $sql, $params); } else if (is_a($context, \context_user::class)) { $params = ['userid' => $context->instanceid]; // Add users through plan related data. $sql = " SELECT p.userid, p.usermodified, p.reviewerid FROM {" . plan::TABLE . "} p WHERE p.userid = :userid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('usermodified', $sql, $params); $userlist->add_from_sql('reviewerid', $sql, $params); $sql = " SELECT pc.usermodified FROM {" . plan::TABLE . "} p JOIN {" . plan_competency::TABLE . "} pc ON pc.planid = p.id WHERE p.userid = :userid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT upc.usermodified FROM {" . user_competency_plan::TABLE . "} upc WHERE upc.userid = :userid"; $userlist->add_from_sql('usermodified', $sql, $params); // Add users through competency data. $sql = " SELECT uc.userid, uc.usermodified, uc.reviewerid FROM {" . user_competency::TABLE . "} uc WHERE uc.userid = :userid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('usermodified', $sql, $params); $userlist->add_from_sql('reviewerid', $sql, $params); $sql = " SELECT e.usermodified, e.actionuserid FROM {" . user_competency::TABLE . "} uc JOIN {" . evidence::TABLE . "} e ON e.usercompetencyid = uc.id WHERE uc.userid = :userid"; $userlist->add_from_sql('usermodified', $sql, $params); $userlist->add_from_sql('actionuserid', $sql, $params); // Add users through evidence data. $sql = " SELECT ue.userid, ue.usermodified FROM {" . user_evidence::TABLE . "} ue WHERE ue.userid = :userid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT ue.usermodified FROM {" . user_evidence::TABLE . "} ue JOIN {" . user_evidence_competency::TABLE . "} uec ON uec.userevidenceid = ue.id WHERE ue.userid = :userid"; $userlist->add_from_sql('usermodified', $sql, $params); } // Add users who commented in the context. // Note: Comment component must be competency and not core_competency. \core_comment\privacy\provider::get_users_in_context_from_sql( $userlist, 'com', 'competency', 'plan', $context->id); \core_comment\privacy\provider::get_users_in_context_from_sql( $userlist, 'com', 'competency', 'user_competency', $context->id); } /** * Export all user data for the specified user, in the specified contexts. *> * We skip the enabled check for competencies, as there could be historical data. This avoids exceptions thrown from * @param approved_contextlist $contextlist The approved contexts to export information for. > * the {@see api::require_enabled} method, which is called at various points during export via the competency API */ > *public static function export_user_data(approved_contextlist $contextlist) {> $user = $contextlist->get_user(); > // Export even if competencies are not currently enabled. $userid = $user->id; > api::skip_enabled(); >// 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: static::delete_user_competencies_in_course($context->instanceid); 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. 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($context->instanceid, [$userid]); break; } } } /** * Delete multiple users within a single context. * * Here we only delete the private data of users, 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_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); $userids = $userlist->get_userids(); switch ($context->contextlevel) { case CONTEXT_USER: // Only delete the user's information when their context is being deleted. // We do not take any action on other user's contexts because we don't own the data there. if (in_array($context->instanceid, $userids)) { static::delete_user_evidence_of_prior_learning($context->instanceid); static::delete_user_plans($context->instanceid); static::delete_user_competencies($context->instanceid); } break; case CONTEXT_COURSE: static::delete_user_competencies_in_course($context->instanceid, $userids); 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 user(s) in a course. * * @param int $courseid The course ID. * @param int[] $userids The user IDs, if deleting for specific user(s). * @return void */ protected static function delete_user_competencies_in_course($courseid, $userids = []) { global $DB; $params = ['courseid' => $courseid]; $where = "courseid = :courseid"; if (!empty($userids)) { list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); $params = $params + $inparams; $where .= " AND userid {$insql}"; } $DB->delete_records_select(user_competency_course::TABLE, $where, $params); } /** * 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, ucc.id"; $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); } } }