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

@param   approved_contextlist    $contextlist    The approved contexts to export information for.
325994
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        $user = $contextlist->get_user();

        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);

        $sql = "SELECT
                    c.id AS contextid,
                    bi.*
                  FROM {context} c
            INNER JOIN {block_instances} bi ON bi.id = c.instanceid AND c.contextlevel = :contextlevel
                 WHERE bi.blockname = 'html'
                   AND(
                    c.id {$contextsql}
                )
        ";

        $params = [
            'contextlevel' => CONTEXT_BLOCK,
        ];
        $params += $contextparams;

        $instances = $DB->get_recordset_sql($sql, $params);
        foreach ($instances as $instance) {
            $context = \context_block::instance($instance->id);
            $block = block_instance('html', $instance);
            if (empty($block->config)) {
                // Skip this block. It has not been configured.
                continue;
            }

            $html = writer::with_context($context)
                ->rewrite_pluginfile_urls([], 'block_html', 'content', null, $block->config->text);

            // Default to FORMAT_HTML which is what will have been used before the
            // editor was properly implemented for the block.
            $format = isset($block->config->format) ? $block->config->format : FORMAT_HTML;

            $filteropt = (object) [
                'overflowdiv' => true,
                'noclean' => true,
            ];
            $html = format_text($html, $format, $filteropt);

            $data = helper::get_context_data($context, $user);
            helper::export_context_files($context, $user);
            $data->title = $block->config->title;
            $data->content = $html;

            writer::with_context($context)->export_data([], $data);
        }
        $instances->close();
    }
906672
public static function delete_data_for_user(approved_contextlist $contextlist) {
        global $DB;

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

        $userid = $contextlist->get_user()->id;

        // Only retrieve assignment submissions submitted by the user for deletion.
        $assignmentsubmissionids = array_keys(self::get_assignment_submissions_by_contextlist($contextlist, $userid));
        $DB->delete_records_list('assignment_submissions', 'id', $assignmentsubmissionids);

        // Delete all file uploads associated with the assignment submission for the user's specified list of contexts.
        $fs = get_file_storage();
        foreach ($contextlist->get_contextids() as $contextid) {
            foreach ($assignmentsubmissionids as $submissionid) {
                $fs->delete_area_files($contextid, 'mod_assignment', 'submission', $submissionid);
            }
        }
    }
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);
    }
445606
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        $context = \context_user::instance($contextlist->get_user()->id);

        $sql = "SELECT ml.id, mh.wwwroot, mh.name, ml.remoteid, ml.time, ml.userid, ml.ip, ml.course,
                       ml.coursename, ml.module, ml.cmid, ml.action, ml.url, ml.info
                  FROM {mnet_log} ml
                  JOIN {mnet_host} mh ON mh.id = ml.hostid
                 WHERE ml.userid = :userid
              ORDER BY mh.name, ml.coursename";
        $params = ['userid' => $contextlist->get_user()->id];

        $data = [];
        $lastcourseid = null;

        $logentries = $DB->get_recordset_sql($sql, $params);
        foreach ($logentries as $logentry) {
            $item = (object) [
                    'time' => transform::datetime($logentry->time),
                    'remoteid' => $logentry->remoteid,
                    'ip' => $logentry->ip,
                    'course' => $logentry->course,
                    'coursename' => format_string($logentry->coursename),
                    'module' => $logentry->module,
                    'cmid' => $logentry->cmid,
                    'action' => $logentry->action,
                    'url' => $logentry->url,
                    'info' => format_string($logentry->info)
            ];

            $item->externalhost =
                    ($logentry->name == '') ? preg_replace('#^https?://#', '', $logentry->wwwroot) :
                            preg_replace('#^https?://#', '', $logentry->name);

            if ($lastcourseid && $lastcourseid != $logentry->course) {
                $path = [get_string('pluginname', 'auth_mnet'), $data[0]->externalhost, $data[0]->coursename];
                writer::with_context($context)->export_data($path, (object) $data);
                $data = [];
            }

            $data[] = $item;
            $lastcourseid = $logentry->course;
        }
        $logentries->close();

        $path = [get_string('pluginname', 'auth_mnet'), $item->externalhost, $item->coursename];
        writer::with_context($context)->export_data($path, (object) $data);
    }
534103
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        // Ensure all contexts are CONTEXT_COURSE.
        $contexts = static::validate_contextlist_contexts($contextlist);
        if (empty($contexts)) {
            return;
        }

        // Get the context instance ids from the contexts. These  are the course ids..
        $contextinstanceids = array_map(function($context) {
            return $context->instanceid;
        }, $contexts);
        $userid = $contextlist->get_user()->id;

        // Now, we just need to fetch and format all entries corresponding to the contextids provided.
        $sql = "SELECT ef.action, r.shortname, ef.courseid, ef.timestart, ef.timeend, ef.timemodified
                  FROM {enrol_flatfile} ef
                  JOIN {context} c ON c.contextlevel = :contextlevel AND c.instanceid = ef.courseid
                  JOIN {role} r ON r.id = ef.roleid
                 WHERE ef.userid = :userid";
        $params = ['contextlevel' => CONTEXT_COURSE, 'userid' => $userid];
        list($insql, $inparams) = $DB->get_in_or_equal($contextinstanceids, SQL_PARAMS_NAMED);
        $sql .= " AND ef.courseid $insql";
        $params = array_merge($params, $inparams);

        $futureenrolments = $DB->get_recordset_sql($sql, $params);
        $enrolmentdata = [];
        foreach ($futureenrolments as $futureenrolment) {
            // It's possible to have more than one future enrolment per course.
            $futureenrolment->timestart = transform::datetime($futureenrolment->timestart);
            $futureenrolment->timeend = transform::datetime($futureenrolment->timeend);
            $futureenrolment->timemodified = transform::datetime($futureenrolment->timemodified);
            $enrolmentdata[$futureenrolment->courseid][] = $futureenrolment;
        }
        $futureenrolments->close();

        // And finally, write out the data to the relevant course contexts.
        $subcontext = \core_enrol\privacy\provider::get_subcontext([get_string('pluginname', 'enrol_flatfile')]);
        foreach ($enrolmentdata as $courseid => $enrolments) {
            $data = (object) [
                'pendingenrolments' => $enrolments,
            ];
            writer::with_context(\context_course::instance($courseid))->export_data($subcontext, $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();
    }
500710
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

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

        $user = $contextlist->get_user();

        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);

        $sql = "SELECT bc.*
                  FROM {backup_controllers} bc
                  JOIN {context} ctx
                    ON ctx.instanceid = bc.itemid AND ctx.contextlevel = :contextlevel
                 WHERE ctx.id {$contextsql}
                   AND bc.userid = :userid
              ORDER BY bc.timecreated ASC";
        $params = ['contextlevel' => CONTEXT_COURSE, 'userid' => $user->id] + $contextparams;
        $backupcontrollers = $DB->get_recordset_sql($sql, $params);
        self::recordset_loop_and_export($backupcontrollers, 'itemid', [], function($carry, $record) {
            $carry[] = [
                'operation' => $record->operation,
                'type' => $record->type,
                'itemid' => $record->itemid,
                'timecreated' => transform::datetime($record->timecreated),
                'timemodified' => transform::datetime($record->timemodified),
            ];
            return $carry;
        }, function($courseid, $data) {
            $context = \context_course::instance($courseid);
            $finaldata = (object) $data;
            writer::with_context($context)->export_data([get_string('backup'), $courseid], $finaldata);
        });
    }
1117657
protected static function export_user_data_lti_types(approved_contextlist $contextlist) {
        global $DB;

        // Filter out any contexts that are not related to courses.
        $courseids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
            if ($context->contextlevel == CONTEXT_COURSE) {
                $carry[] = $context->instanceid;
            }
            return $carry;
        }, []);

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

        $user = $contextlist->get_user();

        list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
        $params = array_merge($inparams, ['userid' => $user->id]);
        $ltitypes = $DB->get_recordset_select('lti_types', "course $insql AND createdby = :userid", $params, 'timecreated ASC');
        self::recordset_loop_and_export($ltitypes, 'course', [], function($carry, $record) {
            $context = \context_course::instance($record->course);
            $options = ['context' => $context];
            $carry[] = [
                'name' => format_string($record->name, true, $options),
                'createdby' => transform::user($record->createdby),
                'timecreated' => transform::datetime($record->timecreated),
                'timemodified' => transform::datetime($record->timemodified)
            ];
            return $carry;
        }, function($courseid, $data) {
            $context = \context_course::instance($courseid);
            $finaldata = (object) ['lti_types' => $data];
            writer::with_context($context)->export_data([], $finaldata);
        });
    }
754781
public static function export_user_data(approved_contextlist $contextlist) {
        if (empty($contextlist)) {
            return;
        }

        self::export_user_calendar_event_data($contextlist);
        self::export_user_calendar_subscription_data($contextlist);
    }
123464
public static function delete_data_for_user(approved_contextlist $contextlist) {
        global $DB;

        // If the user has repository_instances 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;

        // Delete the repository_instances records created for the userid.
        $DB->delete_records('repository_instances', ['userid' => $userid]);
    }
1186268
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        if ($contextlist->get_component() != 'core_portfolio') {
            return;
        }

        $correctusercontext = array_filter($contextlist->get_contexts(), function($context) use ($contextlist) {
            if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $contextlist->get_user()->id) {
                return $context;
            }
        });

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

        $usercontext = array_shift($correctusercontext);

        $sql = "SELECT pi.id AS instanceid, pi.name,
                       piu.id AS preferenceid, piu.name AS preference, piu.value,
                       pl.id AS logid, pl.time AS logtime, pl.caller_class, pl.caller_file,
                       pl.caller_component, pl.returnurl, pl.continueurl
                  FROM {portfolio_instance} pi
             LEFT JOIN {portfolio_instance_user} piu ON piu.instance = pi.id AND piu.userid = :userid1
             LEFT JOIN {portfolio_log} pl ON pl.portfolio = pi.id AND pl.userid = :userid2
                 WHERE piu.id IS NOT NULL OR pl.id IS NOT NULL";
        $params = ['userid1' => $usercontext->instanceid, 'userid2' => $usercontext->instanceid];
        $instances = [];
        $rs = $DB->get_recordset_sql($sql, $params);
        foreach ($rs as $record) {
            $instances += [$record->name =>
                (object)[
                    'name' => $record->name,
                    'preferences' => [],
                    'logs' => [],
                ]
            ];
            if ($record->preferenceid) {
                $instances[$record->name]->preferences[$record->preferenceid] = (object)[
                    'name' => $record->preference,
                    'value' => $record->value,
                ];
            }
            if ($record->logid) {
                $instances[$record->name]->logs[$record->logid] = (object)[
                    'time' => transform::datetime($record->logtime),
                    'caller_class' => $record->caller_class,
                    'caller_file' => $record->caller_file,
                    'caller_component' => $record->caller_component,
                    'returnurl' => $record->returnurl,
                    'continueurl' => $record->continueurl
                ];
            }
        }
        $rs->close();

        if (!empty($instances)) {
            foreach ($instances as &$instance) {
                if (!empty($instance->preferences)) {
                    $instance->preferences = array_values($instance->preferences);
                } else {
                    unset($instance->preferences);
                }
                if (!empty($instance->logs)) {
                    $instance->logs = array_values($instance->logs);
                } else {
                    unset($instance->logs);
                }
            }
            \core_privacy\local\request\writer::with_context($contextlist->current())->export_data(
                    [get_string('privacy:path', 'portfolio')], (object) $instances);
        }
    }
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);
                }
            }
        }
    }
227282
public static function export_user_data(approved_contextlist $contextlist) {
        $rssdata = [];
        $results = static::get_records($contextlist->get_user()->id);
        foreach ($results as $result) {
            $rssdata[] = (object) [
                'title' => $result->title,
                'preferredtitle' => $result->preferredtitle,
                'description' => $result->description,
                'shared' => \core_privacy\local\request\transform::yesno($result->shared),
                'url' => $result->url
            ];
        }
        if (!empty($rssdata)) {
            $data = (object) [
                'feeds' => $rssdata,
            ];
            \core_privacy\local\request\writer::with_context($contextlist->current())->export_data([
                    get_string('pluginname', 'block_rss_client')], $data);
        }
    }
690443
public static function delete_data_for_user(approved_contextlist $contextlist) {
        global $DB;

        $userid = intval($contextlist->get_user()->id);

        $models = self::get_models_with_user_data();
        $modelids = array_keys($models);

        list ($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);

        // Analytics prediction actions.
        list($sql, $apaparams) = self::analytics_prediction_actions_user_sql($userid, $modelids, $contextsql);
        $sql = "SELECT apa.id " . $sql;

        $predictionactionids = $DB->get_fieldset_sql($sql, $apaparams + $contextparams);
        if ($predictionactionids) {
            list ($predictionactionidssql, $params) = $DB->get_in_or_equal($predictionactionids);
            $DB->delete_records_select('analytics_prediction_actions', "id {$predictionactionidssql}", $params);
        }

        foreach ($models as $modelid => $model) {

            $analyser = $model->get_analyser(['notimesplitting' => true]);

            // Analytics predictions.
            $joinusersql = $analyser->join_sample_user('ap');
            $sql = "SELECT DISTINCT ap.id FROM {analytics_predictions} ap
                      {$joinusersql}
                     WHERE u.id = :userid AND ap.modelid = :modelid AND ap.contextid {$contextsql}";

            $predictionids = $DB->get_fieldset_sql($sql, ['userid' => $userid, 'modelid' => $modelid] + $contextparams);
            if ($predictionids) {
                list($predictionidssql, $params) = $DB->get_in_or_equal($predictionids, SQL_PARAMS_NAMED);
                $DB->delete_records_select('analytics_predictions', "id $predictionidssql", $params);
            }

            // Indicator calculations.
            $joinusersql = $analyser->join_sample_user('aic');
            $sql = "SELECT DISTINCT aic.id FROM {analytics_indicator_calc} aic
                      {$joinusersql}
                     WHERE u.id = :userid AND aic.sampleorigin = :analysersamplesorigin AND aic.contextid {$contextsql}";

            $params = ['userid' => $userid, 'analysersamplesorigin' => $analyser->get_samples_origin()] + $contextparams;
            $indicatorcalcids = $DB->get_fieldset_sql($sql, $params);
            if ($indicatorcalcids) {
                list ($indicatorcalcidssql, $params) = $DB->get_in_or_equal($indicatorcalcids, SQL_PARAMS_NAMED);
                $DB->delete_records_select('analytics_indicator_calc', "id $indicatorcalcidssql", $params);
            }
        }
    }
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();
    }
311525
public static function export_user_data(approved_contextlist $contextlist) {
        global $DB;

        $user = $contextlist->get_user();
        $userid = $user->id;
        $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
            if ($context->contextlevel == CONTEXT_MODULE) {
                $carry[] = $context->instanceid;
            }
            return $carry;
        }, []);

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

        // Export the answers.
        list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
        $sql = "
            SELECT sa.*,
                   sq.id as qid,
                   sq.text as qtext,
                   sq.shorttext as qshorttext,
                   sq.intro as qintro,
                   sq.options as qoptions,
                   sq.type as qtype,
                   cm.id as cmid
              FROM {survey_answers} sa
              JOIN {survey_questions} sq
                ON sq.id = sa.question
              JOIN {survey} s
                ON s.id = sa.survey
              JOIN {modules} m
                ON m.name = :survey
              JOIN {course_modules} cm
                ON cm.instance = s.id
               AND cm.module = m.id
             WHERE cm.id $insql
               AND sa.userid = :userid
          ORDER BY s.id, sq.id";
        $params = array_merge($inparams, ['survey' => 'survey', 'userid' => $userid]);

        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'cmid', [], function($carry, $record) {
            $q = survey_translate_question((object) [
                'text' => $record->qtext,
                'shorttext' => $record->qshorttext,
                'intro' => $record->qintro,
                'options' => $record->qoptions
            ]);
            $qtype = $record->qtype;
            $options = explode(',', $q->options);

            $carry[] = [
                'question' => array_merge((array) $q, [
                    'options' => $qtype > 0 ? $options : '-'
                ]),
                'answer' => [
                    'actual' => $qtype > 0 && !empty($record->answer1) ? $options[$record->answer1 - 1] : $record->answer1,
                    'preferred' => $qtype > 0 && !empty($record->answer2) ? $options[$record->answer2 - 1] : $record->answer2,
                ],
                'time' => transform::datetime($record->time),
            ];
            return $carry;

        }, function($cmid, $data) use ($user) {
            $context = context_module::instance($cmid);
            $contextdata = helper::get_context_data($context, $user);
            $contextdata = (object) array_merge((array) $contextdata, ['answers' => $data]);
            helper::export_context_files($context, $user);
            writer::with_context($context)->export_data([], $contextdata);
        });

        // Export the analysis.
        $sql = "
            SELECT sy.*, cm.id as cmid
              FROM {survey_analysis} sy
              JOIN {survey} s
                ON s.id = sy.survey
              JOIN {modules} m
                ON m.name = :survey
              JOIN {course_modules} cm
                ON cm.instance = s.id
               AND cm.module = m.id
             WHERE cm.id $insql
               AND sy.userid = :userid
          ORDER BY s.id";
        $params = array_merge($inparams, ['survey' => 'survey', 'userid' => $userid]);

        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'cmid', null, function($carry, $record) {
            $carry = ['notes' => $record->notes];
            return $carry;
        }, function($cmid, $data) {
            $context = context_module::instance($cmid);
            writer::with_context($context)->export_related_data([], 'survey_analysis', (object) $data);
        });
    }
848596
protected static function export_user_data_outcomes_in_contexts(approved_contextlist $contextlist) {
        global $DB;

        $rootpath = [get_string('grades', 'core_grades')];
        $relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);
        $userid = $contextlist->get_user()->id;

        // Reorganise the contexts.
        $reduced = array_reduce($contextlist->get_contexts(), function($carry, $context) {
            if ($context->contextlevel == CONTEXT_SYSTEM) {
                $carry['in_system'] = true;
            } else if ($context->contextlevel == CONTEXT_COURSE) {
                $carry['courseids'][] = $context->instanceid;
            }
            return $carry;
        }, [
            'in_system' => false,
            'courseids' => []
        ]);

        // Construct SQL.
        $sqltemplateparts = [];
        $templateparams = [];
        if ($reduced['in_system']) {
            $sqltemplateparts[] = '{prefix}.courseid IS NULL';
        }
        if (!empty($reduced['courseids'])) {
            list($insql, $inparams) = $DB->get_in_or_equal($reduced['courseids'], SQL_PARAMS_NAMED);
            $sqltemplateparts[] = "{prefix}.courseid $insql";
            $templateparams = array_merge($templateparams, $inparams);
        }
        if (empty($sqltemplateparts)) {
            return;
        }
        $sqltemplate = '(' . implode(' OR ', $sqltemplateparts) . ')';

        // Export edited outcomes.
        $sqlwhere = str_replace('{prefix}', 'go', $sqltemplate);
        $sql = "
            SELECT go.id, COALESCE(go.courseid, 0) AS courseid, go.shortname, go.fullname, go.timemodified
              FROM {grade_outcomes} go
             WHERE $sqlwhere
               AND go.usermodified = :userid
          ORDER BY go.courseid, go.timemodified, go.id";
        $params = array_merge($templateparams, ['userid' => $userid]);
        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
            $carry[] = [
                'shortname' => $record->shortname,
                'fullname' => $record->fullname,
                'timemodified' => transform::datetime($record->timemodified),
                'created_or_modified_by_you' => transform::yesno(true)
            ];
            return $carry;

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

        // Export edits of outcomes history.
        $sqlwhere = str_replace('{prefix}', 'goh', $sqltemplate);
        $sql = "
            SELECT goh.id, COALESCE(goh.courseid, 0) AS courseid, goh.shortname, goh.fullname, goh.timemodified, goh.action
              FROM {grade_outcomes_history} goh
             WHERE $sqlwhere
               AND goh.loggeduser = :userid
          ORDER BY goh.courseid, goh.timemodified, goh.id";
        $params = array_merge($templateparams, ['userid' => $userid]);
        $recordset = $DB->get_recordset_sql($sql, $params);
        static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
            $carry[] = [
                'shortname' => $record->shortname,
                'fullname' => $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 = $courseid ? context_course::instance($courseid) : context_system::instance();
            writer::with_context($context)->export_related_data($relatedtomepath, 'outcomes_history',
                (object) ['modified_records' => $data]);
        });
    }