Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/lib/ -> upgradelib.php (source)

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

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