Merge branch 'MDL-46064_master' of git://github.com/markn86/moodle
[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  * @package    core
89  * @subpackage upgrade
90  * @copyright  2009 Petr Skoda {@link http://skodak.org}
91  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
92  */
93 class plugin_defective_exception extends moodle_exception {
94     function __construct($plugin, $details) {
95         global $CFG;
96         parent::__construct('detectedbrokenplugin', 'error', "$CFG->wwwroot/$CFG->admin/index.php", $plugin, $details);
97     }
98 }
100 /**
101  * Misplaced plugin exception.
102  *
103  * Note: this should be used only from the upgrade/admin code.
104  *
105  * @package    core
106  * @subpackage upgrade
107  * @copyright  2009 Petr Skoda {@link http://skodak.org}
108  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
109  */
110 class plugin_misplaced_exception extends moodle_exception {
111     /**
112      * Constructor.
113      * @param string $component the component from version.php
114      * @param string $expected expected directory, null means calculate
115      * @param string $current plugin directory path
116      */
117     public function __construct($component, $expected, $current) {
118         global $CFG;
119         if (empty($expected)) {
120             list($type, $plugin) = core_component::normalize_component($component);
121             $plugintypes = core_component::get_plugin_types();
122             if (isset($plugintypes[$type])) {
123                 $expected = $plugintypes[$type] . '/' . $plugin;
124             }
125         }
126         if (strpos($expected, '$CFG->dirroot') !== 0) {
127             $expected = str_replace($CFG->dirroot, '$CFG->dirroot', $expected);
128         }
129         if (strpos($current, '$CFG->dirroot') !== 0) {
130             $current = str_replace($CFG->dirroot, '$CFG->dirroot', $current);
131         }
132         $a = new stdClass();
133         $a->component = $component;
134         $a->expected  = $expected;
135         $a->current   = $current;
136         parent::__construct('detectedmisplacedplugin', 'core_plugin', "$CFG->wwwroot/$CFG->admin/index.php", $a);
137     }
140 /**
141  * Sets maximum expected time needed for upgrade task.
142  * Please always make sure that upgrade will not run longer!
143  *
144  * The script may be automatically aborted if upgrade times out.
145  *
146  * @category upgrade
147  * @param int $max_execution_time in seconds (can not be less than 60 s)
148  */
149 function upgrade_set_timeout($max_execution_time=300) {
150     global $CFG;
152     if (!isset($CFG->upgraderunning) or $CFG->upgraderunning < time()) {
153         $upgraderunning = get_config(null, 'upgraderunning');
154     } else {
155         $upgraderunning = $CFG->upgraderunning;
156     }
158     if (!$upgraderunning) {
159         if (CLI_SCRIPT) {
160             // never stop CLI upgrades
161             $upgraderunning = 0;
162         } else {
163             // web upgrade not running or aborted
164             print_error('upgradetimedout', 'admin', "$CFG->wwwroot/$CFG->admin/");
165         }
166     }
168     if ($max_execution_time < 60) {
169         // protection against 0 here
170         $max_execution_time = 60;
171     }
173     $expected_end = time() + $max_execution_time;
175     if ($expected_end < $upgraderunning + 10 and $expected_end > $upgraderunning - 10) {
176         // no need to store new end, it is nearly the same ;-)
177         return;
178     }
180     if (CLI_SCRIPT) {
181         // there is no point in timing out of CLI scripts, admins can stop them if necessary
182         core_php_time_limit::raise();
183     } else {
184         core_php_time_limit::raise($max_execution_time);
185     }
186     set_config('upgraderunning', $expected_end); // keep upgrade locked until this time
189 /**
190  * Upgrade savepoint, marks end of each upgrade block.
191  * It stores new main version, resets upgrade timeout
192  * and abort upgrade if user cancels page loading.
193  *
194  * Please do not make large upgrade blocks with lots of operations,
195  * for example when adding tables keep only one table operation per block.
196  *
197  * @category upgrade
198  * @param bool $result false if upgrade step failed, true if completed
199  * @param string or float $version main version
200  * @param bool $allowabort allow user to abort script execution here
201  * @return void
202  */
203 function upgrade_main_savepoint($result, $version, $allowabort=true) {
204     global $CFG;
206     //sanity check to avoid confusion with upgrade_mod_savepoint usage.
207     if (!is_bool($allowabort)) {
208         $errormessage = 'Parameter type mismatch. Are you mixing up upgrade_main_savepoint() and upgrade_mod_savepoint()?';
209         throw new coding_exception($errormessage);
210     }
212     if (!$result) {
213         throw new upgrade_exception(null, $version);
214     }
216     if ($CFG->version >= $version) {
217         // something really wrong is going on in main upgrade script
218         throw new downgrade_exception(null, $CFG->version, $version);
219     }
221     set_config('version', $version);
222     upgrade_log(UPGRADE_LOG_NORMAL, null, 'Upgrade savepoint reached');
224     // reset upgrade timeout to default
225     upgrade_set_timeout();
227     // this is a safe place to stop upgrades if user aborts page loading
228     if ($allowabort and connection_aborted()) {
229         die;
230     }
233 /**
234  * Module upgrade savepoint, marks end of module upgrade blocks
235  * It stores module version, resets upgrade timeout
236  * and abort upgrade if user cancels page loading.
237  *
238  * @category upgrade
239  * @param bool $result false if upgrade step failed, true if completed
240  * @param string or float $version main version
241  * @param string $modname name of module
242  * @param bool $allowabort allow user to abort script execution here
243  * @return void
244  */
245 function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
246     global $DB;
248     $component = 'mod_'.$modname;
250     if (!$result) {
251         throw new upgrade_exception($component, $version);
252     }
254     $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
256     if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
257         print_error('modulenotexist', 'debug', '', $modname);
258     }
260     if ($dbversion >= $version) {
261         // something really wrong is going on in upgrade script
262         throw new downgrade_exception($component, $dbversion, $version);
263     }
264     set_config('version', $version, $component);
266     upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
268     // reset upgrade timeout to default
269     upgrade_set_timeout();
271     // this is a safe place to stop upgrades if user aborts page loading
272     if ($allowabort and connection_aborted()) {
273         die;
274     }
277 /**
278  * Blocks upgrade savepoint, marks end of blocks upgrade blocks
279  * It stores block version, resets upgrade timeout
280  * and abort upgrade if user cancels page loading.
281  *
282  * @category upgrade
283  * @param bool $result false if upgrade step failed, true if completed
284  * @param string or float $version main version
285  * @param string $blockname name of block
286  * @param bool $allowabort allow user to abort script execution here
287  * @return void
288  */
289 function upgrade_block_savepoint($result, $version, $blockname, $allowabort=true) {
290     global $DB;
292     $component = 'block_'.$blockname;
294     if (!$result) {
295         throw new upgrade_exception($component, $version);
296     }
298     $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
300     if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
301         print_error('blocknotexist', 'debug', '', $blockname);
302     }
304     if ($dbversion >= $version) {
305         // something really wrong is going on in upgrade script
306         throw new downgrade_exception($component, $dbversion, $version);
307     }
308     set_config('version', $version, $component);
310     upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
312     // reset upgrade timeout to default
313     upgrade_set_timeout();
315     // this is a safe place to stop upgrades if user aborts page loading
316     if ($allowabort and connection_aborted()) {
317         die;
318     }
321 /**
322  * Plugins upgrade savepoint, marks end of blocks upgrade blocks
323  * It stores plugin version, resets upgrade timeout
324  * and abort upgrade if user cancels page loading.
325  *
326  * @category upgrade
327  * @param bool $result false if upgrade step failed, true if completed
328  * @param string or float $version main version
329  * @param string $type name of plugin
330  * @param string $dir location of plugin
331  * @param bool $allowabort allow user to abort script execution here
332  * @return void
333  */
334 function upgrade_plugin_savepoint($result, $version, $type, $plugin, $allowabort=true) {
335     global $DB;
337     $component = $type.'_'.$plugin;
339     if (!$result) {
340         throw new upgrade_exception($component, $version);
341     }
343     $dbversion = $DB->get_field('config_plugins', 'value', array('plugin'=>$component, 'name'=>'version'));
345     if ($dbversion >= $version) {
346         // Something really wrong is going on in the upgrade script
347         throw new downgrade_exception($component, $dbversion, $version);
348     }
349     set_config('version', $version, $component);
350     upgrade_log(UPGRADE_LOG_NORMAL, $component, 'Upgrade savepoint reached');
352     // Reset upgrade timeout to default
353     upgrade_set_timeout();
355     // This is a safe place to stop upgrades if user aborts page loading
356     if ($allowabort and connection_aborted()) {
357         die;
358     }
361 /**
362  * Detect if there are leftovers in PHP source files.
363  *
364  * During main version upgrades administrators MUST move away
365  * old PHP source files and start from scratch (or better
366  * use git).
367  *
368  * @return bool true means borked upgrade, false means previous PHP files were properly removed
369  */
370 function upgrade_stale_php_files_present() {
371     global $CFG;
373     $someexamplesofremovedfiles = array(
374         // Removed in 2.7.
375         '/admin/tool/qeupgradehelper/version.php',
376         // Removed in 2.6.
377         '/admin/block.php',
378         '/admin/oacleanup.php',
379         // Removed in 2.5.
380         '/backup/lib.php',
381         '/backup/bb/README.txt',
382         '/lib/excel/test.php',
383         // Removed in 2.4.
384         '/admin/tool/unittest/simpletestlib.php',
385         // Removed in 2.3.
386         '/lib/minify/builder/',
387         // Removed in 2.2.
388         '/lib/yui/3.4.1pr1/',
389         // Removed in 2.2.
390         '/search/cron_php5.php',
391         '/course/report/log/indexlive.php',
392         '/admin/report/backups/index.php',
393         '/admin/generator.php',
394         // Removed in 2.1.
395         '/lib/yui/2.8.0r4/',
396         // Removed in 2.0.
397         '/blocks/admin/block_admin.php',
398         '/blocks/admin_tree/block_admin_tree.php',
399     );
401     foreach ($someexamplesofremovedfiles as $file) {
402         if (file_exists($CFG->dirroot.$file)) {
403             return true;
404         }
405     }
407     return false;
410 /**
411  * Upgrade plugins
412  * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
413  * return void
414  */
415 function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
416     global $CFG, $DB;
418 /// special cases
419     if ($type === 'mod') {
420         return upgrade_plugins_modules($startcallback, $endcallback, $verbose);
421     } else if ($type === 'block') {
422         return upgrade_plugins_blocks($startcallback, $endcallback, $verbose);
423     }
425     $plugs = core_component::get_plugin_list($type);
427     foreach ($plugs as $plug=>$fullplug) {
428         // Reset time so that it works when installing a large number of plugins
429         core_php_time_limit::raise(600);
430         $component = clean_param($type.'_'.$plug, PARAM_COMPONENT); // standardised plugin name
432         // check plugin dir is valid name
433         if (empty($component)) {
434             throw new plugin_defective_exception($type.'_'.$plug, 'Invalid plugin directory name.');
435         }
437         if (!is_readable($fullplug.'/version.php')) {
438             continue;
439         }
441         $plugin = new stdClass();
442         $plugin->version = null;
443         $module = $plugin; // Prevent some notices when plugin placed in wrong directory.
444         require($fullplug.'/version.php');  // defines $plugin with version etc
445         unset($module);
447         // if plugin tells us it's full name we may check the location
448         if (isset($plugin->component)) {
449             if ($plugin->component !== $component) {
450                 throw new plugin_misplaced_exception($plugin->component, null, $fullplug);
451             }
452         }
454         if (empty($plugin->version)) {
455             throw new plugin_defective_exception($component, 'Missing version value in version.php');
456         }
458         $plugin->name     = $plug;
459         $plugin->fullname = $component;
461         if (!empty($plugin->requires)) {
462             if ($plugin->requires > $CFG->version) {
463                 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
464             } else if ($plugin->requires < 2010000000) {
465                 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
466             }
467         }
469         // try to recover from interrupted install.php if needed
470         if (file_exists($fullplug.'/db/install.php')) {
471             if (get_config($plugin->fullname, 'installrunning')) {
472                 require_once($fullplug.'/db/install.php');
473                 $recover_install_function = 'xmldb_'.$plugin->fullname.'_install_recovery';
474                 if (function_exists($recover_install_function)) {
475                     $startcallback($component, true, $verbose);
476                     $recover_install_function();
477                     unset_config('installrunning', $plugin->fullname);
478                     update_capabilities($component);
479                     log_update_descriptions($component);
480                     external_update_descriptions($component);
481                     events_update_definition($component);
482                     \core\task\manager::reset_scheduled_tasks_for_component($component);
483                     message_update_providers($component);
484                     \core\message\inbound\manager::update_handlers_for_component($component);
485                     if ($type === 'message') {
486                         message_update_processors($plug);
487                     }
488                     upgrade_plugin_mnet_functions($component);
489                     $endcallback($component, true, $verbose);
490                 }
491             }
492         }
494         $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
495         if (empty($installedversion)) { // new installation
496             $startcallback($component, true, $verbose);
498         /// Install tables if defined
499             if (file_exists($fullplug.'/db/install.xml')) {
500                 $DB->get_manager()->install_from_xmldb_file($fullplug.'/db/install.xml');
501             }
503         /// store version
504             upgrade_plugin_savepoint(true, $plugin->version, $type, $plug, false);
506         /// execute post install file
507             if (file_exists($fullplug.'/db/install.php')) {
508                 require_once($fullplug.'/db/install.php');
509                 set_config('installrunning', 1, $plugin->fullname);
510                 $post_install_function = 'xmldb_'.$plugin->fullname.'_install';
511                 $post_install_function();
512                 unset_config('installrunning', $plugin->fullname);
513             }
515         /// Install various components
516             update_capabilities($component);
517             log_update_descriptions($component);
518             external_update_descriptions($component);
519             events_update_definition($component);
520             \core\task\manager::reset_scheduled_tasks_for_component($component);
521             message_update_providers($component);
522             \core\message\inbound\manager::update_handlers_for_component($component);
523             if ($type === 'message') {
524                 message_update_processors($plug);
525             }
526             upgrade_plugin_mnet_functions($component);
527             $endcallback($component, true, $verbose);
529         } else if ($installedversion < $plugin->version) { // upgrade
530         /// Run the upgrade function for the plugin.
531             $startcallback($component, false, $verbose);
533             if (is_readable($fullplug.'/db/upgrade.php')) {
534                 require_once($fullplug.'/db/upgrade.php');  // defines upgrading function
536                 $newupgrade_function = 'xmldb_'.$plugin->fullname.'_upgrade';
537                 $result = $newupgrade_function($installedversion);
538             } else {
539                 $result = true;
540             }
542             $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
543             if ($installedversion < $plugin->version) {
544                 // store version if not already there
545                 upgrade_plugin_savepoint($result, $plugin->version, $type, $plug, false);
546             }
548         /// Upgrade various components
549             update_capabilities($component);
550             log_update_descriptions($component);
551             external_update_descriptions($component);
552             events_update_definition($component);
553             \core\task\manager::reset_scheduled_tasks_for_component($component);
554             message_update_providers($component);
555             \core\message\inbound\manager::update_handlers_for_component($component);
556             if ($type === 'message') {
557                 // Ugly hack!
558                 message_update_processors($plug);
559             }
560             upgrade_plugin_mnet_functions($component);
561             $endcallback($component, false, $verbose);
563         } else if ($installedversion > $plugin->version) {
564             throw new downgrade_exception($component, $installedversion, $plugin->version);
565         }
566     }
569 /**
570  * Find and check all modules and load them up or upgrade them if necessary
571  *
572  * @global object
573  * @global object
574  */
575 function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
576     global $CFG, $DB;
578     $mods = core_component::get_plugin_list('mod');
580     foreach ($mods as $mod=>$fullmod) {
582         if ($mod === 'NEWMODULE') {   // Someone has unzipped the template, ignore it
583             continue;
584         }
586         $component = clean_param('mod_'.$mod, PARAM_COMPONENT);
588         // check module dir is valid name
589         if (empty($component)) {
590             throw new plugin_defective_exception('mod_'.$mod, 'Invalid plugin directory name.');
591         }
593         if (!is_readable($fullmod.'/version.php')) {
594             throw new plugin_defective_exception($component, 'Missing version.php');
595         }
597         // TODO: Support for $module will end with Moodle 2.10 by MDL-43896. Was deprecated for Moodle 2.7 by MDL-43040.
598         $plugin = new stdClass();
599         $plugin->version = null;
600         $module = $plugin;
601         require($fullmod .'/version.php');  // Defines $plugin with version etc.
602         $plugin = clone($module);
603         unset($module->version);
604         unset($module->component);
605         unset($module->dependencies);
606         unset($module->release);
608         // if plugin tells us it's full name we may check the location
609         if (isset($plugin->component)) {
610             if ($plugin->component !== $component) {
611                 throw new plugin_misplaced_exception($plugin->component, null, $fullmod);
612             }
613         }
615         if (empty($plugin->version)) {
616             // Version must be always set now!
617             throw new plugin_defective_exception($component, 'Missing version value in version.php');
618         }
620         if (!empty($plugin->requires)) {
621             if ($plugin->requires > $CFG->version) {
622                 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
623             } else if ($plugin->requires < 2010000000) {
624                 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
625             }
626         }
628         if (empty($module->cron)) {
629             $module->cron = 0;
630         }
632         // all modules must have en lang pack
633         if (!is_readable("$fullmod/lang/en/$mod.php")) {
634             throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
635         }
637         $module->name = $mod;   // The name MUST match the directory
639         $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
641         if (file_exists($fullmod.'/db/install.php')) {
642             if (get_config($module->name, 'installrunning')) {
643                 require_once($fullmod.'/db/install.php');
644                 $recover_install_function = 'xmldb_'.$module->name.'_install_recovery';
645                 if (function_exists($recover_install_function)) {
646                     $startcallback($component, true, $verbose);
647                     $recover_install_function();
648                     unset_config('installrunning', $module->name);
649                     // Install various components too
650                     update_capabilities($component);
651                     log_update_descriptions($component);
652                     external_update_descriptions($component);
653                     events_update_definition($component);
654                     \core\task\manager::reset_scheduled_tasks_for_component($component);
655                     message_update_providers($component);
656                     \core\message\inbound\manager::update_handlers_for_component($component);
657                     upgrade_plugin_mnet_functions($component);
658                     $endcallback($component, true, $verbose);
659                 }
660             }
661         }
663         if (empty($installedversion)) {
664             $startcallback($component, true, $verbose);
666         /// Execute install.xml (XMLDB) - must be present in all modules
667             $DB->get_manager()->install_from_xmldb_file($fullmod.'/db/install.xml');
669         /// Add record into modules table - may be needed in install.php already
670             $module->id = $DB->insert_record('modules', $module);
671             upgrade_mod_savepoint(true, $plugin->version, $module->name, false);
673         /// Post installation hook - optional
674             if (file_exists("$fullmod/db/install.php")) {
675                 require_once("$fullmod/db/install.php");
676                 // Set installation running flag, we need to recover after exception or error
677                 set_config('installrunning', 1, $module->name);
678                 $post_install_function = 'xmldb_'.$module->name.'_install';
679                 $post_install_function();
680                 unset_config('installrunning', $module->name);
681             }
683         /// Install various components
684             update_capabilities($component);
685             log_update_descriptions($component);
686             external_update_descriptions($component);
687             events_update_definition($component);
688             \core\task\manager::reset_scheduled_tasks_for_component($component);
689             message_update_providers($component);
690             \core\message\inbound\manager::update_handlers_for_component($component);
691             upgrade_plugin_mnet_functions($component);
693             $endcallback($component, true, $verbose);
695         } else if ($installedversion < $plugin->version) {
696         /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
697             $startcallback($component, false, $verbose);
699             if (is_readable($fullmod.'/db/upgrade.php')) {
700                 require_once($fullmod.'/db/upgrade.php');  // defines new upgrading function
701                 $newupgrade_function = 'xmldb_'.$module->name.'_upgrade';
702                 $result = $newupgrade_function($installedversion, $module);
703             } else {
704                 $result = true;
705             }
707             $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
708             $currmodule = $DB->get_record('modules', array('name'=>$module->name));
709             if ($installedversion < $plugin->version) {
710                 // store version if not already there
711                 upgrade_mod_savepoint($result, $plugin->version, $mod, false);
712             }
714             // update cron flag if needed
715             if ($currmodule->cron != $module->cron) {
716                 $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
717             }
719             // Upgrade various components
720             update_capabilities($component);
721             log_update_descriptions($component);
722             external_update_descriptions($component);
723             events_update_definition($component);
724             \core\task\manager::reset_scheduled_tasks_for_component($component);
725             message_update_providers($component);
726             \core\message\inbound\manager::update_handlers_for_component($component);
727             upgrade_plugin_mnet_functions($component);
729             $endcallback($component, false, $verbose);
731         } else if ($installedversion > $plugin->version) {
732             throw new downgrade_exception($component, $installedversion, $plugin->version);
733         }
734     }
738 /**
739  * This function finds all available blocks and install them
740  * into blocks table or do all the upgrade process if newer.
741  *
742  * @global object
743  * @global object
744  */
745 function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
746     global $CFG, $DB;
748     require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
750     $blocktitles   = array(); // we do not want duplicate titles
752     //Is this a first install
753     $first_install = null;
755     $blocks = core_component::get_plugin_list('block');
757     foreach ($blocks as $blockname=>$fullblock) {
759         if (is_null($first_install)) {
760             $first_install = ($DB->count_records('block_instances') == 0);
761         }
763         if ($blockname === 'NEWBLOCK') {   // Someone has unzipped the template, ignore it
764             continue;
765         }
767         $component = clean_param('block_'.$blockname, PARAM_COMPONENT);
769         // check block dir is valid name
770         if (empty($component)) {
771             throw new plugin_defective_exception('block_'.$blockname, 'Invalid plugin directory name.');
772         }
774         if (!is_readable($fullblock.'/version.php')) {
775             throw new plugin_defective_exception('block/'.$blockname, 'Missing version.php file.');
776         }
777         $plugin = new stdClass();
778         $plugin->version = null;
779         $plugin->cron    = 0;
780         $module = $plugin; // Prevent some notices when module placed in wrong directory.
781         include($fullblock.'/version.php');
782         unset($module);
783         $block = clone($plugin);
784         unset($block->version);
785         unset($block->component);
786         unset($block->dependencies);
787         unset($block->release);
789         // if plugin tells us it's full name we may check the location
790         if (isset($plugin->component)) {
791             if ($plugin->component !== $component) {
792                 throw new plugin_misplaced_exception($plugin->component, null, $fullblock);
793             }
794         }
796         if (empty($plugin->version)) {
797             throw new plugin_defective_exception($component, 'Missing block version.');
798         }
800         if (!empty($plugin->requires)) {
801             if ($plugin->requires > $CFG->version) {
802                 throw new upgrade_requires_exception($component, $plugin->version, $CFG->version, $plugin->requires);
803             } else if ($plugin->requires < 2010000000) {
804                 throw new plugin_defective_exception($component, 'Plugin is not compatible with Moodle 2.x or later.');
805             }
806         }
808         if (!is_readable($fullblock.'/block_'.$blockname.'.php')) {
809             throw new plugin_defective_exception('block/'.$blockname, 'Missing main block class file.');
810         }
811         include_once($fullblock.'/block_'.$blockname.'.php');
813         $classname = 'block_'.$blockname;
815         if (!class_exists($classname)) {
816             throw new plugin_defective_exception($component, 'Can not load main class.');
817         }
819         $blockobj    = new $classname;   // This is what we'll be testing
820         $blocktitle  = $blockobj->get_title();
822         // OK, it's as we all hoped. For further tests, the object will do them itself.
823         if (!$blockobj->_self_test()) {
824             throw new plugin_defective_exception($component, 'Self test failed.');
825         }
827         $block->name     = $blockname;   // The name MUST match the directory
829         $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
831         if (file_exists($fullblock.'/db/install.php')) {
832             if (get_config('block_'.$blockname, 'installrunning')) {
833                 require_once($fullblock.'/db/install.php');
834                 $recover_install_function = 'xmldb_block_'.$blockname.'_install_recovery';
835                 if (function_exists($recover_install_function)) {
836                     $startcallback($component, true, $verbose);
837                     $recover_install_function();
838                     unset_config('installrunning', 'block_'.$blockname);
839                     // Install various components
840                     update_capabilities($component);
841                     log_update_descriptions($component);
842                     external_update_descriptions($component);
843                     events_update_definition($component);
844                     \core\task\manager::reset_scheduled_tasks_for_component($component);
845                     message_update_providers($component);
846                     \core\message\inbound\manager::update_handlers_for_component($component);
847                     upgrade_plugin_mnet_functions($component);
848                     $endcallback($component, true, $verbose);
849                 }
850             }
851         }
853         if (empty($installedversion)) { // block not installed yet, so install it
854             $conflictblock = array_search($blocktitle, $blocktitles);
855             if ($conflictblock !== false) {
856                 // Duplicate block titles are not allowed, they confuse people
857                 // AND PHP's associative arrays ;)
858                 throw new plugin_defective_exception($component, get_string('blocknameconflict', 'error', (object)array('name'=>$block->name, 'conflict'=>$conflictblock)));
859             }
860             $startcallback($component, true, $verbose);
862             if (file_exists($fullblock.'/db/install.xml')) {
863                 $DB->get_manager()->install_from_xmldb_file($fullblock.'/db/install.xml');
864             }
865             $block->id = $DB->insert_record('block', $block);
866             upgrade_block_savepoint(true, $plugin->version, $block->name, false);
868             if (file_exists($fullblock.'/db/install.php')) {
869                 require_once($fullblock.'/db/install.php');
870                 // Set installation running flag, we need to recover after exception or error
871                 set_config('installrunning', 1, 'block_'.$blockname);
872                 $post_install_function = 'xmldb_block_'.$blockname.'_install';
873                 $post_install_function();
874                 unset_config('installrunning', 'block_'.$blockname);
875             }
877             $blocktitles[$block->name] = $blocktitle;
879             // Install various components
880             update_capabilities($component);
881             log_update_descriptions($component);
882             external_update_descriptions($component);
883             events_update_definition($component);
884             \core\task\manager::reset_scheduled_tasks_for_component($component);
885             message_update_providers($component);
886             \core\message\inbound\manager::update_handlers_for_component($component);
887             upgrade_plugin_mnet_functions($component);
889             $endcallback($component, true, $verbose);
891         } else if ($installedversion < $plugin->version) {
892             $startcallback($component, false, $verbose);
894             if (is_readable($fullblock.'/db/upgrade.php')) {
895                 require_once($fullblock.'/db/upgrade.php');  // defines new upgrading function
896                 $newupgrade_function = 'xmldb_block_'.$blockname.'_upgrade';
897                 $result = $newupgrade_function($installedversion, $block);
898             } else {
899                 $result = true;
900             }
902             $installedversion = $DB->get_field('config_plugins', 'value', array('name'=>'version', 'plugin'=>$component)); // No caching!
903             $currblock = $DB->get_record('block', array('name'=>$block->name));
904             if ($installedversion < $plugin->version) {
905                 // store version if not already there
906                 upgrade_block_savepoint($result, $plugin->version, $block->name, false);
907             }
909             if ($currblock->cron != $block->cron) {
910                 // update cron flag if needed
911                 $DB->set_field('block', 'cron', $block->cron, array('id' => $currblock->id));
912             }
914             // Upgrade various components
915             update_capabilities($component);
916             log_update_descriptions($component);
917             external_update_descriptions($component);
918             events_update_definition($component);
919             \core\task\manager::reset_scheduled_tasks_for_component($component);
920             message_update_providers($component);
921             \core\message\inbound\manager::update_handlers_for_component($component);
922             upgrade_plugin_mnet_functions($component);
924             $endcallback($component, false, $verbose);
926         } else if ($installedversion > $plugin->version) {
927             throw new downgrade_exception($component, $installedversion, $plugin->version);
928         }
929     }
932     // Finally, if we are in the first_install of BLOCKS setup frontpage and admin page blocks
933     if ($first_install) {
934         //Iterate over each course - there should be only site course here now
935         if ($courses = $DB->get_records('course')) {
936             foreach ($courses as $course) {
937                 blocks_add_default_course_blocks($course);
938             }
939         }
941         blocks_add_default_system_blocks();
942     }
946 /**
947  * Log_display description function used during install and upgrade.
948  *
949  * @param string $component name of component (moodle, mod_assignment, etc.)
950  * @return void
951  */
952 function log_update_descriptions($component) {
953     global $DB;
955     $defpath = core_component::get_component_directory($component).'/db/log.php';
957     if (!file_exists($defpath)) {
958         $DB->delete_records('log_display', array('component'=>$component));
959         return;
960     }
962     // load new info
963     $logs = array();
964     include($defpath);
965     $newlogs = array();
966     foreach ($logs as $log) {
967         $newlogs[$log['module'].'-'.$log['action']] = $log; // kind of unique name
968     }
969     unset($logs);
970     $logs = $newlogs;
972     $fields = array('module', 'action', 'mtable', 'field');
973     // update all log fist
974     $dblogs = $DB->get_records('log_display', array('component'=>$component));
975     foreach ($dblogs as $dblog) {
976         $name = $dblog->module.'-'.$dblog->action;
978         if (empty($logs[$name])) {
979             $DB->delete_records('log_display', array('id'=>$dblog->id));
980             continue;
981         }
983         $log = $logs[$name];
984         unset($logs[$name]);
986         $update = false;
987         foreach ($fields as $field) {
988             if ($dblog->$field != $log[$field]) {
989                 $dblog->$field = $log[$field];
990                 $update = true;
991             }
992         }
993         if ($update) {
994             $DB->update_record('log_display', $dblog);
995         }
996     }
997     foreach ($logs as $log) {
998         $dblog = (object)$log;
999         $dblog->component = $component;
1000         $DB->insert_record('log_display', $dblog);
1001     }
1004 /**
1005  * Web service discovery function used during install and upgrade.
1006  * @param string $component name of component (moodle, mod_assignment, etc.)
1007  * @return void
1008  */
1009 function external_update_descriptions($component) {
1010     global $DB, $CFG;
1012     $defpath = core_component::get_component_directory($component).'/db/services.php';
1014     if (!file_exists($defpath)) {
1015         require_once($CFG->dirroot.'/lib/externallib.php');
1016         external_delete_descriptions($component);
1017         return;
1018     }
1020     // load new info
1021     $functions = array();
1022     $services = array();
1023     include($defpath);
1025     // update all function fist
1026     $dbfunctions = $DB->get_records('external_functions', array('component'=>$component));
1027     foreach ($dbfunctions as $dbfunction) {
1028         if (empty($functions[$dbfunction->name])) {
1029             $DB->delete_records('external_functions', array('id'=>$dbfunction->id));
1030             // do not delete functions from external_services_functions, beacuse
1031             // we want to notify admins when functions used in custom services disappear
1033             //TODO: this looks wrong, we have to delete it eventually (skodak)
1034             continue;
1035         }
1037         $function = $functions[$dbfunction->name];
1038         unset($functions[$dbfunction->name]);
1039         $function['classpath'] = empty($function['classpath']) ? null : $function['classpath'];
1041         $update = false;
1042         if ($dbfunction->classname != $function['classname']) {
1043             $dbfunction->classname = $function['classname'];
1044             $update = true;
1045         }
1046         if ($dbfunction->methodname != $function['methodname']) {
1047             $dbfunction->methodname = $function['methodname'];
1048             $update = true;
1049         }
1050         if ($dbfunction->classpath != $function['classpath']) {
1051             $dbfunction->classpath = $function['classpath'];
1052             $update = true;
1053         }
1054         $functioncapabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
1055         if ($dbfunction->capabilities != $functioncapabilities) {
1056             $dbfunction->capabilities = $functioncapabilities;
1057             $update = true;
1058         }
1059         if ($update) {
1060             $DB->update_record('external_functions', $dbfunction);
1061         }
1062     }
1063     foreach ($functions as $fname => $function) {
1064         $dbfunction = new stdClass();
1065         $dbfunction->name       = $fname;
1066         $dbfunction->classname  = $function['classname'];
1067         $dbfunction->methodname = $function['methodname'];
1068         $dbfunction->classpath  = empty($function['classpath']) ? null : $function['classpath'];
1069         $dbfunction->component  = $component;
1070         $dbfunction->capabilities = array_key_exists('capabilities', $function)?$function['capabilities']:'';
1071         $dbfunction->id = $DB->insert_record('external_functions', $dbfunction);
1072     }
1073     unset($functions);
1075     // now deal with services
1076     $dbservices = $DB->get_records('external_services', array('component'=>$component));
1077     foreach ($dbservices as $dbservice) {
1078         if (empty($services[$dbservice->name])) {
1079             $DB->delete_records('external_tokens', array('externalserviceid'=>$dbservice->id));
1080             $DB->delete_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1081             $DB->delete_records('external_services_users', array('externalserviceid'=>$dbservice->id));
1082             $DB->delete_records('external_services', array('id'=>$dbservice->id));
1083             continue;
1084         }
1085         $service = $services[$dbservice->name];
1086         unset($services[$dbservice->name]);
1087         $service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled'];
1088         $service['requiredcapability'] = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1089         $service['restrictedusers'] = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1090         $service['downloadfiles'] = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1091         $service['uploadfiles'] = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
1092         $service['shortname'] = !isset($service['shortname']) ? null : $service['shortname'];
1094         $update = false;
1095         if ($dbservice->requiredcapability != $service['requiredcapability']) {
1096             $dbservice->requiredcapability = $service['requiredcapability'];
1097             $update = true;
1098         }
1099         if ($dbservice->restrictedusers != $service['restrictedusers']) {
1100             $dbservice->restrictedusers = $service['restrictedusers'];
1101             $update = true;
1102         }
1103         if ($dbservice->downloadfiles != $service['downloadfiles']) {
1104             $dbservice->downloadfiles = $service['downloadfiles'];
1105             $update = true;
1106         }
1107         if ($dbservice->uploadfiles != $service['uploadfiles']) {
1108             $dbservice->uploadfiles = $service['uploadfiles'];
1109             $update = true;
1110         }
1111         //if shortname is not a PARAM_ALPHANUMEXT, fail (tested here for service update and creation)
1112         if (isset($service['shortname']) and
1113                 (clean_param($service['shortname'], PARAM_ALPHANUMEXT) != $service['shortname'])) {
1114             throw new moodle_exception('installserviceshortnameerror', 'webservice', '', $service['shortname']);
1115         }
1116         if ($dbservice->shortname != $service['shortname']) {
1117             //check that shortname is unique
1118             if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1119                 $existingservice = $DB->get_record('external_services',
1120                         array('shortname' => $service['shortname']));
1121                 if (!empty($existingservice)) {
1122                     throw new moodle_exception('installexistingserviceshortnameerror', 'webservice', '', $service['shortname']);
1123                 }
1124             }
1125             $dbservice->shortname = $service['shortname'];
1126             $update = true;
1127         }
1128         if ($update) {
1129             $DB->update_record('external_services', $dbservice);
1130         }
1132         $functions = $DB->get_records('external_services_functions', array('externalserviceid'=>$dbservice->id));
1133         foreach ($functions as $function) {
1134             $key = array_search($function->functionname, $service['functions']);
1135             if ($key === false) {
1136                 $DB->delete_records('external_services_functions', array('id'=>$function->id));
1137             } else {
1138                 unset($service['functions'][$key]);
1139             }
1140         }
1141         foreach ($service['functions'] as $fname) {
1142             $newf = new stdClass();
1143             $newf->externalserviceid = $dbservice->id;
1144             $newf->functionname      = $fname;
1145             $DB->insert_record('external_services_functions', $newf);
1146         }
1147         unset($functions);
1148     }
1149     foreach ($services as $name => $service) {
1150         //check that shortname is unique
1151         if (isset($service['shortname'])) { //we currently accepts multiple shortname == null
1152             $existingservice = $DB->get_record('external_services',
1153                     array('shortname' => $service['shortname']));
1154             if (!empty($existingservice)) {
1155                 throw new moodle_exception('installserviceshortnameerror', 'webservice');
1156             }
1157         }
1159         $dbservice = new stdClass();
1160         $dbservice->name               = $name;
1161         $dbservice->enabled            = empty($service['enabled']) ? 0 : $service['enabled'];
1162         $dbservice->requiredcapability = empty($service['requiredcapability']) ? null : $service['requiredcapability'];
1163         $dbservice->restrictedusers    = !isset($service['restrictedusers']) ? 1 : $service['restrictedusers'];
1164         $dbservice->downloadfiles      = !isset($service['downloadfiles']) ? 0 : $service['downloadfiles'];
1165         $dbservice->uploadfiles        = !isset($service['uploadfiles']) ? 0 : $service['uploadfiles'];
1166         $dbservice->shortname          = !isset($service['shortname']) ? null : $service['shortname'];
1167         $dbservice->component          = $component;
1168         $dbservice->timecreated        = time();
1169         $dbservice->id = $DB->insert_record('external_services', $dbservice);
1170         foreach ($service['functions'] as $fname) {
1171             $newf = new stdClass();
1172             $newf->externalserviceid = $dbservice->id;
1173             $newf->functionname      = $fname;
1174             $DB->insert_record('external_services_functions', $newf);
1175         }
1176     }
1179 /**
1180  * upgrade logging functions
1181  */
1182 function upgrade_handle_exception($ex, $plugin = null) {
1183     global $CFG;
1185     // rollback everything, we need to log all upgrade problems
1186     abort_all_db_transactions();
1188     $info = get_exception_info($ex);
1190     // First log upgrade error
1191     upgrade_log(UPGRADE_LOG_ERROR, $plugin, 'Exception: ' . get_class($ex), $info->message, $info->backtrace);
1193     // Always turn on debugging - admins need to know what is going on
1194     set_debugging(DEBUG_DEVELOPER, true);
1196     default_exception_handler($ex, true, $plugin);
1199 /**
1200  * Adds log entry into upgrade_log table
1201  *
1202  * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR
1203  * @param string $plugin frankenstyle component name
1204  * @param string $info short description text of log entry
1205  * @param string $details long problem description
1206  * @param string $backtrace string used for errors only
1207  * @return void
1208  */
1209 function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
1210     global $DB, $USER, $CFG;
1212     if (empty($plugin)) {
1213         $plugin = 'core';
1214     }
1216     list($plugintype, $pluginname) = core_component::normalize_component($plugin);
1217     $component = is_null($pluginname) ? $plugintype : $plugintype . '_' . $pluginname;
1219     $backtrace = format_backtrace($backtrace, true);
1221     $currentversion = null;
1222     $targetversion  = null;
1224     //first try to find out current version number
1225     if ($plugintype === 'core') {
1226         //main
1227         $currentversion = $CFG->version;
1229         $version = null;
1230         include("$CFG->dirroot/version.php");
1231         $targetversion = $version;
1233     } else {
1234         $pluginversion = get_config($component, 'version');
1235         if (!empty($pluginversion)) {
1236             $currentversion = $pluginversion;
1237         }
1238         $cd = core_component::get_component_directory($component);
1239         if (file_exists("$cd/version.php")) {
1240             $plugin = new stdClass();
1241             $plugin->version = null;
1242             $module = $plugin;
1243             include("$cd/version.php");
1244             $targetversion = $plugin->version;
1245         }
1246     }
1248     $log = new stdClass();
1249     $log->type          = $type;
1250     $log->plugin        = $component;
1251     $log->version       = $currentversion;
1252     $log->targetversion = $targetversion;
1253     $log->info          = $info;
1254     $log->details       = $details;
1255     $log->backtrace     = $backtrace;
1256     $log->userid        = $USER->id;
1257     $log->timemodified  = time();
1258     try {
1259         $DB->insert_record('upgrade_log', $log);
1260     } catch (Exception $ignored) {
1261         // possible during install or 2.0 upgrade
1262     }
1265 /**
1266  * Marks start of upgrade, blocks any other access to site.
1267  * The upgrade is finished at the end of script or after timeout.
1268  *
1269  * @global object
1270  * @global object
1271  * @global object
1272  */
1273 function upgrade_started($preinstall=false) {
1274     global $CFG, $DB, $PAGE, $OUTPUT;
1276     static $started = false;
1278     if ($preinstall) {
1279         ignore_user_abort(true);
1280         upgrade_setup_debug(true);
1282     } else if ($started) {
1283         upgrade_set_timeout(120);
1285     } else {
1286         if (!CLI_SCRIPT and !$PAGE->headerprinted) {
1287             $strupgrade  = get_string('upgradingversion', 'admin');
1288             $PAGE->set_pagelayout('maintenance');
1289             upgrade_init_javascript();
1290             $PAGE->set_title($strupgrade.' - Moodle '.$CFG->target_release);
1291             $PAGE->set_heading($strupgrade);
1292             $PAGE->navbar->add($strupgrade);
1293             $PAGE->set_cacheable(false);
1294             echo $OUTPUT->header();
1295         }
1297         ignore_user_abort(true);
1298         core_shutdown_manager::register_function('upgrade_finished_handler');
1299         upgrade_setup_debug(true);
1300         set_config('upgraderunning', time()+300);
1301         $started = true;
1302     }
1305 /**
1306  * Internal function - executed if upgrade interrupted.
1307  */
1308 function upgrade_finished_handler() {
1309     upgrade_finished();
1312 /**
1313  * Indicates upgrade is finished.
1314  *
1315  * This function may be called repeatedly.
1316  *
1317  * @global object
1318  * @global object
1319  */
1320 function upgrade_finished($continueurl=null) {
1321     global $CFG, $DB, $OUTPUT;
1323     if (!empty($CFG->upgraderunning)) {
1324         unset_config('upgraderunning');
1325         // We have to forcefully purge the caches using the writer here.
1326         // This has to be done after we unset the config var. If someone hits the site while this is set they will
1327         // cause the config values to propogate to the caches.
1328         // Caches are purged after the last step in an upgrade but there is several code routines that exceute between
1329         // then and now that leaving a window for things to fall out of sync.
1330         cache_helper::purge_all(true);
1331         upgrade_setup_debug(false);
1332         ignore_user_abort(false);
1333         if ($continueurl) {
1334             echo $OUTPUT->continue_button($continueurl);
1335             echo $OUTPUT->footer();
1336             die;
1337         }
1338     }
1341 /**
1342  * @global object
1343  * @global object
1344  */
1345 function upgrade_setup_debug($starting) {
1346     global $CFG, $DB;
1348     static $originaldebug = null;
1350     if ($starting) {
1351         if ($originaldebug === null) {
1352             $originaldebug = $DB->get_debug();
1353         }
1354         if (!empty($CFG->upgradeshowsql)) {
1355             $DB->set_debug(true);
1356         }
1357     } else {
1358         $DB->set_debug($originaldebug);
1359     }
1362 function print_upgrade_separator() {
1363     if (!CLI_SCRIPT) {
1364         echo '<hr />';
1365     }
1368 /**
1369  * Default start upgrade callback
1370  * @param string $plugin
1371  * @param bool $installation true if installation, false means upgrade
1372  */
1373 function print_upgrade_part_start($plugin, $installation, $verbose) {
1374     global $OUTPUT;
1375     if (empty($plugin) or $plugin == 'moodle') {
1376         upgrade_started($installation); // does not store upgrade running flag yet
1377         if ($verbose) {
1378             echo $OUTPUT->heading(get_string('coresystem'));
1379         }
1380     } else {
1381         upgrade_started();
1382         if ($verbose) {
1383             echo $OUTPUT->heading($plugin);
1384         }
1385     }
1386     if ($installation) {
1387         if (empty($plugin) or $plugin == 'moodle') {
1388             // no need to log - log table not yet there ;-)
1389         } else {
1390             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin installation');
1391         }
1392     } else {
1393         if (empty($plugin) or $plugin == 'moodle') {
1394             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting core upgrade');
1395         } else {
1396             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Starting plugin upgrade');
1397         }
1398     }
1401 /**
1402  * Default end upgrade callback
1403  * @param string $plugin
1404  * @param bool $installation true if installation, false means upgrade
1405  */
1406 function print_upgrade_part_end($plugin, $installation, $verbose) {
1407     global $OUTPUT;
1408     upgrade_started();
1409     if ($installation) {
1410         if (empty($plugin) or $plugin == 'moodle') {
1411             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core installed');
1412         } else {
1413             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin installed');
1414         }
1415     } else {
1416         if (empty($plugin) or $plugin == 'moodle') {
1417             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Core upgraded');
1418         } else {
1419             upgrade_log(UPGRADE_LOG_NORMAL, $plugin, 'Plugin upgraded');
1420         }
1421     }
1422     if ($verbose) {
1423         echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
1424         print_upgrade_separator();
1425     }
1428 /**
1429  * Sets up JS code required for all upgrade scripts.
1430  * @global object
1431  */
1432 function upgrade_init_javascript() {
1433     global $PAGE;
1434     // scroll to the end of each upgrade page so that ppl see either error or continue button,
1435     // no need to scroll continuously any more, it is enough to jump to end once the footer is printed ;-)
1436     $js = "window.scrollTo(0, 5000000);";
1437     $PAGE->requires->js_init_code($js);
1440 /**
1441  * Try to upgrade the given language pack (or current language)
1442  *
1443  * @param string $lang the code of the language to update, defaults to the current language
1444  */
1445 function upgrade_language_pack($lang = null) {
1446     global $CFG;
1448     if (!empty($CFG->skiplangupgrade)) {
1449         return;
1450     }
1452     if (!file_exists("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php")) {
1453         // weird, somebody uninstalled the import utility
1454         return;
1455     }
1457     if (!$lang) {
1458         $lang = current_language();
1459     }
1461     if (!get_string_manager()->translation_exists($lang)) {
1462         return;
1463     }
1465     get_string_manager()->reset_caches();
1467     if ($lang === 'en') {
1468         return;  // Nothing to do
1469     }
1471     upgrade_started(false);
1473     require_once("$CFG->dirroot/$CFG->admin/tool/langimport/lib.php");
1474     tool_langimport_preupgrade_update($lang);
1476     get_string_manager()->reset_caches();
1478     print_upgrade_separator();
1481 /**
1482  * Install core moodle tables and initialize
1483  * @param float $version target version
1484  * @param bool $verbose
1485  * @return void, may throw exception
1486  */
1487 function install_core($version, $verbose) {
1488     global $CFG, $DB;
1490     // We can not call purge_all_caches() yet, make sure the temp and cache dirs exist and are empty.
1491     remove_dir($CFG->cachedir.'', true);
1492     make_cache_directory('', true);
1494     remove_dir($CFG->localcachedir.'', true);
1495     make_localcache_directory('', true);
1497     remove_dir($CFG->tempdir.'', true);
1498     make_temp_directory('', true);
1500     remove_dir($CFG->dataroot.'/muc', true);
1501     make_writable_directory($CFG->dataroot.'/muc', true);
1503     try {
1504         core_php_time_limit::raise(600);
1505         print_upgrade_part_start('moodle', true, $verbose); // does not store upgrade running flag
1507         $DB->get_manager()->install_from_xmldb_file("$CFG->libdir/db/install.xml");
1508         upgrade_started();     // we want the flag to be stored in config table ;-)
1510         // set all core default records and default settings
1511         require_once("$CFG->libdir/db/install.php");
1512         xmldb_main_install(); // installs the capabilities too
1514         // store version
1515         upgrade_main_savepoint(true, $version, false);
1517         // Continue with the installation
1518         log_update_descriptions('moodle');
1519         external_update_descriptions('moodle');
1520         events_update_definition('moodle');
1521         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
1522         message_update_providers('moodle');
1523         \core\message\inbound\manager::update_handlers_for_component('moodle');
1525         // Write default settings unconditionally
1526         admin_apply_default_settings(NULL, true);
1528         print_upgrade_part_end(null, true, $verbose);
1530         // Purge all caches. They're disabled but this ensures that we don't have any persistent data just in case something
1531         // during installation didn't use APIs.
1532         cache_helper::purge_all();
1533     } catch (exception $ex) {
1534         upgrade_handle_exception($ex);
1535     }
1538 /**
1539  * Upgrade moodle core
1540  * @param float $version target version
1541  * @param bool $verbose
1542  * @return void, may throw exception
1543  */
1544 function upgrade_core($version, $verbose) {
1545     global $CFG, $SITE, $DB, $COURSE;
1547     raise_memory_limit(MEMORY_EXTRA);
1549     require_once($CFG->libdir.'/db/upgrade.php');    // Defines upgrades
1551     try {
1552         // Reset caches before any output.
1553         cache_helper::purge_all(true);
1554         purge_all_caches();
1556         // Upgrade current language pack if we can
1557         upgrade_language_pack();
1559         print_upgrade_part_start('moodle', false, $verbose);
1561         // Pre-upgrade scripts for local hack workarounds.
1562         $preupgradefile = "$CFG->dirroot/local/preupgrade.php";
1563         if (file_exists($preupgradefile)) {
1564             core_php_time_limit::raise();
1565             require($preupgradefile);
1566             // Reset upgrade timeout to default.
1567             upgrade_set_timeout();
1568         }
1570         $result = xmldb_main_upgrade($CFG->version);
1571         if ($version > $CFG->version) {
1572             // store version if not already there
1573             upgrade_main_savepoint($result, $version, false);
1574         }
1576         // In case structure of 'course' table has been changed and we forgot to update $SITE, re-read it from db.
1577         $SITE = $DB->get_record('course', array('id' => $SITE->id));
1578         $COURSE = clone($SITE);
1580         // perform all other component upgrade routines
1581         update_capabilities('moodle');
1582         log_update_descriptions('moodle');
1583         external_update_descriptions('moodle');
1584         events_update_definition('moodle');
1585         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
1586         message_update_providers('moodle');
1587         \core\message\inbound\manager::update_handlers_for_component('moodle');
1588         // Update core definitions.
1589         cache_helper::update_definitions(true);
1591         // Purge caches again, just to be sure we arn't holding onto old stuff now.
1592         cache_helper::purge_all(true);
1593         purge_all_caches();
1595         // Clean up contexts - more and more stuff depends on existence of paths and contexts
1596         context_helper::cleanup_instances();
1597         context_helper::create_instances(null, false);
1598         context_helper::build_all_paths(false);
1599         $syscontext = context_system::instance();
1600         $syscontext->mark_dirty();
1602         print_upgrade_part_end('moodle', false, $verbose);
1603     } catch (Exception $ex) {
1604         upgrade_handle_exception($ex);
1605     }
1608 /**
1609  * Upgrade/install other parts of moodle
1610  * @param bool $verbose
1611  * @return void, may throw exception
1612  */
1613 function upgrade_noncore($verbose) {
1614     global $CFG;
1616     raise_memory_limit(MEMORY_EXTRA);
1618     // upgrade all plugins types
1619     try {
1620         // Reset caches before any output.
1621         cache_helper::purge_all(true);
1622         purge_all_caches();
1624         $plugintypes = core_component::get_plugin_types();
1625         foreach ($plugintypes as $type=>$location) {
1626             upgrade_plugins($type, 'print_upgrade_part_start', 'print_upgrade_part_end', $verbose);
1627         }
1628         // Update cache definitions. Involves scanning each plugin for any changes.
1629         cache_helper::update_definitions();
1630         // Mark the site as upgraded.
1631         set_config('allversionshash', core_component::get_all_versions_hash());
1633         // Purge caches again, just to be sure we arn't holding onto old stuff now.
1634         cache_helper::purge_all(true);
1635         purge_all_caches();
1637     } catch (Exception $ex) {
1638         upgrade_handle_exception($ex);
1639     }
1642 /**
1643  * Checks if the main tables have been installed yet or not.
1644  *
1645  * Note: we can not use caches here because they might be stale,
1646  *       use with care!
1647  *
1648  * @return bool
1649  */
1650 function core_tables_exist() {
1651     global $DB;
1653     if (!$tables = $DB->get_tables(false) ) {    // No tables yet at all.
1654         return false;
1656     } else {                                 // Check for missing main tables
1657         $mtables = array('config', 'course', 'groupings'); // some tables used in 1.9 and 2.0, preferable something from the start and end of install.xml
1658         foreach ($mtables as $mtable) {
1659             if (!in_array($mtable, $tables)) {
1660                 return false;
1661             }
1662         }
1663         return true;
1664     }
1667 /**
1668  * upgrades the mnet rpc definitions for the given component.
1669  * this method doesn't return status, an exception will be thrown in the case of an error
1670  *
1671  * @param string $component the plugin to upgrade, eg auth_mnet
1672  */
1673 function upgrade_plugin_mnet_functions($component) {
1674     global $DB, $CFG;
1676     list($type, $plugin) = core_component::normalize_component($component);
1677     $path = core_component::get_plugin_directory($type, $plugin);
1679     $publishes = array();
1680     $subscribes = array();
1681     if (file_exists($path . '/db/mnet.php')) {
1682         require_once($path . '/db/mnet.php'); // $publishes comes from this file
1683     }
1684     if (empty($publishes)) {
1685         $publishes = array(); // still need this to be able to disable stuff later
1686     }
1687     if (empty($subscribes)) {
1688         $subscribes = array(); // still need this to be able to disable stuff later
1689     }
1691     static $servicecache = array();
1693     // rekey an array based on the rpc method for easy lookups later
1694     $publishmethodservices = array();
1695     $subscribemethodservices = array();
1696     foreach($publishes as $servicename => $service) {
1697         if (is_array($service['methods'])) {
1698             foreach($service['methods'] as $methodname) {
1699                 $service['servicename'] = $servicename;
1700                 $publishmethodservices[$methodname][] = $service;
1701             }
1702         }
1703     }
1705     // Disable functions that don't exist (any more) in the source
1706     // Should these be deleted? What about their permissions records?
1707     foreach ($DB->get_records('mnet_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1708         if (!array_key_exists($rpc->functionname, $publishmethodservices) && $rpc->enabled) {
1709             $DB->set_field('mnet_rpc', 'enabled', 0, array('id' => $rpc->id));
1710         } else if (array_key_exists($rpc->functionname, $publishmethodservices) && !$rpc->enabled) {
1711             $DB->set_field('mnet_rpc', 'enabled', 1, array('id' => $rpc->id));
1712         }
1713     }
1715     // reflect all the services we're publishing and save them
1716     require_once($CFG->dirroot . '/lib/zend/Zend/Server/Reflection.php');
1717     static $cachedclasses = array(); // to store reflection information in
1718     foreach ($publishes as $service => $data) {
1719         $f = $data['filename'];
1720         $c = $data['classname'];
1721         foreach ($data['methods'] as $method) {
1722             $dataobject = new stdClass();
1723             $dataobject->plugintype  = $type;
1724             $dataobject->pluginname  = $plugin;
1725             $dataobject->enabled     = 1;
1726             $dataobject->classname   = $c;
1727             $dataobject->filename    = $f;
1729             if (is_string($method)) {
1730                 $dataobject->functionname = $method;
1732             } else if (is_array($method)) { // wants to override file or class
1733                 $dataobject->functionname = $method['method'];
1734                 $dataobject->classname     = $method['classname'];
1735                 $dataobject->filename      = $method['filename'];
1736             }
1737             $dataobject->xmlrpcpath = $type.'/'.$plugin.'/'.$dataobject->filename.'/'.$method;
1738             $dataobject->static = false;
1740             require_once($path . '/' . $dataobject->filename);
1741             $functionreflect = null; // slightly different ways to get this depending on whether it's a class method or a function
1742             if (!empty($dataobject->classname)) {
1743                 if (!class_exists($dataobject->classname)) {
1744                     throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1745                 }
1746                 $key = $dataobject->filename . '|' . $dataobject->classname;
1747                 if (!array_key_exists($key, $cachedclasses)) { // look to see if we've already got a reflection object
1748                     try {
1749                         $cachedclasses[$key] = Zend_Server_Reflection::reflectClass($dataobject->classname);
1750                     } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful
1751                         throw new moodle_exception('installreflectionclasserror', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname, 'error' => $e->getMessage()));
1752                     }
1753                 }
1754                 $r =& $cachedclasses[$key];
1755                 if (!$r->hasMethod($dataobject->functionname)) {
1756                     throw new moodle_exception('installnosuchmethod', 'mnet', '', (object)array('method' => $dataobject->functionname, 'class' => $dataobject->classname));
1757                 }
1758                 // stupid workaround for zend not having a getMethod($name) function
1759                 $ms = $r->getMethods();
1760                 foreach ($ms as $m) {
1761                     if ($m->getName() == $dataobject->functionname) {
1762                         $functionreflect = $m;
1763                         break;
1764                     }
1765                 }
1766                 $dataobject->static = (int)$functionreflect->isStatic();
1767             } else {
1768                 if (!function_exists($dataobject->functionname)) {
1769                     throw new moodle_exception('installnosuchfunction', 'mnet', '', (object)array('method' => $dataobject->functionname, 'file' => $dataobject->filename));
1770                 }
1771                 try {
1772                     $functionreflect = Zend_Server_Reflection::reflectFunction($dataobject->functionname);
1773                 } catch (Zend_Server_Reflection_Exception $e) { // catch these and rethrow them to something more helpful
1774                     throw new moodle_exception('installreflectionfunctionerror', 'mnet', '', (object)array('method' => $dataobject->functionname, '' => $dataobject->filename, 'error' => $e->getMessage()));
1775                 }
1776             }
1777             $dataobject->profile =  serialize(admin_mnet_method_profile($functionreflect));
1778             $dataobject->help = $functionreflect->getDescription();
1780             if ($record_exists = $DB->get_record('mnet_rpc', array('xmlrpcpath'=>$dataobject->xmlrpcpath))) {
1781                 $dataobject->id      = $record_exists->id;
1782                 $dataobject->enabled = $record_exists->enabled;
1783                 $DB->update_record('mnet_rpc', $dataobject);
1784             } else {
1785                 $dataobject->id = $DB->insert_record('mnet_rpc', $dataobject, true);
1786             }
1788             // TODO this API versioning must be reworked, here the recently processed method
1789             // sets the service API which may not be correct
1790             foreach ($publishmethodservices[$dataobject->functionname] as $service) {
1791                 if ($serviceobj = $DB->get_record('mnet_service', array('name'=>$service['servicename']))) {
1792                     $serviceobj->apiversion = $service['apiversion'];
1793                     $DB->update_record('mnet_service', $serviceobj);
1794                 } else {
1795                     $serviceobj = new stdClass();
1796                     $serviceobj->name        = $service['servicename'];
1797                     $serviceobj->description = empty($service['description']) ? '' : $service['description'];
1798                     $serviceobj->apiversion  = $service['apiversion'];
1799                     $serviceobj->offer       = 1;
1800                     $serviceobj->id          = $DB->insert_record('mnet_service', $serviceobj);
1801                 }
1802                 $servicecache[$service['servicename']] = $serviceobj;
1803                 if (!$DB->record_exists('mnet_service2rpc', array('rpcid'=>$dataobject->id, 'serviceid'=>$serviceobj->id))) {
1804                     $obj = new stdClass();
1805                     $obj->rpcid = $dataobject->id;
1806                     $obj->serviceid = $serviceobj->id;
1807                     $DB->insert_record('mnet_service2rpc', $obj, true);
1808                 }
1809             }
1810         }
1811     }
1812     // finished with methods we publish, now do subscribable methods
1813     foreach($subscribes as $service => $methods) {
1814         if (!array_key_exists($service, $servicecache)) {
1815             if (!$serviceobj = $DB->get_record('mnet_service', array('name' =>  $service))) {
1816                 debugging("TODO: skipping unknown service $service - somebody needs to fix MDL-21993");
1817                 continue;
1818             }
1819             $servicecache[$service] = $serviceobj;
1820         } else {
1821             $serviceobj = $servicecache[$service];
1822         }
1823         foreach ($methods as $method => $xmlrpcpath) {
1824             if (!$rpcid = $DB->get_field('mnet_remote_rpc', 'id', array('xmlrpcpath'=>$xmlrpcpath))) {
1825                 $remoterpc = (object)array(
1826                     'functionname' => $method,
1827                     'xmlrpcpath' => $xmlrpcpath,
1828                     'plugintype' => $type,
1829                     'pluginname' => $plugin,
1830                     'enabled'    => 1,
1831                 );
1832                 $rpcid = $remoterpc->id = $DB->insert_record('mnet_remote_rpc', $remoterpc, true);
1833             }
1834             if (!$DB->record_exists('mnet_remote_service2rpc', array('rpcid'=>$rpcid, 'serviceid'=>$serviceobj->id))) {
1835                 $obj = new stdClass();
1836                 $obj->rpcid = $rpcid;
1837                 $obj->serviceid = $serviceobj->id;
1838                 $DB->insert_record('mnet_remote_service2rpc', $obj, true);
1839             }
1840             $subscribemethodservices[$method][] = $service;
1841         }
1842     }
1844     foreach ($DB->get_records('mnet_remote_rpc', array('pluginname'=>$plugin, 'plugintype'=>$type), 'functionname ASC ') as $rpc) {
1845         if (!array_key_exists($rpc->functionname, $subscribemethodservices) && $rpc->enabled) {
1846             $DB->set_field('mnet_remote_rpc', 'enabled', 0, array('id' => $rpc->id));
1847         } else if (array_key_exists($rpc->functionname, $subscribemethodservices) && !$rpc->enabled) {
1848             $DB->set_field('mnet_remote_rpc', 'enabled', 1, array('id' => $rpc->id));
1849         }
1850     }
1852     return true;
1855 /**
1856  * Given some sort of Zend Reflection function/method object, return a profile array, ready to be serialized and stored
1857  *
1858  * @param Zend_Server_Reflection_Function_Abstract $function can be any subclass of this object type
1859  *
1860  * @return array
1861  */
1862 function admin_mnet_method_profile(Zend_Server_Reflection_Function_Abstract $function) {
1863     $protos = $function->getPrototypes();
1864     $proto = array_pop($protos);
1865     $ret = $proto->getReturnValue();
1866     $profile = array(
1867         'parameters' =>  array(),
1868         'return'     =>  array(
1869             'type'        => $ret->getType(),
1870             'description' => $ret->getDescription(),
1871         ),
1872     );
1873     foreach ($proto->getParameters() as $p) {
1874         $profile['parameters'][] = array(
1875             'name' => $p->getName(),
1876             'type' => $p->getType(),
1877             'description' => $p->getDescription(),
1878         );
1879     }
1880     return $profile;
1884 /**
1885  * This function finds duplicate records (based on combinations of fields that should be unique)
1886  * and then progamatically generated a "most correct" version of the data, update and removing
1887  * records as appropriate
1888  *
1889  * Thanks to Dan Marsden for help
1890  *
1891  * @param   string  $table      Table name
1892  * @param   array   $uniques    Array of field names that should be unique
1893  * @param   array   $fieldstocheck  Array of fields to generate "correct" data from (optional)
1894  * @return  void
1895  */
1896 function upgrade_course_completion_remove_duplicates($table, $uniques, $fieldstocheck = array()) {
1897     global $DB;
1899     // Find duplicates
1900     $sql_cols = implode(', ', $uniques);
1902     $sql = "SELECT {$sql_cols} FROM {{$table}} GROUP BY {$sql_cols} HAVING (count(id) > 1)";
1903     $duplicates = $DB->get_recordset_sql($sql, array());
1905     // Loop through duplicates
1906     foreach ($duplicates as $duplicate) {
1907         $pointer = 0;
1909         // Generate SQL for finding records with these duplicate uniques
1910         $sql_select = implode(' = ? AND ', $uniques).' = ?'; // builds "fieldname = ? AND fieldname = ?"
1911         $uniq_values = array();
1912         foreach ($uniques as $u) {
1913             $uniq_values[] = $duplicate->$u;
1914         }
1916         $sql_order = implode(' DESC, ', $uniques).' DESC'; // builds "fieldname DESC, fieldname DESC"
1918         // Get records with these duplicate uniques
1919         $records = $DB->get_records_select(
1920             $table,
1921             $sql_select,
1922             $uniq_values,
1923             $sql_order
1924         );
1926         // Loop through and build a "correct" record, deleting the others
1927         $needsupdate = false;
1928         $origrecord = null;
1929         foreach ($records as $record) {
1930             $pointer++;
1931             if ($pointer === 1) { // keep 1st record but delete all others.
1932                 $origrecord = $record;
1933             } else {
1934                 // If we have fields to check, update original record
1935                 if ($fieldstocheck) {
1936                     // we need to keep the "oldest" of all these fields as the valid completion record.
1937                     // but we want to ignore null values
1938                     foreach ($fieldstocheck as $f) {
1939                         if ($record->$f && (($origrecord->$f > $record->$f) || !$origrecord->$f)) {
1940                             $origrecord->$f = $record->$f;
1941                             $needsupdate = true;
1942                         }
1943                     }
1944                 }
1945                 $DB->delete_records($table, array('id' => $record->id));
1946             }
1947         }
1948         if ($needsupdate || isset($origrecord->reaggregate)) {
1949             // If this table has a reaggregate field, update to force recheck on next cron run
1950             if (isset($origrecord->reaggregate)) {
1951                 $origrecord->reaggregate = time();
1952             }
1953             $DB->update_record($table, $origrecord);
1954         }
1955     }
1958 /**
1959  * Find questions missing an existing category and associate them with
1960  * a category which purpose is to gather them.
1961  *
1962  * @return void
1963  */
1964 function upgrade_save_orphaned_questions() {
1965     global $DB;
1967     // Looking for orphaned questions
1968     $orphans = $DB->record_exists_select('question',
1969             'NOT EXISTS (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)');
1970     if (!$orphans) {
1971         return;
1972     }
1974     // Generate a unique stamp for the orphaned questions category, easier to identify it later on
1975     $uniquestamp = "unknownhost+120719170400+orphan";
1976     $systemcontext = context_system::instance();
1978     // Create the orphaned category at system level
1979     $cat = $DB->get_record('question_categories', array('stamp' => $uniquestamp,
1980             'contextid' => $systemcontext->id));
1981     if (!$cat) {
1982         $cat = new stdClass();
1983         $cat->parent = 0;
1984         $cat->contextid = $systemcontext->id;
1985         $cat->name = get_string('orphanedquestionscategory', 'question');
1986         $cat->info = get_string('orphanedquestionscategoryinfo', 'question');
1987         $cat->sortorder = 999;
1988         $cat->stamp = $uniquestamp;
1989         $cat->id = $DB->insert_record("question_categories", $cat);
1990     }
1992     // Set a category to those orphans
1993     $params = array('catid' => $cat->id);
1994     $DB->execute('UPDATE {question} SET category = :catid WHERE NOT EXISTS
1995             (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)', $params);
1998 /**
1999  * Rename old backup files to current backup files.
2000  *
2001  * When added the setting 'backup_shortname' (MDL-28657) the backup file names did not contain the id of the course.
2002  * Further we fixed that behaviour by forcing the id to be always present in the file name (MDL-33812).
2003  * This function will explore the backup directory and attempt to rename the previously created files to include
2004  * the id in the name. Doing this will put them back in the process of deleting the excess backups for each course.
2005  *
2006  * This function manually recreates the file name, instead of using
2007  * {@link backup_plan_dbops::get_default_backup_filename()}, use it carefully if you're using it outside of the
2008  * usual upgrade process.
2009  *
2010  * @see backup_cron_automated_helper::remove_excess_backups()
2011  * @link http://tracker.moodle.org/browse/MDL-35116
2012  * @return void
2013  * @since Moodle 2.4
2014  */
2015 function upgrade_rename_old_backup_files_using_shortname() {
2016     global $CFG;
2017     $dir = get_config('backup', 'backup_auto_destination');
2018     $useshortname = get_config('backup', 'backup_shortname');
2019     if (empty($dir) || !is_dir($dir) || !is_writable($dir)) {
2020         return;
2021     }
2023     require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
2024     $backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename')));
2025     $backupword = trim(clean_filename($backupword), '_');
2026     $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-';
2027     $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
2028     $thirtyapril = strtotime('30 April 2012 00:00');
2030     // Reading the directory.
2031     if (!$files = scandir($dir)) {
2032         return;
2033     }
2034     foreach ($files as $file) {
2035         // Skip directories and files which do not start with the common prefix.
2036         // This avoids working on files which are not related to this issue.
2037         if (!is_file($dir . '/' . $file) || !preg_match($regex, $file)) {
2038             continue;
2039         }
2041         // Extract the information from the XML file.
2042         try {
2043             $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file);
2044         } catch (backup_helper_exception $e) {
2045             // Some error while retrieving the backup informations, skipping...
2046             continue;
2047         }
2049         // Make sure this a course backup.
2050         if ($bcinfo->format !== backup::FORMAT_MOODLE || $bcinfo->type !== backup::TYPE_1COURSE) {
2051             continue;
2052         }
2054         // Skip the backups created before the short name option was initially introduced (MDL-28657).
2055         // This was integrated on the 2nd of May 2012. Let's play safe with timezone and use the 30th of April.
2056         if ($bcinfo->backup_date < $thirtyapril) {
2057             continue;
2058         }
2060         // Let's check if the file name contains the ID where it is supposed to be, if it is the case then
2061         // we will skip the file. Of course it could happen that the course ID is identical to the course short name
2062         // even though really unlikely, but then renaming this file is not necessary. If the ID is not found in the
2063         // file name then it was probably the short name which was used.
2064         $idfilename = $filename . $bcinfo->original_course_id . '-';
2065         $idregex = '#^'.preg_quote($idfilename, '#').'.*\.mbz$#';
2066         if (preg_match($idregex, $file)) {
2067             continue;
2068         }
2070         // Generating the file name manually. We do not use backup_plan_dbops::get_default_backup_filename() because
2071         // it will query the database to get some course information, and the course could not exist any more.
2072         $newname = $filename . $bcinfo->original_course_id . '-';
2073         if ($useshortname) {
2074             $shortname = str_replace(' ', '_', $bcinfo->original_course_shortname);
2075             $shortname = core_text::strtolower(trim(clean_filename($shortname), '_'));
2076             $newname .= $shortname . '-';
2077         }
2079         $backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig'));
2080         $date = userdate($bcinfo->backup_date, $backupdateformat, 99, false);
2081         $date = core_text::strtolower(trim(clean_filename($date), '_'));
2082         $newname .= $date;
2084         if (isset($bcinfo->root_settings['users']) && !$bcinfo->root_settings['users']) {
2085             $newname .= '-nu';
2086         } else if (isset($bcinfo->root_settings['anonymize']) && $bcinfo->root_settings['anonymize']) {
2087             $newname .= '-an';
2088         }
2089         $newname .= '.mbz';
2091         // Final check before attempting the renaming.
2092         if ($newname == $file || file_exists($dir . '/' . $newname)) {
2093             continue;
2094         }
2095         @rename($dir . '/' . $file, $dir . '/' . $newname);
2096     }
2099 /**
2100  * Detect duplicate grade item sortorders and resort the
2101  * items to remove them.
2102  */
2103 function upgrade_grade_item_fix_sortorder() {
2104     global $DB;
2106     // The simple way to fix these sortorder duplicates would be simply to resort each
2107     // affected course. But in order to reduce the impact of this upgrade step we're trying
2108     // to do it more efficiently by doing a series of update statements rather than updating
2109     // every single grade item in affected courses.
2111     $sql = "SELECT DISTINCT g1.courseid
2112               FROM {grade_items} g1
2113               JOIN {grade_items} g2 ON g1.courseid = g2.courseid
2114              WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id
2115              ORDER BY g1.courseid ASC";
2116     foreach ($DB->get_fieldset_sql($sql) as $courseid) {
2117         $transaction = $DB->start_delegated_transaction();
2118         $items = $DB->get_records('grade_items', array('courseid' => $courseid), '', 'id, sortorder, sortorder AS oldsort');
2120         // Get all duplicates in course order, highest sort order, and higest id first so that we can make space at the
2121         // bottom higher end of the sort orders and work down by id.
2122         $sql = "SELECT DISTINCT g1.id, g1.sortorder
2123                 FROM {grade_items} g1
2124                 JOIN {grade_items} g2 ON g1.courseid = g2.courseid
2125                 WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id AND g1.courseid = :courseid
2126                 ORDER BY g1.sortorder DESC, g1.id DESC";
2128         // This is the O(N*N) like the database version we're replacing, but at least the constants are a billion times smaller...
2129         foreach ($DB->get_records_sql($sql, array('courseid' => $courseid)) as $duplicate) {
2130             foreach ($items as $item) {
2131                 if ($item->sortorder > $duplicate->sortorder || ($item->sortorder == $duplicate->sortorder && $item->id > $duplicate->id)) {
2132                     $item->sortorder += 1;
2133                 }
2134             }
2135         }
2136         foreach ($items as $item) {
2137             if ($item->sortorder != $item->oldsort) {
2138                 $DB->update_record('grade_items', array('id' => $item->id, 'sortorder' => $item->sortorder));
2139             }
2140         }
2142         $transaction->allow_commit();
2143     }
2146 /**
2147  * Detect file areas with missing root directory records and add them.
2148  */
2149 function upgrade_fix_missing_root_folders() {
2150     global $DB, $USER;
2152     $transaction = $DB->start_delegated_transaction();
2154     $sql = "SELECT contextid, component, filearea, itemid
2155               FROM {files}
2156              WHERE (component <> 'user' OR filearea <> 'draft')
2157           GROUP BY contextid, component, filearea, itemid
2158             HAVING MAX(CASE WHEN filename = '.' AND filepath = '/' THEN 1 ELSE 0 END) = 0";
2160     $rs = $DB->get_recordset_sql($sql);
2161     $defaults = array('filepath' => '/',
2162         'filename' => '.',
2163         'userid' => 0, // Don't rely on any particular user for these system records.
2164         'filesize' => 0,
2165         'timecreated' => time(),
2166         'timemodified' => time(),
2167         'contenthash' => sha1(''));
2168     foreach ($rs as $r) {
2169         $pathhash = sha1("/$r->contextid/$r->component/$r->filearea/$r->itemid/.");
2170         $DB->insert_record('files', (array)$r + $defaults +
2171             array('pathnamehash' => $pathhash));
2172     }
2173     $rs->close();
2174     $transaction->allow_commit();
2177 /**
2178  * Detect draft file areas with missing root directory records and add them.
2179  */
2180 function upgrade_fix_missing_root_folders_draft() {
2181     global $DB;
2183     $transaction = $DB->start_delegated_transaction();
2185     $sql = "SELECT contextid, itemid, MAX(timecreated) AS timecreated, MAX(timemodified) AS timemodified
2186               FROM {files}
2187              WHERE (component = 'user' AND filearea = 'draft')
2188           GROUP BY contextid, itemid
2189             HAVING MAX(CASE WHEN filename = '.' AND filepath = '/' THEN 1 ELSE 0 END) = 0";
2191     $rs = $DB->get_recordset_sql($sql);
2192     $defaults = array('component' => 'user',
2193         'filearea' => 'draft',
2194         'filepath' => '/',
2195         'filename' => '.',
2196         'userid' => 0, // Don't rely on any particular user for these system records.
2197         'filesize' => 0,
2198         'contenthash' => sha1(''));
2199     foreach ($rs as $r) {
2200         $r->pathnamehash = sha1("/$r->contextid/user/draft/$r->itemid/.");
2201         $DB->insert_record('files', (array)$r + $defaults);
2202     }
2203     $rs->close();
2204     $transaction->allow_commit();
2207 /**
2208  * This function verifies that the database is not using an unsupported storage engine.
2209  *
2210  * @param environment_results $result object to update, if relevant
2211  * @return environment_results|null updated results object, or null if the storage engine is supported
2212  */
2213 function check_database_storage_engine(environment_results $result) {
2214     global $DB;
2216     // Check if MySQL is the DB family (this will also be the same for MariaDB).
2217     if ($DB->get_dbfamily() == 'mysql') {
2218         // Get the database engine we will either be using to install the tables, or what we are currently using.
2219         $engine = $DB->get_dbengine();
2220         // Check if MyISAM is the storage engine that will be used, if so, do not proceed and display an error.
2221         if ($engine == 'MyISAM') {
2222             $result->setInfo('unsupported_db_storage_engine');
2223             $result->setStatus(false);
2224             return $result;
2225         }
2226     }
2228     return null;