PageRenderTime 57ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/upgradelib.php

https://bitbucket.org/moodle/moodle
PHP | 2734 lines | 1745 code | 360 blank | 629 comment | 328 complexity | ce8e3bdcf4c99617db3095802d616e2b MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0

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(): bool {
  395. global $CFG;
  396. $someexamplesofremovedfiles = [
  397. // Removed in 4.0.
  398. '/admin/classes/task_log_table.php',
  399. '/admin/cli/mysql_engine.php',
  400. '/lib/babel-polyfill/polyfill.js',
  401. '/lib/typo3/class.t3lib_cs.php',
  402. '/question/tests/category_class_test.php',
  403. // Removed in 3.11.
  404. '/customfield/edit.php',
  405. '/lib/phpunit/classes/autoloader.php',
  406. '/lib/xhprof/README',
  407. '/message/defaultoutputs.php',
  408. '/user/files_form.php',
  409. // Removed in 3.10.
  410. '/grade/grading/classes/privacy/gradingform_provider.php',
  411. '/lib/coursecatlib.php',
  412. '/lib/form/htmleditor.php',
  413. '/message/classes/output/messagearea/contact.php',
  414. // Removed in 3.9.
  415. '/course/classes/output/modchooser_item.php',
  416. '/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js',
  417. '/course/yui/src/modchooser/js/modchooser.js',
  418. '/h5p/classes/autoloader.php',
  419. '/lib/adodb/readme.txt',
  420. '/lib/maxmind/GeoIp2/Compat/JsonSerializable.php',
  421. // Removed in 3.8.
  422. '/lib/amd/src/modal_confirm.js',
  423. '/lib/fonts/font-awesome-4.7.0/css/font-awesome.css',
  424. '/lib/jquery/jquery-3.2.1.min.js',
  425. '/lib/recaptchalib.php',
  426. '/lib/sessionkeepalive_ajax.php',
  427. '/lib/yui/src/checknet/js/checknet.js',
  428. '/question/amd/src/qbankmanager.js',
  429. // Removed in 3.7.
  430. '/lib/form/yui/src/showadvanced/js/showadvanced.js',
  431. '/lib/tests/output_external_test.php',
  432. '/message/amd/src/message_area.js',
  433. '/message/templates/message_area.mustache',
  434. '/question/yui/src/qbankmanager/build.json',
  435. // Removed in 3.6.
  436. '/lib/classes/session/memcache.php',
  437. '/lib/eventslib.php',
  438. '/lib/form/submitlink.php',
  439. '/lib/medialib.php',
  440. '/lib/password_compat/lib/password.php',
  441. // Removed in 3.5.
  442. '/lib/dml/mssql_native_moodle_database.php',
  443. '/lib/dml/mssql_native_moodle_recordset.php',
  444. '/lib/dml/mssql_native_moodle_temptables.php',
  445. // Removed in 3.4.
  446. '/auth/README.txt',
  447. '/calendar/set.php',
  448. '/enrol/users.php',
  449. '/enrol/yui/rolemanager/assets/skins/sam/rolemanager.css',
  450. // Removed in 3.3.
  451. '/badges/backpackconnect.php',
  452. '/calendar/yui/src/info/assets/skins/sam/moodle-calendar-info.css',
  453. '/competency/classes/external/exporter.php',
  454. '/mod/forum/forum.js',
  455. '/user/pixgroup.php',
  456. // Removed in 3.2.
  457. '/calendar/preferences.php',
  458. '/lib/alfresco/',
  459. '/lib/jquery/jquery-1.12.1.min.js',
  460. '/lib/password_compat/tests/',
  461. '/lib/phpunit/classes/unittestcase.php',
  462. // Removed in 3.1.
  463. '/lib/classes/log/sql_internal_reader.php',
  464. '/lib/zend/',
  465. '/mod/forum/pix/icon.gif',
  466. '/tag/templates/tagname.mustache',
  467. // Removed in 3.0.
  468. '/mod/lti/grade.php',
  469. '/tag/coursetagslib.php',
  470. // Removed in 2.9.
  471. '/lib/timezone.txt',
  472. // Removed in 2.8.
  473. '/course/delete_category_form.php',
  474. // Removed in 2.7.
  475. '/admin/tool/qeupgradehelper/version.php',
  476. // Removed in 2.6.
  477. '/admin/block.php',
  478. '/admin/oacleanup.php',
  479. // Removed in 2.5.
  480. '/backup/lib.php',
  481. '/backup/bb/README.txt',
  482. '/lib/excel/test.php',
  483. // Removed in 2.4.
  484. '/admin/tool/unittest/simpletestlib.php',
  485. // Removed in 2.3.
  486. '/lib/minify/builder/',
  487. // Removed in 2.2.
  488. '/lib/yui/3.4.1pr1/',
  489. // Removed in 2.2.
  490. '/search/cron_php5.php',
  491. '/course/report/log/indexlive.php',
  492. '/admin/report/backups/index.php',
  493. '/admin/generator.php',
  494. // Removed in 2.1.
  495. '/lib/yui/2.8.0r4/',
  496. // Removed in 2.0.
  497. '/blocks/admin/block_admin.php',
  498. '/blocks/admin_tree/block_admin_tree.php',
  499. ];
  500. foreach ($someexamplesofremovedfiles as $file) {
  501. if (file_exists($CFG->dirroot.$file)) {
  502. return true;
  503. }
  504. }
  505. return false;
  506. }
  507. /**
  508. * Upgrade plugins
  509. * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
  510. * return void
  511. */
  512. function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
  513. global $CFG, $DB;
  514. /// special cases
  515. if ($type === 'mod') {
  516. return upgrade_plugins_modules($startcallback, $endcallback, $verbose);
  517. } else if ($type === 'block') {
  518. return upgrade_plugins_blocks($startcallback, $endcallback, $verbose);
  519. }
  520. $plugs = core_component::get_plugin_list($type);
  521. foreach ($plugs as $plug=>$fullplug) {
  522. // Reset time so that it works when installing a large number of plugins
  523. core_php_time_limit::raise(600);
  524. $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name
  525. // check plugin dir is valid name
  526. if (empty($component)) {
  527. throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.');
  528. }
  529. if (!is_readable($fullplug.'/version.php')) {
  530. continue;
  531. }
  532. $plugin = new stdClass();
  533. $plugin->version = null;
  534. $module = $plugin; // Prevent some notices when plugin placed in wrong directory.
  535. require($fullplug.'/version.php'); // defines $plugin with version etc
  536. unset($module);
  537. if (empty($plugin->version)) {
  538. throw new plugin_defective_exception($component, 'Missing $plugin->version number in version.php.');
  539. }
  540. if (empty($plugin->component)) {
  541. throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
  542. }
  543. if ($plugin->component !== $component) {
  544. throw new plugin_misplaced_exception($plugin->component, null, $fullplug);
  545. }
  546. $plugin->name = $plug;
  547. $plugin->fullname = $component;
  548. if (!empty($plugin->requires)) {
  549. if ($plugin->requires > $CFG->version) {
  550. throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
  551. } else if ($plugin->requires < 2010000000) {
  552. throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
  553. }
  554. }
  555. // Throw exception if plugin is incompatible with moodle version.
  556. if (!empty($plugin->incompatible)) {
  557. if ($CFG->branch <= $plugin->incompatible) {
  558. throw new plugin_incompatible_exception($component, $plugin->version);
  559. }
  560. }
  561. // try to recover from interrupted install.php if needed
  562. if (file_exists($fullplug.'/db/install.php')) {
  563. if (get_config($plugin->fullname, 'installrunning')) {
  564. require_once($fullplug.'/db/install.php');
  565. $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery';
  566. if (function_exists($recover_install_function)) {
  567. $startcallback($component, true, $verbose);
  568. $recover_install_function();
  569. unset_config('installrunning', $plugin->fullname);
  570. update_capabilities($component);
  571. log_update_descriptions($component);
  572. external_update_descriptions($component);
  573. \core\task\manager::reset_scheduled_tasks_for_component($component);
  574. \core_analytics\manager::update_default_models_for_component($component);
  575. message_update_providers($component);
  576. \core\message\inbound\manager::update_handlers_for_component($component);
  577. if ($type === 'message') {
  578. message_update_processors($plug);
  579. }
  580. upgrade_plugin_mnet_functions($component);
  581. core_tag_area::reset_definitions_for_component($component);
  582. $endcallback($component, true, $verbose);
  583. }
  584. }
  585. }
  586. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  587. if (empty($installedversion)) { // new installation
  588. $startcallback($component, true, $verbose);
  589. /// Install tables if defined
  590. if (file_exists($fullplug.'/db/install.xml')) {
  591. $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml');
  592. }
  593. /// store version
  594. upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false);
  595. /// execute post install file
  596. if (file_exists($fullplug.'/db/install.php')) {
  597. require_once($fullplug.'/db/install.php');
  598. set_config('installrunning', 1, $plugin->fullname);
  599. $post_install_function = 'xmldb_'.$plugin->fullname.'_install';
  600. $post_install_function();
  601. unset_config('installrunning', $plugin->fullname);
  602. }
  603. /// Install various components
  604. update_capabilities($component);
  605. log_update_descriptions($component);
  606. external_update_descriptions($component);
  607. \core\task\manager::reset_scheduled_tasks_for_component($component);
  608. \core_analytics\manager::update_default_models_for_component($component);
  609. message_update_providers($component);
  610. \core\message\inbound\manager::update_handlers_for_component($component);
  611. if ($type === 'message') {
  612. message_update_processors($plug);
  613. }
  614. upgrade_plugin_mnet_functions($component);
  615. core_tag_area::reset_definitions_for_component($component);
  616. $endcallback($component, true, $verbose);
  617. } else if ($installedversion < $plugin->version) { // upgrade
  618. /// Run the upgrade function for the plugin.
  619. $startcallback($component, false, $verbose);
  620. if (is_readable($fullplug.'/db/upgrade.php')) {
  621. require_once($fullplug.'/db/upgrade.php'); // defines upgrading function
  622. $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade';
  623. $result = $newupgrade_function($installedversion);
  624. } else {
  625. $result = true;
  626. }
  627. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  628. if ($installedversion < $plugin->version) {
  629. // store version if not already there
  630. upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
  631. }
  632. /// Upgrade various components
  633. update_capabilities($component);
  634. log_update_descriptions($component);
  635. external_update_descriptions($component);
  636. \core\task\manager::reset_scheduled_tasks_for_component($component);
  637. \core_analytics\manager::update_default_models_for_component($component);
  638. message_update_providers($component);
  639. \core\message\inbound\manager::update_handlers_for_component($component);
  640. if ($type === 'message') {
  641. // Ugly hack!
  642. message_update_processors($plug);
  643. }
  644. upgrade_plugin_mnet_functions($component);
  645. core_tag_area::reset_definitions_for_component($component);
  646. $endcallback($component, false, $verbose);
  647. } else if ($installedversion > $plugin->version) {
  648. throw new downgrade_exception($component, $installedversion, $plugin->version);
  649. }
  650. }
  651. }
  652. /**
  653. * Find and check all modules and load them up or upgrade them if necessary
  654. *
  655. * @global object
  656. * @global object
  657. */
  658. function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
  659. global $CFG, $DB;
  660. $mods = core_component::get_plugin_list('mod');
  661. foreach ($mods as $mod=>$fullmod) {
  662. if ($mod === 'NEWMODULE') { // Someone has unzipped the template, ignore it
  663. continue;
  664. }
  665. $component = clean_param('mod_'.$mod, PARAM_COMPONENT);
  666. // check module dir is valid name
  667. if (empty($component)) {
  668. throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.');
  669. }
  670. if (!is_readable($fullmod.'/version.php')) {
  671. throw new plugin_defective_exception($component, 'Missing version.php');
  672. }
  673. $module = new stdClass();
  674. $plugin = new stdClass();
  675. $plugin->version = null;
  676. require($fullmod .'/version.php'); // Defines $plugin with version etc.
  677. // Check if the legacy $module syntax is still used.
  678. if (!is_object($module) or (count((array)$module) > 0)) {
  679. throw new plugin_defective_exception($component, 'Unsupported $module syntax detected in version.php');
  680. }
  681. // Prepare the record for the {modules} table.
  682. $module = clone($plugin);
  683. unset($module->version);
  684. unset($module->component);
  685. unset($module->dependencies);
  686. unset($module->release);
  687. if (empty($plugin->version)) {
  688. throw new plugin_defective_exception($component, 'Missing $plugin->version number in version.php.');
  689. }
  690. if (empty($plugin->component)) {
  691. throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
  692. }
  693. if ($plugin->component !== $component) {
  694. throw new plugin_misplaced_exception($plugin->component, null, $fullmod);
  695. }
  696. if (!empty($plugin->requires)) {
  697. if ($plugin->requires > $CFG->version) {
  698. throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
  699. } else if ($plugin->requires < 2010000000) {
  700. throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
  701. }
  702. }
  703. if (empty($module->cron)) {
  704. $module->cron = 0;
  705. }
  706. // all modules must have en lang pack
  707. if (!is_readable("$fullmod/lang/en/$mod.php")) {
  708. throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
  709. }
  710. $module->name = $mod; // The name MUST match the directory
  711. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  712. if (file_exists($fullmod.'/db/install.php')) {
  713. if (get_config($module->name, 'installrunning')) {
  714. require_once($fullmod.'/db/install.php');
  715. $recover_install_function = 'xmldb_'.$module->name.'_install_recovery';
  716. if (function_exists($recover_install_function)) {
  717. $startcallback($component, true, $verbose);
  718. $recover_install_function();
  719. unset_config('installrunning', $module->name);
  720. // Install various components too
  721. update_capabilities($component);
  722. log_update_descriptions($component);
  723. external_update_descriptions($component);
  724. \core\task\manager::reset_scheduled_tasks_for_component($component);
  725. \core_analytics\manager::update_default_models_for_component($component);
  726. message_update_providers($component);
  727. \core\message\inbound\manager::update_handlers_for_component($component);
  728. upgrade_plugin_mnet_functions($component);
  729. core_tag_area::reset_definitions_for_component($component);
  730. $endcallback($component, true, $verbose);
  731. }
  732. }
  733. }
  734. if (empty($installedversion)) {
  735. $startcallback($component, true, $verbose);
  736. /// Execute install.xml (XMLDB) - must be present in all modules
  737. $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml');
  738. /// Add record into modules table - may be needed in install.php already
  739. $module->id = $DB->insert_record('modules', $module);
  740. upgrade_mod_savepoint(true, $plugin->version, $module->name, false);
  741. /// Post installation hook - optional
  742. if (file_exists("$fullmod/db/install.php")) {
  743. require_once("$fullmod/db/install.php");
  744. // Set installation running flag, we need to recover after exception or error
  745. set_config('installrunning', 1, $module->name);
  746. $post_install_function = 'xmldb_'.$module->name.'_install';
  747. $post_install_function();
  748. unset_config('installrunning', $module->name);
  749. }
  750. /// Install various components
  751. update_capabilities($component);
  752. log_update_descriptions($component);
  753. external_update_descriptions($component);
  754. \core\task\manager::reset_scheduled_tasks_for_component($component);
  755. \core_analytics\manager::update_default_models_for_component($component);
  756. message_update_providers($component);
  757. \core\message\inbound\manager::update_handlers_for_component($component);
  758. upgrade_plugin_mnet_functions($component);
  759. core_tag_area::reset_definitions_for_component($component);
  760. $endcallback($component, true, $verbose);
  761. } else if ($installedversion < $plugin->version) {
  762. /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
  763. $startcallback($component, false, $verbose);
  764. if (is_readable($fullmod.'/db/upgrade.php')) {
  765. require_once($fullmod.'/db/upgrade.php'); // defines new upgrading function
  766. $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
  767. $result = $newupgrade_function($installedversion, $module);
  768. } else {
  769. $result = true;
  770. }
  771. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  772. $currmodule = $DB->get_record('modules', array('name'=>$module->name));
  773. if ($installedversion < $plugin->version) {
  774. // store version if not already there
  775. upgrade_mod_savepoint($result, $plugin->version, $mod, false);
  776. }
  777. // update cron flag if needed
  778. if ($currmodule->cron != $module->cron) {
  779. $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
  780. }
  781. // Upgrade various components
  782. update_capabilities($component);
  783. log_update_descriptions($component);
  784. external_update_descriptions($component);
  785. \core\task\manager::reset_scheduled_tasks_for_component($component);
  786. \core_analytics\manager::update_default_models_for_component($component);
  787. message_update_providers($component);
  788. \core\message\inbound\manager::update_handlers_for_component($component);
  789. upgrade_plugin_mnet_functions($component);
  790. core_tag_area::reset_definitions_for_component($component);
  791. $endcallback($component, false, $verbose);
  792. } else if ($installedversion > $plugin->version) {
  793. throw new downgrade_exception($component, $installedversion, $plugin->version);
  794. }
  795. }
  796. }
  797. /**
  798. * This function finds all available blocks and install them
  799. * into blocks table or do all the upgrade process if newer.
  800. *
  801. * @global object
  802. * @global object
  803. */
  804. function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
  805. global $CFG, $DB;
  806. require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
  807. $blocktitles = array(); // we do not want duplicate titles
  808. //Is this a first install
  809. $first_install = null;
  810. $blocks = core_component::get_plugin_list('block');
  811. foreach ($blocks as $blockname=>$fullblock) {
  812. if (is_null($first_install)) {
  813. $first_install = ($DB->count_records('block_instances') == 0);
  814. }
  815. if ($blockname === 'NEWBLOCK') { // Someone has unzipped the template, ignore it
  816. continue;
  817. }
  818. $component = clean_param('block_'.$blockname, PARAM_COMPONENT);
  819. // check block dir is valid name
  820. if (empty($component)) {
  821. throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.');
  822. }
  823. if (!is_readable($fullblock.'/version.php')) {
  824. throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
  825. }
  826. $plugin = new stdClass();
  827. $plugin->version = null;
  828. $plugin->cron = 0;
  829. $module = $plugin; // Prevent some notices when module placed in wrong directory.
  830. include($fullblock.'/version.php');
  831. unset($module);
  832. $block = clone($plugin);
  833. unset($block->version);
  834. unset($block->component);
  835. unset($block->dependencies);
  836. unset($block->release);
  837. if (empty($plugin->version)) {
  838. throw new plugin_defective_exception($component, 'Missing block version number in version.php.');
  839. }
  840. if (empty($plugin->component)) {
  841. throw new plugin_defective_exception($component, 'Missing $plugin->component declaration in version.php.');
  842. }
  843. if ($plugin->component !== $component) {
  844. throw new plugin_misplaced_exception($plugin->component, null, $fullblock);
  845. }
  846. if (!empty($plugin->requires)) {
  847. if ($plugin->requires > $CFG->version) {
  848. throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
  849. } else if ($plugin->requires < 2010000000) {
  850. throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
  851. }
  852. }
  853. if (!is_readable($fullblock.'/block_'.$blockname.'.php')) {
  854. throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.');
  855. }
  856. include_once($fullblock.'/block_'.$blockname.'.php');
  857. $classname = 'block_'.$blockname;
  858. if (!class_exists($classname)) {
  859. throw new plugin_defective_exception($component, 'Can not load main class.');
  860. }
  861. $blockobj = new $classname; // This is what we'll be testing
  862. $blocktitle = $blockobj->get_title();
  863. // OK, it's as we all hoped. For further tests, the object will do them itself.
  864. if (!$blockobj->_self_test()) {
  865. throw new plugin_defective_exception($component, 'Self test failed.');
  866. }
  867. $block->name = $blockname; // The name MUST match the directory
  868. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  869. if (file_exists($fullblock.'/db/install.php')) {
  870. if (get_config('block_'.$blockname, 'installrunning')) {
  871. require_once($fullblock.'/db/install.php');
  872. $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery';
  873. if (function_exists($recover_install_function)) {
  874. $startcallback($component, true, $verbose);
  875. $recover_install_function();
  876. unset_config('installrunning', 'block_'.$blockname);
  877. // Install various components
  878. update_capabilities($component);
  879. log_update_descriptions($component);
  880. external_update_descriptions($component);
  881. \core\task\manager::reset_scheduled_tasks_for_component($component);
  882. \core_analytics\manager::update_default_models_for_component($component);
  883. message_update_providers($component);
  884. \core\message\inbound\manager::update_handlers_for_component($component);
  885. upgrade_plugin_mnet_functions($component);
  886. core_tag_area::reset_definitions_for_component($component);
  887. $endcallback($component, true, $verbose);
  888. }
  889. }
  890. }
  891. if (empty($installedversion)) { // block not installed yet, so install it
  892. $conflictblock = array_search($blocktitle, $blocktitles);
  893. if ($conflictblock !== false) {
  894. // Duplicate block titles are not allowed, they confuse people
  895. // AND PHP's associative arrays ;)
  896. throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock)));
  897. }
  898. $startcallback($component, true, $verbose);
  899. if (file_exists($fullblock.'/db/install.xml')) {
  900. $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
  901. }
  902. $block->id = $DB->insert_record('block', $block);
  903. upgrade_block_savepoint(true, $plugin->version, $block->name, false);
  904. if (file_exists($fullblock.'/db/install.php')) {
  905. require_once($fullblock.'/db/install.php');
  906. // Set installation running flag, we need to recover after exception or error
  907. set_config('installrunning', 1, 'block_'.$blockname);
  908. $post_install_function = 'xmldb_block_'.$blockname.'_install';
  909. $post_install_function();
  910. unset_config('installrunning', 'block_'.$blockname);
  911. }
  912. $blocktitles[$block->name] = $blocktitle;
  913. // Install various components
  914. update_capabilities($component);
  915. log_update_descriptions($component);
  916. external_update_descriptions($component);
  917. \core\task\manager::reset_scheduled_tasks_for_component($component);
  918. \core_analytics\manager::update_default_models_for_component($component);
  919. message_update_providers($component);
  920. \core\message\inbound\manager::update_handlers_for_component($component);
  921. core_tag_area::reset_definitions_for_component($component);
  922. upgrade_plugin_mnet_functions($component);
  923. $endcallback($component, true, $verbose);
  924. } else if ($installedversion < $plugin->version) {
  925. $startcallback($component, false, $verbose);
  926. if (is_readable($fullblock.'/db/upgrade.php')) {
  927. require_once($fullblock.'/db/upgrade.php'); // defines new upgrading function
  928. $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
  929. $result = $newupgrade_function($installedversion, $block);
  930. } else {
  931. $result = true;
  932. }
  933. $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
  934. $currblock = $DB->get_record('block', array('name'=>$block->name));
  935. if ($installedversion < $plugin->version) {
  936. // store version if not already there
  937. upgrade_block_savepoint($result, $plugin->version, $block->name, false);
  938. }
  939. if ($currblock->cron != $block->cron) {
  940. // update cron flag if needed
  941. $DB->set_field('block', 'cron', $block->cron, array('id' => $currblock->id));
  942. }
  943. // Upgrade various components
  944. update_capabilities($component);
  945. log_update_descriptions($component);
  946. external_update_descriptions($component);
  947. \core\task\manager::reset_scheduled_tasks_for_component($component);
  948. \core_analytics\manager::update_default_models_for_component($component);
  949. message_update_providers($component);
  950. \core\message\inbound\manager::update_handlers_for_component($component);
  951. upgrade_plugin_mnet_functions($component);
  952. core_tag_area::reset_definitions_for_component($component);
  953. $endcallback($component, false, $verbose);
  954. } else if ($installedversion > $plugin->version) {
  955. throw new downgrade_exception($component, $installedversion, $plugin->version);
  956. }
  957. }
  958. // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks
  959. if ($first_install) {
  960. //Iterate over each course - there should be only site course here now
  961. if ($courses = $DB->get_records('course')) {
  962. foreach ($courses as $course) {
  963. blocks_add_default_course_blocks($course);
  964. }
  965. }
  966. blocks_add_default_system_blocks();
  967. }
  968. }
  969. /**
  970. * Log_display description function used during install and upgrade.
  971. *
  972. * @param string $component name of component (moodle, mod_assignment, etc.)
  973. * @return void
  974. */
  975. function log_update_descriptions($component) {
  976. global $DB;
  977. $defpath = core_component::get_component_directory($component).'/db/log.php';
  978. if (!file_exists($defpath)) {
  979. $DB->delete_records('log_display', array('component'=>$component));
  980. return;
  981. }
  982. // load new info
  983. $logs = array();
  984. include($defpath);
  985. $newlogs = array();
  986. foreach ($logs as $log) {
  987. $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name
  988. }
  989. unset($logs);
  990. $logs = $newlogs;
  991. $fields = array('module', 'action', 'mtable', 'field');
  992. // update all log fist
  993. $dblogs = $DB->get_records('log_display', array('component'=>$component));
  994. foreach ($dblogs as $dblog) {
  995. $name = $dblog->module.'-'.$dblog->action;
  996. if (empty($logs[$name])) {
  997. $DB->delete_records('log_display', array('id'=>$dblog->id));
  998. continue;
  999. }
  1000. $log = $logs[$name];
  1001. unset($logs[$name]);
  1002. $update = false;
  1003. foreach ($fields as $field) {
  1004. if ($dblog->$field != $log[$field]) {
  1005. $dblog->$field = $log[$field];
  1006. $update = true;
  1007. }
  1008. }
  1009. if ($update) {
  1010. $DB->update_record('log_display', $dblog);
  1011. }
  1012. }
  1013. foreach ($logs as $log) {
  1014. $dblog = (object)$log;
  1015. $dblog->component = $component;
  1016. $DB->insert_record('log_display', $dblog);
  1017. }
  1018. }
  1019. /**
  1020. * Web service discovery function used during install and upgrade.
  1021. * @param string $component name of component (moodle, mod_assignment, etc.)
  1022. * @return void
  1023. */
  1024. function external_update_descriptions($component) {
  1025. global $DB, $CFG;
  1026. $defpath = core_component::get_component_directory($component).'/db/services.php';
  1027. if (!file_exists($defpath)) {
  1028. require_once($CFG->dirroot.'/lib/externallib.php');
  1029. external_delete_descriptions($component);
  1030. return;
  1031. }
  1032. // load new info
  1033. $functions = array();
  1034. $services = array();
  1035. include($defpath);
  1036. // update all function fist
  1037. $dbfunctions = $DB->get_records('external_functions', array('component'=>$component));
  1038. foreach ($dbfunctions as $dbfunction) {
  1039. if (empty($functions[$dbfunction->name])) {
  1040. $DB->delete_records('external_functions', array('id'=>$dbfunction->id));
  1041. // do not delete functions from external_services_functions, beacuse
  1042. // we want to notify admins when functions used in custom services disappear
  1043. //TODO: this looks wrong, we have to delete it eventually (skodak)
  1044. continue;
  1045. }
  1046. $function = $functions[$dbfunction->name];
  1047. unset($functions[$dbfunction->name]);
  1048. $function['classpath'] = empty($function['classpath']) ? null : $function['classpath'];
  1049. $function['methodname'] = $function['methodname'] ?? 'execute';
  1050. $update = false;
  1051. if ($dbfunction->classname != $function['classname']) {
  1052. $dbfunction->classname = $function['classname'];
  1053. $update = true;
  1054. }
  1055. if ($dbfunction->methodname != $function['methodname']) {
  1056. $dbfunction->methodname = $function['methodname'];
  1057. $update = true;
  1058. }
  1059. if ($dbfunction->classpath != $function['classpath']) {
  1060. $dbfunction->classpath = $function['classpath'];
  1061. $update = true;
  1062. }
  1063. $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
  1064. if ($dbfunction->capabilities != $functioncapabilities) {
  1065. $dbfunction->capabilities = $functioncapabilities;
  1066. $update = true;
  1067. }
  1068. if (isset($function['services']) and is_array($function['services'])) {
  1069. sort($function['services']);
  1070. $functionservices = implode(',', $function['services']);
  1071. } else {
  1072. // Force null values in the DB.
  1073. $functionservices = null;
  1074. }
  1075. if ($dbfunction->services != $functionservices) {
  1076. // Now, we need to check if services were removed, in that case we need to remove the function from them.
  1077. $servicesremoved = array_diff(explode(",", $dbfunction->services), explode(",", $functionservices));
  1078. foreach ($servicesremoved as $removedshortname) {
  1079. if ($externalserviceid = $DB->get_field('external_services', 'id', array("shortname" => $removedshortname))) {
  1080. $DB->delete_records('external_services_functions', array('functionname' => $dbfunction->name,
  1081. 'externalserviceid' => $externalserviceid));
  1082. }
  1083. }
  1084. $dbfunction->services = $functionservices;
  1085. $update = true;
  1086. }
  1087. if ($update) {
  1088. $DB->update_record('external_functions', $dbfunction);
  1089. }
  1090. }
  1091. foreach ($functions as $fname => $function) {
  1092. $dbfunction = new stdClass();
  1093. $dbfunction->name = $fname;
  1094. $dbfunction->classname = $function['c…

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