Export all user data for the specified user, in the specified contexts.

@param   approved_contextlist $contextlist The approved contexts to export information for.
483510
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;
        if (empty($contextlist->count())) {
            return;
        }
        $userid = $contextlist->get_user()->id;
        $contextuser = \context_user::instance($userid);
        list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
        $params = [
            'userid' => $userid,
            'contextlevel' => CONTEXT_USER
         ];
        $params += $inparams;
        $sql = "SELECT me.id,
                       me.rolename,
                       me.enroltime,
                       me.enroltype,
                       mh.name as hostname,
                       mc.fullname
                  FROM {mnetservice_enrol_enrolments} me
                  JOIN {context} ctx
                    ON ctx.instanceid = me.userid
                   AND ctx.contextlevel = :contextlevel
                  JOIN {mnet_host} mh
                    ON mh.id = me.hostid
                  JOIN {mnetservice_enrol_courses} mc
                    ON mc.remoteid = me.remotecourseid
                 WHERE me.userid = :userid
                   AND ctx.id {$insql}";
        $mnetenrolments = $DB->get_records_sql($sql, $params);
        foreach ($mnetenrolments as $mnetenrolment) {
            // The core_enrol data export is organised in:
            // {User Context}/User enrolments/data.json.
            $data[] = (object) [
                'host' => $mnetenrolment->hostname,
                'remotecourseid' => $mnetenrolment->fullname,
                'rolename' => $mnetenrolment->rolename,
                'enroltime' => transform::datetime($mnetenrolment->enroltime),
                'enroltype' => $mnetenrolment->enroltype
            ];
        }
        writer::with_context($contextuser)->export_data(
                [get_string('privacy:metadata:mnetservice_enrol_enrolments', 'mnetservice_enrol')],
                (object)$data
            );
    }
155561
public static function delete_data_for_user(approved_contextlist $contextlist) {
        global $DB;

        // Questions are considered to be 'owned' by the institution, even if they were originally written by a specific
        // user. They are still exported in the list of a users data, but they are not removed.
        // The userid is instead anonymised.

        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
        $contextparams['createdby'] = $contextlist->get_user()->id;
        $DB->set_field_select('question', 'createdby', 0, "
                category IN (SELECT id FROM {question_categories} WHERE contextid {$contextsql})
            AND createdby = :createdby", $contextparams);

        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
        $contextparams['modifiedby'] = $contextlist->get_user()->id;
        $DB->set_field_select('question', 'modifiedby', 0, "
                category IN (SELECT id FROM {question_categories} WHERE contextid {$contextsql})
            AND modifiedby = :modifiedby", $contextparams);
    }
385435
public static function export_user_data(approved_contextlist $contextlist) {
        global $CFG, $DB, $SITE;
        if (empty($contextlist)) {
            return;
        }

        // Use the Moodle XML Data format.
        // It is the only lossless format that we support.
        $format = "xml";
        require_once($CFG->dirroot . "/question/format/{$format}/format.php");

        // THe export system needs questions in a particular format.
        // The easiest way to fetch these is with get_questions_category() which takes the details of a question
        // category.
        // We fetch the root question category for each context and the get_questions_category function recurses to
        // After fetching them, we filter out any not created or modified by the requestor.
        $user = $contextlist->get_user();
        $userid = $user->id;

        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
        $categories = $DB->get_records_select('question_categories', "contextid {$contextsql} AND parent = 0", $contextparams);

        $classname = "qformat_{$format}";
        foreach ($categories as $category) {
            $context = \context::instance_by_id($category->contextid);

            $questions = get_questions_category($category, true);
            $questions = array_filter($questions, function($question) use ($userid) {
                return ($question->createdby == $userid) || ($question->modifiedby == $userid);
            }, ARRAY_FILTER_USE_BOTH);

            if (empty($questions)) {
                continue;
            }

            $qformat = new $classname();
            $qformat->setQuestions($questions);

            $qformat->setContexts([$context]);
            $qformat->setContexttofile(true);

            // We do not know which course this belongs to, and it's not actually used except in error, so use Site.
            $qformat->setCourse($SITE);
            $content = '';
            if ($qformat->exportpreprocess()) {
                $content = $qformat->exportprocess(false);
            }

            $subcontext = [
                get_string('questionbank', 'core_question'),
            ];
            writer::with_context($context)->export_custom_file($subcontext, 'questions.xml', $content);
        }
    }
135795
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        if (empty($contextlist->count())) {
            return;
        }
        $userid = $contextlist->get_user()->id;
        list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
        $params = [
            'contextlevel' => CONTEXT_COURSE,
            'userid' => $userid
         ];
        $params += $inparams;
        $sql = "SELECT ue.id,
                       ue.status,
                       ue.timestart,
                       ue.timeend,
                       ue.timecreated,
                       ue.timemodified,
                       e.enrol,
                       ctx.id as contextid
                  FROM {user_enrolments} ue
                  JOIN {enrol} e
                    ON e.id = ue.enrolid
                   AND ue.userid = :userid
                  JOIN {context} ctx
                    ON ctx.instanceid = e.courseid
                   AND ctx.contextlevel = :contextlevel
                 WHERE ctx.id $insql
                 ORDER BY ctx.id, e.enrol";
        $data = [];
        $lastcontextid = null;
        $lastenrol = null;
        $path = [get_string('privacy:metadata:user_enrolments', 'core_enrol')];
        $flush = function($lastcontextid, $lastenrol, $data) use ($path) {
            $context = \context::instance_by_id($lastcontextid);
            writer::with_context($context)->export_related_data(
                $path,
                $lastenrol,
                (object)$data
            );
        };
        $userenrolments = $DB->get_recordset_sql($sql, $params);
        foreach ($userenrolments as $userenrolment) {
            if (($lastcontextid && $lastcontextid != $userenrolment->contextid) ||
                    ($lastenrol && $lastenrol != $userenrolment->enrol)) {
                $flush($lastcontextid, $lastenrol, $data);
                $data = [];
            }
            $data[] = (object) [
                'status' => $userenrolment->status,
                'timecreated' => transform::datetime($userenrolment->timecreated),
                'timemodified' => transform::datetime($userenrolment->timemodified),
                'timestart' => transform::datetime($userenrolment->timestart),
                'timeend' => transform::datetime($userenrolment->timeend)
            ];
            $lastcontextid = $userenrolment->contextid;
            $lastenrol = $userenrolment->enrol;
        }
        if (!empty($data)) {
            $flush($lastcontextid, $lastenrol, $data);
        }
        $userenrolments->close();
    }
992262
protected static function export_user_calendar_event_data(approved_contextlist $contextlist) {
        // Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
        $eventdetails = self::get_calendar_event_details_by_contextlist($contextlist);

        // Multiple Calendar Events of the same eventtype and time can exist for a context, so collate them for export.
        $eventrecords = [];
        foreach ($eventdetails as $eventdetail) {
            // Create an array key based on the contextid, eventtype, and time.
            $key = $eventdetail->contextid . $eventdetail->eventtype . $eventdetail->timestart;

            if (array_key_exists($key, $eventrecords) === false) {
                $eventrecords[$key] = [ $eventdetail ];
            } else {
                $eventrecords[$key] = array_merge($eventrecords[$key], [$eventdetail]);
            }
        }
        $eventdetails->close();

        // Export Calendar Event data.
        foreach ($eventrecords as $eventrecord) {
            $index = (count($eventrecord) > 1) ? 1 : 0;

            foreach ($eventrecord as $event) {
                // Export the events using the structure Calendar/Events/{datetime}/{eventtype}-event.json.
                $subcontexts = [
                    get_string('calendar', 'calendar'),
                    get_string('events', 'calendar'),
                    date('c', $event->timestart)
                ];
                $name = $event->eventtype . '-event';

                // Use name {eventtype}-event-{index}.json if multiple eventtypes and time exists at the same context.
                if ($index != 0) {
                    $name .= '-' . $index;
                    $index++;
                }

                $eventdetails = (object) [
                    'name' => $event->name,
                    'description' => $event->description,
                    'location' => $event->location,
                    'eventtype' => $event->eventtype,
                    'timestart' => transform::datetime($event->timestart),
                    'timeduration' => $event->timeduration
                ];

                $context = \context::instance_by_id($event->contextid);
                writer::with_context($context)->export_related_data($subcontexts, $name, $eventdetails);
            }
        }
    }
739170
public static function delete_data_for_user(approved_contextlist $contextlist) {
        global $DB;
        $userid = $contextlist->get_user()->id;
        $prefnames = array_reduce($contextlist->get_contexts(), function($carry, $context) {
            if ($context->contextlevel == CONTEXT_BLOCK) {
                $carry[] = "block{$context->instanceid}hidden";
                $carry[] = "docked_block_instance_{$context->instanceid}";
            }
            return $carry;
        }, []);

        if (empty($prefnames)) {
            return;
        }

        list($insql, $inparams) = $DB->get_in_or_equal($prefnames, SQL_PARAMS_NAMED);
        $sql = "userid = :userid AND name $insql";
        $params = array_merge($inparams, ['userid' => $userid]);
        $DB->delete_records_select('user_preferences', $sql, $params);
    }
1014562
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        // If the user has block_community data, then only the User context should be present so get the first context.
        $contexts = $contextlist->get_contexts();
        if (count($contexts) == 0) {
            return;
        }
        $context = reset($contexts);

        // Sanity check that context is at the User context level, then get the userid.
        if ($context->contextlevel !== CONTEXT_USER) {
            return;
        }
        $userid = $context->instanceid;

        // The block_community data export is organised in: {User Context}/Community Finder/My communities/data.json.
        $subcontext = [
            get_string('pluginname', 'block_community'),
            get_string('mycommunities', 'block_community')
        ];

        $sql = "SELECT bc.id as id,
                       bc.coursename as name,
                       bc.coursedescription as description,
                       bc.courseurl as url,
                       bc.imageurl as imageurl
                  FROM {block_community} bc
                 WHERE bc.userid = :userid
              ORDER BY bc.coursename";

        $params = [
            'userid' => $userid
        ];

        $communities = $DB->get_records_sql($sql, $params);

        $data = (object) [
            'communities' => $communities
        ];

        writer::with_context($context)->export_data($subcontext, $data);
    }
930972
public static function export_user_data(approved_contextlist $contextlist) {
        list($db, $table) = static::get_database_and_table();
        if (!$db || !$table) {
            return;
        }

        $userid = $contextlist->get_user()->id;
        list($insql, $inparams) = $db->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);

        $sql = "(userid = :userid1 OR relateduserid = :userid2 OR realuserid = :userid3) AND contextid $insql";
        $params = array_merge($inparams, [
            'userid1' => $userid,
            'userid2' => $userid,
            'userid3' => $userid,
        ]);

        $path = static::get_export_subcontext();
        $flush = function($lastcontextid, $data) use ($path) {
            $context = context::instance_by_id($lastcontextid);
            writer::with_context($context)->export_data($path, (object) ['logs' => $data]);
        };

        $lastcontextid = null;
        $data = [];
        $recordset = $db->get_recordset_select($table, $sql, $params, 'contextid, timecreated, id');
        foreach ($recordset as $record) {
            if ($lastcontextid && $lastcontextid != $record->contextid) {
                $flush($lastcontextid, $data);
                $data = [];
            }
            $data[] = helper::transform_standard_log_record_for_userid($record, $userid);
            $lastcontextid = $record->contextid;
        }
        if ($lastcontextid) {
            $flush($lastcontextid, $data);
        }
        $recordset->close();
    }
425361
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) {
            $conditions = [
                'module'        => 'notes',
                'courseid'      => $context->instanceid,
                'usermodified'  => $userid
            ];

            $DB->delete_records('post', $conditions);
        }
    }
1013733
public static function export_user_data(approved_contextlist $contextlist) {
        $contexts = $contextlist->get_contexts();
        foreach ($contexts as $context) {
            \core_comment\privacy\provider::export_comments(
                    $context,
                    'block_comments',
                    'page_comments',
                    0,
                    []
            );
        }
    }
1299096
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        $user = $contextlist->get_user();
        $userid = $user->id;
        $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) use ($userid) {
            if ($context->contextlevel == CONTEXT_COURSE) {
                $carry[$context->contextlevel][] = $context;

            } else if ($context->contextlevel == CONTEXT_USER) {
                $carry[$context->contextlevel][] = $context;

            }

            return $carry;
        }, [
            CONTEXT_USER => [],
            CONTEXT_COURSE => []
        ]);

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

        // Export the outcomes.
        static::export_user_data_outcomes_in_contexts($contextlist);

        // Export the scales.
        static::export_user_data_scales_in_contexts($contextlist);

        // Export the historical grades which have become orphans (their grade items were deleted).
        // We place those in ther user context of the graded user.
        $userids = array_values(array_map(function($context) {
            return $context->instanceid;
        }, $contexts[CONTEXT_USER]));
        if (!empty($userids)) {

            // Export own historical grades and related ones.
            list($inuseridsql, $inuseridparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
            list($inusermodifiedsql, $inusermodifiedparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
            list($inloggedusersql, $inloggeduserparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
            $usercontext = $contexts[CONTEXT_USER];
            $gghfields = static::get_fields_sql('grade_grades_history', 'ggh', 'ggh_');
            $sql = "
                SELECT $gghfields, ctx.id as ctxid
                  FROM {grade_grades_history} ggh
                  JOIN {context} ctx
                    ON ctx.instanceid = ggh.userid
                   AND ctx.contextlevel = :userlevel
             LEFT JOIN {grade_items} gi
                    ON gi.id = ggh.itemid
                 WHERE gi.id IS NULL
                   AND (ggh.userid $inuseridsql
                    OR ggh.usermodified $inusermodifiedsql
                    OR ggh.loggeduser $inloggedusersql)
                   AND (ggh.userid = :userid1
                    OR ggh.usermodified = :userid2
                    OR ggh.loggeduser = :userid3)
              ORDER BY ggh.userid, ggh.timemodified, ggh.id";
            $params = array_merge($inuseridparams, $inusermodifiedparams, $inloggeduserparams,
                ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid, 'userlevel' => CONTEXT_USER]);

            $deletedstr = get_string('privacy:request:unknowndeletedgradeitem', 'core_grades');
            $recordset = $DB->get_recordset_sql($sql, $params);
            static::recordset_loop_and_export($recordset, 'ctxid', [], function($carry, $record) use ($deletedstr, $userid) {
                $context = context::instance_by_id($record->ctxid);
                $gghrecord = static::extract_record($record, 'ggh_');

                // Orphan grades do not have items, so we do not recreate a grade_grade item, and we do not format grades.
                $carry[] = [
                    'name' => $deletedstr,
                    'graded_user_was_you' => transform::yesno($userid == $gghrecord->userid),
                    'grade' => $gghrecord->finalgrade,
                    'feedback' => format_text($gghrecord->feedback, $gghrecord->feedbackformat, ['context' => $context]),
                    'information' => format_text($gghrecord->information, $gghrecord->informationformat, ['context' => $context]),
                    'timemodified' => transform::datetime($gghrecord->timemodified),
                    'logged_in_user_was_you' => transform::yesno($userid == $gghrecord->loggeduser),
                    'author_of_change_was_you' => transform::yesno($userid == $gghrecord->usermodified),
                    'action' => static::transform_history_action($gghrecord->action)
                ];

                return $carry;

            }, function($ctxid, $data) use ($rootpath) {
                $context = context::instance_by_id($ctxid);
                writer::with_context($context)->export_related_data($rootpath, 'history', (object) ['grades' => $data]);
            });
        }

        // Find out the course IDs.
        $courseids = array_values(array_map(function($context) {
            return $context->instanceid;
        }, $contexts[CONTEXT_COURSE]));
        if (empty($courseids)) {
            return;
        }
        list($incoursesql, $incourseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);

        // Ensure that the grades are final and do not need regrading.
        array_walk($courseids, function($courseid) {
            grade_regrade_final_grades($courseid);
        });

        // Export own grades.
        $ggfields = static::get_fields_sql('grade_grade', 'gg', 'gg_');
        $gifields = static::get_fields_sql('grade_item', 'gi', 'gi_');
        $scalefields = static::get_fields_sql('grade_scale', 'sc', 'sc_');
        $sql = "
            SELECT $ggfields, $gifields, $scalefields
              FROM {grade_grades} gg
              JOIN {grade_items} gi
                ON gi.id = gg.itemid
         LEFT JOIN {scale} sc
                ON sc.id = gi.scaleid
             WHERE gi.courseid $incoursesql
               AND gg.userid = :userid
          ORDER BY gi.courseid, gi.id, gg.id";
        $params = array_merge($incourseparams, ['userid' => $userid]);

        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
            $context = context_course::instance($record->gi_courseid);
            $gg = static::extract_grade_grade_from_record($record);
            $carry[] = static::transform_grade($gg, $context, false);

            return $carry;

        }, function($courseid, $data) use ($rootpath) {
            $context = context_course::instance($courseid);

            $pathtofiles = [
                get_string('grades', 'core_grades'),
                get_string('feedbackfiles', 'core_grades')
            ];
            foreach ($data as $key => $grades) {
                $gg = $grades['gradeobject'];
                writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
                    GRADE_FEEDBACK_FILEAREA, $gg->id);
                unset($data[$key]['gradeobject']); // Do not want to export this later.
            }

            writer::with_context($context)->export_data($rootpath, (object) ['grades' => $data]);
        });

        // Export own historical grades in courses.
        $gghfields = static::get_fields_sql('grade_grades_history', 'ggh', 'ggh_');
        $sql = "
            SELECT $gghfields, $gifields, $scalefields
              FROM {grade_grades_history} ggh
              JOIN {grade_items} gi
                ON gi.id = ggh.itemid
         LEFT JOIN {scale} sc
                ON sc.id = gi.scaleid
             WHERE gi.courseid $incoursesql
               AND ggh.userid = :userid
          ORDER BY gi.courseid, ggh.timemodified, ggh.id";
        $params = array_merge($incourseparams, ['userid' => $userid]);

        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
            $context = context_course::instance($record->gi_courseid);
            $gg = static::extract_grade_grade_from_record($record, true);
            $carry[] = array_merge(static::transform_grade($gg, $context, true), [
                'action' => static::transform_history_action($record->ggh_action)
            ]);
            return $carry;

        }, function($courseid, $data) use ($rootpath) {
            $context = context_course::instance($courseid);

            $pathtofiles = [
                get_string('grades', 'core_grades'),
                get_string('feedbackhistoryfiles', 'core_grades')
            ];
            foreach ($data as $key => $grades) {
                $gg = $grades['gradeobject'];
                writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
                    GRADE_HISTORY_FEEDBACK_FILEAREA, $gg->historyid);
                unset($data[$key]['gradeobject']); // Do not want to export this later.
            }

            writer::with_context($context)->export_related_data($rootpath, 'history', (object) ['grades' => $data]);
        });

        // Export edits of categories history.
        $sql = "
            SELECT gch.id, gch.courseid, gch.fullname, gch.timemodified, gch.action
              FROM {grade_categories_history} gch
             WHERE gch.courseid $incoursesql
               AND gch.loggeduser = :userid
          ORDER BY gch.courseid, gch.timemodified, gch.id";
        $params = array_merge($incourseparams, ['userid' => $userid]);
        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
            $carry[] = [
                'name' => $record->fullname,
                'timemodified' => transform::datetime($record->timemodified),
                'logged_in_user_was_you' => transform::yesno(true),
                'action' => static::transform_history_action($record->action),
            ];
            return $carry;

        }, function($courseid, $data) use ($relatedtomepath) {
            $context = context_course::instance($courseid);
            writer::with_context($context)->export_related_data($relatedtomepath, 'categories_history',
                (object) ['modified_records' => $data]);
        });

        // Export edits of items history.
        $sql = "
            SELECT gih.id, gih.courseid, gih.itemname, gih.itemmodule, gih.iteminfo, gih.timemodified, gih.action
              FROM {grade_items_history} gih
             WHERE gih.courseid $incoursesql
               AND gih.loggeduser = :userid
          ORDER BY gih.courseid, gih.timemodified, gih.id";
        $params = array_merge($incourseparams, ['userid' => $userid]);
        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
            $carry[] = [
                'name' => $record->itemname,
                'module' => $record->itemmodule,
                'info' => $record->iteminfo,
                'timemodified' => transform::datetime($record->timemodified),
                'logged_in_user_was_you' => transform::yesno(true),
                'action' => static::transform_history_action($record->action),
            ];
            return $carry;

        }, function($courseid, $data) use ($relatedtomepath) {
            $context = context_course::instance($courseid);
            writer::with_context($context)->export_related_data($relatedtomepath, 'items_history',
                (object) ['modified_records' => $data]);
        });

        // Export edits of grades in course.
        $sql = "
            SELECT $ggfields, $gifields, $scalefields
              FROM {grade_grades} gg
              JOIN {grade_items} gi
                ON gg.itemid = gi.id
         LEFT JOIN {scale} sc
                ON sc.id = gi.scaleid
             WHERE gi.courseid $incoursesql
               AND gg.userid <> :userid1    -- Our grades have already been exported.
               AND gg.usermodified = :userid2
          ORDER BY gi.courseid, gg.timemodified, gg.id";
        $params = array_merge($incourseparams, ['userid1' => $userid, 'userid2' => $userid]);
        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
            $context = context_course::instance($record->gi_courseid);
            $gg = static::extract_grade_grade_from_record($record);
            $carry[] = array_merge(static::transform_grade($gg, $context, false), [
                'userid' => transform::user($gg->userid),
                'created_or_modified_by_you' => transform::yesno(true),
            ]);
            return $carry;

        }, function($courseid, $data) use ($relatedtomepath) {
            $context = context_course::instance($courseid);

            $pathtofiles = [
                get_string('grades', 'core_grades'),
                get_string('feedbackfiles', 'core_grades')
            ];
            foreach ($data as $key => $grades) {
                $gg = $grades['gradeobject'];
                writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
                    GRADE_FEEDBACK_FILEAREA, $gg->id);
                unset($data[$key]['gradeobject']); // Do not want to export this later.
            }

            writer::with_context($context)->export_related_data($relatedtomepath, 'grades', (object) ['grades' => $data]);
        });

        // Export edits of grades history in course.
        $sql = "
            SELECT $gghfields, $gifields, $scalefields, ggh.loggeduser AS loggeduser
              FROM {grade_grades_history} ggh
              JOIN {grade_items} gi
                ON ggh.itemid = gi.id
         LEFT JOIN {scale} sc
                ON sc.id = gi.scaleid
             WHERE gi.courseid $incoursesql
               AND ggh.userid <> :userid1   -- We've already exported our history.
               AND (ggh.loggeduser = :userid2
                OR ggh.usermodified = :userid3)
          ORDER BY gi.courseid, ggh.timemodified, ggh.id";
        $params = array_merge($incourseparams, ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid]);
        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) use ($userid) {
            $context = context_course::instance($record->gi_courseid);
            $gg = static::extract_grade_grade_from_record($record, true);
            $carry[] = array_merge(static::transform_grade($gg, $context, true), [
                'userid' => transform::user($gg->userid),
                'logged_in_user_was_you' => transform::yesno($userid == $record->loggeduser),
                'author_of_change_was_you' => transform::yesno($userid == $gg->usermodified),
                'action' => static::transform_history_action($record->ggh_action),
            ]);
            return $carry;

        }, function($courseid, $data) use ($relatedtomepath) {
            $context = context_course::instance($courseid);

            $pathtofiles = [
                get_string('grades', 'core_grades'),
                get_string('feedbackhistoryfiles', 'core_grades')
            ];
            foreach ($data as $key => $grades) {
                $gg = $grades['gradeobject'];
                writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
                    GRADE_HISTORY_FEEDBACK_FILEAREA, $gg->historyid);
                unset($data[$key]['gradeobject']); // Do not want to export this later.
            }

            writer::with_context($context)->export_related_data($relatedtomepath, 'grades_history',
                (object) ['modified_records' => $data]);
        });
    }
1095159
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;
        $context = \context_system::instance();
        if (!$contextlist->count() || !in_array($context->id, $contextlist->get_contextids())) {
            return;
        }

        $user = $contextlist->get_user();
        $sql = "SELECT id, userid, tagcollid, name, rawname, isstandard, description, descriptionformat, flag, timemodified
            FROM {tag} WHERE userid = ?";
        $rs = $DB->get_recordset_sql($sql, [$user->id]);
        foreach ($rs as $record) {
            $subcontext = [get_string('tags', 'tag'), $record->id];
            $tag = (object)[
                'id' => $record->id,
                'userid' => transform::user($record->userid),
                'name' => $record->name,
                'rawname' => $record->rawname,
                'isstandard' => transform::yesno($record->isstandard),
                'description' => writer::with_context($context)->rewrite_pluginfile_urls($subcontext,
                    'tag', 'description', $record->id, strval($record->description)),
                'descriptionformat' => $record->descriptionformat,
                'flag' => $record->flag,
                'timemodified' => transform::datetime($record->timemodified),

            ];
            writer::with_context($context)->export_data($subcontext, $tag);
            writer::with_context($context)->export_area_files($subcontext, 'tag', 'description', $record->id);
        }
        $rs->close();
    }
936400
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        if (!$contextlist->count()) {
            return;
        }

        $user = $contextlist->get_user();

        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
        $sql = "SELECT cm.id AS cmid, d.name AS dataname, cm.course AS courseid, " . self::sql_fields() . "
                FROM {context} ctx
                JOIN {course_modules} cm ON cm.id = ctx.instanceid
                JOIN {modules} m ON m.id = cm.module AND m.name = :modname
                JOIN {data} d ON d.id = cm.instance
                JOIN {data_records} dr ON dr.dataid = d.id
                JOIN {data_content} dc ON dc.recordid = dr.id
                JOIN {data_fields} df ON df.id = dc.fieldid
                WHERE ctx.id {$contextsql} AND ctx.contextlevel = :contextlevel
                AND dr.userid = :userid OR
                  EXISTS (SELECT 1 FROM {comments} com WHERE com.commentarea=:commentarea
                    AND com.itemid = dr.id AND com.userid = :userid1) OR
                  EXISTS (SELECT 1 FROM {rating} r WHERE r.contextid = ctx.id AND r.itemid  = dr.id AND r.component = :moddata
                    AND r.ratingarea = :ratingarea AND r.userid = :userid2)
                ORDER BY cm.id, dr.id, dc.fieldid";
        $rs = $DB->get_recordset_sql($sql, $contextparams + ['contextlevel' => CONTEXT_MODULE,
                'modname' => 'data', 'userid' => $user->id, 'userid1' => $user->id, 'commentarea' => 'database_entry',
                'userid2' => $user->id, 'ratingarea' => 'entry', 'moddata' => 'mod_data']);

        $context = null;
        $recordobj = null;
        foreach ($rs as $row) {
            if (!$context || $context->instanceid != $row->cmid) {
                // This row belongs to the different data module than the previous row.
                // Export the data for the previous module.
                self::export_data($context, $user);
                // Start new data module.
                $context = \context_module::instance($row->cmid);
            }

            if (!$recordobj || $row->recordid != $recordobj->id) {
                // Export previous data record.
                self::export_data_record($context, $user, $recordobj);
                // Prepare for exporting new data record.
                $recordobj = self::extract_object_from_record($row, 'record', ['dataid' => $row->dataid]);
            }
            $fieldobj = self::extract_object_from_record($row, 'field', ['dataid' => $row->dataid]);
            $contentobj = self::extract_object_from_record($row, 'content',
                ['fieldid' => $fieldobj->id, 'recordid' => $recordobj->id]);
            self::export_data_content($context, $recordobj, $fieldobj, $contentobj);
        }
        $rs->close();
        self::export_data_record($context, $user, $recordobj);
        self::export_data($context, $user);
    }
892643
public static function delete_data_for_user(approved_contextlist $contextlist) {
        global $DB;
        if (!static::approved_contextlist_contains_my_context($contextlist)) {
            // We only care about the user's user context.
            return;
        }

        static::delete_user_data($contextlist->get_user()->id);
    }
633899
public static function export_user_data(approved_contextlist $contextlist) {
        // If the user has data, then only the CONTEXT_USER should be present so get the first context.
        $contexts = $contextlist->get_contexts();
        if (count($contexts) == 0) {
            return;
        }
        $context = reset($contexts);

        // Sanity check that context is at the user context level, then get the userid.
        if ($context->contextlevel !== CONTEXT_USER) {
            return;
        }

        // Export associated userkeys.
        \core_userkey\privacy\provider::export_userkeys($context, [], 'rss');
    }
74329
protected static function export_user_data_lti_submissions(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 LTI activities associated with the above course modules.
        $ltiidstocmids = self::get_lti_ids_to_cmids_from_cmids($cmids);
        $ltiids = array_keys($ltiidstocmids);

        list($insql, $inparams) = $DB->get_in_or_equal($ltiids, SQL_PARAMS_NAMED);
        $params = array_merge($inparams, ['userid' => $user->id]);
        $recordset = $DB->get_recordset_select('lti_submission', "ltiid $insql AND userid = :userid", $params, 'dateupdated, id');
        self::recordset_loop_and_export($recordset, 'ltiid', [], function($carry, $record) use ($user, $ltiidstocmids) {
            $carry[] = [
                'gradepercent' => $record->gradepercent,
                'originalgrade' => $record->originalgrade,
                'datesubmitted' => transform::datetime($record->datesubmitted),
                'dateupdated' => transform::datetime($record->dateupdated)
            ];
            return $carry;
        }, function($ltiid, $data) use ($user, $ltiidstocmids) {
            $context = \context_module::instance($ltiidstocmids[$ltiid]);
            $contextdata = helper::get_context_data($context, $user);
            $finaldata = (object) array_merge((array) $contextdata, ['submissions' => $data]);
            helper::export_context_files($context, $user);
            writer::with_context($context)->export_data([], $finaldata);
        });
    }
834683
public static function export_user_data(approved_contextlist $contextlist) {
        $user = $contextlist->get_user();
        foreach ($contextlist->get_contexts() as $context) {
            // Check if the context is a user context.
            if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) {
                $results = static::get_records($user->id);
                foreach ($results as $result) {
                    $data = (object) [
                        'name' => $result->name,
                        'description' => $result->description,
                        'data' => transform::date($result->data)
                    ];
                    \core_privacy\local\request\writer::with_context($context)->export_data([
                        get_string('pluginname', 'profilefield_datetime')], $data);
                }
            }
        }
    }