PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/upgradelib.php

http://github.com/moodle/moodle
PHP | 2613 lines | 1681 code | 348 blank | 584 comment | 319 complexity | 9ab291a387223604640c78f5335cb890 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Various upgrade/install related functions and classes.
  18. *
  19. * @package core
  20. * @subpackage upgrade
  21. * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. /** UPGRADE_LOG_NORMAL = 0 */
  26. define('UPGRADE_LOG_NORMAL', 0);
  27. /** UPGRADE_LOG_NOTICE = 1 */
  28. define('UPGRADE_LOG_NOTICE', 1);
  29. /** UPGRADE_LOG_ERROR = 2 */
  30. define('UPGRADE_LOG_ERROR', 2);
  31. /**
  32. * Exception indicating unknown error during upgrade.
  33. *
  34. * @package core
  35. * @subpackage upgrade
  36. * @copyright 2009 Petr Skoda {@link http://skodak.org}
  37. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38. */
  39. class upgrade_exception extends moodle_exception {
  40. function __construct($plugin, $version, $debuginfo=NULL) {
  41. global $CFG;
  42. $a = (object)array('plugin'=>$plugin, 'version'=>$version);
  43. parent::__construct('upgradeerror', 'admin', "$CFG->wwwroot/$CFG->admin/index.php", $a, $debuginfo);
  44. }
  45. }
  46. /**
  47. * Exception indicating downgrade error during upgrade.
  48. *
  49. * @package core
  50. * @subpackage upgrade
  51. * @copyright 2009 Petr Skoda {@link http://skodak.org}
  52. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  53. */
  54. class downgrade_exception extends moodle_exception {
  55. function __construct($plugin, $oldversion, $newversion) {
  56. global $CFG;
  57. $plugin = is_null($plugin) ? 'moodle' : $plugin;
  58. $a = (object)array('plugin'=>$plugin, 'oldversion'=>$oldversion, 'newversion'=>$newversion);
  59. parent::__construct('cannotdowngrade', 'debug', "$CFG->wwwroot/$CFG->admin/index.php", $a);
  60. }
  61. }
  62. /**
  63. * @package core
  64. * @subpackage upgrade
  65. * @copyright 2009 Petr Skoda {@link http://skodak.org}
  66. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  67. */
  68. class upgrade_requires_exception extends moodle_exception {
  69. function __construct($plugin, $pluginversion, $currentmoodle, $requiremoodle) {
  70. global $CFG;
  71. $a = new stdClass();
  72. $a->pluginname = $plugin;
  73. $a->pluginversion = $pluginversion;
  74. $a->currentmoodle = $currentmoodle;
  75. $a->requiremoodle = $requiremoodle;
  76. parent::__construct('pluginrequirementsnotmet', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $a);
  77. }
  78. }
  79. /**
  80. * Exception thrown when attempting to install a plugin that declares incompatibility with moodle version
  81. *
  82. * @package core
  83. * @subpackage upgrade
  84. * @copyright 2019 Peter Burnett <peterburnett@catalyst-au.net>
  85. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  86. */
  87. class plugin_incompatible_exception extends moodle_exception {
  88. /**
  89. * Constructor function for exception
  90. *
  91. * @param \core\plugininfo\base $plugin The plugin causing the exception
  92. * @param int $pluginversion The version of the plugin causing the exception
  93. */
  94. public function __construct($plugin, $pluginversion) {
  95. global $CFG;
  96. $a = new stdClass();
  97. $a->pluginname = $plugin;
  98. $a->pluginversion = $pluginversion;
  99. $a->moodleversion = $CFG->branch;
  100. parent::__construct('pluginunsupported', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $a);
  101. }
  102. }
  103. /**
  104. * @package core
  105. * @subpackage upgrade
  106. * @copyright 2009 Petr Skoda {@link http://skodak.org}
  107. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  108. */
  109. class plugin_defective_exception extends moodle_exception {
  110. function __construct($plugin, $details) {
  111. global $CFG;
  112. parent::__construct('detectedbrokenplugin', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $plugin, $details);
  113. }
  114. }
  115. /**
  116. * Misplaced plugin exception.
  117. *
  118. * Note: this should be used only from the upgrade/admin code.
  119. *
  120. * @package core
  121. * @subpackage upgrade
  122. * @copyright 2009 Petr Skoda {@link http://skodak.org}
  123. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  124. */
  125. class plugin_misplaced_exception extends moodle_exception {
  126. /**
  127. * Constructor.
  128. * @param string $component the component from version.php
  129. * @param string $expected expected directory, null means calculate
  130. * @param string $current plugin directory path
  131. */
  132. public function __construct($component, $expected, $current) {
  133. global $CFG;
  134. if (empty($expected)) {
  135. list($type, $plugin) = core_component::normalize_component($component);
  136. $plugintypes = core_component::get_plugin_types();
  137. if (isset($plugintypes[$type])) {
  138. $expected = $plugintypes[$type] . '/' . $plugin;
  139. }
  140. }
  141. if (strpos($expected, '$CFG->dirroot') !== 0) {
  142. $expected = str_replace($CFG->dirroot, '$CFG->dirroot', $expected);
  143. }
  144. if (strpos($current, '$CFG->dirroot') !== 0) {
  145. $current = str_replace($CFG->dirroot, '$CFG->dirroot', $current);
  146. }
  147. $a = new stdClass();
  148. $a->component = $component;
  149. $a->expected = $expected;
  150. $a->current = $current;
  151. parent::__construct('detectedmisplacedplugin', 'core_plugin', "$CFG->wwwroot/$CFG->admin/index.php", $a);
  152. }
  153. }
  154. /**
  155. * Static class monitors performance of upgrade steps.
  156. */
  157. class core_upgrade_time {
  158. /** @var float Time at start of current upgrade (plugin/system) */
  159. protected static $before;
  160. /** @var float Time at end of last savepoint */
  161. protected static $lastsavepoint;
  162. /** @var bool Flag to indicate whether we are recording timestamps or not. */
  163. protected static $isrecording = false;
  164. /**
  165. * Records current time at the start of the current upgrade item, e.g. plugin.
  166. */
  167. public static function record_start() {
  168. self::$before = microtime(true);
  169. self::$lastsavepoint = self::$before;
  170. self::$isrecording = true;
  171. }
  172. /**
  173. * Records current time at the end of a given numbered step.
  174. *
  175. * @param float $version Version number (may have decimals, or not)
  176. */
  177. public static function record_savepoint($version) {
  178. global $CFG, $OUTPUT;
  179. // In developer debug mode we show a notification after each individual save point.
  180. if ($CFG->debugdeveloper && self::$isrecording) {
  181. $time = microtime(true);
  182. $notification = new \core\output\notification($version . ': ' .
  183. get_string('successduration', '', format_float($time - self::$lastsavepoint, 2)),
  184. \core\output\notification::NOTIFY_SUCCESS);
  185. $notification->set_show_closebutton(false);
  186. echo $OUTPUT->render($notification);
  187. self::$lastsavepoint = $time;
  188. }
  189. }
  190. /**
  191. * Gets the time since the record_start function was called, rounded to 2 digits.
  192. *
  193. * @return float Elapsed time
  194. */
  195. public static function get_elapsed() {
  196. return microtime(true) - self::$before;
  197. }
  198. }
  199. /**
  200. * Sets maximum expected time needed for upgrade task.
  201. * Please always make sure that upgrade will not run longer!
  202. *
  203. * The script may be automatically aborted if upgrade times out.
  204. *
  205. * @category upgrade
  206. * @param int $max_execution_time in seconds (can not be less than 60 s)
  207. */
  208. function upgrade_set_timeout($max_execution_time=300) {
  209. global $CFG;
  210. if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
  211. $upgraderunning = get_config(null, 'upgraderunning');
  212. } else {
  213. $upgraderunning = $CFG->upgraderunning;
  214. }
  215. if (!$upgraderunning) {
  216. if (CLI_SCRIPT) {
  217. // never stop CLI upgrades
  218. $upgraderunning = 0;
  219. } else {
  220. // web upgrade not running or aborted
  221. print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
  222. }
  223. }
  224. if ($max_execution_time < 60) {
  225. // protection against 0 here
  226. $max_execution_time = 60;
  227. }
  228. $expected_end = time() + $max_execution_time;
  229. if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
  230. // no need to store new end, it is nearly the same ;-)
  231. return;
  232. }
  233. if (CLI_SCRIPT) {
  234. // there is no point in timing out of CLI scripts, admins can stop them if necessary
  235. core_php_time_limit::raise();
  236. } else {
  237. core_php_time_limit::raise($max_execution_time);
  238. }
  239. set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
  240. }
  241. /**
  242. * Upgrade savepoint, marks end of each upgrade block.
  243. * It stores new main version, resets upgrade timeout
  244. * and abort upgrade if user cancels page loading.
  245. *
  246. * Please do not make large upgrade blocks with lots of operations,
  247. * for example when adding tables keep only one table operation per block.
  248. *
  249. * @category upgrade
  250. * @param bool $result false if upgrade step failed, true if completed
  251. * @param string or float $version main version
  252. * @param bool $allowabort allow user to abort script execution here
  253. * @return void
  254. */
  255. function upgrade_main_savepoint($result, $version, $allowabort=true) {
  256. global $CFG;
  257. //sanity check to avoid confusion with upgrade_mod_savepoint usage.
  258. if (!is_bool($allowabort)) {
  259. $errormessage = 'Parameter type mismatch. Are you mixing up upgrade_main_savepoint() and upgrade_mod_savepoint()?';
  260. throw new coding_exception($errormessage);
  261. }
  262. if (!$result) {
  263. throw new upgrade_exception(null, $version);
  264. }
  265. if ($CFG->version >= $version) {
  266. // something really wrong is going on in main upgrade script
  267. throw new downgrade_exception(null, $CFG->version, $version);
  268. }
  269. set_config('version', $version);
  270. upgrade_log(UPGRADE_LOG_NORMAL, null, 'Upgrade savepoint reached');
  271. // reset upgrade timeout to default
  272. upgrade_set_timeout();
  273. core_upgrade_time::record_savepoint($version);
  274. // this is a safe place to stop upgrades if user aborts page loading
  275. if ($allowabort and connection_aborted()) {
  276. die;
  277. }
  278. }
  279. /**
  280. * Module upgrade savepoint, marks end of module upgrade blocks
  281. * It stores module version, resets upgrade timeout
  282. * and abort upgrade if user cancels page loading.
  283. *
  284. * @category upgrade
  285. * @param bool $result false if upgrade step failed, true if completed
  286. * @param string or float $version main version
  287. * @param string $modname name of module
  288. * @param bool $allowabort allow user to abort script execution here
  289. * @return void
  290. */
  291. function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
  292. global $DB;
  293. $component = 'mod_'.$modname;
  294. if (!$result) {
  295. throw new upgrade_exception($component, $version);
  296. }
  297. $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
  298. if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
  299. print_error('modulenotexist', 'debug', '', $modname);
  300. }
  301. if ($dbversion >= $version) {
  302. // something really wrong is going on in upgrade script
  303. throw new downgrade_exception($component, $dbversion, $version);
  304. }
  305. set_config('version', $version, $component);
  306. upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
  307. // reset upgrade timeout to default
  308. upgrade_set_timeout();
  309. core_upgrade_time::record_savepoint($version);
  310. // this is a safe place to stop upgrades if user aborts page loading
  311. if ($allowabort and connection_aborted()) {
  312. die;
  313. }
  314. }
  315. /**
  316. * Blocks upgrade savepoint, marks end of blocks upgrade blocks
  317. * It stores block version, resets upgrade timeout
  318. * and abort upgrade if user cancels page loading.
  319. *
  320. * @category upgrade
  321. * @param bool $result false if upgrade step failed, true if completed
  322. * @param string or float $version main version
  323. * @param string $blockname name of block
  324. * @param bool $allowabort allow user to abort script execution here
  325. * @return void
  326. */
  327. function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true) {
  328. global $DB;
  329. $component = 'block_'.$blockname;
  330. if (!$result) {
  331. throw new upgrade_exception($component, $version);
  332. }
  333. $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
  334. if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
  335. print_error('blocknotexist', 'debug', '', $blockname);
  336. }
  337. if ($dbversion >= $version) {
  338. // something really wrong is going on in upgrade script
  339. throw new downgrade_exception($component, $dbversion, $version);
  340. }
  341. set_config('version', $version, $component);
  342. upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
  343. // reset upgrade timeout to default
  344. upgrade_set_timeout();
  345. core_upgrade_time::record_savepoint($version);
  346. // this is a safe place to stop upgrades if user aborts page loading
  347. if ($allowabort and connection_aborted()) {
  348. die;
  349. }
  350. }
  351. /**
  352. * Plugins upgrade savepoint, marks end of blocks upgrade blocks
  353. * It stores plugin version, resets upgrade timeout
  354. * and abort upgrade if user cancels page loading.
  355. *
  356. * @category upgrade
  357. * @param bool $result false if upgrade step failed, true if completed
  358. * @param string or float $version main version
  359. * @param string $type The type of the plugin.
  360. * @param string $plugin The name of the plugin.
  361. * @param bool $allowabort allow user to abort script execution here
  362. * @return void
  363. */
  364. function upgrade_plugin_savepoint($result, $version, $type, $plugin, $allowabort=true) {
  365. global $DB;
  366. $component = $type.'_'.$plugin;
  367. if (!$result) {
  368. throw new upgrade_exception($component, $version);
  369. }
  370. $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
  371. if ($dbversion >= $version) {
  372. // Something really wrong is going on in the upgrade script
  373. throw new downgrade_exception($component, $dbversion, $version);
  374. }
  375. set_config('version', $version, $component);
  376. upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
  377. // Reset upgrade timeout to default
  378. upgrade_set_timeout();
  379. core_upgrade_time::record_savepoint($version);
  380. // This is a safe place to stop upgrades if user aborts page loading
  381. if ($allowabort and connection_aborted()) {
  382. die;
  383. }
  384. }
  385. /**
  386. * Detect if there are leftovers in PHP source files.
  387. *
  388. * During main version upgrades administrators MUST move away
  389. * old PHP source files and start from scratch (or better
  390. * use git).
  391. *
  392. * @return bool true means borked upgrade, false means previous PHP files were properly removed
  393. */
  394. function upgrade_stale_php_files_present() {
  395. global $CFG;
  396. $someexamplesofremovedfiles = array(
  397. // Removed in 3.9.
  398. '/course/classes/output/modchooser_item.php',
  399. '/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js',
  400. '/course/yui/src/modchooser/js/modchooser.js',
  401. '/h5p/classes/autoloader.php',
  402. '/lib/adodb/readme.txt',
  403. '/lib/maxmind/GeoIp2/Compat/JsonSerializable.php',
  404. // Removed in 3.8.
  405. '/lib/amd/src/modal_confirm.js',
  406. '/lib/fonts/font-awesome-4.7.0/css/font-awesome.css',
  407. '/lib/jquery/jquery-3.2.1.min.js',
  408. '/lib/recaptchalib.php',
  409. '/lib/sessionkeepalive_ajax.php',
  410. '/lib/yui/src/checknet/js/checknet.js',
  411. '/question/amd/src/qbankmanager.js',
  412. // Removed in 3.7.
  413. '/lib/form/yui/src/showadvanced/js/showadvanced.js',
  414. '/lib/tests/output_external_test.php',
  415. '/message/amd/src/message_area.js',
  416. '/message/templates/message_area.mustache',
  417. '/question/yui/src/qbankmanager/build.json',
  418. // Removed in 3.6.
  419. '/lib/classes/session/memcache.php',
  420. '/lib/eventslib.php',
  421. '/lib/form/submitlink.php',
  422. '/lib/medialib.php',
  423. '/lib/password_compat/lib/password.php',
  424. // Removed in 3.5.
  425. '/lib/dml/mssql_native_moodle_database.php',
  426. '/lib/dml/mssql_native_moodle_recordset.php',
  427. '/lib/dml/mssql_native_moodle_temptables.php',
  428. // Removed in 3.4.
  429. '/auth/README.txt',
  430. '/calendar/set.php',
  431. '/enrol/users.php',
  432. '/enrol/yui/rolemanager/assets/skins/sam/rolemanager.css',
  433. // Removed in 3.3.
  434. '/badges/backpackconnect.php',
  435. '/calendar/yui/src/info/assets/skins/sam/moodle-calendar-info.css',
  436. '/competency/classes/external/exporter.php',
  437. '/mod/forum/forum.js',
  438. '/user/pixgroup.php',
  439. // Removed in 3.2.
  440. '/calendar/preferences.php',
  441. '/lib/alfresco/',
  442. '/lib/jquery/jquery-1.12.1.min.js',
  443. '/lib/password_compat/tests/',
  444. '/lib/phpunit/classes/unittestcase.php',
  445. // Removed in 3.1.
  446. '/lib/classes/log/sql_internal_reader.php',
  447. '/lib/zend/',
  448. '/mod/forum/pix/icon.gif',
  449. '/tag/templates/tagname.mustache',
  450. // Removed in 3.0.
  451. '/mod/lti/grade.php',
  452. '/tag/coursetagslib.php',
  453. // Removed in 2.9.
  454. '/lib/timezone.txt',
  455. // Removed in 2.8.
  456. '/course/delete_category_form.php',
  457. // Removed in 2.7.
  458. '/admin/tool/qeupgradehelper/version.php',
  459. // Removed in 2.6.
  460. '/admin/block.php',
  461. '/admin/oacleanup.php',
  462. // Removed in 2.5.
  463. '/backup/lib.php',
  464. '/backup/bb/README.txt',
  465. '/lib/excel/test.php',
  466. // Removed in 2.4.
  467. '/admin/tool/unittest/simpletestlib.php',
  468. // Removed in 2.3.
  469. '/lib/minify/builder/',
  470. // Removed in 2.2.
  471. '/lib/yui/3.4.1pr1/',
  472. // Removed in 2.2.
  473. '/search/cron_php5.php',
  474. '/course/report/log/indexlive.php',
  475. '/admin/report/backups/index.php',
  476. '/admin/generator.php',
  477. // Removed in 2.1.
  478. '/lib/yui/2.8.0r4/',
  479. // Removed in 2.0.
  480. '/blocks/admin/block_admin.php',
  481. '/blocks/admin_tree/block_admin_tree.php',
  482. );
  483. foreach ($someexamplesofremovedfiles as $file) {
  484. if (file_exists($CFG->dirroot.$file)) {
  485. return true;
  486. }
  487. }
  488. return false;
  489. }
  490. /**
  491. * Upgrade plugins
  492. * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
  493. * return void
  494. */
  495. function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
  496. global $CFG, $DB;
  497. /// special cases
  498. if ($type === 'mod') {
  499. return upgrade_plugins_modules($startcallback, $endcallback, $verbose);
  500. } else if ($type === 'block') {
  501. return upgrade_plugins_blocks($startcallback, $endcallback, $verbose);
  502. }
  503. $plugs = core_component::get_plugin_list($type);
  504. foreach ($plugs as $plug=>$fullplug) {
  505. // Reset time so that it works when installing a large number of plugins
  506. core_php_time_limit::raise(600);
  507. $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name
  508. // check plugin dir is valid name
  509. if (empty($component)) {
  510. throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.');
  511. }
  512. if (!is_readable($fullplug.'/version.php')) {
  513. continue;
  514. }
  515. $plugin = new stdClass();
  516. $plugin->version = null;
  517. $module = $plugin; // Prevent some notices when plugin placed in wrong directory.
  518. require($fullplug.'/version.php'); // defines $plugin with version etc
  519. unset($module);
  520. if (empty($plugin->version)) {
  521. throw new plugin_defective_exception($component, 'Missing $plugin->version number in version.php.');
  522. }
  523. if (empty($plugin->component)) {
  524. throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
  525. }
  526. if ($plugin->component !== $component) {
  527. throw new plugin_misplaced_exception($plugin->component, null, $fullplug);
  528. }
  529. $plugin->name = $plug;
  530. $plugin->fullname = $component;
  531. if (!empty($plugin->requires)) {
  532. if ($plugin->requires > $CFG->version) {
  533. throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
  534. } else if ($plugin->requires < 2010000000) {
  535. throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
  536. }
  537. }
  538. // Throw exception if plugin is incompatible with moodle version.
  539. if (!empty($plugin->incompatible)) {
  540. if ($CFG->branch <= $plugin->incompatible) {
  541. throw new plugin_incompatible_exception($component, $plugin->version);
  542. }
  543. }
  544. // try to recover from interrupted install.php if needed
  545. if (file_exists($fullplug.'/db/install.php')) {
  546. if (get_config($plugin->fullname, 'installrunning')) {
  547. require_once($fullplug.'/db/install.php');
  548. $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery';
  549. if (function_exists($recover_install_function)) {
  550. $startcallback($component, true, $verbose);
  551. $recover_install_function();
  552. unset_config('installrunning', $plugin->fullname);
  553. update_capabilities($component);
  554. log_update_descriptions($component);
  555. external_update_descriptions($component);
  556. \core\task\manager::reset_scheduled_tasks_for_component($component);
  557. \core_analytics\manager::update_default_models_for_component($component);
  558. message_update_providers($component);
  559. \core\message\inbound\manager::update_handlers_for_component($component);
  560. if ($type === 'message') {
  561. message_update_processors($plug);
  562. }
  563. upgrade_plugin_mnet_functions($component);
  564. core_tag_area::reset_definitions_for_component($component);
  565. $endcallback($component, true, $verbose);
  566. }
  567. }
  568. }
  569. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  570. if (empty($installedversion)) { // new installation
  571. $startcallback($component, true, $verbose);
  572. /// Install tables if defined
  573. if (file_exists($fullplug.'/db/install.xml')) {
  574. $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml');
  575. }
  576. /// store version
  577. upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false);
  578. /// execute post install file
  579. if (file_exists($fullplug.'/db/install.php')) {
  580. require_once($fullplug.'/db/install.php');
  581. set_config('installrunning', 1, $plugin->fullname);
  582. $post_install_function = 'xmldb_'.$plugin->fullname.'_install';
  583. $post_install_function();
  584. unset_config('installrunning', $plugin->fullname);
  585. }
  586. /// Install various components
  587. update_capabilities($component);
  588. log_update_descriptions($component);
  589. external_update_descriptions($component);
  590. \core\task\manager::reset_scheduled_tasks_for_component($component);
  591. \core_analytics\manager::update_default_models_for_component($component);
  592. message_update_providers($component);
  593. \core\message\inbound\manager::update_handlers_for_component($component);
  594. if ($type === 'message') {
  595. message_update_processors($plug);
  596. }
  597. upgrade_plugin_mnet_functions($component);
  598. core_tag_area::reset_definitions_for_component($component);
  599. $endcallback($component, true, $verbose);
  600. } else if ($installedversion < $plugin->version) { // upgrade
  601. /// Run the upgrade function for the plugin.
  602. $startcallback($component, false, $verbose);
  603. if (is_readable($fullplug.'/db/upgrade.php')) {
  604. require_once($fullplug.'/db/upgrade.php'); // defines upgrading function
  605. $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade';
  606. $result = $newupgrade_function($installedversion);
  607. } else {
  608. $result = true;
  609. }
  610. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  611. if ($installedversion < $plugin->version) {
  612. // store version if not already there
  613. upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
  614. }
  615. /// Upgrade various components
  616. update_capabilities($component);
  617. log_update_descriptions($component);
  618. external_update_descriptions($component);
  619. \core\task\manager::reset_scheduled_tasks_for_component($component);
  620. \core_analytics\manager::update_default_models_for_component($component);
  621. message_update_providers($component);
  622. \core\message\inbound\manager::update_handlers_for_component($component);
  623. if ($type === 'message') {
  624. // Ugly hack!
  625. message_update_processors($plug);
  626. }
  627. upgrade_plugin_mnet_functions($component);
  628. core_tag_area::reset_definitions_for_component($component);
  629. $endcallback($component, false, $verbose);
  630. } else if ($installedversion > $plugin->version) {
  631. throw new downgrade_exception($component, $installedversion, $plugin->version);
  632. }
  633. }
  634. }
  635. /**
  636. * Find and check all modules and load them up or upgrade them if necessary
  637. *
  638. * @global object
  639. * @global object
  640. */
  641. function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
  642. global $CFG, $DB;
  643. $mods = core_component::get_plugin_list('mod');
  644. foreach ($mods as $mod=>$fullmod) {
  645. if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
  646. continue;
  647. }
  648. $component = clean_param('mod_'.$mod, PARAM_COMPONENT);
  649. // check module dir is valid name
  650. if (empty($component)) {
  651. throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.');
  652. }
  653. if (!is_readable($fullmod.'/version.php')) {
  654. throw new plugin_defective_exception($component, 'Missing version.php');
  655. }
  656. $module = new stdClass();
  657. $plugin = new stdClass();
  658. $plugin->version = null;
  659. require($fullmod .'/version.php'); // Defines $plugin with version etc.
  660. // Check if the legacy $module syntax is still used.
  661. if (!is_object($module) or (count((array)$module) > 0)) {
  662. throw new plugin_defective_exception($component, 'Unsupported $module syntax detected in version.php');
  663. }
  664. // Prepare the record for the {modules} table.
  665. $module = clone($plugin);
  666. unset($module->version);
  667. unset($module->component);
  668. unset($module->dependencies);
  669. unset($module->release);
  670. if (empty($plugin->version)) {
  671. throw new plugin_defective_exception($component, 'Missing $plugin->version number in version.php.');
  672. }
  673. if (empty($plugin->component)) {
  674. throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
  675. }
  676. if ($plugin->component !== $component) {
  677. throw new plugin_misplaced_exception($plugin->component, null, $fullmod);
  678. }
  679. if (!empty($plugin->requires)) {
  680. if ($plugin->requires > $CFG->version) {
  681. throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
  682. } else if ($plugin->requires < 2010000000) {
  683. throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
  684. }
  685. }
  686. if (empty($module->cron)) {
  687. $module->cron = 0;
  688. }
  689. // all modules must have en lang pack
  690. if (!is_readable("$fullmod/lang/en/$mod.php")) {
  691. throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
  692. }
  693. $module->name = $mod; // The name MUST match the directory
  694. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  695. if (file_exists($fullmod.'/db/install.php')) {
  696. if (get_config($module->name, 'installrunning')) {
  697. require_once($fullmod.'/db/install.php');
  698. $recover_install_function = 'xmldb_'.$module->name.'_install_recovery';
  699. if (function_exists($recover_install_function)) {
  700. $startcallback($component, true, $verbose);
  701. $recover_install_function();
  702. unset_config('installrunning', $module->name);
  703. // Install various components too
  704. update_capabilities($component);
  705. log_update_descriptions($component);
  706. external_update_descriptions($component);
  707. \core\task\manager::reset_scheduled_tasks_for_component($component);
  708. \core_analytics\manager::update_default_models_for_component($component);
  709. message_update_providers($component);
  710. \core\message\inbound\manager::update_handlers_for_component($component);
  711. upgrade_plugin_mnet_functions($component);
  712. core_tag_area::reset_definitions_for_component($component);
  713. $endcallback($component, true, $verbose);
  714. }
  715. }
  716. }
  717. if (empty($installedversion)) {
  718. $startcallback($component, true, $verbose);
  719. /// Execute install.xml (XMLDB) - must be present in all modules
  720. $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml');
  721. /// Add record into modules table - may be needed in install.php already
  722. $module->id = $DB->insert_record('modules', $module);
  723. upgrade_mod_savepoint(true, $plugin->version, $module->name, false);
  724. /// Post installation hook - optional
  725. if (file_exists("$fullmod/db/install.php")) {
  726. require_once("$fullmod/db/install.php");
  727. // Set installation running flag, we need to recover after exception or error
  728. set_config('installrunning', 1, $module->name);
  729. $post_install_function = 'xmldb_'.$module->name.'_install';
  730. $post_install_function();
  731. unset_config('installrunning', $module->name);
  732. }
  733. /// Install various components
  734. update_capabilities($component);
  735. log_update_descriptions($component);
  736. external_update_descriptions($component);
  737. \core\task\manager::reset_scheduled_tasks_for_component($component);
  738. \core_analytics\manager::update_default_models_for_component($component);
  739. message_update_providers($component);
  740. \core\message\inbound\manager::update_handlers_for_component($component);
  741. upgrade_plugin_mnet_functions($component);
  742. core_tag_area::reset_definitions_for_component($component);
  743. $endcallback($component, true, $verbose);
  744. } else if ($installedversion < $plugin->version) {
  745. /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
  746. $startcallback($component, false, $verbose);
  747. if (is_readable($fullmod.'/db/upgrade.php')) {
  748. require_once($fullmod.'/db/upgrade.php'); // defines new upgrading function
  749. $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
  750. $result = $newupgrade_function($installedversion, $module);
  751. } else {
  752. $result = true;
  753. }
  754. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  755. $currmodule = $DB->get_record('modules', array('name'=>$module->name));
  756. if ($installedversion < $plugin->version) {
  757. // store version if not already there
  758. upgrade_mod_savepoint($result, $plugin->version, $mod, false);
  759. }
  760. // update cron flag if needed
  761. if ($currmodule->cron != $module->cron) {
  762. $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
  763. }
  764. // Upgrade various components
  765. update_capabilities($component);
  766. log_update_descriptions($component);
  767. external_update_descriptions($component);
  768. \core\task\manager::reset_scheduled_tasks_for_component($component);
  769. \core_analytics\manager::update_default_models_for_component($component);
  770. message_update_providers($component);
  771. \core\message\inbound\manager::update_handlers_for_component($component);
  772. upgrade_plugin_mnet_functions($component);
  773. core_tag_area::reset_definitions_for_component($component);
  774. $endcallback($component, false, $verbose);
  775. } else if ($installedversion > $plugin->version) {
  776. throw new downgrade_exception($component, $installedversion, $plugin->version);
  777. }
  778. }
  779. }
  780. /**
  781. * This function finds all available blocks and install them
  782. * into blocks table or do all the upgrade process if newer.
  783. *
  784. * @global object
  785. * @global object
  786. */
  787. function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
  788. global $CFG, $DB;
  789. require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
  790. $blocktitles = array(); // we do not want duplicate titles
  791. //Is this a first install
  792. $first_install = null;
  793. $blocks = core_component::get_plugin_list('block');
  794. foreach ($blocks as $blockname=>$fullblock) {
  795. if (is_null($first_install)) {
  796. $first_install = ($DB->count_records('block_instances') == 0);
  797. }
  798. if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
  799. continue;
  800. }
  801. $component = clean_param('block_'.$blockname, PARAM_COMPONENT);
  802. // check block dir is valid name
  803. if (empty($component)) {
  804. throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.');
  805. }
  806. if (!is_readable($fullblock.'/version.php')) {
  807. throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
  808. }
  809. $plugin = new stdClass();
  810. $plugin->version = null;
  811. $plugin->cron = 0;
  812. $module = $plugin; // Prevent some notices when module placed in wrong directory.
  813. include($fullblock.'/version.php');
  814. unset($module);
  815. $block = clone($plugin);
  816. unset($block->version);
  817. unset($block->component);
  818. unset($block->dependencies);
  819. unset($block->release);
  820. if (empty($plugin->version)) {
  821. throw new plugin_defective_exception($component, 'Missing block version number in version.php.');
  822. }
  823. if (empty($plugin->component)) {
  824. throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
  825. }
  826. if ($plugin->component !== $component) {
  827. throw new plugin_misplaced_exception($plugin->component, null, $fullblock);
  828. }
  829. if (!empty($plugin->requires)) {
  830. if ($plugin->requires > $CFG->version) {
  831. throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
  832. } else if ($plugin->requires < 2010000000) {
  833. throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
  834. }
  835. }
  836. if (!is_readable($fullblock.'/block_'.$blockname.'.php')) {
  837. throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.');
  838. }
  839. include_once($fullblock.'/block_'.$blockname.'.php');
  840. $classname = 'block_'.$blockname;
  841. if (!class_exists($classname)) {
  842. throw new plugin_defective_exception($component, 'Can not load main class.');
  843. }
  844. $blockobj = new $classname; // This is what we'll be testing
  845. $blocktitle = $blockobj->get_title();
  846. // OK, it's as we all hoped. For further tests, the object will do them itself.
  847. if (!$blockobj->_self_test()) {
  848. throw new plugin_defective_exception($component, 'Self test failed.');
  849. }
  850. $block->name = $blockname; // The name MUST match the directory
  851. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  852. if (file_exists($fullblock.'/db/install.php')) {
  853. if (get_config('block_'.$blockname, 'installrunning')) {
  854. require_once($fullblock.'/db/install.php');
  855. $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery';
  856. if (function_exists($recover_install_function)) {
  857. $startcallback($component, true, $verbose);
  858. $recover_install_function();
  859. unset_config('installrunning', 'block_'.$blockname);
  860. // Install various components
  861. update_capabilities($component);
  862. log_update_descriptions($component);
  863. external_update_descriptions($component);
  864. \core\task\manager::reset_scheduled_tasks_for_component($component);
  865. \core_analytics\manager::update_default_models_for_component($component);
  866. message_update_providers($component);
  867. \core\message\inbound\manager::update_handlers_for_component($component);
  868. upgrade_plugin_mnet_functions($component);
  869. core_tag_area::reset_definitions_for_component($component);
  870. $endcallback($component, true, $verbose);
  871. }
  872. }
  873. }
  874. if (empty($installedversion)) { // block not installed yet, so install it
  875. $conflictblock = array_search($blocktitle, $blocktitles);
  876. if ($conflictblock !== false) {
  877. // Duplicate block titles are not allowed, they confuse people
  878. // AND PHP's associative arrays ;)
  879. throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock)));
  880. }
  881. $startcallback($component, true, $verbose);
  882. if (file_exists($fullblock.'/db/install.xml')) {
  883. $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
  884. }
  885. $block->id = $DB->insert_record('block', $block);
  886. upgrade_block_savepoint(true, $plugin->version, $block->name, false);
  887. if (file_exists($fullblock.'/db/install.php')) {
  888. require_once($fullblock.'/db/install.php');
  889. // Set installation running flag, we need to recover after exception or error
  890. set_config('installrunning', 1, 'block_'.$blockname);
  891. $post_install_function = 'xmldb_block_'.$blockname.'_install';
  892. $post_install_function();
  893. unset_config('installrunning', 'block_'.$blockname);
  894. }
  895. $blocktitles[$block->name] = $blocktitle;
  896. // Install various components
  897. update_capabilities($component);
  898. log_update_descriptions($component);
  899. external_update_descriptions($component);
  900. \core\task\manager::reset_scheduled_tasks_for_component($component);
  901. \core_analytics\manager::update_default_models_for_component($component);
  902. message_update_providers($component);
  903. \core\message\inbound\manager::update_handlers_for_component($component);
  904. core_tag_area::reset_definitions_for_component($component);
  905. upgrade_plugin_mnet_functions($component);
  906. $endcallback($component, true, $verbose);
  907. } else if ($installedversion < $plugin->version) {
  908. $startcallback($component, false, $verbose);
  909. if (is_readable($fullblock.'/db/upgrade.php')) {
  910. require_once($fullblock.'/db/upgrade.php'); // defines new upgrading function
  911. $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
  912. $result = $newupgrade_function($installedversion, $block);
  913. } else {
  914. $result = true;
  915. }
  916. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  917. $currblock = $DB->get_record('block', array('name'=>$block->name));
  918. if ($installedversion < $plugin->version) {
  919. // store version if not already there
  920. upgrade_block_savepoint($result, $plugin->version, $block->name, false);
  921. }
  922. if ($currblock->cron != $block->cron) {
  923. // update cron flag if needed
  924. $DB->set_field('block', 'cron', $block->cron, array('id' => $currblock->id));
  925. }
  926. // Upgrade various components
  927. update_capabilities($component);
  928. log_update_descriptions($component);
  929. external_update_descriptions($component);
  930. \core\task\manager::reset_scheduled_tasks_for_component($component);
  931. \core_analytics\manager::update_default_models_for_component($component);
  932. message_update_providers($component);
  933. \core\message\inbound\manager::update_handlers_for_component($component);
  934. upgrade_plugin_mnet_functions($component);
  935. core_tag_area::reset_definitions_for_component($component);
  936. $endcallback($component, false, $verbose);
  937. } else if ($installedversion > $plugin->version) {
  938. throw new downgrade_exception($component, $installedversion, $plugin->version);
  939. }
  940. }
  941. // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks
  942. if ($first_install) {
  943. //Iterate over each course - there should be only site course here now
  944. if ($courses = $DB->get_records('course')) {
  945. foreach ($courses as $course) {
  946. blocks_add_default_course_blocks($course);
  947. }
  948. }
  949. blocks_add_default_system_blocks();
  950. }
  951. }
  952. /**
  953. * Log_display description function used during install and upgrade.
  954. *
  955. * @param string $component name of component (moodle, mod_assignment, etc.)
  956. * @return void
  957. */
  958. function log_update_descriptions($component) {
  959. global $DB;
  960. $defpath = core_component::get_component_directory($component).'/db/log.php';
  961. if (!file_exists($defpath)) {
  962. $DB->delete_records('log_display', array('component'=>$component));
  963. return;
  964. }
  965. // load new info
  966. $logs = array();
  967. include($defpath);
  968. $newlogs = array();
  969. foreach ($logs as $log) {
  970. $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name
  971. }
  972. unset($logs);
  973. $logs = $newlogs;
  974. $fields = array('module', 'action', 'mtable', 'field');
  975. // update all log fist
  976. $dblogs = $DB->get_records('log_display', array('component'=>$component));
  977. foreach ($dblogs as $dblog) {
  978. $name = $dblog->module.'-'.$dblog->action;
  979. if (empty($logs[$name])) {
  980. $DB->delete_records('log_display', array('id'=>$dblog->id));
  981. continue;
  982. }
  983. $log = $logs[$name];
  984. unset($logs[$name]);
  985. $update = false;
  986. foreach ($fields as $field) {
  987. if ($dblog->$field != $log[$field]) {
  988. $dblog->$field = $log[$field];
  989. $update = true;
  990. }
  991. }
  992. if ($update) {
  993. $DB->update_record('log_display', $dblog);
  994. }
  995. }
  996. foreach ($logs as $log) {
  997. $dblog = (object)$log;
  998. $dblog->component = $component;
  999. $DB->insert_record('log_display', $dblog);
  1000. }
  1001. }
  1002. /**
  1003. * Web service discovery function used during install and upgrade.
  1004. * @param string $component name of component (moodle, mod_assignment, etc.)
  1005. * @return void
  1006. */
  1007. function external_update_descriptions($component) {
  1008. global $DB, $CFG;
  1009. $defpath = core_component::get_component_directory($component).'/db/services.php';
  1010. if (!file_exists($defpath)) {
  1011. require_once($CFG->dirroot.'/lib/externallib.php');
  1012. external_delete_descriptions($component);
  1013. return;
  1014. }
  1015. // load new info
  1016. $functions = array();
  1017. $services = array();
  1018. include($defpath);
  1019. // update all function fist
  1020. $dbfunctions = $DB->get_records('external_functions', array('component'=>$component));
  1021. foreach ($dbfunctions as $dbfunction) {
  1022. if (empty($functions[$dbfunction->name])) {
  1023. $DB->delete_records('external_functions', array('id'=>$dbfunction->id));
  1024. // do not delete functions from external_services_functions, beacuse
  1025. // we want to notify admins when functions used in custom services disappear
  1026. //TODO: this looks wrong, we have to delete it eventually (skodak)
  1027. continue;
  1028. }
  1029. $function = $functions[$dbfunction->name];
  1030. unset($functions[$dbfunction->name]);
  1031. $function['classpath'] = empty($function['classpath']) ? null : $function['classpath'];
  1032. $update = false;
  1033. if ($dbfunction->classname != $function['classname']) {
  1034. $dbfunction->classname = $function['classname'];
  1035. $update = true;
  1036. }
  1037. if ($dbfunction->methodname != $function['methodname']) {
  1038. $dbfunction->methodname = $function['methodname'];
  1039. $update = true;
  1040. }
  1041. if ($dbfunction->classpath != $function['classpath']) {
  1042. $dbfunction->classpath = $function['classpath'];
  1043. $update = true;
  1044. }
  1045. $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
  1046. if ($dbfunction->capabilities != $functioncapabilities) {
  1047. $dbfunction->capabilities = $functioncapabilities;
  1048. $update = true;
  1049. }
  1050. if (isset($function['services']) and is_array($function['services'])) {
  1051. sort($function['services']);
  1052. $functionservices = implode(',', $function['services']);
  1053. } else {
  1054. // Force null values in the DB.
  1055. $functionservices = null;
  1056. }
  1057. if ($dbfunction->services != $functionservices) {
  1058. // Now, we need to check if services were removed, in that case we need to remove the function from them.
  1059. $servicesremoved = array_diff(explode(",", $dbfunction->services), explode(",", $functionservices));
  1060. foreach ($servicesremoved as $removedshortname) {
  1061. if ($externalserviceid = $DB->get_field('external_services', 'id', array("shortname" => $removedshortname))) {
  1062. $DB->delete_records('external_services_functions', array('functionname' => $dbfunction->name,
  1063. 'externalserviceid' => $externalserviceid));
  1064. }
  1065. }
  1066. $dbfunction->services = $functionservices;
  1067. $update = true;
  1068. }
  1069. if ($update) {
  1070. $DB->update_record('external_functions', $dbfunction);
  1071. }
  1072. }
  1073. foreach ($functions as $fname => $function) {
  1074. $dbfunction = new stdClass();
  1075. $dbfunction->name = $fname;
  1076. $dbfunction->classname = $function['classname'];
  1077. $dbfunction->methodname = $function['methodname'];
  1078. $dbfunction->classpath = empty($function['classpath']) ? null : $function['classpath'];
  1079. $dbfunction->component = $component;
  1080. $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
  1081. if (isset($function['services']) and is_array($function['services'])) {
  1082. sort($function['services']);
  1083. $dbfunction->services = implode(',', $function['services']);
  1084. } else {
  1085. // Force null values in the DB.
  1086. $dbfunction->services = null;
  1087. }
  1088. $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
  1089. }
  1090. unset($functions);
  1091. // …

Large files files are truncated, but you can click here to view the full file