Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
/lib/ -> upgradelib.php (source)

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