Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
/lib/ -> upgradelib.php (source)

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * Various upgrade/install related functions and classes.
  20   *
  21   * @package    core
  22   * @subpackage upgrade
  23   * @copyright  1999 onwards Martin Dougiamas (http://dougiamas.com)
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /** UPGRADE_LOG_NORMAL = 0 */
  30  define('UPGRADE_LOG_NORMAL', 0);
  31  /** UPGRADE_LOG_NOTICE = 1 */
  32  define('UPGRADE_LOG_NOTICE', 1);
  33  /** UPGRADE_LOG_ERROR = 2 */
  34  define('UPGRADE_LOG_ERROR',  2);
  35  
  36  /**
  37   * Exception indicating unknown error during upgrade.
  38   *
  39   * @package    core
  40   * @subpackage upgrade
  41   * @copyright  2009 Petr Skoda {@link http://skodak.org}
  42   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   */
  44  class upgrade_exception extends moodle_exception {
  45      function __construct($plugin, $version, $debuginfo=NULL) {
  46          global $CFG;
  47          $a = (object)array('plugin'=>$plugin, 'version'=>$version);
  48          parent::__construct('upgradeerror', 'admin', "$CFG->wwwroot/$CFG->admin/index.php", $a, $debuginfo);
  49      }
  50  }
  51  
  52  /**
  53   * Exception indicating downgrade error during upgrade.
  54   *
  55   * @package    core
  56   * @subpackage upgrade
  57   * @copyright  2009 Petr Skoda {@link http://skodak.org}
  58   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  59   */
  60  class downgrade_exception extends moodle_exception {
  61      function __construct($plugin, $oldversion, $newversion) {
  62          global $CFG;
  63          $plugin = is_null($plugin) ? 'moodle' : $plugin;
  64          $a = (object)array('plugin'=>$plugin, 'oldversion'=>$oldversion, 'newversion'=>$newversion);
  65          parent::__construct('cannotdowngrade', 'debug', "$CFG->wwwroot/$CFG->admin/index.php", $a);
  66      }
  67  }
  68  
  69  /**
  70   * @package    core
  71   * @subpackage upgrade
  72   * @copyright  2009 Petr Skoda {@link http://skodak.org}
  73   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  74   */
  75  class upgrade_requires_exception extends moodle_exception {
  76      function __construct($plugin, $pluginversion, $currentmoodle, $requiremoodle) {
  77          global $CFG;
  78          $a = new stdClass();
  79          $a->pluginname     = $plugin;
  80          $a->pluginversion  = $pluginversion;
  81          $a->currentmoodle  = $currentmoodle;
  82          $a->requiremoodle  = $requiremoodle;
  83          parent::__construct('pluginrequirementsnotmet', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $a);
  84      }
  85  }
  86  
  87  /**
  88   * Exception thrown when attempting to install a plugin that declares incompatibility with moodle version
  89   *
  90   * @package    core
  91   * @subpackage upgrade
  92   * @copyright  2019 Peter Burnett <peterburnett@catalyst-au.net>
  93   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  94   */
  95  class plugin_incompatible_exception extends moodle_exception {
  96      /**
  97       * Constructor function for exception
  98       *
  99       * @param \core\plugininfo\base $plugin The plugin causing the exception
 100       * @param int $pluginversion The version of the plugin causing the exception
 101       */
 102      public function __construct($plugin, $pluginversion) {
 103          global $CFG;
 104          $a = new stdClass();
 105          $a->pluginname      = $plugin;
 106          $a->pluginversion   = $pluginversion;
 107          $a->moodleversion   = $CFG->branch;
 108  
 109          parent::__construct('pluginunsupported', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $a);
 110      }
 111  }
 112  
 113  /**
 114   * @package    core
 115   * @subpackage upgrade
 116   * @copyright  2009 Petr Skoda {@link http://skodak.org}
 117   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 118   */
 119  class plugin_defective_exception extends moodle_exception {
 120      function __construct($plugin, $details) {
 121          global $CFG;
 122          parent::__construct('detectedbrokenplugin', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $plugin, $details);
 123      }
 124  }
 125  
 126  /**
 127   * Misplaced plugin exception.
 128   *
 129   * Note: this should be used only from the upgrade/admin code.
 130   *
 131   * @package    core
 132   * @subpackage upgrade
 133   * @copyright  2009 Petr Skoda {@link http://skodak.org}
 134   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 135   */
 136  class plugin_misplaced_exception extends moodle_exception {
 137      /**
 138       * Constructor.
 139       * @param string $component the component from version.php
 140       * @param string $expected expected directory, null means calculate
 141       * @param string $current plugin directory path
 142       */
 143      public function __construct($component, $expected, $current) {
 144          global $CFG;
 145          if (empty($expected)) {
 146              list($type, $plugin) = core_component::normalize_component($component);
 147              $plugintypes = core_component::get_plugin_types();
 148              if (isset($plugintypes[$type])) {
 149                  $expected = $plugintypes[$type] . '/' . $plugin;
 150              }
 151          }
 152          if (strpos($expected, '$CFG->dirroot') !== 0) {
 153              $expected = str_replace($CFG->dirroot, '$CFG->dirroot', $expected);
 154          }
 155          if (strpos($current, '$CFG->dirroot') !== 0) {
 156              $current = str_replace($CFG->dirroot, '$CFG->dirroot', $current);
 157          }
 158          $a = new stdClass();
 159          $a->component = $component;
 160          $a->expected  = $expected;
 161          $a->current   = $current;
 162          parent::__construct('detectedmisplacedplugin', 'core_plugin', "$CFG->wwwroot/$CFG->admin/index.php", $a);
 163      }
 164  }
 165  
 166  /**
 167   * Static class monitors performance of upgrade steps.
 168   */
 169  class core_upgrade_time {
 170      /** @var float Time at start of current upgrade (plugin/system) */
 171      protected static $before;
 172      /** @var float Time at end of last recorded savepoint or detail */
 173      protected static $lastdetail;
 174      /** @var bool Flag to indicate whether we are recording timestamps or not. */
 175      protected static $isrecording = false;
 176      /** @var bool Flag indicates whether this is an installation (=no savepoints) */
 177      protected static $installation = false;
 178  
 179      /** @var float For details, only show if they take longer than a second. */
 180      const THRESHOLD = 1.0;
 181  
 182      /**
 183       * Records current time at the start of the current upgrade item, e.g. plugin.
 184       *
 185       * @param bool $installation True if this is an installation (of this item) not upgrade
 186       */
 187      public static function record_start(bool $installation = false): void {
 188          self::$before = microtime(true);
 189          self::$lastdetail = self::$before;
 190          self::$isrecording = true;
 191          self::$installation = $installation;
 192      }
 193  
 194      /**
 195       * Records the end of the current upgrade item.
 196       *
 197       * @param bool $verbose If true, displays output
 198       */
 199      public static function record_end(bool $verbose = true): void {
 200          global $OUTPUT;
 201  
 202          if ($verbose) {
 203              $duration = self::get_elapsed();
 204              $message = get_string('successduration', '', format_float($duration, 2));
 205              $notification = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
 206              $notification->set_show_closebutton(false);
 207              echo $OUTPUT->render($notification);
 208          }
 209  
 210          self::$isrecording = false;
 211      }
 212  
 213      /**
 214       * Records current time at the end of a given numbered step.
 215       *
 216       * @param float $version Version number (may have decimals, or not)
 217       */
 218      public static function record_savepoint($version) {
 219          // Skip savepoints during installation because there is always exactly one and it's not
 220          // interesting.
 221          if (self::$installation) {
 222              return;
 223          }
 224          // We show the time taken by each savepoint even if it's quick, because it could be useful
 225          // just to see the list of upgrade steps completed, so pass $showalways = true.
 226          self::record_detail($version, true);
 227      }
 228  
 229      /**
 230       * Records time taken by a detail of the install process. Time is only displayed if longer than
 231       * threshold, and if in developer debug mode.
 232       *
 233       * @param string $detail Text e.g. file or function name
 234       * @param bool $showalways If true, shows time even if quick
 235       */
 236      public static function record_detail(string $detail, bool $showalways = false): void {
 237          global $CFG, $OUTPUT;
 238  
 239          // In developer debug mode we show a notification after each detail.
 240          if ($CFG->debugdeveloper && self::$isrecording) {
 241              // Calculate time taken since previous detail.
 242              $time = microtime(true);
 243              $duration = $time - self::$lastdetail;
 244  
 245              // Display the time if significant, and always for savepoints.
 246              if ($duration > self::THRESHOLD || $showalways) {
 247                  $notification = new \core\output\notification($detail . ': ' .
 248                          get_string('successduration', '', format_float($duration, 2)),
 249                          \core\output\notification::NOTIFY_SUCCESS);
 250                  $notification->set_show_closebutton(false);
 251                  echo $OUTPUT->render($notification);
 252              }
 253  
 254              // Record the time.
 255              self::$lastdetail = $time;
 256          }
 257      }
 258  
 259      /**
 260       * Gets the time since the record_start function was called, rounded to 2 digits.
 261       *
 262       * @return float Elapsed time
 263       */
 264      public static function get_elapsed() {
 265          return microtime(true) - self::$before;
 266      }
 267  }
 268  
 269  /**
 270   * Sets maximum expected time needed for upgrade task.
 271   * Please always make sure that upgrade will not run longer!
 272   *
 273   * The script may be automatically aborted if upgrade times out.
 274   *
 275   * @category upgrade
 276   * @param int $max_execution_time in seconds (can not be less than 60 s)
 277   */
 278  function upgrade_set_timeout($max_execution_time=300) {
 279      global $CFG;
 280  
 281      // Support outageless upgrades.
 282      if (defined('CLI_UPGRADE_RUNNING') && CLI_UPGRADE_RUNNING) {
 283          return;
 284      }
 285  
 286      if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
 287          $upgraderunning = get_config(null, 'upgraderunning');
 288      } else {
 289          $upgraderunning = $CFG->upgraderunning;
 290      }
 291  
 292      if (!$upgraderunning) {
 293          if (CLI_SCRIPT) {
 294              // never stop CLI upgrades
 295              $upgraderunning = 0;
 296          } else {
 297              // web upgrade not running or aborted
 298              throw new \moodle_exception('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
 299          }
 300      }
 301  
 302      if ($max_execution_time < 60) {
 303          // protection against 0 here
 304          $max_execution_time = 60;
 305      }
 306  
 307      $expected_end = time() + $max_execution_time;
 308  
 309      if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
 310          // no need to store new end, it is nearly the same ;-)
 311          return;
 312      }
 313  
 314      if (CLI_SCRIPT) {
 315          // there is no point in timing out of CLI scripts, admins can stop them if necessary
 316          core_php_time_limit::raise();
 317      } else {
 318          core_php_time_limit::raise($max_execution_time);
 319      }
 320      set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
 321  }
 322  
 323  /**
 324   * Upgrade savepoint, marks end of each upgrade block.
 325   * It stores new main version, resets upgrade timeout
 326   * and abort upgrade if user cancels page loading.
 327   *
 328   * Please do not make large upgrade blocks with lots of operations,
 329   * for example when adding tables keep only one table operation per block.
 330   *
 331   * @category upgrade
 332   * @param bool $result false if upgrade step failed, true if completed
 333   * @param string or float $version main version
 334   * @param bool $allowabort allow user to abort script execution here
 335   * @return void
 336   */
 337  function upgrade_main_savepoint($result, $version, $allowabort=true) {
 338      global $CFG;
 339  
 340      //sanity check to avoid confusion with upgrade_mod_savepoint usage.
 341      if (!is_bool($allowabort)) {
 342          $errormessage = 'Parameter type mismatch. Are you mixing up upgrade_main_savepoint() and upgrade_mod_savepoint()?';
 343          throw new coding_exception($errormessage);
 344      }
 345  
 346      if (!$result) {
 347          throw new upgrade_exception(null, $version);
 348      }
 349  
 350      if ($CFG->version >= $version) {
 351          // something really wrong is going on in main upgrade script
 352          throw new downgrade_exception(null, $CFG->version, $version);
 353      }
 354  
 355      set_config('version', $version);
 356      upgrade_log(UPGRADE_LOG_NORMAL, null, 'Upgrade savepoint reached');
 357  
 358      // reset upgrade timeout to default
 359      upgrade_set_timeout();
 360  
 361      core_upgrade_time::record_savepoint($version);
 362  
 363      // this is a safe place to stop upgrades if user aborts page loading
 364      if ($allowabort and connection_aborted()) {
 365          die;
 366      }
 367  }
 368  
 369  /**
 370   * Module upgrade savepoint, marks end of module upgrade blocks
 371   * It stores module version, resets upgrade timeout
 372   * and abort upgrade if user cancels page loading.
 373   *
 374   * @category upgrade
 375   * @param bool $result false if upgrade step failed, true if completed
 376   * @param string or float $version main version
 377   * @param string $modname name of module
 378   * @param bool $allowabort allow user to abort script execution here
 379   * @return void
 380   */
 381  function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
 382      global $DB;
 383  
 384      $component = 'mod_'.$modname;
 385  
 386      if (!$result) {
 387          throw new upgrade_exception($component, $version);
 388      }
 389  
 390      $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
 391  
 392      if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
 393          throw new \moodle_exception('modulenotexist', 'debug', '', $modname);
 394      }
 395  
 396      if ($dbversion >= $version) {
 397          // something really wrong is going on in upgrade script
 398          throw new downgrade_exception($component, $dbversion, $version);
 399      }
 400      set_config('version', $version, $component);
 401  
 402      upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
 403  
 404      // reset upgrade timeout to default
 405      upgrade_set_timeout();
 406  
 407      core_upgrade_time::record_savepoint($version);
 408  
 409      // this is a safe place to stop upgrades if user aborts page loading
 410      if ($allowabort and connection_aborted()) {
 411          die;
 412      }
 413  }
 414  
 415  /**
 416   * Blocks upgrade savepoint, marks end of blocks upgrade blocks
 417   * It stores block version, resets upgrade timeout
 418   * and abort upgrade if user cancels page loading.
 419   *
 420   * @category upgrade
 421   * @param bool $result false if upgrade step failed, true if completed
 422   * @param string or float $version main version
 423   * @param string $blockname name of block
 424   * @param bool $allowabort allow user to abort script execution here
 425   * @return void
 426   */
 427  function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true) {
 428      global $DB;
 429  
 430      $component = 'block_'.$blockname;
 431  
 432      if (!$result) {
 433          throw new upgrade_exception($component, $version);
 434      }
 435  
 436      $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
 437  
 438      if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
 439          throw new \moodle_exception('blocknotexist', 'debug', '', $blockname);
 440      }
 441  
 442      if ($dbversion >= $version) {
 443          // something really wrong is going on in upgrade script
 444          throw new downgrade_exception($component, $dbversion, $version);
 445      }
 446      set_config('version', $version, $component);
 447  
 448      upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
 449  
 450      // reset upgrade timeout to default
 451      upgrade_set_timeout();
 452  
 453      core_upgrade_time::record_savepoint($version);
 454  
 455      // this is a safe place to stop upgrades if user aborts page loading
 456      if ($allowabort and connection_aborted()) {
 457          die;
 458      }
 459  }
 460  
 461  /**
 462   * Plugins upgrade savepoint, marks end of blocks upgrade blocks
 463   * It stores plugin version, resets upgrade timeout
 464   * and abort upgrade if user cancels page loading.
 465   *
 466   * @category upgrade
 467   * @param bool $result false if upgrade step failed, true if completed
 468   * @param string or float $version main version
 469   * @param string $type The type of the plugin.
 470   * @param string $plugin The name of the plugin.
 471   * @param bool $allowabort allow user to abort script execution here
 472   * @return void
 473   */
 474  function upgrade_plugin_savepoint($result, $version, $type, $plugin, $allowabort=true) {
 475      global $DB;
 476  
 477      $component = $type.'_'.$plugin;
 478  
 479      if (!$result) {
 480          throw new upgrade_exception($component, $version);
 481      }
 482  
 483      $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
 484  
 485      if ($dbversion >= $version) {
 486          // Something really wrong is going on in the upgrade script
 487          throw new downgrade_exception($component, $dbversion, $version);
 488      }
 489      set_config('version', $version, $component);
 490      upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
 491  
 492      // Reset upgrade timeout to default
 493      upgrade_set_timeout();
 494  
 495      core_upgrade_time::record_savepoint($version);
 496  
 497      // This is a safe place to stop upgrades if user aborts page loading
 498      if ($allowabort and connection_aborted()) {
 499          die;
 500      }
 501  }
 502  
 503  /**
 504   * Detect if there are leftovers in PHP source files.
 505   *
 506   * During main version upgrades administrators MUST move away
 507   * old PHP source files and start from scratch (or better
 508   * use git).
 509   *
 510   * @return bool true means borked upgrade, false means previous PHP files were properly removed
 511   */
 512  function upgrade_stale_php_files_present(): bool {
 513      global $CFG;
 514  
 515      $someexamplesofremovedfiles = [
 516          // Removed in 4.3.
 517          '/badges/ajax.php',
 518          '/course/editdefaultcompletion.php',
 519          '/grade/amd/src/searchwidget/group.js',
 520          '/lib/behat/extension/Moodle/BehatExtension/Locator/FilesystemSkipPassedListLocator.php',
 521          '/lib/classes/task/legacy_plugin_cron_task.php',
 522          '/mod/lti/ajax.php',
 523          '/pix/f/archive.png',
 524          '/user/repository.php',
 525          // Removed in 4.2.
 526          '/admin/auth_config.php',
 527          '/auth/yui/passwordunmask/passwordunmask.js',
 528          '/lib/spout/readme_moodle.txt',
 529          '/lib/yui/src/tooltip/js/tooltip.js',
 530          // Removed in 4.1.
 531          '/mod/forum/classes/task/refresh_forum_post_counts.php',
 532          '/user/amd/build/participantsfilter.min.js',
 533          '/user/amd/src/participantsfilter.js',
 534          // Removed in 4.0.
 535          '/admin/classes/task_log_table.php',
 536          '/admin/cli/mysql_engine.php',
 537          '/lib/babel-polyfill/polyfill.js',
 538          '/lib/typo3/class.t3lib_cs.php',
 539          '/question/tests/category_class_test.php',
 540          // Removed in 3.11.
 541          '/customfield/edit.php',
 542          '/lib/phpunit/classes/autoloader.php',
 543          '/lib/xhprof/README',
 544          '/message/defaultoutputs.php',
 545          '/user/files_form.php',
 546          // Removed in 3.10.
 547          '/grade/grading/classes/privacy/gradingform_provider.php',
 548          '/lib/coursecatlib.php',
 549          '/lib/form/htmleditor.php',
 550          '/message/classes/output/messagearea/contact.php',
 551          // Removed in 3.9.
 552          '/course/classes/output/modchooser_item.php',
 553          '/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js',
 554          '/course/yui/src/modchooser/js/modchooser.js',
 555          '/h5p/classes/autoloader.php',
 556          '/lib/adodb/readme.txt',
 557          '/lib/maxmind/GeoIp2/Compat/JsonSerializable.php',
 558          // Removed in 3.8.
 559          '/lib/amd/src/modal_confirm.js',
 560          '/lib/fonts/font-awesome-4.7.0/css/font-awesome.css',
 561          '/lib/jquery/jquery-3.2.1.min.js',
 562          '/lib/recaptchalib.php',
 563          '/lib/sessionkeepalive_ajax.php',
 564          '/lib/yui/src/checknet/js/checknet.js',
 565          '/question/amd/src/qbankmanager.js',
 566          // Removed in 3.7.
 567          '/lib/form/yui/src/showadvanced/js/showadvanced.js',
 568          '/lib/tests/output_external_test.php',
 569          '/message/amd/src/message_area.js',
 570          '/message/templates/message_area.mustache',
 571          '/question/yui/src/qbankmanager/build.json',
 572          // Removed in 3.6.
 573          '/lib/classes/session/memcache.php',
 574          '/lib/eventslib.php',
 575          '/lib/form/submitlink.php',
 576          '/lib/medialib.php',
 577          '/lib/password_compat/lib/password.php',
 578          // Removed in 3.5.
 579          '/lib/dml/mssql_native_moodle_database.php',
 580          '/lib/dml/mssql_native_moodle_recordset.php',
 581          '/lib/dml/mssql_native_moodle_temptables.php',
 582          // Removed in 3.4.
 583          '/auth/README.txt',
 584          '/calendar/set.php',
 585          '/enrol/users.php',
 586          '/enrol/yui/rolemanager/assets/skins/sam/rolemanager.css',
 587          // Removed in 3.3.
 588          '/badges/backpackconnect.php',
 589          '/calendar/yui/src/info/assets/skins/sam/moodle-calendar-info.css',
 590          '/competency/classes/external/exporter.php',
 591          '/mod/forum/forum.js',
 592          '/user/pixgroup.php',
 593          // Removed in 3.2.
 594          '/calendar/preferences.php',
 595          '/lib/alfresco/',
 596          '/lib/jquery/jquery-1.12.1.min.js',
 597          '/lib/password_compat/tests/',
 598          '/lib/phpunit/classes/unittestcase.php',
 599          // Removed in 3.1.
 600          '/lib/classes/log/sql_internal_reader.php',
 601          '/lib/zend/',
 602          '/mod/forum/pix/icon.gif',
 603          '/tag/templates/tagname.mustache',
 604          // Removed in 3.0.
 605          '/tag/coursetagslib.php',
 606          // Removed in 2.9.
 607          '/lib/timezone.txt',
 608          // Removed in 2.8.
 609          '/course/delete_category_form.php',
 610          // Removed in 2.7.
 611          '/admin/tool/qeupgradehelper/version.php',
 612          // Removed in 2.6.
 613          '/admin/block.php',
 614          '/admin/oacleanup.php',
 615          // Removed in 2.5.
 616          '/backup/lib.php',
 617          '/backup/bb/README.txt',
 618          '/lib/excel/test.php',
 619          // Removed in 2.4.
 620          '/admin/tool/unittest/simpletestlib.php',
 621          // Removed in 2.3.
 622          '/lib/minify/builder/',
 623          // Removed in 2.2.
 624          '/lib/yui/3.4.1pr1/',
 625          // Removed in 2.2.
 626          '/search/cron_php5.php',
 627          '/course/report/log/indexlive.php',
 628          '/admin/report/backups/index.php',
 629          '/admin/generator.php',
 630          // Removed in 2.1.
 631          '/lib/yui/2.8.0r4/',
 632          // Removed in 2.0.
 633          '/blocks/admin/block_admin.php',
 634          '/blocks/admin_tree/block_admin_tree.php',
 635      ];
 636  
 637      foreach ($someexamplesofremovedfiles as $file) {
 638          if (file_exists($CFG->dirroot.$file)) {
 639              return true;
 640          }
 641      }
 642  
 643      return false;
 644  }
 645  
 646  /**
 647   * After upgrading a module, block, or generic plugin, various parts of the system need to be
 648   * informed.
 649   *
 650   * @param string $component Frankenstyle component or 'moodle' for core
 651   * @param string $messageplug Set to the name of a message plugin if this is one
 652   * @param bool $coreinstall Set to true if this is the core install
 653   */
 654  function upgrade_component_updated(string $component, string $messageplug = '',
 655          bool $coreinstall = false): void {
 656      if (!$coreinstall) {
 657          update_capabilities($component);
 658          core_upgrade_time::record_detail('update_capabilities');
 659      }
 660      log_update_descriptions($component);
 661      core_upgrade_time::record_detail('log_update_descriptions');
 662      external_update_descriptions($component);
 663      core_upgrade_time::record_detail('external_update_descriptions');
 664      \core\task\manager::reset_scheduled_tasks_for_component($component);
 665      core_upgrade_time::record_detail('\core\task\manager::reset_scheduled_tasks_for_component');
 666      \core_analytics\manager::update_default_models_for_component($component);
 667      core_upgrade_time::record_detail('\core_analytics\manager::update_default_models_for_component');
 668      message_update_providers($component);
 669      core_upgrade_time::record_detail('message_update_providers');
 670      \core\message\inbound\manager::update_handlers_for_component($component);
 671      core_upgrade_time::record_detail('\core\message\inbound\manager::update_handlers_for_component');
 672      if ($messageplug !== '') {
 673          // Ugly hack!
 674          message_update_processors($messageplug);
 675          core_upgrade_time::record_detail('message_update_processors');
 676      }
 677      if ($component !== 'moodle') {
 678          // This one is not run for core upgrades.
 679          upgrade_plugin_mnet_functions($component);
 680          core_upgrade_time::record_detail('upgrade_plugin_mnet_functions');
 681      }
 682      core_tag_area::reset_definitions_for_component($component);
 683      core_upgrade_time::record_detail('core_tag_area::reset_definitions_for_component');
 684  }
 685  
 686  /**
 687   * Upgrade plugins
 688   * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
 689   * return void
 690   */
 691  function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
 692      global $CFG, $DB;
 693  
 694  /// special cases
 695      if ($type === 'mod') {
 696          return upgrade_plugins_modules($startcallback, $endcallback, $verbose);
 697      } else if ($type === 'block') {
 698          return upgrade_plugins_blocks($startcallback, $endcallback, $verbose);
 699      }
 700  
 701      $plugs = core_component::get_plugin_list($type);
 702  
 703      foreach ($plugs as $plug=>$fullplug) {
 704          // Reset time so that it works when installing a large number of plugins
 705          core_php_time_limit::raise(600);
 706          $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name
 707  
 708          // check plugin dir is valid name
 709          if (empty($component)) {
 710              throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.');
 711          }
 712  
 713          if (!is_readable($fullplug.'/version.php')) {
 714              continue;
 715          }
 716  
 717          $plugin = new stdClass();
 718          $plugin->version = null;
 719          $module = $plugin; // Prevent some notices when plugin placed in wrong directory.
 720          require ($fullplug.'/version.php');  // defines $plugin with version etc
 721          unset($module);
 722  
 723          if (empty($plugin->version)) {
 724              throw new plugin_defective_exception($component, 'Missing $plugin->version number in version.php.');
 725          }
 726  
 727          if (empty($plugin->component)) {
 728              throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
 729          }
 730  
 731          if ($plugin->component !== $component) {
 732              throw new plugin_misplaced_exception($plugin->component, null, $fullplug);
 733          }
 734  
 735          $plugin->name     = $plug;
 736          $plugin->fullname = $component;
 737  
 738          if (!empty($plugin->requires)) {
 739              if ($plugin->requires > $CFG->version) {
 740                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
 741              } else if ($plugin->requires < 2010000000) {
 742                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
 743              }
 744          }
 745  
 746          // Throw exception if plugin is incompatible with moodle version.
 747          if (!empty($plugin->incompatible)) {
 748              if ($CFG->branch >= $plugin->incompatible) {
 749                  throw new plugin_incompatible_exception($component, $plugin->version);
 750              }
 751          }
 752  
 753          // try to recover from interrupted install.php if needed
 754          if (file_exists($fullplug.'/db/install.php')) {
 755              if (get_config($plugin->fullname, 'installrunning')) {
 756                  require_once ($fullplug.'/db/install.php');
 757                  $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery';
 758                  if (function_exists($recover_install_function)) {
 759                      $startcallback($component, true, $verbose);
 760                      $recover_install_function();
 761                      unset_config('installrunning', $plugin->fullname);
 762                      upgrade_component_updated($component, $type === 'message' ? $plug : '');
 763                      $endcallback($component, true, $verbose);
 764                  }
 765              }
 766          }
 767  
 768          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 769          if (empty($installedversion)) { // new installation
 770              $startcallback($component, true, $verbose);
 771  
 772          /// Install tables if defined
 773              if (file_exists($fullplug.'/db/install.xml')) {
 774                  $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml');
 775                  core_upgrade_time::record_detail('install.xml');
 776              }
 777  
 778          /// store version
 779              upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false);
 780  
 781          /// execute post install file
 782              if (file_exists($fullplug.'/db/install.php')) {
 783                  require_once ($fullplug.'/db/install.php');
 784                  set_config('installrunning', 1, $plugin->fullname);
 785                  $post_install_function = 'xmldb_'.$plugin->fullname.'_install';
 786                  $post_install_function();
 787                  unset_config('installrunning', $plugin->fullname);
 788                  core_upgrade_time::record_detail('install.php');
 789              }
 790  
 791          /// Install various components
 792              upgrade_component_updated($component, $type === 'message' ? $plug : '');
 793              $endcallback($component, true, $verbose);
 794  
 795          } else if ($installedversion < $plugin->version) { // upgrade
 796          /// Run the upgrade function for the plugin.
 797              $startcallback($component, false, $verbose);
 798  
 799              if (is_readable($fullplug.'/db/upgrade.php')) {
 800                  require_once ($fullplug.'/db/upgrade.php');  // defines upgrading function
 801  
 802                  $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade';
 803                  $result = $newupgrade_function($installedversion);
 804                  core_upgrade_time::record_detail('upgrade.php');
 805              } else {
 806                  $result = true;
 807              }
 808  
 809              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 810              if ($installedversion < $plugin->version) {
 811                  // store version if not already there
 812                  upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
 813              }
 814  
 815          /// Upgrade various components
 816              upgrade_component_updated($component, $type === 'message' ? $plug : '');
 817              $endcallback($component, false, $verbose);
 818  
 819          } else if ($installedversion > $plugin->version) {
 820              throw new downgrade_exception($component, $installedversion, $plugin->version);
 821          }
 822      }
 823  }
 824  
 825  /**
 826   * Find and check all modules and load them up or upgrade them if necessary
 827   *
 828   * @global object
 829   * @global object
 830   */
 831  function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
 832      global $CFG, $DB;
 833  
 834      $mods = core_component::get_plugin_list('mod');
 835  
 836      foreach ($mods as $mod=>$fullmod) {
 837  
 838          if ($mod === 'NEWMODULE') {   // Someone has unzipped the template, ignore it
 839              continue;
 840          }
 841  
 842          $component = clean_param('mod_'.$mod, PARAM_COMPONENT);
 843  
 844          // check module dir is valid name
 845          if (empty($component)) {
 846              throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.');
 847          }
 848  
 849          if (!is_readable($fullmod.'/version.php')) {
 850              throw new plugin_defective_exception($component, 'Missing version.php');
 851          }
 852  
 853          $module = new stdClass();
 854          $plugin = new stdClass();
 855          $plugin->version = null;
 856          require ($fullmod .'/version.php');  // Defines $plugin with version etc.
 857  
 858          // Check if the legacy $module syntax is still used.
 859          if (!is_object($module) or (count((array)$module) > 0)) {
 860              throw new plugin_defective_exception($component, 'Unsupported $module syntax detected in version.php');
 861          }
 862  
 863          // Prepare the record for the {modules} table.
 864          $module = clone($plugin);
 865          unset($module->version);
 866          unset($module->component);
 867          unset($module->dependencies);
 868          unset($module->release);
 869  
 870          if (empty($plugin->version)) {
 871              throw new plugin_defective_exception($component, 'Missing $plugin->version number in version.php.');
 872          }
 873  
 874          if (empty($plugin->component)) {
 875              throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
 876          }
 877  
 878          if ($plugin->component !== $component) {
 879              throw new plugin_misplaced_exception($plugin->component, null, $fullmod);
 880          }
 881  
 882          if (!empty($plugin->requires)) {
 883              if ($plugin->requires > $CFG->version) {
 884                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
 885              } else if ($plugin->requires < 2010000000) {
 886                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
 887              }
 888          }
 889  
 890          if (empty($module->cron)) {
 891              $module->cron = 0;
 892          }
 893  
 894          // all modules must have en lang pack
 895          if (!is_readable("$fullmod/lang/en/$mod.php")) {
 896              throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
 897          }
 898  
 899          $module->name = $mod;   // The name MUST match the directory
 900  
 901          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 902  
 903          if (file_exists($fullmod.'/db/install.php')) {
 904              if (get_config($module->name, 'installrunning')) {
 905                  require_once ($fullmod.'/db/install.php');
 906                  $recover_install_function = 'xmldb_'.$module->name.'_install_recovery';
 907                  if (function_exists($recover_install_function)) {
 908                      $startcallback($component, true, $verbose);
 909                      $recover_install_function();
 910                      unset_config('installrunning', $module->name);
 911                      // Install various components too
 912                      upgrade_component_updated($component);
 913                      $endcallback($component, true, $verbose);
 914                  }
 915              }
 916          }
 917  
 918          if (empty($installedversion)) {
 919              $startcallback($component, true, $verbose);
 920  
 921          /// Execute install.xml (XMLDB) - must be present in all modules
 922              $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml');
 923              core_upgrade_time::record_detail('install.xml');
 924  
 925          /// Add record into modules table - may be needed in install.php already
 926              $module->id = $DB->insert_record('modules', $module);
 927              core_upgrade_time::record_detail('insert_record');
 928              upgrade_mod_savepoint(true, $plugin->version, $module->name, false);
 929  
 930          /// Post installation hook - optional
 931              if (file_exists("$fullmod/db/install.php")) {
 932                  require_once("$fullmod/db/install.php");
 933                  // Set installation running flag, we need to recover after exception or error
 934                  set_config('installrunning', 1, $module->name);
 935                  $post_install_function = 'xmldb_'.$module->name.'_install';
 936                  $post_install_function();
 937                  unset_config('installrunning', $module->name);
 938                  core_upgrade_time::record_detail('install.php');
 939              }
 940  
 941          /// Install various components
 942              upgrade_component_updated($component);
 943  
 944              $endcallback($component, true, $verbose);
 945  
 946          } else if ($installedversion < $plugin->version) {
 947          /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
 948              $startcallback($component, false, $verbose);
 949  
 950              if (is_readable($fullmod.'/db/upgrade.php')) {
 951                  require_once ($fullmod.'/db/upgrade.php');  // defines new upgrading function
 952                  $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
 953                  $result = $newupgrade_function($installedversion, $module);
 954                  core_upgrade_time::record_detail('upgrade.php');
 955              } else {
 956                  $result = true;
 957              }
 958  
 959              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 960              $currmodule = $DB->get_record('modules', array('name'=>$module->name));
 961              if ($installedversion < $plugin->version) {
 962                  // store version if not already there
 963                  upgrade_mod_savepoint($result, $plugin->version, $mod, false);
 964              }
 965  
 966              // update cron flag if needed
 967              if ($currmodule->cron != $module->cron) {
 968                  $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
 969              }
 970  
 971              // Upgrade various components
 972              upgrade_component_updated($component);
 973  
 974              $endcallback($component, false, $verbose);
 975  
 976          } else if ($installedversion > $plugin->version) {
 977              throw new downgrade_exception($component, $installedversion, $plugin->version);
 978          }
 979      }
 980  }
 981  
 982  
 983  /**
 984   * This function finds all available blocks and install them
 985   * into blocks table or do all the upgrade process if newer.
 986   *
 987   * @global object
 988   * @global object
 989   */
 990  function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
 991      global $CFG, $DB;
 992  
 993      require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
 994  
 995      $blocktitles   = array(); // we do not want duplicate titles
 996  
 997      //Is this a first install
 998      $first_install = null;
 999  
1000      $blocks = core_component::get_plugin_list('block');
1001  
1002      foreach ($blocks as $blockname=>$fullblock) {
1003  
1004          if (is_null($first_install)) {
1005              $first_install = ($DB->count_records('block_instances') == 0);
1006          }
1007  
1008          if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
1009              continue;
1010          }
1011  
1012          $component = clean_param('block_'.$blockname, PARAM_COMPONENT);
1013  
1014          // check block dir is valid name
1015          if (empty($component)) {
1016              throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.');
1017          }
1018  
1019          if (!is_readable($fullblock.'/version.php')) {
1020              throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
1021          }
1022          $plugin = new stdClass();
1023          $plugin->version = null;
1024          $plugin->cron    = 0;
1025          $module = $plugin; // Prevent some notices when module placed in wrong directory.
1026          include ($fullblock.'/version.php');
1027          unset($module);
1028          $block = clone($plugin);
1029          unset($block->version);
1030          unset($block->component);
1031          unset($block->dependencies);
1032          unset($block->release);
1033  
1034          if (empty($plugin->version)) {
1035              throw new plugin_defective_exception($component, 'Missing block version number in version.php.');
1036          }
1037  
1038          if (empty($plugin->component)) {
1039              throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
1040          }
1041  
1042          if ($plugin->component !== $component) {
1043              throw new plugin_misplaced_exception($plugin->component, null, $fullblock);
1044          }
1045  
1046          if (!empty($plugin->requires)) {
1047              if ($plugin->requires > $CFG->version) {
1048                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
1049              } else if ($plugin->requires < 2010000000) {
1050                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
1051              }
1052          }
1053  
1054          if (!is_readable($fullblock.'/block_'.$blockname.'.php')) {
1055              throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.');
1056          }
1057          include_once($fullblock.'/block_'.$blockname.'.php');
1058  
1059          $classname = 'block_'.$blockname;
1060  
1061          if (!class_exists($classname)) {
1062              throw new plugin_defective_exception($component, 'Can not load main class.');
1063          }
1064  
1065          $blockobj    = new $classname;   // This is what we'll be testing
1066          $blocktitle  = $blockobj->get_title();
1067  
1068          // OK, it's as we all hoped. For further tests, the object will do them itself.
1069          if (!$blockobj->_self_test()) {
1070              throw new plugin_defective_exception($component, 'Self test failed.');
1071          }
1072  
1073          $block->name     = $blockname;   // The name MUST match the directory
1074  
1075          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
1076  
1077          if (file_exists($fullblock.'/db/install.php')) {
1078              if (get_config('block_'.$blockname, 'installrunning')) {
1079                  require_once ($fullblock.'/db/install.php');
1080                  $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery';
1081                  if (function_exists($recover_install_function)) {
1082                      $startcallback($component, true, $verbose);
1083                      $recover_install_function();
1084                      unset_config('installrunning', 'block_'.$blockname);
1085                      // Install various components
1086                      upgrade_component_updated($component);
1087                      $endcallback($component, true, $verbose);
1088                  }
1089              }
1090          }
1091  
1092          if (empty($installedversion)) { // block not installed yet, so install it
1093              $conflictblock = array_search($blocktitle, $blocktitles);
1094              if ($conflictblock !== false) {
1095                  // Duplicate block titles are not allowed, they confuse people
1096                  // AND PHP's associative arrays ;)
1097                  throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock)));
1098              }
1099              $startcallback($component, true, $verbose);
1100  
1101              if (file_exists($fullblock.'/db/install.xml')) {
1102                  $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
1103                  core_upgrade_time::record_detail('install.xml');
1104              }
1105              $block->id = $DB->insert_record('block', $block);
1106              core_upgrade_time::record_detail('insert_record');
1107              upgrade_block_savepoint(true, $plugin->version, $block->name, false);
1108  
1109              if (file_exists($fullblock.'/db/install.php')) {
1110                  require_once ($fullblock.'/db/install.php');
1111                  // Set installation running flag, we need to recover after exception or error
1112                  set_config('installrunning', 1, 'block_'.$blockname);
1113                  $post_install_function = 'xmldb_block_'.$blockname.'_install';
1114                  $post_install_function();
1115                  unset_config('installrunning', 'block_'.$blockname);
1116                  core_upgrade_time::record_detail('install.php');
1117              }
1118  
1119              $blocktitles[$block->name] = $blocktitle;
1120  
1121              // Install various components
1122              upgrade_component_updated($component);
1123  
1124              $endcallback($component, true, $verbose);
1125  
1126          } else if ($installedversion < $plugin->version) {
1127              $startcallback($component, false, $verbose);
1128  
1129              if (is_readable($fullblock.'/db/upgrade.php')) {
1130                  require_once ($fullblock.'/db/upgrade.php');  // defines new upgrading function
1131                  $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
1132                  $result = $newupgrade_function($installedversion, $block);
1133                  core_upgrade_time::record_detail('upgrade.php');
1134              } else {
1135                  $result = true;
1136              }
1137  
1138              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
1139              $currblock = $DB->get_record('block', array('name'=>$block->name));
1140              if ($installedversion < $plugin->version) {
1141                  // store version if not already there
1142                  upgrade_block_savepoint($result, $plugin->version, $block->name, false);
1143              }
1144  
1145              if ($currblock->cron != $block->cron) {
1146                  // update cron flag if needed
1147                  $DB->set_field('block', 'cron', $block->cron, array('id' => $currblock->id));
1148              }
1149  
1150              // Upgrade various components
1151              upgrade_component_updated($component);
1152  
1153              $endcallback($component, false, $verbose);
1154  
1155          } else if ($installedversion > $plugin->version) {
1156              throw new downgrade_exception($component, $installedversion, $plugin->version);
1157          }
1158      }
1159  
1160  
1161      // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks
1162      if ($first_install) {
1163          //Iterate over each course - there should be only site course here now
1164          if ($courses = $DB->get_records('course')) {
1165              foreach ($courses as $course) {
1166                  blocks_add_default_course_blocks($course);
1167              }
1168          }
1169  
1170          blocks_add_default_system_blocks();
1171      }
1172  }
1173  
1174  
1175  /**
1176   * Log_display description function used during install and upgrade.
1177   *
1178   * @param string $component name of component (moodle, etc.)
1179   * @return void
1180   */
1181  function log_update_descriptions($component) {
1182      global $DB;
1183  
1184      $defpath = core_component::get_component_directory($component).'/db/log.php';
1185  
1186      if (!file_exists($defpath)) {
1187          $DB->delete_records('log_display', array('component'=>$component));
1188          return;
1189      }
1190  
1191      // load new info
1192      $logs = array();
1193      include($defpath);
1194      $newlogs = array();
1195      foreach ($logs as $log) {
1196          $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name
1197      }
1198      unset($logs);
1199      $logs = $newlogs;
1200  
1201      $fields = array('module', 'action', 'mtable', 'field');
1202      // update all log fist
1203      $dblogs = $DB->get_records('log_display', array('component'=>$component));
1204      foreach ($dblogs as $dblog) {
1205          $name = $dblog->module.'-'.$dblog->action;
1206  
1207          if (empty($logs[$name])) {
1208              $DB->delete_records('log_display', array('id'=>$dblog->id));
1209              continue;
1210          }
1211  
1212          $log = $logs[$name];
1213          unset($logs[$name]);
1214  
1215          $update = false;
1216          foreach ($fields as $field) {
1217              if ($dblog->$field != $log[$field]) {
1218                  $dblog->$field = $log[$field];
1219                  $update = true;
1220              }
1221          }
1222          if ($update) {
1223              $DB->update_record('log_display', $dblog);
1224          }
1225      }
1226      foreach ($logs as $log) {
1227          $dblog = (object)$log;
1228          $dblog->component = $component;
1229          $DB->insert_record('log_display', $dblog);
1230      }
1231  }
1232  
1233  /**
1234   * Web service discovery function used during install and upgrade.
1235   * @param string $component name of component (moodle, etc.)
1236   * @return void
1237   */
1238  function external_update_descriptions($component) {
1239      global $DB, $CFG;
1240  
1241      $defpath = core_component::get_component_directory($component).'/db/services.php';
1242  
1243      if (!file_exists($defpath)) {
1244          \core_external\util::delete_service_descriptions($component);
1245          return;
1246      }
1247  
1248      // load new info
1249      $functions = array();
1250      $services = array();
1251      include($defpath);
1252  
1253      // update all function fist
1254      $dbfunctions = $DB->get_records('external_functions', array('component'=>$component));
1255      foreach ($dbfunctions as $dbfunction) {
1256          if (empty($functions[$dbfunction->name])) {
1257              $DB->delete_records('external_functions', array('id'=>$dbfunction->id));
1258              // do not delete functions from external_services_functions, beacuse
1259              // we want to notify admins when functions used in custom services disappear
1260  
1261              //TODO: this looks wrong, we have to delete it eventually (skodak)
1262              continue;
1263          }
1264  
1265          $function = $functions[$dbfunction->name];
1266          unset($functions[$dbfunction->name]);
1267          $function['classpath'] = empty($function['classpath']) ? null : $function['classpath'];
1268          $function['methodname'] = $function['methodname'] ?? 'execute';
1269  
1270          $update = false;
1271          if ($dbfunction->classname != $function['classname']) {
1272              $dbfunction->classname = $function['classname'];
1273              $update = true;
1274          }
1275          if ($dbfunction->methodname != $function['methodname']) {
1276              $dbfunction->methodname = $function['methodname'];
1277              $update = true;
1278          }
1279          if ($dbfunction->classpath != $function['classpath']) {
1280              $dbfunction->classpath = $function['classpath'];
1281              $update = true;
1282          }
1283          $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
1284          if ($dbfunction->capabilities != $functioncapabilities) {
1285              $dbfunction->capabilities = $functioncapabilities;
1286              $update = true;
1287          }
1288  
1289          if (isset($function['services']) and is_array($function['services'])) {
1290              sort($function['services']);
1291              $functionservices = implode(',', $function['services']);
1292          } else {
1293              // Force null values in the DB.
1294              $functionservices = null;
1295          }
1296  
1297          if ($dbfunction->services != $functionservices) {
1298              // Now, we need to check if services were removed, in that case we need to remove the function from them.
1299              $servicesremoved = array_diff(explode(",", $dbfunction->services), explode(",", $functionservices));
1300              foreach ($servicesremoved as $removedshortname) {
1301                  if ($externalserviceid = $DB->get_field('external_services', 'id', array("shortname" => $removedshortname))) {
1302                      $DB->delete_records('external_services_functions', array('functionname' => $dbfunction->name,
1303                                                                                  'externalserviceid' => $externalserviceid));
1304                  }
1305              }
1306  
1307              $dbfunction->services = $functionservices;
1308              $update = true;
1309          }
1310          if ($update) {
1311              $DB->update_record('external_functions', $dbfunction);
1312          }
1313      }
1314      foreach ($functions as $fname => $function) {
1315          $dbfunction = new stdClass();
1316          $dbfunction->name       = $fname;
1317          $dbfunction->classname  = $function['classname'];
1318          $dbfunction->methodname = $function['methodname'] ?? 'execute';
1319          $dbfunction->classpath  = empty($function['classpath']) ? null : $function['classpath'];
1320          $dbfunction->component  = $component;
1321          $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
1322  
1323          if (isset($function['services']) and is_array($function['services'])) {
1324              sort($function['services']);
1325              $dbfunction->services = implode(',', $function['services']);
1326          } else {
1327              // Force null values in the DB.
1328              $dbfunction->services = null;
1329          }
1330  
1331          $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
1332      }
1333      unset($functions);
1334  
1335      // now deal with services
1336      $dbservices = $DB->get_records('external_services', array('component'=>$component));
1337      foreach ($dbservices as $dbservice) {
1338          if (empty($services[$dbservice->name])) {
1339              $DB->delete_records('external_tokens', array('externalserviceid'=>$dbservice->id));
1340              $DB->delete_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1341              $DB->delete_records('external_services_users', array('externalserviceid'=>$dbservice->id));
1342              $DB->delete_records('external_services', array('id'=>$dbservice->id));
1343              continue;
1344          }
1345          $service = $services[$dbservice->name];
1346          unset($services[$dbservice->name]);
1347          $service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled'];
1348          $service['requiredcapability'] = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1349          $service['restrictedusers'] = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1350          $service['downloadfiles'] = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1351          $service['uploadfiles'] = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
1352          $service['shortname'] = !isset($service['shortname']) ? null : $service['shortname'];
1353  
1354          $update = false;
1355          if ($dbservice->requiredcapability != $service['requiredcapability']) {
1356              $dbservice->requiredcapability = $service['requiredcapability'];
1357              $update = true;
1358          }
1359          if ($dbservice->restrictedusers != $service['restrictedusers']) {
1360              $dbservice->restrictedusers = $service['restrictedusers'];
1361              $update = true;
1362          }
1363          if ($dbservice->downloadfiles != $service['downloadfiles']) {
1364              $dbservice->downloadfiles = $service['downloadfiles'];
1365              $update = true;
1366          }
1367          if ($dbservice->uploadfiles != $service['uploadfiles']) {
1368              $dbservice->uploadfiles = $service['uploadfiles'];
1369              $update = true;
1370          }
1371          //if shortname is not a PARAM_ALPHANUMEXT, fail (tested here for service update and creation)
1372          if (isset($service['shortname']) and
1373                  (clean_param($service['shortname'], PARAM_ALPHANUMEXT) != $service['shortname'])) {
1374              throw new moodle_exception('installserviceshortnameerror', 'webservice', '', $service['shortname']);
1375          }
1376          if ($dbservice->shortname != $service['shortname']) {
1377              //check that shortname is unique
1378              if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1379                  $existingservice = $DB->get_record('external_services',
1380                          array('shortname' => $service['shortname']));
1381                  if (!empty($existingservice)) {
1382                      throw new moodle_exception('installexistingserviceshortnameerror', 'webservice', '', $service['shortname']);
1383                  }
1384              }
1385              $dbservice->shortname = $service['shortname'];
1386              $update = true;
1387          }
1388          if ($update) {
1389              $DB->update_record('external_services', $dbservice);
1390          }
1391  
1392          $functions = $DB->get_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1393          foreach ($functions as $function) {
1394              $key = array_search($function->functionname, $service['functions']);
1395              if ($key === false) {
1396                  $DB->delete_records('external_services_functions', array('id'=>$function->id));
1397              } else {
1398                  unset($service['functions'][$key]);
1399              }
1400          }
1401          foreach ($service['functions'] as $fname) {
1402              $newf = new stdClass();
1403              $newf->externalserviceid = $dbservice->id;
1404              $newf->functionname      = $fname;
1405              $DB->insert_record('external_services_functions', $newf);
1406          }
1407          unset($functions);
1408      }
1409      foreach ($services as $name => $service) {
1410          //check that shortname is unique
1411          if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1412              $existingservice = $DB->get_record('external_services',
1413                      array('shortname' => $service['shortname']));
1414              if (!empty($existingservice)) {
1415                  throw new moodle_exception('installserviceshortnameerror', 'webservice');
1416              }
1417          }
1418  
1419          $dbservice = new stdClass();
1420          $dbservice->name               = $name;
1421          $dbservice->enabled            = empty($service['enabled']) ? 0 : $service['enabled'];
1422          $dbservice->requiredcapability = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1423          $dbservice->restrictedusers    = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1424          $dbservice->downloadfiles      = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1425          $dbservice->uploadfiles        = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
1426          $dbservice->shortname          = !isset($service['shortname']) ? null : $service['shortname'];
1427          $dbservice->component          = $component;
1428          $dbservice->timecreated        = time();
1429          $dbservice->id = $DB->insert_record('external_services', $dbservice);
1430          foreach ($service['functions'] as $fname) {
1431              $newf = new stdClass();
1432              $newf->externalserviceid = $dbservice->id;
1433              $newf->functionname      = $fname;
1434              $DB->insert_record('external_services_functions', $newf);
1435          }
1436      }
1437  }
1438  
1439  /**
1440   * Allow plugins and subsystems to add external functions to other plugins or built-in services.
1441   * This function is executed just after all the plugins have been updated.
1442   */
1443  function external_update_services() {
1444      global $DB;
1445  
1446      // Look for external functions that want to be added in existing services.
1447      $functions = $DB->get_records_select('external_functions', 'services IS NOT NULL');
1448  
1449      $servicescache = array();
1450      foreach ($functions as $function) {
1451          // Prevent edge cases.
1452          if (empty($function->services)) {
1453              continue;
1454          }
1455          $services = explode(',', $function->services);
1456  
1457          foreach ($services as $serviceshortname) {
1458              // Get the service id by shortname.
1459              if (!empty($servicescache[$serviceshortname])) {
1460                  $serviceid = $servicescache[$serviceshortname];
1461              } else if ($service = $DB->get_record('external_services', array('shortname' => $serviceshortname))) {
1462                  // If the component is empty, it means that is not a built-in service.
1463                  // We don't allow functions to inject themselves in services created by an user in Moodle.
1464                  if (empty($service->component)) {
1465                      continue;
1466                  }
1467                  $serviceid = $service->id;
1468                  $servicescache[$serviceshortname] = $serviceid;
1469              } else {
1470                  // Service not found.
1471                  continue;
1472              }
1473              // Finally add the function to the service.
1474              $newf = new stdClass();
1475              $newf->externalserviceid = $serviceid;
1476              $newf->functionname      = $function->name;
1477  
1478              if (!$DB->record_exists('external_services_functions', (array)$newf)) {
1479                  $DB->insert_record('external_services_functions', $newf);
1480              }
1481          }
1482      }
1483  }
1484  
1485  /**
1486   * upgrade logging functions
1487   */
1488  function upgrade_handle_exception($ex, $plugin = null) {
1489      global $CFG;
1490  
1491      // rollback everything, we need to log all upgrade problems
1492      abort_all_db_transactions();
1493  
1494      $info = get_exception_info($ex);
1495  
1496      // First log upgrade error
1497      upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace);
1498  
1499      // Always turn on debugging - admins need to know what is going on
1500      set_debugging(DEBUG_DEVELOPER, true);
1501  
1502      default_exception_handler($ex, true, $plugin);
1503  }
1504  
1505  /**
1506   * Adds log entry into upgrade_log table
1507   *
1508   * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR
1509   * @param string $plugin frankenstyle component name
1510   * @param string $info short description text of log entry
1511   * @param string $details long problem description
1512   * @param string $backtrace string used for errors only
1513   * @return void
1514   */
1515  function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
1516      global $DB, $USER, $CFG;
1517  
1518      if (empty($plugin)) {
1519          $plugin = 'core';
1520      }
1521  
1522      list($plugintype, $pluginname) = core_component::normalize_component($plugin);
1523      $component = is_null($pluginname) ? $plugintype : $plugintype . '_' . $pluginname;
1524  
1525      $backtrace = format_backtrace($backtrace, true);
1526  
1527      $currentversion = null;
1528      $targetversion  = null;
1529  
1530      //first try to find out current version number
1531      if ($plugintype === 'core') {
1532          //main
1533          $currentversion = $CFG->version;
1534  
1535          $version = null;
1536          include("$CFG->dirroot/version.php");
1537          $targetversion = $version;
1538  
1539      } else {
1540          $pluginversion = get_config($component, 'version');
1541          if (!empty($pluginversion)) {
1542              $currentversion = $pluginversion;
1543          }
1544          $cd = core_component::get_component_directory($component);
1545          if (file_exists("$cd/version.php")) {
1546              $plugin = new stdClass();
1547              $plugin->version = null;
1548              $module = $plugin;
1549              include("$cd/version.php");
1550              $targetversion = $plugin->version;
1551          }
1552      }
1553  
1554      $log = new stdClass();
1555      $log->type          = $type;
1556      $log->plugin        = $component;
1557      $log->version       = $currentversion;
1558      $log->targetversion = $targetversion;
1559      $log->info          = $info;
1560      $log->details       = $details;
1561      $log->backtrace     = $backtrace;
1562      $log->userid        = $USER->id;
1563      $log->timemodified  = time();
1564      try {
1565          $DB->insert_record('upgrade_log', $log);
1566      } catch (Exception $ignored) {
1567          // possible during install or 2.0 upgrade
1568      }
1569  }
1570  
1571  /**
1572   * Marks start of upgrade, blocks any other access to site.
1573   * The upgrade is finished at the end of script or after timeout.
1574   *
1575   * @global object
1576   * @global object
1577   * @global object
1578   */
1579  function upgrade_started($preinstall=false) {
1580      global $CFG, $DB, $PAGE, $OUTPUT;
1581  
1582      static $started = false;
1583  
1584      if ($preinstall) {
1585          ignore_user_abort(true);
1586          upgrade_setup_debug(true);
1587  
1588      } else if ($started) {
1589          upgrade_set_timeout(120);
1590  
1591      } else {
1592          if (!CLI_SCRIPT and !$PAGE->headerprinted) {
1593              $strupgrade  = get_string('upgradingversion', 'admin');
1594              $PAGE->set_pagelayout('maintenance');
1595              upgrade_init_javascript();
1596              $PAGE->set_title($strupgrade . moodle_page::TITLE_SEPARATOR . 'Moodle ' . $CFG->target_release);
1597              $PAGE->set_heading($strupgrade);
1598              $PAGE->navbar->add($strupgrade);
1599              $PAGE->set_cacheable(false);
1600              echo $OUTPUT->header();
1601          }
1602  
1603          ignore_user_abort(true);
1604          core_shutdown_manager::register_function('upgrade_finished_handler');
1605          upgrade_setup_debug(true);
1606          // Support no-maintenance upgrades.
1607          if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) {
1608              set_config('upgraderunning', time() + 300);
1609          }
1610          $started = true;
1611      }
1612  }
1613  
1614  /**
1615   * Internal function - executed if upgrade interrupted.
1616   */
1617  function upgrade_finished_handler() {
1618      upgrade_finished();
1619  }
1620  
1621  /**
1622   * Indicates upgrade is finished.
1623   *
1624   * This function may be called repeatedly.
1625   *
1626   * @global object
1627   * @global object
1628   */
1629  function upgrade_finished($continueurl=null) {
1630      global $CFG, $DB, $OUTPUT;
1631  
1632      if (!empty($CFG->upgraderunning)) {
1633          unset_config('upgraderunning');
1634          // We have to forcefully purge the caches using the writer here.
1635          // This has to be done after we unset the config var. If someone hits the site while this is set they will
1636          // cause the config values to propogate to the caches.
1637          // Caches are purged after the last step in an upgrade but there is several code routines that exceute between
1638          // then and now that leaving a window for things to fall out of sync.
1639          cache_helper::purge_all(true);
1640          upgrade_setup_debug(false);
1641          ignore_user_abort(false);
1642          if ($continueurl) {
1643              echo $OUTPUT->continue_button($continueurl);
1644              echo $OUTPUT->footer();
1645              die;
1646          }
1647      }
1648  }
1649  
1650  /**
1651   * @global object
1652   * @global object
1653   */
1654  function upgrade_setup_debug($starting) {
1655      global $CFG, $DB;
1656  
1657      static $originaldebug = null;
1658  
1659      if ($starting) {
1660          if ($originaldebug === null) {
1661              $originaldebug = $DB->get_debug();
1662          }
1663          if (!empty($CFG->upgradeshowsql)) {
1664              $DB->set_debug(true);
1665          }
1666      } else {
1667          $DB->set_debug($originaldebug);
1668      }
1669  }
1670  
1671  function print_upgrade_separator() {
1672      if (!CLI_SCRIPT) {
1673          echo '<hr />';
1674      }
1675  }
1676  
1677  /**
1678   * Default start upgrade callback
1679   * @param string $plugin
1680   * @param bool $installation true if installation, false means upgrade
1681   */
1682  function print_upgrade_part_start($plugin, $installation, $verbose) {
1683      global $OUTPUT;
1684      if (empty($plugin) or $plugin == 'moodle') {
1685          upgrade_started($installation); // does not store upgrade running flag yet
1686          if ($verbose) {
1687              echo $OUTPUT->heading(get_string('coresystem'));
1688          }
1689      } else {
1690          upgrade_started();
1691          if ($verbose) {
1692              echo $OUTPUT->heading($plugin);
1693          }
1694      }
1695      core_upgrade_time::record_start($installation);
1696      if ($installation) {
1697          if (empty($plugin) or $plugin == 'moodle') {
1698              // no need to log - log table not yet there ;-)
1699          } else {
1700              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin installation');
1701          }
1702      } else {
1703          if (empty($plugin) or $plugin == 'moodle') {
1704              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting core upgrade');
1705          } else {
1706              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin upgrade');
1707          }
1708      }
1709  }
1710  
1711  /**
1712   * Default end upgrade callback
1713   * @param string $plugin
1714   * @param bool $installation true if installation, false means upgrade
1715   */
1716  function print_upgrade_part_end($plugin, $installation, $verbose) {
1717      global $OUTPUT;
1718      upgrade_started();
1719      if ($installation) {
1720          if (empty($plugin) or $plugin == 'moodle') {
1721              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core installed');
1722          } else {
1723              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin installed');
1724          }
1725      } else {
1726          if (empty($plugin) or $plugin == 'moodle') {
1727              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core upgraded');
1728          } else {
1729              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin upgraded');
1730          }
1731      }
1732      if ($verbose) {
1733          core_upgrade_time::record_end();
1734          print_upgrade_separator();
1735      }
1736  }
1737  
1738  /**
1739   * Sets up JS code required for all upgrade scripts.
1740   * @global object
1741   */
1742  function upgrade_init_javascript() {
1743      global $PAGE;
1744      // scroll to the end of each upgrade page so that ppl see either error or continue button,
1745      // no need to scroll continuously any more, it is enough to jump to end once the footer is printed ;-)
1746      $js = "window.scrollTo(0, 5000000);";
1747      $PAGE->requires->js_init_code($js);
1748  }
1749  
1750  /**
1751   * Try to upgrade the given language pack (or current language)
1752   *
1753   * @param string $lang the code of the language to update, defaults to the current language
1754   */
1755  function upgrade_language_pack($lang = null) {
1756      global $CFG;
1757  
1758      if (!empty($CFG->skiplangupgrade)) {
1759          return;
1760      }
1761  
1762      if (!file_exists("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php")) {
1763          // weird, somebody uninstalled the import utility
1764          return;
1765      }
1766  
1767      if (!$lang) {
1768          $lang = current_language();
1769      }
1770  
1771      if (!get_string_manager()->translation_exists($lang)) {
1772          return;
1773      }
1774  
1775      get_string_manager()->reset_caches();
1776  
1777      if ($lang === 'en') {
1778          return;  // Nothing to do
1779      }
1780  
1781      upgrade_started(false);
1782  
1783      require_once("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php");
1784      tool_langimport_preupgrade_update($lang);
1785  
1786      get_string_manager()->reset_caches();
1787  
1788      print_upgrade_separator();
1789  }
1790  
1791  /**
1792   * Build the current theme so that the user doesn't have to wait for it
1793   * to build on the first page load after the install / upgrade.
1794   */
1795  function upgrade_themes() {
1796      global $CFG;
1797  
1798      require_once("{$CFG->libdir}/outputlib.php");
1799  
1800      // Build the current theme so that the user can immediately
1801      // browse the site without having to wait for the theme to build.
1802      $themeconfig = theme_config::load($CFG->theme);
1803      $direction = right_to_left() ? 'rtl' : 'ltr';
1804      theme_build_css_for_themes([$themeconfig], [$direction]);
1805  
1806      // Only queue the task if there isn't already one queued.
1807      if (empty(\core\task\manager::get_adhoc_tasks('\\core\\task\\build_installed_themes_task'))) {
1808          // Queue a task to build all of the site themes at some point
1809          // later. These can happen offline because it doesn't block the
1810          // user unless they quickly change theme.
1811          $adhoctask = new \core\task\build_installed_themes_task();
1812          \core\task\manager::queue_adhoc_task($adhoctask);
1813      }
1814  }
1815  
1816  /**
1817   * Install core moodle tables and initialize
1818   * @param float $version target version
1819   * @param bool $verbose
1820   * @return void, may throw exception
1821   */
1822  function install_core($version, $verbose) {
1823      global $CFG, $DB;
1824  
1825      // We can not call purge_all_caches() yet, make sure the temp and cache dirs exist and are empty.
1826      remove_dir($CFG->cachedir.'', true);
1827      make_cache_directory('', true);
1828  
1829      remove_dir($CFG->localcachedir.'', true);
1830      make_localcache_directory('', true);
1831  
1832      remove_dir($CFG->tempdir.'', true);
1833      make_temp_directory('', true);
1834  
1835      remove_dir($CFG->backuptempdir.'', true);
1836      make_backup_temp_directory('', true);
1837  
1838      remove_dir($CFG->dataroot.'/muc', true);
1839      make_writable_directory($CFG->dataroot.'/muc', true);
1840  
1841      try {
1842          core_php_time_limit::raise(600);
1843          print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag
1844  
1845          $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml");
1846          core_upgrade_time::record_detail('install.xml');
1847          upgrade_started();     // we want the flag to be stored in config table ;-)
1848          core_upgrade_time::record_detail('upgrade_started');
1849  
1850          // set all core default records and default settings
1851          require_once("$CFG->libdir/db/install.php");
1852          core_upgrade_time::record_detail('install.php');
1853          xmldb_main_install(); // installs the capabilities too
1854          core_upgrade_time::record_detail('xmldb_main_install');
1855  
1856          // store version
1857          upgrade_main_savepoint(true, $version, false);
1858  
1859          // Continue with the installation
1860          upgrade_component_updated('moodle', '', true);
1861  
1862          // Write default settings unconditionally
1863          admin_apply_default_settings(NULL, true);
1864          core_upgrade_time::record_detail('admin_apply_default_settings');
1865  
1866          print_upgrade_part_end(null, true, $verbose);
1867  
1868          // Purge all caches. They're disabled but this ensures that we don't have any persistent data just in case something
1869          // during installation didn't use APIs.
1870          cache_helper::purge_all();
1871      } catch (exception $ex) {
1872          upgrade_handle_exception($ex);
1873      } catch (Throwable $ex) {
1874          // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
1875          upgrade_handle_exception($ex);
1876      }
1877  }
1878  
1879  /**
1880   * Upgrade moodle core
1881   * @param float $version target version
1882   * @param bool $verbose
1883   * @return void, may throw exception
1884   */
1885  function upgrade_core($version, $verbose) {
1886      global $CFG, $SITE, $DB, $COURSE;
1887  
1888      raise_memory_limit(MEMORY_EXTRA);
1889  
1890      require_once($CFG->libdir.'/db/upgrade.php');    // Defines upgrades
1891  
1892      try {
1893          // If we are in maintenance, we can purge all our caches here.
1894          if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) {
1895              cache_helper::purge_all(true);
1896              purge_all_caches();
1897          }
1898  
1899          // Upgrade current language pack if we can
1900          upgrade_language_pack();
1901  
1902          print_upgrade_part_start('moodle', false, $verbose);
1903  
1904          // Pre-upgrade scripts for local hack workarounds.
1905          $preupgradefile = "$CFG->dirroot/local/preupgrade.php";
1906          if (file_exists($preupgradefile)) {
1907              core_php_time_limit::raise();
1908              require($preupgradefile);
1909              // Reset upgrade timeout to default.
1910              upgrade_set_timeout();
1911              core_upgrade_time::record_detail('local/preupgrade.php');
1912          }
1913  
1914          $result = xmldb_main_upgrade($CFG->version);
1915          core_upgrade_time::record_detail('xmldb_main_upgrade');
1916          if ($version > $CFG->version) {
1917              // store version if not already there
1918              upgrade_main_savepoint($result, $version, false);
1919          }
1920  
1921          // In case structure of 'course' table has been changed and we forgot to update $SITE, re-read it from db.
1922          $SITE = $DB->get_record('course', array('id' => $SITE->id));
1923          $COURSE = clone($SITE);
1924  
1925          // perform all other component upgrade routines
1926          upgrade_component_updated('moodle');
1927          // Update core definitions.
1928          cache_helper::update_definitions(true);
1929          core_upgrade_time::record_detail('cache_helper::update_definitions');
1930  
1931          // Purge caches again, just to be sure we arn't holding onto old stuff now.
1932          if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) {
1933              cache_helper::purge_all(true);
1934              core_upgrade_time::record_detail('cache_helper::purge_all');
1935              purge_all_caches();
1936              core_upgrade_time::record_detail('purge_all_caches');
1937          }
1938  
1939          // Clean up contexts - more and more stuff depends on existence of paths and contexts
1940          context_helper::cleanup_instances();
1941          core_upgrade_time::record_detail('context_helper::cleanup_instance');
1942          context_helper::create_instances(null, false);
1943          core_upgrade_time::record_detail('context_helper::create_instances');
1944          context_helper::build_all_paths(false);
1945          core_upgrade_time::record_detail('context_helper::build_all_paths');
1946          $syscontext = context_system::instance();
1947          $syscontext->mark_dirty();
1948          core_upgrade_time::record_detail('context_system::mark_dirty');
1949  
1950          // Prompt admin to register site. Reminder flow handles sites already registered, so admin won't be prompted if registered.
1951          set_config('registrationpending', true);
1952  
1953          print_upgrade_part_end('moodle', false, $verbose);
1954      } catch (Exception $ex) {
1955          upgrade_handle_exception($ex);
1956      } catch (Throwable $ex) {
1957          // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
1958          upgrade_handle_exception($ex);
1959      }
1960  }
1961  
1962  /**
1963   * Upgrade/install other parts of moodle
1964   * @param bool $verbose
1965   * @return void, may throw exception
1966   */
1967  function upgrade_noncore($verbose) {
1968      global $CFG, $OUTPUT;
1969  
1970      raise_memory_limit(MEMORY_EXTRA);
1971  
1972      // upgrade all plugins types
1973      try {
1974          // Reset caches before any output, unless we are not in maintenance.
1975          if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) {
1976              cache_helper::purge_all(true);
1977              purge_all_caches();
1978          }
1979  
1980          $plugintypes = core_component::get_plugin_types();
1981          upgrade_started();
1982          foreach ($plugintypes as $type=>$location) {
1983              upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose);
1984          }
1985          if ($CFG->debugdeveloper) {
1986              // Only show this heading in developer mode to go with the times below.
1987              echo $OUTPUT->heading('upgrade_noncore()');
1988          }
1989          core_upgrade_time::record_start();
1990          // Upgrade services.
1991          // This function gives plugins and subsystems a chance to add functions to existing built-in services.
1992          external_update_services();
1993          core_upgrade_time::record_detail('external_update_services');
1994  
1995          // Update cache definitions. Involves scanning each plugin for any changes.
1996          cache_helper::update_definitions();
1997          core_upgrade_time::record_detail('cache_helper::update_definitions');
1998  
1999          // Mark the site as upgraded.
2000          set_config('allversionshash', core_component::get_all_versions_hash());
2001          core_upgrade_time::record_detail('core_component::get_all_versions_hash');
2002          set_config('allcomponenthash', core_component::get_all_component_hash());
2003          core_upgrade_time::record_detail('core_component::get_all_component_hash');
2004  
2005          // Purge caches again, just to be sure we arn't holding onto old stuff now.
2006          if (!defined('CLI_UPGRADE_RUNNING') || !CLI_UPGRADE_RUNNING) {
2007              cache_helper::purge_all(true);
2008              core_upgrade_time::record_detail('cache_helper::purge_all');
2009              purge_all_caches();
2010              core_upgrade_time::record_detail('purge_all_caches');
2011          }
2012  
2013          // Only display the final 'Success' if we also showed the heading.
2014          core_upgrade_time::record_end($CFG->debugdeveloper);
2015      } catch (Exception $ex) {
2016          upgrade_handle_exception($ex);
2017      } catch (Throwable $ex) {
2018          // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
2019          upgrade_handle_exception($ex);
2020      }
2021  }
2022  
2023  /**
2024   * Checks if the main tables have been installed yet or not.
2025   *
2026   * Note: we can not use caches here because they might be stale,
2027   *       use with care!
2028   *
2029   * @return bool
2030   */
2031  function core_tables_exist() {
2032      global $DB;
2033  
2034      if (!$tables = $DB->get_tables(false) ) {    // No tables yet at all.
2035          return false;
2036  
2037      } else {                                 // Check for missing main tables
2038          $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml
2039          foreach ($mtables as $mtable) {
2040              if (!in_array($mtable, $tables)) {
2041                  return false;
2042              }
2043          }
2044          return true;
2045      }
2046  }
2047  
2048  /**
2049   * upgrades the mnet rpc definitions for the given component.
2050   * this method doesn't return status, an exception will be thrown in the case of an error
2051   *
2052   * @param string $component the plugin to upgrade, eg auth_mnet
2053   */
2054  function upgrade_plugin_mnet_functions($component) {
2055      global $DB, $CFG;
2056  
2057      list($type, $plugin) = core_component::normalize_component($component);
2058      $path = core_component::get_plugin_directory($type, $plugin);
2059  
2060      $publishes = array();
2061      $subscribes = array();
2062      if (file_exists($path . '/db/mnet.php')) {
2063          require_once($path . '/db/mnet.php'); // $publishes comes from this file
2064      }
2065      if (empty($publishes)) {
2066          $publishes = array(); // still need this to be able to disable stuff later
2067      }
2068      if (empty($subscribes)) {
2069          $subscribes = array(); // still need this to be able to disable stuff later
2070      }
2071  
2072      static $servicecache = array();
2073  
2074      // rekey an array based on the rpc method for easy lookups later
2075      $publishmethodservices = array();
2076      $subscribemethodservices = array();
2077      foreach($publishes as $servicename => $service) {
2078          if (is_array($service['methods'])) {
2079              foreach($service['methods'] as $methodname) {
2080                  $service['servicename'] = $servicename;
2081                  $publishmethodservices[$methodname][] = $service;
2082              }
2083          }
2084      }
2085  
2086      // Disable functions that don't exist (any more) in the source
2087      // Should these be deleted? What about their permissions records?
2088      foreach ($DB->get_records('mnet_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
2089          if (!array_key_exists($rpc->functionname, $publishmethodservices) && $rpc->enabled) {
2090              $DB->set_field('mnet_rpc', 'enabled', 0, array('id' => $rpc->id));
2091          } else if (array_key_exists($rpc->functionname, $publishmethodservices) && !$rpc->enabled) {
2092              $DB->set_field('mnet_rpc', 'enabled', 1, array('id' => $rpc->id));
2093          }
2094      }
2095  
2096      // reflect all the services we're publishing and save them
2097      static $cachedclasses = array(); // to store reflection information in
2098      foreach ($publishes as $service => $data) {
2099          $f = $data['filename'];
2100          $c = $data['classname'];
2101          foreach ($data['methods'] as $method) {
2102              $dataobject = new stdClass();
2103              $dataobject->plugintype  = $type;
2104              $dataobject->pluginname  = $plugin;
2105              $dataobject->enabled     = 1;
2106              $dataobject->classname   = $c;
2107              $dataobject->filename    = $f;
2108  
2109              if (is_string($method)) {
2110                  $dataobject->functionname = $method;
2111  
2112              } else if (is_array($method)) { // wants to override file or class
2113                  $dataobject->functionname = $method['method'];
2114                  $dataobject->classname     = $method['classname'];
2115                  $dataobject->filename      = $method['filename'];
2116              }
2117              $dataobject->xmlrpcpath = $type.'/'.$plugin.'/'.$dataobject->filename.'/'.$method;
2118              $dataobject->static = false;
2119  
2120              require_once($path . '/' . $dataobject->filename);
2121              $functionreflect = null; // slightly different ways to get this depending on whether it's a class method or a function
2122              if (!empty($dataobject->classname)) {
2123                  if (!class_exists($dataobject->classname)) {
2124                      throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
2125                  }
2126                  $key = $dataobject->filename . '|' . $dataobject->classname;
2127                  if (!array_key_exists($key, $cachedclasses)) { // look to see if we've already got a reflection object
2128                      try {
2129                          $cachedclasses[$key] = new ReflectionClass($dataobject->classname);
2130                      } catch (ReflectionException $e) { // catch these and rethrow them to something more helpful
2131                          throw new moodle_exception('installreflectionclasserror', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname, 'error' => $e->getMessage()));
2132                      }
2133                  }
2134                  $r =& $cachedclasses[$key];
2135                  if (!$r->hasMethod($dataobject->functionname)) {
2136                      throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
2137                  }
2138                  $functionreflect = $r->getMethod($dataobject->functionname);
2139                  $dataobject->static = (int)$functionreflect->isStatic();
2140              } else {
2141                  if (!function_exists($dataobject->functionname)) {
2142                      throw new moodle_exception('installnosuchfunction', 'mnet', '', (object)array('method' => $dataobject->functionname, 'file' => $dataobject->filename));
2143                  }
2144                  try {
2145                      $functionreflect = new ReflectionFunction($dataobject->functionname);
2146                  } catch (ReflectionException $e) { // catch these and rethrow them to something more helpful
2147                      throw new moodle_exception('installreflectionfunctionerror', 'mnet', '', (object)array('method' => $dataobject->functionname, '' => $dataobject->filename, 'error' => $e->getMessage()));
2148                  }
2149              }
2150              $dataobject->profile =  serialize(admin_mnet_method_profile($functionreflect));
2151              $dataobject->help = admin_mnet_method_get_help($functionreflect);
2152  
2153              if ($record_exists = $DB->get_record('mnet_rpc', array('xmlrpcpath'=>$dataobject->xmlrpcpath))) {
2154                  $dataobject->id      = $record_exists->id;
2155                  $dataobject->enabled = $record_exists->enabled;
2156                  $DB->update_record('mnet_rpc', $dataobject);
2157              } else {
2158                  $dataobject->id = $DB->insert_record('mnet_rpc', $dataobject, true);
2159              }
2160  
2161              // TODO this API versioning must be reworked, here the recently processed method
2162              // sets the service API which may not be correct
2163              foreach ($publishmethodservices[$dataobject->functionname] as $service) {
2164                  if ($serviceobj = $DB->get_record('mnet_service', array('name'=>$service['servicename']))) {
2165                      $serviceobj->apiversion = $service['apiversion'];
2166                      $DB->update_record('mnet_service', $serviceobj);
2167                  } else {
2168                      $serviceobj = new stdClass();
2169                      $serviceobj->name        = $service['servicename'];
2170                      $serviceobj->description = empty($service['description']) ? '' : $service['description'];
2171                      $serviceobj->apiversion  = $service['apiversion'];
2172                      $serviceobj->offer       = 1;
2173                      $serviceobj->id          = $DB->insert_record('mnet_service', $serviceobj);
2174                  }
2175                  $servicecache[$service['servicename']] = $serviceobj;
2176                  if (!$DB->record_exists('mnet_service2rpc', array('rpcid'=>$dataobject->id, 'serviceid'=>$serviceobj->id))) {
2177                      $obj = new stdClass();
2178                      $obj->rpcid = $dataobject->id;
2179                      $obj->serviceid = $serviceobj->id;
2180                      $DB->insert_record('mnet_service2rpc', $obj, true);
2181                  }
2182              }
2183          }
2184      }
2185      // finished with methods we publish, now do subscribable methods
2186      foreach($subscribes as $service => $methods) {
2187          if (!array_key_exists($service, $servicecache)) {
2188              if (!$serviceobj = $DB->get_record('mnet_service', array('name' =>  $service))) {
2189                  debugging("TODO: skipping unknown service $service - somebody needs to fix MDL-21993");
2190                  continue;
2191              }
2192              $servicecache[$service] = $serviceobj;
2193          } else {
2194              $serviceobj = $servicecache[$service];
2195          }
2196          foreach ($methods as $method => $xmlrpcpath) {
2197              if (!$rpcid = $DB->get_field('mnet_remote_rpc', 'id', array('xmlrpcpath'=>$xmlrpcpath))) {
2198                  $remoterpc = (object)array(
2199                      'functionname' => $method,
2200                      'xmlrpcpath' => $xmlrpcpath,
2201                      'plugintype' => $type,
2202                      'pluginname' => $plugin,
2203                      'enabled'    => 1,
2204                  );
2205                  $rpcid = $remoterpc->id = $DB->insert_record('mnet_remote_rpc', $remoterpc, true);
2206              }
2207              if (!$DB->record_exists('mnet_remote_service2rpc', array('rpcid'=>$rpcid, 'serviceid'=>$serviceobj->id))) {
2208                  $obj = new stdClass();
2209                  $obj->rpcid = $rpcid;
2210                  $obj->serviceid = $serviceobj->id;
2211                  $DB->insert_record('mnet_remote_service2rpc', $obj, true);
2212              }
2213              $subscribemethodservices[$method][] = $service;
2214          }
2215      }
2216  
2217      foreach ($DB->get_records('mnet_remote_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
2218          if (!array_key_exists($rpc->functionname, $subscribemethodservices) && $rpc->enabled) {
2219              $DB->set_field('mnet_remote_rpc', 'enabled', 0, array('id' => $rpc->id));
2220          } else if (array_key_exists($rpc->functionname, $subscribemethodservices) && !$rpc->enabled) {
2221              $DB->set_field('mnet_remote_rpc', 'enabled', 1, array('id' => $rpc->id));
2222          }
2223      }
2224  
2225      return true;
2226  }
2227  
2228  /**
2229   * Given some sort of reflection function/method object, return a profile array, ready to be serialized and stored
2230   *
2231   * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
2232   *
2233   * @return array associative array with function/method information
2234   */
2235  function admin_mnet_method_profile(ReflectionFunctionAbstract $function) {
2236      $commentlines = admin_mnet_method_get_docblock($function);
2237      $getkey = function($key) use ($commentlines) {
2238          return array_values(array_filter($commentlines, function($line) use ($key) {
2239              return $line[0] == $key;
2240          }));
2241      };
2242      $returnline = $getkey('@return');
2243      return array (
2244          'parameters' => array_map(function($line) {
2245              return array(
2246                  'name' => trim($line[2], " \t\n\r\0\x0B$"),
2247                  'type' => $line[1],
2248                  'description' => $line[3]
2249              );
2250          }, $getkey('@param')),
2251  
2252          'return' => array(
2253              'type' => !empty($returnline[0][1]) ? $returnline[0][1] : 'void',
2254              'description' => !empty($returnline[0][2]) ? $returnline[0][2] : ''
2255          )
2256      );
2257  }
2258  
2259  /**
2260   * Given some sort of reflection function/method object, return an array of docblock lines, where each line is an array of
2261   * keywords/descriptions
2262   *
2263   * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
2264   *
2265   * @return array docblock converted in to an array
2266   */
2267  function admin_mnet_method_get_docblock(ReflectionFunctionAbstract $function) {
2268      return array_map(function($line) {
2269          $text = trim($line, " \t\n\r\0\x0B*/");
2270          if (strpos($text, '@param') === 0) {
2271              return preg_split('/\s+/', $text, 4);
2272          }
2273  
2274          if (strpos($text, '@return') === 0) {
2275              return preg_split('/\s+/', $text, 3);
2276          }
2277  
2278          return array($text);
2279      }, explode("\n", $function->getDocComment()));
2280  }
2281  
2282  /**
2283   * Given some sort of reflection function/method object, return just the help text
2284   *
2285   * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
2286   *
2287   * @return string docblock help text
2288   */
2289  function admin_mnet_method_get_help(ReflectionFunctionAbstract $function) {
2290      $helplines = array_map(function($line) {
2291          return implode(' ', $line);
2292      }, array_values(array_filter(admin_mnet_method_get_docblock($function), function($line) {
2293          return strpos($line[0], '@') !== 0 && !empty($line[0]);
2294      })));
2295  
2296      return implode("\n", $helplines);
2297  }
2298  
2299  /**
2300   * This function verifies that the database is not using an unsupported storage engine.
2301   *
2302   * @param environment_results $result object to update, if relevant
2303   * @return environment_results|null updated results object, or null if the storage engine is supported
2304   */
2305  function check_database_storage_engine(environment_results $result) {
2306      global $DB;
2307  
2308      // Check if MySQL is the DB family (this will also be the same for MariaDB).
2309      if ($DB->get_dbfamily() == 'mysql') {
2310          // Get the database engine we will either be using to install the tables, or what we are currently using.
2311          $engine = $DB->get_dbengine();
2312          // Check if MyISAM is the storage engine that will be used, if so, do not proceed and display an error.
2313          if ($engine == 'MyISAM') {
2314              $result->setInfo('unsupported_db_storage_engine');
2315              $result->setStatus(false);
2316              return $result;
2317          }
2318      }
2319  
2320      return null;
2321  }
2322  
2323  /**
2324   * Method used to check the usage of slasharguments config and display a warning message.
2325   *
2326   * @param environment_results $result object to update, if relevant.
2327   * @return environment_results|null updated results or null if slasharguments is disabled.
2328   */
2329  function check_slasharguments(environment_results $result){
2330      global $CFG;
2331  
2332      if (!during_initial_install() && empty($CFG->slasharguments)) {
2333          $result->setInfo('slasharguments');
2334          $result->setStatus(false);
2335          return $result;
2336      }
2337  
2338      return null;
2339  }
2340  
2341  /**
2342   * This function verifies if the database has tables using innoDB Antelope row format.
2343   *
2344   * @param environment_results $result
2345   * @return environment_results|null updated results object, or null if no Antelope table has been found.
2346   */
2347  function check_database_tables_row_format(environment_results $result) {
2348      global $DB;
2349  
2350      if ($DB->get_dbfamily() == 'mysql') {
2351          $generator = $DB->get_manager()->generator;
2352  
2353          foreach ($DB->get_tables(false) as $table) {
2354              $columns = $DB->get_columns($table, false);
2355              $size = $generator->guess_antelope_row_size($columns);
2356              $format = $DB->get_row_format($table);
2357  
2358              if ($size <= $generator::ANTELOPE_MAX_ROW_SIZE) {
2359                  continue;
2360              }
2361  
2362              if ($format === 'Compact' or $format === 'Redundant') {
2363                  $result->setInfo('unsupported_db_table_row_format');
2364                  $result->setStatus(false);
2365                  return $result;
2366              }
2367          }
2368      }
2369  
2370      return null;
2371  }
2372  
2373  /**
2374   * This function verfies that the database has tables using InnoDB Antelope row format.
2375   *
2376   * @param environment_results $result
2377   * @return environment_results|null updated results object, or null if no Antelope table has been found.
2378   */
2379  function check_mysql_file_format(environment_results $result) {
2380      global $DB;
2381  
2382      if ($DB->get_dbfamily() == 'mysql') {
2383          $collation = $DB->get_dbcollation();
2384          $collationinfo = explode('_', $collation);
2385          $charset = reset($collationinfo);
2386  
2387          if ($charset == 'utf8mb4') {
2388              if ($DB->get_row_format() !== "Barracuda") {
2389                  $result->setInfo('mysql_full_unicode_support#File_format');
2390                  $result->setStatus(false);
2391                  return $result;
2392              }
2393          }
2394      }
2395      return null;
2396  }
2397  
2398  /**
2399   * This function verfies that the database has a setting of one file per table. This is required for 'utf8mb4'.
2400   *
2401   * @param environment_results $result
2402   * @return environment_results|null updated results object, or null if innodb_file_per_table = 1.
2403   */
2404  function check_mysql_file_per_table(environment_results $result) {
2405      global $DB;
2406  
2407      if ($DB->get_dbfamily() == 'mysql') {
2408          $collation = $DB->get_dbcollation();
2409          $collationinfo = explode('_', $collation);
2410          $charset = reset($collationinfo);
2411  
2412          if ($charset == 'utf8mb4') {
2413              if (!$DB->is_file_per_table_enabled()) {
2414                  $result->setInfo('mysql_full_unicode_support#File_per_table');
2415                  $result->setStatus(false);
2416                  return $result;
2417              }
2418          }
2419      }
2420      return null;
2421  }
2422  
2423  /**
2424   * This function verfies that the database has the setting of large prefix enabled. This is required for 'utf8mb4'.
2425   *
2426   * @param environment_results $result
2427   * @return environment_results|null updated results object, or null if innodb_large_prefix = 1.
2428   */
2429  function check_mysql_large_prefix(environment_results $result) {
2430      global $DB;
2431  
2432      if ($DB->get_dbfamily() == 'mysql') {
2433          $collation = $DB->get_dbcollation();
2434          $collationinfo = explode('_', $collation);
2435          $charset = reset($collationinfo);
2436  
2437          if ($charset == 'utf8mb4') {
2438              if (!$DB->is_large_prefix_enabled()) {
2439                  $result->setInfo('mysql_full_unicode_support#Large_prefix');
2440                  $result->setStatus(false);
2441                  return $result;
2442              }
2443          }
2444      }
2445      return null;
2446  }
2447  
2448  /**
2449   * This function checks the database to see if it is using incomplete unicode support.
2450   *
2451   * @param  environment_results $result $result
2452   * @return environment_results|null updated results object, or null if unicode is fully supported.
2453   */
2454  function check_mysql_incomplete_unicode_support(environment_results $result) {
2455      global $DB;
2456  
2457      if ($DB->get_dbfamily() == 'mysql') {
2458          $collation = $DB->get_dbcollation();
2459          $collationinfo = explode('_', $collation);
2460          $charset = reset($collationinfo);
2461  
2462          if ($charset == 'utf8') {
2463              $result->setInfo('mysql_full_unicode_support');
2464              $result->setStatus(false);
2465              return $result;
2466          }
2467      }
2468      return null;
2469  }
2470  
2471  /**
2472   * Check if the site is being served using an ssl url.
2473   *
2474   * Note this does not really perform any request neither looks for proxies or
2475   * other situations. Just looks to wwwroot and warn if it's not using https.
2476   *
2477   * @param  environment_results $result $result
2478   * @return environment_results|null updated results object, or null if the site is https.
2479   */
2480  function check_is_https(environment_results $result) {
2481      global $CFG;
2482  
2483      // Only if is defined, non-empty and whatever core tell us.
2484      if (!empty($CFG->wwwroot) && !is_https()) {
2485          $result->setInfo('site not https');
2486          $result->setStatus(false);
2487          return $result;
2488      }
2489      return null;
2490  }
2491  
2492  /**
2493   * Check if the site is using 64 bits PHP.
2494   *
2495   * @param  environment_results $result
2496   * @return environment_results|null updated results object, or null if the site is using 64 bits PHP.
2497   */
2498  function check_sixtyfour_bits(environment_results $result) {
2499  
2500      if (PHP_INT_SIZE === 4) {
2501           $result->setInfo('php not 64 bits');
2502           $result->setStatus(false);
2503           return $result;
2504      }
2505      return null;
2506  }
2507  
2508  /**
2509   * Check if the igbinary extension installed is buggy one
2510   *
2511   * There are a few php-igbinary versions that are buggy and
2512   * return any unserialised array with wrong index. This defeats
2513   * key() and next() operations on them.
2514   *
2515   * This library is used by MUC and also by memcached and redis
2516   * when available.
2517   *
2518   * Let's inform if there is some problem when:
2519   *   - php 7.2 is being used (php 7.3 and up are immune).
2520   *   - the igbinary extension is installed.
2521   *   - the version of the extension is between 3.2.2 and 3.2.4.
2522   *   - the buggy behaviour is reproduced.
2523   *
2524   * @param environment_results $result object to update, if relevant.
2525   * @return environment_results|null updated results or null.
2526   */
2527  function check_igbinary322_version(environment_results $result) {
2528  
2529      // No problem if using PHP version 7.3 and up.
2530      $phpversion = normalize_version(phpversion());
2531      if (version_compare($phpversion, '7.3', '>=')) {
2532          return null;
2533      }
2534  
2535      // No problem if igbinary is not installed..
2536      if (!function_exists('igbinary_serialize')) {
2537          return null;
2538      }
2539  
2540      // No problem if using igbinary < 3.2.2 or > 3.2.4.
2541      $igbinaryversion = normalize_version(phpversion('igbinary'));
2542      if (version_compare($igbinaryversion, '3.2.2', '<') or version_compare($igbinaryversion, '3.2.4', '>')) {
2543          return null;
2544      }
2545  
2546      // Let's verify the real behaviour to see if the bug is around.
2547      // Note that we need this extra check because they released 3.2.5 with 3.2.4 version number, so
2548      // over the paper, there are 3.2.4 working versions (3.2.5 ones with messed reflection version).
2549      $data = [1, 2, 3];
2550      $data = igbinary_unserialize(igbinary_serialize($data));
2551      if (key($data) === 0) {
2552          return null;
2553      }
2554  
2555      // Arrived here, we are using PHP 7.2 and a buggy verified igbinary version, let's inform and don't allow to continue.
2556      $result->setInfo('igbinary version problem');
2557      $result->setStatus(false);
2558      return $result;
2559  }
2560  
2561  /**
2562   * This function checks that the database prefix ($CFG->prefix) is <= xmldb_table::PREFIX_MAX_LENGTH
2563   *
2564   * @param environment_results $result
2565   * @return environment_results|null updated results object, or null if the prefix check is passing ok.
2566   */
2567  function check_db_prefix_length(environment_results $result) {
2568      global $CFG;
2569  
2570      require_once($CFG->libdir.'/ddllib.php');
2571      $prefixlen = strlen($CFG->prefix) ?? 0;
2572      if ($prefixlen > xmldb_table::PREFIX_MAX_LENGTH) {
2573          $parameters = (object)['current' => $prefixlen, 'maximum' => xmldb_table::PREFIX_MAX_LENGTH];
2574          $result->setFeedbackStr(['dbprefixtoolong', 'admin', $parameters]);
2575          $result->setInfo('db prefix too long');
2576          $result->setStatus(false);
2577          return $result;
2578      }
2579      return null; // All, good. By returning null we hide the check.
2580  }
2581  
2582  /**
2583   * Assert the upgrade key is provided, if it is defined.
2584   *
2585   * The upgrade key can be defined in the main config.php as $CFG->upgradekey. If
2586   * it is defined there, then its value must be provided every time the site is
2587   * being upgraded, regardless the administrator is logged in or not.
2588   *
2589   * This is supposed to be used at certain places in /admin/index.php only.
2590   *
2591   * @param string|null $upgradekeyhash the SHA-1 of the value provided by the user
2592   */
2593  function check_upgrade_key($upgradekeyhash) {
2594      global $CFG, $PAGE;
2595  
2596      if (isset($CFG->config_php_settings['upgradekey'])) {
2597          if ($upgradekeyhash === null or $upgradekeyhash !== sha1($CFG->config_php_settings['upgradekey'])) {
2598              if (!$PAGE->headerprinted) {
2599                  $PAGE->set_title(get_string('upgradekeyreq', 'admin'));
2600                  $output = $PAGE->get_renderer('core', 'admin');
2601                  echo $output->upgradekey_form_page(new moodle_url('/admin/index.php', array('cache' => 0)));
2602                  die();
2603              } else {
2604                  // This should not happen.
2605                  die('Upgrade locked');
2606              }
2607          }
2608      }
2609  }
2610  
2611  /**
2612   * Helper procedure/macro for installing remote plugins at admin/index.php
2613   *
2614   * Does not return, always redirects or exits.
2615   *
2616   * @param array $installable list of \core\update\remote_info
2617   * @param bool $confirmed false: display the validation screen, true: proceed installation
2618   * @param string $heading validation screen heading
2619   * @param moodle_url|string|null $continue URL to proceed with installation at the validation screen
2620   * @param moodle_url|string|null $return URL to go back on cancelling at the validation screen
2621   */
2622  function upgrade_install_plugins(array $installable, $confirmed, $heading='', $continue=null, $return=null) {
2623      global $CFG, $PAGE;
2624  
2625      if (empty($return)) {
2626          $return = $PAGE->url;
2627      }
2628  
2629      if (!empty($CFG->disableupdateautodeploy)) {
2630          redirect($return);
2631      }
2632  
2633      if (empty($installable)) {
2634          redirect($return);
2635      }
2636  
2637      $pluginman = core_plugin_manager::instance();
2638  
2639      if ($confirmed) {
2640          // Installation confirmed at the validation results page.
2641          if (!$pluginman->install_plugins($installable, true, true)) {
2642              throw new moodle_exception('install_plugins_failed', 'core_plugin', $return);
2643          }
2644  
2645          // Always redirect to admin/index.php to perform the database upgrade.
2646          // Do not throw away the existing $PAGE->url parameters such as
2647          // confirmupgrade or confirmrelease if $PAGE->url is a superset of the
2648          // URL we must go to.
2649          $mustgoto = new moodle_url('/admin/index.php', array('cache' => 0, 'confirmplugincheck' => 0));
2650          if ($mustgoto->compare($PAGE->url, URL_MATCH_PARAMS)) {
2651              redirect($PAGE->url);
2652          } else {
2653              redirect($mustgoto);
2654          }
2655  
2656      } else {
2657          $output = $PAGE->get_renderer('core', 'admin');
2658          echo $output->header();
2659          if ($heading) {
2660              echo $output->heading($heading, 3);
2661          }
2662          echo html_writer::start_tag('pre', array('class' => 'plugin-install-console'));
2663          $validated = $pluginman->install_plugins($installable, false, false);
2664          echo html_writer::end_tag('pre');
2665          if ($validated) {
2666              echo $output->plugins_management_confirm_buttons($continue, $return);
2667          } else {
2668              echo $output->plugins_management_confirm_buttons(null, $return);
2669          }
2670          echo $output->footer();
2671          die();
2672      }
2673  }
2674  /**
2675   * Method used to check the installed unoconv version.
2676   *
2677   * @param environment_results $result object to update, if relevant.
2678   * @return environment_results|null updated results or null if unoconv path is not executable.
2679   */
2680  function check_unoconv_version(environment_results $result) {
2681      global $CFG;
2682  
2683      if (!during_initial_install() && !empty($CFG->pathtounoconv) && file_is_executable(trim($CFG->pathtounoconv))) {
2684          $currentversion = 0;
2685          $supportedversion = 0.7;
2686          $unoconvbin = \escapeshellarg($CFG->pathtounoconv);
2687          $command = "$unoconvbin --version";
2688          exec($command, $output);
2689  
2690          // If the command execution returned some output, then get the unoconv version.
2691          if ($output) {
2692              foreach ($output as $response) {
2693                  if (preg_match('/unoconv (\\d+\\.\\d+)/', $response, $matches)) {
2694                      $currentversion = (float)$matches[1];
2695                  }
2696              }
2697          }
2698  
2699          if ($currentversion < $supportedversion) {
2700              $result->setInfo('unoconv version not supported');
2701              $result->setStatus(false);
2702              return $result;
2703          }
2704      }
2705      return null;
2706  }
2707  
2708  /**
2709   * Checks for up-to-date TLS libraries. NOTE: this is not currently used, see MDL-57262.
2710   *
2711   * @param environment_results $result object to update, if relevant.
2712   * @return environment_results|null updated results or null if unoconv path is not executable.
2713   */
2714  function check_tls_libraries(environment_results $result) {
2715      global $CFG;
2716  
2717      if (!function_exists('curl_version')) {
2718          $result->setInfo('cURL PHP extension is not installed');
2719          $result->setStatus(false);
2720          return $result;
2721      }
2722  
2723      if (!\core\upgrade\util::validate_php_curl_tls(curl_version(), PHP_ZTS)) {
2724          $result->setInfo('invalid ssl/tls configuration');
2725          $result->setStatus(false);
2726          return $result;
2727      }
2728  
2729      if (!\core\upgrade\util::can_use_tls12(curl_version(), php_uname('r'))) {
2730          $result->setInfo('ssl/tls configuration not supported');
2731          $result->setStatus(false);
2732          return $result;
2733      }
2734  
2735      return null;
2736  }
2737  
2738  /**
2739   * Check if recommended version of libcurl is installed or not.
2740   *
2741   * @param environment_results $result object to update, if relevant.
2742   * @return environment_results|null updated results or null.
2743   */
2744  function check_libcurl_version(environment_results $result) {
2745  
2746      if (!function_exists('curl_version')) {
2747          $result->setInfo('cURL PHP extension is not installed');
2748          $result->setStatus(false);
2749          return $result;
2750      }
2751  
2752      // Supported version and version number.
2753      $supportedversion = 0x071304;
2754      $supportedversionstring = "7.19.4";
2755  
2756      // Installed version.
2757      $curlinfo = curl_version();
2758      $currentversion = $curlinfo['version_number'];
2759  
2760      if ($currentversion < $supportedversion) {
2761          // Test fail.
2762          // Set info, we want to let user know how to resolve the problem.
2763          $result->setInfo('Libcurl version check');
2764          $result->setNeededVersion($supportedversionstring);
2765          $result->setCurrentVersion($curlinfo['version']);
2766          $result->setStatus(false);
2767          return $result;
2768      }
2769  
2770      return null;
2771  }
2772  
2773  /**
2774   * Environment check for the php setting max_input_vars
2775   *
2776   * @param environment_results $result
2777   * @return environment_results|null
2778   */
2779  function check_max_input_vars(environment_results $result) {
2780      $max = (int)ini_get('max_input_vars');
2781      if ($max < 5000) {
2782          $result->setInfo('max_input_vars');
2783          $result->setStatus(false);
2784          if (PHP_VERSION_ID >= 80000) {
2785              // For PHP8 this check is required.
2786              $result->setLevel('required');
2787              $result->setFeedbackStr('settingmaxinputvarsrequired');
2788          } else {
2789              // For PHP7 this check is optional (recommended).
2790              $result->setFeedbackStr('settingmaxinputvars');
2791          }
2792          return $result;
2793      }
2794      return null;
2795  }
2796  
2797  /**
2798   * Check whether the admin directory has been configured and warn if so.
2799   *
2800   * The admin directory has been deprecated since Moodle 4.0.
2801   *
2802   * @param environment_results $result
2803   * @return null|environment_results
2804   */
2805  function check_admin_dir_usage(environment_results $result): ?environment_results {
2806      global $CFG;
2807  
2808      if (empty($CFG->admin)) {
2809          return null;
2810      }
2811  
2812      if ($CFG->admin === 'admin') {
2813          return null;
2814      }
2815  
2816      $result->setInfo('admin_dir_usage');
2817      $result->setStatus(false);
2818  
2819      return $result;
2820  }
2821  
2822  /**
2823   * Check whether the XML-RPC protocol is enabled and warn if so.
2824   *
2825   * The XML-RPC protocol will be removed in a future version (4.1) as it is no longer supported by PHP.
2826   *
2827   * See MDL-70889 for further information.
2828   *
2829   * @param environment_results $result
2830   * @return null|environment_results
2831   */
2832  function check_xmlrpc_usage(environment_results $result): ?environment_results {
2833      global $CFG;
2834  
2835      // Checking Web Service protocols.
2836      if (!empty($CFG->webserviceprotocols)) {
2837          $plugins = array_flip(explode(',', $CFG->webserviceprotocols));
2838          if (array_key_exists('xmlrpc', $plugins)) {
2839              $result->setInfo('xmlrpc_webservice_usage');
2840              $result->setFeedbackStr('xmlrpcwebserviceenabled');
2841              return $result;
2842          }
2843      }
2844  
2845      return null;
2846  }
2847  
2848  /**
2849   * Check whether the mod_assignment is currently being used.
2850   *
2851   * @param environment_results $result
2852   * @return environment_results|null
2853   */
2854  function check_mod_assignment(environment_results $result): ?environment_results {
2855      global $CFG, $DB;
2856  
2857      if (!file_exists("{$CFG->dirroot}/mod/assignment/version.php")) {
2858          // Check for mod_assignment instances.
2859          if ($DB->get_manager()->table_exists('assignment') && $DB->count_records('assignment') > 0) {
2860              $result->setInfo('Assignment 2.2 is in use');
2861              $result->setFeedbackStr('modassignmentinuse');
2862              return $result;
2863          }
2864  
2865          // Check for mod_assignment subplugins.
2866          if (is_dir($CFG->dirroot . '/mod/assignment/type')) {
2867              $result->setInfo('Assignment 2.2 subplugins present');
2868              $result->setFeedbackStr('modassignmentsubpluginsexist');
2869              return $result;
2870          }
2871      }
2872  
2873      return null;
2874  }