Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

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