PageRenderTime 621ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/upgradelib.php

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