Moodle Tinymce plugin - MDL-23356 convert tabs to spaces.
[moodle.git] / lib / completion / cron.php
CommitLineData
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 */
27require_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 */
39function 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 */
53function 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 */
135function 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 */
163function 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 */
333function 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}