|
26 | 26 | declare(strict_types=1); |
27 | 27 |
|
28 | 28 | namespace local_adele; |
| 29 | + |
| 30 | +use core\event\course_completed; |
29 | 31 | use local_adele\helper\user_path_relation; |
30 | | -use context_system; |
31 | | -use local_adele\event\user_path_updated; |
| 32 | +use local_adele\learning_path_update; |
32 | 33 |
|
33 | 34 | /** |
34 | 35 | * External Service for local adele. |
|
40 | 41 | */ |
41 | 42 | class completion { |
42 | 43 | /** |
43 | | - * Observer for course completed |
| 44 | + * Observer for the core course_completed event. |
| 45 | + * |
| 46 | + * When a Moodle course is completed, every active learning path of the |
| 47 | + * affected student that references this course in one of its nodes has to be |
| 48 | + * re-evaluated so that the corresponding node-completion criterion can turn |
| 49 | + * the node into "completed". |
44 | 50 | * |
45 | | - * @param object $event |
| 51 | + * Important: on \core\event\course_completed the affected student is carried |
| 52 | + * in $event->relateduserid. $event->userid is the ACTOR that caused the |
| 53 | + * completion (a grading teacher, an administrator or - most commonly - the |
| 54 | + * cron/system process aggregating the completion criteria) and must NOT be |
| 55 | + * used to look up the learning path owner. |
| 56 | + * |
| 57 | + * @param course_completed $event The Moodle course_completed event. |
| 58 | + * @return void |
46 | 59 | */ |
47 | | - public static function completed($event) { |
48 | | - // The course_completed event carries the actor in userid and the affected student |
49 | | - // in relateduserid. Route by the student, otherwise a teacher- or cron-triggered |
50 | | - // completion recomputes the wrong user's paths (or none) and the node never |
51 | | - // completes (#495). |
| 60 | + public static function completed(course_completed $event): void { |
| 61 | + // The student whose course was completed is represented by relateduser, not userid. |
| 62 | + $userid = (int) $event->relateduserid; |
| 63 | + $courseid = (int) $event->courseid; |
| 64 | + |
| 65 | + // Nothing sensible to do without both a student and a course. |
| 66 | + if ($userid <= 0 || $courseid <= 0) { |
| 67 | + return; |
| 68 | + } |
| 69 | + |
52 | 70 | $userpathrelation = new user_path_relation(); |
53 | | - $learningpaths = $userpathrelation->get_learning_paths($event->relateduserid); |
54 | | - if (!$learningpaths) { |
| 71 | + $learningpaths = $userpathrelation->get_learning_paths($userid); |
| 72 | + |
| 73 | + if (empty($learningpaths)) { |
55 | 74 | return; |
56 | 75 | } |
| 76 | + |
57 | 77 | foreach ($learningpaths as $learningpath) { |
58 | | - $learningpath->json = json_decode($learningpath->json, true); |
59 | | - foreach ($learningpath->json['tree']['nodes'] as $node) { |
60 | | - if (is_array($node['data']['course_node_id']) && in_array($event->courseid, $node['data']['course_node_id'])) { |
61 | | - // Recompute the path once per event, even if the course maps to several nodes. |
62 | | - $eventsingle = user_path_updated::create([ |
63 | | - 'objectid' => $learningpath->id, |
64 | | - 'context' => context_system::instance(), |
65 | | - 'other' => [ |
66 | | - 'userpath' => $learningpath, |
67 | | - ], |
68 | | - ]); |
69 | | - $eventsingle->trigger(); |
70 | | - break; |
| 78 | + $pathjson = json_decode($learningpath->json, true); |
| 79 | + |
| 80 | + // A single malformed or incomplete snapshot must not abort the |
| 81 | + // processing of the remaining (valid) learning paths. |
| 82 | + if ( |
| 83 | + !is_array($pathjson) || |
| 84 | + empty($pathjson['tree']['nodes']) || |
| 85 | + !is_array($pathjson['tree']['nodes']) |
| 86 | + ) { |
| 87 | + continue; |
| 88 | + } |
| 89 | + |
| 90 | + foreach ($pathjson['tree']['nodes'] as $node) { |
| 91 | + $courseids = $node['data']['course_node_id'] ?? []; |
| 92 | + |
| 93 | + if (!is_array($courseids)) { |
| 94 | + $courseids = [$courseids]; |
71 | 95 | } |
| 96 | + |
| 97 | + // The stored course_node_id values may be strings or integers |
| 98 | + // depending on the JSON origin - normalise both sides for a |
| 99 | + // type-safe match. |
| 100 | + $courseids = array_map('intval', $courseids); |
| 101 | + |
| 102 | + if (!in_array($courseid, $courseids, true)) { |
| 103 | + continue; |
| 104 | + } |
| 105 | + |
| 106 | + // Hand the decoded snapshot to the central update service, which |
| 107 | + // re-evaluates every node of the path against the current state. |
| 108 | + $learningpath->json = $pathjson; |
| 109 | + learning_path_update::trigger_user_path_update($learningpath); |
| 110 | + |
| 111 | + // The recompute already covers all nodes of this path, so a |
| 112 | + // single trigger per learning path is sufficient even if the |
| 113 | + // course is referenced by more than one node. |
| 114 | + break; |
72 | 115 | } |
73 | 116 | } |
74 | 117 | } |
|
0 commit comments