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