Merge branch 'wip-MDL-61937-master' of git://github.com/marinaglancy/moodle
[moodle.git] / competency / classes / privacy / provider.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Data provider.
19  *
20  * @package    core_competency
21  * @copyright  2018 Frédéric Massart
22  * @author     Frédéric Massart <fred@branchup.tech>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 namespace core_competency\privacy;
27 defined('MOODLE_INTERNAL') || die();
29 use context;
30 use context_course;
31 use context_helper;
32 use context_module;
33 use context_system;
34 use context_user;
35 use moodle_recordset;
36 use core_competency\api;
37 use core_competency\competency;
38 use core_competency\competency_framework;
39 use core_competency\course_competency;
40 use core_competency\course_competency_settings;
41 use core_competency\course_module_competency;
42 use core_competency\evidence;
43 use core_competency\plan;
44 use core_competency\plan_competency;
45 use core_competency\related_competency;
46 use core_competency\template;
47 use core_competency\template_cohort;
48 use core_competency\template_competency;
49 use core_competency\user_competency;
50 use core_competency\user_competency_course;
51 use core_competency\user_competency_plan;
52 use core_competency\user_evidence;
53 use core_competency\user_evidence_competency;
54 use core_competency\external\performance_helper;
55 use core_privacy\local\metadata\collection;
56 use core_privacy\local\request\contextlist;
57 use core_privacy\local\request\approved_contextlist;
58 use core_privacy\local\request\transform;
59 use core_privacy\local\request\writer;
61 /**
62  * Data provider class.
63  *
64  * @package    core_competency
65  * @copyright  2018 Frédéric Massart
66  * @author     Frédéric Massart <fred@branchup.tech>
67  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
68  */
69 class provider implements
70     \core_privacy\local\metadata\provider,
71     \core_privacy\local\request\subsystem\provider {
73     /**
74      * Returns metadata.
75      *
76      * @param collection $collection The initialised collection to add items to.
77      * @return collection A listing of user data stored through this system.
78      */
79     public static function get_metadata(collection $collection) : collection {
81         // Tables not related to users aside from the editing information.
82         $collection->add_database_table('competency', [
83             'timecreated' => 'privacy:metadata:timecreated',
84             'timemodified' => 'privacy:metadata:timemodified',
85             'usermodified' => 'privacy:metadata:usermodified',
86         ], 'privacy:metadata:competency');
88         $collection->add_database_table('competency_coursecompsetting', [
89             'timecreated' => 'privacy:metadata:timecreated',
90             'timemodified' => 'privacy:metadata:timemodified',
91             'usermodified' => 'privacy:metadata:usermodified',
92         ], 'privacy:metadata:competency_coursecompsetting');
94         $collection->add_database_table('competency_framework', [
95             'timecreated' => 'privacy:metadata:timecreated',
96             'timemodified' => 'privacy:metadata:timemodified',
97             'usermodified' => 'privacy:metadata:usermodified',
98         ], 'privacy:metadata:competency_framework');
100         $collection->add_database_table('competency_coursecomp', [
101             'timecreated' => 'privacy:metadata:timecreated',
102             'timemodified' => 'privacy:metadata:timemodified',
103             'usermodified' => 'privacy:metadata:usermodified',
104         ], 'privacy:metadata:competency_coursecomp');
106         $collection->add_database_table('competency_template', [
107             'timecreated' => 'privacy:metadata:timecreated',
108             'timemodified' => 'privacy:metadata:timemodified',
109             'usermodified' => 'privacy:metadata:usermodified',
110         ], 'privacy:metadata:competency_template');
112         $collection->add_database_table('competency_templatecomp', [
113             'timecreated' => 'privacy:metadata:timecreated',
114             'timemodified' => 'privacy:metadata:timemodified',
115             'usermodified' => 'privacy:metadata:usermodified',
116         ], 'privacy:metadata:competency_templatecomp');
118         $collection->add_database_table('competency_templatecohort', [
119             'timecreated' => 'privacy:metadata:timecreated',
120             'timemodified' => 'privacy:metadata:timemodified',
121             'usermodified' => 'privacy:metadata:usermodified',
122         ], 'privacy:metadata:competency_templatecohort');
124         $collection->add_database_table('competency_relatedcomp', [
125             'timecreated' => 'privacy:metadata:timecreated',
126             'timemodified' => 'privacy:metadata:timemodified',
127             'usermodified' => 'privacy:metadata:usermodified',
128         ], 'privacy:metadata:competency_relatedcomp');
130         $collection->add_database_table('competency_modulecomp', [
131             'timecreated' => 'privacy:metadata:timecreated',
132             'timemodified' => 'privacy:metadata:timemodified',
133             'usermodified' => 'privacy:metadata:usermodified',
134         ], 'privacy:metadata:competency_modulecomp');
136         // Tables containing user data.
137         $collection->add_database_table('competency_plan', [
138             'name' => 'privacy:metadata:plan:name',
139             'description' => 'privacy:metadata:plan:description',
140             'userid' => 'privacy:metadata:plan:userid',
141             'status' => 'privacy:metadata:plan:status',
142             'duedate' => 'privacy:metadata:plan:duedate',
143             'reviewerid' => 'privacy:metadata:plan:reviewerid',
144             'timecreated' => 'privacy:metadata:timecreated',
145             'timemodified' => 'privacy:metadata:timemodified',
146             'usermodified' => 'privacy:metadata:usermodified',
147         ], 'privacy:metadata:competency_plan');
149         $collection->add_database_table('competency_usercomp', [
150             'userid' => 'privacy:metadata:usercomp:userid',
151             'status' => 'privacy:metadata:usercomp:status',
152             'reviewerid' => 'privacy:metadata:usercomp:reviewerid',
153             'proficiency' => 'privacy:metadata:usercomp:proficiency',
154             'grade' => 'privacy:metadata:usercomp:grade',
155             'timecreated' => 'privacy:metadata:timecreated',
156             'timemodified' => 'privacy:metadata:timemodified',
157             'usermodified' => 'privacy:metadata:usermodified',
158         ], 'privacy:metadata:competency_usercomp');
160         $collection->add_database_table('competency_usercompcourse', [
161             'userid' => 'privacy:metadata:usercomp:userid',
162             'proficiency' => 'privacy:metadata:usercomp:proficiency',
163             'grade' => 'privacy:metadata:usercomp:grade',
164             'timecreated' => 'privacy:metadata:timecreated',
165             'timemodified' => 'privacy:metadata:timemodified',
166             'usermodified' => 'privacy:metadata:usermodified',
167         ], 'privacy:metadata:competency_usercompcourse');
169         $collection->add_database_table('competency_usercompplan', [
170             'userid' => 'privacy:metadata:usercomp:userid',
171             'proficiency' => 'privacy:metadata:usercomp:proficiency',
172             'grade' => 'privacy:metadata:usercomp:grade',
173             'timecreated' => 'privacy:metadata:timecreated',
174             'timemodified' => 'privacy:metadata:timemodified',
175             'usermodified' => 'privacy:metadata:usermodified',
176         ], 'privacy:metadata:competency_usercompplan');
178         $collection->add_database_table('competency_plancomp', [
179             'timecreated' => 'privacy:metadata:timecreated',
180             'timemodified' => 'privacy:metadata:timemodified',
181             'usermodified' => 'privacy:metadata:usermodified',
182         ], 'privacy:metadata:competency_plancomp');
184         $collection->add_database_table('competency_evidence', [
185             'action' => 'privacy:metadata:evidence:action',
186             'actionuserid' => 'privacy:metadata:evidence:actionuserid',
187             'descidentifier' => 'privacy:metadata:evidence:descidentifier',
188             'desccomponent' => 'privacy:metadata:evidence:desccomponent',
189             'desca' => 'privacy:metadata:evidence:desca',
190             'url' => 'privacy:metadata:evidence:url',
191             'grade' => 'privacy:metadata:evidence:grade',
192             'note' => 'privacy:metadata:evidence:note',
193             'timecreated' => 'privacy:metadata:timecreated',
194             'timemodified' => 'privacy:metadata:timemodified',
195             'usermodified' => 'privacy:metadata:usermodified',
196         ], 'privacy:metadata:competency_evidence');
198         $collection->add_database_table('competency_userevidence', [
199             'name' => 'privacy:metadata:userevidence:name',
200             'description' => 'privacy:metadata:userevidence:description',
201             'url' => 'privacy:metadata:userevidence:url',
202             'timecreated' => 'privacy:metadata:timecreated',
203             'timemodified' => 'privacy:metadata:timemodified',
204             'usermodified' => 'privacy:metadata:usermodified',
205         ], 'privacy:metadata:competency_userevidence');
207         $collection->add_database_table('competency_userevidencecomp', [
208             'timecreated' => 'privacy:metadata:timecreated',
209             'timemodified' => 'privacy:metadata:timemodified',
210             'usermodified' => 'privacy:metadata:usermodified',
211         ], 'privacy:metadata:competency_userevidencecomp');
213         // Comments can be left on learning plans and competencies.
214         $collection->link_subsystem('core_comments', 'privacy:metadata:core_comments');
216         return $collection;
217     }
219     /**
220      * Get the list of contexts that contain user information for the specified user.
221      *
222      * @param int $userid The user to search.
223      * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
224      */
225     public static function get_contexts_for_userid(int $userid) : contextlist {
226         global $DB;
227         $contextlist = new \core_privacy\local\request\contextlist();
229         // Find the contexts of the frameworks, and related data, modified by the user.
230         $sql = "
231              SELECT DISTINCT ctx.id
232                FROM {context} ctx
233                JOIN {" . competency_framework::TABLE . "} cf
234                  ON cf.contextid = ctx.id
235           LEFT JOIN {" . competency::TABLE . "} c
236                  ON c.competencyframeworkid = cf.id
237           LEFT JOIN {" . related_competency::TABLE . "} cr
238                  ON cr.competencyid = c.id
239               WHERE cf.usermodified = :userid1
240                  OR c.usermodified = :userid2
241                  OR cr.usermodified = :userid3";
242         $params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid];
243         $contextlist->add_from_sql($sql, $params);
245         // Find the contexts of the templates, and related data, modified by the user.
246         $sql = "
247              SELECT DISTINCT ctx.id
248                FROM {context} ctx
249                JOIN {" . template::TABLE . "} tpl
250                  ON tpl.contextid = ctx.id
251           LEFT JOIN {" . template_cohort::TABLE . "} tch
252                  ON tch.templateid = tpl.id
253           LEFT JOIN {" . template_competency::TABLE . "} tc
254                  ON tc.templateid = tpl.id
255               WHERE tpl.usermodified = :userid1
256                  OR tch.usermodified = :userid2
257                  OR tc.usermodified = :userid3";
258         $params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid];
259         $contextlist->add_from_sql($sql, $params);
261         // Find the possible course contexts.
262         $sql = "
263              SELECT DISTINCT ctx.id
264                FROM {" . course_competency::TABLE . "} cc
265                JOIN {context} ctx
266                  ON ctx.instanceid = cc.courseid
267                 AND ctx.contextlevel = :courselevel
268               WHERE cc.usermodified = :userid";
269         $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
270         $contextlist->add_from_sql($sql, $params);
272         $sql = "
273              SELECT DISTINCT ctx.id
274                FROM {" . course_competency_settings::TABLE . "} ccs
275                JOIN {context} ctx
276                  ON ctx.instanceid = ccs.courseid
277                 AND ctx.contextlevel = :courselevel
278               WHERE ccs.usermodified = :userid";
279         $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
280         $contextlist->add_from_sql($sql, $params);
282         $sql = "
283              SELECT DISTINCT ctx.id
284                FROM {" . user_competency_course::TABLE . "} ucc
285                JOIN {context} ctx
286                  ON ctx.instanceid = ucc.courseid
287                 AND ctx.contextlevel = :courselevel
288               WHERE ucc.usermodified = :userid";
289         $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
290         $contextlist->add_from_sql($sql, $params);
292         // Find the possible module contexts.
293         $sql = "
294              SELECT DISTINCT ctx.id
295                FROM {" . course_module_competency::TABLE . "} cmc
296                JOIN {context} ctx
297                  ON ctx.instanceid = cmc.cmid
298                 AND ctx.contextlevel = :modulelevel
299               WHERE cmc.usermodified = :userid";
300         $params = ['modulelevel' => CONTEXT_MODULE, 'userid' => $userid];
301         $contextlist->add_from_sql($sql, $params);
303         // Add user contexts through usermodified/reviewing of plan related data.
304         $sql = "
305              SELECT DISTINCT ctx.id
306                FROM {" . plan::TABLE . "} p
307                JOIN {context} ctx
308                  ON ctx.instanceid = p.userid
309                 AND ctx.contextlevel = :userlevel
310           LEFT JOIN {" . plan_competency::TABLE . "} pc
311                  ON pc.planid = p.id
312           LEFT JOIN {" . user_competency_plan::TABLE . "} upc
313                  ON upc.planid = p.id
314               WHERE p.usermodified = :userid1
315                  OR p.reviewerid = :userid2
316                  OR pc.usermodified = :userid3
317                  OR upc.usermodified = :userid4";
318         $params = [
319             'userlevel' => CONTEXT_USER,
320             'userid1' => $userid,
321             'userid2' => $userid,
322             'userid3' => $userid,
323             'userid4' => $userid,
324         ];
325         $contextlist->add_from_sql($sql, $params);
327         // Add user contexts through usermodified/reviewing of competency data.
328         $sql = "
329              SELECT DISTINCT ctx.id
330                FROM {context} ctx
331           LEFT JOIN {" . user_competency::TABLE . "} uc
332                  ON uc.userid = ctx.instanceid
333                 AND ctx.contextlevel = :userlevel1
334           LEFT JOIN {" . evidence::TABLE . "} e
335                  ON e.usercompetencyid = uc.id
336           LEFT JOIN {" . user_evidence::TABLE . "} ue
337                  ON ue.userid = ctx.instanceid
338                 AND ctx.contextlevel = :userlevel2
339           LEFT JOIN {" . user_evidence_competency::TABLE . "} uec
340                  ON uec.userevidenceid = ue.id
341               WHERE uc.usermodified = :userid1
342                  OR uc.reviewerid = :userid2
343                  OR e.usermodified = :userid3
344                  OR e.actionuserid = :userid4
345                  OR ue.usermodified = :userid5
346                  OR uec.usermodified = :userid6";
347         $params = [
348             'userlevel1' => CONTEXT_USER,
349             'userlevel2' => CONTEXT_USER,
350             'userid1' => $userid,
351             'userid2' => $userid,
352             'userid3' => $userid,
353             'userid4' => $userid,
354             'userid5' => $userid,
355             'userid6' => $userid,
356         ];
357         $contextlist->add_from_sql($sql, $params);
359         // Now, the easy part, we fetch the user context for user plans and competencies.
360         // We also fetch the course context for the state of competencies for the user in courses.
361         $sql = "
362              SELECT DISTINCT ctx.id
363                FROM {context} ctx
364           LEFT JOIN {" . plan::TABLE . "} p
365                  ON p.userid = ctx.instanceid
366                 AND ctx.contextlevel = :userlevel1
367           LEFT JOIN {" . user_competency::TABLE . "} uc
368                  ON uc.userid = ctx.instanceid
369                 AND ctx.contextlevel = :userlevel2
370           LEFT JOIN {" . user_evidence::TABLE . "} ue
371                  ON ue.userid = ctx.instanceid
372                 AND ctx.contextlevel = :userlevel3
373           LEFT JOIN {" . user_competency_course::TABLE . "} ucc
374                  ON ucc.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel
375               WHERE p.userid = :userid1
376                  OR uc.userid = :userid2
377                  OR ue.userid = :userid3
378                  OR ucc.userid = :userid4";
379         $params = [
380             'userlevel1' => CONTEXT_USER,
381             'userlevel2' => CONTEXT_USER,
382             'userlevel3' => CONTEXT_USER,
383             'courselevel' => CONTEXT_COURSE,
384             'userid1' => $userid,
385             'userid2' => $userid,
386             'userid3' => $userid,
387             'userid4' => $userid,
388         ];
389         $contextlist->add_from_sql($sql, $params);
391         // Include the user contexts in which the user commented.
392         $sql = "
393             SELECT ctx.id
394               FROM {context} ctx
395               JOIN {comments} c
396                 ON c.contextid = ctx.id
397              WHERE c.component = :component
398                AND c.commentarea IN (:planarea, :usercomparea)
399                AND c.userid = :userid";
400         $params = [
401             'component' => 'competency',    // Must not be core_competency.
402             'planarea' => 'plan',
403             'usercomparea' => 'user_competency',
404             'userid' => $userid
405         ];
406         $contextlist->add_from_sql($sql, $params);
408         return $contextlist;
409     }
411     /**
412      * Export all user data for the specified user, in the specified contexts.
413      *
414      * @param approved_contextlist $contextlist The approved contexts to export information for.
415      */
416     public static function export_user_data(approved_contextlist $contextlist) {
417         $user = $contextlist->get_user();
418         $userid = $user->id;
420         // Re-arrange the contexts by context level.
421         $groupedcontexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
422             $contextlevel = $context->contextlevel;
423             if (!in_array($contextlevel, [CONTEXT_USER, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_SYSTEM, CONTEXT_COURSECAT])) {
424                 return $carry;
425             }
426             $carry[$contextlevel][] = $context;
427             return $carry;
428         }, [
429             CONTEXT_COURSE => [],
430             CONTEXT_COURSECAT => [],
431             CONTEXT_MODULE => [],
432             CONTEXT_SYSTEM => [],
433             CONTEXT_USER => [],
434         ]);
436         // Process module contexts.
437         static::export_user_data_in_module_contexts($userid, $groupedcontexts[CONTEXT_MODULE]);
439         // Process course contexts.
440         static::export_user_data_in_course_contexts($userid, $groupedcontexts[CONTEXT_COURSE]);
442         // Process course categories context.
443         static::export_user_data_in_category_contexts($userid, $groupedcontexts[CONTEXT_COURSECAT]);
445         // Process system context.
446         if (!empty($groupedcontexts[CONTEXT_SYSTEM])) {
447             static::export_user_data_in_system_context($userid);
448         }
450         // Process user contexts.
451         static::export_user_data_in_user_contexts($userid, $groupedcontexts[CONTEXT_USER]);
452     }
454     /**
455      * Delete all data for all users in the specified context.
456      *
457      * @param context $context The specific context to delete data for.
458      */
459     public static function delete_data_for_all_users_in_context(context $context) {
460         global $DB;
462         switch ($context->contextlevel) {
463             case CONTEXT_USER:
464                 $userid = $context->instanceid;
465                 static::delete_user_evidence_of_prior_learning($userid);
466                 static::delete_user_plans($userid);
467                 static::delete_user_competencies($userid);
468                 break;
470             case CONTEXT_COURSE:
471                 $courseid = $context->instanceid;
472                 $DB->delete_records(user_competency_course::TABLE, ['courseid' => $courseid]);
473                 break;
474         }
475     }
477     /**
478      * Delete all user data for the specified user, in the specified contexts.
479      *
480      * Here we only delete the private data of user, not whether they modified, are reviewing,
481      * or are associated with the record on at a second level. Only data directly linked to the
482      * user will be affected.
483      *
484      * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
485      */
486     public static function delete_data_for_user(approved_contextlist $contextlist) {
487         $user = $contextlist->get_user();
488         $userid = $user->id;
490         foreach ($contextlist as $context) {
491             switch ($context->contextlevel) {
492                 case CONTEXT_USER:
493                     if ($context->instanceid != $userid) {
494                         // Only delete the user's information when they requested their context to be deleted. We
495                         // do not take any action on other user's contexts because we don't own the data there.
496                         continue;
497                     }
498                     static::delete_user_evidence_of_prior_learning($userid);
499                     static::delete_user_plans($userid);
500                     static::delete_user_competencies($userid);
501                     break;
503                 case CONTEXT_COURSE:
504                     static::delete_user_competencies_in_course($userid, $context->instanceid);
505                     break;
506             }
507         }
508     }
510     /**
511      * Delete evidence of prior learning.
512      *
513      * @param int $userid The user ID.
514      * @return void
515      */
516     protected static function delete_user_evidence_of_prior_learning($userid) {
517         global $DB;
519         $usercontext = context_user::instance($userid);
520         $ueids = $DB->get_fieldset_select(user_evidence::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
521         if (empty($ueids)) {
522             return;
523         }
524         list($insql, $inparams) = $DB->get_in_or_equal($ueids, SQL_PARAMS_NAMED);
526         // Delete competencies associated with user evidence.
527         $DB->delete_records_select(user_evidence_competency::TABLE, "userevidenceid $insql", $inparams);
529         // Delete the user evidence.
530         $DB->delete_records_select(user_evidence::TABLE, "id $insql", $inparams);
532         // Delete the user evidence files.
533         $fs = get_file_storage();
534         $fs->delete_area_files($usercontext->id, 'core_competency', 'userevidence');
535     }
537     /**
538      * User plans.
539      *
540      * @param int $userid The user ID.
541      * @return void
542      */
543     protected static function delete_user_plans($userid) {
544         global $DB;
545         $usercontext = context_user::instance($userid);
547         // Remove all the comments made on plans.
548         \core_comment\privacy\provider::delete_comments_for_all_users($usercontext, 'competency', 'plan');
550         // Find the user plan IDs.
551         $planids = $DB->get_fieldset_select(plan::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
552         if (empty($planids)) {
553             return;
554         }
555         list($insql, $inparams) = $DB->get_in_or_equal($planids, SQL_PARAMS_NAMED);
557         // Delete all the competencies proficiency in the plans.
558         $DB->delete_records_select(user_competency_plan::TABLE, "planid $insql", $inparams);
560         // Delete all the competencies in the plans.
561         $DB->delete_records_select(plan_competency::TABLE, "planid $insql", $inparams);
563         // Delete all the plans.
564         $DB->delete_records_select(plan::TABLE, "id $insql", $inparams);
565     }
567     /**
568      * Delete user competency data.
569      *
570      * @param int $userid The user ID.
571      * @return void
572      */
573     protected static function delete_user_competencies($userid) {
574         global $DB;
575         $usercontext = context_user::instance($userid);
577         // Remove all the comments made on user competencies.
578         \core_comment\privacy\provider::delete_comments_for_all_users($usercontext, 'competency', 'user_competency');
580         // Find the user competency IDs.
581         $ucids = $DB->get_fieldset_select(user_competency::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
582         if (empty($ucids)) {
583             return;
584         }
585         list($insql, $inparams) = $DB->get_in_or_equal($ucids, SQL_PARAMS_NAMED);
587         // Delete all the evidence associated with competencies.
588         $DB->delete_records_select(evidence::TABLE, "usercompetencyid $insql", $inparams);
590         // Delete all the record of competency.
591         $DB->delete_records_select(user_competency::TABLE, "id $insql", $inparams);
592     }
594     /**
595      * Delete the record of competencies for a user in a course.
596      *
597      * @param int $userid The user ID.
598      * @param int $courseid The course ID.
599      * @return void
600      */
601     protected static function delete_user_competencies_in_course($userid, $courseid) {
602         global $DB;
603         $DB->delete_records(user_competency_course::TABLE, ['userid' => $userid, 'courseid' => $courseid]);
604     }
606     /**
607      * Export the user data in user context.
608      *
609      * @param int $userid The user ID.
610      * @param array $contexts The contexts.
611      * @return void
612      */
613     protected static function export_user_data_in_user_contexts($userid, array $contexts) {
614         global $DB;
616         $mycontext = context_user::instance($userid);
617         $contextids = array_map(function($context) {
618             return $context->id;
619         }, $contexts);
620         $exportowncontext = in_array($mycontext->id, $contextids);
621         $othercontexts = array_filter($contextids, function($contextid) use ($mycontext) {
622             return $contextid != $mycontext->id;
623         });
625         if ($exportowncontext) {
626             static::export_user_data_learning_plans($mycontext);
627             static::export_user_data_competencies($mycontext);
628             static::export_user_data_user_evidence($mycontext);
629         }
631         foreach ($othercontexts as $contextid) {
632             static::export_user_data_learning_plans_related_to_me($userid, context::instance_by_id($contextid));
633             static::export_user_data_competencies_related_to_me($userid, context::instance_by_id($contextid));
634             static::export_user_data_user_evidence_related_to_me($userid, context::instance_by_id($contextid));
635         }
636     }
638     /**
639      * Export the user data in systen context.
640      *
641      * @param int $userid The user ID.
642      * @return void
643      */
644     protected static function export_user_data_in_system_context($userid) {
645         static::export_user_data_frameworks_in_context($userid, context_system::instance());
646         static::export_user_data_templates_in_context($userid, context_system::instance());
647     }
649     /**
650      * Export the user data in category contexts.
651      *
652      * @param int $userid The user ID.
653      * @param array $contexts The contexts.
654      * @return void
655      */
656     protected static function export_user_data_in_category_contexts($userid, array $contexts) {
657         $contexts = array_filter($contexts, function($context) {
658             return $context->contextlevel == CONTEXT_COURSECAT;
659         });
660         if (empty($contexts)) {
661             return;
662         }
664         foreach ($contexts as $context) {
665             static::export_user_data_frameworks_in_context($userid, $context);
666             static::export_user_data_templates_in_context($userid, $context);
667         }
668     }
670     /**
671      * Export the user data in course contexts.
672      *
673      * @param int $userid The user whose data we're exporting.
674      * @param array $contexts A list of contexts.
675      * @return void
676      */
677     protected static function export_user_data_in_course_contexts($userid, array $contexts) {
678         global $DB;
680         $contexts = array_filter($contexts, function($context) {
681             return $context->contextlevel == CONTEXT_COURSE;
682         });
683         if (empty($contexts)) {
684             return;
685         }
687         $helper = new performance_helper();
688         $path = [get_string('competencies', 'core_competency')];
689         $courseids = array_map(function($context) {
690             return $context->instanceid;
691         }, $contexts);
693         // Fetch all the records of competency proficiency in the course.
694         $ffields = competency_framework::get_sql_fields('f', 'f_');
695         $compfields = competency::get_sql_fields('c', 'c_');
696         $uccfields = user_competency_course::get_sql_fields('ucc', 'ucc_');
697         $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
698         list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
699         $sql = "
700             SELECT $ffields, $compfields, $uccfields, $ctxfields
701               FROM {" . user_competency_course::TABLE . "} ucc
702               JOIN {" . competency::TABLE . "} c
703                 ON c.id = ucc.competencyid
704               JOIN {" . competency_framework::TABLE . "} f
705                 ON f.id = c.competencyframeworkid
706               JOIN {context} ctx
707                 ON ctx.id = f.contextid
708              WHERE ucc.userid = :userid
709                AND ucc.courseid $insql
710           ORDER BY ucc.courseid, c.id";
711         $params = array_merge($inparams, ['userid' => $userid]);
713         // Export data.
714         $recordset = $DB->get_recordset_sql($sql, $params);
715         static::recordset_loop_and_export($recordset, 'ucc_courseid', [], function($carry, $record) use ($helper) {
716             context_helper::preload_from_record($record);
717             $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
718             $competency = new competency(null, competency::extract_record($record, 'c_'));
719             $ucc = new user_competency_course(null, user_competency_course::extract_record($record, 'ucc_'));
720             $helper->ingest_framework($framework);
722             $carry[] = array_merge(static::transform_competency_brief($competency), [
723                 'rating' => [
724                     'rating' => static::transform_competency_grade($competency, $ucc->get('grade'), $helper),
725                     'proficient' => static::transform_proficiency($ucc->get('proficiency')),
726                     'timecreated' => transform::datetime($ucc->get('timecreated')),
727                     'timemodified' => transform::datetime($ucc->get('timemodified')),
728                 ]
729             ]);
730             return $carry;
732         }, function($courseid, $data) use ($path) {
733             $context = context_course::instance($courseid);
734             writer::with_context($context)->export_data($path, (object) ['ratings' => $data]);
735         });
737         // Export usermodified data.
738         static::export_user_data_in_course_contexts_associations($userid, $courseids, $path);
739         static::export_user_data_in_course_contexts_settings($userid, $courseids, $path);
740         static::export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path, $helper);
741     }
743     /**
744      * Export the ratings given in a course.
745      *
746      * @param int $userid The user ID.
747      * @param array $courseids The course IDs.
748      * @param array $path The root path.
749      * @param performance_helper $helper The performance helper.
750      * @return void
751      */
752     protected static function export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path,
753             performance_helper $helper) {
754         global $DB;
756         // Fetch all the records of competency proficiency in the course.
757         $ffields = competency_framework::get_sql_fields('f', 'f_');
758         $compfields = competency::get_sql_fields('c', 'c_');
759         $uccfields = user_competency_course::get_sql_fields('ucc', 'ucc_');
760         $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
761         list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
762         $sql = "
763             SELECT $ffields, $compfields, $uccfields, $ctxfields
764               FROM {" . user_competency_course::TABLE . "} ucc
765               JOIN {" . competency::TABLE . "} c
766                 ON c.id = ucc.competencyid
767               JOIN {" . competency_framework::TABLE . "} f
768                 ON f.id = c.competencyframeworkid
769               JOIN {context} ctx
770                 ON ctx.id = f.contextid
771              WHERE ucc.usermodified = :userid
772                AND ucc.courseid $insql
773           ORDER BY ucc.courseid";
774         $params = array_merge($inparams, ['userid' => $userid]);
776         // Export the data.
777         static::recordset_loop_and_export($DB->get_recordset_sql($sql, $params), 'ucc_courseid', [],
778             function($carry, $record) use ($helper) {
779                 context_helper::preload_from_record($record);
781                 $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
782                 $competency = new competency(null, competency::extract_record($record, 'c_'));
783                 $ucc = new user_competency_course(null, user_competency_course::extract_record($record, 'ucc_'));
784                 $helper->ingest_framework($framework);
786                 $carry[] = array_merge(static::transform_competency_brief($competency), [
787                     'rating' => [
788                         'userid' => transform::user($ucc->get('userid')),
789                         'rating' => static::transform_competency_grade($competency, $ucc->get('grade'), $helper),
790                         'proficient' => static::transform_proficiency($ucc->get('proficiency')),
791                         'timemodified' => transform::datetime($ucc->get('timemodified')),
792                     ]
793                 ]);
794                 return $carry;
796             }, function($courseid, $data) use ($path) {
797                 $context = context_course::instance($courseid);
798                 writer::with_context($context)->export_related_data($path, 'rated_by_me', (object) [
799                     'ratings' => $data
800                 ]);
801             }
802         );
803     }
805     /**
806      * Export user data in course contexts related to linked competencies.
807      *
808      * @param int $userid The user ID.
809      * @param array $courseids The course IDs.
810      * @param array $path The root path to export at.
811      * @return void
812      */
813     protected static function export_user_data_in_course_contexts_associations($userid, $courseids, $path) {
814         global $DB;
816         // Fetch all the courses with associations we created or modified.
817         $compfields = competency::get_sql_fields('c', 'c_');
818         $ccfields = course_competency::get_sql_fields('cc', 'cc_');
819         $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
820         list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
821         $sql = "
822             SELECT $compfields, $ccfields, $ctxfields
823               FROM {" . course_competency::TABLE . "} cc
824               JOIN {" . competency::TABLE . "} c
825                 ON c.id = cc.competencyid
826               JOIN {" . competency_framework::TABLE . "} f
827                 ON f.id = c.competencyframeworkid
828               JOIN {context} ctx
829                 ON ctx.id = f.contextid
830              WHERE cc.usermodified = :userid
831                AND cc.courseid $insql
832           ORDER BY cc.courseid, c.id";
833         $params = array_merge($inparams, ['userid' => $userid]);
834         $recordset = $DB->get_recordset_sql($sql, $params);
836         // Export the data.
837         static::recordset_loop_and_export($recordset, 'cc_courseid', [], function($carry, $record) {
838             context_helper::preload_from_record($record);
839             $competency = new competency(null, competency::extract_record($record, 'c_'));
840             $cc = new course_competency(null, course_competency::extract_record($record, 'cc_'));
841             $carry[] = array_merge(static::transform_competency_brief($competency), [
842                 'timemodified' => transform::datetime($cc->get('timemodified')),
843                 'created_or_modified_by_you' => transform::yesno(true)
844             ]);
845             return $carry;
847         }, function($courseid, $data) use ($path, $userid, $DB) {
848             $context = context_course::instance($courseid);
849             writer::with_context($context)->export_related_data($path, 'associations', (object) ['competencies' => $data]);
850         });
851     }
853     /**
854      * Export user data in course contexts related to course settings.
855      *
856      * @param int $userid The user ID.
857      * @param array $courseids The course IDs.
858      * @param array $path The root path to export at.
859      * @return void
860      */
861     protected static function export_user_data_in_course_contexts_settings($userid, $courseids, $path) {
862         global $DB;
864         // Fetch all the courses with associations we created or modified.
865         $ccsfields = course_competency_settings::get_sql_fields('ccs', 'ccs_');
866         list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
867         $sql = "
868             SELECT $ccsfields
869               FROM {" . course_competency_settings::TABLE . "} ccs
870              WHERE ccs.usermodified = :userid
871                AND ccs.courseid $insql
872           ORDER BY ccs.courseid";
873         $params = array_merge($inparams, ['userid' => $userid]);
874         $recordset = $DB->get_recordset_sql($sql, $params);
876         // Export the data.
877         static::recordset_loop_and_export($recordset, 'ccs_courseid', [], function($carry, $record) {
878             $ccs = new course_competency_settings(null, course_competency_settings::extract_record($record, 'ccs_'));
879             return [
880                 'timemodified' => transform::datetime($ccs->get('timemodified')),
881                 'created_or_modified_by_you' => transform::yesno(true)
882             ];
883         }, function($courseid, $data) use ($path, $userid, $DB) {
884             $context = context_course::instance($courseid);
885             writer::with_context($context)->export_related_data($path, 'settings', (object) $data);
886         });
887     }
889     /**
890      * Export the user data in module contexts.
891      *
892      * @param int $userid The user whose data we're exporting.
893      * @param array $contexts A list of contexts.
894      * @return void
895      */
896     protected static function export_user_data_in_module_contexts($userid, array $contexts) {
897         global $DB;
899         $contexts = array_filter($contexts, function($context) {
900             return $context->contextlevel == CONTEXT_MODULE;
901         });
902         if (empty($contexts)) {
903             return;
904         }
906         $path = [get_string('competencies', 'core_competency')];
907         $cmids = array_map(function($context) {
908             return $context->instanceid;
909         }, $contexts);
911         // Fetch all the modules with associations we created or modified.
912         $compfields = competency::get_sql_fields('c', 'c_');
913         $cmcfields = course_module_competency::get_sql_fields('cmc', 'cmc_');
914         $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
915         list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
916         $sql = "
917             SELECT $compfields, $cmcfields, $ctxfields
918               FROM {" . course_module_competency::TABLE . "} cmc
919               JOIN {" . competency::TABLE . "} c
920                 ON c.id = cmc.competencyid
921               JOIN {" . competency_framework::TABLE . "} f
922                 ON f.id = c.competencyframeworkid
923               JOIN {context} ctx
924                 ON ctx.id = f.contextid
925              WHERE cmc.usermodified = :userid
926                AND cmc.cmid $insql
927           ORDER BY cmc.cmid";
928         $params = array_merge($inparams, ['userid' => $userid]);
930         // Export the data.
931         $recordset = $DB->get_recordset_sql($sql, $params);
932         static::recordset_loop_and_export($recordset, 'cmc_cmid', [], function($carry, $record) {
933             context_helper::preload_from_record($record);
934             $competency = new competency(null, competency::extract_record($record, 'c_'));
935             $cmc = new course_module_competency(null, course_module_competency::extract_record($record, 'cmc_'));
936             $carry[] = array_merge(static::transform_competency_brief($competency), [
937                 'timecreated' => transform::datetime($cmc->get('timecreated')),
938                 'timemodified' => transform::datetime($cmc->get('timemodified')),
939                 'created_or_modified_by_you' => transform::yesno(true)
940             ]);
941             return $carry;
943         }, function($cmid, $data) use ($path) {
944             $context = context_module::instance($cmid);
945             writer::with_context($context)->export_data($path, (object) ['associations' => $data]);
946         });
947     }
949     /**
950      * Export a user's competencies.
951      *
952      * @param context_user $context The context of the user requesting the export.
953      * @return void
954      */
955     protected static function export_user_data_competencies(context_user $context) {
956         global $DB;
958         $userid = $context->instanceid;
959         $path = [get_string('competencies', 'core_competency'), get_string('competencies', 'core_competency')];
960         $helper = new performance_helper();
961         $cfields = competency::get_sql_fields('c', 'c_');
962         $ucfields = user_competency::get_sql_fields('uc', 'uc_');
963         $efields = evidence::get_sql_fields('e', 'e_');
965         $makecomppath = function($competencyid, $data) use ($path) {
966             return array_merge($path, [$data['name'] . ' (' . $competencyid . ')']);
967         };
969         $sql = "
970             SELECT $cfields, $ucfields, $efields
971               FROM {" . user_competency::TABLE . "} uc
972               JOIN {" . competency::TABLE . "} c
973                 ON c.id = uc.competencyid
974          LEFT JOIN {" . evidence::TABLE . "} e
975                 ON uc.id = e.usercompetencyid
976              WHERE uc.userid = :userid
977           ORDER BY c.id, e.timecreated DESC, e.id DESC";
978         $params = ['userid' => $userid];
980         $recordset = $DB->get_recordset_sql($sql, $params);
981         static::recordset_loop_and_export($recordset, 'c_id', null, function($carry, $record)
982                 use ($context, $userid, $helper, $makecomppath) {
984             $competency = new competency(null, competency::extract_record($record, 'c_'));
986             if ($carry === null) {
987                 $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
988                 $carry = array_merge(static::transform_competency_brief($competency), [
989                     'rating' => static::transform_user_competency($userid, $uc, $competency, $helper),
990                     'evidence' => []
991                 ]);
992                 \core_comment\privacy\provider::export_comments($context, 'competency', 'user_competency',
993                     $uc->get('id'), $makecomppath($competency->get('id'), $carry), false);
994             }
996             // There is an evidence in this record.
997             if (!empty($record->e_id)) {
998                 $evidence = new evidence(null, evidence::extract_record($record, 'e_'));
999                 $carry['evidence'][] = static::transform_evidence($userid, $evidence, $competency, $helper);
1000             }
1002             return $carry;
1004         }, function($competencyid, $data) use ($makecomppath, $context) {
1005             writer::with_context($context)->export_data($makecomppath($competencyid, $data), (object) $data);
1006         });
1007     }
1009     /**
1010      * Export a user's learning plans.
1011      *
1012      * @param context_user $context The context of the user requesting the export.
1013      * @return void
1014      */
1015     protected static function export_user_data_learning_plans(context_user $context) {
1016         global $DB;
1018         $userid = $context->instanceid;
1019         $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:plans', 'core_competency')];
1020         $helper = new performance_helper();
1021         $pfields = plan::get_sql_fields('p', 'p_');
1022         $pcfields = plan_competency::get_sql_fields('pc', 'pc_');
1023         $cfields = competency::get_sql_fields('c', 'c_');
1024         $ucfields = user_competency::get_sql_fields('uc', 'uc_');
1025         $ucpfields = user_competency_plan::get_sql_fields('ucp', 'ucp_');
1027         // The user's learning plans.
1028         $sql = "
1029             SELECT $pfields, $pcfields, $cfields, $ucfields, $ucpfields
1030               FROM {" . plan::TABLE . "} p
1031          LEFT JOIN {" . plan_competency::TABLE . "} pc
1032                 ON p.id = pc.planid
1033                AND p.templateid IS NULL
1034                AND p.status != :complete1
1035          LEFT JOIN {" . template_competency::TABLE . "} tc
1036                 ON tc.templateid = p.templateid
1037                AND p.templateid IS NOT NULL
1038                AND p.status != :complete2
1039          LEFT JOIN {" . user_competency_plan::TABLE . "} ucp
1040                 ON ucp.planid = p.id
1041                AND p.status = :complete3
1042          LEFT JOIN {" . competency::TABLE . "} c
1043                 ON c.id = pc.competencyid
1044                 OR c.id = tc.competencyid
1045                 OR c.id = ucp.competencyid
1046          LEFT JOIN {" . user_competency::TABLE . "} uc
1047                 ON uc.userid = p.userid
1048                AND (uc.competencyid = pc.competencyid OR uc.competencyid = tc.competencyid)
1049              WHERE p.userid = :userid
1050           ORDER BY p.id, c.id";
1051         $params = [
1052             'userid' => $userid,
1053             'complete1' => plan::STATUS_COMPLETE,
1054             'complete2' => plan::STATUS_COMPLETE,
1055             'complete3' => plan::STATUS_COMPLETE,
1056         ];
1058         $recordset = $DB->get_recordset_sql($sql, $params);
1059         static::recordset_loop_and_export($recordset, 'p_id', null, function($carry, $record) use ($userid, $helper, $context) {
1060             $iscomplete = $record->p_status == plan::STATUS_COMPLETE;
1062             if ($carry === null) {
1063                 $plan = new plan(null, plan::extract_record($record, 'p_'));
1064                 $options = ['context' => $context];
1065                 $carry = [
1066                     'name' => format_string($plan->get('name'), true, $options),
1067                     'description' => format_text($plan->get('description'), $plan->get('descriptionformat'), $options),
1068                     'status' => $plan->get_statusname(),
1069                     'duedate' => $plan->get('duedate') ? transform::datetime($plan->get('duedate')) : '-',
1070                     'reviewerid' => $plan->get('reviewerid') ? transform::user($plan->get('reviewerid')) : '-',
1071                     'timecreated' => transform::datetime($plan->get('timecreated')),
1072                     'timemodified' => transform::datetime($plan->get('timemodified')),
1073                     'competencies' => [],
1074                 ];
1075             }
1077             // The plan is empty.
1078             if (empty($record->c_id)) {
1079                 return $carry;
1080             }
1082             $competency = new competency(null, competency::extract_record($record, 'c_'));
1083             $rating = null;
1085             if ($iscomplete) {
1086                 // When the plan is complete, we should always found the user_competency_plan.
1087                 $ucp = new user_competency_plan(null, user_competency_plan::extract_record($record, 'ucp_'));
1088                 $rating = static::transform_user_competency($userid, $ucp, $competency, $helper);
1090             } else if (!empty($record->uc_id)) {
1091                 // When the plan is complete, there are still records of user_competency but we do not
1092                 // export them here, we export them as part of the competencies structure. The reason why
1093                 // we try to get the user_competency when the plan is not complete is to give the most accurate
1094                 // representation of the plan as possible.
1095                 $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
1096                 $rating = static::transform_user_competency($userid, $uc, $competency, $helper);
1097             }
1099             $carry['competencies'][] = array_merge(static::transform_competency_brief($competency), ['rating' => $rating]);
1100             return $carry;
1102         }, function($planid, $data) use ($context, $path) {
1103             $planpath = array_merge($path, [$data['name'] . ' (' . $planid . ')']);
1104             \core_comment\privacy\provider::export_comments($context, 'competency', 'plan', $planid, $planpath, false);
1105             writer::with_context($context)->export_data($planpath, (object) $data);
1106         });
1107     }
1109     /**
1110      * Export a user's data related to learning plans.
1111      *
1112      * @param int $userid The user ID we're exporting for.
1113      * @param context_user $context The context of the user in which we're gathering data.
1114      * @return void
1115      */
1116     protected static function export_user_data_learning_plans_related_to_me($userid, context_user $context) {
1117         global $DB;
1119         $path = [
1120             get_string('competencies', 'core_competency'),
1121             get_string('privacy:path:relatedtome', 'core_competency'),
1122             get_string('privacy:path:plans', 'core_competency'),
1123         ];
1124         $plans = [];
1125         $helper = new performance_helper();
1126         $pfields = plan::get_sql_fields('p', 'p_');
1127         $pcfields = plan_competency::get_sql_fields('pc', 'pc_');
1128         $cfields = competency::get_sql_fields('c', 'c_');
1129         $ucpfields = user_competency_plan::get_sql_fields('ucp', 'ucp_');
1131         // Function to initialise a plan record.
1132         $initplan = function($record) use ($context, $userid, &$plans) {
1133             $plan = new plan(null, plan::extract_record($record, 'p_'));
1134             $options = ['context' => $context];
1135             $plans[$plan->get('id')] = [
1136                 'name' => format_string($plan->get('name'), true, $options),
1137                 'reviewer_is_you' => transform::yesno($plan->get('reviewerid') == $userid),
1138                 'timecreated' => transform::datetime($plan->get('timecreated')),
1139                 'timemodified' => transform::datetime($plan->get('timemodified')),
1140                 'created_or_modified_by_you' => transform::yesno($plan->get('usermodified') == $userid),
1141                 'competencies' => [],
1142             ];
1143         };
1145         $initcompetency = function($record, $planid) use (&$plans) {
1146             $competency = new competency(null, competency::extract_record($record, 'c_'));
1147             $plans[$planid]['competencies'][$competency->get('id')] = static::transform_competency_brief($competency);
1148         };
1150         // Look for associations that were created.
1151         $sql = "
1152             SELECT $pfields, $pcfields, $cfields
1153               FROM {" . plan_competency::TABLE . "} pc
1154               JOIN {" . plan::TABLE . "} p
1155                 ON p.id = pc.planid
1156               JOIN {" . competency::TABLE . "} c
1157                 ON c.id = pc.competencyid
1158              WHERE p.userid = :targetuserid
1159                AND pc.usermodified = :userid
1160           ORDER BY p.id, c.id";
1161         $params = [
1162             'targetuserid' => $context->instanceid,
1163             'userid' => $userid,
1164         ];
1166         $recordset = $DB->get_recordset_sql($sql, $params);
1167         foreach ($recordset as $record) {
1168             $planid = $record->p_id;
1169             if (!isset($plans[$planid])) {
1170                 $initplan($record);
1171             }
1173             $initcompetency($record, $planid);
1174             $pc = new plan_competency(null, plan_competency::extract_record($record, 'pc_'));
1175             $plans[$planid]['competencies'][$pc->get('competencyid')] = array_merge(
1176                 $plans[$planid]['competencies'][$pc->get('competencyid')], [
1177                     'timemodified' => $pc->get('timemodified') ? transform::datetime($pc->get('timemodified')) : '-',
1178                     'timecreated' => $pc->get('timecreated') ? transform::datetime($pc->get('timecreated')) : '-',
1179                     'created_or_modified_by_you' => transform::yesno($pc->get('usermodified') == $userid),
1180                 ]
1181             );
1182         }
1183         $recordset->close();
1185         // Look for final grades that were given.
1186         $sql = "
1187             SELECT $pfields, $ucpfields, $cfields
1188               FROM {" . user_competency_plan::TABLE . "} ucp
1189               JOIN {" . plan::TABLE . "} p
1190                 ON p.id = ucp.planid
1191               JOIN {" . competency::TABLE . "} c
1192                 ON c.id = ucp.competencyid
1193              WHERE p.userid = :targetuserid
1194                AND ucp.usermodified = :userid
1195           ORDER BY p.id, c.id";
1196         $params = [
1197             'targetuserid' => $context->instanceid,
1198             'userid' => $userid,
1199         ];
1201         $recordset = $DB->get_recordset_sql($sql, $params);
1202         foreach ($recordset as $record) {
1203             $planid = $record->p_id;
1204             $competencyid = $record->c_id;
1206             if (!isset($plans[$planid])) {
1207                 $initplan($record);
1208             }
1210             if (!isset($plans[$planid]['competencies'][$competencyid])) {
1211                 $initcompetency($record, $planid);
1212             }
1214             $competency = new competency(null, competency::extract_record($record, 'c_'));
1215             $ucp = new user_competency_plan(null, user_competency_plan::extract_record($record, 'ucp_'));
1216             $plans[$planid]['competencies'][$competencyid]['rating'] = static::transform_user_competency($userid, $ucp,
1217                 $competency, $helper);
1218         }
1219         $recordset->close();
1221         // Find the plans that were modified or reviewed.
1222         $insql = " > 0";
1223         $inparams = [];
1224         if (!empty($plans)) {
1225             list($insql, $inparams) = $DB->get_in_or_equal(array_keys($plans), SQL_PARAMS_NAMED, 'param', false);
1226         }
1227         $sql = "
1228             SELECT $pfields
1229               FROM {" . plan::TABLE . "} p
1230          LEFT JOIN {comments} c
1231                 ON c.contextid = :contextid
1232                AND c.commentarea = :planarea
1233                AND c.component = :competency
1234                AND c.itemid = p.id
1235              WHERE p.userid = :targetuserid
1236                AND (p.usermodified = :userid1
1237                 OR p.reviewerid = :userid2
1238                 OR c.userid = :userid3)
1239                AND p.id $insql
1240           ORDER BY p.id";
1241         $params = array_merge($inparams, [
1242             'targetuserid' => $context->instanceid,
1243             'userid1' => $userid,
1244             'userid2' => $userid,
1245             'userid3' => $userid,
1246             'contextid' => $context->id,
1247             'planarea' => 'plan',
1248             'competency' => 'competency'
1249         ]);
1251         $recordset = $DB->get_recordset_sql($sql, $params);
1252         foreach ($recordset as $record) {
1253             $planid = $record->p_id;
1254             if (!isset($plans[$planid])) {
1255                 $initplan($record);
1256             }
1257         }
1258         $recordset->close();
1260         // Export each plan on its own.
1261         foreach ($plans as $planid => $plan) {
1262             $planpath = array_merge($path, ["{$plan['name']} ({$planid})"]);
1263             $plan['competencies'] = array_values($plan['competencies']);    // Drop the keys.
1265             writer::with_context($context)->export_data($planpath, (object) $plan);
1266             \core_comment\privacy\provider::export_comments($context, 'competency', 'plan', $planid, $planpath, true);
1267         }
1268     }
1270     /**
1271      * Export a user's data related to competencies.
1272      *
1273      * @param int $userid The user ID we're exporting for.
1274      * @param context_user $context The context of the user in which we're gathering data.
1275      * @return void
1276      */
1277     protected static function export_user_data_competencies_related_to_me($userid, context_user $context) {
1278         global $DB;
1280         $path = [
1281             get_string('competencies', 'core_competency'),
1282             get_string('privacy:path:relatedtome', 'core_competency'),
1283             get_string('competencies', 'core_competency'),
1284         ];
1285         $competencies = [];
1286         $helper = new performance_helper();
1287         $cfields = competency::get_sql_fields('c', 'c_');
1288         $ucfields = user_competency::get_sql_fields('uc', 'uc_');
1289         $efields = evidence::get_sql_fields('e', 'e_');
1291         $initcompetency = function($record) use (&$competencies) {
1292             $competency = new competency(null, competency::extract_record($record, 'c_'));
1293             $competencies[$competency->get('id')] = array_merge(static::transform_competency_brief($competency), [
1294                 'evidence' => []
1295             ]);
1296         };
1298         $initusercomp = function($competency, $record) use (&$competencies, $userid, $helper) {
1299             $competencyid = $competency->get('id');
1300             $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
1301             $competencies[$competencyid]['uc_id'] = $uc->get('id');
1302             $competencies[$competencyid]['rating'] = static::transform_user_competency($userid, $uc, $competency, $helper);
1303         };
1305         // Look for evidence.
1306         $sql = "
1307             SELECT $efields, $ucfields, $cfields
1308               FROM {" . evidence::TABLE . "} e
1309               JOIN {" . user_competency::TABLE . "} uc
1310                 ON uc.id = e.usercompetencyid
1311               JOIN {" . competency::TABLE . "} c
1312                 ON c.id = uc.competencyid
1313              WHERE uc.userid = :targetuserid
1314                AND (e.usermodified = :userid1
1315                 OR e.actionuserid = :userid2)
1316           ORDER BY c.id, e.id";
1317         $params = [
1318             'targetuserid' => $context->instanceid,
1319             'userid1' => $userid,
1320             'userid2' => $userid,
1321         ];
1322         $recordset = $DB->get_recordset_sql($sql, $params);
1323         foreach ($recordset as $record) {
1324             $competencyid = $record->c_id;
1325             $competency = new competency(null, competency::extract_record($record, 'c_'));
1327             if (!isset($competencies[$competencyid])) {
1328                 $initcompetency($record);
1329             }
1331             if (!array_key_exists('rating', $competencies[$competencyid])) {
1332                 $competencies[$competencyid]['rating'] = null;
1333                 if ($record->uc_reviewerid == $userid || $record->uc_usermodified == $userid) {
1334                     $initusercomp($competency, $record);
1335                 }
1336             }
1338             $evidence = new evidence(null, evidence::extract_record($record, 'e_'));
1339             $competencies[$competencyid]['evidence'][] = static::transform_evidence($userid, $evidence, $competency, $helper);
1340         }
1341         $recordset->close();
1343         // Look for user competency we modified and didn't catch.
1344         $insql = ' > 0';
1345         $inparams = [];
1346         if (!empty($competencies)) {
1347             list($insql, $inparams) = $DB->get_in_or_equal(array_keys($competencies), SQL_PARAMS_NAMED, 'param', false);
1348         }
1349         $sql = "
1350             SELECT $ucfields, $cfields
1351               FROM {" . user_competency::TABLE . "} uc
1352               JOIN {" . competency::TABLE . "} c
1353                 ON c.id = uc.competencyid
1354          LEFT JOIN {comments} cmt
1355                 ON cmt.contextid = :contextid
1356                AND cmt.commentarea = :ucarea
1357                AND cmt.component = :competency
1358                AND cmt.itemid = uc.id
1359              WHERE uc.userid = :targetuserid
1360                AND (uc.usermodified = :userid1
1361                 OR uc.reviewerid = :userid2
1362                 OR cmt.userid = :userid3)
1363                AND uc.competencyid $insql
1364           ORDER BY c.id, uc.id";
1365         $params = array_merge($inparams, [
1366             'targetuserid' => $context->instanceid,
1367             'userid1' => $userid,
1368             'userid2' => $userid,
1369             'userid3' => $userid,
1370             'contextid' => $context->id,
1371             'ucarea' => 'user_competency',
1372             'competency' => 'competency',
1373         ]);
1375         $recordset = $DB->get_recordset_sql($sql, $params);
1376         foreach ($recordset as $record) {
1377             $competency = new competency(null, competency::extract_record($record, 'c_'));
1378             if (!isset($competencies[$competency->get('id')])) {
1379                 $initcompetency($record);
1380                 $initusercomp($competency, $record);
1381             }
1382         }
1383         $recordset->close();
1385         // Export each competency on its own.
1386         foreach ($competencies as $competencyid => $competency) {
1387             $comppath = array_merge($path, ["{$competency['name']} ({$competencyid})"]);
1388             $ucid = isset($competency['uc_id']) ? $competency['uc_id'] : null;
1389             unset($competency['uc_id']);
1391             // Send to writer.
1392             writer::with_context($context)->export_data($comppath, (object) $competency);
1393             if ($ucid) {
1394                 \core_comment\privacy\provider::export_comments($context, 'competency', 'user_competency', $ucid, $comppath, true);
1395             }
1396         }
1397     }
1399     /**
1400      * Export a user's data related to evidence of prior learning.
1401      *
1402      * @param int $userid The user ID we're exporting for.
1403      * @param context_user $context The context of the user in which we're gathering data.
1404      * @return void
1405      */
1406     protected static function export_user_data_user_evidence_related_to_me($userid, context_user $context) {
1407         global $DB;
1409         $path = [
1410             get_string('competencies', 'core_competency'),
1411             get_string('privacy:path:relatedtome', 'core_competency'),
1412             get_string('privacy:path:userevidence', 'core_competency'),
1413         ];
1414         $evidence = [];
1415         $helper = new performance_helper();
1416         $cfields = competency::get_sql_fields('c', 'c_');
1417         $uecfields = user_evidence_competency::get_sql_fields('uec', 'uec_');
1418         $uefields = user_evidence::get_sql_fields('ue', 'ue_');
1420         $initevidence = function($record) use (&$evidence, $userid) {
1421             $ue = new user_evidence(null, user_evidence::extract_record($record, 'ue_'));
1422             $evidence[$ue->get('id')] = static::transform_user_evidence($userid, $ue);
1423         };
1425         // Look for evidence.
1426         $sql = "
1427             SELECT $uefields, $uecfields, $cfields
1428               FROM {" . user_evidence_competency::TABLE . "} uec
1429               JOIN {" . user_evidence::TABLE . "} ue
1430                 ON ue.id = uec.userevidenceid
1431               JOIN {" . competency::TABLE . "} c
1432                 ON c.id = uec.competencyid
1433              WHERE ue.userid = :targetuserid
1434                AND uec.usermodified = :userid
1435           ORDER BY ue.id, c.id";
1436         $params = [
1437             'targetuserid' => $context->instanceid,
1438             'userid' => $userid,
1439         ];
1440         $recordset = $DB->get_recordset_sql($sql, $params);
1441         foreach ($recordset as $record) {
1442             $ueid = $record->ue_id;
1443             if (!isset($evidence[$ueid])) {
1444                 $initevidence($record);
1445             }
1447             $competency = new competency(null, competency::extract_record($record, 'c_'));
1448             $uec = new user_evidence_competency(null, user_evidence_competency::extract_record($record, 'uec_'));
1449             $evidence[$ueid]['competencies'][] = array_merge(static::transform_competency_brief($competency), [
1450                 'timemodified' => $uec->get('timemodified') ? transform::datetime($uec->get('timemodified')) : '-',
1451                 'timecreated' => $uec->get('timecreated') ? transform::datetime($uec->get('timecreated')) : '-',
1452                 'created_or_modified_by_you' => transform::yesno($uec->get('usermodified'))
1453             ]);
1454         }
1455         $recordset->close();
1457         // Look for user evidence we modified or reviewed and didn't catch.
1458         $insql = ' > 0';
1459         $inparams = [];
1460         if (!empty($evidence)) {
1461             list($insql, $inparams) = $DB->get_in_or_equal(array_keys($evidence), SQL_PARAMS_NAMED, 'param', false);
1462         }
1463         $sql = "
1464             SELECT $uefields
1465               FROM {" . user_evidence::TABLE . "} ue
1466              WHERE ue.userid = :targetuserid
1467                AND ue.usermodified = :userid
1468                AND ue.id $insql
1469           ORDER BY ue.id";
1470         $params = array_merge($inparams, [
1471             'targetuserid' => $context->instanceid,
1472             'userid' => $userid,
1473         ]);
1475         $recordset = $DB->get_recordset_sql($sql, $params);
1476         foreach ($recordset as $record) {
1477             $initevidence($record);
1478         }
1479         $recordset->close();
1481         // Export files, then content.
1482         foreach ($evidence as $ueid => $data) {
1483             $uepath = array_merge($path, ["{$data['name']} ({$ueid})"]);
1484             writer::with_context($context)->export_area_files($uepath, 'core_competency', 'userevidence', $ueid);
1485             writer::with_context($context)->export_data($uepath, (object) $data);
1486         }
1487     }
1489     /**
1490      * Export the evidence of prior learning of a user.
1491      *
1492      * @param context_user $context The context of the user we're exporting for.
1493      * @return void
1494      */
1495     protected static function export_user_data_user_evidence(context_user $context) {
1496         global $DB;
1498         $userid = $context->instanceid;
1499         $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:userevidence', 'core_competency')];
1500         $uefields = user_evidence::get_sql_fields('ue', 'ue_');
1501         $cfields = competency::get_sql_fields('c', 'c_');
1503         $sql = "
1504             SELECT $uefields, $cfields
1505               FROM {" . user_evidence::TABLE . "} ue
1506          LEFT JOIN {" . user_evidence_competency::TABLE . "} uec
1507                 ON uec.userevidenceid = ue.id
1508          LEFT JOIN {" . competency::TABLE . "} c
1509                 ON c.id = uec.competencyid
1510              WHERE ue.userid = :userid
1511           ORDER BY ue.id";
1512         $params = ['userid' => $userid];
1514         $recordset = $DB->get_recordset_sql($sql, $params);
1515         static::recordset_loop_and_export($recordset, 'ue_id', null, function($carry, $record) use ($userid, $context){
1516             if ($carry === null) {
1517                 $ue = new user_evidence(null, user_evidence::extract_record($record, 'ue_'));
1518                 $carry = static::transform_user_evidence($userid, $ue);
1519             }
1521             if (!empty($record->c_id)) {
1522                 $competency = new competency(null, competency::extract_record($record, 'c_'));
1523                 $carry['competencies'][] = static::transform_competency_brief($competency);
1524             }
1526             return $carry;
1527         }, function($ueid, $data) use ($context, $path) {
1528             $finalpath = array_merge($path, [$data['name'] . ' (' . $ueid . ')']);
1529             writer::with_context($context)->export_area_files($finalpath, 'core_competency', 'userevidence', $ueid);
1530             writer::with_context($context)->export_data($finalpath, (object) $data);
1531         });
1532     }
1534     /**
1535      * Export the user data related to frameworks in context.
1536      *
1537      * @param int $userid The user ID.
1538      * @param context $context The context.
1539      * @return void
1540      */
1541     protected static function export_user_data_frameworks_in_context($userid, context $context) {
1542         global $DB;
1544         $ffields = competency_framework::get_sql_fields('f', 'f_');
1545         $cfields = competency::get_sql_fields('c', 'c_');
1546         $c2fields = competency::get_sql_fields('c2', 'c2_');
1547         $rcfields = related_competency::get_sql_fields('rc', 'rc_');
1549         $frameworks = [];
1550         $initframework = function($record) use (&$frameworks, $userid) {
1551             $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
1552             $frameworks[$framework->get('id')] = array_merge(static::transform_framework_brief($framework), [
1553                 'timemodified' => transform::datetime($framework->get('timemodified')),
1554                 'created_or_modified_by_you' => transform::yesno($framework->get('usermodified') == $userid),
1555                 'competencies' => []
1556             ]);
1557         };
1558         $initcompetency = function($record, $prefix) use (&$frameworks, $userid) {
1559             $competency = new competency(null, competency::extract_record($record, $prefix));
1560             $frameworks[$competency->get('competencyframeworkid')]['competencies'][$competency->get('id')] = array_merge(
1561                 static::transform_competency_brief($competency),
1562                 [
1563                     'timemodified' => transform::datetime($competency->get('timemodified')),
1564                     'created_or_modified_by_you' => transform::yesno($competency->get('usermodified') == $userid),
1565                     'related_competencies' => []
1566                 ]
1567             );
1568         };
1570         // Start by looking for related competencies.
1571         $sql = "
1572             SELECT $ffields, $cfields, $c2fields, $rcfields
1573               FROM {" . related_competency::TABLE . "} rc
1574               JOIN {" . competency::TABLE . "} c
1575                 ON c.id = rc.competencyid
1576               JOIN {" . competency::TABLE . "} c2
1577                 ON c2.id = rc.relatedcompetencyid
1578               JOIN {" . competency_framework::TABLE . "} f
1579                 ON f.id = c.competencyframeworkid
1580              WHERE rc.usermodified = :userid
1581                AND f.contextid = :contextid
1582           ORDER BY rc.id, c.id";
1583         $params = ['userid' => $userid, 'contextid' => $context->id];
1585         $recordset = $DB->get_recordset_sql($sql, $params);
1586         foreach ($recordset as $record) {
1587             $frameworkid = $record->f_id;
1588             $comp1id = $record->c_id;
1589             $comp2id = $record->c2_id;
1591             if (!isset($frameworks[$frameworkid])) {
1592                 $initframework($record);
1593             }
1595             foreach (['c_', 'c2_'] as $key) {
1596                 $competencyid = $record->{$key . 'id'};
1597                 if (!isset($frameworks[$frameworkid]['competencies'][$competencyid])) {
1598                     $initcompetency($record, $key);
1599                 }
1600             }
1602             $relcomp = new related_competency(null, related_competency::extract_record($record, 'rc_'));
1603             foreach (['c_' => 'c2_', 'c2_' => 'c_'] as $key => $relatedkey) {
1604                 $competencyid = $record->{$key . 'id'};
1605                 $competency = new competency(null, competency::extract_record($record, $relatedkey));
1606                 $frameworks[$frameworkid]['competencies'][$competencyid]['related_competencies'][] = [
1607                     'name' => $competency->get('shortname'),
1608                     'idnumber' => $competency->get('idnumber'),
1609                     'timemodified' => transform::datetime($relcomp->get('timemodified')),
1610                     'created_or_modified_by_you' => transform::yesno($relcomp->get('usermodified') == $userid),
1611                 ];
1612             }
1613         }
1614         $recordset->close();
1616         // Now look for competencies, but skip the ones we've already seen.
1617         $competencyids = array_reduce($frameworks, function($carry, $framework) {
1618             return array_merge($carry, array_keys($framework['competencies']));
1619         }, []);
1620         $insql = ' IS NOT NULL';
1621         $inparams = [];
1622         if (!empty($competencyids)) {
1623             list($insql, $inparams) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED, 'param', false);
1624         }
1625         $sql = "
1626             SELECT $ffields, $cfields
1627               FROM {" . competency::TABLE . "} c
1628               JOIN {" . competency_framework::TABLE . "} f
1629                 ON f.id = c.competencyframeworkid
1630              WHERE c.usermodified = :userid
1631                AND f.contextid = :contextid
1632                AND c.id $insql
1633           ORDER BY c.id";
1634         $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
1635         $recordset = $DB->get_recordset_sql($sql, $params);
1636         foreach ($recordset as $record) {
1637             $frameworkid = $record->f_id;
1638             if (!isset($frameworks[$frameworkid])) {
1639                 $initframework($record);
1640             }
1641             $initcompetency($record, 'c_');
1642         }
1643         $recordset->close();
1645         // Now look for frameworks, but skip the ones we've already seen.
1646         $frameworkids = array_keys($frameworks);
1647         $insql = ' IS NOT NULL';
1648         $inparams = [];
1649         if (!empty($frameworkids)) {
1650             list($insql, $inparams) = $DB->get_in_or_equal($frameworkids, SQL_PARAMS_NAMED, 'param', false);
1651         }
1652         $sql = "
1653             SELECT $ffields
1654               FROM {" . competency_framework::TABLE . "} f
1655              WHERE f.usermodified = :userid
1656                AND f.contextid = :contextid
1657                AND f.id $insql
1658           ORDER BY f.id";
1659         $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
1660         $recordset = $DB->get_recordset_sql($sql, $params);
1661         foreach ($recordset as $record) {
1662             context_helper::preload_from_record($record);
1663             $initframework($record);
1664         }
1665         $recordset->close();
1667         // Export all the things!
1668         writer::with_context($context)->export_related_data(
1669             [get_string('competencies', 'core_competency')],
1670             'frameworks',
1671             (object) [
1672                 // Drop the temporary IDs.
1673                 'frameworks' => array_reduce($frameworks, function($carry, $item) {
1674                     $item['competencies'] = array_values($item['competencies']);
1675                     $carry[] = $item;
1676                     return $carry;
1677                 }, [])
1678             ]
1679         );
1680     }
1682     /**
1683      * Export the user data related to templates in contexts.
1684      *
1685      * @param int $userid The user ID.
1686      * @param context $context The context.
1687      * @return void
1688      */
1689     protected static function export_user_data_templates_in_context($userid, context $context) {
1690         global $DB;
1692         $tfields = template::get_sql_fields('t', 't_');
1693         $cfields = competency::get_sql_fields('c', 'c_');
1694         $tcfields = template_competency::get_sql_fields('tc', 'tc_');
1695         $tchfields = template_cohort::get_sql_fields('tch', 'tch_');
1697         $templates = [];
1698         $inittemplate = function($record) use (&$templates, $userid) {
1699             $template = new template(null, template::extract_record($record, 't_'));
1700             $templates[$template->get('id')] = array_merge(static::transform_template_brief($template), [
1701                 'timemodified' => transform::datetime($template->get('timemodified')),
1702                 'created_or_modified_by_you' => transform::yesno($template->get('usermodified') == $userid),
1703                 'competencies' => [],
1704                 'cohorts' => []
1705             ]);
1706         };
1708         // Find the template competencies.
1709         $sql = "
1710             SELECT $tfields, $cfields, $tcfields
1711               FROM {" . template_competency::TABLE . "} tc
1712               JOIN {" . template::TABLE . "} t
1713                 ON t.id = tc.templateid
1714               JOIN {" . competency::TABLE . "} c
1715                 ON c.id = tc.competencyid
1716              WHERE t.contextid = :contextid
1717                AND tc.usermodified = :userid
1718           ORDER BY t.id, tc.id";
1719         $params = ['userid' => $userid, 'contextid' => $context->id];
1720         $recordset = $DB->get_recordset_sql($sql, $params);
1721         foreach ($recordset as $record) {
1722             $templateid = $record->t_id;
1723             if (!isset($templates[$templateid])) {
1724                 $inittemplate($record);
1725             }
1726             $tplcomp = new template_competency(null, template_competency::extract_record($record, 'tc_'));
1727             $competency = new competency(null, competency::extract_record($record, 'c_'));
1728             $templates[$templateid]['competencies'][] = array_merge(
1729                 static::transform_competency_brief($competency),
1730                 [
1731                     'timemodified' => transform::datetime($tplcomp->get('timemodified')),
1732                     'created_or_modified_by_you' => transform::yesno($tplcomp->get('usermodified') == $userid)
1733                 ]
1734             );
1735         }
1736         $recordset->close();
1738         // Find the template cohorts.
1739         $sql = "
1740             SELECT $tfields, $tchfields, c.name AS cohortname
1741               FROM {" . template_cohort::TABLE . "} tch
1742               JOIN {" . template::TABLE . "} t
1743                 ON t.id = tch.templateid
1744               JOIN {cohort} c
1745                 ON c.id = tch.cohortid
1746              WHERE t.contextid = :contextid
1747                AND tch.usermodified = :userid
1748           ORDER BY t.id, tch.id";
1749         $params = ['userid' => $userid, 'contextid' => $context->id];
1750         $recordset = $DB->get_recordset_sql($sql, $params);
1751         foreach ($recordset as $record) {
1752             $templateid = $record->t_id;
1753             if (!isset($templates[$templateid])) {
1754                 $inittemplate($record);
1755             }
1756             $tplcohort = new template_cohort(null, template_cohort::extract_record($record, 'tch_'));
1757             $templates[$templateid]['cohorts'][] = [
1758                 'name' => $record->cohortname,
1759                 'timemodified' => transform::datetime($tplcohort->get('timemodified')),
1760                 'created_or_modified_by_you' => transform::yesno($tplcohort->get('usermodified') == $userid)
1761             ];
1762         }
1763         $recordset->close();
1765         // Find the modified templates which we haven't been found yet.
1766         $templateids = array_keys($templates);
1767         $insql = "IS NOT NULL";
1768         $inparams = [];
1769         if (!empty($templateids)) {
1770             list($insql, $inparams) = $DB->get_in_or_equal($templateids, SQL_PARAMS_NAMED, 'param', false);
1771         }
1772         $sql = "
1773             SELECT $tfields
1774               FROM {" . template::TABLE . "} t
1775              WHERE t.contextid = :contextid
1776                AND t.usermodified = :userid
1777                AND t.id $insql
1778           ORDER BY t.id";
1779         $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
1780         $recordset = $DB->get_recordset_sql($sql, $params);
1781         foreach ($recordset as $record) {
1782             $inittemplate($record);
1783         }
1784         $recordset->close();
1786         // Export all the things!
1787         writer::with_context($context)->export_related_data([get_string('competencies', 'core_competency')],
1788             'templates', (object) ['templates' => array_values($templates)]);
1789     }
1791     /**
1792      * Transform a competency into a brief description.
1793      *
1794      * @param competency $competency The competency.
1795      * @return array
1796      */
1797     protected static function transform_competency_brief(competency $competency) {
1798         global $OUTPUT;
1799         $exporter = new \core_competency\external\competency_exporter($competency, ['context' => $competency->get_context()]);
1800         $data = $exporter->export($OUTPUT);
1801         return [
1802             'idnumber' => $data->idnumber,
1803             'name' => $data->shortname,
1804             'description' => $data->description
1805         ];
1806     }
1808     /**
1809      * Transform a competency rating.
1810      *
1811      * @param competency $competency The competency.
1812      * @param int $grade The grade.
1813      * @param performance_helper $helper The performance helper.
1814      * @return string
1815      */
1816     protected static function transform_competency_grade(competency $competency, $grade, performance_helper $helper) {
1817         if ($grade === null) {
1818             return '-';
1819         }
1820         $scale = $helper->get_scale_from_competency($competency);
1821         return $scale->scale_items[$grade - 1];
1822     }
1824     /**
1825      * Transform an evidence.
1826      *
1827      * @param int $userid The user ID we are exporting for.
1828      * @param evidence $evidence The evidence.
1829      * @param competency $competency The associated competency.
1830      * @param performance_helper $helper The performance helper.
1831      * @return array
1832      */
1833     protected static function transform_evidence($userid, evidence $evidence, competency $competency, performance_helper $helper) {
1834         $action = $evidence->get('action');
1835         $actiontxt = '?';
1836         if ($action == evidence::ACTION_LOG) {
1837             $actiontxt = get_string('privacy:evidence:action:log', 'core_competency');
1838         } else if ($action == evidence::ACTION_COMPLETE) {
1839             $actiontxt = get_string('privacy:evidence:action:complete', 'core_competency');
1840         } else if ($action == evidence::ACTION_OVERRIDE) {
1841             $actiontxt = get_string('privacy:evidence:action:override', 'core_competency');
1842         }
1844         $actionuserid = $evidence->get('actionuserid');
1846         return [
1847             'action' => $actiontxt,
1848             'actionuserid' => $actionuserid ? transform::user($actionuserid) : '-',
1849             'acting_user_is_you' => transform::yesno($userid == $actionuserid),
1850             'description' => (string) $evidence->get_description(),
1851             'url' => $evidence->get('url'),
1852             'grade' => static::transform_competency_grade($competency, $evidence->get('grade'), $helper),
1853             'note' => $evidence->get('note'),
1854             'timecreated' => transform::datetime($evidence->get('timecreated')),
1855             'timemodified' => transform::datetime($evidence->get('timemodified')),
1856             'created_or_modified_by_you' => transform::yesno($userid == $evidence->get('usermodified'))
1857         ];
1858     }
1860     /**
1861      * Transform a framework into a brief description.
1862      *
1863      * @param competency_framework $framework The framework.
1864      * @return array
1865      */
1866     protected static function transform_framework_brief(competency_framework $framework) {
1867         global $OUTPUT;
1868         $exporter = new \core_competency\external\competency_framework_exporter($framework);
1869         $data = $exporter->export($OUTPUT);
1870         return [
1871             'name' => $data->shortname,
1872             'idnumber' => $data->idnumber,
1873             'description' => $data->description
1874         ];
1875     }
1877     /**
1878      * Transform a template into a brief description.
1879      *
1880      * @param template $template The Template.
1881      * @return array
1882      */
1883     protected static function transform_template_brief(template $template) {
1884         global $OUTPUT;
1885         $exporter = new \core_competency\external\template_exporter($template);
1886         $data = $exporter->export($OUTPUT);
1887         return [
1888             'name' => $data->shortname,
1889             'description' => $data->description
1890         ];
1891     }
1893     /**
1894      * Transform proficiency.
1895      *
1896      * @param null|bool $proficiency The proficiency.
1897      * @return string
1898      */
1899     protected static function transform_proficiency($proficiency) {
1900         return $proficiency !== null ? transform::yesno($proficiency) : '-';
1901     }
1903     /**
1904      * Transform user competency.
1905      *
1906      * @param int $userid The user ID we are exporting for.
1907      * @param user_competency|user_competency_plan|user_competency_course $uc The user competency.
1908      * @param competency $competency The associated competency.
1909      * @param performance_helper $helper The performance helper.
1910      * @return array
1911      */
1912     protected static function transform_user_competency($userid, $uc, competency $competency, performance_helper $helper) {
1913         $data = [
1914             'proficient' => static::transform_proficiency($uc->get('proficiency')),
1915             'rating' => static::transform_competency_grade($competency, $uc->get('grade'), $helper),
1916             'timemodified' => $uc->get('timemodified') ? transform::datetime($uc->get('timemodified')) : '-',
1917             'timecreated' => $uc->get('timecreated') ? transform::datetime($uc->get('timecreated')) : '-',
1918             'created_or_modified_by_you' => transform::yesno($uc->get('usermodified') == $userid),
1919         ];
1921         if ($uc instanceof user_competency) {
1922             $reviewer = $uc->get('reviewerid');
1923             $data['status'] = (string) user_competency::get_status_name($uc->get('status'));
1924             $data['reviewerid'] = $reviewer ? transform::user($reviewer) : '-';
1925             $data['reviewer_is_you'] = transform::yesno($reviewer == $userid);
1926         }
1928         return $data;
1929     }
1931     /**
1932      * Transform a user evidence.
1933      *
1934      * @param int $userid The user we are exporting for.
1935      * @param user_evidence $ue The evidence of prior learning.
1936      * @return array
1937      */
1938     protected static function transform_user_evidence($userid, user_evidence $ue) {
1939         $options = ['context' => $ue->get_context()];
1940         return [
1941             'name' => format_string($ue->get('name'), true, $options),
1942             'description' => format_text($ue->get('description'), $ue->get('descriptionformat'), $options),
1943             'url' => $ue->get('url'),
1944             'timecreated' => $ue->get('timecreated') ? transform::datetime($ue->get('timecreated')) : '-',
1945             'timemodified' => $ue->get('timemodified') ? transform::datetime($ue->get('timemodified')) : '-',
1946             'created_or_modified_by_you' => transform::yesno($ue->get('usermodified') == $userid),
1947             'competencies' => []
1948         ];
1949     }
1951     /**
1952      * Loop and export from a recordset.
1953      *
1954      * @param moodle_recordset $recordset The recordset.
1955      * @param string $splitkey The record key to determine when to export.
1956      * @param mixed $initial The initial data to reduce from.
1957      * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
1958      * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
1959      * @return void
1960      */
1961     protected static function recordset_loop_and_export(moodle_recordset $recordset, $splitkey, $initial,
1962             callable $reducer, callable $export) {
1964         $data = $initial;
1965         $lastid = null;
1967         foreach ($recordset as $record) {
1968             if ($lastid && $record->{$splitkey} != $lastid) {
1969                 $export($lastid, $data);
1970                 $data = $initial;
1971             }
1972             $data = $reducer($data, $record);
1973             $lastid = $record->{$splitkey};
1974         }
1975         $recordset->close();
1977         if (!empty($lastid)) {
1978             $export($lastid, $data);
1979         }
1980     }