Commit | Line | Data |
---|---|---|
2be4d090 MD |
1 | <?php |
2 | ||
3 | // This file is part of Moodle - http://moodle.org/ | |
4 | // | |
5 | // Moodle is free software: you can redistribute it and/or modify | |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU General Public License | |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | ||
19 | /** | |
20 | * Cron job for reviewing and aggregating course completion criteria | |
21 | * | |
22 | * @package moodlecore | |
23 | * @copyright 2009 Catalyst IT Ltd | |
24 | * @author Aaron Barnes <aaronb@catalyst.net.nz> | |
25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
26 | */ | |
27 | require_once $CFG->libdir.'/completionlib.php'; | |
28 | ||
29 | ||
30 | /** | |
31 | * Update user's course completion statuses | |
32 | * | |
33 | * First update all criteria completions, then | |
34 | * aggregate all criteria completions and update | |
35 | * overall course completions | |
36 | * | |
37 | * @return void | |
38 | */ | |
39 | function completion_cron() { | |
40 | ||
41 | completion_cron_mark_started(); | |
42 | ||
43 | completion_cron_criteria(); | |
44 | ||
45 | completion_cron_completions(); | |
46 | } | |
47 | ||
48 | /** | |
49 | * Mark users as started if the config option is set | |
50 | * | |
51 | * @return void | |
52 | */ | |
53 | function completion_cron_mark_started() { | |
54 | global $CFG, $DB; | |
55 | ||
56 | if (debugging()) { | |
57 | mtrace('Marking users as started'); | |
58 | } | |
59 | ||
60 | $roles = ''; | |
61 | if (!empty($CFG->progresstrackedroles)) { | |
62 | $roles = 'AND ra.roleid IN ('.$CFG->progresstrackedroles.')'; | |
63 | } | |
64 | ||
65 | $sql = " | |
66 | SELECT DISTINCT | |
67 | c.id AS course, | |
68 | ra.userid AS userid, | |
69 | crc.id AS completionid, | |
70 | MIN(ra.timestart) AS timestarted | |
71 | FROM | |
72 | {course} c | |
73 | INNER JOIN | |
74 | {context} con | |
75 | ON con.instanceid = c.id | |
76 | INNER JOIN | |
77 | {role_assignments} ra | |
78 | ON ra.contextid = con.id | |
79 | LEFT JOIN | |
80 | {course_completions} crc | |
81 | ON crc.course = c.id | |
82 | AND crc.userid = ra.userid | |
83 | WHERE | |
84 | con.contextlevel = ".CONTEXT_COURSE." | |
85 | AND c.enablecompletion = 1 | |
86 | AND c.completionstartonenrol = 1 | |
87 | AND crc.timeenrolled IS NULL | |
88 | AND (ra.timeend IS NULL OR ra.timeend > ".time().") | |
89 | {$roles} | |
90 | GROUP BY | |
91 | c.id, | |
92 | ra.userid, | |
93 | crc.id | |
94 | ORDER BY | |
95 | course, | |
96 | userid | |
97 | "; | |
98 | ||
99 | // Check if result is empty | |
100 | if (!$rs = $DB->get_recordset_sql($sql)) { | |
101 | return; | |
102 | } | |
103 | ||
104 | // Grab records for current user/course | |
105 | foreach ($rs as $record) { | |
106 | $completion = new completion_completion(); | |
107 | $completion->userid = $record->userid; | |
108 | $completion->course = $record->course; | |
109 | $completion->timeenrolled = $record->timestarted; | |
110 | ||
111 | if ($record->completionid) { | |
112 | $completion->id = $record->completionid; | |
113 | } | |
114 | ||
115 | $completion->mark_enrolled(); | |
116 | ||
117 | if (debugging()) { | |
118 | mtrace('Marked started user '.$record->userid.' in course '.$record->course); | |
119 | } | |
120 | } | |
121 | ||
122 | $rs->close(); | |
123 | } | |
124 | ||
125 | /** | |
126 | * Run installed criteria's data aggregation methods | |
127 | * | |
128 | * Loop through each installed criteria and run the | |
129 | * cron() method if it exists | |
130 | * | |
131 | * @return void | |
132 | */ | |
133 | function completion_cron_criteria() { | |
134 | ||
135 | // Process each criteria type | |
136 | global $CFG, $COMPLETION_CRITERIA_TYPES; | |
137 | ||
138 | foreach ($COMPLETION_CRITERIA_TYPES as $type) { | |
139 | ||
140 | $object = 'completion_criteria_'.$type; | |
141 | require_once $CFG->libdir.'/completion/'.$object.'.php'; | |
142 | ||
143 | $class = new $object(); | |
144 | ||
145 | // Run the criteria type's cron method, if it has one | |
146 | if (method_exists($class, 'cron')) { | |
147 | ||
148 | if (debugging()) { | |
149 | mtrace('Running '.$object.'->cron()'); | |
150 | } | |
151 | $class->cron(); | |
152 | } | |
153 | } | |
154 | } | |
155 | ||
156 | /** | |
157 | * Aggregate each user's criteria completions | |
158 | * | |
159 | * @return void | |
160 | */ | |
161 | function completion_cron_completions() { | |
162 | global $DB; | |
163 | ||
164 | if (debugging()) { | |
165 | mtrace('Aggregating completions'); | |
166 | } | |
167 | ||
168 | // Save time started | |
169 | $timestarted = time(); | |
170 | ||
171 | // Grab all criteria and their associated criteria completions | |
172 | $sql = ' | |
173 | SELECT DISTINCT | |
174 | c.id AS course, | |
175 | cr.id AS criteriaid, | |
176 | ra.userid AS userid, | |
177 | cr.criteriatype AS criteriatype, | |
178 | cc.timecompleted AS timecompleted | |
179 | FROM | |
180 | {course_completion_criteria} cr | |
181 | INNER JOIN | |
182 | {course} c | |
183 | ON cr.course = c.id | |
184 | INNER JOIN | |
185 | {context} con | |
186 | ON con.instanceid = c.id | |
187 | INNER JOIN | |
188 | {role_assignments} ra | |
189 | ON ra.contextid = con.id | |
190 | LEFT JOIN | |
191 | {course_completion_crit_compl} cc | |
192 | ON cc.criteriaid = cr.id | |
193 | AND cc.userid = ra.userid | |
194 | LEFT JOIN | |
195 | {course_completions} crc | |
196 | ON crc.course = c.id | |
197 | AND crc.userid = ra.userid | |
198 | WHERE | |
199 | con.contextlevel = '.CONTEXT_COURSE.' | |
200 | AND c.enablecompletion = 1 | |
201 | AND crc.timecompleted IS NULL | |
202 | AND crc.reaggregate > 0 | |
203 | ORDER BY | |
204 | course, | |
205 | userid | |
206 | '; | |
207 | ||
208 | // Check if result is empty | |
209 | if (!$rs = $DB->get_recordset_sql($sql)) { | |
210 | return; | |
211 | } | |
212 | ||
213 | $current_user = null; | |
214 | $current_course = null; | |
215 | $completions = array(); | |
216 | ||
217 | while (1) { | |
218 | ||
219 | // Grab records for current user/course | |
220 | foreach ($rs as $record) { | |
221 | // If we are still grabbing the same users completions | |
222 | if ($record->userid === $current_user && $record->course === $current_course) { | |
223 | $completions[$record->criteriaid] = $record; | |
224 | } else { | |
225 | break; | |
226 | } | |
227 | } | |
228 | ||
229 | // Aggregate | |
230 | if (!empty($completions)) { | |
231 | ||
232 | if (debugging()) { | |
233 | mtrace('Aggregating completions for user '.$current_user.' in course '.$current_course); | |
234 | } | |
235 | ||
236 | // Get course info object | |
237 | $info = new completion_info((object)array('id' => $current_course)); | |
238 | ||
239 | // Setup aggregation | |
240 | $overall = $info->get_aggregation_method(); | |
241 | $activity = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY); | |
242 | $prerequisite = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE); | |
243 | $role = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE); | |
244 | ||
245 | $overall_status = null; | |
246 | $activity_status = null; | |
247 | $prerequisite_status = null; | |
248 | $role_status = null; | |
249 | ||
250 | // Get latest timecompleted | |
251 | $timecompleted = null; | |
252 | ||
253 | // Check each of the criteria | |
254 | foreach ($completions as $params) { | |
255 | $timecompleted = max($timecompleted, $params->timecompleted); | |
256 | ||
257 | $completion = new completion_criteria_completion($params, false); | |
258 | ||
259 | // Handle aggregation special cases | |
260 | if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) { | |
261 | completion_cron_aggregate($activity, $completion->is_complete(), $activity_status); | |
262 | } else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) { | |
263 | completion_cron_aggregate($prerequisite, $completion->is_complete(), $prerequisite_status); | |
264 | } else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ROLE) { | |
265 | completion_cron_aggregate($role, $completion->is_complete(), $role_status); | |
266 | } else { | |
267 | completion_cron_aggregate($overall, $completion->is_complete(), $overall_status); | |
268 | } | |
269 | } | |
270 | ||
271 | // Include role criteria aggregation in overall aggregation | |
272 | if ($role_status !== null) { | |
273 | completion_cron_aggregate($overall, $role_status, $overall_status); | |
274 | } | |
275 | ||
276 | // Include activity criteria aggregation in overall aggregation | |
277 | if ($activity_status !== null) { | |
278 | completion_cron_aggregate($overall, $activity_status, $overall_status); | |
279 | } | |
280 | ||
281 | // Include prerequisite criteria aggregation in overall aggregation | |
282 | if ($prerequisite_status !== null) { | |
283 | completion_cron_aggregate($overall, $prerequisite_status, $overall_status); | |
284 | } | |
285 | ||
286 | // If aggregation status is true, mark course complete for user | |
287 | if ($overall_status) { | |
288 | if (debugging()) { | |
289 | mtrace('Marking complete'); | |
290 | } | |
291 | ||
292 | $ccompletion = new completion_completion(array('course' => $params->course, 'userid' => $params->userid)); | |
293 | $ccompletion->mark_complete($timecompleted); | |
294 | } | |
295 | } | |
296 | ||
297 | // If this is the end of the recordset, break the loop | |
298 | if (!$rs->valid()) { | |
299 | $rs->close(); | |
300 | break; | |
301 | } | |
302 | ||
303 | // New/next user, update user details, reset completions | |
304 | $current_user = $record->userid; | |
305 | $current_course = $record->course; | |
306 | $completions = array(); | |
307 | $completions[$record->criteriaid] = $record; | |
308 | } | |
309 | ||
310 | // Mark all users as aggregated | |
311 | $sql = " | |
312 | UPDATE | |
313 | {course_completions} | |
314 | SET | |
315 | reaggregate = 0 | |
316 | WHERE | |
317 | reaggregate < {$timestarted} | |
318 | "; | |
319 | ||
320 | $DB->execute($sql); | |
321 | } | |
322 | ||
323 | /** | |
324 | * Aggregate criteria status's as per configured aggregation method | |
325 | * | |
326 | * @param int $method COMPLETION_AGGREGATION_* constant | |
327 | * @param bool $data Criteria completion status | |
328 | * @param bool|null $state Aggregation state | |
329 | * @return void | |
330 | */ | |
331 | function completion_cron_aggregate($method, $data, &$state) { | |
332 | if ($method == COMPLETION_AGGREGATION_ALL) { | |
333 | if ($data && $state !== false) { | |
334 | $state = true; | |
335 | } else { | |
336 | $state = false; | |
337 | } | |
338 | } elseif ($method == COMPLETION_AGGREGATION_ANY) { | |
339 | if ($data) { | |
340 | $state = true; | |
341 | } else if (!$data && $state === null) { | |
342 | $state = false; | |
343 | } | |
344 | } | |
345 | } |