2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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();
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
;
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
{
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');
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) {
227 $contextlist = new \core_privacy\local\request\
contextlist();
229 // Find the contexts of the frameworks, and related data, modified by the user.
231 SELECT DISTINCT ctx.id
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.
247 SELECT DISTINCT ctx.id
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.
265 SELECT DISTINCT ctx.id
266 FROM {" . course_competency
::TABLE
. "} cc
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);
275 SELECT DISTINCT ctx.id
276 FROM {" . course_competency_settings
::TABLE
. "} ccs
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);
285 SELECT DISTINCT ctx.id
286 FROM {" . user_competency_course
::TABLE
. "} ucc
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.
296 SELECT DISTINCT ctx.id
297 FROM {" . course_module_competency
::TABLE
. "} cmc
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.
307 SELECT DISTINCT ctx.id
308 FROM {" . plan
::TABLE
. "} p
310 ON ctx.instanceid = p.userid
311 AND ctx.contextlevel = :userlevel
312 LEFT JOIN {" . plan_competency
::TABLE
. "} pc
314 AND pc.usermodified = :userid3
315 LEFT JOIN {" . user_competency_plan
::TABLE
. "} upc
317 AND upc.usermodified = :userid4
318 WHERE p.usermodified = :userid1
319 OR p.reviewerid = :userid2
321 OR upc.id IS NOT NULL";
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.
333 SELECT DISTINCT ctx.id
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
352 OR uec.id IS NOT NULL";
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.
368 SELECT DISTINCT ctx.id
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
388 OR ucc.id IS NOT NULL";
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.
406 ON c.contextid = ctx.id
407 WHERE c.component = :component
408 AND c.commentarea IN (:planarea, :usercomparea)
409 AND c.userid = :userid";
411 'component' => 'competency', // Must not be core_competency.
412 'planarea' => 'plan',
413 'usercomparea' => 'user_competency',
416 $contextlist->add_from_sql($sql, $params);
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();
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
])) {
436 $carry[$contextlevel][] = $context;
439 CONTEXT_COURSE
=> [],
440 CONTEXT_COURSECAT
=> [],
441 CONTEXT_MODULE
=> [],
442 CONTEXT_SYSTEM
=> [],
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) {
472 switch ($context->contextlevel
) {
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);
481 $courseid = $context->instanceid
;
482 $DB->delete_records(user_competency_course
::TABLE
, ['courseid' => $courseid]);
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();
500 foreach ($contextlist as $context) {
501 switch ($context->contextlevel
) {
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.
508 static::delete_user_evidence_of_prior_learning($userid);
509 static::delete_user_plans($userid);
510 static::delete_user_competencies($userid);
514 static::delete_user_competencies_in_course($userid, $context->instanceid
);
521 * Delete evidence of prior learning.
523 * @param int $userid The user ID.
526 protected static function delete_user_evidence_of_prior_learning($userid) {
529 $usercontext = context_user
::instance($userid);
530 $ueids = $DB->get_fieldset_select(user_evidence
::TABLE
, 'id', 'userid = :userid', ['userid' => $userid]);
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');
550 * @param int $userid The user ID.
553 protected static function delete_user_plans($userid) {
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)) {
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.
583 protected static function delete_user_competencies($userid) {
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]);
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.
611 protected static function delete_user_competencies_in_course($userid, $courseid) {
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.
623 protected static function export_user_data_in_user_contexts($userid, array $contexts) {
626 $mycontext = context_user
::instance($userid);
627 $contextids = array_map(function($context) {
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.
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.
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)) {
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.
687 protected static function export_user_data_in_course_contexts($userid, array $contexts) {
690 $contexts = array_filter($contexts, function($context) {
691 return $context->contextlevel
== CONTEXT_COURSE
;
693 if (empty($contexts)) {
697 $helper = new performance_helper();
698 $path = [get_string('competencies', 'core_competency')];
699 $courseids = array_map(function($context) {
700 return $context->instanceid
;
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
);
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
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]);
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), [
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')),
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.
762 protected static function export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path,
763 performance_helper
$helper) {
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
);
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
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]);
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), [
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')),
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) [
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.
823 protected static function export_user_data_in_course_contexts_associations($userid, $courseids, $path) {
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
);
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
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);
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)
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.
871 protected static function export_user_data_in_course_contexts_settings($userid, $courseids, $path) {
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
);
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);
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_'));
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.
906 protected static function export_user_data_in_module_contexts($userid, array $contexts) {
909 $contexts = array_filter($contexts, function($context) {
910 return $context->contextlevel
== CONTEXT_MODULE
;
912 if (empty($contexts)) {
916 $path = [get_string('competencies', 'core_competency')];
917 $cmids = array_map(function($context) {
918 return $context->instanceid
;
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
);
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
934 ON ctx.id = f.contextid
935 WHERE cmc.usermodified = :userid
938 $params = array_merge($inparams, ['userid' => $userid]);
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)
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.
965 protected static function export_user_data_competencies(context_user
$context) {
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 . ')']);
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),
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);
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.
1025 protected static function export_user_data_learning_plans(context_user
$context) {
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.
1039 SELECT $pfields, $pcfields, $cfields, $ucfields, $ucpfields
1040 FROM {" . plan
::TABLE
. "} p
1041 LEFT JOIN {" . plan_competency
::TABLE
. "} pc
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";
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];
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
)) {
1092 $competency = new competency(null, competency
::extract_record($record, 'c_'));
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]);
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.
1126 protected static function export_user_data_learning_plans_related_to_me($userid, context_user
$context) {
1130 get_string('competencies', 'core_competency'),
1131 get_string('privacy:path:relatedtome', 'core_competency'),
1132 get_string('privacy:path:plans', 'core_competency'),
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.
1162 SELECT $pfields, $pcfields, $cfields
1163 FROM {" . plan_competency
::TABLE
. "} pc
1164 JOIN {" . plan
::TABLE
. "} p
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";
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])) {
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.
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";
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])) {
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.
1234 if (!empty($plans)) {
1235 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($plans), SQL_PARAMS_NAMED
, 'param', false);
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
1245 WHERE p.userid = :targetuserid
1246 AND (p.usermodified = :userid1
1247 OR p.reviewerid = :userid2
1248 OR c.userid = :userid3)
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])) {
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.
1287 protected static function export_user_data_competencies_related_to_me($userid, context_user
$context) {
1291 get_string('competencies', 'core_competency'),
1292 get_string('privacy:path:relatedtome', 'core_competency'),
1293 get_string('competencies', 'core_competency'),
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), [
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.
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";
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.
1356 if (!empty($competencies)) {
1357 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($competencies), SQL_PARAMS_NAMED
, 'param', false);
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']);
1402 writer
::with_context($context)->export_data($comppath, (object) $competency);
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.
1416 protected static function export_user_data_user_evidence_related_to_me($userid, context_user
$context) {
1420 get_string('competencies', 'core_competency'),
1421 get_string('privacy:path:relatedtome', 'core_competency'),
1422 get_string('privacy:path:userevidence', 'core_competency'),
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.
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";
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.
1470 if (!empty($evidence)) {
1471 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($evidence), SQL_PARAMS_NAMED
, 'param', false);
1475 FROM {" . user_evidence
::TABLE
. "} ue
1476 WHERE ue.userid = :targetuserid
1477 AND ue.usermodified = :userid
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.
1505 protected static function export_user_data_user_evidence(context_user
$context) {
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_');
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
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);
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.
1551 protected static function export_user_data_frameworks_in_context($userid, context
$context) {
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_');
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.
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']));
1630 $insql = ' IS NOT NULL';
1632 if (!empty($competencyids)) {
1633 list($insql, $inparams) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED
, 'param', false);
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
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';
1659 if (!empty($frameworkids)) {
1660 list($insql, $inparams) = $DB->get_in_or_equal($frameworkids, SQL_PARAMS_NAMED
, 'param', false);
1664 FROM {" . competency_framework
::TABLE
. "} f
1665 WHERE f.usermodified = :userid
1666 AND f.contextid = :contextid
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')],
1682 // Drop the temporary IDs.
1683 'frameworks' => array_reduce($frameworks, function($carry, $item) {
1684 $item['competencies'] = array_values($item['competencies']);
1693 * Export the user data related to templates in contexts.
1695 * @param int $userid The user ID.
1696 * @param context $context The context.
1699 protected static function export_user_data_templates_in_context($userid, context
$context) {
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_');
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' => [],
1718 // Find the template competencies.
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.
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
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";
1779 if (!empty($templateids)) {
1780 list($insql, $inparams) = $DB->get_in_or_equal($templateids, SQL_PARAMS_NAMED
, 'param', false);
1784 FROM {" . template
::TABLE
. "} t
1785 WHERE t.contextid = :contextid
1786 AND t.usermodified = :userid
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.
1807 protected static function transform_competency_brief(competency
$competency) {
1809 $exporter = new \core_competency\external\
competency_exporter($competency, ['context' => $competency->get_context()]);
1810 $data = $exporter->export($OUTPUT);
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.
1826 protected static function transform_competency_grade(competency
$competency, $grade, performance_helper
$helper) {
1827 if ($grade === null) {
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.
1843 protected static function transform_evidence($userid, evidence
$evidence, competency
$competency, performance_helper
$helper) {
1844 $action = $evidence->get('action');
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');
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.
1876 protected static function transform_framework_brief(competency_framework
$framework) {
1878 $exporter = new \core_competency\external\
competency_framework_exporter($framework);
1879 $data = $exporter->export($OUTPUT);
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.
1893 protected static function transform_template_brief(template
$template) {
1895 $exporter = new \core_competency\external\template_exporter
($template);
1896 $data = $exporter->export($OUTPUT);
1898 'name' => $data->shortname
,
1899 'description' => $data->description
1904 * Transform proficiency.
1906 * @param null|bool $proficiency The proficiency.
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.
1922 protected static function transform_user_competency($userid, $uc, competency
$competency, performance_helper
$helper) {
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);
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.
1948 protected static function transform_user_evidence($userid, user_evidence
$ue) {
1949 $options = ['context' => $ue->get_context()];
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.
1971 protected static function recordset_loop_and_export(moodle_recordset
$recordset, $splitkey, $initial,
1972 callable
$reducer, callable
$export) {
1977 foreach ($recordset as $record) {
1978 if ($lastid && $record->{$splitkey} != $lastid) {
1979 $export($lastid, $data);
1982 $data = $reducer($data, $record);
1983 $lastid = $record->{$splitkey};
1985 $recordset->close();
1987 if (!empty($lastid)) {
1988 $export($lastid, $data);