MDL-65276 core: fix CiBoT complains
[moodle.git] / lib / classes / task / completion_regular_task.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * A scheduled task.
19  *
20  * @package    core
21  * @copyright  2015 Josh Willcock
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 namespace core\task;
26 /**
27  * Simple task to run the regular completion cron.
28  * @copyright  2015 Josh Willcock
29  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
30  */
31 class completion_regular_task extends scheduled_task {
33     /**
34      * Get a descriptive name for this task (shown to admins).
35      *
36      * @return string
37      */
38     public function get_name() {
39         return get_string('taskcompletionregular', 'admin');
40     }
42     /**
43      * Do the job.
44      * Throw exceptions on errors (the job will be retried).
45      */
46     public function execute() {
47         global $CFG, $COMPLETION_CRITERIA_TYPES, $DB;
49         if ($CFG->enablecompletion) {
50             require_once($CFG->libdir . "/completionlib.php");
52             // Process each criteria type.
53             foreach ($COMPLETION_CRITERIA_TYPES as $type) {
54                 $object = 'completion_criteria_' . $type;
55                 require_once($CFG->dirroot . '/completion/criteria/' . $object . '.php');
57                 $class = new $object();
58                 // Run the criteria type's cron method, if it has one.
59                 if (method_exists($class, 'cron')) {
60                     if (debugging()) {
61                         mtrace('Running '.$object.'->cron()');
62                     }
63                     $class->cron();
64                 }
65             }
67             if (debugging()) {
68                 mtrace('Aggregating completions');
69             }
71             // Save time started.
72             $timestarted = time();
74             // Grab all criteria and their associated criteria completions.
75             $sql = 'SELECT DISTINCT c.id AS course, cr.id AS criteriaid, crc.userid AS userid,
76                                     cr.criteriatype AS criteriatype, cc.timecompleted AS timecompleted
77                       FROM {course_completion_criteria} cr
78                 INNER JOIN {course} c ON cr.course = c.id
79                 INNER JOIN {course_completions} crc ON crc.course = c.id
80                  LEFT JOIN {course_completion_crit_compl} cc ON cc.criteriaid = cr.id AND crc.userid = cc.userid
81                      WHERE c.enablecompletion = 1
82                        AND crc.timecompleted IS NULL
83                        AND crc.reaggregate > 0
84                        AND crc.reaggregate < :timestarted
85                   ORDER BY course, userid';
86             $rs = $DB->get_recordset_sql($sql, ['timestarted' => $timestarted]);
88             // Check if result is empty.
89             if (!$rs->valid()) {
90                 $rs->close();
91                 return;
92             }
94             $currentuser = null;
95             $currentcourse = null;
96             $completions = [];
97             while (1) {
98                 // Grab records for current user/course.
99                 foreach ($rs as $record) {
100                     // If we are still grabbing the same users completions.
101                     if ($record->userid === $currentuser && $record->course === $currentcourse) {
102                         $completions[$record->criteriaid] = $record;
103                     } else {
104                         break;
105                     }
106                 }
108                 // Aggregate.
109                 if (!empty($completions)) {
110                     if (debugging()) {
111                         mtrace('Aggregating completions for user ' . $currentuser . ' in course ' . $currentcourse);
112                     }
114                     // Get course info object.
115                     $info = new \completion_info((object)['id' => $currentcourse]);
117                     // Setup aggregation.
118                     $overall = $info->get_aggregation_method();
119                     $activity = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY);
120                     $prerequisite = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE);
121                     $role = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE);
123                     $overallstatus = null;
124                     $activitystatus = null;
125                     $prerequisitestatus = null;
126                     $rolestatus = null;
128                     // Get latest timecompleted.
129                     $timecompleted = null;
131                     // Check each of the criteria.
132                     foreach ($completions as $params) {
133                         $timecompleted = max($timecompleted, $params->timecompleted);
134                         $completion = new \completion_criteria_completion((array)$params, false);
136                         // Handle aggregation special cases.
137                         if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
138                             completion_cron_aggregate($activity, $completion->is_complete(), $activitystatus);
139                         } else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) {
140                             completion_cron_aggregate($prerequisite, $completion->is_complete(), $prerequisitestatus);
141                         } else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ROLE) {
142                             completion_cron_aggregate($role, $completion->is_complete(), $rolestatus);
143                         } else {
144                             completion_cron_aggregate($overall, $completion->is_complete(), $overallstatus);
145                         }
146                     }
148                     // Include role criteria aggregation in overall aggregation.
149                     if ($rolestatus !== null) {
150                         completion_cron_aggregate($overall, $rolestatus, $overallstatus);
151                     }
153                     // Include activity criteria aggregation in overall aggregation.
154                     if ($activitystatus !== null) {
155                         completion_cron_aggregate($overall, $activitystatus, $overallstatus);
156                     }
158                     // Include prerequisite criteria aggregation in overall aggregation.
159                     if ($prerequisitestatus !== null) {
160                         completion_cron_aggregate($overall, $prerequisitestatus, $overallstatus);
161                     }
163                     // If aggregation status is true, mark course complete for user.
164                     if ($overallstatus) {
165                         if (debugging()) {
166                             mtrace('Marking complete');
167                         }
169                         $ccompletion = new \completion_completion([
170                             'course' => $params->course,
171                             'userid' => $params->userid
172                         ]);
173                         $ccompletion->mark_complete($timecompleted);
174                     }
175                 }
177                 // If this is the end of the recordset, break the loop.
178                 if (!$rs->valid()) {
179                     $rs->close();
180                     break;
181                 }
183                 // New/next user, update user details, reset completions.
184                 $currentuser = $record->userid;
185                 $currentcourse = $record->course;
186                 $completions = [];
187                 $completions[$record->criteriaid] = $record;
188             }
190             // Mark all users as aggregated.
191             $sql = "UPDATE {course_completions}
192                        SET reaggregate = 0
193                      WHERE reaggregate < :timestarted
194                        AND reaggregate > 0";
195             $DB->execute($sql, ['timestarted' => $timestarted]);
196         }
197     }