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() {
56 //TODO: MDL-22797 completion needs to be updated to use new enrolment framework
59 mtrace('Marking users as started');
63 if (!empty($CFG->progresstrackedroles)) {
64 $roles = 'AND ra.roleid IN ('.$CFG->progresstrackedroles.')';
71 crc.id AS completionid,
72 MIN(ra.timestart) AS timestarted
77 ON con.instanceid = c.id
80 ON ra.contextid = con.id
82 {course_completions} crc
84 AND crc.userid = ra.userid
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().")
101 // Check if result is empty
102 if (!$rs = $DB->get_recordset_sql($sql)) {
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;
113 if ($record->completionid) {
114 $completion->id = $record->completionid;
117 $completion->mark_enrolled();
120 mtrace('Marked started user '.$record->userid.' in course '.$record->course);
128 * Run installed criteria's data aggregation methods
130 * Loop through each installed criteria and run the
131 * cron() method if it exists
135 function completion_cron_criteria() {
137 // Process each criteria type
138 global $CFG, $COMPLETION_CRITERIA_TYPES;
140 foreach ($COMPLETION_CRITERIA_TYPES as $type) {
142 $object = 'completion_criteria_'.$type;
143 require_once $CFG->libdir.'/completion/'.$object.'.php';
145 $class = new $object();
147 // Run the criteria type's cron method, if it has one
148 if (method_exists($class, 'cron')) {
151 mtrace('Running '.$object.'->cron()');
159 * Aggregate each user's criteria completions
163 function completion_cron_completions() {
167 mtrace('Aggregating completions');
171 $timestarted = time();
173 // Grab all criteria and their associated criteria completions
179 cr.criteriatype AS criteriatype,
180 cc.timecompleted AS timecompleted
182 {course_completion_criteria} cr
188 ON con.instanceid = c.id
190 {role_assignments} ra
191 ON ra.contextid = con.id
193 {course_completion_crit_compl} cc
194 ON cc.criteriaid = cr.id
195 AND cc.userid = ra.userid
197 {course_completions} crc
199 AND crc.userid = ra.userid
201 con.contextlevel = '.CONTEXT_COURSE.'
202 AND c.enablecompletion = 1
203 AND crc.timecompleted IS NULL
204 AND crc.reaggregate > 0
210 // Check if result is empty
211 if (!$rs = $DB->get_recordset_sql($sql)) {
215 $current_user = null;
216 $current_course = null;
217 $completions = array();
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;
232 if (!empty($completions)) {
235 mtrace('Aggregating completions for user '.$current_user.' in course '.$current_course);
238 // Get course info object
239 $info = new completion_info((object)array('id' => $current_course));
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);
247 $overall_status = null;
248 $activity_status = null;
249 $prerequisite_status = null;
252 // Get latest timecompleted
253 $timecompleted = null;
255 // Check each of the criteria
256 foreach ($completions as $params) {
257 $timecompleted = max($timecompleted, $params->timecompleted);
259 $completion = new completion_criteria_completion($params, false);
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);
269 completion_cron_aggregate($overall, $completion->is_complete(), $overall_status);
273 // Include role criteria aggregation in overall aggregation
274 if ($role_status !== null) {
275 completion_cron_aggregate($overall, $role_status, $overall_status);
278 // Include activity criteria aggregation in overall aggregation
279 if ($activity_status !== null) {
280 completion_cron_aggregate($overall, $activity_status, $overall_status);
283 // Include prerequisite criteria aggregation in overall aggregation
284 if ($prerequisite_status !== null) {
285 completion_cron_aggregate($overall, $prerequisite_status, $overall_status);
288 // If aggregation status is true, mark course complete for user
289 if ($overall_status) {
291 mtrace('Marking complete');
294 $ccompletion = new completion_completion(array('course' => $params->course, 'userid' => $params->userid));
295 $ccompletion->mark_complete($timecompleted);
299 // If this is the end of the recordset, break the loop
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;
312 // Mark all users as aggregated
319 reaggregate < {$timestarted}
326 * Aggregate criteria status's as per configured aggregation method
328 * @param int $method COMPLETION_AGGREGATION_* constant
329 * @param bool $data Criteria completion status
330 * @param bool|null $state Aggregation state
333 function completion_cron_aggregate($method, $data, &$state) {
334 if ($method == COMPLETION_AGGREGATION_ALL) {
335 if ($data && $state !== false) {
340 } elseif ($method == COMPLETION_AGGREGATION_ANY) {
343 } else if (!$data && $state === null) {