Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
/lib/db/ -> upgrade.php (source)

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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   * This file keeps track of upgrades to Moodle.
  19   *
  20   * Sometimes, changes between versions involve
  21   * alterations to database structures and other
  22   * major things that may break installations.
  23   *
  24   * The upgrade function in this file will attempt
  25   * to perform all the necessary actions to upgrade
  26   * your older installation to the current version.
  27   *
  28   * If there's something it cannot do itself, it
  29   * will tell you what you need to do.
  30   *
  31   * The commands in here will all be database-neutral,
  32   * using the methods of database_manager class
  33   *
  34   * Please do not forget to use upgrade_set_timeout()
  35   * before any action that may take longer time to finish.
  36   *
  37   * @package   core_install
  38   * @category  upgrade
  39   * @copyright 2006 onwards Martin Dougiamas  http://dougiamas.com
  40   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   */
  42  
  43  defined('MOODLE_INTERNAL') || die();
  44  
  45  /**
  46   * Main upgrade tasks to be executed on Moodle version bump
  47   *
  48   * This function is automatically executed after one bump in the Moodle core
  49   * version is detected. It's in charge of performing the required tasks
  50   * to raise core from the previous version to the next one.
  51   *
  52   * It's a collection of ordered blocks of code, named "upgrade steps",
  53   * each one performing one isolated (from the rest of steps) task. Usually
  54   * tasks involve creating new DB objects or performing manipulation of the
  55   * information for cleanup/fixup purposes.
  56   *
  57   * Each upgrade step has a fixed structure, that can be summarised as follows:
  58   *
  59   * if ($oldversion < XXXXXXXXXX.XX) {
  60   *     // Explanation of the update step, linking to issue in the Tracker if necessary
  61   *     upgrade_set_timeout(XX); // Optional for big tasks
  62   *     // Code to execute goes here, usually the XMLDB Editor will
  63   *     // help you here. See {@link https://moodledev.io/general/development/tools/xmldb}.
  64   *     upgrade_main_savepoint(true, XXXXXXXXXX.XX);
  65   * }
  66   *
  67   * All plugins within Moodle (modules, blocks, reports...) support the existence of
  68   * their own upgrade.php file, using the "Frankenstyle" component name as
  69   * defined at {@link https://moodledev.io/general/development/policies/codingstyle/frankenstyle}, for example:
  70   *     - {@link xmldb_page_upgrade($oldversion)}. (modules don't require the plugintype ("mod_") to be used.
  71   *     - {@link xmldb_auth_manual_upgrade($oldversion)}.
  72   *     - {@link xmldb_workshopform_accumulative_upgrade($oldversion)}.
  73   *     - ....
  74   *
  75   * In order to keep the contents of this file reduced, it's allowed to create some helper
  76   * functions to be used here in the {@link upgradelib.php} file at the same directory. Note
  77   * that such a file must be manually included from upgrade.php, and there are some restrictions
  78   * about what can be used within it.
  79   *
  80   * For more information, take a look to the documentation available:
  81   *     - Data definition API: {@link https://moodledev.io/docs/apis/core/dml/ddl}
  82   *     - Upgrade API: {@link https://moodledev.io/docs/guides/upgrade}
  83   *
  84   * @param int $oldversion
  85   * @return bool always true
  86   */
  87  function xmldb_main_upgrade($oldversion) {
  88      global $CFG, $DB;
  89  
  90      require_once($CFG->libdir.'/db/upgradelib.php'); // Core Upgrade-related functions.
  91  
  92      $dbman = $DB->get_manager(); // Loads ddl manager and xmldb classes.
  93  
  94      // Always keep this upgrade step with version being the minimum
  95      // allowed version to upgrade from (v3.9.0 right now).
  96      if ($oldversion < 2020061500) {
  97          // Just in case somebody hacks upgrade scripts or env, we really can not continue.
  98          echo("You need to upgrade to 3.9.x or higher first!\n");
  99          exit(1);
 100          // Note this savepoint is 100% unreachable, but needed to pass the upgrade checks.
 101          upgrade_main_savepoint(true, 2020061500);
 102      }
 103  
 104      // Automatically generated Moodle v3.9.0 release upgrade line.
 105      // Put any upgrade step following this.
 106  
 107      if ($oldversion < 2020061500.02) {
 108          // Update default digital age consent map according to the current legislation on each country.
 109  
 110          // The default age of digital consent map for 38 and below.
 111          $oldageofdigitalconsentmap = implode(PHP_EOL, [
 112              '*, 16',
 113              'AT, 14',
 114              'ES, 14',
 115              'US, 13'
 116          ]);
 117  
 118          // Check if the current age of digital consent map matches the old one.
 119          if (get_config('moodle', 'agedigitalconsentmap') === $oldageofdigitalconsentmap) {
 120              // If the site is still using the old defaults, upgrade to the new default.
 121              $ageofdigitalconsentmap = implode(PHP_EOL, [
 122                  '*, 16',
 123                  'AT, 14',
 124                  'BE, 13',
 125                  'BG, 14',
 126                  'CY, 14',
 127                  'CZ, 15',
 128                  'DK, 13',
 129                  'EE, 13',
 130                  'ES, 14',
 131                  'FI, 13',
 132                  'FR, 15',
 133                  'GB, 13',
 134                  'GR, 15',
 135                  'IT, 14',
 136                  'LT, 14',
 137                  'LV, 13',
 138                  'MT, 13',
 139                  'NO, 13',
 140                  'PT, 13',
 141                  'SE, 13',
 142                  'US, 13'
 143              ]);
 144              set_config('agedigitalconsentmap', $ageofdigitalconsentmap);
 145          }
 146  
 147          upgrade_main_savepoint(true, 2020061500.02);
 148      }
 149  
 150      if ($oldversion < 2020062600.01) {
 151          // Add index to the token field in the external_tokens table.
 152          $table = new xmldb_table('external_tokens');
 153          $index = new xmldb_index('token', XMLDB_INDEX_NOTUNIQUE, ['token']);
 154  
 155          if (!$dbman->index_exists($table, $index)) {
 156              $dbman->add_index($table, $index);
 157          }
 158  
 159          upgrade_main_savepoint(true, 2020062600.01);
 160      }
 161  
 162      if ($oldversion < 2020071100.01) {
 163          // Clean up completion criteria records referring to NULL course prerequisites.
 164          $select = 'criteriatype = :type AND courseinstance IS NULL';
 165          $params = ['type' => 8]; // COMPLETION_CRITERIA_TYPE_COURSE.
 166  
 167          $DB->delete_records_select('course_completion_criteria', $select, $params);
 168  
 169          // Main savepoint reached.
 170          upgrade_main_savepoint(true, 2020071100.01);
 171      }
 172  
 173      if ($oldversion < 2020072300.01) {
 174          // Restore and set the guest user if it has been previously removed via GDPR, or set to an nonexistent
 175          // user account.
 176          $currentguestuser = $DB->get_record('user', array('id' => $CFG->siteguest));
 177  
 178          if (!$currentguestuser) {
 179              if (!$guest = $DB->get_record('user', array('username' => 'guest', 'mnethostid' => $CFG->mnet_localhost_id))) {
 180                  // Create a guest user account.
 181                  $guest = new stdClass();
 182                  $guest->auth        = 'manual';
 183                  $guest->username    = 'guest';
 184                  $guest->password    = hash_internal_user_password('guest');
 185                  $guest->firstname   = get_string('guestuser');
 186                  $guest->lastname    = ' ';
 187                  $guest->email       = 'root@localhost';
 188                  $guest->description = get_string('guestuserinfo');
 189                  $guest->mnethostid  = $CFG->mnet_localhost_id;
 190                  $guest->confirmed   = 1;
 191                  $guest->lang        = $CFG->lang;
 192                  $guest->timemodified= time();
 193                  $guest->id = $DB->insert_record('user', $guest);
 194              }
 195              // Set the guest user.
 196              set_config('siteguest', $guest->id);
 197          }
 198  
 199          // Main savepoint reached.
 200          upgrade_main_savepoint(true, 2020072300.01);
 201      }
 202  
 203      if ($oldversion < 2021052500.01) {
 204          // Delete all user evidence files from users that have been deleted.
 205          $sql = "SELECT DISTINCT f.*
 206                    FROM {files} f
 207               LEFT JOIN {context} c ON f.contextid = c.id
 208                   WHERE f.component = :component
 209                     AND f.filearea = :filearea
 210                     AND c.id IS NULL";
 211          $stalefiles = $DB->get_records_sql($sql, ['component' => 'core_competency', 'filearea' => 'userevidence']);
 212  
 213          $fs = get_file_storage();
 214          foreach ($stalefiles as $stalefile) {
 215              $fs->get_file_instance($stalefile)->delete();
 216          }
 217  
 218          upgrade_main_savepoint(true, 2021052500.01);
 219      }
 220  
 221      if ($oldversion < 2021052500.02) {
 222  
 223          // Define field timecreated to be added to task_adhoc.
 224          $table = new xmldb_table('task_adhoc');
 225          $field = new xmldb_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'blocking');
 226  
 227          // Conditionally launch add field timecreated.
 228          if (!$dbman->field_exists($table, $field)) {
 229              $dbman->add_field($table, $field);
 230          }
 231  
 232          // Main savepoint reached.
 233          upgrade_main_savepoint(true, 2021052500.02);
 234      }
 235  
 236      if ($oldversion < 2021052500.04) {
 237          // Define field metadatasettings to be added to h5p_libraries.
 238          $table = new xmldb_table('h5p_libraries');
 239          $field = new xmldb_field('metadatasettings', XMLDB_TYPE_TEXT, null, null, null, null, null, 'coreminor');
 240  
 241          // Conditionally launch add field metadatasettings.
 242          if (!$dbman->field_exists($table, $field)) {
 243              $dbman->add_field($table, $field);
 244          }
 245  
 246          // Get installed library files that have no metadata settings value.
 247          $params = [
 248              'component' => 'core_h5p',
 249              'filearea' => 'libraries',
 250              'filename' => 'library.json',
 251          ];
 252          $sql = "SELECT l.id, f.id as fileid
 253                    FROM {files} f
 254               LEFT JOIN {h5p_libraries} l ON f.itemid = l.id
 255                   WHERE f.component = :component
 256                         AND f.filearea = :filearea
 257                         AND f.filename = :filename";
 258          $libraries = $DB->get_records_sql($sql, $params);
 259  
 260          // Update metadatasettings field when the attribute is present in the library.json file.
 261          $fs = get_file_storage();
 262          foreach ($libraries as $library) {
 263              $jsonfile = $fs->get_file_by_id($library->fileid);
 264              $jsoncontent = json_decode($jsonfile->get_content());
 265              if (isset($jsoncontent->metadataSettings)) {
 266                  unset($library->fileid);
 267                  $library->metadatasettings = json_encode($jsoncontent->metadataSettings);
 268                  $DB->update_record('h5p_libraries', $library);
 269              }
 270          }
 271  
 272          // Main savepoint reached.
 273          upgrade_main_savepoint(true, 2021052500.04);
 274      }
 275  
 276      if ($oldversion < 2021052500.05) {
 277          // Define fields to be added to task_scheduled.
 278          $table = new xmldb_table('task_scheduled');
 279          $field = new xmldb_field('timestarted', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'disabled');
 280          if (!$dbman->field_exists($table, $field)) {
 281              $dbman->add_field($table, $field);
 282          }
 283          $field = new xmldb_field('hostname', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'timestarted');
 284          if (!$dbman->field_exists($table, $field)) {
 285              $dbman->add_field($table, $field);
 286          }
 287          $field = new xmldb_field('pid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'hostname');
 288          if (!$dbman->field_exists($table, $field)) {
 289              $dbman->add_field($table, $field);
 290          }
 291  
 292          // Define fields to be added to task_adhoc.
 293          $table = new xmldb_table('task_adhoc');
 294          $field = new xmldb_field('timestarted', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'blocking');
 295          if (!$dbman->field_exists($table, $field)) {
 296              $dbman->add_field($table, $field);
 297          }
 298          $field = new xmldb_field('hostname', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'timestarted');
 299          if (!$dbman->field_exists($table, $field)) {
 300              $dbman->add_field($table, $field);
 301          }
 302          $field = new xmldb_field('pid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'hostname');
 303          if (!$dbman->field_exists($table, $field)) {
 304              $dbman->add_field($table, $field);
 305          }
 306  
 307          // Define fields to be added to task_log.
 308          $table = new xmldb_table('task_log');
 309          $field = new xmldb_field('hostname', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'output');
 310          if (!$dbman->field_exists($table, $field)) {
 311              $dbman->add_field($table, $field);
 312          }
 313          $field = new xmldb_field('pid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'hostname');
 314          if (!$dbman->field_exists($table, $field)) {
 315              $dbman->add_field($table, $field);
 316          }
 317  
 318          // Main savepoint reached.
 319          upgrade_main_savepoint(true, 2021052500.05);
 320      }
 321  
 322      if ($oldversion < 2021052500.06) {
 323          // Define table to store virus infected details.
 324          $table = new xmldb_table('infected_files');
 325  
 326          // Adding fields to table infected_files.
 327          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 328          $table->add_field('filename', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
 329          $table->add_field('quarantinedfile', XMLDB_TYPE_TEXT, null, null, null, null, null);
 330          $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 331          $table->add_field('reason', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
 332          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 333  
 334          // Adding keys to table infected_files.
 335          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
 336          $table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
 337  
 338          // Conditionally launch create table for infected_files.
 339          if (!$dbman->table_exists($table)) {
 340              $dbman->create_table($table);
 341          }
 342          upgrade_main_savepoint(true, 2021052500.06);
 343      }
 344  
 345      if ($oldversion < 2021052500.13) {
 346          // Remove all the files with component='core_h5p' and filearea='editor' because they won't be used anymore.
 347          $fs = get_file_storage();
 348          $syscontext = context_system::instance();
 349          $fs->delete_area_files($syscontext->id, 'core_h5p', 'editor');
 350  
 351          // Main savepoint reached.
 352          upgrade_main_savepoint(true, 2021052500.13);
 353      }
 354  
 355      if ($oldversion < 2021052500.15) {
 356          // Copy From id captures the id of the source course when a new course originates from a restore
 357          // of another course on the same site.
 358          $table = new xmldb_table('course');
 359          $field = new xmldb_field('originalcourseid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
 360  
 361          if (!$dbman->field_exists($table, $field)) {
 362              $dbman->add_field($table, $field);
 363          }
 364  
 365          // Main savepoint reached.
 366          upgrade_main_savepoint(true, 2021052500.15);
 367      }
 368  
 369      if ($oldversion < 2021052500.19) {
 370          // Define table oauth2_refresh_token to be created.
 371          $table = new xmldb_table('oauth2_refresh_token');
 372  
 373          // Adding fields to table oauth2_refresh_token.
 374          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 375          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 376          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 377          $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 378          $table->add_field('issuerid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 379          $table->add_field('token', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
 380          $table->add_field('scopehash', XMLDB_TYPE_CHAR, 40, null, XMLDB_NOTNULL, null, null);
 381  
 382          // Adding keys to table oauth2_refresh_token.
 383          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
 384          $table->add_key('issueridkey', XMLDB_KEY_FOREIGN, ['issuerid'], 'oauth2_issuer', ['id']);
 385          $table->add_key('useridkey', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
 386  
 387          // Adding indexes to table oauth2_refresh_token.
 388          $table->add_index('userid-issuerid-scopehash', XMLDB_INDEX_UNIQUE, array('userid', 'issuerid', 'scopehash'));
 389  
 390          // Conditionally launch create table for oauth2_refresh_token.
 391          if (!$dbman->table_exists($table)) {
 392              $dbman->create_table($table);
 393          }
 394  
 395          // Main savepoint reached.
 396          upgrade_main_savepoint(true, 2021052500.19);
 397      }
 398  
 399      if ($oldversion < 2021052500.20) {
 400  
 401          // Define index modulename-instance-eventtype (not unique) to be added to event.
 402          $table = new xmldb_table('event');
 403          $index = new xmldb_index('modulename-instance-eventtype', XMLDB_INDEX_NOTUNIQUE, ['modulename', 'instance', 'eventtype']);
 404  
 405          // Conditionally launch add index modulename-instance-eventtype.
 406          if (!$dbman->index_exists($table, $index)) {
 407              $dbman->add_index($table, $index);
 408          }
 409  
 410          // Define index modulename-instance (not unique) to be dropped form event.
 411          $table = new xmldb_table('event');
 412          $index = new xmldb_index('modulename-instance', XMLDB_INDEX_NOTUNIQUE, ['modulename', 'instance']);
 413  
 414          // Conditionally launch drop index modulename-instance.
 415          if ($dbman->index_exists($table, $index)) {
 416              $dbman->drop_index($table, $index);
 417          }
 418  
 419          // Main savepoint reached.
 420          upgrade_main_savepoint(true, 2021052500.20);
 421      }
 422  
 423      if ($oldversion < 2021052500.24) {
 424          // Define fields tutorial and example to be added to h5p_libraries.
 425          $table = new xmldb_table('h5p_libraries');
 426  
 427          // Add tutorial field.
 428          $field = new xmldb_field('tutorial', XMLDB_TYPE_TEXT, null, null, null, null, null, 'metadatasettings');
 429          if (!$dbman->field_exists($table, $field)) {
 430              $dbman->add_field($table, $field);
 431          }
 432  
 433          // Add example field.
 434          $field = new xmldb_field('example', XMLDB_TYPE_TEXT, null, null, null, null, null, 'tutorial');
 435  
 436          if (!$dbman->field_exists($table, $field)) {
 437              $dbman->add_field($table, $field);
 438          }
 439  
 440          // Main savepoint reached.
 441          upgrade_main_savepoint(true, 2021052500.24);
 442      }
 443  
 444      if ($oldversion < 2021052500.26) {
 445          // Delete orphaned course_modules_completion rows; these were not deleted properly
 446          // by remove_course_contents function.
 447          $DB->delete_records_select('course_modules_completion', "
 448                  NOT EXISTS (
 449                          SELECT 1
 450                            FROM {course_modules} cm
 451                           WHERE cm.id = {course_modules_completion}.coursemoduleid
 452                  )");
 453          upgrade_main_savepoint(true, 2021052500.26);
 454      }
 455  
 456      if ($oldversion < 2021052500.27) {
 457          // Script to fix incorrect records of "hidden" field in existing grade items.
 458          $sql = "SELECT cm.instance, cm.course
 459                    FROM {course_modules} cm
 460                    JOIN {modules} m ON m.id = cm.module
 461                   WHERE m.name = :module AND cm.visible = :visible";
 462          $hidequizlist = $DB->get_recordset_sql($sql, ['module' => 'quiz', 'visible' => 0]);
 463  
 464          foreach ($hidequizlist as $hidequiz) {
 465              $params = [
 466                  'itemmodule'    => 'quiz',
 467                  'courseid'      => $hidequiz->course,
 468                  'iteminstance'  => $hidequiz->instance,
 469              ];
 470  
 471              $DB->set_field('grade_items', 'hidden', 1, $params);
 472          }
 473          $hidequizlist->close();
 474  
 475          upgrade_main_savepoint(true, 2021052500.27);
 476      }
 477  
 478      if ($oldversion < 2021052500.29) {
 479          // Get the current guest user which is also set as 'deleted'.
 480          $guestuser = $DB->get_record('user', ['id' => $CFG->siteguest, 'deleted' => 1]);
 481          // If there is a deleted guest user, reset the user to not be deleted and make sure the related
 482          // user context exists.
 483          if ($guestuser) {
 484              $guestuser->deleted = 0;
 485              $DB->update_record('user', $guestuser);
 486  
 487              // Get the guest user context.
 488              $guestusercontext = $DB->get_record('context',
 489                  ['contextlevel' => CONTEXT_USER, 'instanceid' => $guestuser->id]);
 490  
 491              // If the guest user context does not exist, create it.
 492              if (!$guestusercontext) {
 493                  $record = new stdClass();
 494                  $record->contextlevel = CONTEXT_USER;
 495                  $record->instanceid = $guestuser->id;
 496                  $record->depth = 0;
 497                  // The path is not known before insert.
 498                  $record->path = null;
 499                  $record->locked = 0;
 500  
 501                  $record->id = $DB->insert_record('context', $record);
 502  
 503                  // Update the path.
 504                  $record->path = '/' . SYSCONTEXTID . '/' . $record->id;
 505                  $record->depth = substr_count($record->path, '/');
 506                  $DB->update_record('context', $record);
 507              }
 508          }
 509  
 510          // Main savepoint reached.
 511          upgrade_main_savepoint(true, 2021052500.29);
 512      }
 513  
 514      if ($oldversion < 2021052500.30) {
 515          // Reset analytics model output dir if it's the default value.
 516          $modeloutputdir = get_config('analytics', 'modeloutputdir');
 517          if (strcasecmp($modeloutputdir, $CFG->dataroot . DIRECTORY_SEPARATOR . 'models') == 0) {
 518              set_config('modeloutputdir', '', 'analytics');
 519          }
 520  
 521          // Main savepoint reached.
 522          upgrade_main_savepoint(true, 2021052500.30);
 523      }
 524  
 525      if ($oldversion < 2021052500.32) {
 526          // Define field downloadcontent to be added to course.
 527          $table = new xmldb_table('course');
 528          $field = new xmldb_field('downloadcontent', XMLDB_TYPE_INTEGER, '1', null, null, null, null, 'visibleold');
 529  
 530          if (!$dbman->field_exists($table, $field)) {
 531              $dbman->add_field($table, $field);
 532          }
 533  
 534          // Main savepoint reached.
 535          upgrade_main_savepoint(true, 2021052500.32);
 536      }
 537  
 538      if ($oldversion < 2021052500.33) {
 539          $table = new xmldb_table('badge_backpack');
 540  
 541          // There is no key_exists, so test the equivalent index.
 542          $oldindex = new xmldb_index('backpackcredentials', XMLDB_KEY_UNIQUE, ['userid', 'externalbackpackid']);
 543          if (!$dbman->index_exists($table, $oldindex)) {
 544              // All external backpack providers/hosts are now exclusively stored in badge_external_backpack.
 545              // All credentials are stored in badge_backpack and are unique per user, backpack.
 546              $uniquekey = new xmldb_key('backpackcredentials', XMLDB_KEY_UNIQUE, ['userid', 'externalbackpackid']);
 547              $dbman->add_key($table, $uniquekey);
 548          }
 549  
 550          // Drop the password field as this is moved to badge_backpack.
 551          $table = new xmldb_table('badge_external_backpack');
 552          $field = new xmldb_field('password', XMLDB_TYPE_CHAR, '50');
 553          if ($dbman->field_exists($table, $field)) {
 554              // If there is a current backpack set then copy it across to the new structure.
 555              if ($CFG->badges_defaultissuercontact) {
 556                  // Get the currently used site backpacks.
 557                  $records = $DB->get_records_select('badge_external_backpack', "password IS NOT NULL AND password != ''");
 558                  $backpack = [
 559                      'userid' => '0',
 560                      'email' => $CFG->badges_defaultissuercontact,
 561                      'backpackuid' => -1
 562                  ];
 563  
 564                  // Create records corresponding to the site backpacks.
 565                  foreach ($records as $record) {
 566                      $backpack['password'] = $record->password;
 567                      $backpack['externalbackpackid'] = $record->id;
 568                      $DB->insert_record('badge_backpack', (object) $backpack);
 569                  }
 570              }
 571  
 572              $dbman->drop_field($table, $field);
 573          }
 574  
 575          // Main savepoint reached.
 576          upgrade_main_savepoint(true, 2021052500.33);
 577      }
 578  
 579      if ($oldversion < 2021052500.36) {
 580          // Define table payment_accounts to be created.
 581          $table = new xmldb_table('payment_accounts');
 582  
 583          // Adding fields to table payment_accounts.
 584          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 585          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
 586          $table->add_field('idnumber', XMLDB_TYPE_CHAR, '100', null, null, null, null);
 587          $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 588          $table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
 589          $table->add_field('archived', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
 590          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
 591          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
 592  
 593          // Adding keys to table payment_accounts.
 594          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
 595  
 596          // Conditionally launch create table for payment_accounts.
 597          if (!$dbman->table_exists($table)) {
 598              $dbman->create_table($table);
 599          }
 600  
 601          // Define table payment_gateways to be created.
 602          $table = new xmldb_table('payment_gateways');
 603  
 604          // Adding fields to table payment_gateways.
 605          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 606          $table->add_field('accountid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 607          $table->add_field('gateway', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
 608          $table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1');
 609          $table->add_field('config', XMLDB_TYPE_TEXT, null, null, null, null, null);
 610          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 611          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 612  
 613          // Adding keys to table payment_gateways.
 614          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
 615          $table->add_key('accountid', XMLDB_KEY_FOREIGN, ['accountid'], 'payment_accounts', ['id']);
 616  
 617          // Conditionally launch create table for payment_gateways.
 618          if (!$dbman->table_exists($table)) {
 619              $dbman->create_table($table);
 620          }
 621  
 622          // Define table payments to be created.
 623          $table = new xmldb_table('payments');
 624  
 625          // Adding fields to table payments.
 626          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 627          $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
 628          $table->add_field('paymentarea', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null);
 629          $table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 630          $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 631          $table->add_field('amount', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, null);
 632          $table->add_field('currency', XMLDB_TYPE_CHAR, '3', null, XMLDB_NOTNULL, null, null);
 633          $table->add_field('accountid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 634          $table->add_field('gateway', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
 635          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 636          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 637  
 638          // Adding keys to table payments.
 639          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
 640          $table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
 641          $table->add_key('accountid', XMLDB_KEY_FOREIGN, ['accountid'], 'payment_accounts', ['id']);
 642  
 643          // Adding indexes to table payments.
 644          $table->add_index('gateway', XMLDB_INDEX_NOTUNIQUE, ['gateway']);
 645          $table->add_index('component-paymentarea-itemid', XMLDB_INDEX_NOTUNIQUE, ['component', 'paymentarea', 'itemid']);
 646  
 647          // Conditionally launch create table for payments.
 648          if (!$dbman->table_exists($table)) {
 649              $dbman->create_table($table);
 650          }
 651  
 652          // Main savepoint reached.
 653          upgrade_main_savepoint(true, 2021052500.36);
 654      }
 655  
 656      if ($oldversion < 2021052500.42) {
 657          // Get all lessons that are set with a completion criteria of 'requires grade' but with no grade type set.
 658          $sql = "SELECT cm.id
 659                    FROM {course_modules} cm
 660                    JOIN {lesson} l ON l.id = cm.instance
 661                    JOIN {modules} m ON m.id = cm.module
 662                   WHERE m.name = :name AND cm.completiongradeitemnumber IS NOT NULL AND l.grade = :grade";
 663  
 664          do {
 665              if ($invalidconfigrations = $DB->get_records_sql($sql, ['name' => 'lesson', 'grade' => 0], 0, 1000)) {
 666                  list($insql, $inparams) = $DB->get_in_or_equal(array_keys($invalidconfigrations), SQL_PARAMS_NAMED);
 667                  $DB->set_field_select('course_modules', 'completiongradeitemnumber', null, "id $insql", $inparams);
 668              }
 669          } while ($invalidconfigrations);
 670  
 671          upgrade_main_savepoint(true, 2021052500.42);
 672      }
 673  
 674      if ($oldversion < 2021052500.55) {
 675          $DB->delete_records_select('event', "eventtype = 'category' AND categoryid = 0 AND userid <> 0");
 676  
 677          upgrade_main_savepoint(true, 2021052500.55);
 678      }
 679  
 680      if ($oldversion < 2021052500.59) {
 681          // Define field visibility to be added to contentbank_content.
 682          $table = new xmldb_table('contentbank_content');
 683          $field = new xmldb_field('visibility', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'contextid');
 684  
 685          // Conditionally launch add field visibility.
 686          if (!$dbman->field_exists($table, $field)) {
 687              $dbman->add_field($table, $field);
 688          }
 689  
 690          // Main savepoint reached.
 691          upgrade_main_savepoint(true, 2021052500.59);
 692      }
 693  
 694      if ($oldversion < 2021052500.60) {
 695  
 696          // We are going to remove the field 'hidepicture' from the groups
 697          // so we need to remove the pictures from those groups. But we prevent
 698          // the execution twice because this could be executed again when upgrading
 699          // to different versions.
 700          if ($dbman->field_exists('groups', 'hidepicture')) {
 701  
 702              $sql = "SELECT g.id, g.courseid, ctx.id AS contextid
 703                         FROM {groups} g
 704                         JOIN {context} ctx
 705                           ON ctx.instanceid = g.courseid
 706                          AND ctx.contextlevel = :contextlevel
 707                        WHERE g.hidepicture = 1";
 708  
 709              // Selecting all the groups that have hide picture enabled, and organising them by context.
 710              $groupctx = [];
 711              $records = $DB->get_recordset_sql($sql, ['contextlevel' => CONTEXT_COURSE]);
 712              foreach ($records as $record) {
 713                  if (!isset($groupctx[$record->contextid])) {
 714                      $groupctx[$record->contextid] = [];
 715                  }
 716                  $groupctx[$record->contextid][] = $record->id;
 717              }
 718              $records->close();
 719  
 720              // Deleting the group files.
 721              $fs = get_file_storage();
 722              foreach ($groupctx as $contextid => $groupids) {
 723                  list($in, $inparams) = $DB->get_in_or_equal($groupids, SQL_PARAMS_NAMED);
 724                  $fs->delete_area_files_select($contextid, 'group', 'icon', $in, $inparams);
 725              }
 726  
 727              // Updating the database to remove picture from all those groups.
 728              $sql = "UPDATE {groups} SET picture = :pic WHERE hidepicture = :hide";
 729              $DB->execute($sql, ['pic' => 0, 'hide' => 1]);
 730          }
 731  
 732          // Define field hidepicture to be dropped from groups.
 733          $table = new xmldb_table('groups');
 734          $field = new xmldb_field('hidepicture');
 735  
 736          // Conditionally launch drop field hidepicture.
 737          if ($dbman->field_exists($table, $field)) {
 738              $dbman->drop_field($table, $field);
 739          }
 740  
 741          // Main savepoint reached.
 742          upgrade_main_savepoint(true, 2021052500.60);
 743      }
 744  
 745      if ($oldversion < 2021052500.64) {
 746          // Get all the external backpacks and update the sortorder column, to avoid repeated/wrong values. As sortorder was not
 747          // used since now, the id column will be the criteria to follow for re-ordering them with a valid value.
 748          $i = 1;
 749          $records = $DB->get_records('badge_external_backpack', null, 'id ASC');
 750          foreach ($records as $record) {
 751              $record->sortorder = $i++;
 752              $DB->update_record('badge_external_backpack', $record);
 753          }
 754  
 755          upgrade_main_savepoint(true, 2021052500.64);
 756      }
 757  
 758      if ($oldversion < 2021052500.67) {
 759          // The $CFG->badges_site_backpack setting has been removed because it's not required anymore. From now, the default backpack
 760          // will be the one with lower sortorder value.
 761          unset_config('badges_site_backpack');
 762  
 763          upgrade_main_savepoint(true, 2021052500.67);
 764      }
 765  
 766      if ($oldversion < 2021052500.69) {
 767  
 768          // Define field type to be added to oauth2_issuer.
 769          $table = new xmldb_table('oauth2_issuer');
 770          $field = new xmldb_field('servicetype', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'requireconfirmation');
 771  
 772          // Conditionally launch add field type.
 773          if (!$dbman->field_exists($table, $field)) {
 774              $dbman->add_field($table, $field);
 775          }
 776  
 777          // Set existing values to the proper servicetype value.
 778          // It's not critical if the servicetype column doesn't contain the proper value for Google, Microsoft, Facebook or
 779          // Nextcloud services because, for now, this value is used for services using different discovery method.
 780          // However, let's try to upgrade it using the default value for the baseurl or image. If any of these default values
 781          // have been changed, the servicetype column will remain NULL.
 782          $recordset = $DB->get_recordset('oauth2_issuer');
 783          foreach ($recordset as $record) {
 784              if ($record->baseurl == 'https://accounts.google.com/') {
 785                  $record->servicetype = 'google';
 786                  $DB->update_record('oauth2_issuer', $record);
 787              } else if ($record->image == 'https://www.microsoft.com/favicon.ico') {
 788                  $record->servicetype = 'microsoft';
 789                  $DB->update_record('oauth2_issuer', $record);
 790              } else if ($record->image == 'https://facebookbrand.com/wp-content/uploads/2016/05/flogo_rgb_hex-brc-site-250.png') {
 791                  $record->servicetype = 'facebook';
 792                  $DB->update_record('oauth2_issuer', $record);
 793              } else if ($record->image == 'https://nextcloud.com/wp-content/themes/next/assets/img/common/favicon.png?x16328') {
 794                  $record->servicetype = 'nextcloud';
 795                  $DB->update_record('oauth2_issuer', $record);
 796              }
 797          }
 798          $recordset->close();
 799  
 800          // Main savepoint reached.
 801          upgrade_main_savepoint(true, 2021052500.69);
 802      }
 803  
 804      if ($oldversion < 2021052500.74) {
 805          // Define field 'showactivitydates' to be added to course table.
 806          $table = new xmldb_table('course');
 807          $field = new xmldb_field('showactivitydates', XMLDB_TYPE_INTEGER, '1', null,
 808              XMLDB_NOTNULL, null, '0', 'originalcourseid');
 809  
 810          if (!$dbman->field_exists($table, $field)) {
 811              $dbman->add_field($table, $field);
 812          }
 813  
 814          // Main savepoint reached.
 815          upgrade_main_savepoint(true, 2021052500.74);
 816      }
 817  
 818      if ($oldversion < 2021052500.75) {
 819          // Define field 'showcompletionconditions' to be added to course.
 820          $table = new xmldb_table('course');
 821          $field = new xmldb_field('showcompletionconditions', XMLDB_TYPE_INTEGER, '1', null,
 822              XMLDB_NOTNULL, null, '1', 'completionnotify');
 823  
 824          if (!$dbman->field_exists($table, $field)) {
 825              $dbman->add_field($table, $field);
 826          }
 827  
 828          // Main savepoint reached.
 829          upgrade_main_savepoint(true, 2021052500.75);
 830      }
 831  
 832      if ($oldversion < 2021052500.78) {
 833  
 834          // Define field enabled to be added to h5p_libraries.
 835          $table = new xmldb_table('h5p_libraries');
 836          $field = new xmldb_field('enabled', XMLDB_TYPE_INTEGER, '1', null, null, null, '1', 'example');
 837  
 838          // Conditionally launch add field enabled.
 839          if (!$dbman->field_exists($table, $field)) {
 840              $dbman->add_field($table, $field);
 841          }
 842  
 843          // Main savepoint reached.
 844          upgrade_main_savepoint(true, 2021052500.78);
 845      }
 846  
 847      if ($oldversion < 2021052500.83) {
 848  
 849          // Define field loginpagename to be added to oauth2_issuer.
 850          $table = new xmldb_table('oauth2_issuer');
 851          $field = new xmldb_field('loginpagename', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'servicetype');
 852  
 853          // Conditionally launch add field loginpagename.
 854          if (!$dbman->field_exists($table, $field)) {
 855              $dbman->add_field($table, $field);
 856          }
 857  
 858          // Main savepoint reached.
 859          upgrade_main_savepoint(true, 2021052500.83);
 860      }
 861  
 862      if ($oldversion < 2021052500.84) {
 863          require_once($CFG->dirroot . '/user/profile/field/social/upgradelib.php');
 864          $table = new xmldb_table('user');
 865          $tablecolumns = ['icq', 'skype', 'aim', 'yahoo', 'msn', 'url'];
 866  
 867          foreach ($tablecolumns as $column) {
 868              $field = new xmldb_field($column);
 869              if ($dbman->field_exists($table, $field)) {
 870                  user_profile_social_moveto_profilefield($column);
 871                  $dbman->drop_field($table, $field);
 872              }
 873          }
 874  
 875          // Update all module availability if it relies on the old user fields.
 876          user_profile_social_update_module_availability();
 877  
 878          // Remove field mapping for oauth2.
 879          $DB->delete_records('oauth2_user_field_mapping', array('internalfield' => 'url'));
 880  
 881          // Main savepoint reached.
 882          upgrade_main_savepoint(true, 2021052500.84);
 883      }
 884  
 885      if ($oldversion < 2021052500.85) {
 886          require_once($CFG->libdir . '/db/upgradelib.php');
 887  
 888          // Check if this site has executed the problematic upgrade steps.
 889          $needsfixing = upgrade_calendar_site_status(false);
 890  
 891          // Only queue the task if this site has been affected by the problematic upgrade step.
 892          if ($needsfixing) {
 893  
 894              // Create adhoc task to search and recover orphaned calendar events.
 895              $record = new \stdClass();
 896              $record->classname = '\core\task\calendar_fix_orphaned_events';
 897  
 898              // Next run time based from nextruntime computation in \core\task\manager::queue_adhoc_task().
 899              $nextruntime = time() - 1;
 900              $record->nextruntime = $nextruntime;
 901              $DB->insert_record('task_adhoc', $record);
 902          }
 903  
 904          // Main savepoint reached.
 905          upgrade_main_savepoint(true, 2021052500.85);
 906      }
 907  
 908      if ($oldversion < 2021052500.87) {
 909          // Changing the default of field showcompletionconditions on table course to 0.
 910          $table = new xmldb_table('course');
 911          $field = new xmldb_field('showcompletionconditions', XMLDB_TYPE_INTEGER, '1', null, null, null, null, 'showactivitydates');
 912  
 913          // Launch change of nullability for field showcompletionconditions.
 914          $dbman->change_field_notnull($table, $field);
 915  
 916          // Launch change of default for field showcompletionconditions.
 917          $dbman->change_field_default($table, $field);
 918  
 919          // Set showcompletionconditions to null for courses which don't track completion.
 920          $sql = "UPDATE {course}
 921                     SET showcompletionconditions = null
 922                   WHERE enablecompletion <> 1";
 923          $DB->execute($sql);
 924  
 925          // Main savepoint reached.
 926          upgrade_main_savepoint(true, 2021052500.87);
 927      }
 928  
 929      if ($oldversion < 2021052500.90) {
 930          // Remove usemodchooser user preference for every user.
 931          $DB->delete_records('user_preferences', ['name' => 'usemodchooser']);
 932  
 933          // Main savepoint reached.
 934          upgrade_main_savepoint(true, 2021052500.90);
 935      }
 936  
 937      if ($oldversion < 2021060200.00) {
 938  
 939          // Define index name (not unique) to be added to user_preferences.
 940          $table = new xmldb_table('user_preferences');
 941          $index = new xmldb_index('name', XMLDB_INDEX_NOTUNIQUE, ['name']);
 942  
 943          // Conditionally launch add index name.
 944          if (!$dbman->index_exists($table, $index)) {
 945              $dbman->add_index($table, $index);
 946          }
 947  
 948          // Main savepoint reached.
 949          upgrade_main_savepoint(true, 2021060200.00);
 950      }
 951  
 952      if ($oldversion < 2021060900.00) {
 953          // Update the externalfield to be larger.
 954          $table = new xmldb_table('oauth2_user_field_mapping');
 955          $field = new xmldb_field('externalfield', XMLDB_TYPE_CHAR, '500', null, XMLDB_NOTNULL, false, null, 'issuerid');
 956          $dbman->change_field_type($table, $field);
 957  
 958          // Main savepoint reached.
 959          upgrade_main_savepoint(true, 2021060900.00);
 960      }
 961  
 962      if ($oldversion < 2021072800.01) {
 963          // Define table reportbuilder_report to be created.
 964          $table = new xmldb_table('reportbuilder_report');
 965  
 966          // Adding fields to table reportbuilder_report.
 967          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 968          $table->add_field('source', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
 969          $table->add_field('type', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
 970          $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 971          $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
 972          $table->add_field('area', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
 973          $table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 974          $table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 975          $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 976          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 977          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 978  
 979          // Adding keys to table reportbuilder_report.
 980          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
 981          $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']);
 982          $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
 983          $table->add_key('contextid', XMLDB_KEY_FOREIGN, ['contextid'], 'context', ['id']);
 984  
 985          // Conditionally launch create table for reportbuilder_report.
 986          if (!$dbman->table_exists($table)) {
 987              $dbman->create_table($table);
 988          }
 989  
 990          // Main savepoint reached.
 991          upgrade_main_savepoint(true, 2021072800.01);
 992      }
 993  
 994      if ($oldversion < 2021090200.01) {
 995          // Remove qformat_webct (unless it has manually been added back).
 996          if (!file_exists($CFG->dirroot . '/question/format/webct/format.php')) {
 997              unset_all_config_for_plugin('qformat_webct');
 998          }
 999  
1000          // Main savepoint reached.
1001          upgrade_main_savepoint(true, 2021090200.01);
1002      }
1003  
1004      if ($oldversion < 2021091100.01) {
1005          // If message_jabber is no longer present, remove it.
1006          if (!file_exists($CFG->dirroot . '/message/output/jabber/message_output_jabber.php')) {
1007              // Remove Jabber from the notification plugins list.
1008              $DB->delete_records('message_processors', ['name' => 'jabber']);
1009  
1010              // Remove user preference settings.
1011              $DB->delete_records('user_preferences', ['name' => 'message_processor_jabber_jabberid']);
1012              $sql = 'SELECT *
1013                      FROM {user_preferences} up
1014                      WHERE ' . $DB->sql_like('up.name', ':name', false, false) . ' AND ' .
1015                          $DB->sql_like('up.value', ':value', false, false);
1016              $params = [
1017                  'name' => 'message_provider_%',
1018                  'value' => '%jabber%',
1019              ];
1020              $jabbersettings = $DB->get_recordset_sql($sql, $params);
1021              foreach ($jabbersettings as $jabbersetting) {
1022                  // Remove 'jabber' from the value.
1023                  $jabbersetting->value = implode(',', array_diff(explode(',', $jabbersetting->value), ['jabber']));
1024                  $DB->update_record('user_preferences', $jabbersetting);
1025              }
1026              $jabbersettings->close();
1027  
1028              // Clean config settings.
1029              unset_config('jabberhost');
1030              unset_config('jabberserver');
1031              unset_config('jabberusername');
1032              unset_config('jabberpassword');
1033              unset_config('jabberport');
1034  
1035              // Remove default notification preferences.
1036              $like = $DB->sql_like('name', '?', true, true, false, '|');
1037              $params = [$DB->sql_like_escape('jabber_provider_', '|') . '%'];
1038              $DB->delete_records_select('config_plugins', $like, $params);
1039  
1040              // Clean config config settings.
1041              unset_all_config_for_plugin('message_jabber');
1042          }
1043  
1044          upgrade_main_savepoint(true, 2021091100.01);
1045      }
1046  
1047      if ($oldversion < 2021091100.02) {
1048          // Set the description field to HTML format for the Default course category.
1049          $category = $DB->get_record('course_categories', ['id' => 1]);
1050  
1051          if (!empty($category) && $category->descriptionformat == FORMAT_MOODLE) {
1052              // Format should be changed only if it's still set to FORMAT_MOODLE.
1053              if (!is_null($category->description)) {
1054                  // If description is not empty, format the content to HTML.
1055                  $category->description = format_text($category->description, FORMAT_MOODLE);
1056              }
1057              $category->descriptionformat = FORMAT_HTML;
1058              $DB->update_record('course_categories', $category);
1059          }
1060  
1061          // Main savepoint reached.
1062          upgrade_main_savepoint(true, 2021091100.02);
1063      }
1064  
1065      if ($oldversion < 2021091700.01) {
1066          // Default 'off' for existing sites as this is the behaviour they had earlier.
1067          set_config('enroladminnewcourse', false);
1068  
1069          // Main savepoint reached.
1070          upgrade_main_savepoint(true, 2021091700.01);
1071      }
1072  
1073      if ($oldversion < 2021091700.02) {
1074          // If portfolio_picasa is no longer present, remove it.
1075          if (!file_exists($CFG->dirroot . '/portfolio/picasa/version.php')) {
1076              $instance = $DB->get_record('portfolio_instance', ['plugin' => 'picasa']);
1077              if (!empty($instance)) {
1078                  // Remove all records from portfolio_instance_config.
1079                  $DB->delete_records('portfolio_instance_config', ['instance' => $instance->id]);
1080                  // Remove all records from portfolio_instance_user.
1081                  $DB->delete_records('portfolio_instance_user', ['instance' => $instance->id]);
1082                  // Remove all records from portfolio_log.
1083                  $DB->delete_records('portfolio_log', ['portfolio' => $instance->id]);
1084                  // Remove all records from portfolio_tempdata.
1085                  $DB->delete_records('portfolio_tempdata', ['instance' => $instance->id]);
1086                  // Remove the record from the portfolio_instance table.
1087                  $DB->delete_records('portfolio_instance', ['id' => $instance->id]);
1088              }
1089  
1090              // Clean config.
1091              unset_all_config_for_plugin('portfolio_picasa');
1092          }
1093  
1094          upgrade_main_savepoint(true, 2021091700.02);
1095      }
1096  
1097      if ($oldversion < 2021091700.03) {
1098          // If repository_picasa is no longer present, remove it.
1099          if (!file_exists($CFG->dirroot . '/repository/picasa/version.php')) {
1100              $instance = $DB->get_record('repository', ['type' => 'picasa']);
1101              if (!empty($instance)) {
1102                  // Remove all records from repository_instance_config table.
1103                  $DB->delete_records('repository_instance_config', ['instanceid' => $instance->id]);
1104                  // Remove all records from repository_instances table.
1105                  $DB->delete_records('repository_instances', ['typeid' => $instance->id]);
1106                  // Remove the record from the repository table.
1107                  $DB->delete_records('repository', ['id' => $instance->id]);
1108              }
1109  
1110              // Clean config.
1111              unset_all_config_for_plugin('picasa');
1112  
1113              // Remove orphaned files.
1114              upgrade_delete_orphaned_file_records();
1115          }
1116  
1117          upgrade_main_savepoint(true, 2021091700.03);
1118      }
1119  
1120      if ($oldversion < 2021091700.04) {
1121          // Remove media_swf (unless it has manually been added back).
1122          if (!file_exists($CFG->dirroot . '/media/player/swf/classes/plugin.php')) {
1123              unset_all_config_for_plugin('media_swf');
1124          }
1125  
1126          upgrade_main_savepoint(true, 2021091700.04);
1127      }
1128  
1129      if ($oldversion < 2021092400.01) {
1130          // If tool_health is no longer present, remove it.
1131          if (!file_exists($CFG->dirroot . '/admin/tool/health/version.php')) {
1132              // Clean config.
1133              unset_all_config_for_plugin('tool_health');
1134          }
1135  
1136          // Main savepoint reached.
1137          upgrade_main_savepoint(true, 2021092400.01);
1138      }
1139  
1140      if ($oldversion < 2021092400.03) {
1141          // Remove repository_picasa configuration (unless it has manually been added back).
1142          if (!file_exists($CFG->dirroot . '/repository/picasa/version.php')) {
1143              unset_all_config_for_plugin('repository_picasa');
1144          }
1145  
1146          upgrade_main_savepoint(true, 2021092400.03);
1147      }
1148  
1149      if ($oldversion < 2021100300.01) {
1150          // Remove repository_skydrive (unless it has manually been added back).
1151          if (!file_exists($CFG->dirroot . '/repository/skydrive/lib.php')) {
1152              unset_all_config_for_plugin('repository_skydrive');
1153          }
1154  
1155          // Main savepoint reached.
1156          upgrade_main_savepoint(true, 2021100300.01);
1157      }
1158  
1159      if ($oldversion < 2021100300.02) {
1160          // Remove filter_censor (unless it has manually been added back).
1161          if (!file_exists($CFG->dirroot . '/filter/censor/filter.php')) {
1162              unset_all_config_for_plugin('filter_censor');
1163          }
1164  
1165          // Main savepoint reached.
1166          upgrade_main_savepoint(true, 2021100300.02);
1167      }
1168  
1169      if ($oldversion < 2021100600.01) {
1170          // Remove qformat_examview (unless it has manually been added back).
1171          if (!file_exists($CFG->dirroot . '/question/format/examview/format.php')) {
1172              unset_all_config_for_plugin('qformat_examview');
1173          }
1174  
1175          // Main savepoint reached.
1176          upgrade_main_savepoint(true, 2021100600.01);
1177      }
1178  
1179      if ($oldversion < 2021100600.02) {
1180          $table = new xmldb_table('course_completion_defaults');
1181  
1182          // Adding fields to table course_completion_defaults.
1183          $field = new xmldb_field('completionpassgrade', XMLDB_TYPE_INTEGER, '1', null,
1184              XMLDB_NOTNULL, null, '0', 'completionusegrade');
1185  
1186          // Conditionally launch add field for course_completion_defaults.
1187          if (!$dbman->field_exists($table, $field)) {
1188              $dbman->add_field($table, $field);
1189          }
1190  
1191          upgrade_main_savepoint(true, 2021100600.02);
1192      }
1193  
1194      if ($oldversion < 2021100600.03) {
1195          $table = new xmldb_table('course_modules');
1196  
1197          // Adding new fields to table course_module table.
1198          $field = new xmldb_field('completionpassgrade', XMLDB_TYPE_INTEGER, '1', null,
1199              XMLDB_NOTNULL, null, '0', 'completionexpected');
1200          // Conditionally launch create table for course_completion_defaults.
1201          if (!$dbman->field_exists($table, $field)) {
1202              $dbman->add_field($table, $field);
1203          }
1204  
1205          upgrade_main_savepoint(true, 2021100600.03);
1206      }
1207  
1208      if ($oldversion < 2021100600.04) {
1209          // Define index itemtype-mod-inst-course (not unique) to be added to grade_items.
1210          $table = new xmldb_table('grade_items');
1211          $index = new xmldb_index('itemtype-mod-inst-course', XMLDB_INDEX_NOTUNIQUE,
1212              ['itemtype', 'itemmodule', 'iteminstance', 'courseid']);
1213  
1214          // Conditionally launch add index itemtype-mod-inst-course.
1215          if (!$dbman->index_exists($table, $index)) {
1216              $dbman->add_index($table, $index);
1217          }
1218  
1219          // Main savepoint reached.
1220          upgrade_main_savepoint(true, 2021100600.04);
1221      }
1222  
1223      if ($oldversion < 2021101900.01) {
1224          $table = new xmldb_table('reportbuilder_report');
1225  
1226          // Define field name to be added to reportbuilder_report.
1227          $field = new xmldb_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'id');
1228          if (!$dbman->field_exists($table, $field)) {
1229              $dbman->add_field($table, $field);
1230          }
1231  
1232          // Define field conditiondata to be added to reportbuilder_report.
1233          $field = new xmldb_field('conditiondata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'type');
1234          if (!$dbman->field_exists($table, $field)) {
1235              $dbman->add_field($table, $field);
1236          }
1237  
1238          // Define table reportbuilder_column to be created.
1239          $table = new xmldb_table('reportbuilder_column');
1240  
1241          // Adding fields to table reportbuilder_column.
1242          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1243          $table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1244          $table->add_field('uniqueidentifier', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1245          $table->add_field('aggregation', XMLDB_TYPE_CHAR, '32', null, null, null, null);
1246          $table->add_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null);
1247          $table->add_field('columnorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1248          $table->add_field('sortenabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
1249          $table->add_field('sortdirection', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
1250          $table->add_field('sortorder', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
1251          $table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1252          $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1253          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1254          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1255  
1256          // Adding keys to table reportbuilder_column.
1257          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1258          $table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']);
1259          $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']);
1260          $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1261  
1262          // Conditionally launch create table for reportbuilder_column.
1263          if (!$dbman->table_exists($table)) {
1264              $dbman->create_table($table);
1265          }
1266  
1267          // Define table reportbuilder_filter to be created.
1268          $table = new xmldb_table('reportbuilder_filter');
1269  
1270          // Adding fields to table reportbuilder_filter.
1271          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1272          $table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1273          $table->add_field('uniqueidentifier', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1274          $table->add_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null);
1275          $table->add_field('iscondition', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
1276          $table->add_field('filterorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1277          $table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1278          $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1279          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1280          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1281  
1282          // Adding keys to table reportbuilder_filter.
1283          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1284          $table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']);
1285          $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']);
1286          $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1287  
1288          // Conditionally launch create table for reportbuilder_filter.
1289          if (!$dbman->table_exists($table)) {
1290              $dbman->create_table($table);
1291          }
1292  
1293          // Main savepoint reached.
1294          upgrade_main_savepoint(true, 2021101900.01);
1295      }
1296  
1297      if ($oldversion < 2021102600.01) {
1298          // Remove block_quiz_results (unless it has manually been added back).
1299          if (!file_exists($CFG->dirroot . '/blocks/quiz_result/block_quiz_results.php')) {
1300              // Delete instances.
1301              $instances = $DB->get_records_list('block_instances', 'blockname', ['quiz_results']);
1302              $instanceids = array_keys($instances);
1303  
1304              if (!empty($instanceids)) {
1305                  blocks_delete_instances($instanceids);
1306              }
1307  
1308              // Delete the block from the block table.
1309              $DB->delete_records('block', array('name' => 'quiz_results'));
1310  
1311              // Remove capabilities.
1312              capabilities_cleanup('block_quiz_results');
1313              // Clean config.
1314              unset_all_config_for_plugin('block_quiz_results');
1315  
1316              // Remove Moodle-level quiz_results based capabilities.
1317              $capabilitiestoberemoved = ['block/quiz_results:addinstance'];
1318              // Delete any role_capabilities for the old roles.
1319              $DB->delete_records_list('role_capabilities', 'capability', $capabilitiestoberemoved);
1320              // Delete the capability itself.
1321              $DB->delete_records_list('capabilities', 'name', $capabilitiestoberemoved);
1322          }
1323  
1324          upgrade_main_savepoint(true, 2021102600.01);
1325      }
1326  
1327      if ($oldversion < 2021102900.02) {
1328          // If portfolio_boxnet is no longer present, remove it.
1329          if (!file_exists($CFG->dirroot . '/portfolio/boxnet/version.php')) {
1330              $instance = $DB->get_record('portfolio_instance', ['plugin' => 'boxnet']);
1331              if (!empty($instance)) {
1332                  // Remove all records from portfolio_instance_config.
1333                  $DB->delete_records('portfolio_instance_config', ['instance' => $instance->id]);
1334                  // Remove all records from portfolio_instance_user.
1335                  $DB->delete_records('portfolio_instance_user', ['instance' => $instance->id]);
1336                  // Remove all records from portfolio_log.
1337                  $DB->delete_records('portfolio_log', ['portfolio' => $instance->id]);
1338                  // Remove all records from portfolio_tempdata.
1339                  $DB->delete_records('portfolio_tempdata', ['instance' => $instance->id]);
1340                  // Remove the record from the portfolio_instance table.
1341                  $DB->delete_records('portfolio_instance', ['id' => $instance->id]);
1342              }
1343  
1344              // Clean config.
1345              unset_all_config_for_plugin('portfolio_boxnet');
1346          }
1347  
1348          // If repository_boxnet is no longer present, remove it.
1349          if (!file_exists($CFG->dirroot . '/repository/boxnet/version.php')) {
1350              $instance = $DB->get_record('repository', ['type' => 'boxnet']);
1351              if (!empty($instance)) {
1352                  // Remove all records from repository_instance_config table.
1353                  $DB->delete_records('repository_instance_config', ['instanceid' => $instance->id]);
1354                  // Remove all records from repository_instances table.
1355                  $DB->delete_records('repository_instances', ['typeid' => $instance->id]);
1356                  // Remove the record from the repository table.
1357                  $DB->delete_records('repository', ['id' => $instance->id]);
1358              }
1359  
1360              // Clean config.
1361              unset_all_config_for_plugin('repository_boxnet');
1362  
1363              // The boxnet repository plugin stores some config in 'boxnet' incorrectly.
1364              unset_all_config_for_plugin('boxnet');
1365  
1366              // Remove orphaned files.
1367              upgrade_delete_orphaned_file_records();
1368          }
1369  
1370          upgrade_main_savepoint(true, 2021102900.02);
1371      }
1372  
1373      if ($oldversion < 2021110100.00) {
1374  
1375          // Define table reportbuilder_audience to be created.
1376          $table = new xmldb_table('reportbuilder_audience');
1377  
1378          // Adding fields to table reportbuilder_audience.
1379          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1380          $table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1381          $table->add_field('classname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1382          $table->add_field('configdata', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
1383          $table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1384          $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1385          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1386          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1387  
1388          // Adding keys to table reportbuilder_audience.
1389          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1390          $table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']);
1391          $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']);
1392          $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1393  
1394          // Conditionally launch create table for reportbuilder_audience.
1395          if (!$dbman->table_exists($table)) {
1396              $dbman->create_table($table);
1397          }
1398  
1399          // Main savepoint reached.
1400          upgrade_main_savepoint(true, 2021110100.00);
1401      }
1402  
1403      if ($oldversion < 2021110800.02) {
1404          // Define a field 'downloadcontent' in the 'course_modules' table.
1405          $table = new xmldb_table('course_modules');
1406          $field = new xmldb_field('downloadcontent', XMLDB_TYPE_INTEGER, '1', null, null, null, 1, 'deletioninprogress');
1407  
1408          // Conditionally launch add field 'downloadcontent'.
1409          if (!$dbman->field_exists($table, $field)) {
1410              $dbman->add_field($table, $field);
1411          }
1412  
1413          // Main savepoint reached.
1414          upgrade_main_savepoint(true, 2021110800.02);
1415      }
1416  
1417      if ($oldversion < 2021110800.03) {
1418  
1419          // Define field settingsdata to be added to reportbuilder_report.
1420          $table = new xmldb_table('reportbuilder_report');
1421          $field = new xmldb_field('settingsdata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'conditiondata');
1422  
1423          // Conditionally launch add field settingsdata.
1424          if (!$dbman->field_exists($table, $field)) {
1425              $dbman->add_field($table, $field);
1426          }
1427  
1428          // Main savepoint reached.
1429          upgrade_main_savepoint(true, 2021110800.03);
1430      }
1431  
1432      if ($oldversion < 2021111700.00) {
1433          $mycoursespage = new stdClass();
1434          $mycoursespage->userid = null;
1435          $mycoursespage->name = '__courses';
1436          $mycoursespage->private = 0;
1437          $mycoursespage->sortorder  = 0;
1438          $DB->insert_record('my_pages', $mycoursespage);
1439  
1440          upgrade_main_savepoint(true, 2021111700.00);
1441      }
1442  
1443      if ($oldversion < 2021111700.01) {
1444  
1445          // Define field uniquerows to be added to reportbuilder_report.
1446          $table = new xmldb_table('reportbuilder_report');
1447          $field = new xmldb_field('uniquerows', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'type');
1448  
1449          // Conditionally launch add field uniquerows.
1450          if (!$dbman->field_exists($table, $field)) {
1451              $dbman->add_field($table, $field);
1452          }
1453  
1454          // Main savepoint reached.
1455          upgrade_main_savepoint(true, 2021111700.01);
1456      }
1457  
1458      if ($oldversion < 2021120100.01) {
1459  
1460          // Get current configuration data.
1461          $currentcustomusermenuitems = str_replace(["\r\n", "\r"], "\n", $CFG->customusermenuitems);
1462          $lines = explode("\n", $currentcustomusermenuitems);
1463          $lines = array_map('trim', $lines);
1464          $calendarcustomusermenu = 'calendar,core_calendar|/calendar/view.php?view=month|i/calendar';
1465  
1466          if (!in_array($calendarcustomusermenu, $lines)) {
1467              // Add Calendar item to the menu.
1468              array_splice($lines, 1, 0, [$calendarcustomusermenu]);
1469              set_config('customusermenuitems', implode("\n", $lines));
1470          }
1471  
1472          // Main savepoint reached.
1473          upgrade_main_savepoint(true, 2021120100.01);
1474      }
1475  
1476      if ($oldversion < 2021121400.01) {
1477          // The $CFG->grade_navmethod setting has been removed because it's not required anymore. This setting was used
1478          // to set the type of navigation (tabs or dropdown box) which will be displayed in gradebook. However, these
1479          // navigation methods are no longer used and replaced with tertiary navigation.
1480          unset_config('grade_navmethod');
1481  
1482          // Main savepoint reached.
1483          upgrade_main_savepoint(true, 2021121400.01);
1484      }
1485  
1486      if ($oldversion < 2021121700.01) {
1487          // Get current support email setting value.
1488          $config = get_config('moodle', 'supportemail');
1489  
1490          // Check if support email setting is empty and then set it to null.
1491          // We must do that so the setting is displayed during the upgrade.
1492          if (empty($config)) {
1493              set_config('supportemail', null);
1494          }
1495  
1496          // Main savepoint reached.
1497          upgrade_main_savepoint(true, 2021121700.01);
1498      }
1499  
1500      if ($oldversion < 2021122100.00) {
1501          // Get current configuration data.
1502          $currentcustomusermenuitems = str_replace(["\r\n", "\r"], "\n", $CFG->customusermenuitems);
1503  
1504          // The old default customusermenuitems config for 3.11 and below.
1505          $oldcustomusermenuitems = 'grades,grades|/grade/report/mygrades.php|t/grades
1506  calendar,core_calendar|/calendar/view.php?view=month|i/calendar
1507  messages,message|/message/index.php|t/message
1508  preferences,moodle|/user/preferences.php|t/preferences';
1509  
1510          // Check if the current customusermenuitems config matches the old customusermenuitems config.
1511          $samecustomusermenuitems = $currentcustomusermenuitems == $oldcustomusermenuitems;
1512          if ($samecustomusermenuitems) {
1513              // If the site is still using the old defaults, upgrade to the new default.
1514              $newcustomusermenuitems = 'profile,moodle|/user/profile.php
1515  grades,grades|/grade/report/mygrades.php
1516  calendar,core_calendar|/calendar/view.php?view=month
1517  privatefiles,moodle|/user/files.php';
1518              // Set the new configuration back.
1519              set_config('customusermenuitems', $newcustomusermenuitems);
1520          } else {
1521              // If the site is not using the old defaults, only add necessary entries.
1522              $lines = preg_split('/\n/', $currentcustomusermenuitems, -1, PREG_SPLIT_NO_EMPTY);
1523              $lines = array_map(static function(string $line): string {
1524                  // Previous format was "<langstring>|<url>[|<pixicon>]" - pix icon is no longer supported.
1525                  $lineparts = explode('|', trim($line), 3);
1526                  // Return first two parts of line.
1527                  return implode('|', array_slice($lineparts, 0, 2));
1528              }, $lines);
1529  
1530              // Remove the Preference entry from the menu to prevent duplication
1531              // since it will be added again in user_get_user_navigation_info().
1532              $lines = array_filter($lines, function($value) {
1533                  return strpos($value, 'preferences,moodle|/user/preferences.php') === false;
1534              });
1535  
1536              $matches = preg_grep('/\|\/user\/files.php/i', $lines);
1537              if (!$matches) {
1538                  // Add the Private files entry to the menu.
1539                  $lines[] = 'privatefiles,moodle|/user/files.php';
1540              }
1541  
1542              $matches = preg_grep('/\|\/user\/profile.php/i', $lines);
1543              if (!$matches) {
1544                  // Add the Profile entry to top of the menu.
1545                  array_unshift($lines, 'profile,moodle|/user/profile.php');
1546              }
1547  
1548              // Set the new configuration back.
1549              set_config('customusermenuitems', implode("\n", $lines));
1550          }
1551  
1552          // Main savepoint reached.
1553          upgrade_main_savepoint(true, 2021122100.00);
1554      }
1555  
1556  
1557      if ($oldversion < 2021122100.01) {
1558  
1559          // Define field heading to be added to reportbuilder_audience.
1560          $table = new xmldb_table('reportbuilder_audience');
1561          $field = new xmldb_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'reportid');
1562  
1563          // Conditionally launch add field heading.
1564          if (!$dbman->field_exists($table, $field)) {
1565              $dbman->add_field($table, $field);
1566          }
1567  
1568          // Main savepoint reached.
1569          upgrade_main_savepoint(true, 2021122100.01);
1570      }
1571  
1572      if ($oldversion < 2021122100.02) {
1573  
1574          // Define table reportbuilder_schedule to be created.
1575          $table = new xmldb_table('reportbuilder_schedule');
1576  
1577          // Adding fields to table reportbuilder_schedule.
1578          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1579          $table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1580          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1581          $table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1');
1582          $table->add_field('audiences', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
1583          $table->add_field('format', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1584          $table->add_field('subject', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1585          $table->add_field('message', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
1586          $table->add_field('messageformat', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1587          $table->add_field('userviewas', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1588          $table->add_field('timescheduled', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1589          $table->add_field('recurrence', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1590          $table->add_field('reportempty', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1591          $table->add_field('timelastsent', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1592          $table->add_field('timenextsend', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1593          $table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1594          $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1595          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1596          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1597  
1598          // Adding keys to table reportbuilder_schedule.
1599          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1600          $table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']);
1601          $table->add_key('userviewas', XMLDB_KEY_FOREIGN, ['userviewas'], 'user', ['id']);
1602          $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']);
1603          $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
1604  
1605          // Conditionally launch create table for reportbuilder_schedule.
1606          if (!$dbman->table_exists($table)) {
1607              $dbman->create_table($table);
1608          }
1609  
1610          // Main savepoint reached.
1611          upgrade_main_savepoint(true, 2021122100.02);
1612      }
1613  
1614      if ($oldversion < 2021123000.01) {
1615          // The tool_admin_presets tables have been moved to core, because core_adminpresets component has been created, so
1616          // it can interact with the rest of core.
1617          // So the tool_admin_presetsXXX tables will be renamed to adminipresetsXXX if they exists; otherwise, they will be created.
1618  
1619          $tooltable = new xmldb_table('tool_admin_presets');
1620          $table = new xmldb_table('adminpresets');
1621          if ($dbman->table_exists($tooltable)) {
1622              $dbman->rename_table($tooltable, 'adminpresets');
1623          } else if (!$dbman->table_exists($table)) {
1624              // Adding fields to table adminpresets.
1625              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1626              $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1627              $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1628              $table->add_field('comments', XMLDB_TYPE_TEXT, null, null, null, null, null);
1629              $table->add_field('site', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1630              $table->add_field('author', XMLDB_TYPE_CHAR, '255', null, null, null, null);
1631              $table->add_field('moodleversion', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, null);
1632              $table->add_field('moodlerelease', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
1633              $table->add_field('iscore', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
1634              $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1635              $table->add_field('timeimported', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1636  
1637              // Adding keys to table adminpresets.
1638              $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1639  
1640              // Launch create table for adminpresets.
1641              $dbman->create_table($table);
1642          }
1643  
1644          $tooltable = new xmldb_table('tool_admin_presets_it');
1645          $table = new xmldb_table('adminpresets_it');
1646          if ($dbman->table_exists($tooltable)) {
1647              $dbman->rename_table($tooltable, 'adminpresets_it');
1648          } else if (!$dbman->table_exists($table)) {
1649              // Adding fields to table adminpresets_it.
1650              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1651              $table->add_field('adminpresetid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1652              $table->add_field('plugin', XMLDB_TYPE_CHAR, '100', null, null, null, null);
1653              $table->add_field('name', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
1654              $table->add_field('value', XMLDB_TYPE_TEXT, null, null, null, null, null);
1655  
1656              // Adding keys to table adminpresets_it.
1657              $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1658  
1659              // Adding indexes to table adminpresets_it.
1660              $table->add_index('adminpresetid', XMLDB_INDEX_NOTUNIQUE, ['adminpresetid']);
1661  
1662              // Launch create table for adminpresets_it.
1663              $dbman->create_table($table);
1664          }
1665  
1666          $tooltable = new xmldb_table('tool_admin_presets_it_a');
1667          $table = new xmldb_table('adminpresets_it_a');
1668          if ($dbman->table_exists($tooltable)) {
1669              $dbman->rename_table($tooltable, 'adminpresets_it_a');
1670          } else if (!$dbman->table_exists($table)) {
1671              // Adding fields to table adminpresets_it_a.
1672              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1673              $table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1674              $table->add_field('name', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
1675              $table->add_field('value', XMLDB_TYPE_TEXT, null, null, null, null, null);
1676  
1677              // Adding keys to table adminpresets_it_a.
1678              $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1679  
1680              // Adding indexes to table adminpresets_it_a.
1681              $table->add_index('itemid', XMLDB_INDEX_NOTUNIQUE, ['itemid']);
1682  
1683              // Launch create table for adminpresets_it_a.
1684              $dbman->create_table($table);
1685          }
1686  
1687          $tooltable = new xmldb_table('tool_admin_presets_app');
1688          $table = new xmldb_table('adminpresets_app');
1689          if ($dbman->table_exists($tooltable)) {
1690              $dbman->rename_table($tooltable, 'adminpresets_app');
1691          } else if (!$dbman->table_exists($table)) {
1692              // Adding fields to table adminpresets_app.
1693              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1694              $table->add_field('adminpresetid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1695              $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1696              $table->add_field('time', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1697  
1698              // Adding keys to table adminpresets_app.
1699              $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1700  
1701              // Adding indexes to table adminpresets_app.
1702              $table->add_index('adminpresetid', XMLDB_INDEX_NOTUNIQUE, ['adminpresetid']);
1703  
1704              // Launch create table for adminpresets_app.
1705              $dbman->create_table($table);
1706          }
1707  
1708          $tooltable = new xmldb_table('tool_admin_presets_app_it');
1709          $table = new xmldb_table('adminpresets_app_it');
1710          if ($dbman->table_exists($tooltable)) {
1711              $dbman->rename_table($tooltable, 'adminpresets_app_it');
1712          } else if (!$dbman->table_exists($table)) {
1713              // Adding fields to table adminpresets_app_it.
1714              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1715              $table->add_field('adminpresetapplyid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1716              $table->add_field('configlogid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1717  
1718              // Adding keys to table adminpresets_app_it.
1719              $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1720  
1721              // Adding indexes to table adminpresets_app_it.
1722              $table->add_index('configlogid', XMLDB_INDEX_NOTUNIQUE, ['configlogid']);
1723              $table->add_index('adminpresetapplyid', XMLDB_INDEX_NOTUNIQUE, ['adminpresetapplyid']);
1724  
1725              // Launch create table for adminpresets_app_it.
1726              $dbman->create_table($table);
1727          }
1728  
1729          $tooltable = new xmldb_table('tool_admin_presets_app_it_a');
1730          $table = new xmldb_table('adminpresets_app_it_a');
1731          if ($dbman->table_exists($tooltable)) {
1732              $dbman->rename_table($tooltable, 'adminpresets_app_it_a');
1733          } else if (!$dbman->table_exists($table)) {
1734              // Adding fields to table adminpresets_app_it_a.
1735              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1736              $table->add_field('adminpresetapplyid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1737              $table->add_field('configlogid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1738              $table->add_field('itemname', XMLDB_TYPE_CHAR, '100', null, null, null, null);
1739  
1740              // Adding keys to table adminpresets_app_it_a.
1741              $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1742  
1743              // Adding indexes to table adminpresets_app_it_a.
1744              $table->add_index('configlogid', XMLDB_INDEX_NOTUNIQUE, ['configlogid']);
1745              $table->add_index('adminpresetapplyid', XMLDB_INDEX_NOTUNIQUE, ['adminpresetapplyid']);
1746  
1747              // Launch create table for adminpresets_app_it_a.
1748              $dbman->create_table($table);
1749          }
1750  
1751          $tooltable = new xmldb_table('tool_admin_presets_plug');
1752          $table = new xmldb_table('adminpresets_plug');
1753          if ($dbman->table_exists($tooltable)) {
1754              $dbman->rename_table($tooltable, 'adminpresets_plug');
1755          } else if (!$dbman->table_exists($table)) {
1756              // Adding fields to table adminpresets_plug.
1757              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1758              $table->add_field('adminpresetid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1759              $table->add_field('plugin', XMLDB_TYPE_CHAR, '100', null, null, null, null);
1760              $table->add_field('name', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
1761              $table->add_field('enabled', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0');
1762  
1763              // Adding keys to table adminpresets_plug.
1764              $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1765  
1766              // Adding indexes to table adminpresets_plug.
1767              $table->add_index('adminpresetid', XMLDB_INDEX_NOTUNIQUE, ['adminpresetid']);
1768  
1769              // Launch create table for adminpresets_plug.
1770              $dbman->create_table($table);
1771          }
1772  
1773          $tooltable = new xmldb_table('tool_admin_presets_app_plug');
1774          $table = new xmldb_table('adminpresets_app_plug');
1775          if ($dbman->table_exists($tooltable)) {
1776              $dbman->rename_table($tooltable, 'adminpresets_app_plug');
1777          } else if (!$dbman->table_exists($table)) {
1778              // Adding fields to table adminpresets_app_plug.
1779              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1780              $table->add_field('adminpresetapplyid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1781              $table->add_field('plugin', XMLDB_TYPE_CHAR, '100', null, null, null, null);
1782              $table->add_field('name', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
1783              $table->add_field('value', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0');
1784              $table->add_field('oldvalue', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0');
1785  
1786              // Adding keys to table adminpresets_app_plug.
1787              $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
1788  
1789              // Adding indexes to table adminpresets_app_plug.
1790              $table->add_index('adminpresetapplyid', XMLDB_INDEX_NOTUNIQUE, ['adminpresetapplyid']);
1791  
1792              // Launch create table for adminpresets_app_plug.
1793              if (!$dbman->table_exists($table)) {
1794                  $dbman->create_table($table);
1795              }
1796          }
1797  
1798          if ($DB->count_records('adminpresets', ['iscore' => 1]) == 0) {
1799              // Create default core site admin presets.
1800              require_once($CFG->dirroot . '/admin/presets/classes/helper.php');
1801              \core_adminpresets\helper::create_default_presets();
1802          }
1803  
1804          // Main savepoint reached.
1805          upgrade_main_savepoint(true, 2021123000.01);
1806      }
1807  
1808      if ($oldversion < 2021123000.02) {
1809          // If exists, migrate sensiblesettings admin settings from tool_admin_preset to adminpresets.
1810          if (get_config('tool_admin_presets', 'sensiblesettings') !== false) {
1811              set_config('sensiblesettings', get_config('tool_admin_presets', 'sensiblesettings'), 'adminpresets');
1812              unset_config('sensiblesettings', 'tool_admin_presets');
1813          }
1814  
1815          // Main savepoint reached.
1816          upgrade_main_savepoint(true, 2021123000.02);
1817      }
1818  
1819      if ($oldversion < 2021123000.03) {
1820          // If exists, migrate lastpresetapplied setting from tool_admin_preset to adminpresets.
1821          if (get_config('tool_admin_presets', 'lastpresetapplied') !== false) {
1822              set_config('lastpresetapplied', get_config('tool_admin_presets', 'lastpresetapplied'), 'adminpresets');
1823              unset_config('lastpresetapplied', 'tool_admin_presets');
1824          }
1825  
1826          // Main savepoint reached.
1827          upgrade_main_savepoint(true, 2021123000.03);
1828      }
1829  
1830      if ($oldversion < 2022011100.01) {
1831          // The following blocks have been hidden by default, so they shouldn't be enabled in the Full core preset: Course/site
1832          // summary, RSS feeds, Self completion and Feedback.
1833          $params = ['name' => get_string('fullpreset', 'core_adminpresets')];
1834          $fullpreset = $DB->get_record_select('adminpresets', 'name = :name and iscore > 0', $params);
1835  
1836          if (!$fullpreset) {
1837              // Full admin preset might have been created using the English name.
1838              $name = get_string_manager()->get_string('fullpreset', 'core_adminpresets', null, 'en');
1839              $params['name'] = $name;
1840              $fullpreset = $DB->get_record_select('adminpresets', 'name = :name and iscore > 0', $params);
1841          }
1842          if (!$fullpreset) {
1843              // We tried, but we didn't find full by name. Let's find a core preset that sets 'usecomments' setting to 1.
1844              $sql = "SELECT preset.*
1845                        FROM {adminpresets} preset
1846                  INNER JOIN {adminpresets_it} it ON preset.id = it.adminpresetid
1847                       WHERE it.name = :name AND it.value = :value AND preset.iscore > 0";
1848              $params = ['name' => 'usecomments', 'value' => '1'];
1849              $fullpreset = $DB->get_record_sql($sql, $params);
1850          }
1851  
1852          if ($fullpreset) {
1853              $blocknames = ['course_summary', 'feedback', 'rss_client', 'selfcompletion'];
1854              list($blocksinsql, $blocksinparams) = $DB->get_in_or_equal($blocknames);
1855  
1856              // Remove entries from the adminpresets_app_plug table (in case the preset has been applied).
1857              $appliedpresets = $DB->get_records('adminpresets_app', ['adminpresetid' => $fullpreset->id], '', 'id');
1858              if ($appliedpresets) {
1859                  list($appsinsql, $appsinparams) = $DB->get_in_or_equal(array_keys($appliedpresets));
1860                  $sql = "adminpresetapplyid $appsinsql AND plugin='block' AND name $blocksinsql";
1861                  $params = array_merge($appsinparams, $blocksinparams);
1862                  $DB->delete_records_select('adminpresets_app_plug', $sql, $params);
1863              }
1864  
1865              // Remove entries for these blocks from the adminpresets_plug table.
1866              $sql = "adminpresetid = ? AND plugin='block' AND name $blocksinsql";
1867              $params = array_merge([$fullpreset->id], $blocksinparams);
1868              $DB->delete_records_select('adminpresets_plug', $sql, $params);
1869          }
1870  
1871          // Main savepoint reached.
1872          upgrade_main_savepoint(true, 2022011100.01);
1873      }
1874  
1875      if ($oldversion < 2022012100.02) {
1876          // Migrate default message output config.
1877          $preferences = get_config('message');
1878  
1879          $treatedprefs = [];
1880  
1881          foreach ($preferences as $preference => $value) {
1882              // Extract provider and preference name from the setting name.
1883              // Example name: airnotifier_provider_enrol_imsenterprise_imsenterprise_enrolment_permitted
1884              // Provider: airnotifier
1885              // Preference: enrol_imsenterprise_imsenterprise_enrolment_permitted.
1886              $providerparts = explode('_provider_', $preference);
1887              if (count($providerparts) <= 1) {
1888                  continue;
1889              }
1890  
1891              $provider = $providerparts[0];
1892              $preference = $providerparts[1];
1893  
1894              // Extract and remove last part of the preference previously extracted: ie. permitted.
1895              $parts = explode('_', $preference);
1896              $key = array_pop($parts);
1897  
1898              if (in_array($key, ['permitted', 'loggedin', 'loggedoff'])) {
1899                  if ($key == 'permitted') {
1900                      // We will use provider name instead of permitted.
1901                      $key = $provider;
1902                  } else {
1903                      // Logged in and logged off values are a csv of the enabled providers.
1904                      $value = explode(',', $value);
1905                  }
1906  
1907                  // Join the rest of the parts: ie enrol_imsenterprise_imsenterprise_enrolment.
1908                  $prefname = implode('_', $parts);
1909  
1910                  if (!isset($treatedprefs[$prefname])) {
1911                      $treatedprefs[$prefname] = [];
1912                  }
1913  
1914                  // Save the value with the selected key.
1915                  $treatedprefs[$prefname][$key] = $value;
1916              }
1917          }
1918  
1919          // Now take every preference previous treated and its values.
1920          foreach ($treatedprefs as $prefname => $values) {
1921              $enabled = []; // List of providers enabled for each preference.
1922  
1923              // Enable if one of those is enabled.
1924              $loggedin = isset($values['loggedin']) ? $values['loggedin'] : [];
1925              foreach ($loggedin as $provider) {
1926                  $enabled[$provider] = 1;
1927              }
1928              $loggedoff = isset($values['loggedoff']) ? $values['loggedoff'] : [];
1929              foreach ($loggedoff as $provider) {
1930                  $enabled[$provider] = 1;
1931              }
1932  
1933              // Do not treat those values again.
1934              unset($values['loggedin']);
1935              unset($values['loggedoff']);
1936  
1937              // Translate rest of values coming from permitted "key".
1938              foreach ($values as $provider => $value) {
1939                  $locked = false;
1940  
1941                  switch ($value) {
1942                      case 'forced':
1943                          // Provider is enabled by force.
1944                          $enabled[$provider] = 1;
1945                          $locked = true;
1946                          break;
1947                      case 'disallowed':
1948                          // Provider is disabled by force.
1949                          unset($enabled[$provider]);
1950                          $locked = true;
1951                          break;
1952                      default:
1953                          // Provider is not forced (permitted) or invalid values.
1954                  }
1955  
1956                  // Save locked.
1957                  if ($locked) {
1958                      set_config($provider.'_provider_'.$prefname.'_locked', 1, 'message');
1959                  } else {
1960                      set_config($provider.'_provider_'.$prefname.'_locked', 0, 'message');
1961                  }
1962                  // Remove old value.
1963                  unset_config($provider.'_provider_'.$prefname.'_permitted', 'message');
1964              }
1965  
1966              // Save the new values.
1967              $value = implode(',', array_keys($enabled));
1968              set_config('message_provider_'.$prefname.'_enabled', $value, 'message');
1969              // Remove old values.
1970              unset_config('message_provider_'.$prefname.'_loggedin', 'message');
1971              unset_config('message_provider_'.$prefname.'_loggedoff', 'message');
1972          }
1973  
1974          // Migrate user preferences. ie merging message_provider_moodle_instantmessage_loggedoff with
1975          // message_provider_moodle_instantmessage_loggedin to message_provider_moodle_instantmessage_enabled.
1976  
1977          $allrecordsloggedoff = $DB->sql_like('name', ':loggedoff');
1978          $total = $DB->count_records_select(
1979              'user_preferences',
1980              $allrecordsloggedoff,
1981              ['loggedoff' => 'message_provider_%_loggedoff']
1982          );
1983          $i = 0;
1984          if ($total == 0) {
1985              $total = 1; // Avoid division by zero.
1986          }
1987  
1988          // Show a progress bar.
1989          $pbar = new progress_bar('upgradeusernotificationpreferences', 500, true);
1990          $pbar->update($i, $total, "Upgrading user notifications preferences - $i/$total.");
1991  
1992          // We're migrating provider per provider to reduce memory usage.
1993          $providers = $DB->get_records('message_providers', null, 'name');
1994          foreach ($providers as $provider) {
1995              // 60 minutes to migrate each provider.
1996              upgrade_set_timeout(3600);
1997              $componentproviderbase = 'message_provider_'.$provider->component.'_'.$provider->name;
1998  
1999              $loggedinname = $componentproviderbase.'_loggedin';
2000              $loggedoffname = $componentproviderbase.'_loggedoff';
2001  
2002              // Change loggedin to enabled.
2003              $enabledname = $componentproviderbase.'_enabled';
2004              $DB->set_field('user_preferences', 'name', $enabledname, ['name' => $loggedinname]);
2005  
2006              $selectparams = [
2007                  'enabled' => $enabledname,
2008                  'loggedoff' => $loggedoffname,
2009              ];
2010              $sql = 'SELECT m1.id loggedoffid, m1.value as loggedoff, m2.value as enabled, m2.id as enabledid
2011                  FROM
2012                      (SELECT id, userid, value FROM {user_preferences} WHERE name = :loggedoff) m1
2013                  LEFT JOIN
2014                      (SELECT id, userid, value FROM {user_preferences} WHERE name = :enabled) m2
2015                      ON m1.userid = m2.userid';
2016  
2017              while (($rs = $DB->get_recordset_sql($sql, $selectparams, 0, 1000)) && $rs->valid()) {
2018                  // 10 minutes for every chunk.
2019                  upgrade_set_timeout(600);
2020  
2021                  $deleterecords = [];
2022                  $changename = [];
2023                  $changevalue = []; // Multidimensional array with possible values as key to reduce SQL queries.
2024                  foreach ($rs as $record) {
2025                      if (empty($record->enabledid)) {
2026                          // Enabled does not exists, change the name.
2027                          $changename[] = $record->loggedoffid;
2028                      } else if ($record->enabledid != $record->loggedoff) {
2029                          // Exist and values differ (checked on SQL), update the enabled record.
2030  
2031                          if ($record->enabled != 'none' && !empty($record->enabled)) {
2032                              $enabledvalues = explode(',', $record->enabled);
2033                          } else {
2034                              $enabledvalues = [];
2035                          }
2036  
2037                          if ($record->loggedoff != 'none' && !empty($record->loggedoff)) {
2038                              $loggedoffvalues = explode(',', $record->loggedoff);
2039                          } else {
2040                              $loggedoffvalues = [];
2041                          }
2042  
2043                          $values = array_unique(array_merge($enabledvalues, $loggedoffvalues));
2044                          sort($values);
2045  
2046                          $newvalue = empty($values) ? 'none' : implode(',', $values);
2047                          if (!isset($changevalue[$newvalue])) {
2048                              $changevalue[$newvalue] = [];
2049                          }
2050                          $changevalue[$newvalue][] = $record->enabledid;
2051  
2052                          $deleterecords[] = $record->loggedoffid;
2053                      } else {
2054                          // They are the same, just delete loggedoff one.
2055                          $deleterecords[] = $record->loggedoffid;
2056                      }
2057                      $i++;
2058                  }
2059                  $rs->close();
2060  
2061                  // Commit the changes.
2062                  if (!empty($changename)) {
2063                      $changenameparams = [
2064                          'name' => $loggedoffname,
2065                      ];
2066                      $changenameselect = 'name = :name AND id IN (' . implode(',', $changename) . ')';
2067                      $DB->set_field_select('user_preferences', 'name', $enabledname, $changenameselect, $changenameparams);
2068                  }
2069  
2070                  if (!empty($changevalue)) {
2071                      $changevalueparams = [
2072                          'name' => $enabledname,
2073                      ];
2074                      foreach ($changevalue as $value => $ids) {
2075                          $changevalueselect = 'name = :name AND id IN (' . implode(',', $ids) . ')';
2076                          $DB->set_field_select('user_preferences', 'value', $value, $changevalueselect, $changevalueparams);
2077                      }
2078                  }
2079  
2080                  if (!empty($deleterecords)) {
2081                      $deleteparams = [
2082                          'name' => $loggedoffname,
2083                      ];
2084                      $deleteselect = 'name = :name AND id IN (' . implode(',', $deleterecords) . ')';
2085                      $DB->delete_records_select('user_preferences', $deleteselect, $deleteparams);
2086                  }
2087  
2088                  // Update progress.
2089                  $pbar->update($i, $total, "Upgrading user notifications preferences - $i/$total.");
2090              }
2091              $rs->close();
2092  
2093              // Delete the rest of loggedoff values (that are equal than enabled).
2094              $deleteparams = [
2095                  'name' => $loggedoffname,
2096              ];
2097              $deleteselect = 'name = :name';
2098              $i += $DB->count_records_select('user_preferences', $deleteselect, $deleteparams);
2099              $DB->delete_records_select('user_preferences', $deleteselect, $deleteparams);
2100  
2101              // Update progress.
2102              $pbar->update($i, $total, "Upgrading user notifications preferences - $i/$total.");
2103          }
2104  
2105          core_plugin_manager::reset_caches();
2106  
2107          // Delete the orphan records.
2108          $allrecordsparams = ['loggedin' => 'message_provider_%_loggedin', 'loggedoff' => 'message_provider_%_loggedoff'];
2109          $allrecordsloggedin = $DB->sql_like('name', ':loggedin');
2110          $allrecordsloggedinoffsql = "$allrecordsloggedin OR $allrecordsloggedoff";
2111          $DB->delete_records_select('user_preferences', $allrecordsloggedinoffsql, $allrecordsparams);
2112  
2113          // Update progress.
2114          $pbar->update($total, $total, "Upgrading user notifications preferences - $total/$total.");
2115  
2116          upgrade_main_savepoint(true, 2022012100.02);
2117      }
2118  
2119      // Introduce question versioning to core.
2120      // First, create the new tables.
2121      if ($oldversion < 2022020200.01) {
2122          // Define table question_bank_entries to be created.
2123          $table = new xmldb_table('question_bank_entries');
2124  
2125          // Adding fields to table question_bank_entries.
2126          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2127          $table->add_field('questioncategoryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
2128          $table->add_field('idnumber', XMLDB_TYPE_CHAR, '100', null, null, null, null);
2129          $table->add_field('ownerid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
2130  
2131          // Adding keys to table question_bank_entries.
2132          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
2133          $table->add_key('questioncategoryid', XMLDB_KEY_FOREIGN, ['questioncategoryid'], 'question_categories', ['id']);
2134          $table->add_key('ownerid', XMLDB_KEY_FOREIGN, ['ownerid'], 'user', ['id']);
2135  
2136          // Conditionally launch create table for question_bank_entries.
2137          if (!$dbman->table_exists($table)) {
2138              $dbman->create_table($table);
2139          }
2140  
2141          // Create category id and id number index.
2142          $index = new xmldb_index('categoryidnumber', XMLDB_INDEX_UNIQUE, ['questioncategoryid', 'idnumber']);
2143  
2144          // Conditionally launch add index categoryidnumber.
2145          if (!$dbman->index_exists($table, $index)) {
2146              $dbman->add_index($table, $index);
2147          }
2148  
2149          // Define table question_versions to be created.
2150          $table = new xmldb_table('question_versions');
2151  
2152          // Adding fields to table question_versions.
2153          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2154          $table->add_field('questionbankentryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
2155          $table->add_field('version', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 1);
2156          $table->add_field('questionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
2157          $table->add_field('status', XMLDB_TYPE_CHAR, '10', null, XMLDB_NOTNULL, null, 'ready');
2158  
2159          // Adding keys to table question_versions.
2160          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
2161          $table->add_key('questionbankentryid', XMLDB_KEY_FOREIGN, ['questionbankentryid'], 'question_bank_entries', ['id']);
2162          $table->add_key('questionid', XMLDB_KEY_FOREIGN, ['questionid'], 'question', ['id']);
2163  
2164          // Conditionally launch create table for question_versions.
2165          if (!$dbman->table_exists($table)) {
2166              $dbman->create_table($table);
2167          }
2168  
2169          // Define table question_references to be created.
2170          $table = new xmldb_table('question_references');
2171  
2172          // Adding fields to table question_references.
2173          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2174          $table->add_field('usingcontextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
2175          $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null);
2176          $table->add_field('questionarea', XMLDB_TYPE_CHAR, '50', null, null, null, null);
2177          $table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
2178          $table->add_field('questionbankentryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
2179          $table->add_field('version', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
2180  
2181          // Adding keys to table question_references.
2182          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
2183          $table->add_key('usingcontextid', XMLDB_KEY_FOREIGN, ['usingcontextid'], 'context', ['id']);
2184          $table->add_key('questionbankentryid', XMLDB_KEY_FOREIGN, ['questionbankentryid'], 'question_bank_entries', ['id']);
2185  
2186          // Conditionally launch create table for question_references.
2187          if (!$dbman->table_exists($table)) {
2188              $dbman->create_table($table);
2189          }
2190  
2191          // Define table question_set_references to be created.
2192          $table = new xmldb_table('question_set_references');
2193  
2194          // Adding fields to table question_set_references.
2195          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2196          $table->add_field('usingcontextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
2197          $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null);
2198          $table->add_field('questionarea', XMLDB_TYPE_CHAR, '50', null, null, null, null);
2199          $table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
2200          $table->add_field('questionscontextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0);
2201          $table->add_field('filtercondition', XMLDB_TYPE_TEXT, null, null, null, null, null);
2202  
2203          // Adding keys to table question_set_references.
2204          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
2205          $table->add_key('usingcontextid', XMLDB_KEY_FOREIGN, ['usingcontextid'], 'context', ['id']);
2206          $table->add_key('questionscontextid', XMLDB_KEY_FOREIGN, ['questionscontextid'], 'context', ['id']);
2207  
2208          // Conditionally launch create table for question_set_references.
2209          if (!$dbman->table_exists($table)) {
2210              $dbman->create_table($table);
2211          }
2212  
2213          // Main savepoint reached.
2214          upgrade_main_savepoint(true, 2022020200.01);
2215      }
2216  
2217      if ($oldversion < 2022020200.02) {
2218          // Define a new temporary field in the question_bank_entries tables.
2219          // Creating temporary field questionid to populate the data in question version table.
2220          // This will make sure the appropriate question id is inserted the version table without making any complex joins.
2221          $table = new xmldb_table('question_bank_entries');
2222          $field = new xmldb_field('questionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL);
2223          if (!$dbman->field_exists($table, $field)) {
2224              $dbman->add_field($table, $field);
2225          }
2226  
2227          $transaction = $DB->start_delegated_transaction();
2228          upgrade_set_timeout(3600);
2229          // Create the data for the question_bank_entries table with, including the new temporary field.
2230          $sql = <<<EOF
2231              INSERT INTO {question_bank_entries}
2232                  (questionid, questioncategoryid, idnumber, ownerid)
2233              SELECT id, category, idnumber, createdby
2234              FROM {question} q
2235              EOF;
2236  
2237          // Inserting question_bank_entries data.
2238          $DB->execute($sql);
2239  
2240          $transaction->allow_commit();
2241  
2242          // Main savepoint reached.
2243          upgrade_main_savepoint(true, 2022020200.02);
2244      }
2245  
2246      if ($oldversion < 2022020200.03) {
2247          $transaction = $DB->start_delegated_transaction();
2248          upgrade_set_timeout(3600);
2249          // Create the question_versions using that temporary field.
2250          $sql = <<<EOF
2251              INSERT INTO {question_versions}
2252                  (questionbankentryid, questionid, status)
2253              SELECT
2254                  qbe.id,
2255                  q.id,
2256                  CASE
2257                      WHEN q.hidden > 0 THEN 'hidden'
2258                      ELSE 'ready'
2259                  END
2260              FROM {question_bank_entries} qbe
2261              INNER JOIN {question} q ON qbe.questionid = q.id
2262              EOF;
2263  
2264          // Inserting question_versions data.
2265          $DB->execute($sql);
2266  
2267          $transaction->allow_commit();
2268  
2269          // Dropping temporary field questionid.
2270          $table = new xmldb_table('question_bank_entries');
2271          $field = new xmldb_field('questionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL);
2272          if ($dbman->field_exists($table, $field)) {
2273              $dbman->drop_field($table, $field);
2274          }
2275  
2276          // Main savepoint reached.
2277          upgrade_main_savepoint(true, 2022020200.03);
2278      }
2279  
2280      if ($oldversion < 2022020200.04) {
2281          $transaction = $DB->start_delegated_transaction();
2282          upgrade_set_timeout(3600);
2283          // Create the base data for the random questions in the set_references table.
2284          // This covers most of the hard work in one go.
2285          $concat = $DB->sql_concat("'{\"questioncategoryid\":\"'", 'q.category', "'\",\"includingsubcategories\":\"'",
2286              'qs.includingsubcategories', "'\"}'");
2287          $sql = <<<EOF
2288              INSERT INTO {question_set_references}
2289              (usingcontextid, component, questionarea, itemid, questionscontextid, filtercondition)
2290              SELECT
2291                  c.id,
2292                  'mod_quiz',
2293                  'slot',
2294                  qs.id,
2295                  qc.contextid,
2296                  $concat
2297              FROM {question} q
2298              INNER JOIN {quiz_slots} qs on q.id = qs.questionid
2299              INNER JOIN {course_modules} cm ON cm.instance = qs.quizid AND cm.module = :quizmoduleid
2300              INNER JOIN {context} c ON cm.id = c.instanceid AND c.contextlevel = :contextmodule
2301              INNER JOIN {question_categories} qc ON qc.id = q.category
2302              WHERE q.qtype = :random
2303              EOF;
2304  
2305          // Inserting question_set_references data.
2306          $DB->execute($sql, [
2307              'quizmoduleid' => $DB->get_field('modules', 'id', ['name' => 'quiz']),
2308              'contextmodule' => CONTEXT_MODULE,
2309              'random' => 'random',
2310          ]);
2311  
2312          $transaction->allow_commit();
2313  
2314          // Main savepoint reached.
2315          upgrade_main_savepoint(true, 2022020200.04);
2316      }
2317  
2318      if ($oldversion < 2022020200.05) {
2319          $transaction = $DB->start_delegated_transaction();
2320          upgrade_set_timeout(3600);
2321  
2322          // Count all the slot tags to be migrated (for progress bar).
2323          $total = $DB->count_records('quiz_slot_tags');
2324          $pbar = new progress_bar('migratequestiontags', 1000, true);
2325          $i = 0;
2326          // Updating slot_tags for random question tags.
2327          // Now fetch any quiz slot tags and update those slot details into the question_set_references.
2328          $slottags = $DB->get_recordset('quiz_slot_tags', [], 'slotid ASC');
2329  
2330          $tagstrings = [];
2331          $lastslot = null;
2332          $runinsert = function (int $lastslot, array $tagstrings) use ($DB) {
2333              $conditiondata = $DB->get_field('question_set_references', 'filtercondition',
2334                  ['itemid' => $lastslot, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
2335  
2336              // It is possible to have leftover tags in the database, without a corresponding
2337              // slot, because of an old bugs (e.g. MDL-76193). Therefore, if the slot is not found,
2338              // we can safely discard these tags.
2339              if (!empty($conditiondata)) {
2340                  $condition = json_decode($conditiondata);
2341                  $condition->tags = $tagstrings;
2342                  $DB->set_field('question_set_references', 'filtercondition', json_encode($condition),
2343                          ['itemid' => $lastslot, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
2344              }
2345          };
2346  
2347          foreach ($slottags as $tag) {
2348              upgrade_set_timeout(3600);
2349              if ($lastslot && $tag->slotid != $lastslot) {
2350                  if (!empty($tagstrings)) {
2351                      // Insert the data.
2352                      $runinsert($lastslot, $tagstrings);
2353                  }
2354                  // Prepare for the next slot id.
2355                  $tagstrings = [];
2356              }
2357  
2358              $lastslot = $tag->slotid;
2359              $tagstrings[] = "{$tag->tagid},{$tag->tagname}";
2360              // Update progress.
2361              $i++;
2362              $pbar->update($i, $total, "Migrating question tags - $i/$total.");
2363          }
2364          if ($tagstrings) {
2365              $runinsert($lastslot, $tagstrings);
2366          }
2367          $slottags->close();
2368  
2369          $transaction->allow_commit();
2370          // Main savepoint reached.
2371          upgrade_main_savepoint(true, 2022020200.05);
2372      }
2373  
2374      if ($oldversion < 2022020200.06) {
2375          $transaction = $DB->start_delegated_transaction();
2376          upgrade_set_timeout(3600);
2377          // Create question_references record for each question.
2378          // Except if qtype is random. That case is handled by question_set_reference.
2379          $sql = "INSERT INTO {question_references}
2380                          (usingcontextid, component, questionarea, itemid, questionbankentryid)
2381                   SELECT c.id, 'mod_quiz', 'slot', qs.id, qv.questionbankentryid
2382                     FROM {question} q
2383                     JOIN {question_versions} qv ON q.id = qv.questionid
2384                     JOIN {quiz_slots} qs ON q.id = qs.questionid
2385                     JOIN {modules} m ON m.name = 'quiz'
2386                     JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = qs.quizid
2387                     JOIN {context} c ON c.instanceid = cm.id AND c.contextlevel = " . CONTEXT_MODULE . "
2388                    WHERE q.qtype <> 'random'";
2389  
2390          // Inserting question_references data.
2391          $DB->execute($sql);
2392  
2393          $transaction->allow_commit();
2394          // Main savepoint reached.
2395          upgrade_main_savepoint(true, 2022020200.06);
2396      }
2397  
2398      // Finally, drop fields from question table.
2399      if ($oldversion < 2022020200.07) {
2400          // Define fields to be dropped from questions.
2401          $table = new xmldb_table('question');
2402  
2403          $field = new xmldb_field('version');
2404          // Conditionally launch drop field version.
2405          if ($dbman->field_exists($table, $field)) {
2406              $dbman->drop_field($table, $field);
2407          }
2408  
2409          $field = new xmldb_field('hidden');
2410          // Conditionally launch drop field hidden.
2411          if ($dbman->field_exists($table, $field)) {
2412              $dbman->drop_field($table, $field);
2413          }
2414  
2415          // Define index categoryidnumber (not unique) to be dropped form question.
2416          $index = new xmldb_index('categoryidnumber', XMLDB_INDEX_UNIQUE, ['category', 'idnumber']);
2417  
2418          // Conditionally launch drop index categoryidnumber.
2419          if ($dbman->index_exists($table, $index)) {
2420              $dbman->drop_index($table, $index);
2421          }
2422  
2423          // Define key category (foreign) to be dropped form questions.
2424          $key = new xmldb_key('category', XMLDB_KEY_FOREIGN, ['category'], 'question_categories', ['id']);
2425  
2426          // Launch drop key category.
2427          $dbman->drop_key($table, $key);
2428  
2429          $field = new xmldb_field('idnumber');
2430          // Conditionally launch drop field idnumber.
2431          if ($dbman->field_exists($table, $field)) {
2432              $dbman->drop_field($table, $field);
2433          }
2434  
2435          $field = new xmldb_field('category');
2436          // Conditionally launch drop field category.
2437          if ($dbman->field_exists($table, $field)) {
2438              $dbman->drop_field($table, $field);
2439          }
2440  
2441          // Main savepoint reached.
2442          upgrade_main_savepoint(true, 2022020200.07);
2443      }
2444  
2445      if ($oldversion < 2022021100.01) {
2446          $sql = "SELECT preset.*
2447                    FROM {adminpresets} preset
2448              INNER JOIN {adminpresets_it} it ON preset.id = it.adminpresetid
2449                   WHERE it.name = :name AND it.value = :value AND preset.iscore > 0";
2450          // Some settings and plugins have been added/removed to the Starter and Full preset. Add them to the core presets if
2451          // they haven't been included yet.
2452          $params = ['name' => get_string('starterpreset', 'core_adminpresets'), 'iscore' => 1];
2453          $starterpreset = $DB->get_record('adminpresets', $params);
2454          if (!$starterpreset) {
2455              // Starter admin preset might have been created using the English name.
2456              $name = get_string_manager()->get_string('starterpreset', 'core_adminpresets', null, 'en');
2457              $params['name'] = $name;
2458              $starterpreset = $DB->get_record('adminpresets', $params);
2459          }
2460          if (!$starterpreset) {
2461              // We tried, but we didn't find starter by name. Let's find a core preset that sets 'usecomments' setting to 0.
2462              $params = ['name' => 'usecomments', 'value' => '0'];
2463              $starterpreset = $DB->get_record_sql($sql, $params);
2464          }
2465  
2466          $params = ['name' => get_string('fullpreset', 'core_adminpresets')];
2467          $fullpreset = $DB->get_record_select('adminpresets', 'name = :name and iscore > 0', $params);
2468          if (!$fullpreset) {
2469              // Full admin preset might have been created using the English name.
2470              $name = get_string_manager()->get_string('fullpreset', 'core_adminpresets', null, 'en');
2471              $params['name'] = $name;
2472              $fullpreset = $DB->get_record_select('adminpresets', 'name = :name and iscore > 0', $params);
2473          }
2474          if (!$fullpreset) {
2475              // We tried, but we didn't find full by name. Let's find a core preset that sets 'usecomments' setting to 1.
2476              $params = ['name' => 'usecomments', 'value' => '1'];
2477              $fullpreset = $DB->get_record_sql($sql, $params);
2478          }
2479  
2480          $settings = [
2481              // Settings. Hide Guest login button for Starter preset (and back to show for Full).
2482              [
2483                  'presetid' => $starterpreset->id,
2484                  'plugin' => 'none',
2485                  'name' => 'guestloginbutton',
2486                  'value' => '0',
2487              ],
2488              [
2489                  'presetid' => $fullpreset->id,
2490                  'plugin' => 'none',
2491                  'name' => 'guestloginbutton',
2492                  'value' => '1',
2493              ],
2494              // Settings. Set Activity chooser tabs to "Starred, All, Recommended"(1) for Starter and back it to default(0) for Full.
2495              [
2496                  'presetid' => $starterpreset->id,
2497                  'plugin' => 'none',
2498                  'name' => 'activitychoosertabmode',
2499                  'value' => '1',
2500              ],
2501              [
2502                  'presetid' => $fullpreset->id,
2503                  'plugin' => 'none',
2504                  'name' => 'activitychoosertabmode',
2505                  'value' => '0',
2506              ],
2507          ];
2508          foreach ($settings as $notused => $setting) {
2509              $params = ['adminpresetid' => $setting['presetid'], 'plugin' => $setting['plugin'], 'name' => $setting['name']];
2510              if (!$DB->record_exists('adminpresets_it', $params)) {
2511                  $record = new \stdClass();
2512                  $record->adminpresetid = $setting['presetid'];
2513                  $record->plugin = $setting['plugin'];
2514                  $record->name = $setting['name'];
2515                  $record->value = $setting['value'];
2516                  $DB->insert_record('adminpresets_it', $record);
2517              }
2518          }
2519  
2520          $plugins = [
2521              // Plugins. Blocks. Disable/enable Online users, Recently accessed courses and Starred courses.
2522              [
2523                  'presetid' => $starterpreset->id,
2524                  'plugin' => 'block',
2525                  'name' => 'online_users',
2526                  'enabled' => '0',
2527              ],
2528              [
2529                  'presetid' => $fullpreset->id,
2530                  'plugin' => 'block',
2531                  'name' => 'online_users',
2532                  'enabled' => '1',
2533              ],
2534              [
2535                  'presetid' => $starterpreset->id,
2536                  'plugin' => 'block',
2537                  'name' => 'recentlyaccessedcourses',
2538                  'enabled' => '0',
2539              ],
2540              [
2541                  'presetid' => $fullpreset->id,
2542                  'plugin' => 'block',
2543                  'name' => 'recentlyaccessedcourses',
2544                  'enabled' => '1',
2545              ],
2546              [
2547                  'presetid' => $starterpreset->id,
2548                  'plugin' => 'block',
2549                  'name' => 'starredcourses',
2550                  'enabled' => '0',
2551              ],
2552              [
2553                  'presetid' => $fullpreset->id,
2554                  'plugin' => 'block',
2555                  'name' => 'starredcourses',
2556                  'enabled' => '1',
2557              ],
2558              // Plugins. Enrolments. Disable/enable Guest access.
2559              [
2560                  'presetid' => $starterpreset->id,
2561                  'plugin' => 'enrol',
2562                  'name' => 'guest',
2563                  'enabled' => '0',
2564              ],
2565              [
2566                  'presetid' => $fullpreset->id,
2567                  'plugin' => 'enrol',
2568                  'name' => 'guest',
2569                  'enabled' => '1',
2570              ],
2571          ];
2572          foreach ($plugins as $notused => $plugin) {
2573              $params = ['adminpresetid' => $plugin['presetid'], 'plugin' => $plugin['plugin'], 'name' => $plugin['name']];
2574              if (!$DB->record_exists('adminpresets_plug', $params)) {
2575                  $record = new \stdClass();
2576                  $record->adminpresetid = $plugin['presetid'];
2577                  $record->plugin = $plugin['plugin'];
2578                  $record->name = $plugin['name'];
2579                  $record->enabled = $plugin['enabled'];
2580                  $DB->insert_record('adminpresets_plug', $record);
2581              }
2582          }
2583  
2584          // Settings: Remove customusermenuitems setting from Starter and Full presets.
2585          $sql = "(adminpresetid = ? OR adminpresetid = ?) AND plugin = 'none' AND name = 'customusermenuitems'";
2586          $params = [$starterpreset->id, $fullpreset->id];
2587          $DB->delete_records_select('adminpresets_it', $sql, $params);
2588  
2589          // Plugins. Question types. Re-enable Description and Essay for Starter.
2590          $sql = "(adminpresetid = ? OR adminpresetid = ?) AND plugin = 'qtype' AND (name = 'description' OR name = 'essay')";
2591          $DB->delete_records_select('adminpresets_plug', $sql, $params);
2592  
2593          // Main savepoint reached.
2594          upgrade_main_savepoint(true, 2022021100.01);
2595  
2596      }
2597  
2598      if ($oldversion < 2022021100.02) {
2599          $table = new xmldb_table('task_scheduled');
2600  
2601          // Changing precision of field minute on table task_scheduled to (200).
2602          $field = new xmldb_field('minute', XMLDB_TYPE_CHAR, '200', null, XMLDB_NOTNULL, null, null, 'blocking');
2603          $dbman->change_field_precision($table, $field);
2604          // Changing precision of field hour on table task_scheduled to (70).
2605          $field = new xmldb_field('hour', XMLDB_TYPE_CHAR, '70', null, XMLDB_NOTNULL, null, null, 'minute');
2606          $dbman->change_field_precision($table, $field);
2607          // Changing precision of field day on table task_scheduled to (90).
2608          $field = new xmldb_field('day', XMLDB_TYPE_CHAR, '90', null, XMLDB_NOTNULL, null, null, 'hour');
2609          $dbman->change_field_precision($table, $field);
2610          // Changing precision of field month on table task_scheduled to (30).
2611          $field = new xmldb_field('month', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, null, 'day');
2612          $dbman->change_field_precision($table, $field);
2613  
2614          // Main savepoint reached.
2615          upgrade_main_savepoint(true, 2022021100.02);
2616      }
2617  
2618      if ($oldversion < 2022022600.01) {
2619          // Get all processor and existing preferences.
2620          $processors = $DB->get_records('message_processors');
2621          $providers = $DB->get_records('message_providers', null, '', 'id, name, component');
2622          $existingpreferences = get_config('message');
2623  
2624          foreach ($processors as $processor) {
2625              foreach ($providers as $provider) {
2626                  // Setting default preference name.
2627                  $componentproviderbase = $provider->component . '_' . $provider->name;
2628                  $preferencename = $processor->name.'_provider_'.$componentproviderbase.'_locked';
2629                  // If we do not have this setting yet, set it to 0.
2630                  if (!isset($existingpreferences->{$preferencename})) {
2631                      set_config($preferencename, 0, 'message');
2632                  }
2633              }
2634          }
2635  
2636          upgrade_main_savepoint(true, 2022022600.01);
2637      }
2638  
2639      if ($oldversion < 2022030100.00) {
2640          $sql = "SELECT preset.*
2641                    FROM {adminpresets} preset
2642              INNER JOIN {adminpresets_it} it ON preset.id = it.adminpresetid
2643                   WHERE it.name = :name AND it.value = :value AND preset.iscore > 0";
2644  
2645          $name = get_string('starterpreset', 'core_adminpresets');
2646          $params = ['name' => $name, 'iscore' => 1];
2647          $starterpreset = $DB->get_record('adminpresets', $params);
2648          if (!$starterpreset) {
2649              // Starter admin preset might have been created using the English name. Let's change it to current language.
2650              $englishname = get_string_manager()->get_string('starterpreset', 'core_adminpresets', null, 'en');
2651              $params['name'] = $englishname;
2652              $starterpreset = $DB->get_record('adminpresets', $params);
2653          }
2654          if (!$starterpreset) {
2655              // We tried, but we didn't find starter by name. Let's find a core preset that sets 'usecomments' setting to 0.
2656              $params = ['name' => 'usecomments', 'value' => '0'];
2657              $starterpreset = $DB->get_record_sql($sql, $params);
2658          }
2659          // The iscore field is already 1 for starterpreset, so we don't need to change it.
2660          // We only need to update the name and comment in case they are different to current language strings.
2661          if ($starterpreset && $starterpreset->name != $name) {
2662              $starterpreset->name = $name;
2663              $starterpreset->comments = get_string('starterpresetdescription', 'core_adminpresets');
2664              $DB->update_record('adminpresets', $starterpreset);
2665          }
2666  
2667          // Let's mark Full admin presets with current FULL_PRESETS value and change the name to current language.
2668          $name = get_string('fullpreset', 'core_adminpresets');
2669          $params = ['name' => $name];
2670          $fullpreset = $DB->get_record_select('adminpresets', 'name = :name and iscore > 0', $params);
2671          if (!$fullpreset) {
2672              // Full admin preset might have been created using the English name.
2673              $englishname = get_string_manager()->get_string('fullpreset', 'core_adminpresets', null, 'en');
2674              $params['name'] = $englishname;
2675              $fullpreset = $DB->get_record_select('adminpresets', 'name = :name and iscore > 0', $params);
2676          }
2677          if (!$fullpreset) {
2678              // We tried, but we didn't find full by name. Let's find a core preset that sets 'usecomments' setting to 1.
2679              $params = ['name' => 'usecomments', 'value' => '1'];
2680              $fullpreset = $DB->get_record_sql($sql, $params);
2681          }
2682          if ($fullpreset) {
2683              // We need to update iscore field value, whether the name is the same or not.
2684              $fullpreset->name = $name;
2685              $fullpreset->comments = get_string('fullpresetdescription', 'core_adminpresets');
2686              $fullpreset->iscore = 2;
2687              $DB->update_record('adminpresets', $fullpreset);
2688  
2689              // We are applying again changes made on 2022011100.01 upgrading step because of MDL-73953 bug.
2690              $blocknames = ['course_summary', 'feedback', 'rss_client', 'selfcompletion'];
2691              list($blocksinsql, $blocksinparams) = $DB->get_in_or_equal($blocknames);
2692  
2693              // Remove entries from the adminpresets_app_plug table (in case the preset has been applied).
2694              $appliedpresets = $DB->get_records('adminpresets_app', ['adminpresetid' => $fullpreset->id], '', 'id');
2695              if ($appliedpresets) {
2696                  list($appsinsql, $appsinparams) = $DB->get_in_or_equal(array_keys($appliedpresets));
2697                  $sql = "adminpresetapplyid $appsinsql AND plugin='block' AND name $blocksinsql";
2698                  $params = array_merge($appsinparams, $blocksinparams);
2699                  $DB->delete_records_select('adminpresets_app_plug', $sql, $params);
2700              }
2701  
2702              // Remove entries for these blocks from the adminpresets_plug table.
2703              $sql = "adminpresetid = ? AND plugin='block' AND name $blocksinsql";
2704              $params = array_merge([$fullpreset->id], $blocksinparams);
2705              $DB->delete_records_select('adminpresets_plug', $sql, $params);
2706          }
2707  
2708          // Main savepoint reached.
2709          upgrade_main_savepoint(true, 2022030100.00);
2710      }
2711  
2712      if ($oldversion < 2022031100.01) {
2713          $reportsusermenuitem = 'reports,core_reportbuilder|/reportbuilder/index.php';
2714          upgrade_add_item_to_usermenu($reportsusermenuitem);
2715          // Main savepoint reached.
2716          upgrade_main_savepoint(true, 2022031100.01);
2717      }
2718  
2719      if ($oldversion < 2022032200.01) {
2720  
2721          // Define index to be added to question_references.
2722          $table = new xmldb_table('question_references');
2723          $index = new xmldb_index('context-component-area-itemid', XMLDB_INDEX_UNIQUE,
2724              ['usingcontextid', 'component', 'questionarea', 'itemid']);
2725  
2726          // Conditionally launch add field id.
2727          if (!$dbman->index_exists($table, $index)) {
2728              $dbman->add_index($table, $index);
2729          }
2730  
2731          // Main savepoint reached.
2732          upgrade_main_savepoint(true, 2022032200.01);
2733      }
2734  
2735      if ($oldversion < 2022032200.02) {
2736  
2737          // Define index to be added to question_references.
2738          $table = new xmldb_table('question_set_references');
2739          $index = new xmldb_index('context-component-area-itemid', XMLDB_INDEX_UNIQUE,
2740              ['usingcontextid', 'component', 'questionarea', 'itemid']);
2741  
2742          // Conditionally launch add field id.
2743          if (!$dbman->index_exists($table, $index)) {
2744              $dbman->add_index($table, $index);
2745          }
2746  
2747          // Main savepoint reached.
2748          upgrade_main_savepoint(true, 2022032200.02);
2749      }
2750  
2751      if ($oldversion < 2022041200.01) {
2752  
2753          // The original default admin presets "sensible settings" (those that should be treated as sensitive).
2754          $originalsensiblesettings = 'recaptchapublickey@@none, recaptchaprivatekey@@none, googlemapkey3@@none, ' .
2755              'secretphrase@@url, cronremotepassword@@none, smtpuser@@none, smtppass@none, proxypassword@@none, ' .
2756              'quizpassword@@quiz, allowedip@@none, blockedip@@none, dbpass@@logstore_database, messageinbound_hostpass@@none, ' .
2757              'bind_pw@@auth_cas, pass@@auth_db, bind_pw@@auth_ldap, dbpass@@enrol_database, bind_pw@@enrol_ldap, ' .
2758              'server_password@@search_solr, ssl_keypassword@@search_solr, alternateserver_password@@search_solr, ' .
2759              'alternatessl_keypassword@@search_solr, test_password@@cachestore_redis, password@@mlbackend_python';
2760  
2761          // Check if the current config matches the original default, upgrade to new default if so.
2762          if (get_config('adminpresets', 'sensiblesettings') === $originalsensiblesettings) {
2763              $newsensiblesettings = "{$originalsensiblesettings}, badges_badgesalt@@none, calendar_exportsalt@@none";
2764              set_config('sensiblesettings', $newsensiblesettings, 'adminpresets');
2765          }
2766  
2767          // Main savepoint reached.
2768          upgrade_main_savepoint(true, 2022041200.01);
2769      }
2770  
2771      // Automatically generated Moodle v4.0.0 release upgrade line.
2772      // Put any upgrade step following this.
2773  
2774      if ($oldversion < 2022042900.01) {
2775          // Social custom fields could had been created linked to category id = 1. Let's check category 1 exists.
2776          if (!$DB->get_record('user_info_category', ['id' => 1])) {
2777              // Let's check if we have any custom field linked to category id = 1.
2778              $fields = $DB->get_records('user_info_field', ['categoryid' => 1]);
2779              if (!empty($fields)) {
2780                  $categoryid = $DB->get_field_sql('SELECT min(id) from {user_info_category}');
2781                  foreach ($fields as $field) {
2782                      $field->categoryid = $categoryid;
2783                      $DB->update_record('user_info_field', $field);
2784                  }
2785              }
2786          }
2787  
2788          // Main savepoint reached.
2789          upgrade_main_savepoint(true, 2022042900.01);
2790      }
2791  
2792      if ($oldversion < 2022051000.00) {
2793          // Add index to the sid field in the external_tokens table.
2794          $table = new xmldb_table('external_tokens');
2795          $index = new xmldb_index('sid', XMLDB_INDEX_NOTUNIQUE, ['sid']);
2796  
2797          if (!$dbman->index_exists($table, $index)) {
2798              $dbman->add_index($table, $index);
2799          }
2800  
2801          upgrade_main_savepoint(true, 2022051000.00);
2802      }
2803  
2804      if ($oldversion < 2022052500.00) {
2805          // Start an adhoc task to fix the file timestamps of restored files.
2806          $task = new core\task\fix_file_timestamps_task();
2807          \core\task\manager::queue_adhoc_task($task);
2808  
2809          // Main savepoint reached.
2810          upgrade_main_savepoint(true, 2022052500.00);
2811      }
2812  
2813      if ($oldversion < 2022052700.01) {
2814  
2815          // Define index timestarted_idx (not unique) to be added to task_adhoc.
2816          $table = new xmldb_table('task_adhoc');
2817          $index = new xmldb_index('timestarted_idx', XMLDB_INDEX_NOTUNIQUE, ['timestarted']);
2818  
2819          // Conditionally launch add index timestarted_idx.
2820          if (!$dbman->index_exists($table, $index)) {
2821              $dbman->add_index($table, $index);
2822          }
2823  
2824          // Main savepoint reached.
2825          upgrade_main_savepoint(true, 2022052700.01);
2826      }
2827  
2828      if ($oldversion < 2022052700.02) {
2829  
2830          // Define index filename (not unique) to be added to files.
2831          $table = new xmldb_table('files');
2832          $index = new xmldb_index('filename', XMLDB_INDEX_NOTUNIQUE, ['filename']);
2833  
2834          // Conditionally launch add index filename.
2835          if (!$dbman->index_exists($table, $index)) {
2836              $dbman->add_index($table, $index);
2837          }
2838  
2839          // Main savepoint reached.
2840          upgrade_main_savepoint(true, 2022052700.02);
2841      }
2842  
2843      if ($oldversion < 2022060300.01) {
2844  
2845          // Changing precision of field hidden on table grade_categories to (10).
2846          $table = new xmldb_table('grade_categories');
2847          $field = new xmldb_field('hidden', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'timemodified');
2848  
2849          // Launch change of precision for field hidden.
2850          $dbman->change_field_precision($table, $field);
2851  
2852          // Changing precision of field hidden on table grade_categories_history to (10).
2853          $table = new xmldb_table('grade_categories_history');
2854          $field = new xmldb_field('hidden', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'aggregatesubcats');
2855  
2856          // Launch change of precision for field hidden.
2857          $dbman->change_field_precision($table, $field);
2858  
2859          // Main savepoint reached.
2860          upgrade_main_savepoint(true, 2022060300.01);
2861      }
2862  
2863      if ($oldversion < 2022061000.01) {
2864          // Iterate over custom user menu items configuration, removing pix icon references.
2865          $customusermenuitems = str_replace(["\r\n", "\r"], "\n", $CFG->customusermenuitems);
2866  
2867          $lines = preg_split('/\n/', $customusermenuitems, -1, PREG_SPLIT_NO_EMPTY);
2868          $lines = array_map(static function(string $line): string {
2869              // Previous format was "<langstring>|<url>[|<pixicon>]" - pix icon is no longer supported.
2870              $lineparts = explode('|', trim($line), 3);
2871              // Return first two parts of line.
2872              return implode('|', array_slice($lineparts, 0, 2));
2873          }, $lines);
2874  
2875          set_config('customusermenuitems', implode("\n", $lines));
2876  
2877          upgrade_main_savepoint(true, 2022061000.01);
2878      }
2879  
2880      if ($oldversion < 2022061500.00) {
2881          // Remove drawer-open-nav user preference for every user.
2882          $DB->delete_records('user_preferences', ['name' => 'drawer-open-nav']);
2883  
2884          // Main savepoint reached.
2885          upgrade_main_savepoint(true, 2022061500.00);
2886  
2887      }
2888  
2889      if ($oldversion < 2022072900.00) {
2890          // Call the helper function that updates the foreign keys and indexes in MDL-49795.
2891          upgrade_add_foreign_key_and_indexes();
2892  
2893          // Main savepoint reached.
2894          upgrade_main_savepoint(true, 2022072900.00);
2895      }
2896  
2897      if ($oldversion < 2022081200.01) {
2898  
2899          // Define field lang to be added to course_modules.
2900          $table = new xmldb_table('course_modules');
2901          $field = new xmldb_field('lang', XMLDB_TYPE_CHAR, '30', null, null, null, null, 'downloadcontent');
2902  
2903          // Conditionally launch add field lang.
2904          if (!$dbman->field_exists($table, $field)) {
2905              $dbman->add_field($table, $field);
2906          }
2907  
2908          // Main savepoint reached.
2909          upgrade_main_savepoint(true, 2022081200.01);
2910      }
2911  
2912      if ($oldversion < 2022091000.01) {
2913          $table = new xmldb_table('h5p');
2914          $indexpathnamehash = new xmldb_index('pathnamehash_idx', XMLDB_INDEX_NOTUNIQUE, ['pathnamehash']);
2915  
2916          if (!$dbman->index_exists($table, $indexpathnamehash)) {
2917              $dbman->add_index($table, $indexpathnamehash);
2918          }
2919          // Main savepoint reached.
2920          upgrade_main_savepoint(true, 2022091000.01);
2921      }
2922  
2923      if ($oldversion < 2022092200.01) {
2924  
2925          // Remove any orphaned tag instance records (pointing to non-existing context).
2926          $DB->delete_records_select('tag_instance', 'NOT EXISTS (
2927              SELECT ctx.id FROM {context} ctx WHERE ctx.id = {tag_instance}.contextid
2928          )');
2929  
2930          // Main savepoint reached.
2931          upgrade_main_savepoint(true, 2022092200.01);
2932      }
2933  
2934      if ($oldversion < 2022101400.01) {
2935          $table = new xmldb_table('competency_modulecomp');
2936          $field = new xmldb_field('overridegrade', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'ruleoutcome');
2937  
2938          if (!$dbman->field_exists($table, $field)) {
2939              $dbman->add_field($table, $field);
2940          }
2941  
2942          // Main savepoint reached.
2943          upgrade_main_savepoint(true, 2022101400.01);
2944      }
2945  
2946      if ($oldversion < 2022101400.03) {
2947          // Define table to store completion viewed.
2948          $table = new xmldb_table('course_modules_viewed');
2949  
2950          // Adding fields to table course_modules_viewed.
2951          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2952          $table->add_field('coursemoduleid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'id');
2953          $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'coursemoduleid');
2954          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'userid');
2955  
2956          // Adding keys to table course_modules_viewed.
2957          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
2958  
2959          // Adding indexes to table course_modules_viewed.
2960          $table->add_index('coursemoduleid', XMLDB_INDEX_NOTUNIQUE, ['coursemoduleid']);
2961          $table->add_index('userid-coursemoduleid', XMLDB_INDEX_UNIQUE, ['userid', 'coursemoduleid']);
2962  
2963          if (!$dbman->table_exists($table)) {
2964              $dbman->create_table($table);
2965          }
2966  
2967          // Main savepoint reached.
2968          upgrade_main_savepoint(true, 2022101400.03);
2969      }
2970  
2971      if ($oldversion < 2022101400.04) {
2972          // Add legacy data to the new table.
2973          $transaction = $DB->start_delegated_transaction();
2974          upgrade_set_timeout(3600);
2975          $sql = "INSERT INTO {course_modules_viewed}
2976                              (userid, coursemoduleid, timecreated)
2977                       SELECT userid, coursemoduleid, timemodified
2978                         FROM {course_modules_completion}
2979                        WHERE viewed = 1";
2980          $DB->execute($sql);
2981          $transaction->allow_commit();
2982  
2983          // Main savepoint reached.
2984          upgrade_main_savepoint(true, 2022101400.04);
2985      }
2986  
2987      if ($oldversion < 2022101400.05) {
2988          // Define field viewed to be dropped from course_modules_completion.
2989          $table = new xmldb_table('course_modules_completion');
2990          $field = new xmldb_field('viewed');
2991  
2992          // Conditionally launch drop field viewed.
2993          if ($dbman->field_exists($table, $field)) {
2994              $dbman->drop_field($table, $field);
2995          }
2996  
2997          // Main savepoint reached.
2998          upgrade_main_savepoint(true, 2022101400.05);
2999      }
3000  
3001      if ($oldversion < 2022102800.01) {
3002          // For sites with "contact site support" already available (4.0.x), maintain existing functionality.
3003          if ($oldversion >= 2022041900.00) {
3004              set_config('supportavailability', CONTACT_SUPPORT_ANYONE);
3005          } else {
3006              // Sites which did not previously have the "contact site support" feature default to it requiring authentication.
3007              set_config('supportavailability', CONTACT_SUPPORT_AUTHENTICATED);
3008          }
3009  
3010          // Main savepoint reached.
3011          upgrade_main_savepoint(true, 2022102800.01);
3012      }
3013  
3014      if ($oldversion < 2022110600.00) {
3015          // If webservice_xmlrpc isn't any longer installed, remove its configuration,
3016          // capabilities and presence in other settings.
3017          if (!file_exists($CFG->dirroot . '/webservice/xmlrpc/version.php')) {
3018              // No DB structures to delete in this plugin.
3019  
3020              // Remove capabilities.
3021              capabilities_cleanup('webservice_xmlrpc');
3022  
3023              // Remove own configuration.
3024              unset_all_config_for_plugin('webservice_xmlrpc');
3025  
3026              // Remove it from the enabled protocols if it was there.
3027              $protos = get_config('core', 'webserviceprotocols');
3028              $protoarr = explode(',', $protos);
3029              $protoarr = array_filter($protoarr, function($ele) {
3030                  return trim($ele) !== 'xmlrpc';
3031              });
3032              $protos = implode(',', $protoarr);
3033              set_config('webserviceprotocols', $protos);
3034          }
3035  
3036          // Main savepoint reached.
3037          upgrade_main_savepoint(true, 2022110600.00);
3038      }
3039  
3040      // Automatically generated Moodle v4.1.0 release upgrade line.
3041      // Put any upgrade step following this.
3042  
3043      if ($oldversion < 2022112800.03) {
3044  
3045          // Remove any orphaned role assignment records (pointing to non-existing roles).
3046          $DB->delete_records_select('role_assignments', 'NOT EXISTS (
3047              SELECT r.id FROM {role} r WHERE r.id = {role_assignments}.roleid
3048          )');
3049  
3050          // Main savepoint reached.
3051          upgrade_main_savepoint(true, 2022112800.03);
3052      }
3053  
3054      if ($oldversion < 2022112803.03) {
3055          // Add public key field to user_devices table.
3056          $table = new xmldb_table('user_devices');
3057          $field = new xmldb_field('publickey', XMLDB_TYPE_TEXT, null, null, null, null, null, 'uuid');
3058  
3059          if (!$dbman->field_exists($table, $field)) {
3060              $dbman->add_field($table, $field);
3061          }
3062  
3063          // Main savepoint reached.
3064          upgrade_main_savepoint(true, 2022112803.03);
3065      }
3066  
3067      if ($oldversion < 2022112804.09) {
3068          // Upgrade yaml mime type for existing yaml and yml files.
3069          $filetypes = [
3070              '%.yaml' => 'application/yaml',
3071              '%.yml' => 'application/yaml,'
3072          ];
3073  
3074          $select = $DB->sql_like('filename', '?', false);
3075          foreach ($filetypes as $extension => $mimetype) {
3076              $DB->set_field_select(
3077                  'files',
3078                  'mimetype',
3079                  $mimetype,
3080                  $select,
3081                  [$extension]
3082              );
3083          }
3084  
3085          // Main savepoint reached.
3086          upgrade_main_savepoint(true, 2022112804.09);
3087      }
3088  
3089      if ($oldversion < 2022112805.03) {
3090  
3091          // The previous default configuration had a typo, check for its presence and correct if necessary.
3092          $sensiblesettings = get_config('adminpresets', 'sensiblesettings');
3093          if (strpos($sensiblesettings, 'smtppass@none') !== false) {
3094              $newsensiblesettings = str_replace('smtppass@none', 'smtppass@@none', $sensiblesettings);
3095              set_config('sensiblesettings', $newsensiblesettings, 'adminpresets');
3096          }
3097  
3098          // Main savepoint reached.
3099          upgrade_main_savepoint(true, 2022112805.03);
3100      }
3101  
3102      if ($oldversion < 2022112805.11) {
3103          upgrade_core_licenses();
3104          upgrade_main_savepoint(true, 2022112805.11);
3105      }
3106  
3107      if ($oldversion < 2022112805.14) {
3108          // Delete datakey with datavalue -1.
3109          $DB->delete_records('messageinbound_datakeys', ['datavalue' => '-1']);
3110          // Main savepoint reached.
3111          upgrade_main_savepoint(true, 2022112805.14);
3112      }
3113  
3114      return true;
3115  }