MDL-17427 moving upgrade code from adminlib to upgradelib + new upgrade logging functions
[moodle.git] / lib / upgradelib.php
CommitLineData
db9d4a3d 1<?php //$Id$
2
3/**
4 * upgradelib.php - Contains functions used during upgrade
5 *
6 * @author Martin Dougiamas and many others
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8 * @package moodlecore
9 */
10
11define('UPGRADE_LOG_NORMAL', 0);
12define('UPGRADE_LOG_NOTICE', 1);
13define('UPGRADE_LOG_ERROR', 2);
14
15/**
16 * Insert or update log display entry. Entry may already exist.
17 * $module, $action must be unique
18 *
19 * @param string $module
20 * @param string $action
21 * @param string $mtable
22 * @param string $field
23 * @return void
24 *
25 */
26function update_log_display_entry($module, $action, $mtable, $field) {
27 global $DB;
28
29 if ($type = $DB->get_record('log_display', array('module'=>$module, 'action'=>$action))) {
30 $type->mtable = $mtable;
31 $type->field = $field;
32 $DB->update_record('log_display', $type);
33
34 } else {
35 $type = new object();
36 $type->module = $module;
37 $type->action = $action;
38 $type->mtable = $mtable;
39 $type->field = $field;
40
41 $DB->insert_record('log_display', $type, false);
42 }
43}
44
45/**
46 * Upgrade savepoint, marks end of each upgrade block.
47 * It stores new main version, resets upgrade timeout
48 * and abort upgrade if user cancels page loading.
49 *
50 * Please do not make large upgrade blocks with lots of operations,
51 * for example when adding tables keep only one table operation per block.
52 *
53 * @param bool $result false if upgrade step failed, true if completed
54 * @param string or float $version main version
55 * @param bool $allowabort allow user to abort script execution here
56 * @return void
57 */
58function upgrade_main_savepoint($result, $version, $allowabort=true) {
59 global $CFG;
60
61 if ($result) {
62 if ($CFG->version >= $version) {
63 // something really wrong is going on in main upgrade script
64 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$CFG->version, 'newversion'=>$version));
65 }
66 set_config('version', $version);
67 } else {
68 error("Upgrade savepoint: Error during main upgrade to version $version"); // TODO: localise
69 }
70
71 // reset upgrade timeout to default
72 upgrade_set_timeout();
73
74 // this is a safe place to stop upgrades if user aborts page loading
75 if ($allowabort and connection_aborted()) {
76 die;
77 }
78}
79
80/**
81 * Module upgrade savepoint, marks end of module upgrade blocks
82 * It stores module version, resets upgrade timeout
83 * and abort upgrade if user cancels page loading.
84 *
85 * @param bool $result false if upgrade step failed, true if completed
86 * @param string or float $version main version
87 * @param string $modname name of module
88 * @param bool $allowabort allow user to abort script execution here
89 * @return void
90 */
91function upgrade_mod_savepoint($result, $version, $modname, $allowabort=true) {
92 global $DB;
93
94 if (!$module = $DB->get_record('modules', array('name'=>$modname))) {
95 print_error('modulenotexist', 'debug', '', $modname);
96 }
97
98 if ($result) {
99 if ($module->version >= $version) {
100 // something really wrong is going on in upgrade script
101 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$module->version, 'newversion'=>$version));
102 }
103 $module->version = $version;
104 $DB->update_record('modules', $module);
105 } else {
106 error("Upgrade savepoint: Error during mod upgrade to version $version"); // TODO: localise
107 }
108
109 // reset upgrade timeout to default
110 upgrade_set_timeout();
111
112 // this is a safe place to stop upgrades if user aborts page loading
113 if ($allowabort and connection_aborted()) {
114 die;
115 }
116}
117
118/**
119 * Blocks upgrade savepoint, marks end of blocks upgrade blocks
120 * It stores block version, resets upgrade timeout
121 * and abort upgrade if user cancels page loading.
122 *
123 * @param bool $result false if upgrade step failed, true if completed
124 * @param string or float $version main version
125 * @param string $blockname name of block
126 * @param bool $allowabort allow user to abort script execution here
127 * @return void
128 */
129function upgrade_blocks_savepoint($result, $version, $blockname, $allowabort=true) {
130 global $DB;
131
132 if (!$block = $DB->get_record('block', array('name'=>$blockname))) {
133 print_error('blocknotexist', 'debug', '', $blockname);
134 }
135
136 if ($result) {
137 if ($block->version >= $version) {
138 // something really wrong is going on in upgrade script
139 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$block->version, 'newversion'=>$version));
140 }
141 $block->version = $version;
142 $DB->update_record('block', $block);
143 } else {
144 error("Upgrade savepoint: Error during mod upgrade to version $version"); // TODO: localise
145 }
146
147 // reset upgrade timeout to default
148 upgrade_set_timeout();
149
150 // this is a safe place to stop upgrades if user aborts page loading
151 if ($allowabort and connection_aborted()) {
152 die;
153 }
154}
155
156/**
157 * Plugins upgrade savepoint, marks end of blocks upgrade blocks
158 * It stores plugin version, resets upgrade timeout
159 * and abort upgrade if user cancels page loading.
160 *
161 * @param bool $result false if upgrade step failed, true if completed
162 * @param string or float $version main version
163 * @param string $type name of plugin
164 * @param string $dir location of plugin
165 * @param bool $allowabort allow user to abort script execution here
166 * @return void
167 */
168function upgrade_plugin_savepoint($result, $version, $type, $dir, $allowabort=true) {
169 if ($result) {
170 $fullname = $type . '_' . $dir;
171 $installedversion = get_config($fullname, 'version');
172 if ($installedversion >= $version) {
173 // Something really wrong is going on in the upgrade script
174 $a = new stdClass;
175 $a->oldversion = $installedversion;
176 $a->newversion = $version;
177 print_error('cannotdowngrade', 'debug', '', $a);
178 }
179 set_config('version', $version, $fullname);
180 } else {
181 error("Upgrade savepoint: Error during mod upgrade to version $version"); // TODO: localise
182 }
183
184 // Reset upgrade timeout to default
185 upgrade_set_timeout();
186
187 // This is a safe place to stop upgrades if user aborts page loading
188 if ($allowabort and connection_aborted()) {
189 die;
190 }
191}
192
193
194/**
195 * Upgrade plugins
196 *
197 * @uses $CFG
198 * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
199 * @param string $dir The directory where the plugins are located (e.g. 'question/questiontypes')
200 * @param string $return The url to prompt the user to continue to
201 */
202function upgrade_plugins($type, $dir) {
203 global $CFG, $DB;
204
205/// special cases
206 if ($type === 'mod') {
207 return upgrade_activity_modules();
208 } else if ($type === 'blocks') {
209 return upgrade_blocks_plugins();
210 }
211
212 $plugs = get_list_of_plugins($dir);
213 $updated_plugins = false;
214 $strpluginsetup = get_string('pluginsetup');
215
216 foreach ($plugs as $plug) {
217
218 $fullplug = $CFG->dirroot .'/'.$dir.'/'. $plug;
219
220 unset($plugin);
221
222 if (is_readable($fullplug .'/version.php')) {
223 include($fullplug .'/version.php'); // defines $plugin with version etc
224 } else {
225 continue; // Nothing to do.
226 }
227
228 $newupgrade = false;
229 if (is_readable($fullplug . '/db/upgrade.php')) {
230 include_once($fullplug . '/db/upgrade.php'); // defines new upgrading function
231 $newupgrade = true;
232 }
233
234 if (!isset($plugin)) {
235 continue;
236 }
237
238 if (!empty($plugin->requires)) {
239 if ($plugin->requires > $CFG->version) {
240 $info = new object();
241 $info->pluginname = $plug;
242 $info->pluginversion = $plugin->version;
243 $info->currentmoodle = $CFG->version;
244 $info->requiremoodle = $plugin->requires;
245 upgrade_started();
246 notify(get_string('pluginrequirementsnotmet', 'error', $info));
247 $updated_plugins = true;
248 continue;
249 }
250 }
251
252 $plugin->name = $plug; // The name MUST match the directory
253 $plugin->fullname = $type.'_'.$plug; // The name MUST match the directory
254
255 $installedversion = get_config($plugin->fullname, 'version');
256
257 if ($installedversion === false) {
258 set_config('version', 0, $plugin->fullname);
259 }
260
261 if ($installedversion == $plugin->version) {
262 // do nothing
263 } else if ($installedversion < $plugin->version) {
264 $updated_plugins = true;
265 upgrade_started();
266 print_heading($dir.'/'. $plugin->name .' plugin needs upgrading');
267 @set_time_limit(0); // To allow slow databases to complete the long SQL
268
269 if ($installedversion == 0) { // It's a new install of this plugin
270 /// Both old .sql files and new install.xml are supported
271 /// but we priorize install.xml (XMLDB) if present
272 if (file_exists($fullplug . '/db/install.xml')) {
273 $DB->get_manager()->install_from_xmldb_file($fullplug . '/db/install.xml'); //New method
274 }
275 $status = true;
276 /// Continue with the instalation, roles and other stuff
277 if ($status) {
278 /// OK so far, now update the plugins record
279 set_config('version', $plugin->version, $plugin->fullname);
280
281 /// Install capabilities
282 update_capabilities($type.'/'.$plug);
283
284 /// Install events
285 events_update_definition($type.'/'.$plug);
286
287 /// Install message providers
288 message_update_providers($type.'/'.$plug);
289
290 /// Run local install function if there is one
291 if (is_readable($fullplug . '/lib.php')) {
292 include_once($fullplug . '/lib.php');
293 $installfunction = $plugin->name.'_install';
294 if (function_exists($installfunction)) {
295 if (! $installfunction() ) {
296 notify('Encountered a problem running install function for '.$module->name.'!');
297 }
298 }
299 }
300
301 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
302 } else {
303 notify('Installing '. $plugin->name .' FAILED!');
304 }
305 } else { // Upgrade existing install
306 /// Run the upgrade function for the plugin.
307 $newupgrade_function = 'xmldb_' .$plugin->fullname .'_upgrade';
308 $newupgrade_status = true;
309 if ($newupgrade && function_exists($newupgrade_function)) {
310 $newupgrade_status = $newupgrade_function($installedversion);
311 } else if ($newupgrade) {
312 notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
313 $fullplug . '/db/upgrade.php');
314 }
315 /// Now analyze upgrade results
316 if ($newupgrade_status) { // No upgrading failed
317 /// OK so far, now update the plugins record
318 set_config('version', $plugin->version, $plugin->fullname);
319 update_capabilities($type.'/'.$plug);
320 /// Update events
321 events_update_definition($type.'/'.$plug);
322
323 /// Update message providers
324 message_update_providers($type.'/'.$plug);
325
326 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
327 } else {
328 notify('Upgrading '. $plugin->name .' from '. $installedversion .' to '. $plugin->version .' FAILED!');
329 }
330 }
331 print_upgrade_separator();
332 } else {
333 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$installedversion, 'newversion'=>$plugin->version));
334 }
335 }
336
337 return $updated_plugins;
338}
339
340/**
341 * Find and check all modules and load them up or upgrade them if necessary
342 */
343function upgrade_activity_modules() {
344 global $CFG, $DB;
345
346 if (!$mods = get_list_of_plugins('mod') ) {
347 print_error('nomodules', 'debug');
348 }
349
350 $strmodulesetup = get_string('modulesetup');
351
352 foreach ($mods as $mod) {
353
354 if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it
355 continue;
356 }
357
358 $fullmod = $CFG->dirroot .'/mod/'. $mod;
359
360 unset($module);
361
362
363 if (is_readable($fullmod .'/version.php')) {
364 require($fullmod .'/version.php'); // defines $module with version etc
365 } else {
366 error('Module '. $mod .': '. $fullmod .'/version.php was not readable'); // TODO: localise
367 }
368
369 $newupgrade = false;
370 if ( is_readable($fullmod . '/db/upgrade.php')) {
371 include_once($fullmod . '/db/upgrade.php'); // defines new upgrading function
372 $newupgrade = true;
373 }
374
375 if (!isset($module)) {
376 continue;
377 }
378
379 if (!empty($module->requires)) {
380 if ($module->requires > $CFG->version) {
381 $info = new object();
382 $info->modulename = $mod;
383 $info->moduleversion = $module->version;
384 $info->currentmoodle = $CFG->version;
385 $info->requiremoodle = $module->requires;
386 upgrade_started();
387 notify(get_string('modulerequirementsnotmet', 'error', $info));
388 continue;
389 }
390 }
391
392 $module->name = $mod; // The name MUST match the directory
393
394 include_once($fullmod.'/lib.php'); // defines upgrading and/or installing functions
395
396 if ($currmodule = $DB->get_record('modules', array('name'=>$module->name))) {
397 if ($currmodule->version == $module->version) {
398 // do nothing
399 } else if ($currmodule->version < $module->version) {
400 /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
401 if (!$newupgrade) {
402 notify('Upgrade file ' . $mod . ': ' . $fullmod . '/db/upgrade.php is not readable');
403 continue;
404 }
405 upgrade_started();
406
407 print_heading($module->name .' module needs upgrading');
408
409 /// Run de old and new upgrade functions for the module
410 $newupgrade_function = 'xmldb_' . $module->name . '_upgrade';
411
412 /// Then, the new function if exists and the old one was ok
413 $newupgrade_status = true;
414 if ($newupgrade && function_exists($newupgrade_function)) {
415 $newupgrade_status = $newupgrade_function($currmodule->version, $module);
416 } else if ($newupgrade) {
417 notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
418 $mod . ': ' . $fullmod . '/db/upgrade.php');
419 }
420 /// Now analyze upgrade results
421 if ($newupgrade_status) { // No upgrading failed
422 // OK so far, now update the modules record
423 $module->id = $currmodule->id;
424 $DB->update_record('modules', $module);
425 remove_dir($CFG->dataroot . '/cache', true); // flush cache
426 notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
427 print_upgrade_separator();
428 } else {
429 notify('Upgrading '. $module->name .' from '. $currmodule->version .' to '. $module->version .' FAILED!');
430 }
431
432 /// Update the capabilities table?
433 update_capabilities('mod/'.$module->name);
434
435 /// Update events
436 events_update_definition('mod/'.$module->name);
437
438 /// Update message providers
439 message_update_providers('mod/'.$module->name);
440
441 } else {
442 print_error('cannotdowngrade', 'debug', '', (object)array('oldversion'=>$currmodule->version, 'newversion'=>$module->version));
443 }
444
445 } else { // module not installed yet, so install it
446 upgrade_started();
447 print_heading($module->name);
448
449 /// Execute install.xml (XMLDB) - must be present
450 $DB->get_manager()->install_from_xmldb_file($fullmod . '/db/install.xml'); //New method
451
452 /// Post installation hook - optional
453 if (file_exists("$fullmod/db/install.php")) {
454 require_once("$fullmod/db/install.php");
455 $post_install_function = 'xmldb_'.$module->name.'_install';;
456 $post_install_function();
457 }
458
459 /// Continue with the installation, roles and other stuff
460 $module->id = $DB->insert_record('modules', $module);
461
462 /// Capabilities
463 update_capabilities('mod/'.$module->name);
464
465 /// Events
466 events_update_definition('mod/'.$module->name);
467
468 /// Message providers
469 message_update_providers('mod/'.$module->name);
470
471 notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
472 print_upgrade_separator();
473 }
474
475 /// Check submodules of this module if necessary
476
477 $submoduleupgrade = $module->name.'_upgrade_submodules';
478 if (function_exists($submoduleupgrade)) {
479 $submoduleupgrade();
480 }
481
482 /// Run any defaults or final code that is necessary for this module
483
484 if ( is_readable($fullmod .'/defaults.php')) {
485 // Insert default values for any important configuration variables
486 unset($defaults);
487 include($fullmod .'/defaults.php'); // include here means execute, not library include
488 if (!empty($defaults)) {
489 if (!empty($defaults['_use_config_plugins'])) {
490 unset($defaults['_use_config_plugins']);
491 $localcfg = get_config($module->name);
492 foreach ($defaults as $name => $value) {
493 if (!isset($localcfg->$name)) {
494 set_config($name, $value, $module->name);
495 }
496 }
497 } else {
498 foreach ($defaults as $name => $value) {
499 if (!isset($CFG->$name)) {
500 set_config($name, $value);
501 }
502 }
503 }
504 }
505 }
506 }
507}
508
509
510////////////////////////////////////////////////
511/// upgrade logging functions
512////////////////////////////////////////////////
513
514function upgrade_handle_exception($exception, $plugin=null) {
515 //TODO
516}
517
518/**
519 * Adds log entry into upgrade_log table
520 *
521 * @param int $type UPGRADE_LOG_NORMAL, UPGRADE_LOG_NOTICE or UPGRADE_LOG_ERROR
522 * @param string $plugin plugin or null if main
523 * @param string $info short description text of log entry
524 * @param string $details long problem description
525 * @param string $backtrace string used for errors only
526 * @return void
527 */
528function upgrade_log($type, $plugin, $info, $details=null, $backtrace=null) {
529 global $DB, $USER, $CFG;
530
531 static $plugins = null;
532 if (!$plugins) {
533 $plugins = get_plugin_types();
534 }
535
536 $version = null;
537
538 //first try to find out current version number
539 if (is_null($plugin)) {
540 //main
541 $version = $CFG->version;
542
543 } else if (strpos('mod/', $plugin) === 0) {
544 try {
545 $modname = substr($plugin, strlen('mod/'));
546 $version = $DB->get_field('modules', 'version', array('name'=>$modname));
547 $version = $version === false ? null : $version;
548 } catch (Exception $ignored) {
549 }
550
551 } else if (strpos('blocks/', $plugin) === 0) {
552 try {
553 $blockname = substr($plugin, strlen('blocks/'));
554 if ($block = $DB->get_record('block', array('name'=>$blockname))) {
555 $version = $block->version;
556 }
557 } catch (Exception $ignored) {
558 }
559 }
560
561 $log = new object();
562 $log->type = $type;
563 $log->plugin = $plugin;
564 $log->version = $version;
565 $log->info = $info;
566 $log->details = $details;
567 $log->backtrace = $backtrace;
568 $log->userid = $USER->id;
569 $log->timemodified = time();
570
571 try {
572 $DB->insert_record('upgrade_log', $log);
573 } catch (Exception $ignored) {
574 // possible during install or upgrade
575 }
576}
577
578/**
579 * Marks start of upgrade, blocks any other access to site.
580 * The upgrade is finished at the end of script or after timeout.
581 */
582function upgrade_started($preinstall=false) {
583 global $CFG, $DB;
584
585 static $started = false;
586
587 if ($preinstall) {
588 ignore_user_abort(true);
589 upgrade_setup_debug(true);
590
591 } else if ($started) {
592 upgrade_set_timeout(120);
593
594 } else {
595 if (!CLI_SCRIPT and !defined('HEADER_PRINTED')) {
596 $strupgrade = get_string('upgradingversion', 'admin');
597
598 print_header($strupgrade, $strupgrade,
599 build_navigation(array(array('name' => $strupgrade, 'link' => null, 'type' => 'misc'))), '',
600 upgrade_get_javascript(), false, '&nbsp;', '&nbsp;');
601 }
602
603 ignore_user_abort(true);
604 register_shutdown_function('upgrade_finished_handler');
605 upgrade_setup_debug(true);
606 set_config('upgraderunning', time()+300);
607 $started = true;
608 }
609}
610
611/**
612 * Internal function - executed if upgrade interruped.
613 */
614function upgrade_finished_handler() {
615 upgrade_finished();
616}
617
618/**
619 * Indicates upgrade is finished.
620 *
621 * This function may be called repeatedly.
622 */
623function upgrade_finished($continueurl=null) {
624 global $CFG, $DB;
625
626 if (!empty($CFG->upgraderunning)) {
627 unset_config('upgraderunning');
628 upgrade_setup_debug(false);
629 ignore_user_abort(false);
630 if ($continueurl) {
631 print_continue($continueurl);
632 print_footer('none');
633 die;
634 }
635 }
636}
637
638function upgrade_setup_debug($starting) {
639 global $CFG, $DB;
640
641 static $originaldebug = null;
642
643 if ($starting) {
644 if ($originaldebug === null) {
645 $originaldebug = $DB->get_debug();
646 }
647 if (!empty($CFG->upgradeshowsql)) {
648 $DB->set_debug(true);
649 }
650 } else {
651 $DB->set_debug($originaldebug);
652 }
653}
654
655function print_upgrade_separator() {
656 if (!CLI_SCRIPT) {
657 echo '<hr />';
658 }
659}
660
661
662function upgrade_get_javascript() {
663 global $CFG;
664
665 return '<script type="text/javascript" src="'.$CFG->wwwroot.'/lib/scroll_to_errors.js"></script>';
666}
667
668
669/**
670 * Try to upgrade the given language pack (or current language)
671 * If it doesn't work, fail silently and return false
672 */
673function upgrade_language_pack($lang='') {
674 global $CFG;
675
676 if (empty($lang)) {
677 $lang = current_language();
678 }
679
680 if ($lang == 'en_utf8') {
681 return true; // Nothing to do
682 }
683
684 notify(get_string('langimport', 'admin').': '.$lang.' ... ', 'notifysuccess');
685
686 @mkdir ($CFG->dataroot.'/temp/'); //make it in case it's a fresh install, it might not be there
687 @mkdir ($CFG->dataroot.'/lang/');
688
689 require_once($CFG->libdir.'/componentlib.class.php');
690
691 if ($cd = new component_installer('http://download.moodle.org', 'lang16', $lang.'.zip', 'languages.md5', 'lang')) {
692 $status = $cd->install(); //returns COMPONENT_(ERROR | UPTODATE | INSTALLED)
693
694 if ($status == COMPONENT_INSTALLED) {
695 debugging('Downloading successful: '.$lang);
696 @unlink($CFG->dataroot.'/cache/languages');
697 return true;
698 }
699 }
700
701 return false;
702}