[ Index ]

PHP Cross Reference of moodle-2.8

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 2.7.
 375          '/admin/tool/qeupgradehelper/version.php',
 376          // Removed in 2.6.
 377          '/admin/block.php',
 378          '/admin/oacleanup.php',
 379          // Removed in 2.5.
 380          '/backup/lib.php',
 381          '/backup/bb/README.txt',
 382          '/lib/excel/test.php',
 383          // Removed in 2.4.
 384          '/admin/tool/unittest/simpletestlib.php',
 385          // Removed in 2.3.
 386          '/lib/minify/builder/',
 387          // Removed in 2.2.
 388          '/lib/yui/3.4.1pr1/',
 389          // Removed in 2.2.
 390          '/search/cron_php5.php',
 391          '/course/report/log/indexlive.php',
 392          '/admin/report/backups/index.php',
 393          '/admin/generator.php',
 394          // Removed in 2.1.
 395          '/lib/yui/2.8.0r4/',
 396          // Removed in 2.0.
 397          '/blocks/admin/block_admin.php',
 398          '/blocks/admin_tree/block_admin_tree.php',
 399      );
 400  
 401      foreach ($someexamplesofremovedfiles as $file) {
 402          if (file_exists($CFG->dirroot.$file)) {
 403              return true;
 404          }
 405      }
 406  
 407      return false;
 408  }
 409  
 410  /**
 411   * Upgrade plugins
 412   * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
 413   * return void
 414   */
 415  function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
 416      global $CFG, $DB;
 417  
 418  /// special cases
 419      if ($type === 'mod') {
 420          return upgrade_plugins_modules($startcallback, $endcallback, $verbose);
 421      } else if ($type === 'block') {
 422          return upgrade_plugins_blocks($startcallback, $endcallback, $verbose);
 423      }
 424  
 425      $plugs = core_component::get_plugin_list($type);
 426  
 427      foreach ($plugs as $plug=>$fullplug) {
 428          // Reset time so that it works when installing a large number of plugins
 429          core_php_time_limit::raise(600);
 430          $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name
 431  
 432          // check plugin dir is valid name
 433          if (empty($component)) {
 434              throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.');
 435          }
 436  
 437          if (!is_readable($fullplug.'/version.php')) {
 438              continue;
 439          }
 440  
 441          $plugin = new stdClass();
 442          $plugin->version = null;
 443          $module = $plugin; // Prevent some notices when plugin placed in wrong directory.
 444          require ($fullplug.'/version.php');  // defines $plugin with version etc
 445          unset($module);
 446  
 447          // if plugin tells us it's full name we may check the location
 448          if (isset($plugin->component)) {
 449              if ($plugin->component !== $component) {
 450                  throw new plugin_misplaced_exception($plugin->component, null, $fullplug);
 451              }
 452          }
 453  
 454          if (empty($plugin->version)) {
 455              throw new plugin_defective_exception($component, 'Missing version value in version.php');
 456          }
 457  
 458          $plugin->name     = $plug;
 459          $plugin->fullname = $component;
 460  
 461          if (!empty($plugin->requires)) {
 462              if ($plugin->requires > $CFG->version) {
 463                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
 464              } else if ($plugin->requires < 2010000000) {
 465                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
 466              }
 467          }
 468  
 469          // try to recover from interrupted install.php if needed
 470          if (file_exists($fullplug.'/db/install.php')) {
 471              if (get_config($plugin->fullname, 'installrunning')) {
 472                  require_once ($fullplug.'/db/install.php');
 473                  $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery';
 474                  if (function_exists($recover_install_function)) {
 475                      $startcallback($component, true, $verbose);
 476                      $recover_install_function();
 477                      unset_config('installrunning', $plugin->fullname);
 478                      update_capabilities($component);
 479                      log_update_descriptions($component);
 480                      external_update_descriptions($component);
 481                      events_update_definition($component);
 482                      \core\task\manager::reset_scheduled_tasks_for_component($component);
 483                      message_update_providers($component);
 484                      \core\message\inbound\manager::update_handlers_for_component($component);
 485                      if ($type === 'message') {
 486                          message_update_processors($plug);
 487                      }
 488                      upgrade_plugin_mnet_functions($component);
 489                      $endcallback($component, true, $verbose);
 490                  }
 491              }
 492          }
 493  
 494          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 495          if (empty($installedversion)) { // new installation
 496              $startcallback($component, true, $verbose);
 497  
 498          /// Install tables if defined
 499              if (file_exists($fullplug.'/db/install.xml')) {
 500                  $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml');
 501              }
 502  
 503          /// store version
 504              upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false);
 505  
 506          /// execute post install file
 507              if (file_exists($fullplug.'/db/install.php')) {
 508                  require_once ($fullplug.'/db/install.php');
 509                  set_config('installrunning', 1, $plugin->fullname);
 510                  $post_install_function = 'xmldb_'.$plugin->fullname.'_install';
 511                  $post_install_function();
 512                  unset_config('installrunning', $plugin->fullname);
 513              }
 514  
 515          /// Install various components
 516              update_capabilities($component);
 517              log_update_descriptions($component);
 518              external_update_descriptions($component);
 519              events_update_definition($component);
 520              \core\task\manager::reset_scheduled_tasks_for_component($component);
 521              message_update_providers($component);
 522              \core\message\inbound\manager::update_handlers_for_component($component);
 523              if ($type === 'message') {
 524                  message_update_processors($plug);
 525              }
 526              upgrade_plugin_mnet_functions($component);
 527              $endcallback($component, true, $verbose);
 528  
 529          } else if ($installedversion < $plugin->version) { // upgrade
 530          /// Run the upgrade function for the plugin.
 531              $startcallback($component, false, $verbose);
 532  
 533              if (is_readable($fullplug.'/db/upgrade.php')) {
 534                  require_once ($fullplug.'/db/upgrade.php');  // defines upgrading function
 535  
 536                  $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade';
 537                  $result = $newupgrade_function($installedversion);
 538              } else {
 539                  $result = true;
 540              }
 541  
 542              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 543              if ($installedversion < $plugin->version) {
 544                  // store version if not already there
 545                  upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
 546              }
 547  
 548          /// Upgrade various components
 549              update_capabilities($component);
 550              log_update_descriptions($component);
 551              external_update_descriptions($component);
 552              events_update_definition($component);
 553              \core\task\manager::reset_scheduled_tasks_for_component($component);
 554              message_update_providers($component);
 555              \core\message\inbound\manager::update_handlers_for_component($component);
 556              if ($type === 'message') {
 557                  // Ugly hack!
 558                  message_update_processors($plug);
 559              }
 560              upgrade_plugin_mnet_functions($component);
 561              $endcallback($component, false, $verbose);
 562  
 563          } else if ($installedversion > $plugin->version) {
 564              throw new downgrade_exception($component, $installedversion, $plugin->version);
 565          }
 566      }
 567  }
 568  
 569  /**
 570   * Find and check all modules and load them up or upgrade them if necessary
 571   *
 572   * @global object
 573   * @global object
 574   */
 575  function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
 576      global $CFG, $DB;
 577  
 578      $mods = core_component::get_plugin_list('mod');
 579  
 580      foreach ($mods as $mod=>$fullmod) {
 581  
 582          if ($mod === 'NEWMODULE') {   // Someone has unzipped the template, ignore it
 583              continue;
 584          }
 585  
 586          $component = clean_param('mod_'.$mod, PARAM_COMPONENT);
 587  
 588          // check module dir is valid name
 589          if (empty($component)) {
 590              throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.');
 591          }
 592  
 593          if (!is_readable($fullmod.'/version.php')) {
 594              throw new plugin_defective_exception($component, 'Missing version.php');
 595          }
 596  
 597          // TODO: Support for $module will end with Moodle 2.10 by MDL-43896. Was deprecated for Moodle 2.7 by MDL-43040.
 598          $plugin = new stdClass();
 599          $plugin->version = null;
 600          $module = $plugin;
 601          require ($fullmod .'/version.php');  // Defines $plugin with version etc.
 602          $plugin = clone($module);
 603          unset($module->version);
 604          unset($module->component);
 605          unset($module->dependencies);
 606          unset($module->release);
 607  
 608          // if plugin tells us it's full name we may check the location
 609          if (isset($plugin->component)) {
 610              if ($plugin->component !== $component) {
 611                  throw new plugin_misplaced_exception($plugin->component, null, $fullmod);
 612              }
 613          }
 614  
 615          if (empty($plugin->version)) {
 616              // Version must be always set now!
 617              throw new plugin_defective_exception($component, 'Missing version value in version.php');
 618          }
 619  
 620          if (!empty($plugin->requires)) {
 621              if ($plugin->requires > $CFG->version) {
 622                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
 623              } else if ($plugin->requires < 2010000000) {
 624                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
 625              }
 626          }
 627  
 628          if (empty($module->cron)) {
 629              $module->cron = 0;
 630          }
 631  
 632          // all modules must have en lang pack
 633          if (!is_readable("$fullmod/lang/en/$mod.php")) {
 634              throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
 635          }
 636  
 637          $module->name = $mod;   // The name MUST match the directory
 638  
 639          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 640  
 641          if (file_exists($fullmod.'/db/install.php')) {
 642              if (get_config($module->name, 'installrunning')) {
 643                  require_once ($fullmod.'/db/install.php');
 644                  $recover_install_function = 'xmldb_'.$module->name.'_install_recovery';
 645                  if (function_exists($recover_install_function)) {
 646                      $startcallback($component, true, $verbose);
 647                      $recover_install_function();
 648                      unset_config('installrunning', $module->name);
 649                      // Install various components too
 650                      update_capabilities($component);
 651                      log_update_descriptions($component);
 652                      external_update_descriptions($component);
 653                      events_update_definition($component);
 654                      \core\task\manager::reset_scheduled_tasks_for_component($component);
 655                      message_update_providers($component);
 656                      \core\message\inbound\manager::update_handlers_for_component($component);
 657                      upgrade_plugin_mnet_functions($component);
 658                      $endcallback($component, true, $verbose);
 659                  }
 660              }
 661          }
 662  
 663          if (empty($installedversion)) {
 664              $startcallback($component, true, $verbose);
 665  
 666          /// Execute install.xml (XMLDB) - must be present in all modules
 667              $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml');
 668  
 669          /// Add record into modules table - may be needed in install.php already
 670              $module->id = $DB->insert_record('modules', $module);
 671              upgrade_mod_savepoint(true, $plugin->version, $module->name, false);
 672  
 673          /// Post installation hook - optional
 674              if (file_exists("$fullmod/db/install.php")) {
 675                  require_once("$fullmod/db/install.php");
 676                  // Set installation running flag, we need to recover after exception or error
 677                  set_config('installrunning', 1, $module->name);
 678                  $post_install_function = 'xmldb_'.$module->name.'_install';
 679                  $post_install_function();
 680                  unset_config('installrunning', $module->name);
 681              }
 682  
 683          /// Install various components
 684              update_capabilities($component);
 685              log_update_descriptions($component);
 686              external_update_descriptions($component);
 687              events_update_definition($component);
 688              \core\task\manager::reset_scheduled_tasks_for_component($component);
 689              message_update_providers($component);
 690              \core\message\inbound\manager::update_handlers_for_component($component);
 691              upgrade_plugin_mnet_functions($component);
 692  
 693              $endcallback($component, true, $verbose);
 694  
 695          } else if ($installedversion < $plugin->version) {
 696          /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
 697              $startcallback($component, false, $verbose);
 698  
 699              if (is_readable($fullmod.'/db/upgrade.php')) {
 700                  require_once ($fullmod.'/db/upgrade.php');  // defines new upgrading function
 701                  $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
 702                  $result = $newupgrade_function($installedversion, $module);
 703              } else {
 704                  $result = true;
 705              }
 706  
 707              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 708              $currmodule = $DB->get_record('modules', array('name'=>$module->name));
 709              if ($installedversion < $plugin->version) {
 710                  // store version if not already there
 711                  upgrade_mod_savepoint($result, $plugin->version, $mod, false);
 712              }
 713  
 714              // update cron flag if needed
 715              if ($currmodule->cron != $module->cron) {
 716                  $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
 717              }
 718  
 719              // Upgrade various components
 720              update_capabilities($component);
 721              log_update_descriptions($component);
 722              external_update_descriptions($component);
 723              events_update_definition($component);
 724              \core\task\manager::reset_scheduled_tasks_for_component($component);
 725              message_update_providers($component);
 726              \core\message\inbound\manager::update_handlers_for_component($component);
 727              upgrade_plugin_mnet_functions($component);
 728  
 729              $endcallback($component, false, $verbose);
 730  
 731          } else if ($installedversion > $plugin->version) {
 732              throw new downgrade_exception($component, $installedversion, $plugin->version);
 733          }
 734      }
 735  }
 736  
 737  
 738  /**
 739   * This function finds all available blocks and install them
 740   * into blocks table or do all the upgrade process if newer.
 741   *
 742   * @global object
 743   * @global object
 744   */
 745  function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
 746      global $CFG, $DB;
 747  
 748      require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
 749  
 750      $blocktitles   = array(); // we do not want duplicate titles
 751  
 752      //Is this a first install
 753      $first_install = null;
 754  
 755      $blocks = core_component::get_plugin_list('block');
 756  
 757      foreach ($blocks as $blockname=>$fullblock) {
 758  
 759          if (is_null($first_install)) {
 760              $first_install = ($DB->count_records('block_instances') == 0);
 761          }
 762  
 763          if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
 764              continue;
 765          }
 766  
 767          $component = clean_param('block_'.$blockname, PARAM_COMPONENT);
 768  
 769          // check block dir is valid name
 770          if (empty($component)) {
 771              throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.');
 772          }
 773  
 774          if (!is_readable($fullblock.'/version.php')) {
 775              throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
 776          }
 777          $plugin = new stdClass();
 778          $plugin->version = null;
 779          $plugin->cron    = 0;
 780          $module = $plugin; // Prevent some notices when module placed in wrong directory.
 781          include ($fullblock.'/version.php');
 782          unset($module);
 783          $block = clone($plugin);
 784          unset($block->version);
 785          unset($block->component);
 786          unset($block->dependencies);
 787          unset($block->release);
 788  
 789          // if plugin tells us it's full name we may check the location
 790          if (isset($plugin->component)) {
 791              if ($plugin->component !== $component) {
 792                  throw new plugin_misplaced_exception($plugin->component, null, $fullblock);
 793              }
 794          }
 795  
 796          if (empty($plugin->version)) {
 797              throw new plugin_defective_exception($component, 'Missing block version.');
 798          }
 799  
 800          if (!empty($plugin->requires)) {
 801              if ($plugin->requires > $CFG->version) {
 802                  throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
 803              } else if ($plugin->requires < 2010000000) {
 804                  throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
 805              }
 806          }
 807  
 808          if (!is_readable($fullblock.'/block_'.$blockname.'.php')) {
 809              throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.');
 810          }
 811          include_once($fullblock.'/block_'.$blockname.'.php');
 812  
 813          $classname = 'block_'.$blockname;
 814  
 815          if (!class_exists($classname)) {
 816              throw new plugin_defective_exception($component, 'Can not load main class.');
 817          }
 818  
 819          $blockobj    = new $classname;   // This is what we'll be testing
 820          $blocktitle  = $blockobj->get_title();
 821  
 822          // OK, it's as we all hoped. For further tests, the object will do them itself.
 823          if (!$blockobj->_self_test()) {
 824              throw new plugin_defective_exception($component, 'Self test failed.');
 825          }
 826  
 827          $block->name     = $blockname;   // The name MUST match the directory
 828  
 829          $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 830  
 831          if (file_exists($fullblock.'/db/install.php')) {
 832              if (get_config('block_'.$blockname, 'installrunning')) {
 833                  require_once ($fullblock.'/db/install.php');
 834                  $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery';
 835                  if (function_exists($recover_install_function)) {
 836                      $startcallback($component, true, $verbose);
 837                      $recover_install_function();
 838                      unset_config('installrunning', 'block_'.$blockname);
 839                      // Install various components
 840                      update_capabilities($component);
 841                      log_update_descriptions($component);
 842                      external_update_descriptions($component);
 843                      events_update_definition($component);
 844                      \core\task\manager::reset_scheduled_tasks_for_component($component);
 845                      message_update_providers($component);
 846                      \core\message\inbound\manager::update_handlers_for_component($component);
 847                      upgrade_plugin_mnet_functions($component);
 848                      $endcallback($component, true, $verbose);
 849                  }
 850              }
 851          }
 852  
 853          if (empty($installedversion)) { // block not installed yet, so install it
 854              $conflictblock = array_search($blocktitle, $blocktitles);
 855              if ($conflictblock !== false) {
 856                  // Duplicate block titles are not allowed, they confuse people
 857                  // AND PHP's associative arrays ;)
 858                  throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock)));
 859              }
 860              $startcallback($component, true, $verbose);
 861  
 862              if (file_exists($fullblock.'/db/install.xml')) {
 863                  $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
 864              }
 865              $block->id = $DB->insert_record('block', $block);
 866              upgrade_block_savepoint(true, $plugin->version, $block->name, false);
 867  
 868              if (file_exists($fullblock.'/db/install.php')) {
 869                  require_once ($fullblock.'/db/install.php');
 870                  // Set installation running flag, we need to recover after exception or error
 871                  set_config('installrunning', 1, 'block_'.$blockname);
 872                  $post_install_function = 'xmldb_block_'.$blockname.'_install';
 873                  $post_install_function();
 874                  unset_config('installrunning', 'block_'.$blockname);
 875              }
 876  
 877              $blocktitles[$block->name] = $blocktitle;
 878  
 879              // Install various components
 880              update_capabilities($component);
 881              log_update_descriptions($component);
 882              external_update_descriptions($component);
 883              events_update_definition($component);
 884              \core\task\manager::reset_scheduled_tasks_for_component($component);
 885              message_update_providers($component);
 886              \core\message\inbound\manager::update_handlers_for_component($component);
 887              upgrade_plugin_mnet_functions($component);
 888  
 889              $endcallback($component, true, $verbose);
 890  
 891          } else if ($installedversion < $plugin->version) {
 892              $startcallback($component, false, $verbose);
 893  
 894              if (is_readable($fullblock.'/db/upgrade.php')) {
 895                  require_once ($fullblock.'/db/upgrade.php');  // defines new upgrading function
 896                  $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
 897                  $result = $newupgrade_function($installedversion, $block);
 898              } else {
 899                  $result = true;
 900              }
 901  
 902              $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
 903              $currblock = $DB->get_record('block', array('name'=>$block->name));
 904              if ($installedversion < $plugin->version) {
 905                  // store version if not already there
 906                  upgrade_block_savepoint($result, $plugin->version, $block->name, false);
 907              }
 908  
 909              if ($currblock->cron != $block->cron) {
 910                  // update cron flag if needed
 911                  $DB->set_field('block', 'cron', $block->cron, array('id' => $currblock->id));
 912              }
 913  
 914              // Upgrade various components
 915              update_capabilities($component);
 916              log_update_descriptions($component);
 917              external_update_descriptions($component);
 918              events_update_definition($component);
 919              \core\task\manager::reset_scheduled_tasks_for_component($component);
 920              message_update_providers($component);
 921              \core\message\inbound\manager::update_handlers_for_component($component);
 922              upgrade_plugin_mnet_functions($component);
 923  
 924              $endcallback($component, false, $verbose);
 925  
 926          } else if ($installedversion > $plugin->version) {
 927              throw new downgrade_exception($component, $installedversion, $plugin->version);
 928          }
 929      }
 930  
 931  
 932      // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks
 933      if ($first_install) {
 934          //Iterate over each course - there should be only site course here now
 935          if ($courses = $DB->get_records('course')) {
 936              foreach ($courses as $course) {
 937                  blocks_add_default_course_blocks($course);
 938              }
 939          }
 940  
 941          blocks_add_default_system_blocks();
 942      }
 943  }
 944  
 945  
 946  /**
 947   * Log_display description function used during install and upgrade.
 948   *
 949   * @param string $component name of component (moodle, mod_assignment, etc.)
 950   * @return void
 951   */
 952  function log_update_descriptions($component) {
 953      global $DB;
 954  
 955      $defpath = core_component::get_component_directory($component).'/db/log.php';
 956  
 957      if (!file_exists($defpath)) {
 958          $DB->delete_records('log_display', array('component'=>$component));
 959          return;
 960      }
 961  
 962      // load new info
 963      $logs = array();
 964      include($defpath);
 965      $newlogs = array();
 966      foreach ($logs as $log) {
 967          $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name
 968      }
 969      unset($logs);
 970      $logs = $newlogs;
 971  
 972      $fields = array('module', 'action', 'mtable', 'field');
 973      // update all log fist
 974      $dblogs = $DB->get_records('log_display', array('component'=>$component));
 975      foreach ($dblogs as $dblog) {
 976          $name = $dblog->module.'-'.$dblog->action;
 977  
 978          if (empty($logs[$name])) {
 979              $DB->delete_records('log_display', array('id'=>$dblog->id));
 980              continue;
 981          }
 982  
 983          $log = $logs[$name];
 984          unset($logs[$name]);
 985  
 986          $update = false;
 987          foreach ($fields as $field) {
 988              if ($dblog->$field != $log[$field]) {
 989                  $dblog->$field = $log[$field];
 990                  $update = true;
 991              }
 992          }
 993          if ($update) {
 994              $DB->update_record('log_display', $dblog);
 995          }
 996      }
 997      foreach ($logs as $log) {
 998          $dblog = (object)$log;
 999          $dblog->component = $component;
1000          $DB->insert_record('log_display', $dblog);
1001      }
1002  }
1003  
1004  /**
1005   * Web service discovery function used during install and upgrade.
1006   * @param string $component name of component (moodle, mod_assignment, etc.)
1007   * @return void
1008   */
1009  function external_update_descriptions($component) {
1010      global $DB, $CFG;
1011  
1012      $defpath = core_component::get_component_directory($component).'/db/services.php';
1013  
1014      if (!file_exists($defpath)) {
1015          require_once($CFG->dirroot.'/lib/externallib.php');
1016          external_delete_descriptions($component);
1017          return;
1018      }
1019  
1020      // load new info
1021      $functions = array();
1022      $services = array();
1023      include($defpath);
1024  
1025      // update all function fist
1026      $dbfunctions = $DB->get_records('external_functions', array('component'=>$component));
1027      foreach ($dbfunctions as $dbfunction) {
1028          if (empty($functions[$dbfunction->name])) {
1029              $DB->delete_records('external_functions', array('id'=>$dbfunction->id));
1030              // do not delete functions from external_services_functions, beacuse
1031              // we want to notify admins when functions used in custom services disappear
1032  
1033              //TODO: this looks wrong, we have to delete it eventually (skodak)
1034              continue;
1035          }
1036  
1037          $function = $functions[$dbfunction->name];
1038          unset($functions[$dbfunction->name]);
1039          $function['classpath'] = empty($function['classpath']) ? null : $function['classpath'];
1040  
1041          $update = false;
1042          if ($dbfunction->classname != $function['classname']) {
1043              $dbfunction->classname = $function['classname'];
1044              $update = true;
1045          }
1046          if ($dbfunction->methodname != $function['methodname']) {
1047              $dbfunction->methodname = $function['methodname'];
1048              $update = true;
1049          }
1050          if ($dbfunction->classpath != $function['classpath']) {
1051              $dbfunction->classpath = $function['classpath'];
1052              $update = true;
1053          }
1054          $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
1055          if ($dbfunction->capabilities != $functioncapabilities) {
1056              $dbfunction->capabilities = $functioncapabilities;
1057              $update = true;
1058          }
1059          if ($update) {
1060              $DB->update_record('external_functions', $dbfunction);
1061          }
1062      }
1063      foreach ($functions as $fname => $function) {
1064          $dbfunction = new stdClass();
1065          $dbfunction->name       = $fname;
1066          $dbfunction->classname  = $function['classname'];
1067          $dbfunction->methodname = $function['methodname'];
1068          $dbfunction->classpath  = empty($function['classpath']) ? null : $function['classpath'];
1069          $dbfunction->component  = $component;
1070          $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
1071          $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
1072      }
1073      unset($functions);
1074  
1075      // now deal with services
1076      $dbservices = $DB->get_records('external_services', array('component'=>$component));
1077      foreach ($dbservices as $dbservice) {
1078          if (empty($services[$dbservice->name])) {
1079              $DB->delete_records('external_tokens', array('externalserviceid'=>$dbservice->id));
1080              $DB->delete_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1081              $DB->delete_records('external_services_users', array('externalserviceid'=>$dbservice->id));
1082              $DB->delete_records('external_services', array('id'=>$dbservice->id));
1083              continue;
1084          }
1085          $service = $services[$dbservice->name];
1086          unset($services[$dbservice->name]);
1087          $service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled'];
1088          $service['requiredcapability'] = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1089          $service['restrictedusers'] = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1090          $service['downloadfiles'] = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1091          $service['uploadfiles'] = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
1092          $service['shortname'] = !isset($service['shortname']) ? null : $service['shortname'];
1093  
1094          $update = false;
1095          if ($dbservice->requiredcapability != $service['requiredcapability']) {
1096              $dbservice->requiredcapability = $service['requiredcapability'];
1097              $update = true;
1098          }
1099          if ($dbservice->restrictedusers != $service['restrictedusers']) {
1100              $dbservice->restrictedusers = $service['restrictedusers'];
1101              $update = true;
1102          }
1103          if ($dbservice->downloadfiles != $service['downloadfiles']) {
1104              $dbservice->downloadfiles = $service['downloadfiles'];
1105              $update = true;
1106          }
1107          if ($dbservice->uploadfiles != $service['uploadfiles']) {
1108              $dbservice->uploadfiles = $service['uploadfiles'];
1109              $update = true;
1110          }
1111          //if shortname is not a PARAM_ALPHANUMEXT, fail (tested here for service update and creation)
1112          if (isset($service['shortname']) and
1113                  (clean_param($service['shortname'], PARAM_ALPHANUMEXT) != $service['shortname'])) {
1114              throw new moodle_exception('installserviceshortnameerror', 'webservice', '', $service['shortname']);
1115          }
1116          if ($dbservice->shortname != $service['shortname']) {
1117              //check that shortname is unique
1118              if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1119                  $existingservice = $DB->get_record('external_services',
1120                          array('shortname' => $service['shortname']));
1121                  if (!empty($existingservice)) {
1122                      throw new moodle_exception('installexistingserviceshortnameerror', 'webservice', '', $service['shortname']);
1123                  }
1124              }
1125              $dbservice->shortname = $service['shortname'];
1126              $update = true;
1127          }
1128          if ($update) {
1129              $DB->update_record('external_services', $dbservice);
1130          }
1131  
1132          $functions = $DB->get_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1133          foreach ($functions as $function) {
1134              $key = array_search($function->functionname, $service['functions']);
1135              if ($key === false) {
1136                  $DB->delete_records('external_services_functions', array('id'=>$function->id));
1137              } else {
1138                  unset($service['functions'][$key]);
1139              }
1140          }
1141          foreach ($service['functions'] as $fname) {
1142              $newf = new stdClass();
1143              $newf->externalserviceid = $dbservice->id;
1144              $newf->functionname      = $fname;
1145              $DB->insert_record('external_services_functions', $newf);
1146          }
1147          unset($functions);
1148      }
1149      foreach ($services as $name => $service) {
1150          //check that shortname is unique
1151          if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1152              $existingservice = $DB->get_record('external_services',
1153                      array('shortname' => $service['shortname']));
1154              if (!empty($existingservice)) {
1155                  throw new moodle_exception('installserviceshortnameerror', 'webservice');
1156              }
1157          }
1158  
1159          $dbservice = new stdClass();
1160          $dbservice->name               = $name;
1161          $dbservice->enabled            = empty($service['enabled']) ? 0 : $service['enabled'];
1162          $dbservice->requiredcapability = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1163          $dbservice->restrictedusers    = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1164          $dbservice->downloadfiles      = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1165          $dbservice->uploadfiles        = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
1166          $dbservice->shortname          = !isset($service['shortname']) ? null : $service['shortname'];
1167          $dbservice->component          = $component;
1168          $dbservice->timecreated        = time();
1169          $dbservice->id = $DB->insert_record('external_services', $dbservice);
1170          foreach ($service['functions'] as $fname) {
1171              $newf = new stdClass();
1172              $newf->externalserviceid = $dbservice->id;
1173              $newf->functionname      = $fname;
1174              $DB->insert_record('external_services_functions', $newf);
1175          }
1176      }
1177  }
1178  
1179  /**
1180   * upgrade logging functions
1181   */
1182  function upgrade_handle_exception($ex, $plugin = null) {
1183      global $CFG;
1184  
1185      // rollback everything, we need to log all upgrade problems
1186      abort_all_db_transactions();
1187  
1188      $info = get_exception_info($ex);
1189  
1190      // First log upgrade error
1191      upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace);
1192  
1193      // Always turn on debugging - admins need to know what is going on
1194      set_debugging(DEBUG_DEVELOPER, true);
1195  
1196      default_exception_handler($ex, true, $plugin);
1197  }
1198  
1199  /**
1200   * Adds log entry into upgrade_log table
1201   *
1202   * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR
1203   * @param string $plugin frankenstyle component name
1204   * @param string $info short description text of log entry
1205   * @param string $details long problem description
1206   * @param string $backtrace string used for errors only
1207   * @return void
1208   */
1209  function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
1210      global $DB, $USER, $CFG;
1211  
1212      if (empty($plugin)) {
1213          $plugin = 'core';
1214      }
1215  
1216      list($plugintype, $pluginname) = core_component::normalize_component($plugin);
1217      $component = is_null($pluginname) ? $plugintype : $plugintype . '_' . $pluginname;
1218  
1219      $backtrace = format_backtrace($backtrace, true);
1220  
1221      $currentversion = null;
1222      $targetversion  = null;
1223  
1224      //first try to find out current version number
1225      if ($plugintype === 'core') {
1226          //main
1227          $currentversion = $CFG->version;
1228  
1229          $version = null;
1230          include("$CFG->dirroot/version.php");
1231          $targetversion = $version;
1232  
1233      } else {
1234          $pluginversion = get_config($component, 'version');
1235          if (!empty($pluginversion)) {
1236              $currentversion = $pluginversion;
1237          }
1238          $cd = core_component::get_component_directory($component);
1239          if (file_exists("$cd/version.php")) {
1240              $plugin = new stdClass();
1241              $plugin->version = null;
1242              $module = $plugin;
1243              include("$cd/version.php");
1244              $targetversion = $plugin->version;
1245          }
1246      }
1247  
1248      $log = new stdClass();
1249      $log->type          = $type;
1250      $log->plugin        = $component;
1251      $log->version       = $currentversion;
1252      $log->targetversion = $targetversion;
1253      $log->info          = $info;
1254      $log->details       = $details;
1255      $log->backtrace     = $backtrace;
1256      $log->userid        = $USER->id;
1257      $log->timemodified  = time();
1258      try {
1259          $DB->insert_record('upgrade_log', $log);
1260      } catch (Exception $ignored) {
1261          // possible during install or 2.0 upgrade
1262      }
1263  }
1264  
1265  /**
1266   * Marks start of upgrade, blocks any other access to site.
1267   * The upgrade is finished at the end of script or after timeout.
1268   *
1269   * @global object
1270   * @global object
1271   * @global object
1272   */
1273  function upgrade_started($preinstall=false) {
1274      global $CFG, $DB, $PAGE, $OUTPUT;
1275  
1276      static $started = false;
1277  
1278      if ($preinstall) {
1279          ignore_user_abort(true);
1280          upgrade_setup_debug(true);
1281  
1282      } else if ($started) {
1283          upgrade_set_timeout(120);
1284  
1285      } else {
1286          if (!CLI_SCRIPT and !$PAGE->headerprinted) {
1287              $strupgrade  = get_string('upgradingversion', 'admin');
1288              $PAGE->set_pagelayout('maintenance');
1289              upgrade_init_javascript();
1290              $PAGE->set_title($strupgrade.' - Moodle '.$CFG->target_release);
1291              $PAGE->set_heading($strupgrade);
1292              $PAGE->navbar->add($strupgrade);
1293              $PAGE->set_cacheable(false);
1294              echo $OUTPUT->header();
1295          }
1296  
1297          ignore_user_abort(true);
1298          core_shutdown_manager::register_function('upgrade_finished_handler');
1299          upgrade_setup_debug(true);
1300          set_config('upgraderunning', time()+300);
1301          $started = true;
1302      }
1303  }
1304  
1305  /**
1306   * Internal function - executed if upgrade interrupted.
1307   */
1308  function upgrade_finished_handler() {
1309      upgrade_finished();
1310  }
1311  
1312  /**
1313   * Indicates upgrade is finished.
1314   *
1315   * This function may be called repeatedly.
1316   *
1317   * @global object
1318   * @global object
1319   */
1320  function upgrade_finished($continueurl=null) {
1321      global $CFG, $DB, $OUTPUT;
1322  
1323      if (!empty($CFG->upgraderunning)) {
1324          unset_config('upgraderunning');
1325          // We have to forcefully purge the caches using the writer here.
1326          // This has to be done after we unset the config var. If someone hits the site while this is set they will
1327          // cause the config values to propogate to the caches.
1328          // Caches are purged after the last step in an upgrade but there is several code routines that exceute between
1329          // then and now that leaving a window for things to fall out of sync.
1330          cache_helper::purge_all(true);
1331          upgrade_setup_debug(false);
1332          ignore_user_abort(false);
1333          if ($continueurl) {
1334              echo $OUTPUT->continue_button($continueurl);
1335              echo $OUTPUT->footer();
1336              die;
1337          }
1338      }
1339  }
1340  
1341  /**
1342   * @global object
1343   * @global object
1344   */
1345  function upgrade_setup_debug($starting) {
1346      global $CFG, $DB;
1347  
1348      static $originaldebug = null;
1349  
1350      if ($starting) {
1351          if ($originaldebug === null) {
1352              $originaldebug = $DB->get_debug();
1353          }
1354          if (!empty($CFG->upgradeshowsql)) {
1355              $DB->set_debug(true);
1356          }
1357      } else {
1358          $DB->set_debug($originaldebug);
1359      }
1360  }
1361  
1362  function print_upgrade_separator() {
1363      if (!CLI_SCRIPT) {
1364          echo '<hr />';
1365      }
1366  }
1367  
1368  /**
1369   * Default start upgrade callback
1370   * @param string $plugin
1371   * @param bool $installation true if installation, false means upgrade
1372   */
1373  function print_upgrade_part_start($plugin, $installation, $verbose) {
1374      global $OUTPUT;
1375      if (empty($plugin) or $plugin == 'moodle') {
1376          upgrade_started($installation); // does not store upgrade running flag yet
1377          if ($verbose) {
1378              echo $OUTPUT->heading(get_string('coresystem'));
1379          }
1380      } else {
1381          upgrade_started();
1382          if ($verbose) {
1383              echo $OUTPUT->heading($plugin);
1384          }
1385      }
1386      if ($installation) {
1387          if (empty($plugin) or $plugin == 'moodle') {
1388              // no need to log - log table not yet there ;-)
1389          } else {
1390              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin installation');
1391          }
1392      } else {
1393          if (empty($plugin) or $plugin == 'moodle') {
1394              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting core upgrade');
1395          } else {
1396              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin upgrade');
1397          }
1398      }
1399  }
1400  
1401  /**
1402   * Default end upgrade callback
1403   * @param string $plugin
1404   * @param bool $installation true if installation, false means upgrade
1405   */
1406  function print_upgrade_part_end($plugin, $installation, $verbose) {
1407      global $OUTPUT;
1408      upgrade_started();
1409      if ($installation) {
1410          if (empty($plugin) or $plugin == 'moodle') {
1411              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core installed');
1412          } else {
1413              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin installed');
1414          }
1415      } else {
1416          if (empty($plugin) or $plugin == 'moodle') {
1417              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core upgraded');
1418          } else {
1419              upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin upgraded');
1420          }
1421      }
1422      if ($verbose) {
1423          echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
1424          print_upgrade_separator();
1425      }
1426  }
1427  
1428  /**
1429   * Sets up JS code required for all upgrade scripts.
1430   * @global object
1431   */
1432  function upgrade_init_javascript() {
1433      global $PAGE;
1434      // scroll to the end of each upgrade page so that ppl see either error or continue button,
1435      // no need to scroll continuously any more, it is enough to jump to end once the footer is printed ;-)
1436      $js = "window.scrollTo(0, 5000000);";
1437      $PAGE->requires->js_init_code($js);
1438  }
1439  
1440  /**
1441   * Try to upgrade the given language pack (or current language)
1442   *
1443   * @param string $lang the code of the language to update, defaults to the current language
1444   */
1445  function upgrade_language_pack($lang = null) {
1446      global $CFG;
1447  
1448      if (!empty($CFG->skiplangupgrade)) {
1449          return;
1450      }
1451  
1452      if (!file_exists("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php")) {
1453          // weird, somebody uninstalled the import utility
1454          return;
1455      }
1456  
1457      if (!$lang) {
1458          $lang = current_language();
1459      }
1460  
1461      if (!get_string_manager()->translation_exists($lang)) {
1462          return;
1463      }
1464  
1465      get_string_manager()->reset_caches();
1466  
1467      if ($lang === 'en') {
1468          return;  // Nothing to do
1469      }
1470  
1471      upgrade_started(false);
1472  
1473      require_once("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php");
1474      tool_langimport_preupgrade_update($lang);
1475  
1476      get_string_manager()->reset_caches();
1477  
1478      print_upgrade_separator();
1479  }
1480  
1481  /**
1482   * Install core moodle tables and initialize
1483   * @param float $version target version
1484   * @param bool $verbose
1485   * @return void, may throw exception
1486   */
1487  function install_core($version, $verbose) {
1488      global $CFG, $DB;
1489  
1490      // We can not call purge_all_caches() yet, make sure the temp and cache dirs exist and are empty.
1491      remove_dir($CFG->cachedir.'', true);
1492      make_cache_directory('', true);
1493  
1494      remove_dir($CFG->localcachedir.'', true);
1495      make_localcache_directory('', true);
1496  
1497      remove_dir($CFG->tempdir.'', true);
1498      make_temp_directory('', true);
1499  
1500      remove_dir($CFG->dataroot.'/muc', true);
1501      make_writable_directory($CFG->dataroot.'/muc', true);
1502  
1503      try {
1504          core_php_time_limit::raise(600);
1505          print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag
1506  
1507          $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml");
1508          upgrade_started();     // we want the flag to be stored in config table ;-)
1509  
1510          // set all core default records and default settings
1511          require_once("$CFG->libdir/db/install.php");
1512          xmldb_main_install(); // installs the capabilities too
1513  
1514          // store version
1515          upgrade_main_savepoint(true, $version, false);
1516  
1517          // Continue with the installation
1518          log_update_descriptions('moodle');
1519          external_update_descriptions('moodle');
1520          events_update_definition('moodle');
1521          \core\task\manager::reset_scheduled_tasks_for_component('moodle');
1522          message_update_providers('moodle');
1523          \core\message\inbound\manager::update_handlers_for_component('moodle');
1524  
1525          // Write default settings unconditionally
1526          admin_apply_default_settings(NULL, true);
1527  
1528          print_upgrade_part_end(null, true, $verbose);
1529  
1530          // Purge all caches. They're disabled but this ensures that we don't have any persistent data just in case something
1531          // during installation didn't use APIs.
1532          cache_helper::purge_all();
1533      } catch (exception $ex) {
1534          upgrade_handle_exception($ex);
1535      }
1536  }
1537  
1538  /**
1539   * Upgrade moodle core
1540   * @param float $version target version
1541   * @param bool $verbose
1542   * @return void, may throw exception
1543   */
1544  function upgrade_core($version, $verbose) {
1545      global $CFG, $SITE, $DB, $COURSE;
1546  
1547      raise_memory_limit(MEMORY_EXTRA);
1548  
1549      require_once($CFG->libdir.'/db/upgrade.php');    // Defines upgrades
1550  
1551      try {
1552          // Reset caches before any output.
1553          cache_helper::purge_all(true);
1554          purge_all_caches();
1555  
1556          // Upgrade current language pack if we can
1557          upgrade_language_pack();
1558  
1559          print_upgrade_part_start('moodle', false, $verbose);
1560  
1561          // Pre-upgrade scripts for local hack workarounds.
1562          $preupgradefile = "$CFG->dirroot/local/preupgrade.php";
1563          if (file_exists($preupgradefile)) {
1564              core_php_time_limit::raise();
1565              require($preupgradefile);
1566              // Reset upgrade timeout to default.
1567              upgrade_set_timeout();
1568          }
1569  
1570          $result = xmldb_main_upgrade($CFG->version);
1571          if ($version > $CFG->version) {
1572              // store version if not already there
1573              upgrade_main_savepoint($result, $version, false);
1574          }
1575  
1576          // In case structure of 'course' table has been changed and we forgot to update $SITE, re-read it from db.
1577          $SITE = $DB->get_record('course', array('id' => $SITE->id));
1578          $COURSE = clone($SITE);
1579  
1580          // perform all other component upgrade routines
1581          update_capabilities('moodle');
1582          log_update_descriptions('moodle');
1583          external_update_descriptions('moodle');
1584          events_update_definition('moodle');
1585          \core\task\manager::reset_scheduled_tasks_for_component('moodle');
1586          message_update_providers('moodle');
1587          \core\message\inbound\manager::update_handlers_for_component('moodle');
1588          // Update core definitions.
1589          cache_helper::update_definitions(true);
1590  
1591          // Purge caches again, just to be sure we arn't holding onto old stuff now.
1592          cache_helper::purge_all(true);
1593          purge_all_caches();
1594  
1595          // Clean up contexts - more and more stuff depends on existence of paths and contexts
1596          context_helper::cleanup_instances();
1597          context_helper::create_instances(null, false);
1598          context_helper::build_all_paths(false);
1599          $syscontext = context_system::instance();
1600          $syscontext->mark_dirty();
1601  
1602          print_upgrade_part_end('moodle', false, $verbose);
1603      } catch (Exception $ex) {
1604          upgrade_handle_exception($ex);
1605      }
1606  }
1607  
1608  /**
1609   * Upgrade/install other parts of moodle
1610   * @param bool $verbose
1611   * @return void, may throw exception
1612   */
1613  function upgrade_noncore($verbose) {
1614      global $CFG;
1615  
1616      raise_memory_limit(MEMORY_EXTRA);
1617  
1618      // upgrade all plugins types
1619      try {
1620          // Reset caches before any output.
1621          cache_helper::purge_all(true);
1622          purge_all_caches();
1623  
1624          $plugintypes = core_component::get_plugin_types();
1625          foreach ($plugintypes as $type=>$location) {
1626              upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose);
1627          }
1628          // Update cache definitions. Involves scanning each plugin for any changes.
1629          cache_helper::update_definitions();
1630          // Mark the site as upgraded.
1631          set_config('allversionshash', core_component::get_all_versions_hash());
1632  
1633          // Purge caches again, just to be sure we arn't holding onto old stuff now.
1634          cache_helper::purge_all(true);
1635          purge_all_caches();
1636  
1637      } catch (Exception $ex) {
1638          upgrade_handle_exception($ex);
1639      }
1640  }
1641  
1642  /**
1643   * Checks if the main tables have been installed yet or not.
1644   *
1645   * Note: we can not use caches here because they might be stale,
1646   *       use with care!
1647   *
1648   * @return bool
1649   */
1650  function core_tables_exist() {
1651      global $DB;
1652  
1653      if (!$tables = $DB->get_tables(false) ) {    // No tables yet at all.
1654          return false;
1655  
1656      } else {                                 // Check for missing main tables
1657          $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml
1658          foreach ($mtables as $mtable) {
1659              if (!in_array($mtable, $tables)) {
1660                  return false;
1661              }
1662          }
1663          return true;
1664      }
1665  }
1666  
1667  /**
1668   * upgrades the mnet rpc definitions for the given component.
1669   * this method doesn't return status, an exception will be thrown in the case of an error
1670   *
1671   * @param string $component the plugin to upgrade, eg auth_mnet
1672   */
1673  function upgrade_plugin_mnet_functions($component) {
1674      global $DB, $CFG;
1675  
1676      list($type, $plugin) = core_component::normalize_component($component);
1677      $path = core_component::get_plugin_directory($type, $plugin);
1678  
1679      $publishes = array();
1680      $subscribes = array();
1681      if (file_exists($path . '/db/mnet.php')) {
1682          require_once($path . '/db/mnet.php'); // $publishes comes from this file
1683      }
1684      if (empty($publishes)) {
1685          $publishes = array(); // still need this to be able to disable stuff later
1686      }
1687      if (empty($subscribes)) {
1688          $subscribes = array(); // still need this to be able to disable stuff later
1689      }
1690  
1691      static $servicecache = array();
1692  
1693      // rekey an array based on the rpc method for easy lookups later
1694      $publishmethodservices = array();
1695      $subscribemethodservices = array();
1696      foreach($publishes as $servicename => $service) {
1697          if (is_array($service['methods'])) {
1698              foreach($service['methods'] as $methodname) {
1699                  $service['servicename'] = $servicename;
1700                  $publishmethodservices[$methodname][] = $service;
1701              }
1702          }
1703      }
1704  
1705      // Disable functions that don't exist (any more) in the source
1706      // Should these be deleted? What about their permissions records?
1707      foreach ($DB->get_records('mnet_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1708          if (!array_key_exists($rpc->functionname, $publishmethodservices) && $rpc->enabled) {
1709              $DB->set_field('mnet_rpc', 'enabled', 0, array('id' => $rpc->id));
1710          } else if (array_key_exists($rpc->functionname, $publishmethodservices) && !$rpc->enabled) {
1711              $DB->set_field('mnet_rpc', 'enabled', 1, array('id' => $rpc->id));
1712          }
1713      }
1714  
1715      // reflect all the services we're publishing and save them
1716      require_once($CFG->dirroot . '/lib/zend/Zend/Server/Reflection.php');
1717      static $cachedclasses = array(); // to store reflection information in
1718      foreach ($publishes as $service => $data) {
1719          $f = $data['filename'];
1720          $c = $data['classname'];
1721          foreach ($data['methods'] as $method) {
1722              $dataobject = new stdClass();
1723              $dataobject->plugintype  = $type;
1724              $dataobject->pluginname  = $plugin;
1725              $dataobject->enabled     = 1;
1726              $dataobject->classname   = $c;
1727              $dataobject->filename    = $f;
1728  
1729              if (is_string($method)) {
1730                  $dataobject->functionname = $method;
1731  
1732              } else if (is_array($method)) { // wants to override file or class
1733                  $dataobject->functionname = $method['method'];
1734                  $dataobject->classname     = $method['classname'];
1735                  $dataobject->filename      = $method['filename'];
1736              }
1737              $dataobject->xmlrpcpath = $type.'/'.$plugin.'/'.$dataobject->filename.'/'.$method;
1738              $dataobject->static = false;
1739  
1740              require_once($path . '/' . $dataobject->filename);
1741              $functionreflect = null; // slightly different ways to get this depending on whether it's a class method or a function
1742              if (!empty($dataobject->classname)) {
1743                  if (!class_exists($dataobject->classname)) {
1744                      throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1745                  }
1746                  $key = $dataobject->filename . '|' . $dataobject->classname;
1747                  if (!array_key_exists($key, $cachedclasses)) { // look to see if we've already got a reflection object
1748                      try {
1749                          $cachedclasses[$key] = Zend_Server_Reflection::reflectClass($dataobject->classname);
1750                      } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful
1751                          throw new moodle_exception('installreflectionclasserror', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname, 'error' => $e->getMessage()));
1752                      }
1753                  }
1754                  $r =& $cachedclasses[$key];
1755                  if (!$r->hasMethod($dataobject->functionname)) {
1756                      throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1757                  }
1758                  // stupid workaround for zend not having a getMethod($name) function
1759                  $ms = $r->getMethods();
1760                  foreach ($ms as $m) {
1761                      if ($m->getName() == $dataobject->functionname) {
1762                          $functionreflect = $m;
1763                          break;
1764                      }
1765                  }
1766                  $dataobject->static = (int)$functionreflect->isStatic();
1767              } else {
1768                  if (!function_exists($dataobject->functionname)) {
1769                      throw new moodle_exception('installnosuchfunction', 'mnet', '', (object)array('method' => $dataobject->functionname, 'file' => $dataobject->filename));
1770                  }
1771                  try {
1772                      $functionreflect = Zend_Server_Reflection::reflectFunction($dataobject->functionname);
1773                  } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful
1774                      throw new moodle_exception('installreflectionfunctionerror', 'mnet', '', (object)array('method' => $dataobject->functionname, '' => $dataobject->filename, 'error' => $e->getMessage()));
1775                  }
1776              }
1777              $dataobject->profile =  serialize(admin_mnet_method_profile($functionreflect));
1778              $dataobject->help = $functionreflect->getDescription();
1779  
1780              if ($record_exists = $DB->get_record('mnet_rpc', array('xmlrpcpath'=>$dataobject->xmlrpcpath))) {
1781                  $dataobject->id      = $record_exists->id;
1782                  $dataobject->enabled = $record_exists->enabled;
1783                  $DB->update_record('mnet_rpc', $dataobject);
1784              } else {
1785                  $dataobject->id = $DB->insert_record('mnet_rpc', $dataobject, true);
1786              }
1787  
1788              // TODO this API versioning must be reworked, here the recently processed method
1789              // sets the service API which may not be correct
1790              foreach ($publishmethodservices[$dataobject->functionname] as $service) {
1791                  if ($serviceobj = $DB->get_record('mnet_service', array('name'=>$service['servicename']))) {
1792                      $serviceobj->apiversion = $service['apiversion'];
1793                      $DB->update_record('mnet_service', $serviceobj);
1794                  } else {
1795                      $serviceobj = new stdClass();
1796                      $serviceobj->name        = $service['servicename'];
1797                      $serviceobj->description = empty($service['description']) ? '' : $service['description'];
1798                      $serviceobj->apiversion  = $service['apiversion'];
1799                      $serviceobj->offer       = 1;
1800                      $serviceobj->id          = $DB->insert_record('mnet_service', $serviceobj);
1801                  }
1802                  $servicecache[$service['servicename']] = $serviceobj;
1803                  if (!$DB->record_exists('mnet_service2rpc', array('rpcid'=>$dataobject->id, 'serviceid'=>$serviceobj->id))) {
1804                      $obj = new stdClass();
1805                      $obj->rpcid = $dataobject->id;
1806                      $obj->serviceid = $serviceobj->id;
1807                      $DB->insert_record('mnet_service2rpc', $obj, true);
1808                  }
1809              }
1810          }
1811      }
1812      // finished with methods we publish, now do subscribable methods
1813      foreach($subscribes as $service => $methods) {
1814          if (!array_key_exists($service, $servicecache)) {
1815              if (!$serviceobj = $DB->get_record('mnet_service', array('name' =>  $service))) {
1816                  debugging("TODO: skipping unknown service $service - somebody needs to fix MDL-21993");
1817                  continue;
1818              }
1819              $servicecache[$service] = $serviceobj;
1820          } else {
1821              $serviceobj = $servicecache[$service];
1822          }
1823          foreach ($methods as $method => $xmlrpcpath) {
1824              if (!$rpcid = $DB->get_field('mnet_remote_rpc', 'id', array('xmlrpcpath'=>$xmlrpcpath))) {
1825                  $remoterpc = (object)array(
1826                      'functionname' => $method,
1827                      'xmlrpcpath' => $xmlrpcpath,
1828                      'plugintype' => $type,
1829                      'pluginname' => $plugin,
1830                      'enabled'    => 1,
1831                  );
1832                  $rpcid = $remoterpc->id = $DB->insert_record('mnet_remote_rpc', $remoterpc, true);
1833              }
1834              if (!$DB->record_exists('mnet_remote_service2rpc', array('rpcid'=>$rpcid, 'serviceid'=>$serviceobj->id))) {
1835                  $obj = new stdClass();
1836                  $obj->rpcid = $rpcid;
1837                  $obj->serviceid = $serviceobj->id;
1838                  $DB->insert_record('mnet_remote_service2rpc', $obj, true);
1839              }
1840              $subscribemethodservices[$method][] = $service;
1841          }
1842      }
1843  
1844      foreach ($DB->get_records('mnet_remote_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1845          if (!array_key_exists($rpc->functionname, $subscribemethodservices) && $rpc->enabled) {
1846              $DB->set_field('mnet_remote_rpc', 'enabled', 0, array('id' => $rpc->id));
1847          } else if (array_key_exists($rpc->functionname, $subscribemethodservices) && !$rpc->enabled) {
1848              $DB->set_field('mnet_remote_rpc', 'enabled', 1, array('id' => $rpc->id));
1849          }
1850      }
1851  
1852      return true;
1853  }
1854  
1855  /**
1856   * Given some sort of Zend Reflection function/method object, return a profile array, ready to be serialized and stored
1857   *
1858   * @param Zend_Server_Reflection_Function_Abstract $function can be any subclass of this object type
1859   *
1860   * @return array
1861   */
1862  function admin_mnet_method_profile(Zend_Server_Reflection_Function_Abstract $function) {
1863      $protos = $function->getPrototypes();
1864      $proto = array_pop($protos);
1865      $ret = $proto->getReturnValue();
1866      $profile = array(
1867          'parameters' =>  array(),
1868          'return'     =>  array(
1869              'type'        => $ret->getType(),
1870              'description' => $ret->getDescription(),
1871          ),
1872      );
1873      foreach ($proto->getParameters() as $p) {
1874          $profile['parameters'][] = array(
1875              'name' => $p->getName(),
1876              'type' => $p->getType(),
1877              'description' => $p->getDescription(),
1878          );
1879      }
1880      return $profile;
1881  }
1882  
1883  
1884  /**
1885   * This function finds duplicate records (based on combinations of fields that should be unique)
1886   * and then progamatically generated a "most correct" version of the data, update and removing
1887   * records as appropriate
1888   *
1889   * Thanks to Dan Marsden for help
1890   *
1891   * @param   string  $table      Table name
1892   * @param   array   $uniques    Array of field names that should be unique
1893   * @param   array   $fieldstocheck  Array of fields to generate "correct" data from (optional)
1894   * @return  void
1895   */
1896  function upgrade_course_completion_remove_duplicates($table, $uniques, $fieldstocheck = array()) {
1897      global $DB;
1898  
1899      // Find duplicates
1900      $sql_cols = implode(', ', $uniques);
1901  
1902      $sql = "SELECT {$sql_cols} FROM {{$table}} GROUP BY {$sql_cols} HAVING (count(id) > 1)";
1903      $duplicates = $DB->get_recordset_sql($sql, array());
1904  
1905      // Loop through duplicates
1906      foreach ($duplicates as $duplicate) {
1907          $pointer = 0;
1908  
1909          // Generate SQL for finding records with these duplicate uniques
1910          $sql_select = implode(' = ? AND ', $uniques).' = ?'; // builds "fieldname = ? AND fieldname = ?"
1911          $uniq_values = array();
1912          foreach ($uniques as $u) {
1913              $uniq_values[] = $duplicate->$u;
1914          }
1915  
1916          $sql_order = implode(' DESC, ', $uniques).' DESC'; // builds "fieldname DESC, fieldname DESC"
1917  
1918          // Get records with these duplicate uniques
1919          $records = $DB->get_records_select(
1920              $table,
1921              $sql_select,
1922              $uniq_values,
1923              $sql_order
1924          );
1925  
1926          // Loop through and build a "correct" record, deleting the others
1927          $needsupdate = false;
1928          $origrecord = null;
1929          foreach ($records as $record) {
1930              $pointer++;
1931              if ($pointer === 1) { // keep 1st record but delete all others.
1932                  $origrecord = $record;
1933              } else {
1934                  // If we have fields to check, update original record
1935                  if ($fieldstocheck) {
1936                      // we need to keep the "oldest" of all these fields as the valid completion record.
1937                      // but we want to ignore null values
1938                      foreach ($fieldstocheck as $f) {
1939                          if ($record->$f && (($origrecord->$f > $record->$f) || !$origrecord->$f)) {
1940                              $origrecord->$f = $record->$f;
1941                              $needsupdate = true;
1942                          }
1943                      }
1944                  }
1945                  $DB->delete_records($table, array('id' => $record->id));
1946              }
1947          }
1948          if ($needsupdate || isset($origrecord->reaggregate)) {
1949              // If this table has a reaggregate field, update to force recheck on next cron run
1950              if (isset($origrecord->reaggregate)) {
1951                  $origrecord->reaggregate = time();
1952              }
1953              $DB->update_record($table, $origrecord);
1954          }
1955      }
1956  }
1957  
1958  /**
1959   * Find questions missing an existing category and associate them with
1960   * a category which purpose is to gather them.
1961   *
1962   * @return void
1963   */
1964  function upgrade_save_orphaned_questions() {
1965      global $DB;
1966  
1967      // Looking for orphaned questions
1968      $orphans = $DB->record_exists_select('question',
1969              'NOT EXISTS (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)');
1970      if (!$orphans) {
1971          return;
1972      }
1973  
1974      // Generate a unique stamp for the orphaned questions category, easier to identify it later on
1975      $uniquestamp = "unknownhost+120719170400+orphan";
1976      $systemcontext = context_system::instance();
1977  
1978      // Create the orphaned category at system level
1979      $cat = $DB->get_record('question_categories', array('stamp' => $uniquestamp,
1980              'contextid' => $systemcontext->id));
1981      if (!$cat) {
1982          $cat = new stdClass();
1983          $cat->parent = 0;
1984          $cat->contextid = $systemcontext->id;
1985          $cat->name = get_string('orphanedquestionscategory', 'question');
1986          $cat->info = get_string('orphanedquestionscategoryinfo', 'question');
1987          $cat->sortorder = 999;
1988          $cat->stamp = $uniquestamp;
1989          $cat->id = $DB->insert_record("question_categories", $cat);
1990      }
1991  
1992      // Set a category to those orphans
1993      $params = array('catid' => $cat->id);
1994      $DB->execute('UPDATE {question} SET category = :catid WHERE NOT EXISTS
1995              (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)', $params);
1996  }
1997  
1998  /**
1999   * Rename old backup files to current backup files.
2000   *
2001   * When added the setting 'backup_shortname' (MDL-28657) the backup file names did not contain the id of the course.
2002   * Further we fixed that behaviour by forcing the id to be always present in the file name (MDL-33812).
2003   * This function will explore the backup directory and attempt to rename the previously created files to include
2004   * the id in the name. Doing this will put them back in the process of deleting the excess backups for each course.
2005   *
2006   * This function manually recreates the file name, instead of using
2007   * {@link backup_plan_dbops::get_default_backup_filename()}, use it carefully if you're using it outside of the
2008   * usual upgrade process.
2009   *
2010   * @see backup_cron_automated_helper::remove_excess_backups()
2011   * @link http://tracker.moodle.org/browse/MDL-35116
2012   * @return void
2013   * @since Moodle 2.4
2014   */
2015  function upgrade_rename_old_backup_files_using_shortname() {
2016      global $CFG;
2017      $dir = get_config('backup', 'backup_auto_destination');
2018      $useshortname = get_config('backup', 'backup_shortname');
2019      if (empty($dir) || !is_dir($dir) || !is_writable($dir)) {
2020          return;
2021      }
2022  
2023      require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
2024      $backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename')));
2025      $backupword = trim(clean_filename($backupword), '_');
2026      $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-';
2027      $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
2028      $thirtyapril = strtotime('30 April 2012 00:00');
2029  
2030      // Reading the directory.
2031      if (!$files = scandir($dir)) {
2032          return;
2033      }
2034      foreach ($files as $file) {
2035          // Skip directories and files which do not start with the common prefix.
2036          // This avoids working on files which are not related to this issue.
2037          if (!is_file($dir . '/' . $file) || !preg_match($regex, $file)) {
2038              continue;
2039          }
2040  
2041          // Extract the information from the XML file.
2042          try {
2043              $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file);
2044          } catch (backup_helper_exception $e) {
2045              // Some error while retrieving the backup informations, skipping...
2046              continue;
2047          }
2048  
2049          // Make sure this a course backup.
2050          if ($bcinfo->format !== backup::FORMAT_MOODLE || $bcinfo->type !== backup::TYPE_1COURSE) {
2051              continue;
2052          }
2053  
2054          // Skip the backups created before the short name option was initially introduced (MDL-28657).
2055          // This was integrated on the 2nd of May 2012. Let's play safe with timezone and use the 30th of April.
2056          if ($bcinfo->backup_date < $thirtyapril) {
2057              continue;
2058          }
2059  
2060          // Let's check if the file name contains the ID where it is supposed to be, if it is the case then
2061          // we will skip the file. Of course it could happen that the course ID is identical to the course short name
2062          // even though really unlikely, but then renaming this file is not necessary. If the ID is not found in the
2063          // file name then it was probably the short name which was used.
2064          $idfilename = $filename . $bcinfo->original_course_id . '-';
2065          $idregex = '#^'.preg_quote($idfilename, '#').'.*\.mbz$#';
2066          if (preg_match($idregex, $file)) {
2067              continue;
2068          }
2069  
2070          // Generating the file name manually. We do not use backup_plan_dbops::get_default_backup_filename() because
2071          // it will query the database to get some course information, and the course could not exist any more.
2072          $newname = $filename . $bcinfo->original_course_id . '-';
2073          if ($useshortname) {
2074              $shortname = str_replace(' ', '_', $bcinfo->original_course_shortname);
2075              $shortname = core_text::strtolower(trim(clean_filename($shortname), '_'));
2076              $newname .= $shortname . '-';
2077          }
2078  
2079          $backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig'));
2080          $date = userdate($bcinfo->backup_date, $backupdateformat, 99, false);
2081          $date = core_text::strtolower(trim(clean_filename($date), '_'));
2082          $newname .= $date;
2083  
2084          if (isset($bcinfo->root_settings['users']) && !$bcinfo->root_settings['users']) {
2085              $newname .= '-nu';
2086          } else if (isset($bcinfo->root_settings['anonymize']) && $bcinfo->root_settings['anonymize']) {
2087              $newname .= '-an';
2088          }
2089          $newname .= '.mbz';
2090  
2091          // Final check before attempting the renaming.
2092          if ($newname == $file || file_exists($dir . '/' . $newname)) {
2093              continue;
2094          }
2095          @rename($dir . '/' . $file, $dir . '/' . $newname);
2096      }
2097  }
2098  
2099  /**
2100   * Detect duplicate grade item sortorders and resort the
2101   * items to remove them.
2102   */
2103  function upgrade_grade_item_fix_sortorder() {
2104      global $DB;
2105  
2106      // The simple way to fix these sortorder duplicates would be simply to resort each
2107      // affected course. But in order to reduce the impact of this upgrade step we're trying
2108      // to do it more efficiently by doing a series of update statements rather than updating
2109      // every single grade item in affected courses.
2110  
2111      $sql = "SELECT DISTINCT g1.courseid
2112                FROM {grade_items} g1
2113                JOIN {grade_items} g2 ON g1.courseid = g2.courseid
2114               WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id
2115               ORDER BY g1.courseid ASC";
2116      foreach ($DB->get_fieldset_sql($sql) as $courseid) {
2117          $transaction = $DB->start_delegated_transaction();
2118          $items = $DB->get_records('grade_items', array('courseid' => $courseid), '', 'id, sortorder, sortorder AS oldsort');
2119  
2120          // Get all duplicates in course order, highest sort order, and higest id first so that we can make space at the
2121          // bottom higher end of the sort orders and work down by id.
2122          $sql = "SELECT DISTINCT g1.id, g1.sortorder
2123                  FROM {grade_items} g1
2124                  JOIN {grade_items} g2 ON g1.courseid = g2.courseid
2125                  WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id AND g1.courseid = :courseid
2126                  ORDER BY g1.sortorder DESC, g1.id DESC";
2127  
2128          // This is the O(N*N) like the database version we're replacing, but at least the constants are a billion times smaller...
2129          foreach ($DB->get_records_sql($sql, array('courseid' => $courseid)) as $duplicate) {
2130              foreach ($items as $item) {
2131                  if ($item->sortorder > $duplicate->sortorder || ($item->sortorder == $duplicate->sortorder && $item->id > $duplicate->id)) {
2132                      $item->sortorder += 1;
2133                  }
2134              }
2135          }
2136          foreach ($items as $item) {
2137              if ($item->sortorder != $item->oldsort) {
2138                  $DB->update_record('grade_items', array('id' => $item->id, 'sortorder' => $item->sortorder));
2139              }
2140          }
2141  
2142          $transaction->allow_commit();
2143      }
2144  }
2145  
2146  /**
2147   * Detect file areas with missing root directory records and add them.
2148   */
2149  function upgrade_fix_missing_root_folders() {
2150      global $DB, $USER;
2151  
2152      $transaction = $DB->start_delegated_transaction();
2153  
2154      $sql = "SELECT contextid, component, filearea, itemid
2155                FROM {files}
2156               WHERE (component <> 'user' OR filearea <> 'draft')
2157            GROUP BY contextid, component, filearea, itemid
2158              HAVING MAX(CASE WHEN filename = '.' AND filepath = '/' THEN 1 ELSE 0 END) = 0";
2159  
2160      $rs = $DB->get_recordset_sql($sql);
2161      $defaults = array('filepath' => '/',
2162          'filename' => '.',
2163          'userid' => 0, // Don't rely on any particular user for these system records.
2164          'filesize' => 0,
2165          'timecreated' => time(),
2166          'timemodified' => time(),
2167          'contenthash' => sha1(''));
2168      foreach ($rs as $r) {
2169          $pathhash = sha1("/$r->contextid/$r->component/$r->filearea/$r->itemid/.");
2170          $DB->insert_record('files', (array)$r + $defaults +
2171              array('pathnamehash' => $pathhash));
2172      }
2173      $rs->close();
2174      $transaction->allow_commit();
2175  }
2176  
2177  /**
2178   * Detect draft file areas with missing root directory records and add them.
2179   */
2180  function upgrade_fix_missing_root_folders_draft() {
2181      global $DB;
2182  
2183      $transaction = $DB->start_delegated_transaction();
2184  
2185      $sql = "SELECT contextid, itemid, MAX(timecreated) AS timecreated, MAX(timemodified) AS timemodified
2186                FROM {files}
2187               WHERE (component = 'user' AND filearea = 'draft')
2188            GROUP BY contextid, itemid
2189              HAVING MAX(CASE WHEN filename = '.' AND filepath = '/' THEN 1 ELSE 0 END) = 0";
2190  
2191      $rs = $DB->get_recordset_sql($sql);
2192      $defaults = array('component' => 'user',
2193          'filearea' => 'draft',
2194          'filepath' => '/',
2195          'filename' => '.',
2196          'userid' => 0, // Don't rely on any particular user for these system records.
2197          'filesize' => 0,
2198          'contenthash' => sha1(''));
2199      foreach ($rs as $r) {
2200          $r->pathnamehash = sha1("/$r->contextid/user/draft/$r->itemid/.");
2201          $DB->insert_record('files', (array)$r + $defaults);
2202      }
2203      $rs->close();
2204      $transaction->allow_commit();
2205  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1