. /** * Privacy Subsystem implementation for mod_customcert. * * @package mod_customcert * @copyright 2018 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace mod_customcert\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; use core_privacy\local\request\contextlist; use core_privacy\local\request\helper; use core_privacy\local\request\transform; use core_privacy\local\request\userlist; use core_privacy\local\request\writer; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem implementation for mod_customcert. * * @copyright 2018 Mark Nelson * @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\plugin\provider, \core_privacy\local\request\core_userlist_provider { /** * Return the fields which contain personal data. * * @param collection $items a reference to the collection to use to store the metadata. * @return collection the updated collection of metadata items. */ public static function get_metadata(collection $items) : collection { $items->add_database_table( 'customcert_issues', [ 'userid' => 'privacy:metadata:customcert_issues:userid', 'customcertid' => 'privacy:metadata:customcert_issues:customcertid', 'code' => 'privacy:metadata:customcert_issues:code', 'emailed' => 'privacy:metadata:customcert_issues:emailed', 'timecreated' => 'privacy:metadata:customcert_issues:timecreated', ], 'privacy:metadata:customcert_issues' ); return $items; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid the userid. * @return contextlist the list of contexts containing user info for the user. */ public static function get_contexts_for_userid(int $userid) : contextlist { $sql = "SELECT c.id FROM {context} c INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modulename INNER JOIN {customcert} customcert ON customcert.id = cm.instance INNER JOIN {customcert_issues} customcertissues ON customcertissues.customcertid = customcert.id WHERE customcertissues.userid = :userid"; $params = [ 'modulename' => 'customcert', 'contextlevel' => CONTEXT_MODULE, 'userid' => $userid, ]; $contextlist = new contextlist(); $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(); if (!$context instanceof \context_module) { return; } // Fetch all users who have a custom certificate. $sql = "SELECT customcertissues.userid FROM {course_modules} cm JOIN {modules} m ON m.id = cm.module AND m.name = :modname JOIN {customcert} customcert ON customcert.id = cm.instance JOIN {customcert_issues} customcertissues ON customcertissues.customcertid = customcert.id WHERE cm.id = :cmid"; $params = [ 'cmid' => $context->instanceid, 'modname' => 'customcert', ]; $userlist->add_from_sql('userid', $sql, $params); } /** * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist. * * @param approved_contextlist $contextlist a list of contexts approved for export. */ public static function export_user_data(approved_contextlist $contextlist) { global $DB; // Filter out any contexts that are not related to modules. $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) { if ($context->contextlevel == CONTEXT_MODULE) { $carry[] = $context->instanceid; } return $carry; }, []); if (empty($cmids)) { return; } $user = $contextlist->get_user(); // Get all the customcert activities associated with the above course modules. $customcertidstocmids = self::get_customcert_ids_to_cmids_from_cmids($cmids); list($insql, $inparams) = $DB->get_in_or_equal(array_keys($customcertidstocmids), SQL_PARAMS_NAMED); $params = array_merge($inparams, ['userid' => $user->id]); $recordset = $DB->get_recordset_select('customcert_issues', "customcertid $insql AND userid = :userid", $params, 'timecreated, id ASC'); self::recordset_loop_and_export($recordset, 'customcertid', [], function($carry, $record) { $carry[] = [ 'code' => $record->code, 'emailed' => transform::yesno($record->emailed), 'timecreated' => transform::datetime($record->timecreated) ]; return $carry; }, function($customcertid, $data) use ($user, $customcertidstocmids) { $context = \context_module::instance($customcertidstocmids[$customcertid]); $contextdata = helper::get_context_data($context, $user); $finaldata = (object) array_merge((array) $contextdata, ['issues' => $data]); helper::export_context_files($context, $user); writer::with_context($context)->export_data([], $finaldata); }); } /** * Delete all data for all users in the specified context. * * @param \context $context the context to delete in. */ public static function delete_data_for_all_users_in_context(\context $context) { global $DB; if (!$context instanceof \context_module) { return; } if (!$cm = get_coursemodule_from_id('customcert', $context->instanceid)) { return; } $DB->delete_records('customcert_issues', ['customcertid' => $cm->instance]); } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist a list of contexts approved for deletion. */ public static function delete_data_for_user(approved_contextlist $contextlist) { global $DB; if (empty($contextlist->count())) { return; } $userid = $contextlist->get_user()->id; foreach ($contextlist->get_contexts() as $context) { if (!$context instanceof \context_module) { continue; } $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST); $DB->delete_records('customcert_issues', ['customcertid' => $instanceid, 'userid' => $userid]); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { global $DB; $context = $userlist->get_context(); if (!$context instanceof \context_module) { return; } $cm = get_coursemodule_from_id('customcert', $context->instanceid); if (!$cm) { // Only customcert module will be handled. return; } $userids = $userlist->get_userids(); list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); $select = "customcertid = :customcertid AND userid $usersql"; $params = ['customcertid' => $cm->instance] + $userparams; $DB->delete_records_select('customcert_issues', $select, $params); } /** * Return a list of Customcert IDs mapped to their course module ID. * * @param array $cmids The course module IDs. * @return array In the form of [$customcertid => $cmid]. */ protected static function get_customcert_ids_to_cmids_from_cmids(array $cmids) { global $DB; list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED); $sql = "SELECT customcert.id, cm.id AS cmid FROM {customcert} customcert JOIN {modules} m ON m.name = :modulename JOIN {course_modules} cm ON cm.instance = customcert.id AND cm.module = m.id WHERE cm.id $insql"; $params = array_merge($inparams, ['modulename' => 'customcert']); return $DB->get_records_sql_menu($sql, $params); } /** * 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); } } }