[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/ -> upgradelib.php (source)

   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   * @package    core
  89   * @subpackage upgrade
  90   * @copyright  2009 Petr Skoda {@link http://skodak.org}
  91   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  92   */
  93  class plugin_defective_exception extends moodle_exception {
  94      function __construct($plugin, $details) {
  95          global $CFG;
  96          parent::__construct('detectedbrokenplugin', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $plugin, $details);
  97      }
  98  }
  99  
 100  /**
 101   * Misplaced plugin exception.
 102   *
 103   * Note: this should be used only from the upgrade/admin code.
 104   *
 105   * @package    core
 106   * @subpackage upgrade
 107   * @copyright  2009 Petr Skoda {@link http://skodak.org}
 108   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 109   */
 110  class plugin_misplaced_exception extends moodle_exception {
 111      /**
 112       * Constructor.
 113       * @param string $component the component from version.php
 114       * @param string $expected expected directory, null means calculate
 115       * @param string $current plugin directory path
 116       */
 117      public function __construct($component, $expected, $current) {
 118          global $CFG;
 119          if (empty($expected)) {
 120              list($type, $plugin) = core_component::normalize_component($component);
 121              $plugintypes = core_component::get_plugin_types();
 122              if (isset($plugintypes[$type])) {
 123                  $expected = $plugintypes[$type] . '/' . $plugin;
 124              }
 125          }
 126          if (strpos($expected, '$CFG->dirroot') !== 0) {
 127              $expected = str_replace($CFG->dirroot, '$CFG->dirroot', $expected);
 128          }
 129          if (strpos($current, '$CFG->dirroot') !== 0) {
 130              $current = str_replace($CFG->dirroot, '$CFG->dirroot', $current);
 131          }
 132          $a = new stdClass();
 133          $a->component = $component;
 134          $a->expected  = $expected;
 135          $a->current   = $current;
 136          parent::__construct('detectedmisplacedplugin', 'core_plugin', "$CFG->wwwroot/$CFG->admin/index.php", $a);
 137      }
 138  }
 139  
 140  /**
 141   * Sets maximum expected time needed for upgrade task.
 142   * Please always make sure that upgrade will not run longer!
 143   *
 144   * The script may be automatically aborted if upgrade times out.
 145   *
 146   * @category upgrade
 147   * @param int $max_execution_time in seconds (can not be less than 60 s)
 148   */
 149  function upgrade_set_timeout($max_execution_time=300) {
 150      global $CFG;
 151  
 152      if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
 153          $upgraderunning = get_config(null, 'upgraderunning');
 154      } else {
 155          $upgraderunning = $CFG->upgraderunning;
 156      }
 157  
 158      if (!$upgraderunning) {
 159          if (CLI_SCRIPT) {
 160              // never stop CLI upgrades
 161              $upgraderunning = 0;
 162          } else {
 163              // web upgrade not running or aborted
 164              print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
 165          }
 166      }
 167  
 168      if ($max_execution_time < 60) {
 169          // protection against 0 here
 170          $max_execution_time = 60;
 171      }
 172  
 173      $expected_end = time() + $max_execution_time;
 174  
 175      if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
 176          // no need to store new end, it is nearly the same ;-)
 177          return;
 178      }
 179  
 180      if (CLI_SCRIPT) {
 181          // there is no point in timing out of CLI scripts, admins can stop them if necessary
 182          core_php_time_limit::raise();
 183      } else {
 184          core_php_time_limit::raise($max_execution_time);
 185      }
 186      set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
 187  }
 188  
 189  /**
 190   * Upgrade savepoint, marks end of each upgrade block.
 191   * It stores new main version, resets upgrade timeout
 192   * and abort upgrade if user cancels page loading.
 193   *
 194   * Please do not make large upgrade blocks with lots of operations,
 195   * for example when adding tables keep only one table operation per block.
 196   *
 197   * @category upgrade
 198   * @param bool $result false if upgrade step failed, true if completed
 199   * @param string or float $version main version
 200   * @param bool $allowabort allow user to abort script execution here
 201   * @return void
 202   */
 203  function upgrade_main_savepoint($result, $version, $allowabort=true) {
 204      global $CFG;
 205  
 206      //sanity check to avoid confusion with upgrade_mod_savepoint usage.
 207      if (!is_bool($allowabort)) {
 208          $errormessage = 'Parameter type mismatch. Are you mixing up upgrade_main_savepoint() and upgrade_mod_savepoint()?';
 209          throw new coding_exception($errormessage);
 210      }
 211  
 212      if (!$result) {
 213          throw new upgrade_exception(null, $version);
 214      }
 215  
 216      if ($CFG->version >= $version) {
 217          // something really wrong is going on in main upgrade script
 218          throw new downgrade_exception(null, $CFG->version, $version);
 219      }
 220  
 221      set_config('version', $version);
 222      upgrade_log(UPGRADE_LOG_NORMAL, null, 'Upgrade savepoint reached');
 223  
 224      // reset upgrade timeout to default
 225      upgrade_set_timeout();
 226  
 227      // this is a safe place to stop upgrades if user aborts page loading
 228      if ($allowabort and connection_aborted()) {
 229          die;
 230      }
 231  }
 232  
 233  /**
 234   * Module upgrade savepoint, marks end of module upgrade blocks
 235   * It stores module version, resets upgrade timeout
 236   * and abort upgrade if user cancels page loading.
 237   *
 238   * @category upgrade
 239   * @param bool $result false if upgrade step failed, true if completed
 240   * @param string or float $version main version
 241   * @param string $modname name of module
 242   * @param bool $allowabort allow user to abort script execution here
 243   * @return void
 244   */
 245  function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
 246      global $DB;
 247  
 248      $component = 'mod_'.$modname;
 249  
 250      if (!$result) {
 251          throw new upgrade_exception($component, $version);
 252      }
 253  
 254      $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
 255  
 256      if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
 257          print_error('modulenotexist', 'debug', '', $modname);
 258      }
 259  
 260      if ($dbversion >= $version) {
 261          // something really wrong is going on in upgrade script
 262          throw new downgrade_exception($component, $dbversion, $version);
 263      }
 264      set_config('version', $version, $component);
 265  
 266      upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
 267  
 268      // reset upgrade timeout to default
 269      upgrade_set_timeout();
 270  
 271      // this is a safe place to stop upgrades if user aborts page loading
 272      if ($allowabort and connection_aborted()) {
 273          die;
 274      }
 275  }
 276  
 277  /**
 278   * Blocks upgrade savepoint, marks end of blocks upgrade blocks
 279   * It stores block version, resets upgrade timeout
 280   * and abort upgrade if user cancels page loading.
 281   *
 282   * @category upgrade
 283   * @param bool $result false if upgrade step failed, true if completed
 284   * @param string or float $version main version
 285   * @param string $blockname name of block
 286   * @param bool $allowabort allow user to abort script execution here
 287   * @return void
 288   */
 289  function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true) {
 290      global $DB;
 291  
 292      $component = 'block_'.$blockname;
 293  
 294      if (!$result) {
 295          throw new upgrade_exception($component, $version);
 296      }
 297  
 298      $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
 299  
 300      if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
 301          print_error('blocknotexist', 'debug', '', $blockname);
 302      }
 303  
 304      if ($dbversion >= $version) {
 305          // something really wrong is going on in upgrade script
 306          throw new downgrade_exception($component, $dbversion, $version);
 307      }
 308      set_config('version', $version, $component);
 309  
 310      upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
 311  
 312      // reset upgrade timeout to default
 313      upgrade_set_timeout();
 314  
 315      // this is a safe place to stop upgrades if user aborts page loading
 316      if ($allowabort and connection_aborted()) {
 317          die;
 318      }
 319  }
 320  
 321  /**
 322   * Plugins upgrade savepoint, marks end of blocks upgrade blocks
 323   * It stores plugin version, resets upgrade timeout
 324   * and abort upgrade if user cancels page loading.
 325   *
 326   * @category upgrade
 327   * @param bool $result false if upgrade step failed, true if completed
 328   * @param string or float $version main version
 329   * @param string $type name of plugin
 330   * @param string $dir location of plugin
 331   * @param bool $allowabort allow user to abort script execution here
 332   * @return void
 333   */
 334  function upgrade_plugin_savepoint($result, $version, $type, $plugin, $allowabort=true) {
 335      global $DB;
 336  
 337      $component = $type.'_'.$plugin;
 338  
 339      if (!$result) {
 340          throw new upgrade_exception($component, $version);
 341      }
 342  
 343      $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
 344  
 345      if ($dbversion >= $version) {
 346          // Something really wrong is going on in the upgrade script
 347          throw new downgrade_exception($component, $dbversion, $version);
 348      }
 349      set_config('version', $version, $component);
 350      upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
 351  
 352      // Reset upgrade timeout to default
 353      upgrade_set_timeout();
 354  
 355      // This is a safe place to stop upgrades if user aborts page loading
 356      if ($allowabort and connection_aborted()) {
 357          die;
 358      }
 359  }
 360  
 361  /**
 362   * Detect if there are leftovers in PHP source files.
 363   *
 364   * During main version upgrades administrators MUST move away
 365   * old PHP source files and start from scratch (or better
 366   * use git).
 367   *
 368   * @return bool true means borked upgrade, false means previous PHP files were properly removed
 369   */
 370  function upgrade_stale_php_files_present() {
 371      global $CFG;
 372  
 373      $someexamplesofremovedfiles = array(
 374          // Removed in 3.1.
 375          '/lib/classes/log/sql_internal_reader.php',
 376          '/lib/zend/',
 377          '/mod/forum/pix/icon.gif',
 378          '/tag/templates/tagname.mustache',
 379          // Removed in 3.0.
 380          '/mod/lti/grade.php',
 381          '/tag/coursetagslib.php',
 382          // Removed in 2.9.
 383          '/lib/timezone.txt',
 384          // Removed in 2.8.
 385          '/course/delete_category_form.php',
 386          // Removed in 2.7.
 387          '/admin/tool/qeupgradehelper/version.php',
 388          // Removed in 2.6.
 389          '/admin/block.php',
 390          '/admin/oacleanup.php',
 391          // Removed in 2.5.
 392          '/backup/lib.php',
 393          '/backup/bb/README.txt',
 394          '/lib/excel/test.php',
 395          // Removed in 2.4.
 396          '/admin/tool/unittest/simpletestlib.php',
 397          // Removed in 2.3.
 398          '/lib/minify/builder/',
 399          // Removed in 2.2.
 400          '/lib/yui/3.4.1pr1/',
 401          // Removed in 2.2.
 402          '/search/cron_php5.php',
 403          '/course/report/log/indexlive.php',
 404          '/admin/report/backups/index.php',
 405          '/admin/generator.php',
 406          // Removed in 2.1.
 407          '/lib/yui/2.8.0r4/',
 408          // Removed in 2.0.
 409          '/blocks/admin/block_admin.php',
 410          '/blocks/admin_tree/block_admin_tree.php',
 411      );
 412  
 413      foreach ($someexamplesofremovedfiles as $file) {
 414          if (file_exists($CFG->dirroot.$file)) {
 415              return true;
 416          }
 417      }
 418  
 419      return false;
 420  }
 421  
 422  /**
 423   * Upgrade plugins
 424   * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
 425   * return void
 426   */
 427  function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
 428      global $CFG, $DB;
 429  
 430  /// special cases
 431      if ($type === 'mod') {
 432          return upgrade_plugins_modules($startcallback, $endcallback, $verbose);
 433      } else if ($type === 'block') {
 434          return upgrade_plugins_blocks($startcallback, $endcallback, $verbose);
 435      }
 436  
 437      $plugs = core_component::get_plugin_list($type);
 438  
 439      foreach ($plugs as $plug=>$fullplug) {
 440          // Reset time so that it works when installing a large number of plugins
 441          core_php_time_limit::raise(600);
 442          $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name
 443  
 444          // check plugin dir is valid name
 445          if (empty($component)) {
 446              throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.');
 447          }
 448  
 449          if (!is_readable($fullplug.'/version.php')) {
 450              continue;
 451          }
 452  
 453          $plugin = new stdClass();
 454          $plugin->version = null;
 455          $module = $plugin; // Prevent some notices when plugin placed in wrong directory.
 456          require ($fullplug.'/version.php');  // defines $plugin with version etc
 457          unset($module);
 458  
 459          if (empty($plugin->version)) {
 460              throw new plugin_defective_exception($component, 'Missing $plugin->version number in version.php.');
 461          }
 462  
 463          if (empty($plugin->component)) {
 464              throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
 465          }
 466  
 467          if ($plugin->component !== $component) {
 468              throw new plugin_misplaced_exception($plugin->component, null, $fullplug);
 469          }
 470  
 471          $plugin->name     = $plug;
 472          $plugin->fullname = $component;
 473  
 474          if (!empty($plugin->requires)) {
 475              if ($plugin->requires > $CFG->version) {
 476                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
 477              } else if ($plugin->requires < 2010000000) {
 478                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
 479              }
 480          }
 481  
 482          // try to recover from interrupted install.php if needed
 483          if (file_exists($fullplug.'/db/install.php')) {
 484              if (get_config($plugin->fullname, 'installrunning')) {
 485                  require_once ($fullplug.'/db/install.php');
 486                  $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery';
 487                  if (function_exists($recover_install_function)) {
 488                      $startcallback($component, true, $verbose);
 489                      $recover_install_function();
 490                      unset_config('installrunning', $plugin->fullname);
 491                      update_capabilities($component);
 492                      log_update_descriptions($component);
 493                      external_update_descriptions($component);
 494                      events_update_definition($component);
 495                      \core\task\manager::reset_scheduled_tasks_for_component($component);
 496                      message_update_providers($component);
 497                      \core\message\inbound\manager::update_handlers_for_component($component);
 498                      if ($type === 'message') {
 499                          message_update_processors($plug);
 500                      }
 501                      upgrade_plugin_mnet_functions($component);
 502                      core_tag_area::reset_definitions_for_component($component);
 503                      $endcallback($component, true, $verbose);
 504                  }
 505              }
 506          }
 507  
 508          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 509          if (empty($installedversion)) { // new installation
 510              $startcallback($component, true, $verbose);
 511  
 512          /// Install tables if defined
 513              if (file_exists($fullplug.'/db/install.xml')) {
 514                  $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml');
 515              }
 516  
 517          /// store version
 518              upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false);
 519  
 520          /// execute post install file
 521              if (file_exists($fullplug.'/db/install.php')) {
 522                  require_once ($fullplug.'/db/install.php');
 523                  set_config('installrunning', 1, $plugin->fullname);
 524                  $post_install_function = 'xmldb_'.$plugin->fullname.'_install';
 525                  $post_install_function();
 526                  unset_config('installrunning', $plugin->fullname);
 527              }
 528  
 529          /// Install various components
 530              update_capabilities($component);
 531              log_update_descriptions($component);
 532              external_update_descriptions($component);
 533              events_update_definition($component);
 534              \core\task\manager::reset_scheduled_tasks_for_component($component);
 535              message_update_providers($component);
 536              \core\message\inbound\manager::update_handlers_for_component($component);
 537              if ($type === 'message') {
 538                  message_update_processors($plug);
 539              }
 540              upgrade_plugin_mnet_functions($component);
 541              core_tag_area::reset_definitions_for_component($component);
 542              $endcallback($component, true, $verbose);
 543  
 544          } else if ($installedversion < $plugin->version) { // upgrade
 545          /// Run the upgrade function for the plugin.
 546              $startcallback($component, false, $verbose);
 547  
 548              if (is_readable($fullplug.'/db/upgrade.php')) {
 549                  require_once ($fullplug.'/db/upgrade.php');  // defines upgrading function
 550  
 551                  $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade';
 552                  $result = $newupgrade_function($installedversion);
 553              } else {
 554                  $result = true;
 555              }
 556  
 557              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 558              if ($installedversion < $plugin->version) {
 559                  // store version if not already there
 560                  upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
 561              }
 562  
 563          /// Upgrade various components
 564              update_capabilities($component);
 565              log_update_descriptions($component);
 566              external_update_descriptions($component);
 567              events_update_definition($component);
 568              \core\task\manager::reset_scheduled_tasks_for_component($component);
 569              message_update_providers($component);
 570              \core\message\inbound\manager::update_handlers_for_component($component);
 571              if ($type === 'message') {
 572                  // Ugly hack!
 573                  message_update_processors($plug);
 574              }
 575              upgrade_plugin_mnet_functions($component);
 576              core_tag_area::reset_definitions_for_component($component);
 577              $endcallback($component, false, $verbose);
 578  
 579          } else if ($installedversion > $plugin->version) {
 580              throw new downgrade_exception($component, $installedversion, $plugin->version);
 581          }
 582      }
 583  }
 584  
 585  /**
 586   * Find and check all modules and load them up or upgrade them if necessary
 587   *
 588   * @global object
 589   * @global object
 590   */
 591  function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
 592      global $CFG, $DB;
 593  
 594      $mods = core_component::get_plugin_list('mod');
 595  
 596      foreach ($mods as $mod=>$fullmod) {
 597  
 598          if ($mod === 'NEWMODULE') {   // Someone has unzipped the template, ignore it
 599              continue;
 600          }
 601  
 602          $component = clean_param('mod_'.$mod, PARAM_COMPONENT);
 603  
 604          // check module dir is valid name
 605          if (empty($component)) {
 606              throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.');
 607          }
 608  
 609          if (!is_readable($fullmod.'/version.php')) {
 610              throw new plugin_defective_exception($component, 'Missing version.php');
 611          }
 612  
 613          $module = new stdClass();
 614          $plugin = new stdClass();
 615          $plugin->version = null;
 616          require ($fullmod .'/version.php');  // Defines $plugin with version etc.
 617  
 618          // Check if the legacy $module syntax is still used.
 619          if (!is_object($module) or (count((array)$module) > 0)) {
 620              throw new plugin_defective_exception($component, 'Unsupported $module syntax detected in version.php');
 621          }
 622  
 623          // Prepare the record for the {modules} table.
 624          $module = clone($plugin);
 625          unset($module->version);
 626          unset($module->component);
 627          unset($module->dependencies);
 628          unset($module->release);
 629  
 630          if (empty($plugin->version)) {
 631              throw new plugin_defective_exception($component, 'Missing $plugin->version number in version.php.');
 632          }
 633  
 634          if (empty($plugin->component)) {
 635              throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
 636          }
 637  
 638          if ($plugin->component !== $component) {
 639              throw new plugin_misplaced_exception($plugin->component, null, $fullmod);
 640          }
 641  
 642          if (!empty($plugin->requires)) {
 643              if ($plugin->requires > $CFG->version) {
 644                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
 645              } else if ($plugin->requires < 2010000000) {
 646                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
 647              }
 648          }
 649  
 650          if (empty($module->cron)) {
 651              $module->cron = 0;
 652          }
 653  
 654          // all modules must have en lang pack
 655          if (!is_readable("$fullmod/lang/en/$mod.php")) {
 656              throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
 657          }
 658  
 659          $module->name = $mod;   // The name MUST match the directory
 660  
 661          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 662  
 663          if (file_exists($fullmod.'/db/install.php')) {
 664              if (get_config($module->name, 'installrunning')) {
 665                  require_once ($fullmod.'/db/install.php');
 666                  $recover_install_function = 'xmldb_'.$module->name.'_install_recovery';
 667                  if (function_exists($recover_install_function)) {
 668                      $startcallback($component, true, $verbose);
 669                      $recover_install_function();
 670                      unset_config('installrunning', $module->name);
 671                      // Install various components too
 672                      update_capabilities($component);
 673                      log_update_descriptions($component);
 674                      external_update_descriptions($component);
 675                      events_update_definition($component);
 676                      \core\task\manager::reset_scheduled_tasks_for_component($component);
 677                      message_update_providers($component);
 678                      \core\message\inbound\manager::update_handlers_for_component($component);
 679                      upgrade_plugin_mnet_functions($component);
 680                      core_tag_area::reset_definitions_for_component($component);
 681                      $endcallback($component, true, $verbose);
 682                  }
 683              }
 684          }
 685  
 686          if (empty($installedversion)) {
 687              $startcallback($component, true, $verbose);
 688  
 689          /// Execute install.xml (XMLDB) - must be present in all modules
 690              $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml');
 691  
 692          /// Add record into modules table - may be needed in install.php already
 693              $module->id = $DB->insert_record('modules', $module);
 694              upgrade_mod_savepoint(true, $plugin->version, $module->name, false);
 695  
 696          /// Post installation hook - optional
 697              if (file_exists("$fullmod/db/install.php")) {
 698                  require_once("$fullmod/db/install.php");
 699                  // Set installation running flag, we need to recover after exception or error
 700                  set_config('installrunning', 1, $module->name);
 701                  $post_install_function = 'xmldb_'.$module->name.'_install';
 702                  $post_install_function();
 703                  unset_config('installrunning', $module->name);
 704              }
 705  
 706          /// Install various components
 707              update_capabilities($component);
 708              log_update_descriptions($component);
 709              external_update_descriptions($component);
 710              events_update_definition($component);
 711              \core\task\manager::reset_scheduled_tasks_for_component($component);
 712              message_update_providers($component);
 713              \core\message\inbound\manager::update_handlers_for_component($component);
 714              upgrade_plugin_mnet_functions($component);
 715              core_tag_area::reset_definitions_for_component($component);
 716  
 717              $endcallback($component, true, $verbose);
 718  
 719          } else if ($installedversion < $plugin->version) {
 720          /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
 721              $startcallback($component, false, $verbose);
 722  
 723              if (is_readable($fullmod.'/db/upgrade.php')) {
 724                  require_once ($fullmod.'/db/upgrade.php');  // defines new upgrading function
 725                  $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
 726                  $result = $newupgrade_function($installedversion, $module);
 727              } else {
 728                  $result = true;
 729              }
 730  
 731              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 732              $currmodule = $DB->get_record('modules', array('name'=>$module->name));
 733              if ($installedversion < $plugin->version) {
 734                  // store version if not already there
 735                  upgrade_mod_savepoint($result, $plugin->version, $mod, false);
 736              }
 737  
 738              // update cron flag if needed
 739              if ($currmodule->cron != $module->cron) {
 740                  $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
 741              }
 742  
 743              // Upgrade various components
 744              update_capabilities($component);
 745              log_update_descriptions($component);
 746              external_update_descriptions($component);
 747              events_update_definition($component);
 748              \core\task\manager::reset_scheduled_tasks_for_component($component);
 749              message_update_providers($component);
 750              \core\message\inbound\manager::update_handlers_for_component($component);
 751              upgrade_plugin_mnet_functions($component);
 752              core_tag_area::reset_definitions_for_component($component);
 753  
 754              $endcallback($component, false, $verbose);
 755  
 756          } else if ($installedversion > $plugin->version) {
 757              throw new downgrade_exception($component, $installedversion, $plugin->version);
 758          }
 759      }
 760  }
 761  
 762  
 763  /**
 764   * This function finds all available blocks and install them
 765   * into blocks table or do all the upgrade process if newer.
 766   *
 767   * @global object
 768   * @global object
 769   */
 770  function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
 771      global $CFG, $DB;
 772  
 773      require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
 774  
 775      $blocktitles   = array(); // we do not want duplicate titles
 776  
 777      //Is this a first install
 778      $first_install = null;
 779  
 780      $blocks = core_component::get_plugin_list('block');
 781  
 782      foreach ($blocks as $blockname=>$fullblock) {
 783  
 784          if (is_null($first_install)) {
 785              $first_install = ($DB->count_records('block_instances') == 0);
 786          }
 787  
 788          if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
 789              continue;
 790          }
 791  
 792          $component = clean_param('block_'.$blockname, PARAM_COMPONENT);
 793  
 794          // check block dir is valid name
 795          if (empty($component)) {
 796              throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.');
 797          }
 798  
 799          if (!is_readable($fullblock.'/version.php')) {
 800              throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
 801          }
 802          $plugin = new stdClass();
 803          $plugin->version = null;
 804          $plugin->cron    = 0;
 805          $module = $plugin; // Prevent some notices when module placed in wrong directory.
 806          include ($fullblock.'/version.php');
 807          unset($module);
 808          $block = clone($plugin);
 809          unset($block->version);
 810          unset($block->component);
 811          unset($block->dependencies);
 812          unset($block->release);
 813  
 814          if (empty($plugin->version)) {
 815              throw new plugin_defective_exception($component, 'Missing block version number in version.php.');
 816          }
 817  
 818          if (empty($plugin->component)) {
 819              throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
 820          }
 821  
 822          if ($plugin->component !== $component) {
 823              throw new plugin_misplaced_exception($plugin->component, null, $fullblock);
 824          }
 825  
 826          if (!empty($plugin->requires)) {
 827              if ($plugin->requires > $CFG->version) {
 828                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
 829              } else if ($plugin->requires < 2010000000) {
 830                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
 831              }
 832          }
 833  
 834          if (!is_readable($fullblock.'/block_'.$blockname.'.php')) {
 835              throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.');
 836          }
 837          include_once($fullblock.'/block_'.$blockname.'.php');
 838  
 839          $classname = 'block_'.$blockname;
 840  
 841          if (!class_exists($classname)) {
 842              throw new plugin_defective_exception($component, 'Can not load main class.');
 843          }
 844  
 845          $blockobj    = new $classname;   // This is what we'll be testing
 846          $blocktitle  = $blockobj->get_title();
 847  
 848          // OK, it's as we all hoped. For further tests, the object will do them itself.
 849          if (!$blockobj->_self_test()) {
 850              throw new plugin_defective_exception($component, 'Self test failed.');
 851          }
 852  
 853          $block->name     = $blockname;   // The name MUST match the directory
 854  
 855          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 856  
 857          if (file_exists($fullblock.'/db/install.php')) {
 858              if (get_config('block_'.$blockname, 'installrunning')) {
 859                  require_once ($fullblock.'/db/install.php');
 860                  $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery';
 861                  if (function_exists($recover_install_function)) {
 862                      $startcallback($component, true, $verbose);
 863                      $recover_install_function();
 864                      unset_config('installrunning', 'block_'.$blockname);
 865                      // Install various components
 866                      update_capabilities($component);
 867                      log_update_descriptions($component);
 868                      external_update_descriptions($component);
 869                      events_update_definition($component);
 870                      \core\task\manager::reset_scheduled_tasks_for_component($component);
 871                      message_update_providers($component);
 872                      \core\message\inbound\manager::update_handlers_for_component($component);
 873                      upgrade_plugin_mnet_functions($component);
 874                      core_tag_area::reset_definitions_for_component($component);
 875                      $endcallback($component, true, $verbose);
 876                  }
 877              }
 878          }
 879  
 880          if (empty($installedversion)) { // block not installed yet, so install it
 881              $conflictblock = array_search($blocktitle, $blocktitles);
 882              if ($conflictblock !== false) {
 883                  // Duplicate block titles are not allowed, they confuse people
 884                  // AND PHP's associative arrays ;)
 885                  throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock)));
 886              }
 887              $startcallback($component, true, $verbose);
 888  
 889              if (file_exists($fullblock.'/db/install.xml')) {
 890                  $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
 891              }
 892              $block->id = $DB->insert_record('block', $block);
 893              upgrade_block_savepoint(true, $plugin->version, $block->name, false);
 894  
 895              if (file_exists($fullblock.'/db/install.php')) {
 896                  require_once ($fullblock.'/db/install.php');
 897                  // Set installation running flag, we need to recover after exception or error
 898                  set_config('installrunning', 1, 'block_'.$blockname);
 899                  $post_install_function = 'xmldb_block_'.$blockname.'_install';
 900                  $post_install_function();
 901                  unset_config('installrunning', 'block_'.$blockname);
 902              }
 903  
 904              $blocktitles[$block->name] = $blocktitle;
 905  
 906              // Install various components
 907              update_capabilities($component);
 908              log_update_descriptions($component);
 909              external_update_descriptions($component);
 910              events_update_definition($component);
 911              \core\task\manager::reset_scheduled_tasks_for_component($component);
 912              message_update_providers($component);
 913              \core\message\inbound\manager::update_handlers_for_component($component);
 914              core_tag_area::reset_definitions_for_component($component);
 915              upgrade_plugin_mnet_functions($component);
 916  
 917              $endcallback($component, true, $verbose);
 918  
 919          } else if ($installedversion < $plugin->version) {
 920              $startcallback($component, false, $verbose);
 921  
 922              if (is_readable($fullblock.'/db/upgrade.php')) {
 923                  require_once ($fullblock.'/db/upgrade.php');  // defines new upgrading function
 924                  $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
 925                  $result = $newupgrade_function($installedversion, $block);
 926              } else {
 927                  $result = true;
 928              }
 929  
 930              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 931              $currblock = $DB->get_record('block', array('name'=>$block->name));
 932              if ($installedversion < $plugin->version) {
 933                  // store version if not already there
 934                  upgrade_block_savepoint($result, $plugin->version, $block->name, false);
 935              }
 936  
 937              if ($currblock->cron != $block->cron) {
 938                  // update cron flag if needed
 939                  $DB->set_field('block', 'cron', $block->cron, array('id' => $currblock->id));
 940              }
 941  
 942              // Upgrade various components
 943              update_capabilities($component);
 944              log_update_descriptions($component);
 945              external_update_descriptions($component);
 946              events_update_definition($component);
 947              \core\task\manager::reset_scheduled_tasks_for_component($component);
 948              message_update_providers($component);
 949              \core\message\inbound\manager::update_handlers_for_component($component);
 950              upgrade_plugin_mnet_functions($component);
 951              core_tag_area::reset_definitions_for_component($component);
 952  
 953              $endcallback($component, false, $verbose);
 954  
 955          } else if ($installedversion > $plugin->version) {
 956              throw new downgrade_exception($component, $installedversion, $plugin->version);
 957          }
 958      }
 959  
 960  
 961      // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks
 962      if ($first_install) {
 963          //Iterate over each course - there should be only site course here now
 964          if ($courses = $DB->get_records('course')) {
 965              foreach ($courses as $course) {
 966                  blocks_add_default_course_blocks($course);
 967              }
 968          }
 969  
 970          blocks_add_default_system_blocks();
 971      }
 972  }
 973  
 974  
 975  /**
 976   * Log_display description function used during install and upgrade.
 977   *
 978   * @param string $component name of component (moodle, mod_assignment, etc.)
 979   * @return void
 980   */
 981  function log_update_descriptions($component) {
 982      global $DB;
 983  
 984      $defpath = core_component::get_component_directory($component).'/db/log.php';
 985  
 986      if (!file_exists($defpath)) {
 987          $DB->delete_records('log_display', array('component'=>$component));
 988          return;
 989      }
 990  
 991      // load new info
 992      $logs = array();
 993      include($defpath);
 994      $newlogs = array();
 995      foreach ($logs as $log) {
 996          $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name
 997      }
 998      unset($logs);
 999      $logs = $newlogs;
1000  
1001      $fields = array('module', 'action', 'mtable', 'field');
1002      // update all log fist
1003      $dblogs = $DB->get_records('log_display', array('component'=>$component));
1004      foreach ($dblogs as $dblog) {
1005          $name = $dblog->module.'-'.$dblog->action;
1006  
1007          if (empty($logs[$name])) {
1008              $DB->delete_records('log_display', array('id'=>$dblog->id));
1009              continue;
1010          }
1011  
1012          $log = $logs[$name];
1013          unset($logs[$name]);
1014  
1015          $update = false;
1016          foreach ($fields as $field) {
1017              if ($dblog->$field != $log[$field]) {
1018                  $dblog->$field = $log[$field];
1019                  $update = true;
1020              }
1021          }
1022          if ($update) {
1023              $DB->update_record('log_display', $dblog);
1024          }
1025      }
1026      foreach ($logs as $log) {
1027          $dblog = (object)$log;
1028          $dblog->component = $component;
1029          $DB->insert_record('log_display', $dblog);
1030      }
1031  }
1032  
1033  /**
1034   * Web service discovery function used during install and upgrade.
1035   * @param string $component name of component (moodle, mod_assignment, etc.)
1036   * @return void
1037   */
1038  function external_update_descriptions($component) {
1039      global $DB, $CFG;
1040  
1041      $defpath = core_component::get_component_directory($component).'/db/services.php';
1042  
1043      if (!file_exists($defpath)) {
1044          require_once($CFG->dirroot.'/lib/externallib.php');
1045          external_delete_descriptions($component);
1046          return;
1047      }
1048  
1049      // load new info
1050      $functions = array();
1051      $services = array();
1052      include($defpath);
1053  
1054      // update all function fist
1055      $dbfunctions = $DB->get_records('external_functions', array('component'=>$component));
1056      foreach ($dbfunctions as $dbfunction) {
1057          if (empty($functions[$dbfunction->name])) {
1058              $DB->delete_records('external_functions', array('id'=>$dbfunction->id));
1059              // do not delete functions from external_services_functions, beacuse
1060              // we want to notify admins when functions used in custom services disappear
1061  
1062              //TODO: this looks wrong, we have to delete it eventually (skodak)
1063              continue;
1064          }
1065  
1066          $function = $functions[$dbfunction->name];
1067          unset($functions[$dbfunction->name]);
1068          $function['classpath'] = empty($function['classpath']) ? null : $function['classpath'];
1069  
1070          $update = false;
1071          if ($dbfunction->classname != $function['classname']) {
1072              $dbfunction->classname = $function['classname'];
1073              $update = true;
1074          }
1075          if ($dbfunction->methodname != $function['methodname']) {
1076              $dbfunction->methodname = $function['methodname'];
1077              $update = true;
1078          }
1079          if ($dbfunction->classpath != $function['classpath']) {
1080              $dbfunction->classpath = $function['classpath'];
1081              $update = true;
1082          }
1083          $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
1084          if ($dbfunction->capabilities != $functioncapabilities) {
1085              $dbfunction->capabilities = $functioncapabilities;
1086              $update = true;
1087          }
1088  
1089          if (isset($function['services']) and is_array($function['services'])) {
1090              sort($function['services']);
1091              $functionservices = implode(',', $function['services']);
1092          } else {
1093              // Force null values in the DB.
1094              $functionservices = null;
1095          }
1096  
1097          if ($dbfunction->services != $functionservices) {
1098              // Now, we need to check if services were removed, in that case we need to remove the function from them.
1099              $servicesremoved = array_diff(explode(",", $dbfunction->services), explode(",", $functionservices));
1100              foreach ($servicesremoved as $removedshortname) {
1101                  if ($externalserviceid = $DB->get_field('external_services', 'id', array("shortname" => $removedshortname))) {
1102                      $DB->delete_records('external_services_functions', array('functionname' => $dbfunction->name,
1103                                                                                  'externalserviceid' => $externalserviceid));
1104                  }
1105              }
1106  
1107              $dbfunction->services = $functionservices;
1108              $update = true;
1109          }
1110          if ($update) {
1111              $DB->update_record('external_functions', $dbfunction);
1112          }
1113      }
1114      foreach ($functions as $fname => $function) {
1115          $dbfunction = new stdClass();
1116          $dbfunction->name       = $fname;
1117          $dbfunction->classname  = $function['classname'];
1118          $dbfunction->methodname = $function['methodname'];
1119          $dbfunction->classpath  = empty($function['classpath']) ? null : $function['classpath'];
1120          $dbfunction->component  = $component;
1121          $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
1122  
1123          if (isset($function['services']) and is_array($function['services'])) {
1124              sort($function['services']);
1125              $dbfunction->services = implode(',', $function['services']);
1126          } else {
1127              // Force null values in the DB.
1128              $dbfunction->services = null;
1129          }
1130  
1131          $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
1132      }
1133      unset($functions);
1134  
1135      // now deal with services
1136      $dbservices = $DB->get_records('external_services', array('component'=>$component));
1137      foreach ($dbservices as $dbservice) {
1138          if (empty($services[$dbservice->name])) {
1139              $DB->delete_records('external_tokens', array('externalserviceid'=>$dbservice->id));
1140              $DB->delete_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1141              $DB->delete_records('external_services_users', array('externalserviceid'=>$dbservice->id));
1142              $DB->delete_records('external_services', array('id'=>$dbservice->id));
1143              continue;
1144          }
1145          $service = $services[$dbservice->name];
1146          unset($services[$dbservice->name]);
1147          $service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled'];
1148          $service['requiredcapability'] = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1149          $service['restrictedusers'] = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1150          $service['downloadfiles'] = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1151          $service['uploadfiles'] = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
1152          $service['shortname'] = !isset($service['shortname']) ? null : $service['shortname'];
1153  
1154          $update = false;
1155          if ($dbservice->requiredcapability != $service['requiredcapability']) {
1156              $dbservice->requiredcapability = $service['requiredcapability'];
1157              $update = true;
1158          }
1159          if ($dbservice->restrictedusers != $service['restrictedusers']) {
1160              $dbservice->restrictedusers = $service['restrictedusers'];
1161              $update = true;
1162          }
1163          if ($dbservice->downloadfiles != $service['downloadfiles']) {
1164              $dbservice->downloadfiles = $service['downloadfiles'];
1165              $update = true;
1166          }
1167          if ($dbservice->uploadfiles != $service['uploadfiles']) {
1168              $dbservice->uploadfiles = $service['uploadfiles'];
1169              $update = true;
1170          }
1171          //if shortname is not a PARAM_ALPHANUMEXT, fail (tested here for service update and creation)
1172          if (isset($service['shortname']) and
1173                  (clean_param($service['shortname'], PARAM_ALPHANUMEXT) != $service['shortname'])) {
1174              throw new moodle_exception('installserviceshortnameerror', 'webservice', '', $service['shortname']);
1175          }
1176          if ($dbservice->shortname != $service['shortname']) {
1177              //check that shortname is unique
1178              if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1179                  $existingservice = $DB->get_record('external_services',
1180                          array('shortname' => $service['shortname']));
1181                  if (!empty($existingservice)) {
1182                      throw new moodle_exception('installexistingserviceshortnameerror', 'webservice', '', $service['shortname']);
1183                  }
1184              }
1185              $dbservice->shortname = $service['shortname'];
1186              $update = true;
1187          }
1188          if ($update) {
1189              $DB->update_record('external_services', $dbservice);
1190          }
1191  
1192          $functions = $DB->get_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1193          foreach ($functions as $function) {
1194              $key = array_search($function->functionname, $service['functions']);
1195              if ($key === false) {
1196                  $DB->delete_records('external_services_functions', array('id'=>$function->id));
1197              } else {
1198                  unset($service['functions'][$key]);
1199              }
1200          }
1201          foreach ($service['functions'] as $fname) {
1202              $newf = new stdClass();
1203              $newf->externalserviceid = $dbservice->id;
1204              $newf->functionname      = $fname;
1205              $DB->insert_record('external_services_functions', $newf);
1206          }
1207          unset($functions);
1208      }
1209      foreach ($services as $name => $service) {
1210          //check that shortname is unique
1211          if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1212              $existingservice = $DB->get_record('external_services',
1213                      array('shortname' => $service['shortname']));
1214              if (!empty($existingservice)) {
1215                  throw new moodle_exception('installserviceshortnameerror', 'webservice');
1216              }
1217          }
1218  
1219          $dbservice = new stdClass();
1220          $dbservice->name               = $name;
1221          $dbservice->enabled            = empty($service['enabled']) ? 0 : $service['enabled'];
1222          $dbservice->requiredcapability = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1223          $dbservice->restrictedusers    = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1224          $dbservice->downloadfiles      = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1225          $dbservice->uploadfiles        = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
1226          $dbservice->shortname          = !isset($service['shortname']) ? null : $service['shortname'];
1227          $dbservice->component          = $component;
1228          $dbservice->timecreated        = time();
1229          $dbservice->id = $DB->insert_record('external_services', $dbservice);
1230          foreach ($service['functions'] as $fname) {
1231              $newf = new stdClass();
1232              $newf->externalserviceid = $dbservice->id;
1233              $newf->functionname      = $fname;
1234              $DB->insert_record('external_services_functions', $newf);
1235          }
1236      }
1237  }
1238  
1239  /**
1240   * Allow plugins and subsystems to add external functions to other plugins or built-in services.
1241   * This function is executed just after all the plugins have been updated.
1242   */
1243  function external_update_services() {
1244      global $DB;
1245  
1246      // Look for external functions that want to be added in existing services.
1247      $functions = $DB->get_records_select('external_functions', 'services IS NOT NULL');
1248  
1249      $servicescache = array();
1250      foreach ($functions as $function) {
1251          // Prevent edge cases.
1252          if (empty($function->services)) {
1253              continue;
1254          }
1255          $services = explode(',', $function->services);
1256  
1257          foreach ($services as $serviceshortname) {
1258              // Get the service id by shortname.
1259              if (!empty($servicescache[$serviceshortname])) {
1260                  $serviceid = $servicescache[$serviceshortname];
1261              } else if ($service = $DB->get_record('external_services', array('shortname' => $serviceshortname))) {
1262                  // If the component is empty, it means that is not a built-in service.
1263                  // We don't allow functions to inject themselves in services created by an user in Moodle.
1264                  if (empty($service->component)) {
1265                      continue;
1266                  }
1267                  $serviceid = $service->id;
1268                  $servicescache[$serviceshortname] = $serviceid;
1269              } else {
1270                  // Service not found.
1271                  continue;
1272              }
1273              // Finally add the function to the service.
1274              $newf = new stdClass();
1275              $newf->externalserviceid = $serviceid;
1276              $newf->functionname      = $function->name;
1277  
1278              if (!$DB->record_exists('external_services_functions', (array)$newf)) {
1279                  $DB->insert_record('external_services_functions', $newf);
1280              }
1281          }
1282      }
1283  }
1284  
1285  /**
1286   * upgrade logging functions
1287   */
1288  function upgrade_handle_exception($ex, $plugin = null) {
1289      global $CFG;
1290  
1291      // rollback everything, we need to log all upgrade problems
1292      abort_all_db_transactions();
1293  
1294      $info = get_exception_info($ex);
1295  
1296      // First log upgrade error
1297      upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace);
1298  
1299      // Always turn on debugging - admins need to know what is going on
1300      set_debugging(DEBUG_DEVELOPER, true);
1301  
1302      default_exception_handler($ex, true, $plugin);
1303  }
1304  
1305  /**
1306   * Adds log entry into upgrade_log table
1307   *
1308   * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR
1309   * @param string $plugin frankenstyle component name
1310   * @param string $info short description text of log entry
1311   * @param string $details long problem description
1312   * @param string $backtrace string used for errors only
1313   * @return void
1314   */
1315  function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
1316      global $DB, $USER, $CFG;
1317  
1318      if (empty($plugin)) {
1319          $plugin = 'core';
1320      }
1321  
1322      list($plugintype, $pluginname) = core_component::normalize_component($plugin);
1323      $component = is_null($pluginname) ? $plugintype : $plugintype . '_' . $pluginname;
1324  
1325      $backtrace = format_backtrace($backtrace, true);
1326  
1327      $currentversion = null;
1328      $targetversion  = null;
1329  
1330      //first try to find out current version number
1331      if ($plugintype === 'core') {
1332          //main
1333          $currentversion = $CFG->version;
1334  
1335          $version = null;
1336          include("$CFG->dirroot/version.php");
1337          $targetversion = $version;
1338  
1339      } else {
1340          $pluginversion = get_config($component, 'version');
1341          if (!empty($pluginversion)) {
1342              $currentversion = $pluginversion;
1343          }
1344          $cd = core_component::get_component_directory($component);
1345          if (file_exists("$cd/version.php")) {
1346              $plugin = new stdClass();
1347              $plugin->version = null;
1348              $module = $plugin;
1349              include("$cd/version.php");
1350              $targetversion = $plugin->version;
1351          }
1352      }
1353  
1354      $log = new stdClass();
1355      $log->type          = $type;
1356      $log->plugin        = $component;
1357      $log->version       = $currentversion;
1358      $log->targetversion = $targetversion;
1359      $log->info          = $info;
1360      $log->details       = $details;
1361      $log->backtrace     = $backtrace;
1362      $log->userid        = $USER->id;
1363      $log->timemodified  = time();
1364      try {
1365          $DB->insert_record('upgrade_log', $log);
1366      } catch (Exception $ignored) {
1367          // possible during install or 2.0 upgrade
1368      }
1369  }
1370  
1371  /**
1372   * Marks start of upgrade, blocks any other access to site.
1373   * The upgrade is finished at the end of script or after timeout.
1374   *
1375   * @global object
1376   * @global object
1377   * @global object
1378   */
1379  function upgrade_started($preinstall=false) {
1380      global $CFG, $DB, $PAGE, $OUTPUT;
1381  
1382      static $started = false;
1383  
1384      if ($preinstall) {
1385          ignore_user_abort(true);
1386          upgrade_setup_debug(true);
1387  
1388      } else if ($started) {
1389          upgrade_set_timeout(120);
1390  
1391      } else {
1392          if (!CLI_SCRIPT and !$PAGE->headerprinted) {
1393              $strupgrade  = get_string('upgradingversion', 'admin');
1394              $PAGE->set_pagelayout('maintenance');
1395              upgrade_init_javascript();
1396              $PAGE->set_title($strupgrade.' - Moodle '.$CFG->target_release);
1397              $PAGE->set_heading($strupgrade);
1398              $PAGE->navbar->add($strupgrade);
1399              $PAGE->set_cacheable(false);
1400              echo $OUTPUT->header();
1401          }
1402  
1403          ignore_user_abort(true);
1404          core_shutdown_manager::register_function('upgrade_finished_handler');
1405          upgrade_setup_debug(true);
1406          set_config('upgraderunning', time()+300);
1407          $started = true;
1408      }
1409  }
1410  
1411  /**
1412   * Internal function - executed if upgrade interrupted.
1413   */
1414  function upgrade_finished_handler() {
1415      upgrade_finished();
1416  }
1417  
1418  /**
1419   * Indicates upgrade is finished.
1420   *
1421   * This function may be called repeatedly.
1422   *
1423   * @global object
1424   * @global object
1425   */
1426  function upgrade_finished($continueurl=null) {
1427      global $CFG, $DB, $OUTPUT;
1428  
1429      if (!empty($CFG->upgraderunning)) {
1430          unset_config('upgraderunning');
1431          // We have to forcefully purge the caches using the writer here.
1432          // This has to be done after we unset the config var. If someone hits the site while this is set they will
1433          // cause the config values to propogate to the caches.
1434          // Caches are purged after the last step in an upgrade but there is several code routines that exceute between
1435          // then and now that leaving a window for things to fall out of sync.
1436          cache_helper::purge_all(true);
1437          upgrade_setup_debug(false);
1438          ignore_user_abort(false);
1439          if ($continueurl) {
1440              echo $OUTPUT->continue_button($continueurl);
1441              echo $OUTPUT->footer();
1442              die;
1443          }
1444      }
1445  }
1446  
1447  /**
1448   * @global object
1449   * @global object
1450   */
1451  function upgrade_setup_debug($starting) {
1452      global $CFG, $DB;
1453  
1454      static $originaldebug = null;
1455  
1456      if ($starting) {
1457          if ($originaldebug === null) {
1458              $originaldebug = $DB->get_debug();
1459          }
1460          if (!empty($CFG->upgradeshowsql)) {
1461              $DB->set_debug(true);
1462          }
1463      } else {
1464          $DB->set_debug($originaldebug);
1465      }
1466  }
1467  
1468  function print_upgrade_separator() {
1469      if (!CLI_SCRIPT) {
1470          echo '<hr />';
1471      }
1472  }
1473  
1474  /**
1475   * Default start upgrade callback
1476   * @param string $plugin
1477   * @param bool $installation true if installation, false means upgrade
1478   */
1479  function print_upgrade_part_start($plugin, $installation, $verbose) {
1480      global $OUTPUT;
1481      if (empty($plugin) or $plugin == 'moodle') {
1482          upgrade_started($installation); // does not store upgrade running flag yet
1483          if ($verbose) {
1484              echo $OUTPUT->heading(get_string('coresystem'));
1485          }
1486      } else {
1487          upgrade_started();
1488          if ($verbose) {
1489              echo $OUTPUT->heading($plugin);
1490          }
1491      }
1492      if ($installation) {
1493          if (empty($plugin) or $plugin == 'moodle') {
1494              // no need to log - log table not yet there ;-)
1495          } else {
1496              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin installation');
1497          }
1498      } else {
1499          if (empty($plugin) or $plugin == 'moodle') {
1500              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting core upgrade');
1501          } else {
1502              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin upgrade');
1503          }
1504      }
1505  }
1506  
1507  /**
1508   * Default end upgrade callback
1509   * @param string $plugin
1510   * @param bool $installation true if installation, false means upgrade
1511   */
1512  function print_upgrade_part_end($plugin, $installation, $verbose) {
1513      global $OUTPUT;
1514      upgrade_started();
1515      if ($installation) {
1516          if (empty($plugin) or $plugin == 'moodle') {
1517              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core installed');
1518          } else {
1519              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin installed');
1520          }
1521      } else {
1522          if (empty($plugin) or $plugin == 'moodle') {
1523              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core upgraded');
1524          } else {
1525              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin upgraded');
1526          }
1527      }
1528      if ($verbose) {
1529          $notification = new \core\output\notification(get_string('success'), \core\output\notification::NOTIFY_SUCCESS);
1530          $notification->set_show_closebutton(false);
1531          echo $OUTPUT->render($notification);
1532          print_upgrade_separator();
1533      }
1534  }
1535  
1536  /**
1537   * Sets up JS code required for all upgrade scripts.
1538   * @global object
1539   */
1540  function upgrade_init_javascript() {
1541      global $PAGE;
1542      // scroll to the end of each upgrade page so that ppl see either error or continue button,
1543      // no need to scroll continuously any more, it is enough to jump to end once the footer is printed ;-)
1544      $js = "window.scrollTo(0, 5000000);";
1545      $PAGE->requires->js_init_code($js);
1546  }
1547  
1548  /**
1549   * Try to upgrade the given language pack (or current language)
1550   *
1551   * @param string $lang the code of the language to update, defaults to the current language
1552   */
1553  function upgrade_language_pack($lang = null) {
1554      global $CFG;
1555  
1556      if (!empty($CFG->skiplangupgrade)) {
1557          return;
1558      }
1559  
1560      if (!file_exists("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php")) {
1561          // weird, somebody uninstalled the import utility
1562          return;
1563      }
1564  
1565      if (!$lang) {
1566          $lang = current_language();
1567      }
1568  
1569      if (!get_string_manager()->translation_exists($lang)) {
1570          return;
1571      }
1572  
1573      get_string_manager()->reset_caches();
1574  
1575      if ($lang === 'en') {
1576          return;  // Nothing to do
1577      }
1578  
1579      upgrade_started(false);
1580  
1581      require_once("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php");
1582      tool_langimport_preupgrade_update($lang);
1583  
1584      get_string_manager()->reset_caches();
1585  
1586      print_upgrade_separator();
1587  }
1588  
1589  /**
1590   * Install core moodle tables and initialize
1591   * @param float $version target version
1592   * @param bool $verbose
1593   * @return void, may throw exception
1594   */
1595  function install_core($version, $verbose) {
1596      global $CFG, $DB;
1597  
1598      // We can not call purge_all_caches() yet, make sure the temp and cache dirs exist and are empty.
1599      remove_dir($CFG->cachedir.'', true);
1600      make_cache_directory('', true);
1601  
1602      remove_dir($CFG->localcachedir.'', true);
1603      make_localcache_directory('', true);
1604  
1605      remove_dir($CFG->tempdir.'', true);
1606      make_temp_directory('', true);
1607  
1608      remove_dir($CFG->dataroot.'/muc', true);
1609      make_writable_directory($CFG->dataroot.'/muc', true);
1610  
1611      try {
1612          core_php_time_limit::raise(600);
1613          print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag
1614  
1615          $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml");
1616          upgrade_started();     // we want the flag to be stored in config table ;-)
1617  
1618          // set all core default records and default settings
1619          require_once("$CFG->libdir/db/install.php");
1620          xmldb_main_install(); // installs the capabilities too
1621  
1622          // store version
1623          upgrade_main_savepoint(true, $version, false);
1624  
1625          // Continue with the installation
1626          log_update_descriptions('moodle');
1627          external_update_descriptions('moodle');
1628          events_update_definition('moodle');
1629          \core\task\manager::reset_scheduled_tasks_for_component('moodle');
1630          message_update_providers('moodle');
1631          \core\message\inbound\manager::update_handlers_for_component('moodle');
1632          core_tag_area::reset_definitions_for_component('moodle');
1633  
1634          // Write default settings unconditionally
1635          admin_apply_default_settings(NULL, true);
1636  
1637          print_upgrade_part_end(null, true, $verbose);
1638  
1639          // Purge all caches. They're disabled but this ensures that we don't have any persistent data just in case something
1640          // during installation didn't use APIs.
1641          cache_helper::purge_all();
1642      } catch (exception $ex) {
1643          upgrade_handle_exception($ex);
1644      } catch (Throwable $ex) {
1645          // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
1646          upgrade_handle_exception($ex);
1647      }
1648  }
1649  
1650  /**
1651   * Upgrade moodle core
1652   * @param float $version target version
1653   * @param bool $verbose
1654   * @return void, may throw exception
1655   */
1656  function upgrade_core($version, $verbose) {
1657      global $CFG, $SITE, $DB, $COURSE;
1658  
1659      raise_memory_limit(MEMORY_EXTRA);
1660  
1661      require_once($CFG->libdir.'/db/upgrade.php');    // Defines upgrades
1662  
1663      try {
1664          // Reset caches before any output.
1665          cache_helper::purge_all(true);
1666          purge_all_caches();
1667  
1668          // Upgrade current language pack if we can
1669          upgrade_language_pack();
1670  
1671          print_upgrade_part_start('moodle', false, $verbose);
1672  
1673          // Pre-upgrade scripts for local hack workarounds.
1674          $preupgradefile = "$CFG->dirroot/local/preupgrade.php";
1675          if (file_exists($preupgradefile)) {
1676              core_php_time_limit::raise();
1677              require($preupgradefile);
1678              // Reset upgrade timeout to default.
1679              upgrade_set_timeout();
1680          }
1681  
1682          $result = xmldb_main_upgrade($CFG->version);
1683          if ($version > $CFG->version) {
1684              // store version if not already there
1685              upgrade_main_savepoint($result, $version, false);
1686          }
1687  
1688          // In case structure of 'course' table has been changed and we forgot to update $SITE, re-read it from db.
1689          $SITE = $DB->get_record('course', array('id' => $SITE->id));
1690          $COURSE = clone($SITE);
1691  
1692          // perform all other component upgrade routines
1693          update_capabilities('moodle');
1694          log_update_descriptions('moodle');
1695          external_update_descriptions('moodle');
1696          events_update_definition('moodle');
1697          \core\task\manager::reset_scheduled_tasks_for_component('moodle');
1698          message_update_providers('moodle');
1699          \core\message\inbound\manager::update_handlers_for_component('moodle');
1700          core_tag_area::reset_definitions_for_component('moodle');
1701          // Update core definitions.
1702          cache_helper::update_definitions(true);
1703  
1704          // Purge caches again, just to be sure we arn't holding onto old stuff now.
1705          cache_helper::purge_all(true);
1706          purge_all_caches();
1707  
1708          // Clean up contexts - more and more stuff depends on existence of paths and contexts
1709          context_helper::cleanup_instances();
1710          context_helper::create_instances(null, false);
1711          context_helper::build_all_paths(false);
1712          $syscontext = context_system::instance();
1713          $syscontext->mark_dirty();
1714  
1715          print_upgrade_part_end('moodle', false, $verbose);
1716      } catch (Exception $ex) {
1717          upgrade_handle_exception($ex);
1718      } catch (Throwable $ex) {
1719          // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
1720          upgrade_handle_exception($ex);
1721      }
1722  }
1723  
1724  /**
1725   * Upgrade/install other parts of moodle
1726   * @param bool $verbose
1727   * @return void, may throw exception
1728   */
1729  function upgrade_noncore($verbose) {
1730      global $CFG;
1731  
1732      raise_memory_limit(MEMORY_EXTRA);
1733  
1734      // upgrade all plugins types
1735      try {
1736          // Reset caches before any output.
1737          cache_helper::purge_all(true);
1738          purge_all_caches();
1739  
1740          $plugintypes = core_component::get_plugin_types();
1741          foreach ($plugintypes as $type=>$location) {
1742              upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose);
1743          }
1744          // Upgrade services.
1745          // This function gives plugins and subsystems a chance to add functions to existing built-in services.
1746          external_update_services();
1747  
1748          // Update cache definitions. Involves scanning each plugin for any changes.
1749          cache_helper::update_definitions();
1750          // Mark the site as upgraded.
1751          set_config('allversionshash', core_component::get_all_versions_hash());
1752  
1753          // Purge caches again, just to be sure we arn't holding onto old stuff now.
1754          cache_helper::purge_all(true);
1755          purge_all_caches();
1756  
1757      } catch (Exception $ex) {
1758          upgrade_handle_exception($ex);
1759      } catch (Throwable $ex) {
1760          // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
1761          upgrade_handle_exception($ex);
1762      }
1763  }
1764  
1765  /**
1766   * Checks if the main tables have been installed yet or not.
1767   *
1768   * Note: we can not use caches here because they might be stale,
1769   *       use with care!
1770   *
1771   * @return bool
1772   */
1773  function core_tables_exist() {
1774      global $DB;
1775  
1776      if (!$tables = $DB->get_tables(false) ) {    // No tables yet at all.
1777          return false;
1778  
1779      } else {                                 // Check for missing main tables
1780          $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml
1781          foreach ($mtables as $mtable) {
1782              if (!in_array($mtable, $tables)) {
1783                  return false;
1784              }
1785          }
1786          return true;
1787      }
1788  }
1789  
1790  /**
1791   * upgrades the mnet rpc definitions for the given component.
1792   * this method doesn't return status, an exception will be thrown in the case of an error
1793   *
1794   * @param string $component the plugin to upgrade, eg auth_mnet
1795   */
1796  function upgrade_plugin_mnet_functions($component) {
1797      global $DB, $CFG;
1798  
1799      list($type, $plugin) = core_component::normalize_component($component);
1800      $path = core_component::get_plugin_directory($type, $plugin);
1801  
1802      $publishes = array();
1803      $subscribes = array();
1804      if (file_exists($path . '/db/mnet.php')) {
1805          require_once($path . '/db/mnet.php'); // $publishes comes from this file
1806      }
1807      if (empty($publishes)) {
1808          $publishes = array(); // still need this to be able to disable stuff later
1809      }
1810      if (empty($subscribes)) {
1811          $subscribes = array(); // still need this to be able to disable stuff later
1812      }
1813  
1814      static $servicecache = array();
1815  
1816      // rekey an array based on the rpc method for easy lookups later
1817      $publishmethodservices = array();
1818      $subscribemethodservices = array();
1819      foreach($publishes as $servicename => $service) {
1820          if (is_array($service['methods'])) {
1821              foreach($service['methods'] as $methodname) {
1822                  $service['servicename'] = $servicename;
1823                  $publishmethodservices[$methodname][] = $service;
1824              }
1825          }
1826      }
1827  
1828      // Disable functions that don't exist (any more) in the source
1829      // Should these be deleted? What about their permissions records?
1830      foreach ($DB->get_records('mnet_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1831          if (!array_key_exists($rpc->functionname, $publishmethodservices) && $rpc->enabled) {
1832              $DB->set_field('mnet_rpc', 'enabled', 0, array('id' => $rpc->id));
1833          } else if (array_key_exists($rpc->functionname, $publishmethodservices) && !$rpc->enabled) {
1834              $DB->set_field('mnet_rpc', 'enabled', 1, array('id' => $rpc->id));
1835          }
1836      }
1837  
1838      // reflect all the services we're publishing and save them
1839      static $cachedclasses = array(); // to store reflection information in
1840      foreach ($publishes as $service => $data) {
1841          $f = $data['filename'];
1842          $c = $data['classname'];
1843          foreach ($data['methods'] as $method) {
1844              $dataobject = new stdClass();
1845              $dataobject->plugintype  = $type;
1846              $dataobject->pluginname  = $plugin;
1847              $dataobject->enabled     = 1;
1848              $dataobject->classname   = $c;
1849              $dataobject->filename    = $f;
1850  
1851              if (is_string($method)) {
1852                  $dataobject->functionname = $method;
1853  
1854              } else if (is_array($method)) { // wants to override file or class
1855                  $dataobject->functionname = $method['method'];
1856                  $dataobject->classname     = $method['classname'];
1857                  $dataobject->filename      = $method['filename'];
1858              }
1859              $dataobject->xmlrpcpath = $type.'/'.$plugin.'/'.$dataobject->filename.'/'.$method;
1860              $dataobject->static = false;
1861  
1862              require_once($path . '/' . $dataobject->filename);
1863              $functionreflect = null; // slightly different ways to get this depending on whether it's a class method or a function
1864              if (!empty($dataobject->classname)) {
1865                  if (!class_exists($dataobject->classname)) {
1866                      throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1867                  }
1868                  $key = $dataobject->filename . '|' . $dataobject->classname;
1869                  if (!array_key_exists($key, $cachedclasses)) { // look to see if we've already got a reflection object
1870                      try {
1871                          $cachedclasses[$key] = new ReflectionClass($dataobject->classname);
1872                      } catch (ReflectionException $e) { // catch these and rethrow them to something more helpful
1873                          throw new moodle_exception('installreflectionclasserror', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname, 'error' => $e->getMessage()));
1874                      }
1875                  }
1876                  $r =& $cachedclasses[$key];
1877                  if (!$r->hasMethod($dataobject->functionname)) {
1878                      throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1879                  }
1880                  $functionreflect = $r->getMethod($dataobject->functionname);
1881                  $dataobject->static = (int)$functionreflect->isStatic();
1882              } else {
1883                  if (!function_exists($dataobject->functionname)) {
1884                      throw new moodle_exception('installnosuchfunction', 'mnet', '', (object)array('method' => $dataobject->functionname, 'file' => $dataobject->filename));
1885                  }
1886                  try {
1887                      $functionreflect = new ReflectionFunction($dataobject->functionname);
1888                  } catch (ReflectionException $e) { // catch these and rethrow them to something more helpful
1889                      throw new moodle_exception('installreflectionfunctionerror', 'mnet', '', (object)array('method' => $dataobject->functionname, '' => $dataobject->filename, 'error' => $e->getMessage()));
1890                  }
1891              }
1892              $dataobject->profile =  serialize(admin_mnet_method_profile($functionreflect));
1893              $dataobject->help = admin_mnet_method_get_help($functionreflect);
1894  
1895              if ($record_exists = $DB->get_record('mnet_rpc', array('xmlrpcpath'=>$dataobject->xmlrpcpath))) {
1896                  $dataobject->id      = $record_exists->id;
1897                  $dataobject->enabled = $record_exists->enabled;
1898                  $DB->update_record('mnet_rpc', $dataobject);
1899              } else {
1900                  $dataobject->id = $DB->insert_record('mnet_rpc', $dataobject, true);
1901              }
1902  
1903              // TODO this API versioning must be reworked, here the recently processed method
1904              // sets the service API which may not be correct
1905              foreach ($publishmethodservices[$dataobject->functionname] as $service) {
1906                  if ($serviceobj = $DB->get_record('mnet_service', array('name'=>$service['servicename']))) {
1907                      $serviceobj->apiversion = $service['apiversion'];
1908                      $DB->update_record('mnet_service', $serviceobj);
1909                  } else {
1910                      $serviceobj = new stdClass();
1911                      $serviceobj->name        = $service['servicename'];
1912                      $serviceobj->description = empty($service['description']) ? '' : $service['description'];
1913                      $serviceobj->apiversion  = $service['apiversion'];
1914                      $serviceobj->offer       = 1;
1915                      $serviceobj->id          = $DB->insert_record('mnet_service', $serviceobj);
1916                  }
1917                  $servicecache[$service['servicename']] = $serviceobj;
1918                  if (!$DB->record_exists('mnet_service2rpc', array('rpcid'=>$dataobject->id, 'serviceid'=>$serviceobj->id))) {
1919                      $obj = new stdClass();
1920                      $obj->rpcid = $dataobject->id;
1921                      $obj->serviceid = $serviceobj->id;
1922                      $DB->insert_record('mnet_service2rpc', $obj, true);
1923                  }
1924              }
1925          }
1926      }
1927      // finished with methods we publish, now do subscribable methods
1928      foreach($subscribes as $service => $methods) {
1929          if (!array_key_exists($service, $servicecache)) {
1930              if (!$serviceobj = $DB->get_record('mnet_service', array('name' =>  $service))) {
1931                  debugging("TODO: skipping unknown service $service - somebody needs to fix MDL-21993");
1932                  continue;
1933              }
1934              $servicecache[$service] = $serviceobj;
1935          } else {
1936              $serviceobj = $servicecache[$service];
1937          }
1938          foreach ($methods as $method => $xmlrpcpath) {
1939              if (!$rpcid = $DB->get_field('mnet_remote_rpc', 'id', array('xmlrpcpath'=>$xmlrpcpath))) {
1940                  $remoterpc = (object)array(
1941                      'functionname' => $method,
1942                      'xmlrpcpath' => $xmlrpcpath,
1943                      'plugintype' => $type,
1944                      'pluginname' => $plugin,
1945                      'enabled'    => 1,
1946                  );
1947                  $rpcid = $remoterpc->id = $DB->insert_record('mnet_remote_rpc', $remoterpc, true);
1948              }
1949              if (!$DB->record_exists('mnet_remote_service2rpc', array('rpcid'=>$rpcid, 'serviceid'=>$serviceobj->id))) {
1950                  $obj = new stdClass();
1951                  $obj->rpcid = $rpcid;
1952                  $obj->serviceid = $serviceobj->id;
1953                  $DB->insert_record('mnet_remote_service2rpc', $obj, true);
1954              }
1955              $subscribemethodservices[$method][] = $service;
1956          }
1957      }
1958  
1959      foreach ($DB->get_records('mnet_remote_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1960          if (!array_key_exists($rpc->functionname, $subscribemethodservices) && $rpc->enabled) {
1961              $DB->set_field('mnet_remote_rpc', 'enabled', 0, array('id' => $rpc->id));
1962          } else if (array_key_exists($rpc->functionname, $subscribemethodservices) && !$rpc->enabled) {
1963              $DB->set_field('mnet_remote_rpc', 'enabled', 1, array('id' => $rpc->id));
1964          }
1965      }
1966  
1967      return true;
1968  }
1969  
1970  /**
1971   * Given some sort of reflection function/method object, return a profile array, ready to be serialized and stored
1972   *
1973   * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
1974   *
1975   * @return array associative array with function/method information
1976   */
1977  function admin_mnet_method_profile(ReflectionFunctionAbstract $function) {
1978      $commentlines = admin_mnet_method_get_docblock($function);
1979      $getkey = function($key) use ($commentlines) {
1980          return array_values(array_filter($commentlines, function($line) use ($key) {
1981              return $line[0] == $key;
1982          }));
1983      };
1984      $returnline = $getkey('@return');
1985      return array (
1986          'parameters' => array_map(function($line) {
1987              return array(
1988                  'name' => trim($line[2], " \t\n\r\0\x0B$"),
1989                  'type' => $line[1],
1990                  'description' => $line[3]
1991              );
1992          }, $getkey('@param')),
1993  
1994          'return' => array(
1995              'type' => !empty($returnline[0][1]) ? $returnline[0][1] : 'void',
1996              'description' => !empty($returnline[0][2]) ? $returnline[0][2] : ''
1997          )
1998      );
1999  }
2000  
2001  /**
2002   * Given some sort of reflection function/method object, return an array of docblock lines, where each line is an array of
2003   * keywords/descriptions
2004   *
2005   * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
2006   *
2007   * @return array docblock converted in to an array
2008   */
2009  function admin_mnet_method_get_docblock(ReflectionFunctionAbstract $function) {
2010      return array_map(function($line) {
2011          $text = trim($line, " \t\n\r\0\x0B*/");
2012          if (strpos($text, '@param') === 0) {
2013              return preg_split('/\s+/', $text, 4);
2014          }
2015  
2016          if (strpos($text, '@return') === 0) {
2017              return preg_split('/\s+/', $text, 3);
2018          }
2019  
2020          return array($text);
2021      }, explode("\n", $function->getDocComment()));
2022  }
2023  
2024  /**
2025   * Given some sort of reflection function/method object, return just the help text
2026   *
2027   * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
2028   *
2029   * @return string docblock help text
2030   */
2031  function admin_mnet_method_get_help(ReflectionFunctionAbstract $function) {
2032      $helplines = array_map(function($line) {
2033          return implode(' ', $line);
2034      }, array_values(array_filter(admin_mnet_method_get_docblock($function), function($line) {
2035          return strpos($line[0], '@') !== 0 && !empty($line[0]);
2036      })));
2037  
2038      return implode("\n", $helplines);
2039  }
2040  
2041  /**
2042   * Detect draft file areas with missing root directory records and add them.
2043   */
2044  function upgrade_fix_missing_root_folders_draft() {
2045      global $DB;
2046  
2047      $transaction = $DB->start_delegated_transaction();
2048  
2049      $sql = "SELECT contextid, itemid, MAX(timecreated) AS timecreated, MAX(timemodified) AS timemodified
2050                FROM {files}
2051               WHERE (component = 'user' AND filearea = 'draft')
2052            GROUP BY contextid, itemid
2053              HAVING MAX(CASE WHEN filename = '.' AND filepath = '/' THEN 1 ELSE 0 END) = 0";
2054  
2055      $rs = $DB->get_recordset_sql($sql);
2056      $defaults = array('component' => 'user',
2057          'filearea' => 'draft',
2058          'filepath' => '/',
2059          'filename' => '.',
2060          'userid' => 0, // Don't rely on any particular user for these system records.
2061          'filesize' => 0,
2062          'contenthash' => sha1(''));
2063      foreach ($rs as $r) {
2064          $r->pathnamehash = sha1("/$r->contextid/user/draft/$r->itemid/.");
2065          $DB->insert_record('files', (array)$r + $defaults);
2066      }
2067      $rs->close();
2068      $transaction->allow_commit();
2069  }
2070  
2071  /**
2072   * This function verifies that the database is not using an unsupported storage engine.
2073   *
2074   * @param environment_results $result object to update, if relevant
2075   * @return environment_results|null updated results object, or null if the storage engine is supported
2076   */
2077  function check_database_storage_engine(environment_results $result) {
2078      global $DB;
2079  
2080      // Check if MySQL is the DB family (this will also be the same for MariaDB).
2081      if ($DB->get_dbfamily() == 'mysql') {
2082          // Get the database engine we will either be using to install the tables, or what we are currently using.
2083          $engine = $DB->get_dbengine();
2084          // Check if MyISAM is the storage engine that will be used, if so, do not proceed and display an error.
2085          if ($engine == 'MyISAM') {
2086              $result->setInfo('unsupported_db_storage_engine');
2087              $result->setStatus(false);
2088              return $result;
2089          }
2090      }
2091  
2092      return null;
2093  }
2094  
2095  /**
2096   * Method used to check the usage of slasharguments config and display a warning message.
2097   *
2098   * @param environment_results $result object to update, if relevant.
2099   * @return environment_results|null updated results or null if slasharguments is disabled.
2100   */
2101  function check_slasharguments(environment_results $result){
2102      global $CFG;
2103  
2104      if (!during_initial_install() && empty($CFG->slasharguments)) {
2105          $result->setInfo('slasharguments');
2106          $result->setStatus(false);
2107          return $result;
2108      }
2109  
2110      return null;
2111  }
2112  
2113  /**
2114   * This function verifies if the database has tables using innoDB Antelope row format.
2115   *
2116   * @param environment_results $result
2117   * @return environment_results|null updated results object, or null if no Antelope table has been found.
2118   */
2119  function check_database_tables_row_format(environment_results $result) {
2120      global $DB;
2121  
2122      if ($DB->get_dbfamily() == 'mysql') {
2123          $generator = $DB->get_manager()->generator;
2124  
2125          foreach ($DB->get_tables(false) as $table) {
2126              $columns = $DB->get_columns($table, false);
2127              $size = $generator->guess_antelope_row_size($columns);
2128              $format = $DB->get_row_format($table);
2129  
2130              if ($size <= $generator::ANTELOPE_MAX_ROW_SIZE) {
2131                  continue;
2132              }
2133  
2134              if ($format === 'Compact' or $format === 'Redundant') {
2135                  $result->setInfo('unsupported_db_table_row_format');
2136                  $result->setStatus(false);
2137                  return $result;
2138              }
2139          }
2140      }
2141  
2142      return null;
2143  }
2144  
2145  /**
2146   * Upgrade the minmaxgrade setting.
2147   *
2148   * This step should only be run for sites running 2.8 or later. Sites using 2.7 will be fine
2149   * using the new default system setting $CFG->grade_minmaxtouse.
2150   *
2151   * @return void
2152   */
2153  function upgrade_minmaxgrade() {
2154      global $CFG, $DB;
2155  
2156      // 2 is a copy of GRADE_MIN_MAX_FROM_GRADE_GRADE.
2157      $settingvalue = 2;
2158  
2159      // Set the course setting when:
2160      // - The system setting does not exist yet.
2161      // - The system seeting is not set to what we'd set the course setting.
2162      $setcoursesetting = !isset($CFG->grade_minmaxtouse) || $CFG->grade_minmaxtouse != $settingvalue;
2163  
2164      // Identify the courses that have inconsistencies grade_item vs grade_grade.
2165      $sql = "SELECT DISTINCT(gi.courseid)
2166                FROM {grade_grades} gg
2167                JOIN {grade_items} gi
2168                  ON gg.itemid = gi.id
2169               WHERE gi.itemtype NOT IN (?, ?)
2170                 AND (gg.rawgrademax != gi.grademax OR gg.rawgrademin != gi.grademin)";
2171  
2172      $rs = $DB->get_recordset_sql($sql, array('course', 'category'));
2173      foreach ($rs as $record) {
2174          // Flag the course to show a notice in the gradebook.
2175          set_config('show_min_max_grades_changed_' . $record->courseid, 1);
2176  
2177          // Set the appropriate course setting so that grades displayed are not changed.
2178          $configname = 'minmaxtouse';
2179          if ($setcoursesetting &&
2180                  !$DB->record_exists('grade_settings', array('courseid' => $record->courseid, 'name' => $configname))) {
2181              // Do not set the setting when the course already defines it.
2182              $data = new stdClass();
2183              $data->courseid = $record->courseid;
2184              $data->name     = $configname;
2185              $data->value    = $settingvalue;
2186              $DB->insert_record('grade_settings', $data);
2187          }
2188  
2189          // Mark the grades to be regraded.
2190          $DB->set_field('grade_items', 'needsupdate', 1, array('courseid' => $record->courseid));
2191      }
2192      $rs->close();
2193  }
2194  
2195  
2196  /**
2197   * Assert the upgrade key is provided, if it is defined.
2198   *
2199   * The upgrade key can be defined in the main config.php as $CFG->upgradekey. If
2200   * it is defined there, then its value must be provided every time the site is
2201   * being upgraded, regardless the administrator is logged in or not.
2202   *
2203   * This is supposed to be used at certain places in /admin/index.php only.
2204   *
2205   * @param string|null $upgradekeyhash the SHA-1 of the value provided by the user
2206   */
2207  function check_upgrade_key($upgradekeyhash) {
2208      global $CFG, $PAGE;
2209  
2210      if (isset($CFG->config_php_settings['upgradekey'])) {
2211          if ($upgradekeyhash === null or $upgradekeyhash !== sha1($CFG->config_php_settings['upgradekey'])) {
2212              if (!$PAGE->headerprinted) {
2213                  $output = $PAGE->get_renderer('core', 'admin');
2214                  echo $output->upgradekey_form_page(new moodle_url('/admin/index.php', array('cache' => 0)));
2215                  die();
2216              } else {
2217                  // This should not happen.
2218                  die('Upgrade locked');
2219              }
2220          }
2221      }
2222  }
2223  
2224  /**
2225   * Helper procedure/macro for installing remote plugins at admin/index.php
2226   *
2227   * Does not return, always redirects or exits.
2228   *
2229   * @param array $installable list of \core\update\remote_info
2230   * @param bool $confirmed false: display the validation screen, true: proceed installation
2231   * @param string $heading validation screen heading
2232   * @param moodle_url|string|null $continue URL to proceed with installation at the validation screen
2233   * @param moodle_url|string|null $return URL to go back on cancelling at the validation screen
2234   */
2235  function upgrade_install_plugins(array $installable, $confirmed, $heading='', $continue=null, $return=null) {
2236      global $CFG, $PAGE;
2237  
2238      if (empty($return)) {
2239          $return = $PAGE->url;
2240      }
2241  
2242      if (!empty($CFG->disableupdateautodeploy)) {
2243          redirect($return);
2244      }
2245  
2246      if (empty($installable)) {
2247          redirect($return);
2248      }
2249  
2250      $pluginman = core_plugin_manager::instance();
2251  
2252      if ($confirmed) {
2253          // Installation confirmed at the validation results page.
2254          if (!$pluginman->install_plugins($installable, true, true)) {
2255              throw new moodle_exception('install_plugins_failed', 'core_plugin', $return);
2256          }
2257  
2258          // Always redirect to admin/index.php to perform the database upgrade.
2259          // Do not throw away the existing $PAGE->url parameters such as
2260          // confirmupgrade or confirmrelease if $PAGE->url is a superset of the
2261          // URL we must go to.
2262          $mustgoto = new moodle_url('/admin/index.php', array('cache' => 0, 'confirmplugincheck' => 0));
2263          if ($mustgoto->compare($PAGE->url, URL_MATCH_PARAMS)) {
2264              redirect($PAGE->url);
2265          } else {
2266              redirect($mustgoto);
2267          }
2268  
2269      } else {
2270          $output = $PAGE->get_renderer('core', 'admin');
2271          echo $output->header();
2272          if ($heading) {
2273              echo $output->heading($heading, 3);
2274          }
2275          echo html_writer::start_tag('pre', array('class' => 'plugin-install-console'));
2276          $validated = $pluginman->install_plugins($installable, false, false);
2277          echo html_writer::end_tag('pre');
2278          if ($validated) {
2279              echo $output->plugins_management_confirm_buttons($continue, $return);
2280          } else {
2281              echo $output->plugins_management_confirm_buttons(null, $return);
2282          }
2283          echo $output->footer();
2284          die();
2285      }
2286  }
2287  /**
2288   * Method used to check the installed unoconv version.
2289   *
2290   * @param environment_results $result object to update, if relevant.
2291   * @return environment_results|null updated results or null if unoconv path is not executable.
2292   */
2293  function check_unoconv_version(environment_results $result) {
2294      global $CFG;
2295  
2296      if (!during_initial_install() && !empty($CFG->pathtounoconv) && file_is_executable(trim($CFG->pathtounoconv))) {
2297          $currentversion = 0;
2298          $supportedversion = 0.7;
2299          $unoconvbin = \escapeshellarg($CFG->pathtounoconv);
2300          $command = "$unoconvbin --version";
2301          exec($command, $output);
2302  
2303          // If the command execution returned some output, then get the unoconv version.
2304          if ($output) {
2305              foreach ($output as $response) {
2306                  if (preg_match('/unoconv (\\d+\\.\\d+)/', $response, $matches)) {
2307                      $currentversion = (float)$matches[1];
2308                  }
2309              }
2310          }
2311  
2312          if ($currentversion < $supportedversion) {
2313              $result->setInfo('unoconv version not supported');
2314              $result->setStatus(false);
2315              return $result;
2316          }
2317      }
2318      return null;
2319  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1