Search moodle.org's
Developer Documentation

See Release Notes

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

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

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