See Release Notes
Long Term Support Release
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/>. 16 17 namespace tool_brickfield; 18 19 /** 20 * Scheduler class. 21 * 22 * @package tool_brickfield 23 * @author Mike Churchward <mike@brickfieldlabs.ie> 24 * @copyright 2020 Brickfield Education Labs https://www.brickfield.ie 25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL 26 */ 27 class scheduler { 28 29 /** 30 * Analysis has not been requested. 31 */ 32 const STATUS_NOT_REQUESTED = 0; 33 34 /** 35 * Analysis has been requested but not submitted. 36 */ 37 const STATUS_REQUESTED = 1; 38 39 /** 40 * Analysis has been submitted. 41 */ 42 const STATUS_SUBMITTED = 2; 43 44 /** 45 * The data table used by the scheduler. 46 */ 47 const DATA_TABLE = 'tool_brickfield_schedule'; 48 49 /** @var int $instanceid The specific instance id of the context e.g. courseid. */ 50 public $instanceid; 51 52 /** @var int $contextlevel The context level of the instance id e.g. CONTEXT_COURSE / 50. */ 53 public $contextlevel; 54 55 /** 56 * Scheduler constructor. 57 * @param int $instanceid 58 * @param int $contextlevel 59 */ 60 public function __construct(int $instanceid = 0, int $contextlevel = CONTEXT_COURSE) { 61 $this->instanceid = $instanceid; 62 $this->contextlevel = $contextlevel; 63 } 64 65 /** 66 * Request this schedule object to be analyzed. Create the schedule if not present. 67 * @return bool 68 */ 69 public function request_analysis(): bool { 70 global $DB; 71 if (!$this->create_schedule()) { 72 return false; 73 } 74 if ($DB->set_field(self::DATA_TABLE, 'status', self::STATUS_REQUESTED, $this->standard_search_params())) { 75 if ($this->contextlevel == CONTEXT_COURSE) { 76 $context = \context_course::instance($this->instanceid); 77 } else { 78 $context = \context_system::instance(); 79 } 80 $event = \tool_brickfield\event\analysis_requested::create([ 81 'context' => $context, 82 'other' => ['course' => $this->instanceid], 83 ]); 84 $event->trigger(); 85 86 return true; 87 } else { 88 return false; 89 } 90 } 91 92 /** 93 * Mark this schedule object as analyzed. 94 * @return bool 95 * @throws \dml_exception 96 */ 97 public function mark_analyzed(): bool { 98 global $DB; 99 if (!$this->create_schedule()) { 100 return false; 101 } 102 $DB->set_field(self::DATA_TABLE, 'status', self::STATUS_SUBMITTED, $this->standard_search_params()); 103 $DB->set_field(self::DATA_TABLE, 'timeanalyzed', time(), $this->standard_search_params()); 104 return true; 105 } 106 107 /** 108 * Request this schedule object be added. Return true if already added, or the status of the insert operation. 109 * @return bool 110 */ 111 public function create_schedule(): bool { 112 global $DB; 113 if (!$this->is_in_schedule()) { 114 $datarecord = $this->get_datarecord(); 115 return $DB->insert_record(self::DATA_TABLE, $datarecord, false); 116 } 117 return true; 118 } 119 120 /** 121 * Request this schedule object be deleted. 122 * @return bool 123 */ 124 public function delete_schedule(): bool { 125 global $DB; 126 if ($this->is_in_schedule()) { 127 return $DB->delete_records(self::DATA_TABLE, $this->standard_search_params()); 128 } 129 return true; 130 } 131 132 /** 133 * Return true if this schedule object is in the schedule. 134 * @return bool 135 */ 136 public function is_in_schedule(): bool { 137 global $DB; 138 return $DB->record_exists(self::DATA_TABLE, $this->standard_search_params()); 139 } 140 141 /** 142 * Return true if this schedule object has been requested to be analyzed. 143 * @return bool 144 */ 145 public function is_scheduled(): bool { 146 global $DB; 147 return $DB->record_exists(self::DATA_TABLE, $this->standard_search_params() + ['status' => self::STATUS_REQUESTED]); 148 } 149 150 /** 151 * Return true if this schedule object has been submitted. 152 * @return bool 153 */ 154 public function is_submitted(): bool { 155 global $DB; 156 return $DB->record_exists(self::DATA_TABLE, $this->standard_search_params() + ['status' => self::STATUS_SUBMITTED]); 157 } 158 159 /** 160 * Return true if this schedule object has been analyzed. 161 * @return bool 162 */ 163 public function is_analyzed(): bool { 164 global $DB; 165 166 // Future... If not a course analysis request, just return the schedule table status. 167 if ($this->contextlevel != CONTEXT_COURSE) { 168 return $this->is_submitted(); 169 } 170 171 // A course is considered analyzed if it has been submitted and there is summary cache data for it. 172 $sql = 'SELECT * ' . 173 'FROM {' . self::DATA_TABLE . '} sch ' . 174 'INNER JOIN {' . manager::DB_SUMMARY . '} sum ON sch.instanceid = sum.courseid ' . 175 'WHERE (sch.contextlevel = :contextlevel) AND (sch.instanceid = :instanceid) AND (sch.status = :status)'; 176 if (!$DB->record_exists_sql($sql, $this->standard_search_params() + ['status' => self::STATUS_SUBMITTED])) { 177 // It may have been created in a prior version, so check before returning false. If it was, add a record for it. 178 if ($DB->record_exists(manager::DB_SUMMARY, ['courseid' => $this->instanceid])) { 179 return $this->mark_analyzed(); 180 } else { 181 return false; 182 } 183 } else { 184 return true; 185 } 186 } 187 188 /** 189 * The nornal data parameters to search for. 190 * @return array 191 */ 192 protected function standard_search_params(): array { 193 return ['contextlevel' => $this->contextlevel, 'instanceid' => $this->instanceid]; 194 } 195 196 /** 197 * Get the context id for the specified context level and instance. 198 * @return int 199 * @throws \dml_exception 200 */ 201 protected function get_contextid(): int { 202 global $DB; 203 $contextid = $DB->get_field('context', 'id', $this->standard_search_params()); 204 if ($contextid === false) { 205 $contextid = 0; 206 } 207 return $contextid; 208 } 209 210 /** 211 * Create and return a datarecord object for the data table. 212 * @param int $status 213 * @return \stdClass 214 * @throws \dml_exception 215 */ 216 public function get_datarecord(int $status = self::STATUS_NOT_REQUESTED): \stdClass { 217 $datarecord = new \stdClass(); 218 $datarecord->contextlevel = $this->contextlevel; 219 $datarecord->instanceid = $this->instanceid; 220 $datarecord->contextid = $this->get_contextid(); 221 $datarecord->status = $status; 222 $datarecord->timeanalyzed = 0; 223 $datarecord->timemodified = time(); 224 return $datarecord; 225 } 226 227 /** 228 * Process all the course analysis requests, and mark them as analyzed. Limit the number of requests processed by time. 229 * @throws \ReflectionException 230 * @throws \dml_exception 231 */ 232 public static function process_scheduled_requests() { 233 global $DB; 234 235 // Run a registration check. 236 if (!(new registration())->validate()) { 237 return; 238 } 239 240 $runtimemax = MINSECS * 5; // Only process requests for five minutes. May want to tie this to task schedule. 241 $runtime = time(); 242 $requestset = $DB->get_recordset(self::DATA_TABLE, ['status' => self::STATUS_REQUESTED], 'timemodified ASC'); 243 foreach ($requestset as $request) { 244 if ($request->contextlevel == CONTEXT_COURSE) { 245 manager::find_new_or_updated_areas_per_course($request->instanceid); 246 $request->status = self::STATUS_SUBMITTED; 247 $request->timeanalyzed = time(); 248 $request->timemodified = time(); 249 $DB->update_record(self::DATA_TABLE, $request); 250 } 251 $runtime = time() - $runtime; 252 if ($runtime >= $runtimemax) { 253 break; 254 } 255 } 256 $requestset->close(); 257 } 258 259 // The following are static versions of the above functions for courses that do not require creating an object first. 260 261 /** 262 * Load all requested context types into the schedule as requested. Write records in groups of 100. 263 * @param int $contextlevel 264 * @return bool 265 * @throws \coding_exception 266 * @throws \dml_exception 267 */ 268 public static function initialize_schedule(int $contextlevel = CONTEXT_COURSE): bool { 269 global $DB; 270 271 $writelimit = 100; 272 $recordcount = 0; 273 $records = []; 274 $scheduler = new scheduler(0, $contextlevel); 275 $coursesset = $DB->get_recordset('course', null, 'id', 'id, id as courseid'); 276 foreach ($coursesset as $course) { 277 $recordcount++; 278 $scheduler->instanceid = $course->id; 279 $records[] = $scheduler->get_datarecord(self::STATUS_REQUESTED); 280 if ($recordcount >= $writelimit) { 281 $DB->insert_records(self::DATA_TABLE, $records); 282 $recordcount = 0; 283 $records = []; 284 } 285 } 286 287 if ($recordcount > 0) { 288 $DB->insert_records(self::DATA_TABLE, $records); 289 } 290 $coursesset->close(); 291 292 return true; 293 } 294 295 /** 296 * Request the specified course be analyzed. 297 * @param int $courseid 298 * @return bool 299 */ 300 public static function request_course_analysis(int $courseid) { 301 return (new scheduler($courseid))->request_analysis(); 302 } 303 304 /** 305 * Request the specified course be added. 306 * @param int $courseid 307 * @return bool 308 */ 309 public static function create_course_schedule(int $courseid) { 310 return (new scheduler($courseid))->create_schedule(); 311 } 312 313 /** 314 * Delete the specified course from the schedule. 315 * @param int $courseid 316 * @return bool 317 */ 318 public static function delete_course_schedule(int $courseid) { 319 return (new scheduler($courseid))->delete_schedule(); 320 } 321 322 /** 323 * Return true if the specified course is in the schedule. 324 * @param int $courseid 325 * @return bool 326 */ 327 public static function is_course_in_schedule(int $courseid) { 328 return (new scheduler($courseid))->is_in_schedule(); 329 } 330 331 /** 332 * Return true if the specified course is scheduled. 333 * @param int $courseid 334 * @return bool 335 */ 336 public static function is_course_scheduled(int $courseid) { 337 return (new scheduler($courseid))->is_scheduled(); 338 } 339 340 /** 341 * Return true if the specified course has been analyzed. 342 * @param int $courseid 343 * @return bool 344 */ 345 public static function is_course_analyzed(int $courseid) { 346 return (new scheduler($courseid))->is_analyzed(); 347 } 348 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body