3 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
20 * Cron job for reviewing and aggregating course completion criteria
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
27 require_once $CFG->libdir.'/completionlib.php';
31 * Update user's course completion statuses
33 * First update all criteria completions, then
34 * aggregate all criteria completions and update
35 * overall course completions
39 function completion_cron() {
41 completion_cron_mark_started();
43 completion_cron_criteria();
45 completion_cron_completions();
49 * Mark users as started if the config option is set
53 function completion_cron_mark_started() {
57 mtrace('Marking users as started');
61 if (!empty($CFG->progresstrackedroles)) {
62 $roles = 'AND ra.roleid IN ('.$CFG->progresstrackedroles.')';
69 crc.id AS completionid,
70 MIN(ra.timestart) AS timestarted
75 ON con.instanceid = c.id
78 ON ra.contextid = con.id
80 {course_completions} crc
82 AND crc.userid = ra.userid
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().")
99 // Check if result is empty
100 if (!$rs = $DB->get_recordset_sql($sql)) {
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;
111 if ($record->completionid) {
112 $completion->id = $record->completionid;
115 $completion->mark_enrolled();
118 mtrace('Marked started user '.$record->userid.' in course '.$record->course);
126 * Run installed criteria's data aggregation methods
128 * Loop through each installed criteria and run the
129 * cron() method if it exists
133 function completion_cron_criteria() {
135 // Process each criteria type
136 global $CFG, $COMPLETION_CRITERIA_TYPES;
138 foreach ($COMPLETION_CRITERIA_TYPES as $type) {
140 $object = 'completion_criteria_'.$type;
141 require_once $CFG->libdir.'/completion/'.$object.'.php';
143 $class = new $object();
145 // Run the criteria type's cron method, if it has one
146 if (method_exists($class, 'cron')) {
149 mtrace('Running '.$object.'->cron()');
157 * Aggregate each user's criteria completions
161 function completion_cron_completions() {
165 mtrace('Aggregating completions');
169 $timestarted = time();
171 // Grab all criteria and their associated criteria completions
177 cr.criteriatype AS criteriatype,
178 cc.timecompleted AS timecompleted
180 {course_completion_criteria} cr
186 ON con.instanceid = c.id
188 {role_assignments} ra
189 ON ra.contextid = con.id
191 {course_completion_crit_compl} cc
192 ON cc.criteriaid = cr.id
193 AND cc.userid = ra.userid
195 {course_completions} crc
197 AND crc.userid = ra.userid
199 con.contextlevel = '.CONTEXT_COURSE.'
200 AND c.enablecompletion = 1
201 AND crc.timecompleted IS NULL
202 AND crc.reaggregate > 0
208 // Check if result is empty
209 if (!$rs = $DB->get_recordset_sql($sql)) {
213 $current_user = null;
214 $current_course = null;
215 $completions = array();
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;
230 if (!empty($completions)) {
233 mtrace('Aggregating completions for user '.$current_user.' in course '.$current_course);
236 // Get course info object
237 $info = new completion_info((object)array('id' => $current_course));
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);
245 $overall_status = null;
246 $activity_status = null;
247 $prerequisite_status = null;
250 // Get latest timecompleted
251 $timecompleted = null;
253 // Check each of the criteria
254 foreach ($completions as $params) {
255 $timecompleted = max($timecompleted, $params->timecompleted);
257 $completion = new completion_criteria_completion($params, false);
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);
267 completion_cron_aggregate($overall, $completion->is_complete(), $overall_status);
271 // Include role criteria aggregation in overall aggregation
272 if ($role_status !== null) {
273 completion_cron_aggregate($overall, $role_status, $overall_status);
276 // Include activity criteria aggregation in overall aggregation
277 if ($activity_status !== null) {
278 completion_cron_aggregate($overall, $activity_status, $overall_status);
281 // Include prerequisite criteria aggregation in overall aggregation
282 if ($prerequisite_status !== null) {
283 completion_cron_aggregate($overall, $prerequisite_status, $overall_status);
286 // If aggregation status is true, mark course complete for user
287 if ($overall_status) {
289 mtrace('Marking complete');
292 $ccompletion = new completion_completion(array('course' => $params->course, 'userid' => $params->userid));
293 $ccompletion->mark_complete($timecompleted);
297 // If this is the end of the recordset, break the loop
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;
310 // Mark all users as aggregated
317 reaggregate < {$timestarted}
324 * Aggregate criteria status's as per configured aggregation method
326 * @param int $method COMPLETION_AGGREGATION_* constant
327 * @param bool $data Criteria completion status
328 * @param bool|null $state Aggregation state
331 function completion_cron_aggregate($method, $data, &$state) {
332 if ($method == COMPLETION_AGGREGATION_ALL) {
333 if ($data && $state !== false) {
338 } elseif ($method == COMPLETION_AGGREGATION_ANY) {
341 } else if (!$data && $state === null) {