Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

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