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 | ||
df997f84 PS |
56 | //TODO: MDL-22797 completion needs to be updated to use new enrolment framework |
57 | ||
2be4d090 MD |
58 | if (debugging()) { |
59 | mtrace('Marking users as started'); | |
60 | } | |
61 | ||
62 | $roles = ''; | |
63 | if (!empty($CFG->progresstrackedroles)) { | |
64 | $roles = 'AND ra.roleid IN ('.$CFG->progresstrackedroles.')'; | |
65 | } | |
66 | ||
67 | $sql = " | |
68 | SELECT DISTINCT | |
69 | c.id AS course, | |
70 | ra.userid AS userid, | |
71 | crc.id AS completionid, | |
72 | MIN(ra.timestart) AS timestarted | |
73 | FROM | |
74 | {course} c | |
75 | INNER JOIN | |
76 | {context} con | |
77 | ON con.instanceid = c.id | |
78 | INNER JOIN | |
79 | {role_assignments} ra | |
80 | ON ra.contextid = con.id | |
81 | LEFT JOIN | |
82 | {course_completions} crc | |
83 | ON crc.course = c.id | |
84 | AND crc.userid = ra.userid | |
85 | WHERE | |
86 | con.contextlevel = ".CONTEXT_COURSE." | |
87 | AND c.enablecompletion = 1 | |
88 | AND c.completionstartonenrol = 1 | |
89 | AND crc.timeenrolled IS NULL | |
90 | AND (ra.timeend IS NULL OR ra.timeend > ".time().") | |
91 | {$roles} | |
92 | GROUP BY | |
93 | c.id, | |
94 | ra.userid, | |
95 | crc.id | |
96 | ORDER BY | |
97 | course, | |
98 | userid | |
99 | "; | |
100 | ||
101 | // Check if result is empty | |
102 | if (!$rs = $DB->get_recordset_sql($sql)) { | |
103 | return; | |
104 | } | |
105 | ||
106 | // Grab records for current user/course | |
107 | foreach ($rs as $record) { | |
108 | $completion = new completion_completion(); | |
109 | $completion->userid = $record->userid; | |
110 | $completion->course = $record->course; | |
111 | $completion->timeenrolled = $record->timestarted; | |
112 | ||
113 | if ($record->completionid) { | |
114 | $completion->id = $record->completionid; | |
115 | } | |
116 | ||
117 | $completion->mark_enrolled(); | |
118 | ||
119 | if (debugging()) { | |
120 | mtrace('Marked started user '.$record->userid.' in course '.$record->course); | |
121 | } | |
122 | } | |
123 | ||
124 | $rs->close(); | |
125 | } | |
126 | ||
127 | /** | |
128 | * Run installed criteria's data aggregation methods | |
129 | * | |
130 | * Loop through each installed criteria and run the | |
131 | * cron() method if it exists | |
132 | * | |
133 | * @return void | |
134 | */ | |
135 | function completion_cron_criteria() { | |
136 | ||
137 | // Process each criteria type | |
138 | global $CFG, $COMPLETION_CRITERIA_TYPES; | |
139 | ||
140 | foreach ($COMPLETION_CRITERIA_TYPES as $type) { | |
141 | ||
142 | $object = 'completion_criteria_'.$type; | |
143 | require_once $CFG->libdir.'/completion/'.$object.'.php'; | |
144 | ||
145 | $class = new $object(); | |
146 | ||
147 | // Run the criteria type's cron method, if it has one | |
148 | if (method_exists($class, 'cron')) { | |
149 | ||
150 | if (debugging()) { | |
151 | mtrace('Running '.$object.'->cron()'); | |
152 | } | |
153 | $class->cron(); | |
154 | } | |
155 | } | |
156 | } | |
157 | ||
158 | /** | |
159 | * Aggregate each user's criteria completions | |
160 | * | |
161 | * @return void | |
162 | */ | |
163 | function completion_cron_completions() { | |
164 | global $DB; | |
165 | ||
166 | if (debugging()) { | |
167 | mtrace('Aggregating completions'); | |
168 | } | |
169 | ||
170 | // Save time started | |
171 | $timestarted = time(); | |
172 | ||
173 | // Grab all criteria and their associated criteria completions | |
174 | $sql = ' | |
175 | SELECT DISTINCT | |
176 | c.id AS course, | |
177 | cr.id AS criteriaid, | |
178 | ra.userid AS userid, | |
179 | cr.criteriatype AS criteriatype, | |
180 | cc.timecompleted AS timecompleted | |
181 | FROM | |
182 | {course_completion_criteria} cr | |
183 | INNER JOIN | |
184 | {course} c | |
185 | ON cr.course = c.id | |
186 | INNER JOIN | |
187 | {context} con | |
188 | ON con.instanceid = c.id | |
189 | INNER JOIN | |
190 | {role_assignments} ra | |
191 | ON ra.contextid = con.id | |
192 | LEFT JOIN | |
193 | {course_completion_crit_compl} cc | |
194 | ON cc.criteriaid = cr.id | |
195 | AND cc.userid = ra.userid | |
196 | LEFT JOIN | |
197 | {course_completions} crc | |
198 | ON crc.course = c.id | |
199 | AND crc.userid = ra.userid | |
200 | WHERE | |
201 | con.contextlevel = '.CONTEXT_COURSE.' | |
202 | AND c.enablecompletion = 1 | |
203 | AND crc.timecompleted IS NULL | |
204 | AND crc.reaggregate > 0 | |
205 | ORDER BY | |
206 | course, | |
207 | userid | |
208 | '; | |
209 | ||
210 | // Check if result is empty | |
211 | if (!$rs = $DB->get_recordset_sql($sql)) { | |
212 | return; | |
213 | } | |
214 | ||
215 | $current_user = null; | |
216 | $current_course = null; | |
217 | $completions = array(); | |
218 | ||
219 | while (1) { | |
220 | ||
221 | // Grab records for current user/course | |
222 | foreach ($rs as $record) { | |
223 | // If we are still grabbing the same users completions | |
224 | if ($record->userid === $current_user && $record->course === $current_course) { | |
225 | $completions[$record->criteriaid] = $record; | |
226 | } else { | |
227 | break; | |
228 | } | |
229 | } | |
230 | ||
231 | // Aggregate | |
232 | if (!empty($completions)) { | |
233 | ||
234 | if (debugging()) { | |
235 | mtrace('Aggregating completions for user '.$current_user.' in course '.$current_course); | |
236 | } | |
237 | ||
238 | // Get course info object | |
239 | $info = new completion_info((object)array('id' => $current_course)); | |
240 | ||
241 | // Setup aggregation | |
242 | $overall = $info->get_aggregation_method(); | |
243 | $activity = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY); | |
244 | $prerequisite = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE); | |
245 | $role = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE); | |
246 | ||
247 | $overall_status = null; | |
248 | $activity_status = null; | |
249 | $prerequisite_status = null; | |
250 | $role_status = null; | |
251 | ||
252 | // Get latest timecompleted | |
253 | $timecompleted = null; | |
254 | ||
255 | // Check each of the criteria | |
256 | foreach ($completions as $params) { | |
257 | $timecompleted = max($timecompleted, $params->timecompleted); | |
258 | ||
259 | $completion = new completion_criteria_completion($params, false); | |
260 | ||
261 | // Handle aggregation special cases | |
262 | if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) { | |
263 | completion_cron_aggregate($activity, $completion->is_complete(), $activity_status); | |
264 | } else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) { | |
265 | completion_cron_aggregate($prerequisite, $completion->is_complete(), $prerequisite_status); | |
266 | } else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ROLE) { | |
267 | completion_cron_aggregate($role, $completion->is_complete(), $role_status); | |
268 | } else { | |
269 | completion_cron_aggregate($overall, $completion->is_complete(), $overall_status); | |
270 | } | |
271 | } | |
272 | ||
273 | // Include role criteria aggregation in overall aggregation | |
274 | if ($role_status !== null) { | |
275 | completion_cron_aggregate($overall, $role_status, $overall_status); | |
276 | } | |
277 | ||
278 | // Include activity criteria aggregation in overall aggregation | |
279 | if ($activity_status !== null) { | |
280 | completion_cron_aggregate($overall, $activity_status, $overall_status); | |
281 | } | |
282 | ||
283 | // Include prerequisite criteria aggregation in overall aggregation | |
284 | if ($prerequisite_status !== null) { | |
285 | completion_cron_aggregate($overall, $prerequisite_status, $overall_status); | |
286 | } | |
287 | ||
288 | // If aggregation status is true, mark course complete for user | |
289 | if ($overall_status) { | |
290 | if (debugging()) { | |
291 | mtrace('Marking complete'); | |
292 | } | |
293 | ||
294 | $ccompletion = new completion_completion(array('course' => $params->course, 'userid' => $params->userid)); | |
295 | $ccompletion->mark_complete($timecompleted); | |
296 | } | |
297 | } | |
298 | ||
299 | // If this is the end of the recordset, break the loop | |
300 | if (!$rs->valid()) { | |
301 | $rs->close(); | |
302 | break; | |
303 | } | |
304 | ||
305 | // New/next user, update user details, reset completions | |
306 | $current_user = $record->userid; | |
307 | $current_course = $record->course; | |
308 | $completions = array(); | |
309 | $completions[$record->criteriaid] = $record; | |
310 | } | |
311 | ||
312 | // Mark all users as aggregated | |
313 | $sql = " | |
314 | UPDATE | |
315 | {course_completions} | |
316 | SET | |
317 | reaggregate = 0 | |
318 | WHERE | |
319 | reaggregate < {$timestarted} | |
320 | "; | |
321 | ||
322 | $DB->execute($sql); | |
323 | } | |
324 | ||
325 | /** | |
326 | * Aggregate criteria status's as per configured aggregation method | |
327 | * | |
328 | * @param int $method COMPLETION_AGGREGATION_* constant | |
329 | * @param bool $data Criteria completion status | |
330 | * @param bool|null $state Aggregation state | |
331 | * @return void | |
332 | */ | |
333 | function completion_cron_aggregate($method, $data, &$state) { | |
334 | if ($method == COMPLETION_AGGREGATION_ALL) { | |
335 | if ($data && $state !== false) { | |
336 | $state = true; | |
337 | } else { | |
338 | $state = false; | |
339 | } | |
340 | } elseif ($method == COMPLETION_AGGREGATION_ANY) { | |
341 | if ($data) { | |
342 | $state = true; | |
343 | } else if (!$data && $state === null) { | |
344 | $state = false; | |
345 | } | |
346 | } | |
347 | } |