Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]
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 /** 18 * Upgrade helper functions 19 * 20 * This file is used for special upgrade functions - for example groups and gradebook. 21 * These functions must use SQL and database related functions only- no other Moodle API, 22 * because it might depend on db structures that are not yet present during upgrade. 23 * (Do not use functions from accesslib.php, grades classes or group functions at all!) 24 * 25 * @package core_install 26 * @category upgrade 27 * @copyright 2007 Petr Skoda (http://skodak.org) 28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 29 */ 30 31 defined('MOODLE_INTERNAL') || die(); 32 33 /** 34 * Returns all non-view and non-temp tables with sane names. 35 * Prints list of non-supported tables using $OUTPUT->notification() 36 * 37 * @return array 38 */ 39 function upgrade_mysql_get_supported_tables() { 40 global $OUTPUT, $DB; 41 42 $tables = array(); 43 $patprefix = str_replace('_', '\\_', $DB->get_prefix()); 44 $pregprefix = preg_quote($DB->get_prefix(), '/'); 45 46 $sql = "SHOW FULL TABLES LIKE '$patprefix%'"; 47 $rs = $DB->get_recordset_sql($sql); 48 foreach ($rs as $record) { 49 $record = array_change_key_case((array)$record, CASE_LOWER); 50 $type = $record['table_type']; 51 unset($record['table_type']); 52 $fullname = array_shift($record); 53 54 if ($pregprefix === '') { 55 $name = $fullname; 56 } else { 57 $count = null; 58 $name = preg_replace("/^$pregprefix/", '', $fullname, -1, $count); 59 if ($count !== 1) { 60 continue; 61 } 62 } 63 64 if (!preg_match("/^[a-z][a-z0-9_]*$/", $name)) { 65 echo $OUTPUT->notification("Database table with invalid name '$fullname' detected, skipping.", 'notifyproblem'); 66 continue; 67 } 68 if ($type === 'VIEW') { 69 echo $OUTPUT->notification("Unsupported database table view '$fullname' detected, skipping.", 'notifyproblem'); 70 continue; 71 } 72 $tables[$name] = $name; 73 } 74 $rs->close(); 75 76 return $tables; 77 } 78 79 /** 80 * Using data for a single course-module that has groupmembersonly enabled, 81 * returns the new availability value that incorporates the correct 82 * groupmembersonly option. 83 * 84 * Included as a function so that it can be shared between upgrade and restore, 85 * and unit-tested. 86 * 87 * @param int $groupingid Grouping id for the course-module (0 if none) 88 * @param string $availability Availability JSON data for the module (null if none) 89 * @return string New value for availability for the module 90 */ 91 function upgrade_group_members_only($groupingid, $availability) { 92 // Work out the new JSON object representing this option. 93 if ($groupingid) { 94 // Require specific grouping. 95 $condition = (object)array('type' => 'grouping', 'id' => (int)$groupingid); 96 } else { 97 // No grouping specified, so require membership of any group. 98 $condition = (object)array('type' => 'group'); 99 } 100 101 if (is_null($availability)) { 102 // If there are no conditions using the new API then just set it. 103 $tree = (object)array('op' => '&', 'c' => array($condition), 'showc' => array(false)); 104 } else { 105 // There are existing conditions. 106 $tree = json_decode($availability); 107 switch ($tree->op) { 108 case '&' : 109 // For & conditions we can just add this one. 110 $tree->c[] = $condition; 111 $tree->showc[] = false; 112 break; 113 case '!|' : 114 // For 'not or' conditions we can add this one 115 // but negated. 116 $tree->c[] = (object)array('op' => '!&', 'c' => array($condition)); 117 $tree->showc[] = false; 118 break; 119 default: 120 // For the other two (OR and NOT AND) we have to add 121 // an extra level to the tree. 122 $tree = (object)array('op' => '&', 'c' => array($tree, $condition), 123 'showc' => array($tree->show, false)); 124 // Inner trees do not have a show option, so remove it. 125 unset($tree->c[0]->show); 126 break; 127 } 128 } 129 130 return json_encode($tree); 131 } 132 133 /** 134 * Marks all courses with changes in extra credit weight calculation 135 * 136 * Used during upgrade and in course restore process 137 * 138 * This upgrade script is needed because we changed the algorithm for calculating the automatic weights of extra 139 * credit items and want to prevent changes in the existing student grades. 140 * 141 * @param int $onlycourseid 142 */ 143 function upgrade_extra_credit_weightoverride($onlycourseid = 0) { 144 global $DB; 145 146 // Find all courses that have categories in Natural aggregation method where there is at least one extra credit 147 // item and at least one item with overridden weight. 148 $courses = $DB->get_fieldset_sql( 149 "SELECT DISTINCT gc.courseid 150 FROM {grade_categories} gc 151 INNER JOIN {grade_items} gi ON gc.id = gi.categoryid AND gi.weightoverride = :weightoverriden 152 INNER JOIN {grade_items} gie ON gc.id = gie.categoryid AND gie.aggregationcoef = :extracredit 153 WHERE gc.aggregation = :naturalaggmethod" . ($onlycourseid ? " AND gc.courseid = :onlycourseid" : ''), 154 array('naturalaggmethod' => 13, 155 'weightoverriden' => 1, 156 'extracredit' => 1, 157 'onlycourseid' => $onlycourseid, 158 ) 159 ); 160 foreach ($courses as $courseid) { 161 $gradebookfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid); 162 if (!$gradebookfreeze) { 163 set_config('gradebook_calculations_freeze_' . $courseid, 20150619); 164 } 165 } 166 } 167 168 /** 169 * Marks all courses that require calculated grade items be updated. 170 * 171 * Used during upgrade and in course restore process. 172 * 173 * This upgrade script is needed because the calculated grade items were stuck with a maximum of 100 and could be changed. 174 * This flags the courses that are affected and the grade book is frozen to retain grade integrity. 175 * 176 * @param int $courseid Specify a course ID to run this script on just one course. 177 */ 178 function upgrade_calculated_grade_items($courseid = null) { 179 global $DB, $CFG; 180 181 $affectedcourses = array(); 182 $possiblecourseids = array(); 183 $params = array(); 184 $singlecoursesql = ''; 185 if (isset($courseid)) { 186 $singlecoursesql = "AND ns.id = :courseid"; 187 $params['courseid'] = $courseid; 188 } 189 $siteminmaxtouse = 1; 190 if (isset($CFG->grade_minmaxtouse)) { 191 $siteminmaxtouse = $CFG->grade_minmaxtouse; 192 } 193 $courseidsql = "SELECT ns.id 194 FROM ( 195 SELECT c.id, coalesce(" . $DB->sql_compare_text('gs.value') . ", :siteminmax) AS gradevalue 196 FROM {course} c 197 LEFT JOIN {grade_settings} gs 198 ON c.id = gs.courseid 199 AND ((gs.name = 'minmaxtouse' AND " . $DB->sql_compare_text('gs.value') . " = '2')) 200 ) ns 201 WHERE " . $DB->sql_compare_text('ns.gradevalue') . " = '2' $singlecoursesql"; 202 $params['siteminmax'] = $siteminmaxtouse; 203 $courses = $DB->get_records_sql($courseidsql, $params); 204 foreach ($courses as $course) { 205 $possiblecourseids[$course->id] = $course->id; 206 } 207 208 if (!empty($possiblecourseids)) { 209 list($sql, $params) = $DB->get_in_or_equal($possiblecourseids); 210 // A calculated grade item grade min != 0 and grade max != 100 and the course setting is set to 211 // "Initial min and max grades". 212 $coursesql = "SELECT DISTINCT courseid 213 FROM {grade_items} 214 WHERE calculation IS NOT NULL 215 AND itemtype = 'manual' 216 AND (grademax <> 100 OR grademin <> 0) 217 AND courseid $sql"; 218 $affectedcourses = $DB->get_records_sql($coursesql, $params); 219 } 220 221 // Check for second type of affected courses. 222 // If we already have the courseid parameter set in the affectedcourses then there is no need to run through this section. 223 if (!isset($courseid) || !in_array($courseid, $affectedcourses)) { 224 $singlecoursesql = ''; 225 $params = array(); 226 if (isset($courseid)) { 227 $singlecoursesql = "AND courseid = :courseid"; 228 $params['courseid'] = $courseid; 229 } 230 $nestedsql = "SELECT id 231 FROM {grade_items} 232 WHERE itemtype = 'category' 233 AND calculation IS NOT NULL $singlecoursesql"; 234 $calculatedgradecategories = $DB->get_records_sql($nestedsql, $params); 235 $categoryids = array(); 236 foreach ($calculatedgradecategories as $key => $gradecategory) { 237 $categoryids[$key] = $gradecategory->id; 238 } 239 240 if (!empty($categoryids)) { 241 list($sql, $params) = $DB->get_in_or_equal($categoryids); 242 // A category with a calculation where the raw grade min and the raw grade max don't match the grade min and grade max 243 // for the category. 244 $coursesql = "SELECT DISTINCT gi.courseid 245 FROM {grade_grades} gg, {grade_items} gi 246 WHERE gi.id = gg.itemid 247 AND (gg.rawgrademax <> gi.grademax OR gg.rawgrademin <> gi.grademin) 248 AND gi.id $sql"; 249 $additionalcourses = $DB->get_records_sql($coursesql, $params); 250 foreach ($additionalcourses as $key => $additionalcourse) { 251 if (!array_key_exists($key, $affectedcourses)) { 252 $affectedcourses[$key] = $additionalcourse; 253 } 254 } 255 } 256 } 257 258 foreach ($affectedcourses as $affectedcourseid) { 259 if (isset($CFG->upgrade_calculatedgradeitemsonlyregrade) && !($courseid)) { 260 $DB->set_field('grade_items', 'needsupdate', 1, array('courseid' => $affectedcourseid->courseid)); 261 } else { 262 // Check to see if the gradebook freeze is already in affect. 263 $gradebookfreeze = get_config('core', 'gradebook_calculations_freeze_' . $affectedcourseid->courseid); 264 if (!$gradebookfreeze) { 265 set_config('gradebook_calculations_freeze_' . $affectedcourseid->courseid, 20150627); 266 } 267 } 268 } 269 } 270 271 /** 272 * This function creates a default separated/connected scale 273 * so there's something in the database. The locations of 274 * strings and files is a bit odd, but this is because we 275 * need to maintain backward compatibility with many different 276 * existing language translations and older sites. 277 * 278 * @global object 279 * @return void 280 */ 281 function make_default_scale() { 282 global $DB; 283 284 $defaultscale = new stdClass(); 285 $defaultscale->courseid = 0; 286 $defaultscale->userid = 0; 287 $defaultscale->name = get_string('separateandconnected'); 288 $defaultscale->description = get_string('separateandconnectedinfo'); 289 $defaultscale->scale = get_string('postrating1', 'forum').','. 290 get_string('postrating2', 'forum').','. 291 get_string('postrating3', 'forum'); 292 $defaultscale->timemodified = time(); 293 294 $defaultscale->id = $DB->insert_record('scale', $defaultscale); 295 return $defaultscale; 296 } 297 298 299 /** 300 * Create another default scale. 301 * 302 * @param int $oldversion 303 * @return bool always true 304 */ 305 function make_competence_scale() { 306 global $DB; 307 308 $defaultscale = new stdClass(); 309 $defaultscale->courseid = 0; 310 $defaultscale->userid = 0; 311 $defaultscale->name = get_string('defaultcompetencescale'); 312 $defaultscale->description = get_string('defaultcompetencescaledesc'); 313 $defaultscale->scale = get_string('defaultcompetencescalenotproficient').','. 314 get_string('defaultcompetencescaleproficient'); 315 $defaultscale->timemodified = time(); 316 317 $defaultscale->id = $DB->insert_record('scale', $defaultscale); 318 return $defaultscale; 319 } 320 321 /** 322 * Marks all courses that require rounded grade items be updated. 323 * 324 * Used during upgrade and in course restore process. 325 * 326 * This upgrade script is needed because it has been decided that if a grade is rounded up, and it will changed a letter 327 * grade or satisfy a course completion grade criteria, then it should be set as so, and the letter will be awarded and or 328 * the course completion grade will be awarded. 329 * 330 * @param int $courseid Specify a course ID to run this script on just one course. 331 */ 332 function upgrade_course_letter_boundary($courseid = null) { 333 global $DB, $CFG; 334 335 $coursesql = ''; 336 $params = array('contextlevel' => CONTEXT_COURSE); 337 if (!empty($courseid)) { 338 $coursesql = 'AND c.id = :courseid'; 339 $params['courseid'] = $courseid; 340 } 341 342 // Check to see if the system letter boundaries are borked. 343 $systemcontext = context_system::instance(); 344 $systemneedsfreeze = upgrade_letter_boundary_needs_freeze($systemcontext); 345 346 // Check the setting for showing the letter grade in a column (default is false). 347 $usergradelettercolumnsetting = 0; 348 if (isset($CFG->grade_report_user_showlettergrade)) { 349 $usergradelettercolumnsetting = (int)$CFG->grade_report_user_showlettergrade; 350 } 351 $lettercolumnsql = ''; 352 if ($usergradelettercolumnsetting) { 353 // The system default is to show a column with letters (and the course uses the defaults). 354 $lettercolumnsql = '(gss.value is NULL OR ' . $DB->sql_compare_text('gss.value') . ' <> \'0\')'; 355 } else { 356 // The course displays a column with letters. 357 $lettercolumnsql = $DB->sql_compare_text('gss.value') . ' = \'1\''; 358 } 359 360 // 3, 13, 23, 31, and 32 are the grade display types that incorporate showing letters. See lib/grade/constants/php. 361 $systemusesletters = (int) (isset($CFG->grade_displaytype) && in_array($CFG->grade_displaytype, array(3, 13, 23, 31, 32))); 362 $systemletters = $systemusesletters || $usergradelettercolumnsetting; 363 364 $contextselect = context_helper::get_preload_record_columns_sql('ctx'); 365 366 if ($systemletters && $systemneedsfreeze) { 367 // Select courses with no grade setting for display and a grade item that is using the default display, 368 // but have not altered the course letter boundary configuration. These courses are definitely affected. 369 370 $sql = "SELECT DISTINCT c.id AS courseid 371 FROM {course} c 372 JOIN {grade_items} gi ON c.id = gi.courseid 373 JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel 374 LEFT JOIN {grade_settings} gs ON gs.courseid = c.id AND gs.name = 'displaytype' 375 LEFT JOIN {grade_settings} gss ON gss.courseid = c.id AND gss.name = 'report_user_showlettergrade' 376 LEFT JOIN {grade_letters} gl ON gl.contextid = ctx.id 377 WHERE gi.display = 0 378 AND ((gs.value is NULL) 379 AND ($lettercolumnsql)) 380 AND gl.id is NULL $coursesql"; 381 $affectedcourseids = $DB->get_recordset_sql($sql, $params); 382 foreach ($affectedcourseids as $courseid) { 383 set_config('gradebook_calculations_freeze_' . $courseid->courseid, 20160518); 384 } 385 $affectedcourseids->close(); 386 } 387 388 // If the system letter boundary is okay proceed to check grade item and course grade display settings. 389 $sql = "SELECT DISTINCT c.id AS courseid, $contextselect 390 FROM {course} c 391 JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel 392 JOIN {grade_items} gi ON c.id = gi.courseid 393 LEFT JOIN {grade_settings} gs ON c.id = gs.courseid AND gs.name = 'displaytype' 394 LEFT JOIN {grade_settings} gss ON gss.courseid = c.id AND gss.name = 'report_user_showlettergrade' 395 WHERE 396 ( 397 -- A grade item is using letters 398 (gi.display IN (3, 13, 23, 31, 32)) 399 -- OR the course is using letters 400 OR (" . $DB->sql_compare_text('gs.value') . " IN ('3', '13', '23', '31', '32') 401 -- OR the course using the system default which is letters 402 OR (gs.value IS NULL AND $systemusesletters = 1) 403 ) 404 OR ($lettercolumnsql) 405 ) 406 -- AND the course matches 407 $coursesql"; 408 409 $potentialcourses = $DB->get_recordset_sql($sql, $params); 410 411 foreach ($potentialcourses as $value) { 412 $gradebookfreeze = 'gradebook_calculations_freeze_' . $value->courseid; 413 414 // Check also if this course id has already been frozen. 415 // If we already have this course ID then move on to the next record. 416 if (!property_exists($CFG, $gradebookfreeze)) { 417 // Check for 57 letter grade issue. 418 context_helper::preload_from_record($value); 419 $coursecontext = context_course::instance($value->courseid); 420 if (upgrade_letter_boundary_needs_freeze($coursecontext)) { 421 // We have a course with a possible score standardisation problem. Flag for freeze. 422 // Flag this course as being frozen. 423 set_config('gradebook_calculations_freeze_' . $value->courseid, 20160518); 424 } 425 } 426 } 427 $potentialcourses->close(); 428 } 429 430 /** 431 * Checks the letter boundary of the provided context to see if it needs freezing. 432 * Each letter boundary is tested to see if receiving that boundary number will 433 * result in achieving the cosponsoring letter. 434 * 435 * @param object $context Context object 436 * @return bool if the letter boundary for this context should be frozen. 437 */ 438 function upgrade_letter_boundary_needs_freeze($context) { 439 global $DB; 440 441 $contexts = $context->get_parent_context_ids(); 442 array_unshift($contexts, $context->id); 443 444 foreach ($contexts as $ctxid) { 445 446 $letters = $DB->get_records_menu('grade_letters', array('contextid' => $ctxid), 'lowerboundary DESC', 447 'lowerboundary, letter'); 448 449 if (!empty($letters)) { 450 foreach ($letters as $boundary => $notused) { 451 $standardisedboundary = upgrade_standardise_score($boundary, 0, 100, 0, 100); 452 if ($standardisedboundary < $boundary) { 453 return true; 454 } 455 } 456 // We found letters but we have no boundary problem. 457 return false; 458 } 459 } 460 return false; 461 } 462 463 /** 464 * Given a float value situated between a source minimum and a source maximum, converts it to the 465 * corresponding value situated between a target minimum and a target maximum. Thanks to Darlene 466 * for the formula :-) 467 * 468 * @param float $rawgrade 469 * @param float $sourcemin 470 * @param float $sourcemax 471 * @param float $targetmin 472 * @param float $targetmax 473 * @return float Converted value 474 */ 475 function upgrade_standardise_score($rawgrade, $sourcemin, $sourcemax, $targetmin, $targetmax) { 476 if (is_null($rawgrade)) { 477 return null; 478 } 479 480 if ($sourcemax == $sourcemin or $targetmin == $targetmax) { 481 // Prevent division by 0. 482 return $targetmax; 483 } 484 485 $factor = ($rawgrade - $sourcemin) / ($sourcemax - $sourcemin); 486 $diff = $targetmax - $targetmin; 487 $standardisedvalue = $factor * $diff + $targetmin; 488 return $standardisedvalue; 489 } 490 491 /** 492 * Provides a way to check and update a serialized string that uses the deprecated object class. 493 * 494 * @param string $serializeddata Serialized string which may contain the now deprecated object. 495 * @return array Returns an array where the first variable is a bool with a status of whether the initial data was changed 496 * or not. The second variable is the said data. 497 */ 498 function upgrade_fix_serialized_objects($serializeddata) { 499 $updated = false; 500 if (strpos($serializeddata, ":6:\"object") !== false) { 501 $serializeddata = str_replace(":6:\"object", ":8:\"stdClass", $serializeddata); 502 $updated = true; 503 } 504 return [$updated, $serializeddata]; 505 } 506 507 /** 508 * Deletes file records which have their repository deleted. 509 * 510 */ 511 function upgrade_delete_orphaned_file_records() { 512 global $DB; 513 514 $sql = "SELECT f.id, f.contextid, f.component, f.filearea, f.itemid, fr.id AS referencefileid 515 FROM {files} f 516 JOIN {files_reference} fr ON f.referencefileid = fr.id 517 LEFT JOIN {repository_instances} ri ON fr.repositoryid = ri.id 518 WHERE ri.id IS NULL"; 519 520 $deletedfiles = $DB->get_recordset_sql($sql); 521 522 $deletedfileids = array(); 523 524 $fs = get_file_storage(); 525 foreach ($deletedfiles as $deletedfile) { 526 $fs->delete_area_files($deletedfile->contextid, $deletedfile->component, $deletedfile->filearea, $deletedfile->itemid); 527 $deletedfileids[] = $deletedfile->referencefileid; 528 } 529 $deletedfiles->close(); 530 531 $DB->delete_records_list('files_reference', 'id', $deletedfileids); 532 } 533 534 /** 535 * Updates the existing prediction actions in the database according to the new suggested actions. 536 * @return null 537 */ 538 function upgrade_rename_prediction_actions_useful_incorrectly_flagged() { 539 global $DB; 540 541 // The update depends on the analyser class used by each model so we need to iterate through the models in the system. 542 $modelids = $DB->get_records_sql("SELECT DISTINCT am.id, am.target 543 FROM {analytics_models} am 544 JOIN {analytics_predictions} ap ON ap.modelid = am.id 545 JOIN {analytics_prediction_actions} apa ON ap.id = apa.predictionid"); 546 foreach ($modelids as $model) { 547 $targetname = $model->target; 548 if (!class_exists($targetname)) { 549 // The plugin may not be available. 550 continue; 551 } 552 $target = new $targetname(); 553 554 $analyserclass = $target->get_analyser_class(); 555 if (!class_exists($analyserclass)) { 556 // The plugin may not be available. 557 continue; 558 } 559 560 if ($analyserclass::one_sample_per_analysable()) { 561 // From 'fixed' to 'useful'. 562 $params = ['oldaction' => 'fixed', 'newaction' => 'useful']; 563 } else { 564 // From 'notuseful' to 'incorrectlyflagged'. 565 $params = ['oldaction' => 'notuseful', 'newaction' => 'incorrectlyflagged']; 566 } 567 568 $subsql = "SELECT id FROM {analytics_predictions} WHERE modelid = :modelid"; 569 $updatesql = "UPDATE {analytics_prediction_actions} 570 SET actionname = :newaction 571 WHERE predictionid IN ($subsql) AND actionname = :oldaction"; 572 573 $DB->execute($updatesql, $params + ['modelid' => $model->id]); 574 } 575 } 576 577 /** 578 * Convert the site settings for the 'hub' component in the config_plugins table. 579 * 580 * @param stdClass $hubconfig Settings loaded for the 'hub' component. 581 * @param string $huburl The URL of the hub to use as the valid one in case of conflict. 582 * @return stdClass List of new settings to be applied (including null values to be unset). 583 */ 584 function upgrade_convert_hub_config_site_param_names(stdClass $hubconfig, string $huburl): stdClass { 585 586 $cleanhuburl = clean_param($huburl, PARAM_ALPHANUMEXT); 587 $converted = []; 588 589 foreach ($hubconfig as $oldname => $value) { 590 if (preg_match('/^site_([a-z]+)([A-Za-z0-9_-]*)/', $oldname, $matches)) { 591 $newname = 'site_'.$matches[1]; 592 593 if ($oldname === $newname) { 594 // There is an existing value with the new naming convention already. 595 $converted[$newname] = $value; 596 597 } else if (!array_key_exists($newname, $converted)) { 598 // Add the value under a new name and mark the original to be unset. 599 $converted[$newname] = $value; 600 $converted[$oldname] = null; 601 602 } else if ($matches[2] === '_'.$cleanhuburl) { 603 // The new name already exists, overwrite only if coming from the valid hub. 604 $converted[$newname] = $value; 605 $converted[$oldname] = null; 606 607 } else { 608 // Just unset the old value. 609 $converted[$oldname] = null; 610 } 611 612 } else { 613 // Not a hub-specific site setting, just keep it. 614 $converted[$oldname] = $value; 615 } 616 } 617 618 return (object) $converted; 619 } 620 621 /** 622 * Fix the incorrect default values inserted into analytics contextids field. 623 */ 624 function upgrade_analytics_fix_contextids_defaults() { 625 global $DB; 626 627 $select = $DB->sql_compare_text('contextids') . ' = :zero OR ' . $DB->sql_compare_text('contextids') . ' = :null'; 628 $params = ['zero' => '0', 'null' => 'null']; 629 $DB->execute("UPDATE {analytics_models} set contextids = null WHERE " . $select, $params); 630 } 631 632 /** 633 * Upgrade core licenses shipped with Moodle. 634 */ 635 function upgrade_core_licenses() { 636 global $CFG, $DB; 637 638 $corelicenses = []; 639 640 $license = new stdClass(); 641 $license->shortname = 'unknown'; 642 $license->fullname = 'Licence not specified'; 643 $license->source = ''; 644 $license->enabled = 1; 645 $license->version = '2010033100'; 646 $license->custom = 0; 647 $corelicenses[] = $license; 648 649 $license = new stdClass(); 650 $license->shortname = 'allrightsreserved'; 651 $license->fullname = 'All rights reserved'; 652 $license->source = 'https://en.wikipedia.org/wiki/All_rights_reserved'; 653 $license->enabled = 1; 654 $license->version = '2010033100'; 655 $license->custom = 0; 656 $corelicenses[] = $license; 657 658 $license = new stdClass(); 659 $license->shortname = 'public'; 660 $license->fullname = 'Public domain'; 661 $license->source = 'https://en.wikipedia.org/wiki/Public_domain'; 662 $license->enabled = 1; 663 $license->version = '2010033100'; 664 $license->custom = 0; 665 $corelicenses[] = $license; 666 667 $license = new stdClass(); 668 $license->shortname = 'cc'; 669 $license->fullname = 'Creative Commons'; 670 $license->source = 'https://creativecommons.org/licenses/by/3.0/'; 671 $license->enabled = 1; 672 $license->version = '2010033100'; 673 $license->custom = 0; 674 $corelicenses[] = $license; 675 676 $license = new stdClass(); 677 $license->shortname = 'cc-nd'; 678 $license->fullname = 'Creative Commons - NoDerivs'; 679 $license->source = 'https://creativecommons.org/licenses/by-nd/3.0/'; 680 $license->enabled = 1; 681 $license->version = '2010033100'; 682 $license->custom = 0; 683 $corelicenses[] = $license; 684 685 $license = new stdClass(); 686 $license->shortname = 'cc-nc-nd'; 687 $license->fullname = 'Creative Commons - No Commercial NoDerivs'; 688 $license->source = 'https://creativecommons.org/licenses/by-nc-nd/3.0/'; 689 $license->enabled = 1; 690 $license->version = '2010033100'; 691 $license->custom = 0; 692 $corelicenses[] = $license; 693 694 $license = new stdClass(); 695 $license->shortname = 'cc-nc'; 696 $license->fullname = 'Creative Commons - No Commercial'; 697 $license->source = 'https://creativecommons.org/licenses/by-nc/3.0/'; 698 $license->enabled = 1; 699 $license->version = '2010033100'; 700 $license->custom = 0; 701 $corelicenses[] = $license; 702 703 $license = new stdClass(); 704 $license->shortname = 'cc-nc-sa'; 705 $license->fullname = 'Creative Commons - No Commercial ShareAlike'; 706 $license->source = 'https://creativecommons.org/licenses/by-nc-sa/3.0/'; 707 $license->enabled = 1; 708 $license->version = '2010033100'; 709 $license->custom = 0; 710 $corelicenses[] = $license; 711 712 $license = new stdClass(); 713 $license->shortname = 'cc-sa'; 714 $license->fullname = 'Creative Commons - ShareAlike'; 715 $license->source = 'https://creativecommons.org/licenses/by-sa/3.0/'; 716 $license->enabled = 1; 717 $license->version = '2010033100'; 718 $license->custom = 0; 719 $corelicenses[] = $license; 720 721 foreach ($corelicenses as $corelicense) { 722 // Check for current license to maintain idempotence. 723 $currentlicense = $DB->get_record('license', ['shortname' => $corelicense->shortname]); 724 if (!empty($currentlicense)) { 725 $corelicense->id = $currentlicense->id; 726 // Remember if the license was enabled before upgrade. 727 $corelicense->enabled = $currentlicense->enabled; 728 $DB->update_record('license', $corelicense); 729 } else if (!isset($CFG->upgraderunning) || during_initial_install()) { 730 // Only install missing core licenses if not upgrading or during initial install. 731 $DB->insert_record('license', $corelicense); 732 } 733 } 734 735 // Add sortorder to all licenses. 736 $licenses = $DB->get_records('license'); 737 $sortorder = 1; 738 foreach ($licenses as $license) { 739 $license->sortorder = $sortorder++; 740 $DB->update_record('license', $license); 741 } 742 743 // Set the license config values, used by file repository for rendering licenses at front end. 744 $activelicenses = $DB->get_records_menu('license', ['enabled' => 1], 'id', 'id, shortname'); 745 set_config('licenses', implode(',', $activelicenses)); 746 747 $sitedefaultlicense = get_config('', 'sitedefaultlicense'); 748 if (empty($sitedefaultlicense) || !in_array($sitedefaultlicense, $activelicenses)) { 749 set_config('sitedefaultlicense', reset($activelicenses)); 750 } 751 } 752 753 /** 754 * Detects if the site may need to get the calendar events fixed or no. With optional output. 755 * 756 * @param bool $output true if the function must output information, false if not. 757 * @return bool true if the site needs to run the fixes, false if not. 758 */ 759 function upgrade_calendar_site_status(bool $output = true): bool { 760 global $DB; 761 762 // List of upgrade steps where the bug happened. 763 $badsteps = [ 764 '3.9.5' => '2020061504.08', 765 '3.10.2' => '2020110901.09', 766 '3.11dev' => '2021022600.02', 767 '4.0dev' => '2021052500.65', 768 ]; 769 770 // List of upgrade steps that ran the fixer. 771 $fixsteps = [ 772 '3.9.6+' => '2020061506.05', 773 '3.10.3+' => '2020110903.05', 774 '3.11dev' => '2021042100.02', 775 '4.0dev' => '2021052500.85', 776 ]; 777 778 $targetsteps = array_merge(array_values($badsteps), array_values( $fixsteps)); 779 list($insql, $inparams) = $DB->get_in_or_equal($targetsteps); 780 $foundsteps = $DB->get_fieldset_sql(" 781 SELECT DISTINCT version 782 FROM {upgrade_log} 783 WHERE plugin = 'core' 784 AND version " . $insql . " 785 ORDER BY version", $inparams); 786 787 // Analyse the found steps, to decide if the site needs upgrading or no. 788 $badfound = false; 789 $fixfound = false; 790 foreach ($foundsteps as $foundstep) { 791 $badfound = $badfound ?: array_search($foundstep, $badsteps, true); 792 $fixfound = $fixfound ?: array_search($foundstep, $fixsteps, true); 793 } 794 $needsfix = $badfound && !$fixfound; 795 796 // Let's output some textual information if required to. 797 if ($output) { 798 mtrace(""); 799 if ($badfound) { 800 mtrace("This site has executed the problematic upgrade step {$badsteps[$badfound]} present in {$badfound}."); 801 } else { 802 mtrace("Problematic upgrade steps were NOT found, site should be safe."); 803 } 804 if ($fixfound) { 805 mtrace("This site has executed the fix upgrade step {$fixsteps[$fixfound]} present in {$fixfound}."); 806 } else { 807 mtrace("Fix upgrade steps were NOT found."); 808 } 809 mtrace(""); 810 if ($needsfix) { 811 mtrace("This site NEEDS to run the calendar events fix!"); 812 mtrace(''); 813 mtrace("You can use this CLI tool or upgrade to a version of Moodle that includes"); 814 mtrace("the fix and will be executed as part of the normal upgrade procedure."); 815 mtrace("The following versions or up are known candidates to upgrade to:"); 816 foreach ($fixsteps as $key => $value) { 817 mtrace(" - {$key}: {$value}"); 818 } 819 mtrace(""); 820 } 821 } 822 return $needsfix; 823 } 824 825 /** 826 * Detects the calendar events needing to be fixed. With optional output. 827 * 828 * @param bool $output true if the function must output information, false if not. 829 * @return stdClass[] an array of event types (as keys) with total and bad counters, plus sql to retrieve them. 830 */ 831 function upgrade_calendar_events_status(bool $output = true): array { 832 global $DB; 833 834 // Calculate the list of standard (core) activity plugins. 835 $plugins = core_plugin_manager::standard_plugins_list('mod'); 836 $coremodules = "modulename IN ('" . implode("', '", $plugins) . "')"; 837 838 // Some query parts go here. 839 $brokenevents = "(userid = 0 AND (eventtype <> 'user' OR priority <> 0))"; // From the original bad upgrade step. 840 $standardevents = "(eventtype IN ('site', 'category', 'course', 'group', 'user') AND subscriptionid IS NULL)"; 841 $subscriptionevents = "(subscriptionid IS NOT NULL)"; 842 $overrideevents = "({$coremodules} AND priority IS NOT NULL)"; 843 $actionevents = "({$coremodules} AND instance > 0 and priority IS NULL)"; 844 $otherevents = "(NOT ({$standardevents} OR {$subscriptionevents} OR {$overrideevents} OR {$actionevents}))"; 845 846 // Detailed query template. 847 $detailstemplate = " 848 SELECT ##group## AS groupname, COUNT(1) AS count 849 FROM {event} 850 WHERE ##groupconditions## 851 GROUP BY ##group##"; 852 853 // Count total and potentially broken events. 854 $total = $DB->count_records_select('event', ''); 855 $totalbadsql = $brokenevents; 856 $totalbad = $DB->count_records_select('event', $totalbadsql); 857 858 // Standard events. 859 $standard = $DB->count_records_select('event', $standardevents); 860 $standardbadsql = "{$brokenevents} AND {$standardevents}"; 861 $standardbad = $DB->count_records_select('event', $standardbadsql); 862 $standarddetails = $DB->get_records_sql( 863 str_replace( 864 ['##group##', '##groupconditions##'], 865 ['eventtype', $standardbadsql], 866 $detailstemplate 867 ) 868 ); 869 array_walk($standarddetails, function (&$rec) { 870 $rec = $rec->groupname . ': ' . $rec->count; 871 }); 872 $standarddetails = $standarddetails ? '(' . implode(', ', $standarddetails) . ')' : '- all good!'; 873 874 // Subscription events. 875 $subscription = $DB->count_records_select('event', $subscriptionevents); 876 $subscriptionbadsql = "{$brokenevents} AND {$subscriptionevents}"; 877 $subscriptionbad = $DB->count_records_select('event', $subscriptionbadsql); 878 $subscriptiondetails = $DB->get_records_sql( 879 str_replace( 880 ['##group##', '##groupconditions##'], 881 ['eventtype', $subscriptionbadsql], 882 $detailstemplate 883 ) 884 ); 885 array_walk($subscriptiondetails, function (&$rec) { 886 $rec = $rec->groupname . ': ' . $rec->count; 887 }); 888 $subscriptiondetails = $subscriptiondetails ? '(' . implode(', ', $subscriptiondetails) . ')' : '- all good!'; 889 890 // Override events. 891 $override = $DB->count_records_select('event', $overrideevents); 892 $overridebadsql = "{$brokenevents} AND {$overrideevents}"; 893 $overridebad = $DB->count_records_select('event', $overridebadsql); 894 $overridedetails = $DB->get_records_sql( 895 str_replace( 896 ['##group##', '##groupconditions##'], 897 ['modulename', $overridebadsql], 898 $detailstemplate 899 ) 900 ); 901 array_walk($overridedetails, function (&$rec) { 902 $rec = $rec->groupname . ': ' . $rec->count; 903 }); 904 $overridedetails = $overridedetails ? '(' . implode(', ', $overridedetails) . ')' : '- all good!'; 905 906 // Action events. 907 $action = $DB->count_records_select('event', $actionevents); 908 $actionbadsql = "{$brokenevents} AND {$actionevents}"; 909 $actionbad = $DB->count_records_select('event', $actionbadsql); 910 $actiondetails = $DB->get_records_sql( 911 str_replace( 912 ['##group##', '##groupconditions##'], 913 ['modulename', $actionbadsql], 914 $detailstemplate 915 ) 916 ); 917 array_walk($actiondetails, function (&$rec) { 918 $rec = $rec->groupname . ': ' . $rec->count; 919 }); 920 $actiondetails = $actiondetails ? '(' . implode(', ', $actiondetails) . ')' : '- all good!'; 921 922 // Other events. 923 $other = $DB->count_records_select('event', $otherevents); 924 $otherbadsql = "{$brokenevents} AND {$otherevents}"; 925 $otherbad = $DB->count_records_select('event', $otherbadsql); 926 $otherdetails = $DB->get_records_sql( 927 str_replace( 928 ['##group##', '##groupconditions##'], 929 ['COALESCE(component, modulename)', $otherbadsql], 930 $detailstemplate 931 ) 932 ); 933 array_walk($otherdetails, function (&$rec) { 934 $rec = ($rec->groupname ?: 'unknown') . ': ' . $rec->count; 935 }); 936 $otherdetails = $otherdetails ? '(' . implode(', ', $otherdetails) . ')' : '- all good!'; 937 938 // Let's output some textual information if required to. 939 if ($output) { 940 mtrace(""); 941 mtrace("Totals: {$total} / {$totalbad} (total / wrong)"); 942 mtrace(" - standards events: {$standard} / {$standardbad} {$standarddetails}"); 943 mtrace(" - subscription events: {$subscription} / {$subscriptionbad} {$subscriptiondetails}"); 944 mtrace(" - override events: {$override} / {$overridebad} {$overridedetails}"); 945 mtrace(" - action events: {$action} / {$actionbad} {$actiondetails}"); 946 mtrace(" - other events: {$other} / {$otherbad} {$otherdetails}"); 947 mtrace(""); 948 } 949 950 return [ 951 'total' => (object)['count' => $total, 'bad' => $totalbad, 'sql' => $totalbadsql], 952 'standard' => (object)['count' => $standard, 'bad' => $standardbad, 'sql' => $standardbadsql], 953 'subscription' => (object)['count' => $subscription, 'bad' => $subscriptionbad, 'sql' => $subscriptionbadsql], 954 'override' => (object)['count' => $override, 'bad' => $overridebad, 'sql' => $overridebadsql], 955 'action' => (object)['count' => $action, 'bad' => $actionbad, 'sql' => $actionbadsql], 956 'other' => (object)['count' => $other, 'bad' => $otherbad, 'sql' => $otherbadsql], 957 ]; 958 } 959 960 /** 961 * Detects the calendar events needing to be fixed. With optional output. 962 * 963 * @param stdClass[] an array of event types (as keys) with total and bad counters, plus sql to retrieve them. 964 * @param bool $output true if the function must output information, false if not. 965 * @param int $maxseconds Number of seconds the function will run as max, with zero meaning no limit. 966 * @return bool true if the function has not finished fixing everything, false if it has finished. 967 */ 968 function upgrade_calendar_events_fix_remaining(array $info, bool $output = true, int $maxseconds = 0): bool { 969 global $DB; 970 971 upgrade_calendar_events_mtrace('', $output); 972 973 // Initial preparations. 974 $starttime = time(); 975 $endtime = $maxseconds ? ($starttime + $maxseconds) : 0; 976 977 // No bad events, or all bad events are "other" events, finished. 978 if ($info['total']->bad == 0 || $info['total']->bad == $info['other']->bad) { 979 return false; 980 } 981 982 // Let's fix overriden events first (they are the ones performing worse with the missing userid). 983 if ($info['override']->bad != 0) { 984 if (upgrade_calendar_override_events_fix($info['override'], $output, $endtime)) { 985 return true; // Not finished yet. 986 } 987 } 988 989 // Let's fix the subscription events (like standard ones, but with the event_subscriptions table). 990 if ($info['subscription']->bad != 0) { 991 if (upgrade_calendar_subscription_events_fix($info['subscription'], $output, $endtime)) { 992 return true; // Not finished yet. 993 } 994 } 995 996 // Let's fix the standard events (site, category, course, group). 997 if ($info['standard']->bad != 0) { 998 if (upgrade_calendar_standard_events_fix($info['standard'], $output, $endtime)) { 999 return true; // Not finished yet. 1000 } 1001 } 1002 1003 // Let's fix the action events (all them are "general" ones, not user-specific in core). 1004 if ($info['action']->bad != 0) { 1005 if (upgrade_calendar_action_events_fix($info['action'], $output, $endtime)) { 1006 return true; // Not finished yet. 1007 } 1008 } 1009 1010 // Have arrived here, finished! 1011 return false; 1012 } 1013 1014 /** 1015 * Wrapper over mtrace() to allow a few more things to be specified. 1016 * 1017 * @param string $string string to output. 1018 * @param bool $output true to perform the output, false to avoid it. 1019 */ 1020 function upgrade_calendar_events_mtrace(string $string, bool $output): void { 1021 static $cols = 0; 1022 1023 // No output, do nothing. 1024 if (!$output) { 1025 return; 1026 } 1027 1028 // Printing dots... let's output them slightly nicer. 1029 if ($string === '.') { 1030 $cols++; 1031 // Up to 60 cols. 1032 if ($cols < 60) { 1033 mtrace($string, ''); 1034 } else { 1035 mtrace($string); 1036 $cols = 0; 1037 } 1038 return; 1039 } 1040 1041 // Reset cols, have ended printing dots. 1042 if ($cols) { 1043 $cols = 0; 1044 mtrace(''); 1045 } 1046 1047 // Normal output. 1048 mtrace($string); 1049 } 1050 1051 /** 1052 * Get a valid editing teacher for a given courseid 1053 * 1054 * @param int $courseid The course to look for editing teachers. 1055 * @return int A user id of an editing teacher or, if missing, the admin userid. 1056 */ 1057 function upgrade_calendar_events_get_teacherid(int $courseid): int { 1058 1059 if ($context = context_course::instance($courseid, IGNORE_MISSING)) { 1060 if ($havemanage = get_users_by_capability($context, 'moodle/course:manageactivities', 'u.id')) { 1061 return array_keys($havemanage)[0]; 1062 } 1063 } 1064 return get_admin()->id; // Could not find a teacher, default to admin. 1065 } 1066 1067 /** 1068 * Detects the calendar standard events needing to be fixed. With optional output. 1069 * 1070 * @param stdClass $info an object with total and bad counters, plus sql to retrieve them. 1071 * @param bool $output true if the function must output information, false if not. 1072 * @param int $endtime cutoff time when the process must stop (0 means no cutoff). 1073 * @return bool true if the function has not finished fixing everything, false if it has finished. 1074 */ 1075 function upgrade_calendar_standard_events_fix(stdClass $info, bool $output = true, int $endtime = 0): bool { 1076 global $DB; 1077 1078 $return = false; // Let's assume the function is going to finish by default. 1079 $status = "Finished!"; // To decide the message to be presented on return. 1080 1081 upgrade_calendar_events_mtrace('Processing standard events', $output); 1082 1083 $rs = $DB->get_recordset_sql(" 1084 SELECT DISTINCT eventtype, courseid 1085 FROM {event} 1086 WHERE {$info->sql}"); 1087 1088 foreach ($rs as $record) { 1089 switch ($record->eventtype) { 1090 case 'site': 1091 case 'category': 1092 // These are created by admin. 1093 $DB->set_field('event', 'userid', get_admin()->id, ['eventtype' => $record->eventtype]); 1094 break; 1095 case 'course': 1096 case 'group': 1097 // These are created by course teacher. 1098 $DB->set_field('event', 'userid', upgrade_calendar_events_get_teacherid($record->courseid), 1099 ['eventtype' => $record->eventtype, 'courseid' => $record->courseid]); 1100 break; 1101 } 1102 1103 // Cutoff time, let's exit. 1104 if ($endtime && $endtime <= time()) { 1105 $status = 'Remaining standard events pending'; 1106 $return = true; // Not finished yet. 1107 break; 1108 } 1109 upgrade_calendar_events_mtrace('.', $output); 1110 } 1111 $rs->close(); 1112 upgrade_calendar_events_mtrace($status, $output); 1113 upgrade_calendar_events_mtrace('', $output); 1114 return $return; 1115 } 1116 1117 /** 1118 * Detects the calendar subscription events needing to be fixed. With optional output. 1119 * 1120 * @param stdClass $info an object with total and bad counters, plus sql to retrieve them. 1121 * @param bool $output true if the function must output information, false if not. 1122 * @param int $endtime cutoff time when the process must stop (0 means no cutoff). 1123 * @return bool true if the function has not finished fixing everything, false if it has finished. 1124 */ 1125 function upgrade_calendar_subscription_events_fix(stdClass $info, bool $output = true, int $endtime = 0): bool { 1126 global $DB; 1127 1128 $return = false; // Let's assume the function is going to finish by default. 1129 $status = "Finished!"; // To decide the message to be presented on return. 1130 1131 upgrade_calendar_events_mtrace('Processing subscription events', $output); 1132 1133 $rs = $DB->get_recordset_sql(" 1134 SELECT DISTINCT subscriptionid AS id 1135 FROM {event} 1136 WHERE {$info->sql}"); 1137 1138 foreach ($rs as $subscription) { 1139 // Subscriptions can be site or category level, let's put the admin as userid. 1140 // (note that "user" subscription weren't deleted so there is nothing to recover with them. 1141 $DB->set_field('event_subscriptions', 'userid', get_admin()->id, ['id' => $subscription->id]); 1142 $DB->set_field('event', 'userid', get_admin()->id, ['subscriptionid' => $subscription->id]); 1143 1144 // Cutoff time, let's exit. 1145 if ($endtime && $endtime <= time()) { 1146 $status = 'Remaining subscription events pending'; 1147 $return = true; // Not finished yet. 1148 break; 1149 } 1150 upgrade_calendar_events_mtrace('.', $output); 1151 } 1152 $rs->close(); 1153 upgrade_calendar_events_mtrace($status, $output); 1154 upgrade_calendar_events_mtrace('', $output); 1155 return $return; 1156 } 1157 1158 /** 1159 * Detects the calendar action events needing to be fixed. With optional output. 1160 * 1161 * @param stdClass $info an object with total and bad counters, plus sql to retrieve them. 1162 * @param bool $output true if the function must output information, false if not. 1163 * @param int $endtime cutoff time when the process must stop (0 means no cutoff). 1164 * @return bool true if the function has not finished fixing everything, false if it has finished. 1165 */ 1166 function upgrade_calendar_action_events_fix(stdClass $info, bool $output = true, int $endtime = 0): bool { 1167 global $DB; 1168 1169 $return = false; // Let's assume the function is going to finish by default. 1170 $status = "Finished!"; // To decide the message to be presented on return. 1171 1172 upgrade_calendar_events_mtrace('Processing action events', $output); 1173 1174 $rs = $DB->get_recordset_sql(" 1175 SELECT DISTINCT modulename, instance, courseid 1176 FROM {event} 1177 WHERE {$info->sql}"); 1178 1179 foreach ($rs as $record) { 1180 // These are created by course teacher. 1181 $DB->set_field('event', 'userid', upgrade_calendar_events_get_teacherid($record->courseid), 1182 ['modulename' => $record->modulename, 'instance' => $record->instance, 'courseid' => $record->courseid]); 1183 1184 // Cutoff time, let's exit. 1185 if ($endtime && $endtime <= time()) { 1186 $status = 'Remaining action events pending'; 1187 $return = true; // Not finished yet. 1188 break; 1189 } 1190 upgrade_calendar_events_mtrace('.', $output); 1191 } 1192 $rs->close(); 1193 upgrade_calendar_events_mtrace($status, $output); 1194 upgrade_calendar_events_mtrace('', $output); 1195 return $return; 1196 } 1197 1198 /** 1199 * Detects the calendar override events needing to be fixed. With optional output. 1200 * 1201 * @param stdClass $info an object with total and bad counters, plus sql to retrieve them. 1202 * @param bool $output true if the function must output information, false if not. 1203 * @param int $endtime cutoff time when the process must stop (0 means no cutoff). 1204 * @return bool true if the function has not finished fixing everything, false if it has finished. 1205 */ 1206 function upgrade_calendar_override_events_fix(stdClass $info, bool $output = true, int $endtime = 0): bool { 1207 global $CFG, $DB; 1208 1209 include_once($CFG->dirroot. '/course/lib.php'); 1210 include_once($CFG->dirroot. '/mod/assign/lib.php'); 1211 include_once($CFG->dirroot. '/mod/assign/locallib.php'); 1212 include_once($CFG->dirroot. '/mod/lesson/lib.php'); 1213 include_once($CFG->dirroot. '/mod/lesson/locallib.php'); 1214 include_once($CFG->dirroot. '/mod/quiz/lib.php'); 1215 include_once($CFG->dirroot. '/mod/quiz/locallib.php'); 1216 1217 $return = false; // Let's assume the function is going to finish by default. 1218 $status = "Finished!"; // To decide the message to be presented on return. 1219 1220 upgrade_calendar_events_mtrace('Processing override events', $output); 1221 1222 $rs = $DB->get_recordset_sql(" 1223 SELECT DISTINCT modulename, instance 1224 FROM {event} 1225 WHERE {$info->sql}"); 1226 1227 foreach ($rs as $module) { 1228 // Remove all the records from the events table for the module. 1229 $DB->delete_records('event', ['modulename' => $module->modulename, 'instance' => $module->instance]); 1230 1231 // Get the activity record. 1232 if (!$activityrecord = $DB->get_record($module->modulename, ['id' => $module->instance])) { 1233 // Orphaned calendar event (activity doesn't exists), skip. 1234 continue; 1235 } 1236 1237 // Let's rebuild it by calling to each module API. 1238 switch ($module->modulename) { 1239 case 'assign'; 1240 if (function_exists('assign_prepare_update_events')) { 1241 assign_prepare_update_events($activityrecord); 1242 } 1243 break; 1244 case 'lesson': 1245 if (function_exists('lesson_update_events')) { 1246 lesson_update_events($activityrecord); 1247 } 1248 break; 1249 case 'quiz': 1250 if (function_exists('quiz_update_events')) { 1251 quiz_update_events($activityrecord); 1252 } 1253 break; 1254 } 1255 1256 // Sometimes, some (group) overrides are created without userid, when that happens, they deserve 1257 // some user (teacher or admin). This doesn't affect to groups calendar events behaviour, 1258 // but allows counters to detect already processed group overrides and makes things 1259 // consistent. 1260 $DB->set_field_select('event', 'userid', upgrade_calendar_events_get_teacherid($activityrecord->course), 1261 'modulename = ? AND instance = ? and priority != 0 and userid = 0', 1262 ['modulename' => $module->modulename, 'instance' => $module->instance]); 1263 1264 // Cutoff time, let's exit. 1265 if ($endtime && $endtime <= time()) { 1266 $status = 'Remaining override events pending'; 1267 $return = true; // Not finished yet. 1268 break; 1269 } 1270 upgrade_calendar_events_mtrace('.', $output); 1271 } 1272 $rs->close(); 1273 upgrade_calendar_events_mtrace($status, $output); 1274 upgrade_calendar_events_mtrace('', $output); 1275 return $return; 1276 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body