PageRenderTime 70ms CodeModel.GetById 24ms 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
  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['classname'];
  1095. $dbfunction->methodname = $function['methodname'] ?? 'execute';
  1096. $dbfunction->classpath = empty($function['classpath']) ? null : $function['classpath'];
  1097. $dbfunction->component = $component;
  1098. $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
  1099. if (isset($function['services']) and is_array($function['services'])) {
  1100. sort($function['services']);
  1101. $dbfunction->services = implode(',', $function['services']);
  1102. } else {
  1103. // Force null values in the DB.
  1104. $dbfunction->services = null;
  1105. }
  1106. $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
  1107. }
  1108. unset($functions);
  1109. // now deal with services
  1110. $dbservices = $DB->get_records('external_services', array('component'=>$component));
  1111. foreach ($dbservices as $dbservice) {
  1112. if (empty($services[$dbservice->name])) {
  1113. $DB->delete_records('external_tokens', array('externalserviceid'=>$dbservice->id));
  1114. $DB->delete_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
  1115. $DB->delete_records('external_services_users', array('externalserviceid'=>$dbservice->id));
  1116. $DB->delete_records('external_services', array('id'=>$dbservice->id));
  1117. continue;
  1118. }
  1119. $service = $services[$dbservice->name];
  1120. unset($services[$dbservice->name]);
  1121. $service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled'];
  1122. $service['requiredcapability'] = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
  1123. $service['restrictedusers'] = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
  1124. $service['downloadfiles'] = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
  1125. $service['uploadfiles'] = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
  1126. $service['shortname'] = !isset($service['shortname']) ? null : $service['shortname'];
  1127. $update = false;
  1128. if ($dbservice->requiredcapability != $service['requiredcapability']) {
  1129. $dbservice->requiredcapability = $service['requiredcapability'];
  1130. $update = true;
  1131. }
  1132. if ($dbservice->restrictedusers != $service['restrictedusers']) {
  1133. $dbservice->restrictedusers = $service['restrictedusers'];
  1134. $update = true;
  1135. }
  1136. if ($dbservice->downloadfiles != $service['downloadfiles']) {
  1137. $dbservice->downloadfiles = $service['downloadfiles'];
  1138. $update = true;
  1139. }
  1140. if ($dbservice->uploadfiles != $service['uploadfiles']) {
  1141. $dbservice->uploadfiles = $service['uploadfiles'];
  1142. $update = true;
  1143. }
  1144. //if shortname is not a PARAM_ALPHANUMEXT, fail (tested here for service update and creation)
  1145. if (isset($service['shortname']) and
  1146. (clean_param($service['shortname'], PARAM_ALPHANUMEXT) != $service['shortname'])) {
  1147. throw new moodle_exception('installserviceshortnameerror', 'webservice', '', $service['shortname']);
  1148. }
  1149. if ($dbservice->shortname != $service['shortname']) {
  1150. //check that shortname is unique
  1151. if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
  1152. $existingservice = $DB->get_record('external_services',
  1153. array('shortname' => $service['shortname']));
  1154. if (!empty($existingservice)) {
  1155. throw new moodle_exception('installexistingserviceshortnameerror', 'webservice', '', $service['shortname']);
  1156. }
  1157. }
  1158. $dbservice->shortname = $service['shortname'];
  1159. $update = true;
  1160. }
  1161. if ($update) {
  1162. $DB->update_record('external_services', $dbservice);
  1163. }
  1164. $functions = $DB->get_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
  1165. foreach ($functions as $function) {
  1166. $key = array_search($function->functionname, $service['functions']);
  1167. if ($key === false) {
  1168. $DB->delete_records('external_services_functions', array('id'=>$function->id));
  1169. } else {
  1170. unset($service['functions'][$key]);
  1171. }
  1172. }
  1173. foreach ($service['functions'] as $fname) {
  1174. $newf = new stdClass();
  1175. $newf->externalserviceid = $dbservice->id;
  1176. $newf->functionname = $fname;
  1177. $DB->insert_record('external_services_functions', $newf);
  1178. }
  1179. unset($functions);
  1180. }
  1181. foreach ($services as $name => $service) {
  1182. //check that shortname is unique
  1183. if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
  1184. $existingservice = $DB->get_record('external_services',
  1185. array('shortname' => $service['shortname']));
  1186. if (!empty($existingservice)) {
  1187. throw new moodle_exception('installserviceshortnameerror', 'webservice');
  1188. }
  1189. }
  1190. $dbservice = new stdClass();
  1191. $dbservice->name = $name;
  1192. $dbservice->enabled = empty($service['enabled']) ? 0 : $service['enabled'];
  1193. $dbservice->requiredcapability = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
  1194. $dbservice->restrictedusers = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
  1195. $dbservice->downloadfiles = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
  1196. $dbservice->uploadfiles = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
  1197. $dbservice->shortname = !isset($service['shortname']) ? null : $service['shortname'];
  1198. $dbservice->component = $component;
  1199. $dbservice->timecreated = time();
  1200. $dbservice->id = $DB->insert_record('external_services', $dbservice);
  1201. foreach ($service['functions'] as $fname) {
  1202. $newf = new stdClass();
  1203. $newf->externalserviceid = $dbservice->id;
  1204. $newf->functionname = $fname;
  1205. $DB->insert_record('external_services_functions', $newf);
  1206. }
  1207. }
  1208. }
  1209. /**
  1210. * Allow plugins and subsystems to add external functions to other plugins or built-in services.
  1211. * This function is executed just after all the plugins have been updated.
  1212. */
  1213. function external_update_services() {
  1214. global $DB;
  1215. // Look for external functions that want to be added in existing services.
  1216. $functions = $DB->get_records_select('external_functions', 'services IS NOT NULL');
  1217. $servicescache = array();
  1218. foreach ($functions as $function) {
  1219. // Prevent edge cases.
  1220. if (empty($function->services)) {
  1221. continue;
  1222. }
  1223. $services = explode(',', $function->services);
  1224. foreach ($services as $serviceshortname) {
  1225. // Get the service id by shortname.
  1226. if (!empty($servicescache[$serviceshortname])) {
  1227. $serviceid = $servicescache[$serviceshortname];
  1228. } else if ($service = $DB->get_record('external_services', array('shortname' => $serviceshortname))) {
  1229. // If the component is empty, it means that is not a built-in service.
  1230. // We don't allow functions to inject themselves in services created by an user in Moodle.
  1231. if (empty($service->component)) {
  1232. continue;
  1233. }
  1234. $serviceid = $service->id;
  1235. $servicescache[$serviceshortname] = $serviceid;
  1236. } else {
  1237. // Service not found.
  1238. continue;
  1239. }
  1240. // Finally add the function to the service.
  1241. $newf = new stdClass();
  1242. $newf->externalserviceid = $serviceid;
  1243. $newf->functionname = $function->name;
  1244. if (!$DB->record_exists('external_services_functions', (array)$newf)) {
  1245. $DB->insert_record('external_services_functions', $newf);
  1246. }
  1247. }
  1248. }
  1249. }
  1250. /**
  1251. * upgrade logging functions
  1252. */
  1253. function upgrade_handle_exception($ex, $plugin = null) {
  1254. global $CFG;
  1255. // rollback everything, we need to log all upgrade problems
  1256. abort_all_db_transactions();
  1257. $info = get_exception_info($ex);
  1258. // First log upgrade error
  1259. upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace);
  1260. // Always turn on debugging - admins need to know what is going on
  1261. set_debugging(DEBUG_DEVELOPER, true);
  1262. default_exception_handler($ex, true, $plugin);
  1263. }
  1264. /**
  1265. * Adds log entry into upgrade_log table
  1266. *
  1267. * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR
  1268. * @param string $plugin frankenstyle component name
  1269. * @param string $info short description text of log entry
  1270. * @param string $details long problem description
  1271. * @param string $backtrace string used for errors only
  1272. * @return void
  1273. */
  1274. function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
  1275. global $DB, $USER, $CFG;
  1276. if (empty($plugin)) {
  1277. $plugin = 'core';
  1278. }
  1279. list($plugintype, $pluginname) = core_component::normalize_component($plugin);
  1280. $component = is_null($pluginname) ? $plugintype : $plugintype . '_' . $pluginname;
  1281. $backtrace = format_backtrace($backtrace, true);
  1282. $currentversion = null;
  1283. $targetversion = null;
  1284. //first try to find out current version number
  1285. if ($plugintype === 'core') {
  1286. //main
  1287. $currentversion = $CFG->version;
  1288. $version = null;
  1289. include("$CFG->dirroot/version.php");
  1290. $targetversion = $version;
  1291. } else {
  1292. $pluginversion = get_config($component, 'version');
  1293. if (!empty($pluginversion)) {
  1294. $currentversion = $pluginversion;
  1295. }
  1296. $cd = core_component::get_component_directory($component);
  1297. if (file_exists("$cd/version.php")) {
  1298. $plugin = new stdClass();
  1299. $plugin->version = null;
  1300. $module = $plugin;
  1301. include("$cd/version.php");
  1302. $targetversion = $plugin->version;
  1303. }
  1304. }
  1305. $log = new stdClass();
  1306. $log->type = $type;
  1307. $log->plugin = $component;
  1308. $log->version = $currentversion;
  1309. $log->targetversion = $targetversion;
  1310. $log->info = $info;
  1311. $log->details = $details;
  1312. $log->backtrace = $backtrace;
  1313. $log->userid = $USER->id;
  1314. $log->timemodified = time();
  1315. try {
  1316. $DB->insert_record('upgrade_log', $log);
  1317. } catch (Exception $ignored) {
  1318. // possible during install or 2.0 upgrade
  1319. }
  1320. }
  1321. /**
  1322. * Marks start of upgrade, blocks any other access to site.
  1323. * The upgrade is finished at the end of script or after timeout.
  1324. *
  1325. * @global object
  1326. * @global object
  1327. * @global object
  1328. */
  1329. function upgrade_started($preinstall=false) {
  1330. global $CFG, $DB, $PAGE, $OUTPUT;
  1331. static $started = false;
  1332. if ($preinstall) {
  1333. ignore_user_abort(true);
  1334. upgrade_setup_debug(true);
  1335. } else if ($started) {
  1336. upgrade_set_timeout(120);
  1337. } else {
  1338. if (!CLI_SCRIPT and !$PAGE->headerprinted) {
  1339. $strupgrade = get_string('upgradingversion', 'admin');
  1340. $PAGE->set_pagelayout('maintenance');
  1341. upgrade_init_javascript();
  1342. $PAGE->set_title($strupgrade.' - Moodle '.$CFG->target_release);
  1343. $PAGE->set_heading($strupgrade);
  1344. $PAGE->navbar->add($strupgrade);
  1345. $PAGE->set_cacheable(false);
  1346. echo $OUTPUT->header();
  1347. }
  1348. ignore_user_abort(true);
  1349. core_shutdown_manager::register_function('upgrade_finished_handler');
  1350. upgrade_setup_debug(true);
  1351. set_config('upgraderunning', time()+300);
  1352. $started = true;
  1353. }
  1354. }
  1355. /**
  1356. * Internal function - executed if upgrade interrupted.
  1357. */
  1358. function upgrade_finished_handler() {
  1359. upgrade_finished();
  1360. }
  1361. /**
  1362. * Indicates upgrade is finished.
  1363. *
  1364. * This function may be called repeatedly.
  1365. *
  1366. * @global object
  1367. * @global object
  1368. */
  1369. function upgrade_finished($continueurl=null) {
  1370. global $CFG, $DB, $OUTPUT;
  1371. if (!empty($CFG->upgraderunning)) {
  1372. unset_config('upgraderunning');
  1373. // We have to forcefully purge the caches using the writer here.
  1374. // This has to be done after we unset the config var. If someone hits the site while this is set they will
  1375. // cause the config values to propogate to the caches.
  1376. // Caches are purged after the last step in an upgrade but there is several code routines that exceute between
  1377. // then and now that leaving a window for things to fall out of sync.
  1378. cache_helper::purge_all(true);
  1379. upgrade_setup_debug(false);
  1380. ignore_user_abort(false);
  1381. if ($continueurl) {
  1382. echo $OUTPUT->continue_button($continueurl);
  1383. echo $OUTPUT->footer();
  1384. die;
  1385. }
  1386. }
  1387. }
  1388. /**
  1389. * @global object
  1390. * @global object
  1391. */
  1392. function upgrade_setup_debug($starting) {
  1393. global $CFG, $DB;
  1394. static $originaldebug = null;
  1395. if ($starting) {
  1396. if ($originaldebug === null) {
  1397. $originaldebug = $DB->get_debug();
  1398. }
  1399. if (!empty($CFG->upgradeshowsql)) {
  1400. $DB->set_debug(true);
  1401. }
  1402. } else {
  1403. $DB->set_debug($originaldebug);
  1404. }
  1405. }
  1406. function print_upgrade_separator() {
  1407. if (!CLI_SCRIPT) {
  1408. echo '<hr />';
  1409. }
  1410. }
  1411. /**
  1412. * Default start upgrade callback
  1413. * @param string $plugin
  1414. * @param bool $installation true if installation, false means upgrade
  1415. */
  1416. function print_upgrade_part_start($plugin, $installation, $verbose) {
  1417. global $OUTPUT;
  1418. if (empty($plugin) or $plugin == 'moodle') {
  1419. upgrade_started($installation); // does not store upgrade running flag yet
  1420. if ($verbose) {
  1421. echo $OUTPUT->heading(get_string('coresystem'));
  1422. }
  1423. } else {
  1424. upgrade_started();
  1425. if ($verbose) {
  1426. echo $OUTPUT->heading($plugin);
  1427. }
  1428. }
  1429. if ($installation) {
  1430. if (empty($plugin) or $plugin == 'moodle') {
  1431. // no need to log - log table not yet there ;-)
  1432. } else {
  1433. upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin installation');
  1434. }
  1435. } else {
  1436. core_upgrade_time::record_start();
  1437. if (empty($plugin) or $plugin == 'moodle') {
  1438. upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting core upgrade');
  1439. } else {
  1440. upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin upgrade');
  1441. }
  1442. }
  1443. }
  1444. /**
  1445. * Default end upgrade callback
  1446. * @param string $plugin
  1447. * @param bool $installation true if installation, false means upgrade
  1448. */
  1449. function print_upgrade_part_end($plugin, $installation, $verbose) {
  1450. global $OUTPUT;
  1451. upgrade_started();
  1452. if ($installation) {
  1453. if (empty($plugin) or $plugin == 'moodle') {
  1454. upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core installed');
  1455. } else {
  1456. upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin installed');
  1457. }
  1458. } else {
  1459. if (empty($plugin) or $plugin == 'moodle') {
  1460. upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core upgraded');
  1461. } else {
  1462. upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin upgraded');
  1463. }
  1464. }
  1465. if ($verbose) {
  1466. if ($installation) {
  1467. $message = get_string('success');
  1468. } else {
  1469. $duration = core_upgrade_time::get_elapsed();
  1470. $message = get_string('successduration', '', format_float($duration, 2));
  1471. }
  1472. $notification = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
  1473. $notification->set_show_closebutton(false);
  1474. echo $OUTPUT->render($notification);
  1475. print_upgrade_separator();
  1476. }
  1477. }
  1478. /**
  1479. * Sets up JS code required for all upgrade scripts.
  1480. * @global object
  1481. */
  1482. function upgrade_init_javascript() {
  1483. global $PAGE;
  1484. // scroll to the end of each upgrade page so that ppl see either error or continue button,
  1485. // no need to scroll continuously any more, it is enough to jump to end once the footer is printed ;-)
  1486. $js = "window.scrollTo(0, 5000000);";
  1487. $PAGE->requires->js_init_code($js);
  1488. }
  1489. /**
  1490. * Try to upgrade the given language pack (or current language)
  1491. *
  1492. * @param string $lang the code of the language to update, defaults to the current language
  1493. */
  1494. function upgrade_language_pack($lang = null) {
  1495. global $CFG;
  1496. if (!empty($CFG->skiplangupgrade)) {
  1497. return;
  1498. }
  1499. if (!file_exists("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php")) {
  1500. // weird, somebody uninstalled the import utility
  1501. return;
  1502. }
  1503. if (!$lang) {
  1504. $lang = current_language();
  1505. }
  1506. if (!get_string_manager()->translation_exists($lang)) {
  1507. return;
  1508. }
  1509. get_string_manager()->reset_caches();
  1510. if ($lang === 'en') {
  1511. return; // Nothing to do
  1512. }
  1513. upgrade_started(false);
  1514. require_once("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php");
  1515. tool_langimport_preupgrade_update($lang);
  1516. get_string_manager()->reset_caches();
  1517. print_upgrade_separator();
  1518. }
  1519. /**
  1520. * Build the current theme so that the user doesn't have to wait for it
  1521. * to build on the first page load after the install / upgrade.
  1522. */
  1523. function upgrade_themes() {
  1524. global $CFG;
  1525. require_once("{$CFG->libdir}/outputlib.php");
  1526. // Build the current theme so that the user can immediately
  1527. // browse the site without having to wait for the theme to build.
  1528. $themeconfig = theme_config::load($CFG->theme);
  1529. $direction = right_to_left() ? 'rtl' : 'ltr';
  1530. theme_build_css_for_themes([$themeconfig], [$direction]);
  1531. // Only queue the task if there isn't already one queued.
  1532. if (empty(\core\task\manager::get_adhoc_tasks('\\core\\task\\build_installed_themes_task'))) {
  1533. // Queue a task to build all of the site themes at some point
  1534. // later. These can happen offline because it doesn't block the
  1535. // user unless they quickly change theme.
  1536. $adhoctask = new \core\task\build_installed_themes_task();
  1537. \core\task\manager::queue_adhoc_task($adhoctask);
  1538. }
  1539. }
  1540. /**
  1541. * Install core moodle tables and initialize
  1542. * @param float $version target version
  1543. * @param bool $verbose
  1544. * @return void, may throw exception
  1545. */
  1546. function install_core($version, $verbose) {
  1547. global $CFG, $DB;
  1548. // We can not call purge_all_caches() yet, make sure the temp and cache dirs exist and are empty.
  1549. remove_dir($CFG->cachedir.'', true);
  1550. make_cache_directory('', true);
  1551. remove_dir($CFG->localcachedir.'', true);
  1552. make_localcache_directory('', true);
  1553. remove_dir($CFG->tempdir.'', true);
  1554. make_temp_directory('', true);
  1555. remove_dir($CFG->backuptempdir.'', true);
  1556. make_backup_temp_directory('', true);
  1557. remove_dir($CFG->dataroot.'/muc', true);
  1558. make_writable_directory($CFG->dataroot.'/muc', true);
  1559. try {
  1560. core_php_time_limit::raise(600);
  1561. print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag
  1562. $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml");
  1563. upgrade_started(); // we want the flag to be stored in config table ;-)
  1564. // set all core default records and default settings
  1565. require_once("$CFG->libdir/db/install.php");
  1566. xmldb_main_install(); // installs the capabilities too
  1567. // store version
  1568. upgrade_main_savepoint(true, $version, false);
  1569. // Continue with the installation
  1570. log_update_descriptions('moodle');
  1571. external_update_descriptions('moodle');
  1572. \core\task\manager::reset_scheduled_tasks_for_component('moodle');
  1573. \core_analytics\manager::update_default_models_for_component('moodle');
  1574. message_update_providers('moodle');
  1575. \core\message\inbound\manager::update_handlers_for_component('moodle');
  1576. core_tag_area::reset_definitions_for_component('moodle');
  1577. // Write default settings unconditionally
  1578. admin_apply_default_settings(NULL, true);
  1579. print_upgrade_part_end(null, true, $verbose);
  1580. // Purge all caches. They're disabled but this ensures that we don't have any persistent data just in case something
  1581. // during installation didn't use APIs.
  1582. cache_helper::purge_all();
  1583. } catch (exception $ex) {
  1584. upgrade_handle_exception($ex);
  1585. } catch (Throwable $ex) {
  1586. // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
  1587. upgrade_handle_exception($ex);
  1588. }
  1589. }
  1590. /**
  1591. * Upgrade moodle core
  1592. * @param float $version target version
  1593. * @param bool $verbose
  1594. * @return void, may throw exception
  1595. */
  1596. function upgrade_core($version, $verbose) {
  1597. global $CFG, $SITE, $DB, $COURSE;
  1598. raise_memory_limit(MEMORY_EXTRA);
  1599. require_once($CFG->libdir.'/db/upgrade.php'); // Defines upgrades
  1600. try {
  1601. // Reset caches before any output.
  1602. cache_helper::purge_all(true);
  1603. purge_all_caches();
  1604. // Upgrade current language pack if we can
  1605. upgrade_language_pack();
  1606. print_upgrade_part_start('moodle', false, $verbose);
  1607. // Pre-upgrade scripts for local hack workarounds.
  1608. $preupgradefile = "$CFG->dirroot/local/preupgrade.php";
  1609. if (file_exists($preupgradefile)) {
  1610. core_php_time_limit::raise();
  1611. require($preupgradefile);
  1612. // Reset upgrade timeout to default.
  1613. upgrade_set_timeout();
  1614. }
  1615. $result = xmldb_main_upgrade($CFG->version);
  1616. if ($version > $CFG->version) {
  1617. // store version if not already there
  1618. upgrade_main_savepoint($result, $version, false);
  1619. }
  1620. // In case structure of 'course' table has been changed and we forgot to update $SITE, re-read it from db.
  1621. $SITE = $DB->get_record('course', array('id' => $SITE->id));
  1622. $COURSE = clone($SITE);
  1623. // perform all other component upgrade routines
  1624. update_capabilities('moodle');
  1625. log_update_descriptions('moodle');
  1626. external_update_descriptions('moodle');
  1627. \core\task\manager::reset_scheduled_tasks_for_component('moodle');
  1628. \core_analytics\manager::update_default_models_for_component('moodle');
  1629. message_update_providers('moodle');
  1630. \core\message\inbound\manager::update_handlers_for_component('moodle');
  1631. core_tag_area::reset_definitions_for_component('moodle');
  1632. // Update core definitions.
  1633. cache_helper::update_definitions(true);
  1634. // Purge caches again, just to be sure we arn't holding onto old stuff now.
  1635. cache_helper::purge_all(true);
  1636. purge_all_caches();
  1637. // Clean up contexts - more and more stuff depends on existence of paths and contexts
  1638. context_helper::cleanup_instances();
  1639. context_helper::create_instances(null, false);
  1640. context_helper::build_all_paths(false);
  1641. $syscontext = context_system::instance();
  1642. $syscontext->mark_dirty();
  1643. print_upgrade_part_end('moodle', false, $verbose);
  1644. } catch (Exception $ex) {
  1645. upgrade_handle_exception($ex);
  1646. } catch (Throwable $ex) {
  1647. // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
  1648. upgrade_handle_exception($ex);
  1649. }
  1650. }
  1651. /**
  1652. * Upgrade/install other parts of moodle
  1653. * @param bool $verbose
  1654. * @return void, may throw exception
  1655. */
  1656. function upgrade_noncore($verbose) {
  1657. global $CFG;
  1658. raise_memory_limit(MEMORY_EXTRA);
  1659. // upgrade all plugins types
  1660. try {
  1661. // Reset caches before any output.
  1662. cache_helper::purge_all(true);
  1663. purge_all_caches();
  1664. $plugintypes = core_component::get_plugin_types();
  1665. foreach ($plugintypes as $type=>$location) {
  1666. upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose);
  1667. }
  1668. // Upgrade services.
  1669. // This function gives plugins and subsystems a chance to add functions to existing built-in services.
  1670. external_update_services();
  1671. // Update cache definitions. Involves scanning each plugin for any changes.
  1672. cache_helper::update_definitions();
  1673. // Mark the site as upgraded.
  1674. set_config('allversionshash', core_component::get_all_versions_hash());
  1675. // Purge caches again, just to be sure we arn't holding onto old stuff now.
  1676. cache_helper::purge_all(true);
  1677. purge_all_caches();
  1678. } catch (Exception $ex) {
  1679. upgrade_handle_exception($ex);
  1680. } catch (Throwable $ex) {
  1681. // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5).
  1682. upgrade_handle_exception($ex);
  1683. }
  1684. }
  1685. /**
  1686. * Checks if the main tables have been installed yet or not.
  1687. *
  1688. * Note: we can not use caches here because they might be stale,
  1689. * use with care!
  1690. *
  1691. * @return bool
  1692. */
  1693. function core_tables_exist() {
  1694. global $DB;
  1695. if (!$tables = $DB->get_tables(false) ) { // No tables yet at all.
  1696. return false;
  1697. } else { // Check for missing main tables
  1698. $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml
  1699. foreach ($mtables as $mtable) {
  1700. if (!in_array($mtable, $tables)) {
  1701. return false;
  1702. }
  1703. }
  1704. return true;
  1705. }
  1706. }
  1707. /**
  1708. * upgrades the mnet rpc definitions for the given component.
  1709. * this method doesn't return status, an exception will be thrown in the case of an error
  1710. *
  1711. * @param string $component the plugin to upgrade, eg auth_mnet
  1712. */
  1713. function upgrade_plugin_mnet_functions($component) {
  1714. global $DB, $CFG;
  1715. list($type, $plugin) = core_component::normalize_component($component);
  1716. $path = core_component::get_plugin_directory($type, $plugin);
  1717. $publishes = array();
  1718. $subscribes = array();
  1719. if (file_exists($path . '/db/mnet.php')) {
  1720. require_once($path . '/db/mnet.php'); // $publishes comes from this file
  1721. }
  1722. if (empty($publishes)) {
  1723. $publishes = array(); // still need this to be able to disable stuff later
  1724. }
  1725. if (empty($subscribes)) {
  1726. $subscribes = array(); // still need this to be able to disable stuff later
  1727. }
  1728. static $servicecache = array();
  1729. // rekey an array based on the rpc method for easy lookups later
  1730. $publishmethodservices = array();
  1731. $subscribemethodservices = array();
  1732. foreach($publishes as $servicename => $service) {
  1733. if (is_array($service['methods'])) {
  1734. foreach($service['methods'] as $methodname) {
  1735. $service['servicename'] = $servicename;
  1736. $publishmethodservices[$methodname][] = $service;
  1737. }
  1738. }
  1739. }
  1740. // Disable functions that don't exist (any more) in the source
  1741. // Should these be deleted? What about their permissions records?
  1742. foreach ($DB->get_records('mnet_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
  1743. if (!array_key_exists($rpc->functionname, $publishmethodservices) && $rpc->enabled) {
  1744. $DB->set_field('mnet_rpc', 'enabled', 0, array('id' => $rpc->id));
  1745. } else if (array_key_exists($rpc->functionname, $publishmethodservices) && !$rpc->enabled) {
  1746. $DB->set_field('mnet_rpc', 'enabled', 1, array('id' => $rpc->id));
  1747. }
  1748. }
  1749. // reflect all the services we're publishing and save them
  1750. static $cachedclasses = array(); // to store reflection information in
  1751. foreach ($publishes as $service => $data) {
  1752. $f = $data['filename'];
  1753. $c = $data['classname'];
  1754. foreach ($data['methods'] as $method) {
  1755. $dataobject = new stdClass();
  1756. $dataobject->plugintype = $type;
  1757. $dataobject->pluginname = $plugin;
  1758. $dataobject->enabled = 1;
  1759. $dataobject->classname = $c;
  1760. $dataobject->filename = $f;
  1761. if (is_string($method)) {
  1762. $dataobject->functionname = $method;
  1763. } else if (is_array($method)) { // wants to override file or class
  1764. $dataobject->functionname = $method['method'];
  1765. $dataobject->classname = $method['classname'];
  1766. $dataobject->filename = $method['filename'];
  1767. }
  1768. $dataobject->xmlrpcpath = $type.'/'.$plugin.'/'.$dataobject->filename.'/'.$method;
  1769. $dataobject->static = false;
  1770. require_once($path . '/' . $dataobject->filename);
  1771. $functionreflect = null; // slightly different ways to get this depending on whether it's a class method or a function
  1772. if (!empty($dataobject->classname)) {
  1773. if (!class_exists($dataobject->classname)) {
  1774. throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
  1775. }
  1776. $key = $dataobject->filename . '|' . $dataobject->classname;
  1777. if (!array_key_exists($key, $cachedclasses)) { // look to see if we've already got a reflection object
  1778. try {
  1779. $cachedclasses[$key] = new ReflectionClass($dataobject->classname);
  1780. } catch (ReflectionException $e) { // catch these and rethrow them to something more helpful
  1781. throw new moodle_exception('installreflectionclasserror', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname, 'error' => $e->getMessage()));
  1782. }
  1783. }
  1784. $r =& $cachedclasses[$key];
  1785. if (!$r->hasMethod($dataobject->functionname)) {
  1786. throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
  1787. }
  1788. $functionreflect = $r->getMethod($dataobject->functionname);
  1789. $dataobject->static = (int)$functionreflect->isStatic();
  1790. } else {
  1791. if (!function_exists($dataobject->functionname)) {
  1792. throw new moodle_exception('installnosuchfunction', 'mnet', '', (object)array('method' => $dataobject->functionname, 'file' => $dataobject->filename));
  1793. }
  1794. try {
  1795. $functionreflect = new ReflectionFunction($dataobject->functionname);
  1796. } catch (ReflectionException $e) { // catch these and rethrow them to something more helpful
  1797. throw new moodle_exception('installreflectionfunctionerror', 'mnet', '', (object)array('method' => $dataobject->functionname, '' => $dataobject->filename, 'error' => $e->getMessage()));
  1798. }
  1799. }
  1800. $dataobject->profile = serialize(admin_mnet_method_profile($functionreflect));
  1801. $dataobject->help = admin_mnet_method_get_help($functionreflect);
  1802. if ($record_exists = $DB->get_record('mnet_rpc', array('xmlrpcpath'=>$dataobject->xmlrpcpath))) {
  1803. $dataobject->id = $record_exists->id;
  1804. $dataobject->enabled = $record_exists->enabled;
  1805. $DB->update_record('mnet_rpc', $dataobject);
  1806. } else {
  1807. $dataobject->id = $DB->insert_record('mnet_rpc', $dataobject, true);
  1808. }
  1809. // TODO this API versioning must be reworked, here the recently processed method
  1810. // sets the service API which may not be correct
  1811. foreach ($publishmethodservices[$dataobject->functionname] as $service) {
  1812. if ($serviceobj = $DB->get_record('mnet_service', array('name'=>$service['servicename']))) {
  1813. $serviceobj->apiversion = $service['apiversion'];
  1814. $DB->update_record('mnet_service', $serviceobj);
  1815. } else {
  1816. $serviceobj = new stdClass();
  1817. $serviceobj->name = $service['servicename'];
  1818. $serviceobj->description = empty($service['description']) ? '' : $service['description'];
  1819. $serviceobj->apiversion = $service['apiversion'];
  1820. $serviceobj->offer = 1;
  1821. $serviceobj->id = $DB->insert_record('mnet_service', $serviceobj);
  1822. }
  1823. $servicecache[$service['servicename']] = $serviceobj;
  1824. if (!$DB->record_exists('mnet_service2rpc', array('rpcid'=>$dataobject->id, 'serviceid'=>$serviceobj->id))) {
  1825. $obj = new stdClass();
  1826. $obj->rpcid = $dataobject->id;
  1827. $obj->serviceid = $serviceobj->id;
  1828. $DB->insert_record('mnet_service2rpc', $obj, true);
  1829. }
  1830. }
  1831. }
  1832. }
  1833. // finished with methods we publish, now do subscribable methods
  1834. foreach($subscribes as $service => $methods) {
  1835. if (!array_key_exists($service, $servicecache)) {
  1836. if (!$serviceobj = $DB->get_record('mnet_service', array('name' => $service))) {
  1837. debugging("TODO: skipping unknown service $service - somebody needs to fix MDL-21993");
  1838. continue;
  1839. }
  1840. $servicecache[$service] = $serviceobj;
  1841. } else {
  1842. $serviceobj = $servicecache[$service];
  1843. }
  1844. foreach ($methods as $method => $xmlrpcpath) {
  1845. if (!$rpcid = $DB->get_field('mnet_remote_rpc', 'id', array('xmlrpcpath'=>$xmlrpcpath))) {
  1846. $remoterpc = (object)array(
  1847. 'functionname' => $method,
  1848. 'xmlrpcpath' => $xmlrpcpath,
  1849. 'plugintype' => $type,
  1850. 'pluginname' => $plugin,
  1851. 'enabled' => 1,
  1852. );
  1853. $rpcid = $remoterpc->id = $DB->insert_record('mnet_remote_rpc', $remoterpc, true);
  1854. }
  1855. if (!$DB->record_exists('mnet_remote_service2rpc', array('rpcid'=>$rpcid, 'serviceid'=>$serviceobj->id))) {
  1856. $obj = new stdClass();
  1857. $obj->rpcid = $rpcid;
  1858. $obj->serviceid = $serviceobj->id;
  1859. $DB->insert_record('mnet_remote_service2rpc', $obj, true);
  1860. }
  1861. $subscribemethodservices[$method][] = $service;
  1862. }
  1863. }
  1864. foreach ($DB->get_records('mnet_remote_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
  1865. if (!array_key_exists($rpc->functionname, $subscribemethodservices) && $rpc->enabled) {
  1866. $DB->set_field('mnet_remote_rpc', 'enabled', 0, array('id' => $rpc->id));
  1867. } else if (array_key_exists($rpc->functionname, $subscribemethodservices) && !$rpc->enabled) {
  1868. $DB->set_field('mnet_remote_rpc', 'enabled', 1, array('id' => $rpc->id));
  1869. }
  1870. }
  1871. return true;
  1872. }
  1873. /**
  1874. * Given some sort of reflection function/method object, return a profile array, ready to be serialized and stored
  1875. *
  1876. * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
  1877. *
  1878. * @return array associative array with function/method information
  1879. */
  1880. function admin_mnet_method_profile(ReflectionFunctionAbstract $function) {
  1881. $commentlines = admin_mnet_method_get_docblock($function);
  1882. $getkey = function($key) use ($commentlines) {
  1883. return array_values(array_filter($commentlines, function($line) use ($key) {
  1884. return $line[0] == $key;
  1885. }));
  1886. };
  1887. $returnline = $getkey('@return');
  1888. return array (
  1889. 'parameters' => array_map(function($line) {
  1890. return array(
  1891. 'name' => trim($line[2], " \t\n\r\0\x0B$"),
  1892. 'type' => $line[1],
  1893. 'description' => $line[3]
  1894. );
  1895. }, $getkey('@param')),
  1896. 'return' => array(
  1897. 'type' => !empty($returnline[0][1]) ? $returnline[0][1] : 'void',
  1898. 'description' => !empty($returnline[0][2]) ? $returnline[0][2] : ''
  1899. )
  1900. );
  1901. }
  1902. /**
  1903. * Given some sort of reflection function/method object, return an array of docblock lines, where each line is an array of
  1904. * keywords/descriptions
  1905. *
  1906. * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
  1907. *
  1908. * @return array docblock converted in to an array
  1909. */
  1910. function admin_mnet_method_get_docblock(ReflectionFunctionAbstract $function) {
  1911. return array_map(function($line) {
  1912. $text = trim($line, " \t\n\r\0\x0B*/");
  1913. if (strpos($text, '@param') === 0) {
  1914. return preg_split('/\s+/', $text, 4);
  1915. }
  1916. if (strpos($text, '@return') === 0) {
  1917. return preg_split('/\s+/', $text, 3);
  1918. }
  1919. return array($text);
  1920. }, explode("\n", $function->getDocComment()));
  1921. }
  1922. /**
  1923. * Given some sort of reflection function/method object, return just the help text
  1924. *
  1925. * @param ReflectionFunctionAbstract $function reflection function/method object from which to extract information
  1926. *
  1927. * @return string docblock help text
  1928. */
  1929. function admin_mnet_method_get_help(ReflectionFunctionAbstract $function) {
  1930. $helplines = array_map(function($line) {
  1931. return implode(' ', $line);
  1932. }, array_values(array_filter(admin_mnet_method_get_docblock($function), function($line) {
  1933. return strpos($line[0], '@') !== 0 && !empty($line[0]);
  1934. })));
  1935. return implode("\n", $helplines);
  1936. }
  1937. /**
  1938. * This function verifies that the database is not using an unsupported storage engine.
  1939. *
  1940. * @param environment_results $result object to update, if relevant
  1941. * @return environment_results|null updated results object, or null if the storage engine is supported
  1942. */
  1943. function check_database_storage_engine(environment_results $result) {
  1944. global $DB;
  1945. // Check if MySQL is the DB family (this will also be the same for MariaDB).
  1946. if ($DB->get_dbfamily() == 'mysql') {
  1947. // Get the database engine we will either be using to install the tables, or what we are currently using.
  1948. $engine = $DB->get_dbengine();
  1949. // Check if MyISAM is the storage engine that will be used, if so, do not proceed and display an error.
  1950. if ($engine == 'MyISAM') {
  1951. $result->setInfo('unsupported_db_storage_engine');
  1952. $result->setStatus(false);
  1953. return $result;
  1954. }
  1955. }
  1956. return null;
  1957. }
  1958. /**
  1959. * Method used to check the usage of slasharguments config and display a warning message.
  1960. *
  1961. * @param environment_results $result object to update, if relevant.
  1962. * @return environment_results|null updated results or null if slasharguments is disabled.
  1963. */
  1964. function check_slasharguments(environment_results $result){
  1965. global $CFG;
  1966. if (!during_initial_install() && empty($CFG->slasharguments)) {
  1967. $result->setInfo('slasharguments');
  1968. $result->setStatus(false);
  1969. return $result;
  1970. }
  1971. return null;
  1972. }
  1973. /**
  1974. * This function verifies if the database has tables using innoDB Antelope row format.
  1975. *
  1976. * @param environment_results $result
  1977. * @return environment_results|null updated results object, or null if no Antelope table has been found.
  1978. */
  1979. function check_database_tables_row_format(environment_results $result) {
  1980. global $DB;
  1981. if ($DB->get_dbfamily() == 'mysql') {
  1982. $generator = $DB->get_manager()->generator;
  1983. foreach ($DB->get_tables(false) as $table) {
  1984. $columns = $DB->get_columns($table, false);
  1985. $size = $generator->guess_antelope_row_size($columns);
  1986. $format = $DB->get_row_format($table);
  1987. if ($size <= $generator::ANTELOPE_MAX_ROW_SIZE) {
  1988. continue;
  1989. }
  1990. if ($format === 'Compact' or $format === 'Redundant') {
  1991. $result->setInfo('unsupported_db_table_row_format');
  1992. $result->setStatus(false);
  1993. return $result;
  1994. }
  1995. }
  1996. }
  1997. return null;
  1998. }
  1999. /**
  2000. * This function verfies that the database has tables using InnoDB Antelope row format.
  2001. *
  2002. * @param environment_results $result
  2003. * @return environment_results|null updated results object, or null if no Antelope table has been found.
  2004. */
  2005. function check_mysql_file_format(environment_results $result) {
  2006. global $DB;
  2007. if ($DB->get_dbfamily() == 'mysql') {
  2008. $collation = $DB->get_dbcollation();
  2009. $collationinfo = explode('_', $collation);
  2010. $charset = reset($collationinfo);
  2011. if ($charset == 'utf8mb4') {
  2012. if ($DB->get_row_format() !== "Barracuda") {
  2013. $result->setInfo('mysql_full_unicode_support#File_format');
  2014. $result->setStatus(false);
  2015. return $result;
  2016. }
  2017. }
  2018. }
  2019. return null;
  2020. }
  2021. /**
  2022. * This function verfies that the database has a setting of one file per table. This is required for 'utf8mb4'.
  2023. *
  2024. * @param environment_results $result
  2025. * @return environment_results|null updated results object, or null if innodb_file_per_table = 1.
  2026. */
  2027. function check_mysql_file_per_table(environment_results $result) {
  2028. global $DB;
  2029. if ($DB->get_dbfamily() == 'mysql') {
  2030. $collation = $DB->get_dbcollation();
  2031. $collationinfo = explode('_', $collation);
  2032. $charset = reset($collationinfo);
  2033. if ($charset == 'utf8mb4') {
  2034. if (!$DB->is_file_per_table_enabled()) {
  2035. $result->setInfo('mysql_full_unicode_support#File_per_table');
  2036. $result->setStatus(false);
  2037. return $result;
  2038. }
  2039. }
  2040. }
  2041. return null;
  2042. }
  2043. /**
  2044. * This function verfies that the database has the setting of large prefix enabled. This is required for 'utf8mb4'.
  2045. *
  2046. * @param environment_results $result
  2047. * @return environment_results|null updated results object, or null if innodb_large_prefix = 1.
  2048. */
  2049. function check_mysql_large_prefix(environment_results $result) {
  2050. global $DB;
  2051. if ($DB->get_dbfamily() == 'mysql') {
  2052. $collation = $DB->get_dbcollation();
  2053. $collationinfo = explode('_', $collation);
  2054. $charset = reset($collationinfo);
  2055. if ($charset == 'utf8mb4') {
  2056. if (!$DB->is_large_prefix_enabled()) {
  2057. $result->setInfo('mysql_full_unicode_support#Large_prefix');
  2058. $result->setStatus(false);
  2059. return $result;
  2060. }
  2061. }
  2062. }
  2063. return null;
  2064. }
  2065. /**
  2066. * This function checks the database to see if it is using incomplete unicode support.
  2067. *
  2068. * @param environment_results $result $result
  2069. * @return environment_results|null updated results object, or null if unicode is fully supported.
  2070. */
  2071. function check_mysql_incomplete_unicode_support(environment_results $result) {
  2072. global $DB;
  2073. if ($DB->get_dbfamily() == 'mysql') {
  2074. $collation = $DB->get_dbcollation();
  2075. $collationinfo = explode('_', $collation);
  2076. $charset = reset($collationinfo);
  2077. if ($charset == 'utf8') {
  2078. $result->setInfo('mysql_full_unicode_support');
  2079. $result->setStatus(false);
  2080. return $result;
  2081. }
  2082. }
  2083. return null;
  2084. }
  2085. /**
  2086. * Check if the site is being served using an ssl url.
  2087. *
  2088. * Note this does not really perform any request neither looks for proxies or
  2089. * other situations. Just looks to wwwroot and warn if it's not using https.
  2090. *
  2091. * @param environment_results $result $result
  2092. * @return environment_results|null updated results object, or null if the site is https.
  2093. */
  2094. function check_is_https(environment_results $result) {
  2095. global $CFG;
  2096. // Only if is defined, non-empty and whatever core tell us.
  2097. if (!empty($CFG->wwwroot) && !is_https()) {
  2098. $result->setInfo('site not https');
  2099. $result->setStatus(false);
  2100. return $result;
  2101. }
  2102. return null;
  2103. }
  2104. /**
  2105. * Check if the site is using 64 bits PHP.
  2106. *
  2107. * @param environment_results $result
  2108. * @return environment_results|null updated results object, or null if the site is using 64 bits PHP.
  2109. */
  2110. function check_sixtyfour_bits(environment_results $result) {
  2111. if (PHP_INT_SIZE === 4) {
  2112. $result->setInfo('php not 64 bits');
  2113. $result->setStatus(false);
  2114. return $result;
  2115. }
  2116. return null;
  2117. }
  2118. /**
  2119. * Check if the igbinary extension installed is buggy one
  2120. *
  2121. * There are a few php-igbinary versions that are buggy and
  2122. * return any unserialised array with wrong index. This defeats
  2123. * key() and next() operations on them.
  2124. *
  2125. * This library is used by MUC and also by memcached and redis
  2126. * when available.
  2127. *
  2128. * Let's inform if there is some problem when:
  2129. * - php 7.2 is being used (php 7.3 and up are immune).
  2130. * - the igbinary extension is installed.
  2131. * - the version of the extension is between 3.2.2 and 3.2.4.
  2132. * - the buggy behaviour is reproduced.
  2133. *
  2134. * @param environment_results $result object to update, if relevant.
  2135. * @return environment_results|null updated results or null.
  2136. */
  2137. function check_igbinary322_version(environment_results $result) {
  2138. // No problem if using PHP version 7.3 and up.
  2139. $phpversion = normalize_version(phpversion());
  2140. if (version_compare($phpversion, '7.3', '>=')) {
  2141. return null;
  2142. }
  2143. // No problem if igbinary is not installed..
  2144. if (!function_exists('igbinary_serialize')) {
  2145. return null;
  2146. }
  2147. // No problem if using igbinary < 3.2.2 or > 3.2.4.
  2148. $igbinaryversion = normalize_version(phpversion('igbinary'));
  2149. if (version_compare($igbinaryversion, '3.2.2', '<') or version_compare($igbinaryversion, '3.2.4', '>')) {
  2150. return null;
  2151. }
  2152. // Let's verify the real behaviour to see if the bug is around.
  2153. // Note that we need this extra check because they released 3.2.5 with 3.2.4 version number, so
  2154. // over the paper, there are 3.2.4 working versions (3.2.5 ones with messed reflection version).
  2155. $data = [1, 2, 3];
  2156. $data = igbinary_unserialize(igbinary_serialize($data));
  2157. if (key($data) === 0) {
  2158. return null;
  2159. }
  2160. // Arrived here, we are using PHP 7.2 and a buggy verified igbinary version, let's inform and don't allow to continue.
  2161. $result->setInfo('igbinary version problem');
  2162. $result->setStatus(false);
  2163. return $result;
  2164. }
  2165. /**
  2166. * Assert the upgrade key is provided, if it is defined.
  2167. *
  2168. * The upgrade key can be defined in the main config.php as $CFG->upgradekey. If
  2169. * it is defined there, then its value must be provided every time the site is
  2170. * being upgraded, regardless the administrator is logged in or not.
  2171. *
  2172. * This is supposed to be used at certain places in /admin/index.php only.
  2173. *
  2174. * @param string|null $upgradekeyhash the SHA-1 of the value provided by the user
  2175. */
  2176. function check_upgrade_key($upgradekeyhash) {
  2177. global $CFG, $PAGE;
  2178. if (isset($CFG->config_php_settings['upgradekey'])) {
  2179. if ($upgradekeyhash === null or $upgradekeyhash !== sha1($CFG->config_php_settings['upgradekey'])) {
  2180. if (!$PAGE->headerprinted) {
  2181. $PAGE->set_title(get_string('upgradekeyreq', 'admin'));
  2182. $output = $PAGE->get_renderer('core', 'admin');
  2183. echo $output->upgradekey_form_page(new moodle_url('/admin/index.php', array('cache' => 0)));
  2184. die();
  2185. } else {
  2186. // This should not happen.
  2187. die('Upgrade locked');
  2188. }
  2189. }
  2190. }
  2191. }
  2192. /**
  2193. * Helper procedure/macro for installing remote plugins at admin/index.php
  2194. *
  2195. * Does not return, always redirects or exits.
  2196. *
  2197. * @param array $installable list of \core\update\remote_info
  2198. * @param bool $confirmed false: display the validation screen, true: proceed installation
  2199. * @param string $heading validation screen heading
  2200. * @param moodle_url|string|null $continue URL to proceed with installation at the validation screen
  2201. * @param moodle_url|string|null $return URL to go back on cancelling at the validation screen
  2202. */
  2203. function upgrade_install_plugins(array $installable, $confirmed, $heading='', $continue=null, $return=null) {
  2204. global $CFG, $PAGE;
  2205. if (empty($return)) {
  2206. $return = $PAGE->url;
  2207. }
  2208. if (!empty($CFG->disableupdateautodeploy)) {
  2209. redirect($return);
  2210. }
  2211. if (empty($installable)) {
  2212. redirect($return);
  2213. }
  2214. $pluginman = core_plugin_manager::instance();
  2215. if ($confirmed) {
  2216. // Installation confirmed at the validation results page.
  2217. if (!$pluginman->install_plugins($installable, true, true)) {
  2218. throw new moodle_exception('install_plugins_failed', 'core_plugin', $return);
  2219. }
  2220. // Always redirect to admin/index.php to perform the database upgrade.
  2221. // Do not throw away the existing $PAGE->url parameters such as
  2222. // confirmupgrade or confirmrelease if $PAGE->url is a superset of the
  2223. // URL we must go to.
  2224. $mustgoto = new moodle_url('/admin/index.php', array('cache' => 0, 'confirmplugincheck' => 0));
  2225. if ($mustgoto->compare($PAGE->url, URL_MATCH_PARAMS)) {
  2226. redirect($PAGE->url);
  2227. } else {
  2228. redirect($mustgoto);
  2229. }
  2230. } else {
  2231. $output = $PAGE->get_renderer('core', 'admin');
  2232. echo $output->header();
  2233. if ($heading) {
  2234. echo $output->heading($heading, 3);
  2235. }
  2236. echo html_writer::start_tag('pre', array('class' => 'plugin-install-console'));
  2237. $validated = $pluginman->install_plugins($installable, false, false);
  2238. echo html_writer::end_tag('pre');
  2239. if ($validated) {
  2240. echo $output->plugins_management_confirm_buttons($continue, $return);
  2241. } else {
  2242. echo $output->plugins_management_confirm_buttons(null, $return);
  2243. }
  2244. echo $output->footer();
  2245. die();
  2246. }
  2247. }
  2248. /**
  2249. * Method used to check the installed unoconv version.
  2250. *
  2251. * @param environment_results $result object to update, if relevant.
  2252. * @return environment_results|null updated results or null if unoconv path is not executable.
  2253. */
  2254. function check_unoconv_version(environment_results $result) {
  2255. global $CFG;
  2256. if (!during_initial_install() && !empty($CFG->pathtounoconv) && file_is_executable(trim($CFG->pathtounoconv))) {
  2257. $currentversion = 0;
  2258. $supportedversion = 0.7;
  2259. $unoconvbin = \escapeshellarg($CFG->pathtounoconv);
  2260. $command = "$unoconvbin --version";
  2261. exec($command, $output);
  2262. // If the command execution returned some output, then get the unoconv version.
  2263. if ($output) {
  2264. foreach ($output as $response) {
  2265. if (preg_match('/unoconv (\\d+\\.\\d+)/', $response, $matches)) {
  2266. $currentversion = (float)$matches[1];
  2267. }
  2268. }
  2269. }
  2270. if ($currentversion < $supportedversion) {
  2271. $result->setInfo('unoconv version not supported');
  2272. $result->setStatus(false);
  2273. return $result;
  2274. }
  2275. }
  2276. return null;
  2277. }
  2278. /**
  2279. * Checks for up-to-date TLS libraries. NOTE: this is not currently used, see MDL-57262.
  2280. *
  2281. * @param environment_results $result object to update, if relevant.
  2282. * @return environment_results|null updated results or null if unoconv path is not executable.
  2283. */
  2284. function check_tls_libraries(environment_results $result) {
  2285. global $CFG;
  2286. if (!function_exists('curl_version')) {
  2287. $result->setInfo('cURL PHP extension is not installed');
  2288. $result->setStatus(false);
  2289. return $result;
  2290. }
  2291. if (!\core\upgrade\util::validate_php_curl_tls(curl_version(), PHP_ZTS)) {
  2292. $result->setInfo('invalid ssl/tls configuration');
  2293. $result->setStatus(false);
  2294. return $result;
  2295. }
  2296. if (!\core\upgrade\util::can_use_tls12(curl_version(), php_uname('r'))) {
  2297. $result->setInfo('ssl/tls configuration not supported');
  2298. $result->setStatus(false);
  2299. return $result;
  2300. }
  2301. return null;
  2302. }
  2303. /**
  2304. * Check if recommended version of libcurl is installed or not.
  2305. *
  2306. * @param environment_results $result object to update, if relevant.
  2307. * @return environment_results|null updated results or null.
  2308. */
  2309. function check_libcurl_version(environment_results $result) {
  2310. if (!function_exists('curl_version')) {
  2311. $result->setInfo('cURL PHP extension is not installed');
  2312. $result->setStatus(false);
  2313. return $result;
  2314. }
  2315. // Supported version and version number.
  2316. $supportedversion = 0x071304;
  2317. $supportedversionstring = "7.19.4";
  2318. // Installed version.
  2319. $curlinfo = curl_version();
  2320. $currentversion = $curlinfo['version_number'];
  2321. if ($currentversion < $supportedversion) {
  2322. // Test fail.
  2323. // Set info, we want to let user know how to resolve the problem.
  2324. $result->setInfo('Libcurl version check');
  2325. $result->setNeededVersion($supportedversionstring);
  2326. $result->setCurrentVersion($curlinfo['version']);
  2327. $result->setStatus(false);
  2328. return $result;
  2329. }
  2330. return null;
  2331. }
  2332. /**
  2333. * Environment check for the php setting max_input_vars
  2334. *
  2335. * @param environment_results $result
  2336. * @return environment_results|null
  2337. */
  2338. function check_max_input_vars(environment_results $result) {
  2339. $max = (int)ini_get('max_input_vars');
  2340. if ($max < 5000) {
  2341. $result->setInfo('max_input_vars');
  2342. $result->setStatus(false);
  2343. if (PHP_VERSION_ID >= 80000) {
  2344. // For PHP8 this check is required.
  2345. $result->setLevel('required');
  2346. $result->setFeedbackStr('settingmaxinputvarsrequired');
  2347. } else {
  2348. // For PHP7 this check is optional (recommended).
  2349. $result->setFeedbackStr('settingmaxinputvars');
  2350. }
  2351. return $result;
  2352. }
  2353. return null;
  2354. }
  2355. /**
  2356. * Check whether the admin directory has been configured and warn if so.
  2357. *
  2358. * The admin directory has been deprecated since Moodle 4.0.
  2359. *
  2360. * @param environment_results $result
  2361. * @return null|environment_results
  2362. */
  2363. function check_admin_dir_usage(environment_results $result): ?environment_results {
  2364. global $CFG;
  2365. if (empty($CFG->admin)) {
  2366. return null;
  2367. }
  2368. if ($CFG->admin === 'admin') {
  2369. return null;
  2370. }
  2371. $result->setInfo('admin_dir_usage');
  2372. $result->setStatus(false);
  2373. return $result;
  2374. }