Merge branch 'MDL-62384-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE
[moodle.git] / competency / classes / privacy / provider.php
blob7f6e118c261bc2b3c55e53d377118e6c8d6078a1
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.
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.
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
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.
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
69 class provider implements
70 \core_privacy\local\metadata\provider,
71 \core_privacy\local\request\subsystem\provider {
73 /**
74 * Returns metadata.
76 * @param collection $collection The initialised collection to add items to.
77 * @return collection A listing of user data stored through this system.
79 public static function get_metadata(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_comment', 'privacy:metadata:core_comments');
216 return $collection;
220 * Get the list of contexts that contain user information for the specified user.
222 * @param int $userid The user to search.
223 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
225 public static function get_contexts_for_userid($userid) {
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 AND tch.usermodified = :userid2
254 LEFT JOIN {" . template_competency::TABLE . "} tc
255 ON tc.templateid = tpl.id
256 AND tc.usermodified = :userid3
257 WHERE tpl.usermodified = :userid1
258 OR tch.id IS NOT NULL
259 OR tc.id IS NOT NULL";
260 $params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid];
261 $contextlist->add_from_sql($sql, $params);
263 // Find the possible course contexts.
264 $sql = "
265 SELECT DISTINCT ctx.id
266 FROM {" . course_competency::TABLE . "} cc
267 JOIN {context} ctx
268 ON ctx.instanceid = cc.courseid
269 AND ctx.contextlevel = :courselevel
270 WHERE cc.usermodified = :userid";
271 $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
272 $contextlist->add_from_sql($sql, $params);
274 $sql = "
275 SELECT DISTINCT ctx.id
276 FROM {" . course_competency_settings::TABLE . "} ccs
277 JOIN {context} ctx
278 ON ctx.instanceid = ccs.courseid
279 AND ctx.contextlevel = :courselevel
280 WHERE ccs.usermodified = :userid";
281 $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
282 $contextlist->add_from_sql($sql, $params);
284 $sql = "
285 SELECT DISTINCT ctx.id
286 FROM {" . user_competency_course::TABLE . "} ucc
287 JOIN {context} ctx
288 ON ctx.instanceid = ucc.courseid
289 AND ctx.contextlevel = :courselevel
290 WHERE ucc.usermodified = :userid";
291 $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid];
292 $contextlist->add_from_sql($sql, $params);
294 // Find the possible module contexts.
295 $sql = "
296 SELECT DISTINCT ctx.id
297 FROM {" . course_module_competency::TABLE . "} cmc
298 JOIN {context} ctx
299 ON ctx.instanceid = cmc.cmid
300 AND ctx.contextlevel = :modulelevel
301 WHERE cmc.usermodified = :userid";
302 $params = ['modulelevel' => CONTEXT_MODULE, 'userid' => $userid];
303 $contextlist->add_from_sql($sql, $params);
305 // Add user contexts through usermodified/reviewing of plan related data.
306 $sql = "
307 SELECT DISTINCT ctx.id
308 FROM {" . plan::TABLE . "} p
309 JOIN {context} ctx
310 ON ctx.instanceid = p.userid
311 AND ctx.contextlevel = :userlevel
312 LEFT JOIN {" . plan_competency::TABLE . "} pc
313 ON pc.planid = p.id
314 AND pc.usermodified = :userid3
315 LEFT JOIN {" . user_competency_plan::TABLE . "} upc
316 ON upc.planid = p.id
317 AND upc.usermodified = :userid4
318 WHERE p.usermodified = :userid1
319 OR p.reviewerid = :userid2
320 OR pc.id IS NOT NULL
321 OR upc.id IS NOT NULL";
322 $params = [
323 'userlevel' => CONTEXT_USER,
324 'userid1' => $userid,
325 'userid2' => $userid,
326 'userid3' => $userid,
327 'userid4' => $userid,
329 $contextlist->add_from_sql($sql, $params);
331 // Add user contexts through usermodified/reviewing of competency data.
332 $sql = "
333 SELECT DISTINCT ctx.id
334 FROM {context} ctx
335 LEFT JOIN {" . user_competency::TABLE . "} uc
336 ON uc.userid = ctx.instanceid
337 AND ctx.contextlevel = :userlevel1
338 LEFT JOIN {" . evidence::TABLE . "} e
339 ON e.usercompetencyid = uc.id
340 AND (e.usermodified = :userid3 OR e.actionuserid = :userid4)
341 LEFT JOIN {" . user_evidence::TABLE . "} ue
342 ON ue.userid = ctx.instanceid
343 AND ctx.contextlevel = :userlevel2
344 AND ue.usermodified = :userid5
345 LEFT JOIN {" . user_evidence_competency::TABLE . "} uec
346 ON uec.userevidenceid = ue.id
347 AND uec.usermodified = :userid6
348 WHERE uc.usermodified = :userid1
349 OR uc.reviewerid = :userid2
350 OR e.id IS NOT NULL
351 OR ue.id IS NOT NULL
352 OR uec.id IS NOT NULL";
353 $params = [
354 'userlevel1' => CONTEXT_USER,
355 'userlevel2' => CONTEXT_USER,
356 'userid1' => $userid,
357 'userid2' => $userid,
358 'userid3' => $userid,
359 'userid4' => $userid,
360 'userid5' => $userid,
361 'userid6' => $userid,
363 $contextlist->add_from_sql($sql, $params);
365 // Now, the easy part, we fetch the user context for user plans and competencies.
366 // We also fetch the course context for the state of competencies for the user in courses.
367 $sql = "
368 SELECT DISTINCT ctx.id
369 FROM {context} ctx
370 LEFT JOIN {" . plan::TABLE . "} p
371 ON p.userid = ctx.instanceid
372 AND ctx.contextlevel = :userlevel1
373 LEFT JOIN {" . user_competency::TABLE . "} uc
374 ON uc.userid = ctx.instanceid
375 AND ctx.contextlevel = :userlevel2
376 AND uc.userid = :userid2
377 LEFT JOIN {" . user_evidence::TABLE . "} ue
378 ON ue.userid = ctx.instanceid
379 AND ctx.contextlevel = :userlevel3
380 AND ue.userid = :userid3
381 LEFT JOIN {" . user_competency_course::TABLE . "} ucc
382 ON ucc.courseid = ctx.instanceid
383 AND ctx.contextlevel = :courselevel
384 AND ucc.userid = :userid4
385 WHERE p.userid = :userid1
386 OR uc.id IS NOT NULL
387 OR ue.id IS NOT NULL
388 OR ucc.id IS NOT NULL";
389 $params = [
390 'userlevel1' => CONTEXT_USER,
391 'userlevel2' => CONTEXT_USER,
392 'userlevel3' => CONTEXT_USER,
393 'courselevel' => CONTEXT_COURSE,
394 'userid1' => $userid,
395 'userid2' => $userid,
396 'userid3' => $userid,
397 'userid4' => $userid,
399 $contextlist->add_from_sql($sql, $params);
401 // Include the user contexts in which the user commented.
402 $sql = "
403 SELECT ctx.id
404 FROM {context} ctx
405 JOIN {comments} c
406 ON c.contextid = ctx.id
407 WHERE c.component = :component
408 AND c.commentarea IN (:planarea, :usercomparea)
409 AND c.userid = :userid";
410 $params = [
411 'component' => 'competency', // Must not be core_competency.
412 'planarea' => 'plan',
413 'usercomparea' => 'user_competency',
414 'userid' => $userid
416 $contextlist->add_from_sql($sql, $params);
418 return $contextlist;
422 * Export all user data for the specified user, in the specified contexts.
424 * @param approved_contextlist $contextlist The approved contexts to export information for.
426 public static function export_user_data(approved_contextlist $contextlist) {
427 $user = $contextlist->get_user();
428 $userid = $user->id;
430 // Re-arrange the contexts by context level.
431 $groupedcontexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
432 $contextlevel = $context->contextlevel;
433 if (!in_array($contextlevel, [CONTEXT_USER, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_SYSTEM, CONTEXT_COURSECAT])) {
434 return $carry;
436 $carry[$contextlevel][] = $context;
437 return $carry;
438 }, [
439 CONTEXT_COURSE => [],
440 CONTEXT_COURSECAT => [],
441 CONTEXT_MODULE => [],
442 CONTEXT_SYSTEM => [],
443 CONTEXT_USER => [],
446 // Process module contexts.
447 static::export_user_data_in_module_contexts($userid, $groupedcontexts[CONTEXT_MODULE]);
449 // Process course contexts.
450 static::export_user_data_in_course_contexts($userid, $groupedcontexts[CONTEXT_COURSE]);
452 // Process course categories context.
453 static::export_user_data_in_category_contexts($userid, $groupedcontexts[CONTEXT_COURSECAT]);
455 // Process system context.
456 if (!empty($groupedcontexts[CONTEXT_SYSTEM])) {
457 static::export_user_data_in_system_context($userid);
460 // Process user contexts.
461 static::export_user_data_in_user_contexts($userid, $groupedcontexts[CONTEXT_USER]);
465 * Delete all data for all users in the specified context.
467 * @param context $context The specific context to delete data for.
469 public static function delete_data_for_all_users_in_context(context $context) {
470 global $DB;
472 switch ($context->contextlevel) {
473 case CONTEXT_USER:
474 $userid = $context->instanceid;
475 static::delete_user_evidence_of_prior_learning($userid);
476 static::delete_user_plans($userid);
477 static::delete_user_competencies($userid);
478 break;
480 case CONTEXT_COURSE:
481 $courseid = $context->instanceid;
482 $DB->delete_records(user_competency_course::TABLE, ['courseid' => $courseid]);
483 break;
488 * Delete all user data for the specified user, in the specified contexts.
490 * Here we only delete the private data of user, not whether they modified, are reviewing,
491 * or are associated with the record on at a second level. Only data directly linked to the
492 * user will be affected.
494 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
496 public static function delete_data_for_user(approved_contextlist $contextlist) {
497 $user = $contextlist->get_user();
498 $userid = $user->id;
500 foreach ($contextlist as $context) {
501 switch ($context->contextlevel) {
502 case CONTEXT_USER:
503 if ($context->instanceid != $userid) {
504 // Only delete the user's information when they requested their context to be deleted. We
505 // do not take any action on other user's contexts because we don't own the data there.
506 continue;
508 static::delete_user_evidence_of_prior_learning($userid);
509 static::delete_user_plans($userid);
510 static::delete_user_competencies($userid);
511 break;
513 case CONTEXT_COURSE:
514 static::delete_user_competencies_in_course($userid, $context->instanceid);
515 break;
521 * Delete evidence of prior learning.
523 * @param int $userid The user ID.
524 * @return void
526 protected static function delete_user_evidence_of_prior_learning($userid) {
527 global $DB;
529 $usercontext = context_user::instance($userid);
530 $ueids = $DB->get_fieldset_select(user_evidence::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
531 if (empty($ueids)) {
532 return;
534 list($insql, $inparams) = $DB->get_in_or_equal($ueids, SQL_PARAMS_NAMED);
536 // Delete competencies associated with user evidence.
537 $DB->delete_records_select(user_evidence_competency::TABLE, "userevidenceid $insql", $inparams);
539 // Delete the user evidence.
540 $DB->delete_records_select(user_evidence::TABLE, "id $insql", $inparams);
542 // Delete the user evidence files.
543 $fs = get_file_storage();
544 $fs->delete_area_files($usercontext->id, 'core_competency', 'userevidence');
548 * User plans.
550 * @param int $userid The user ID.
551 * @return void
553 protected static function delete_user_plans($userid) {
554 global $DB;
555 $usercontext = context_user::instance($userid);
557 // Remove all the comments made on plans.
558 \core_comment\privacy\provider::delete_comments_for_all_users($usercontext, 'competency', 'plan');
560 // Find the user plan IDs.
561 $planids = $DB->get_fieldset_select(plan::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
562 if (empty($planids)) {
563 return;
565 list($insql, $inparams) = $DB->get_in_or_equal($planids, SQL_PARAMS_NAMED);
567 // Delete all the competencies proficiency in the plans.
568 $DB->delete_records_select(user_competency_plan::TABLE, "planid $insql", $inparams);
570 // Delete all the competencies in the plans.
571 $DB->delete_records_select(plan_competency::TABLE, "planid $insql", $inparams);
573 // Delete all the plans.
574 $DB->delete_records_select(plan::TABLE, "id $insql", $inparams);
578 * Delete user competency data.
580 * @param int $userid The user ID.
581 * @return void
583 protected static function delete_user_competencies($userid) {
584 global $DB;
585 $usercontext = context_user::instance($userid);
587 // Remove all the comments made on user competencies.
588 \core_comment\privacy\provider::delete_comments_for_all_users($usercontext, 'competency', 'user_competency');
590 // Find the user competency IDs.
591 $ucids = $DB->get_fieldset_select(user_competency::TABLE, 'id', 'userid = :userid', ['userid' => $userid]);
592 if (empty($ucids)) {
593 return;
595 list($insql, $inparams) = $DB->get_in_or_equal($ucids, SQL_PARAMS_NAMED);
597 // Delete all the evidence associated with competencies.
598 $DB->delete_records_select(evidence::TABLE, "usercompetencyid $insql", $inparams);
600 // Delete all the record of competency.
601 $DB->delete_records_select(user_competency::TABLE, "id $insql", $inparams);
605 * Delete the record of competencies for a user in a course.
607 * @param int $userid The user ID.
608 * @param int $courseid The course ID.
609 * @return void
611 protected static function delete_user_competencies_in_course($userid, $courseid) {
612 global $DB;
613 $DB->delete_records(user_competency_course::TABLE, ['userid' => $userid, 'courseid' => $courseid]);
617 * Export the user data in user context.
619 * @param int $userid The user ID.
620 * @param array $contexts The contexts.
621 * @return void
623 protected static function export_user_data_in_user_contexts($userid, array $contexts) {
624 global $DB;
626 $mycontext = context_user::instance($userid);
627 $contextids = array_map(function($context) {
628 return $context->id;
629 }, $contexts);
630 $exportowncontext = in_array($mycontext->id, $contextids);
631 $othercontexts = array_filter($contextids, function($contextid) use ($mycontext) {
632 return $contextid != $mycontext->id;
635 if ($exportowncontext) {
636 static::export_user_data_learning_plans($mycontext);
637 static::export_user_data_competencies($mycontext);
638 static::export_user_data_user_evidence($mycontext);
641 foreach ($othercontexts as $contextid) {
642 static::export_user_data_learning_plans_related_to_me($userid, context::instance_by_id($contextid));
643 static::export_user_data_competencies_related_to_me($userid, context::instance_by_id($contextid));
644 static::export_user_data_user_evidence_related_to_me($userid, context::instance_by_id($contextid));
649 * Export the user data in systen context.
651 * @param int $userid The user ID.
652 * @return void
654 protected static function export_user_data_in_system_context($userid) {
655 static::export_user_data_frameworks_in_context($userid, context_system::instance());
656 static::export_user_data_templates_in_context($userid, context_system::instance());
660 * Export the user data in category contexts.
662 * @param int $userid The user ID.
663 * @param array $contexts The contexts.
664 * @return void
666 protected static function export_user_data_in_category_contexts($userid, array $contexts) {
667 $contexts = array_filter($contexts, function($context) {
668 return $context->contextlevel == CONTEXT_COURSECAT;
670 if (empty($contexts)) {
671 return;
674 foreach ($contexts as $context) {
675 static::export_user_data_frameworks_in_context($userid, $context);
676 static::export_user_data_templates_in_context($userid, $context);
681 * Export the user data in course contexts.
683 * @param int $userid The user whose data we're exporting.
684 * @param array $contexts A list of contexts.
685 * @return void
687 protected static function export_user_data_in_course_contexts($userid, array $contexts) {
688 global $DB;
690 $contexts = array_filter($contexts, function($context) {
691 return $context->contextlevel == CONTEXT_COURSE;
693 if (empty($contexts)) {
694 return;
697 $helper = new performance_helper();
698 $path = [get_string('competencies', 'core_competency')];
699 $courseids = array_map(function($context) {
700 return $context->instanceid;
701 }, $contexts);
703 // Fetch all the records of competency proficiency in the course.
704 $ffields = competency_framework::get_sql_fields('f', 'f_');
705 $compfields = competency::get_sql_fields('c', 'c_');
706 $uccfields = user_competency_course::get_sql_fields('ucc', 'ucc_');
707 $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
708 list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
709 $sql = "
710 SELECT $ffields, $compfields, $uccfields, $ctxfields
711 FROM {" . user_competency_course::TABLE . "} ucc
712 JOIN {" . competency::TABLE . "} c
713 ON c.id = ucc.competencyid
714 JOIN {" . competency_framework::TABLE . "} f
715 ON f.id = c.competencyframeworkid
716 JOIN {context} ctx
717 ON ctx.id = f.contextid
718 WHERE ucc.userid = :userid
719 AND ucc.courseid $insql
720 ORDER BY ucc.courseid, c.id";
721 $params = array_merge($inparams, ['userid' => $userid]);
723 // Export data.
724 $recordset = $DB->get_recordset_sql($sql, $params);
725 static::recordset_loop_and_export($recordset, 'ucc_courseid', [], function($carry, $record) use ($helper) {
726 context_helper::preload_from_record($record);
727 $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
728 $competency = new competency(null, competency::extract_record($record, 'c_'));
729 $ucc = new user_competency_course(null, user_competency_course::extract_record($record, 'ucc_'));
730 $helper->ingest_framework($framework);
732 $carry[] = array_merge(static::transform_competency_brief($competency), [
733 'rating' => [
734 'rating' => static::transform_competency_grade($competency, $ucc->get('grade'), $helper),
735 'proficient' => static::transform_proficiency($ucc->get('proficiency')),
736 'timecreated' => transform::datetime($ucc->get('timecreated')),
737 'timemodified' => transform::datetime($ucc->get('timemodified')),
740 return $carry;
742 }, function($courseid, $data) use ($path) {
743 $context = context_course::instance($courseid);
744 writer::with_context($context)->export_data($path, (object) ['ratings' => $data]);
747 // Export usermodified data.
748 static::export_user_data_in_course_contexts_associations($userid, $courseids, $path);
749 static::export_user_data_in_course_contexts_settings($userid, $courseids, $path);
750 static::export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path, $helper);
754 * Export the ratings given in a course.
756 * @param int $userid The user ID.
757 * @param array $courseids The course IDs.
758 * @param array $path The root path.
759 * @param performance_helper $helper The performance helper.
760 * @return void
762 protected static function export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path,
763 performance_helper $helper) {
764 global $DB;
766 // Fetch all the records of competency proficiency in the course.
767 $ffields = competency_framework::get_sql_fields('f', 'f_');
768 $compfields = competency::get_sql_fields('c', 'c_');
769 $uccfields = user_competency_course::get_sql_fields('ucc', 'ucc_');
770 $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
771 list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
772 $sql = "
773 SELECT $ffields, $compfields, $uccfields, $ctxfields
774 FROM {" . user_competency_course::TABLE . "} ucc
775 JOIN {" . competency::TABLE . "} c
776 ON c.id = ucc.competencyid
777 JOIN {" . competency_framework::TABLE . "} f
778 ON f.id = c.competencyframeworkid
779 JOIN {context} ctx
780 ON ctx.id = f.contextid
781 WHERE ucc.usermodified = :userid
782 AND ucc.courseid $insql
783 ORDER BY ucc.courseid, ucc.id";
784 $params = array_merge($inparams, ['userid' => $userid]);
786 // Export the data.
787 static::recordset_loop_and_export($DB->get_recordset_sql($sql, $params), 'ucc_courseid', [],
788 function($carry, $record) use ($helper) {
789 context_helper::preload_from_record($record);
791 $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
792 $competency = new competency(null, competency::extract_record($record, 'c_'));
793 $ucc = new user_competency_course(null, user_competency_course::extract_record($record, 'ucc_'));
794 $helper->ingest_framework($framework);
796 $carry[] = array_merge(static::transform_competency_brief($competency), [
797 'rating' => [
798 'userid' => transform::user($ucc->get('userid')),
799 'rating' => static::transform_competency_grade($competency, $ucc->get('grade'), $helper),
800 'proficient' => static::transform_proficiency($ucc->get('proficiency')),
801 'timemodified' => transform::datetime($ucc->get('timemodified')),
804 return $carry;
806 }, function($courseid, $data) use ($path) {
807 $context = context_course::instance($courseid);
808 writer::with_context($context)->export_related_data($path, 'rated_by_me', (object) [
809 'ratings' => $data
816 * Export user data in course contexts related to linked competencies.
818 * @param int $userid The user ID.
819 * @param array $courseids The course IDs.
820 * @param array $path The root path to export at.
821 * @return void
823 protected static function export_user_data_in_course_contexts_associations($userid, $courseids, $path) {
824 global $DB;
826 // Fetch all the courses with associations we created or modified.
827 $compfields = competency::get_sql_fields('c', 'c_');
828 $ccfields = course_competency::get_sql_fields('cc', 'cc_');
829 $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
830 list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
831 $sql = "
832 SELECT $compfields, $ccfields, $ctxfields
833 FROM {" . course_competency::TABLE . "} cc
834 JOIN {" . competency::TABLE . "} c
835 ON c.id = cc.competencyid
836 JOIN {" . competency_framework::TABLE . "} f
837 ON f.id = c.competencyframeworkid
838 JOIN {context} ctx
839 ON ctx.id = f.contextid
840 WHERE cc.usermodified = :userid
841 AND cc.courseid $insql
842 ORDER BY cc.courseid, c.id";
843 $params = array_merge($inparams, ['userid' => $userid]);
844 $recordset = $DB->get_recordset_sql($sql, $params);
846 // Export the data.
847 static::recordset_loop_and_export($recordset, 'cc_courseid', [], function($carry, $record) {
848 context_helper::preload_from_record($record);
849 $competency = new competency(null, competency::extract_record($record, 'c_'));
850 $cc = new course_competency(null, course_competency::extract_record($record, 'cc_'));
851 $carry[] = array_merge(static::transform_competency_brief($competency), [
852 'timemodified' => transform::datetime($cc->get('timemodified')),
853 'created_or_modified_by_you' => transform::yesno(true)
855 return $carry;
857 }, function($courseid, $data) use ($path, $userid, $DB) {
858 $context = context_course::instance($courseid);
859 writer::with_context($context)->export_related_data($path, 'associations', (object) ['competencies' => $data]);
864 * Export user data in course contexts related to course settings.
866 * @param int $userid The user ID.
867 * @param array $courseids The course IDs.
868 * @param array $path The root path to export at.
869 * @return void
871 protected static function export_user_data_in_course_contexts_settings($userid, $courseids, $path) {
872 global $DB;
874 // Fetch all the courses with associations we created or modified.
875 $ccsfields = course_competency_settings::get_sql_fields('ccs', 'ccs_');
876 list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
877 $sql = "
878 SELECT $ccsfields
879 FROM {" . course_competency_settings::TABLE . "} ccs
880 WHERE ccs.usermodified = :userid
881 AND ccs.courseid $insql
882 ORDER BY ccs.courseid";
883 $params = array_merge($inparams, ['userid' => $userid]);
884 $recordset = $DB->get_recordset_sql($sql, $params);
886 // Export the data.
887 static::recordset_loop_and_export($recordset, 'ccs_courseid', [], function($carry, $record) {
888 $ccs = new course_competency_settings(null, course_competency_settings::extract_record($record, 'ccs_'));
889 return [
890 'timemodified' => transform::datetime($ccs->get('timemodified')),
891 'created_or_modified_by_you' => transform::yesno(true)
893 }, function($courseid, $data) use ($path, $userid, $DB) {
894 $context = context_course::instance($courseid);
895 writer::with_context($context)->export_related_data($path, 'settings', (object) $data);
900 * Export the user data in module contexts.
902 * @param int $userid The user whose data we're exporting.
903 * @param array $contexts A list of contexts.
904 * @return void
906 protected static function export_user_data_in_module_contexts($userid, array $contexts) {
907 global $DB;
909 $contexts = array_filter($contexts, function($context) {
910 return $context->contextlevel == CONTEXT_MODULE;
912 if (empty($contexts)) {
913 return;
916 $path = [get_string('competencies', 'core_competency')];
917 $cmids = array_map(function($context) {
918 return $context->instanceid;
919 }, $contexts);
921 // Fetch all the modules with associations we created or modified.
922 $compfields = competency::get_sql_fields('c', 'c_');
923 $cmcfields = course_module_competency::get_sql_fields('cmc', 'cmc_');
924 $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
925 list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
926 $sql = "
927 SELECT $compfields, $cmcfields, $ctxfields
928 FROM {" . course_module_competency::TABLE . "} cmc
929 JOIN {" . competency::TABLE . "} c
930 ON c.id = cmc.competencyid
931 JOIN {" . competency_framework::TABLE . "} f
932 ON f.id = c.competencyframeworkid
933 JOIN {context} ctx
934 ON ctx.id = f.contextid
935 WHERE cmc.usermodified = :userid
936 AND cmc.cmid $insql
937 ORDER BY cmc.cmid";
938 $params = array_merge($inparams, ['userid' => $userid]);
940 // Export the data.
941 $recordset = $DB->get_recordset_sql($sql, $params);
942 static::recordset_loop_and_export($recordset, 'cmc_cmid', [], function($carry, $record) {
943 context_helper::preload_from_record($record);
944 $competency = new competency(null, competency::extract_record($record, 'c_'));
945 $cmc = new course_module_competency(null, course_module_competency::extract_record($record, 'cmc_'));
946 $carry[] = array_merge(static::transform_competency_brief($competency), [
947 'timecreated' => transform::datetime($cmc->get('timecreated')),
948 'timemodified' => transform::datetime($cmc->get('timemodified')),
949 'created_or_modified_by_you' => transform::yesno(true)
951 return $carry;
953 }, function($cmid, $data) use ($path) {
954 $context = context_module::instance($cmid);
955 writer::with_context($context)->export_data($path, (object) ['associations' => $data]);
960 * Export a user's competencies.
962 * @param context_user $context The context of the user requesting the export.
963 * @return void
965 protected static function export_user_data_competencies(context_user $context) {
966 global $DB;
968 $userid = $context->instanceid;
969 $path = [get_string('competencies', 'core_competency'), get_string('competencies', 'core_competency')];
970 $helper = new performance_helper();
971 $cfields = competency::get_sql_fields('c', 'c_');
972 $ucfields = user_competency::get_sql_fields('uc', 'uc_');
973 $efields = evidence::get_sql_fields('e', 'e_');
975 $makecomppath = function($competencyid, $data) use ($path) {
976 return array_merge($path, [$data['name'] . ' (' . $competencyid . ')']);
979 $sql = "
980 SELECT $cfields, $ucfields, $efields
981 FROM {" . user_competency::TABLE . "} uc
982 JOIN {" . competency::TABLE . "} c
983 ON c.id = uc.competencyid
984 LEFT JOIN {" . evidence::TABLE . "} e
985 ON uc.id = e.usercompetencyid
986 WHERE uc.userid = :userid
987 ORDER BY c.id, e.timecreated DESC, e.id DESC";
988 $params = ['userid' => $userid];
990 $recordset = $DB->get_recordset_sql($sql, $params);
991 static::recordset_loop_and_export($recordset, 'c_id', null, function($carry, $record)
992 use ($context, $userid, $helper, $makecomppath) {
994 $competency = new competency(null, competency::extract_record($record, 'c_'));
996 if ($carry === null) {
997 $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
998 $carry = array_merge(static::transform_competency_brief($competency), [
999 'rating' => static::transform_user_competency($userid, $uc, $competency, $helper),
1000 'evidence' => []
1002 \core_comment\privacy\provider::export_comments($context, 'competency', 'user_competency',
1003 $uc->get('id'), $makecomppath($competency->get('id'), $carry), false);
1006 // There is an evidence in this record.
1007 if (!empty($record->e_id)) {
1008 $evidence = new evidence(null, evidence::extract_record($record, 'e_'));
1009 $carry['evidence'][] = static::transform_evidence($userid, $evidence, $competency, $helper);
1012 return $carry;
1014 }, function($competencyid, $data) use ($makecomppath, $context) {
1015 writer::with_context($context)->export_data($makecomppath($competencyid, $data), (object) $data);
1020 * Export a user's learning plans.
1022 * @param context_user $context The context of the user requesting the export.
1023 * @return void
1025 protected static function export_user_data_learning_plans(context_user $context) {
1026 global $DB;
1028 $userid = $context->instanceid;
1029 $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:plans', 'core_competency')];
1030 $helper = new performance_helper();
1031 $pfields = plan::get_sql_fields('p', 'p_');
1032 $pcfields = plan_competency::get_sql_fields('pc', 'pc_');
1033 $cfields = competency::get_sql_fields('c', 'c_');
1034 $ucfields = user_competency::get_sql_fields('uc', 'uc_');
1035 $ucpfields = user_competency_plan::get_sql_fields('ucp', 'ucp_');
1037 // The user's learning plans.
1038 $sql = "
1039 SELECT $pfields, $pcfields, $cfields, $ucfields, $ucpfields
1040 FROM {" . plan::TABLE . "} p
1041 LEFT JOIN {" . plan_competency::TABLE . "} pc
1042 ON p.id = pc.planid
1043 AND p.templateid IS NULL
1044 AND p.status != :complete1
1045 LEFT JOIN {" . template_competency::TABLE . "} tc
1046 ON tc.templateid = p.templateid
1047 AND p.templateid IS NOT NULL
1048 AND p.status != :complete2
1049 LEFT JOIN {" . user_competency_plan::TABLE . "} ucp
1050 ON ucp.planid = p.id
1051 AND p.status = :complete3
1052 LEFT JOIN {" . competency::TABLE . "} c
1053 ON c.id = pc.competencyid
1054 OR c.id = tc.competencyid
1055 OR c.id = ucp.competencyid
1056 LEFT JOIN {" . user_competency::TABLE . "} uc
1057 ON uc.userid = p.userid
1058 AND (uc.competencyid = pc.competencyid OR uc.competencyid = tc.competencyid)
1059 WHERE p.userid = :userid
1060 ORDER BY p.id, c.id";
1061 $params = [
1062 'userid' => $userid,
1063 'complete1' => plan::STATUS_COMPLETE,
1064 'complete2' => plan::STATUS_COMPLETE,
1065 'complete3' => plan::STATUS_COMPLETE,
1068 $recordset = $DB->get_recordset_sql($sql, $params);
1069 static::recordset_loop_and_export($recordset, 'p_id', null, function($carry, $record) use ($userid, $helper, $context) {
1070 $iscomplete = $record->p_status == plan::STATUS_COMPLETE;
1072 if ($carry === null) {
1073 $plan = new plan(null, plan::extract_record($record, 'p_'));
1074 $options = ['context' => $context];
1075 $carry = [
1076 'name' => format_string($plan->get('name'), true, $options),
1077 'description' => format_text($plan->get('description'), $plan->get('descriptionformat'), $options),
1078 'status' => $plan->get_statusname(),
1079 'duedate' => $plan->get('duedate') ? transform::datetime($plan->get('duedate')) : '-',
1080 'reviewerid' => $plan->get('reviewerid') ? transform::user($plan->get('reviewerid')) : '-',
1081 'timecreated' => transform::datetime($plan->get('timecreated')),
1082 'timemodified' => transform::datetime($plan->get('timemodified')),
1083 'competencies' => [],
1087 // The plan is empty.
1088 if (empty($record->c_id)) {
1089 return $carry;
1092 $competency = new competency(null, competency::extract_record($record, 'c_'));
1093 $rating = null;
1095 if ($iscomplete) {
1096 // When the plan is complete, we should always found the user_competency_plan.
1097 $ucp = new user_competency_plan(null, user_competency_plan::extract_record($record, 'ucp_'));
1098 $rating = static::transform_user_competency($userid, $ucp, $competency, $helper);
1100 } else if (!empty($record->uc_id)) {
1101 // When the plan is complete, there are still records of user_competency but we do not
1102 // export them here, we export them as part of the competencies structure. The reason why
1103 // we try to get the user_competency when the plan is not complete is to give the most accurate
1104 // representation of the plan as possible.
1105 $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
1106 $rating = static::transform_user_competency($userid, $uc, $competency, $helper);
1109 $carry['competencies'][] = array_merge(static::transform_competency_brief($competency), ['rating' => $rating]);
1110 return $carry;
1112 }, function($planid, $data) use ($context, $path) {
1113 $planpath = array_merge($path, [$data['name'] . ' (' . $planid . ')']);
1114 \core_comment\privacy\provider::export_comments($context, 'competency', 'plan', $planid, $planpath, false);
1115 writer::with_context($context)->export_data($planpath, (object) $data);
1120 * Export a user's data related to learning plans.
1122 * @param int $userid The user ID we're exporting for.
1123 * @param context_user $context The context of the user in which we're gathering data.
1124 * @return void
1126 protected static function export_user_data_learning_plans_related_to_me($userid, context_user $context) {
1127 global $DB;
1129 $path = [
1130 get_string('competencies', 'core_competency'),
1131 get_string('privacy:path:relatedtome', 'core_competency'),
1132 get_string('privacy:path:plans', 'core_competency'),
1134 $plans = [];
1135 $helper = new performance_helper();
1136 $pfields = plan::get_sql_fields('p', 'p_');
1137 $pcfields = plan_competency::get_sql_fields('pc', 'pc_');
1138 $cfields = competency::get_sql_fields('c', 'c_');
1139 $ucpfields = user_competency_plan::get_sql_fields('ucp', 'ucp_');
1141 // Function to initialise a plan record.
1142 $initplan = function($record) use ($context, $userid, &$plans) {
1143 $plan = new plan(null, plan::extract_record($record, 'p_'));
1144 $options = ['context' => $context];
1145 $plans[$plan->get('id')] = [
1146 'name' => format_string($plan->get('name'), true, $options),
1147 'reviewer_is_you' => transform::yesno($plan->get('reviewerid') == $userid),
1148 'timecreated' => transform::datetime($plan->get('timecreated')),
1149 'timemodified' => transform::datetime($plan->get('timemodified')),
1150 'created_or_modified_by_you' => transform::yesno($plan->get('usermodified') == $userid),
1151 'competencies' => [],
1155 $initcompetency = function($record, $planid) use (&$plans) {
1156 $competency = new competency(null, competency::extract_record($record, 'c_'));
1157 $plans[$planid]['competencies'][$competency->get('id')] = static::transform_competency_brief($competency);
1160 // Look for associations that were created.
1161 $sql = "
1162 SELECT $pfields, $pcfields, $cfields
1163 FROM {" . plan_competency::TABLE . "} pc
1164 JOIN {" . plan::TABLE . "} p
1165 ON p.id = pc.planid
1166 JOIN {" . competency::TABLE . "} c
1167 ON c.id = pc.competencyid
1168 WHERE p.userid = :targetuserid
1169 AND pc.usermodified = :userid
1170 ORDER BY p.id, c.id";
1171 $params = [
1172 'targetuserid' => $context->instanceid,
1173 'userid' => $userid,
1176 $recordset = $DB->get_recordset_sql($sql, $params);
1177 foreach ($recordset as $record) {
1178 $planid = $record->p_id;
1179 if (!isset($plans[$planid])) {
1180 $initplan($record);
1183 $initcompetency($record, $planid);
1184 $pc = new plan_competency(null, plan_competency::extract_record($record, 'pc_'));
1185 $plans[$planid]['competencies'][$pc->get('competencyid')] = array_merge(
1186 $plans[$planid]['competencies'][$pc->get('competencyid')], [
1187 'timemodified' => $pc->get('timemodified') ? transform::datetime($pc->get('timemodified')) : '-',
1188 'timecreated' => $pc->get('timecreated') ? transform::datetime($pc->get('timecreated')) : '-',
1189 'created_or_modified_by_you' => transform::yesno($pc->get('usermodified') == $userid),
1193 $recordset->close();
1195 // Look for final grades that were given.
1196 $sql = "
1197 SELECT $pfields, $ucpfields, $cfields
1198 FROM {" . user_competency_plan::TABLE . "} ucp
1199 JOIN {" . plan::TABLE . "} p
1200 ON p.id = ucp.planid
1201 JOIN {" . competency::TABLE . "} c
1202 ON c.id = ucp.competencyid
1203 WHERE p.userid = :targetuserid
1204 AND ucp.usermodified = :userid
1205 ORDER BY p.id, c.id";
1206 $params = [
1207 'targetuserid' => $context->instanceid,
1208 'userid' => $userid,
1211 $recordset = $DB->get_recordset_sql($sql, $params);
1212 foreach ($recordset as $record) {
1213 $planid = $record->p_id;
1214 $competencyid = $record->c_id;
1216 if (!isset($plans[$planid])) {
1217 $initplan($record);
1220 if (!isset($plans[$planid]['competencies'][$competencyid])) {
1221 $initcompetency($record, $planid);
1224 $competency = new competency(null, competency::extract_record($record, 'c_'));
1225 $ucp = new user_competency_plan(null, user_competency_plan::extract_record($record, 'ucp_'));
1226 $plans[$planid]['competencies'][$competencyid]['rating'] = static::transform_user_competency($userid, $ucp,
1227 $competency, $helper);
1229 $recordset->close();
1231 // Find the plans that were modified or reviewed.
1232 $insql = " > 0";
1233 $inparams = [];
1234 if (!empty($plans)) {
1235 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($plans), SQL_PARAMS_NAMED, 'param', false);
1237 $sql = "
1238 SELECT $pfields
1239 FROM {" . plan::TABLE . "} p
1240 LEFT JOIN {comments} c
1241 ON c.contextid = :contextid
1242 AND c.commentarea = :planarea
1243 AND c.component = :competency
1244 AND c.itemid = p.id
1245 WHERE p.userid = :targetuserid
1246 AND (p.usermodified = :userid1
1247 OR p.reviewerid = :userid2
1248 OR c.userid = :userid3)
1249 AND p.id $insql
1250 ORDER BY p.id";
1251 $params = array_merge($inparams, [
1252 'targetuserid' => $context->instanceid,
1253 'userid1' => $userid,
1254 'userid2' => $userid,
1255 'userid3' => $userid,
1256 'contextid' => $context->id,
1257 'planarea' => 'plan',
1258 'competency' => 'competency'
1261 $recordset = $DB->get_recordset_sql($sql, $params);
1262 foreach ($recordset as $record) {
1263 $planid = $record->p_id;
1264 if (!isset($plans[$planid])) {
1265 $initplan($record);
1268 $recordset->close();
1270 // Export each plan on its own.
1271 foreach ($plans as $planid => $plan) {
1272 $planpath = array_merge($path, ["{$plan['name']} ({$planid})"]);
1273 $plan['competencies'] = array_values($plan['competencies']); // Drop the keys.
1275 writer::with_context($context)->export_data($planpath, (object) $plan);
1276 \core_comment\privacy\provider::export_comments($context, 'competency', 'plan', $planid, $planpath, true);
1281 * Export a user's data related to competencies.
1283 * @param int $userid The user ID we're exporting for.
1284 * @param context_user $context The context of the user in which we're gathering data.
1285 * @return void
1287 protected static function export_user_data_competencies_related_to_me($userid, context_user $context) {
1288 global $DB;
1290 $path = [
1291 get_string('competencies', 'core_competency'),
1292 get_string('privacy:path:relatedtome', 'core_competency'),
1293 get_string('competencies', 'core_competency'),
1295 $competencies = [];
1296 $helper = new performance_helper();
1297 $cfields = competency::get_sql_fields('c', 'c_');
1298 $ucfields = user_competency::get_sql_fields('uc', 'uc_');
1299 $efields = evidence::get_sql_fields('e', 'e_');
1301 $initcompetency = function($record) use (&$competencies) {
1302 $competency = new competency(null, competency::extract_record($record, 'c_'));
1303 $competencies[$competency->get('id')] = array_merge(static::transform_competency_brief($competency), [
1304 'evidence' => []
1308 $initusercomp = function($competency, $record) use (&$competencies, $userid, $helper) {
1309 $competencyid = $competency->get('id');
1310 $uc = new user_competency(null, user_competency::extract_record($record, 'uc_'));
1311 $competencies[$competencyid]['uc_id'] = $uc->get('id');
1312 $competencies[$competencyid]['rating'] = static::transform_user_competency($userid, $uc, $competency, $helper);
1315 // Look for evidence.
1316 $sql = "
1317 SELECT $efields, $ucfields, $cfields
1318 FROM {" . evidence::TABLE . "} e
1319 JOIN {" . user_competency::TABLE . "} uc
1320 ON uc.id = e.usercompetencyid
1321 JOIN {" . competency::TABLE . "} c
1322 ON c.id = uc.competencyid
1323 WHERE uc.userid = :targetuserid
1324 AND (e.usermodified = :userid1
1325 OR e.actionuserid = :userid2)
1326 ORDER BY c.id, e.id";
1327 $params = [
1328 'targetuserid' => $context->instanceid,
1329 'userid1' => $userid,
1330 'userid2' => $userid,
1332 $recordset = $DB->get_recordset_sql($sql, $params);
1333 foreach ($recordset as $record) {
1334 $competencyid = $record->c_id;
1335 $competency = new competency(null, competency::extract_record($record, 'c_'));
1337 if (!isset($competencies[$competencyid])) {
1338 $initcompetency($record);
1341 if (!array_key_exists('rating', $competencies[$competencyid])) {
1342 $competencies[$competencyid]['rating'] = null;
1343 if ($record->uc_reviewerid == $userid || $record->uc_usermodified == $userid) {
1344 $initusercomp($competency, $record);
1348 $evidence = new evidence(null, evidence::extract_record($record, 'e_'));
1349 $competencies[$competencyid]['evidence'][] = static::transform_evidence($userid, $evidence, $competency, $helper);
1351 $recordset->close();
1353 // Look for user competency we modified and didn't catch.
1354 $insql = ' > 0';
1355 $inparams = [];
1356 if (!empty($competencies)) {
1357 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($competencies), SQL_PARAMS_NAMED, 'param', false);
1359 $sql = "
1360 SELECT $ucfields, $cfields
1361 FROM {" . user_competency::TABLE . "} uc
1362 JOIN {" . competency::TABLE . "} c
1363 ON c.id = uc.competencyid
1364 LEFT JOIN {comments} cmt
1365 ON cmt.contextid = :contextid
1366 AND cmt.commentarea = :ucarea
1367 AND cmt.component = :competency
1368 AND cmt.itemid = uc.id
1369 WHERE uc.userid = :targetuserid
1370 AND (uc.usermodified = :userid1
1371 OR uc.reviewerid = :userid2
1372 OR cmt.userid = :userid3)
1373 AND uc.competencyid $insql
1374 ORDER BY c.id, uc.id";
1375 $params = array_merge($inparams, [
1376 'targetuserid' => $context->instanceid,
1377 'userid1' => $userid,
1378 'userid2' => $userid,
1379 'userid3' => $userid,
1380 'contextid' => $context->id,
1381 'ucarea' => 'user_competency',
1382 'competency' => 'competency',
1385 $recordset = $DB->get_recordset_sql($sql, $params);
1386 foreach ($recordset as $record) {
1387 $competency = new competency(null, competency::extract_record($record, 'c_'));
1388 if (!isset($competencies[$competency->get('id')])) {
1389 $initcompetency($record);
1390 $initusercomp($competency, $record);
1393 $recordset->close();
1395 // Export each competency on its own.
1396 foreach ($competencies as $competencyid => $competency) {
1397 $comppath = array_merge($path, ["{$competency['name']} ({$competencyid})"]);
1398 $ucid = isset($competency['uc_id']) ? $competency['uc_id'] : null;
1399 unset($competency['uc_id']);
1401 // Send to writer.
1402 writer::with_context($context)->export_data($comppath, (object) $competency);
1403 if ($ucid) {
1404 \core_comment\privacy\provider::export_comments($context, 'competency', 'user_competency', $ucid, $comppath, true);
1410 * Export a user's data related to evidence of prior learning.
1412 * @param int $userid The user ID we're exporting for.
1413 * @param context_user $context The context of the user in which we're gathering data.
1414 * @return void
1416 protected static function export_user_data_user_evidence_related_to_me($userid, context_user $context) {
1417 global $DB;
1419 $path = [
1420 get_string('competencies', 'core_competency'),
1421 get_string('privacy:path:relatedtome', 'core_competency'),
1422 get_string('privacy:path:userevidence', 'core_competency'),
1424 $evidence = [];
1425 $helper = new performance_helper();
1426 $cfields = competency::get_sql_fields('c', 'c_');
1427 $uecfields = user_evidence_competency::get_sql_fields('uec', 'uec_');
1428 $uefields = user_evidence::get_sql_fields('ue', 'ue_');
1430 $initevidence = function($record) use (&$evidence, $userid) {
1431 $ue = new user_evidence(null, user_evidence::extract_record($record, 'ue_'));
1432 $evidence[$ue->get('id')] = static::transform_user_evidence($userid, $ue);
1435 // Look for evidence.
1436 $sql = "
1437 SELECT $uefields, $uecfields, $cfields
1438 FROM {" . user_evidence_competency::TABLE . "} uec
1439 JOIN {" . user_evidence::TABLE . "} ue
1440 ON ue.id = uec.userevidenceid
1441 JOIN {" . competency::TABLE . "} c
1442 ON c.id = uec.competencyid
1443 WHERE ue.userid = :targetuserid
1444 AND uec.usermodified = :userid
1445 ORDER BY ue.id, c.id";
1446 $params = [
1447 'targetuserid' => $context->instanceid,
1448 'userid' => $userid,
1450 $recordset = $DB->get_recordset_sql($sql, $params);
1451 foreach ($recordset as $record) {
1452 $ueid = $record->ue_id;
1453 if (!isset($evidence[$ueid])) {
1454 $initevidence($record);
1457 $competency = new competency(null, competency::extract_record($record, 'c_'));
1458 $uec = new user_evidence_competency(null, user_evidence_competency::extract_record($record, 'uec_'));
1459 $evidence[$ueid]['competencies'][] = array_merge(static::transform_competency_brief($competency), [
1460 'timemodified' => $uec->get('timemodified') ? transform::datetime($uec->get('timemodified')) : '-',
1461 'timecreated' => $uec->get('timecreated') ? transform::datetime($uec->get('timecreated')) : '-',
1462 'created_or_modified_by_you' => transform::yesno($uec->get('usermodified'))
1465 $recordset->close();
1467 // Look for user evidence we modified or reviewed and didn't catch.
1468 $insql = ' > 0';
1469 $inparams = [];
1470 if (!empty($evidence)) {
1471 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($evidence), SQL_PARAMS_NAMED, 'param', false);
1473 $sql = "
1474 SELECT $uefields
1475 FROM {" . user_evidence::TABLE . "} ue
1476 WHERE ue.userid = :targetuserid
1477 AND ue.usermodified = :userid
1478 AND ue.id $insql
1479 ORDER BY ue.id";
1480 $params = array_merge($inparams, [
1481 'targetuserid' => $context->instanceid,
1482 'userid' => $userid,
1485 $recordset = $DB->get_recordset_sql($sql, $params);
1486 foreach ($recordset as $record) {
1487 $initevidence($record);
1489 $recordset->close();
1491 // Export files, then content.
1492 foreach ($evidence as $ueid => $data) {
1493 $uepath = array_merge($path, ["{$data['name']} ({$ueid})"]);
1494 writer::with_context($context)->export_area_files($uepath, 'core_competency', 'userevidence', $ueid);
1495 writer::with_context($context)->export_data($uepath, (object) $data);
1500 * Export the evidence of prior learning of a user.
1502 * @param context_user $context The context of the user we're exporting for.
1503 * @return void
1505 protected static function export_user_data_user_evidence(context_user $context) {
1506 global $DB;
1508 $userid = $context->instanceid;
1509 $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:userevidence', 'core_competency')];
1510 $uefields = user_evidence::get_sql_fields('ue', 'ue_');
1511 $cfields = competency::get_sql_fields('c', 'c_');
1513 $sql = "
1514 SELECT $uefields, $cfields
1515 FROM {" . user_evidence::TABLE . "} ue
1516 LEFT JOIN {" . user_evidence_competency::TABLE . "} uec
1517 ON uec.userevidenceid = ue.id
1518 LEFT JOIN {" . competency::TABLE . "} c
1519 ON c.id = uec.competencyid
1520 WHERE ue.userid = :userid
1521 ORDER BY ue.id";
1522 $params = ['userid' => $userid];
1524 $recordset = $DB->get_recordset_sql($sql, $params);
1525 static::recordset_loop_and_export($recordset, 'ue_id', null, function($carry, $record) use ($userid, $context){
1526 if ($carry === null) {
1527 $ue = new user_evidence(null, user_evidence::extract_record($record, 'ue_'));
1528 $carry = static::transform_user_evidence($userid, $ue);
1531 if (!empty($record->c_id)) {
1532 $competency = new competency(null, competency::extract_record($record, 'c_'));
1533 $carry['competencies'][] = static::transform_competency_brief($competency);
1536 return $carry;
1537 }, function($ueid, $data) use ($context, $path) {
1538 $finalpath = array_merge($path, [$data['name'] . ' (' . $ueid . ')']);
1539 writer::with_context($context)->export_area_files($finalpath, 'core_competency', 'userevidence', $ueid);
1540 writer::with_context($context)->export_data($finalpath, (object) $data);
1545 * Export the user data related to frameworks in context.
1547 * @param int $userid The user ID.
1548 * @param context $context The context.
1549 * @return void
1551 protected static function export_user_data_frameworks_in_context($userid, context $context) {
1552 global $DB;
1554 $ffields = competency_framework::get_sql_fields('f', 'f_');
1555 $cfields = competency::get_sql_fields('c', 'c_');
1556 $c2fields = competency::get_sql_fields('c2', 'c2_');
1557 $rcfields = related_competency::get_sql_fields('rc', 'rc_');
1559 $frameworks = [];
1560 $initframework = function($record) use (&$frameworks, $userid) {
1561 $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_'));
1562 $frameworks[$framework->get('id')] = array_merge(static::transform_framework_brief($framework), [
1563 'timemodified' => transform::datetime($framework->get('timemodified')),
1564 'created_or_modified_by_you' => transform::yesno($framework->get('usermodified') == $userid),
1565 'competencies' => []
1568 $initcompetency = function($record, $prefix) use (&$frameworks, $userid) {
1569 $competency = new competency(null, competency::extract_record($record, $prefix));
1570 $frameworks[$competency->get('competencyframeworkid')]['competencies'][$competency->get('id')] = array_merge(
1571 static::transform_competency_brief($competency),
1573 'timemodified' => transform::datetime($competency->get('timemodified')),
1574 'created_or_modified_by_you' => transform::yesno($competency->get('usermodified') == $userid),
1575 'related_competencies' => []
1580 // Start by looking for related competencies.
1581 $sql = "
1582 SELECT $ffields, $cfields, $c2fields, $rcfields
1583 FROM {" . related_competency::TABLE . "} rc
1584 JOIN {" . competency::TABLE . "} c
1585 ON c.id = rc.competencyid
1586 JOIN {" . competency::TABLE . "} c2
1587 ON c2.id = rc.relatedcompetencyid
1588 JOIN {" . competency_framework::TABLE . "} f
1589 ON f.id = c.competencyframeworkid
1590 WHERE rc.usermodified = :userid
1591 AND f.contextid = :contextid
1592 ORDER BY rc.id, c.id";
1593 $params = ['userid' => $userid, 'contextid' => $context->id];
1595 $recordset = $DB->get_recordset_sql($sql, $params);
1596 foreach ($recordset as $record) {
1597 $frameworkid = $record->f_id;
1598 $comp1id = $record->c_id;
1599 $comp2id = $record->c2_id;
1601 if (!isset($frameworks[$frameworkid])) {
1602 $initframework($record);
1605 foreach (['c_', 'c2_'] as $key) {
1606 $competencyid = $record->{$key . 'id'};
1607 if (!isset($frameworks[$frameworkid]['competencies'][$competencyid])) {
1608 $initcompetency($record, $key);
1612 $relcomp = new related_competency(null, related_competency::extract_record($record, 'rc_'));
1613 foreach (['c_' => 'c2_', 'c2_' => 'c_'] as $key => $relatedkey) {
1614 $competencyid = $record->{$key . 'id'};
1615 $competency = new competency(null, competency::extract_record($record, $relatedkey));
1616 $frameworks[$frameworkid]['competencies'][$competencyid]['related_competencies'][] = [
1617 'name' => $competency->get('shortname'),
1618 'idnumber' => $competency->get('idnumber'),
1619 'timemodified' => transform::datetime($relcomp->get('timemodified')),
1620 'created_or_modified_by_you' => transform::yesno($relcomp->get('usermodified') == $userid),
1624 $recordset->close();
1626 // Now look for competencies, but skip the ones we've already seen.
1627 $competencyids = array_reduce($frameworks, function($carry, $framework) {
1628 return array_merge($carry, array_keys($framework['competencies']));
1629 }, []);
1630 $insql = ' IS NOT NULL';
1631 $inparams = [];
1632 if (!empty($competencyids)) {
1633 list($insql, $inparams) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED, 'param', false);
1635 $sql = "
1636 SELECT $ffields, $cfields
1637 FROM {" . competency::TABLE . "} c
1638 JOIN {" . competency_framework::TABLE . "} f
1639 ON f.id = c.competencyframeworkid
1640 WHERE c.usermodified = :userid
1641 AND f.contextid = :contextid
1642 AND c.id $insql
1643 ORDER BY c.id";
1644 $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
1645 $recordset = $DB->get_recordset_sql($sql, $params);
1646 foreach ($recordset as $record) {
1647 $frameworkid = $record->f_id;
1648 if (!isset($frameworks[$frameworkid])) {
1649 $initframework($record);
1651 $initcompetency($record, 'c_');
1653 $recordset->close();
1655 // Now look for frameworks, but skip the ones we've already seen.
1656 $frameworkids = array_keys($frameworks);
1657 $insql = ' IS NOT NULL';
1658 $inparams = [];
1659 if (!empty($frameworkids)) {
1660 list($insql, $inparams) = $DB->get_in_or_equal($frameworkids, SQL_PARAMS_NAMED, 'param', false);
1662 $sql = "
1663 SELECT $ffields
1664 FROM {" . competency_framework::TABLE . "} f
1665 WHERE f.usermodified = :userid
1666 AND f.contextid = :contextid
1667 AND f.id $insql
1668 ORDER BY f.id";
1669 $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
1670 $recordset = $DB->get_recordset_sql($sql, $params);
1671 foreach ($recordset as $record) {
1672 context_helper::preload_from_record($record);
1673 $initframework($record);
1675 $recordset->close();
1677 // Export all the things!
1678 writer::with_context($context)->export_related_data(
1679 [get_string('competencies', 'core_competency')],
1680 'frameworks',
1681 (object) [
1682 // Drop the temporary IDs.
1683 'frameworks' => array_reduce($frameworks, function($carry, $item) {
1684 $item['competencies'] = array_values($item['competencies']);
1685 $carry[] = $item;
1686 return $carry;
1687 }, [])
1693 * Export the user data related to templates in contexts.
1695 * @param int $userid The user ID.
1696 * @param context $context The context.
1697 * @return void
1699 protected static function export_user_data_templates_in_context($userid, context $context) {
1700 global $DB;
1702 $tfields = template::get_sql_fields('t', 't_');
1703 $cfields = competency::get_sql_fields('c', 'c_');
1704 $tcfields = template_competency::get_sql_fields('tc', 'tc_');
1705 $tchfields = template_cohort::get_sql_fields('tch', 'tch_');
1707 $templates = [];
1708 $inittemplate = function($record) use (&$templates, $userid) {
1709 $template = new template(null, template::extract_record($record, 't_'));
1710 $templates[$template->get('id')] = array_merge(static::transform_template_brief($template), [
1711 'timemodified' => transform::datetime($template->get('timemodified')),
1712 'created_or_modified_by_you' => transform::yesno($template->get('usermodified') == $userid),
1713 'competencies' => [],
1714 'cohorts' => []
1718 // Find the template competencies.
1719 $sql = "
1720 SELECT $tfields, $cfields, $tcfields
1721 FROM {" . template_competency::TABLE . "} tc
1722 JOIN {" . template::TABLE . "} t
1723 ON t.id = tc.templateid
1724 JOIN {" . competency::TABLE . "} c
1725 ON c.id = tc.competencyid
1726 WHERE t.contextid = :contextid
1727 AND tc.usermodified = :userid
1728 ORDER BY t.id, tc.id";
1729 $params = ['userid' => $userid, 'contextid' => $context->id];
1730 $recordset = $DB->get_recordset_sql($sql, $params);
1731 foreach ($recordset as $record) {
1732 $templateid = $record->t_id;
1733 if (!isset($templates[$templateid])) {
1734 $inittemplate($record);
1736 $tplcomp = new template_competency(null, template_competency::extract_record($record, 'tc_'));
1737 $competency = new competency(null, competency::extract_record($record, 'c_'));
1738 $templates[$templateid]['competencies'][] = array_merge(
1739 static::transform_competency_brief($competency),
1741 'timemodified' => transform::datetime($tplcomp->get('timemodified')),
1742 'created_or_modified_by_you' => transform::yesno($tplcomp->get('usermodified') == $userid)
1746 $recordset->close();
1748 // Find the template cohorts.
1749 $sql = "
1750 SELECT $tfields, $tchfields, c.name AS cohortname
1751 FROM {" . template_cohort::TABLE . "} tch
1752 JOIN {" . template::TABLE . "} t
1753 ON t.id = tch.templateid
1754 JOIN {cohort} c
1755 ON c.id = tch.cohortid
1756 WHERE t.contextid = :contextid
1757 AND tch.usermodified = :userid
1758 ORDER BY t.id, tch.id";
1759 $params = ['userid' => $userid, 'contextid' => $context->id];
1760 $recordset = $DB->get_recordset_sql($sql, $params);
1761 foreach ($recordset as $record) {
1762 $templateid = $record->t_id;
1763 if (!isset($templates[$templateid])) {
1764 $inittemplate($record);
1766 $tplcohort = new template_cohort(null, template_cohort::extract_record($record, 'tch_'));
1767 $templates[$templateid]['cohorts'][] = [
1768 'name' => $record->cohortname,
1769 'timemodified' => transform::datetime($tplcohort->get('timemodified')),
1770 'created_or_modified_by_you' => transform::yesno($tplcohort->get('usermodified') == $userid)
1773 $recordset->close();
1775 // Find the modified templates which we haven't been found yet.
1776 $templateids = array_keys($templates);
1777 $insql = "IS NOT NULL";
1778 $inparams = [];
1779 if (!empty($templateids)) {
1780 list($insql, $inparams) = $DB->get_in_or_equal($templateids, SQL_PARAMS_NAMED, 'param', false);
1782 $sql = "
1783 SELECT $tfields
1784 FROM {" . template::TABLE . "} t
1785 WHERE t.contextid = :contextid
1786 AND t.usermodified = :userid
1787 AND t.id $insql
1788 ORDER BY t.id";
1789 $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]);
1790 $recordset = $DB->get_recordset_sql($sql, $params);
1791 foreach ($recordset as $record) {
1792 $inittemplate($record);
1794 $recordset->close();
1796 // Export all the things!
1797 writer::with_context($context)->export_related_data([get_string('competencies', 'core_competency')],
1798 'templates', (object) ['templates' => array_values($templates)]);
1802 * Transform a competency into a brief description.
1804 * @param competency $competency The competency.
1805 * @return array
1807 protected static function transform_competency_brief(competency $competency) {
1808 global $OUTPUT;
1809 $exporter = new \core_competency\external\competency_exporter($competency, ['context' => $competency->get_context()]);
1810 $data = $exporter->export($OUTPUT);
1811 return [
1812 'idnumber' => $data->idnumber,
1813 'name' => $data->shortname,
1814 'description' => $data->description
1819 * Transform a competency rating.
1821 * @param competency $competency The competency.
1822 * @param int $grade The grade.
1823 * @param performance_helper $helper The performance helper.
1824 * @return string
1826 protected static function transform_competency_grade(competency $competency, $grade, performance_helper $helper) {
1827 if ($grade === null) {
1828 return '-';
1830 $scale = $helper->get_scale_from_competency($competency);
1831 return $scale->scale_items[$grade - 1];
1835 * Transform an evidence.
1837 * @param int $userid The user ID we are exporting for.
1838 * @param evidence $evidence The evidence.
1839 * @param competency $competency The associated competency.
1840 * @param performance_helper $helper The performance helper.
1841 * @return array
1843 protected static function transform_evidence($userid, evidence $evidence, competency $competency, performance_helper $helper) {
1844 $action = $evidence->get('action');
1845 $actiontxt = '?';
1846 if ($action == evidence::ACTION_LOG) {
1847 $actiontxt = get_string('privacy:evidence:action:log', 'core_competency');
1848 } else if ($action == evidence::ACTION_COMPLETE) {
1849 $actiontxt = get_string('privacy:evidence:action:complete', 'core_competency');
1850 } else if ($action == evidence::ACTION_OVERRIDE) {
1851 $actiontxt = get_string('privacy:evidence:action:override', 'core_competency');
1854 $actionuserid = $evidence->get('actionuserid');
1856 return [
1857 'action' => $actiontxt,
1858 'actionuserid' => $actionuserid ? transform::user($actionuserid) : '-',
1859 'acting_user_is_you' => transform::yesno($userid == $actionuserid),
1860 'description' => (string) $evidence->get_description(),
1861 'url' => $evidence->get('url'),
1862 'grade' => static::transform_competency_grade($competency, $evidence->get('grade'), $helper),
1863 'note' => $evidence->get('note'),
1864 'timecreated' => transform::datetime($evidence->get('timecreated')),
1865 'timemodified' => transform::datetime($evidence->get('timemodified')),
1866 'created_or_modified_by_you' => transform::yesno($userid == $evidence->get('usermodified'))
1871 * Transform a framework into a brief description.
1873 * @param competency_framework $framework The framework.
1874 * @return array
1876 protected static function transform_framework_brief(competency_framework $framework) {
1877 global $OUTPUT;
1878 $exporter = new \core_competency\external\competency_framework_exporter($framework);
1879 $data = $exporter->export($OUTPUT);
1880 return [
1881 'name' => $data->shortname,
1882 'idnumber' => $data->idnumber,
1883 'description' => $data->description
1888 * Transform a template into a brief description.
1890 * @param template $template The Template.
1891 * @return array
1893 protected static function transform_template_brief(template $template) {
1894 global $OUTPUT;
1895 $exporter = new \core_competency\external\template_exporter($template);
1896 $data = $exporter->export($OUTPUT);
1897 return [
1898 'name' => $data->shortname,
1899 'description' => $data->description
1904 * Transform proficiency.
1906 * @param null|bool $proficiency The proficiency.
1907 * @return string
1909 protected static function transform_proficiency($proficiency) {
1910 return $proficiency !== null ? transform::yesno($proficiency) : '-';
1914 * Transform user competency.
1916 * @param int $userid The user ID we are exporting for.
1917 * @param user_competency|user_competency_plan|user_competency_course $uc The user competency.
1918 * @param competency $competency The associated competency.
1919 * @param performance_helper $helper The performance helper.
1920 * @return array
1922 protected static function transform_user_competency($userid, $uc, competency $competency, performance_helper $helper) {
1923 $data = [
1924 'proficient' => static::transform_proficiency($uc->get('proficiency')),
1925 'rating' => static::transform_competency_grade($competency, $uc->get('grade'), $helper),
1926 'timemodified' => $uc->get('timemodified') ? transform::datetime($uc->get('timemodified')) : '-',
1927 'timecreated' => $uc->get('timecreated') ? transform::datetime($uc->get('timecreated')) : '-',
1928 'created_or_modified_by_you' => transform::yesno($uc->get('usermodified') == $userid),
1931 if ($uc instanceof user_competency) {
1932 $reviewer = $uc->get('reviewerid');
1933 $data['status'] = (string) user_competency::get_status_name($uc->get('status'));
1934 $data['reviewerid'] = $reviewer ? transform::user($reviewer) : '-';
1935 $data['reviewer_is_you'] = transform::yesno($reviewer == $userid);
1938 return $data;
1942 * Transform a user evidence.
1944 * @param int $userid The user we are exporting for.
1945 * @param user_evidence $ue The evidence of prior learning.
1946 * @return array
1948 protected static function transform_user_evidence($userid, user_evidence $ue) {
1949 $options = ['context' => $ue->get_context()];
1950 return [
1951 'name' => format_string($ue->get('name'), true, $options),
1952 'description' => format_text($ue->get('description'), $ue->get('descriptionformat'), $options),
1953 'url' => $ue->get('url'),
1954 'timecreated' => $ue->get('timecreated') ? transform::datetime($ue->get('timecreated')) : '-',
1955 'timemodified' => $ue->get('timemodified') ? transform::datetime($ue->get('timemodified')) : '-',
1956 'created_or_modified_by_you' => transform::yesno($ue->get('usermodified') == $userid),
1957 'competencies' => []
1962 * Loop and export from a recordset.
1964 * @param moodle_recordset $recordset The recordset.
1965 * @param string $splitkey The record key to determine when to export.
1966 * @param mixed $initial The initial data to reduce from.
1967 * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
1968 * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
1969 * @return void
1971 protected static function recordset_loop_and_export(moodle_recordset $recordset, $splitkey, $initial,
1972 callable $reducer, callable $export) {
1974 $data = $initial;
1975 $lastid = null;
1977 foreach ($recordset as $record) {
1978 if ($lastid && $record->{$splitkey} != $lastid) {
1979 $export($lastid, $data);
1980 $data = $initial;
1982 $data = $reducer($data, $record);
1983 $lastid = $record->{$splitkey};
1985 $recordset->close();
1987 if (!empty($lastid)) {
1988 $export($lastid, $data);