Merged bug fix for MDL-6325
[moodle.git] / lib / adminlib.php
CommitLineData
88a7228a 1<?php //
2 //
3
4/**
5 * adminlib.php - Contains functions that only administrators will ever need to use
6 *
7 * @author Martin Dougiamas
8 * @version $Id$
9 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
10 * @package moodlecore
11 */
12
13/**
ead29342 14 * Upgrade plugins
88a7228a 15 *
16 * @uses $db
17 * @uses $CFG
ead29342 18 * @param string $type The type of plugins that should be updated (e.g. 'enrol', 'qtype')
19 * @param string $dir The directory where the plugins are located (e.g. 'question/questiontypes')
20 * @param string $return The url to prompt the user to continue to
88a7228a 21 */
ead29342 22function upgrade_plugins($type, $dir, $return) {
e69ef14b 23 global $CFG, $db;
173cc1c3 24
ead29342 25 if (!$plugs = get_list_of_plugins($dir) ) {
26 error('No '.$type.' plugins installed!');
173cc1c3 27 }
28
583fad99 29 $updated_plugins = false;
30 $strpluginsetup = get_string('pluginsetup');
31
ead29342 32 foreach ($plugs as $plug) {
173cc1c3 33
ead29342 34 $fullplug = $CFG->dirroot .'/'.$dir.'/'. $plug;
173cc1c3 35
ead29342 36 unset($plugin);
173cc1c3 37
bbbf2d40 38 if (is_readable($fullplug .'/version.php')) {
ead29342 39 include_once($fullplug .'/version.php'); // defines $plugin with version etc
173cc1c3 40 } else {
41 continue; // Nothing to do.
42 }
43
bbbf2d40 44 if (is_readable($fullplug .'/db/'. $CFG->dbtype .'.php')) {
ead29342 45 include_once($fullplug .'/db/'. $CFG->dbtype .'.php'); // defines upgrading function
173cc1c3 46 } else {
47 continue;
48 }
49
ead29342 50 if (!isset($plugin)) {
173cc1c3 51 continue;
52 }
53
ead29342 54 if (!empty($plugin->requires)) {
55 if ($plugin->requires > $CFG->version) {
56 $info->pluginname = $plug;
57 $info->pluginversion = $plugin->version;
173cc1c3 58 $info->currentmoodle = $CFG->version;
ead29342 59 $info->requiremoodle = $plugin->requires;
583fad99 60 if (!$updated_plugins) {
61 print_header($strpluginsetup, $strpluginsetup, $strpluginsetup, '',
62 '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>',
63 false, '&nbsp;', '&nbsp;');
64 }
65 upgrade_log_start();
ead29342 66 notify(get_string('pluginrequirementsnotmet', 'error', $info));
583fad99 67 $updated_plugins = true;
173cc1c3 68 unset($info);
69 continue;
70 }
71 }
72
ead29342 73 $plugin->name = $plug; // The name MUST match the directory
173cc1c3 74
ead29342 75 $pluginversion = $type.'_'.$plug.'_version';
173cc1c3 76
ead29342 77 if (!isset($CFG->$pluginversion)) {
78 set_config($pluginversion, 0);
173cc1c3 79 }
80
ead29342 81 if ($CFG->$pluginversion == $plugin->version) {
173cc1c3 82 // do nothing
ead29342 83 } else if ($CFG->$pluginversion < $plugin->version) {
583fad99 84 if (!$updated_plugins) {
a36f058e 85 print_header($strpluginsetup, $strpluginsetup, $strpluginsetup, '',
86 '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>',
87 false, '&nbsp;', '&nbsp;');
173cc1c3 88 }
583fad99 89 upgrade_log_start();
ead29342 90 print_heading($plugin->name .' plugin needs upgrading');
d87a9d73 91 if ($CFG->$pluginversion == 0) { // It's a new install of this plugin
92 if (file_exists($fullplug .'/db/'. $CFG->dbtype .'.sql')) {
93 $db->debug = true;
94 @set_time_limit(0); // To allow slow databases to complete the long SQL
95 if (modify_database($fullplug .'/db/'. $CFG->dbtype .'.sql')) {
96 // OK so far, now update the plugins record
97 set_config($pluginversion, $plugin->version);
e6316185 98 if (!update_capabilities($dir.'/'.$plug)) {
bbbf2d40 99 error('Could not set up the capabilities for '.$module->name.'!');
100 }
d87a9d73 101 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
102 } else {
103 notify('Installing '. $plugin->name .' FAILED!');
104 }
105 $db->debug = false;
106 } else { // We'll assume no tables are necessary
ead29342 107 set_config($pluginversion, $plugin->version);
108 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
d87a9d73 109 }
110 } else { // Upgrade existing install
111 $upgrade_function = $type.'_'.$plugin->name .'_upgrade';
112 if (function_exists($upgrade_function)) {
113 $db->debug=true;
114 if ($upgrade_function($CFG->$pluginversion)) {
115 $db->debug=false;
116 // OK so far, now update the plugins record
117 set_config($pluginversion, $plugin->version);
e6316185 118 if (!update_capabilities($dir.'/'.$plug)) {
bbbf2d40 119 error('Could not update '.$plugin->name.' capabilities!');
120 }
d87a9d73 121 notify(get_string('modulesuccess', '', $plugin->name), 'notifysuccess');
122 } else {
123 $db->debug=false;
124 notify('Upgrading '. $plugin->name .' from '. $CFG->$pluginversion .' to '. $plugin->version .' FAILED!');
125 }
173cc1c3 126 }
127 }
d87a9d73 128 echo '<hr />';
ead29342 129 $updated_plugins = true;
173cc1c3 130 } else {
583fad99 131 upgrade_log_start();
ead29342 132 error('Version mismatch: '. $plugin->name .' can\'t downgrade '. $CFG->$pluginversion .' -> '. $plugin->version .' !');
173cc1c3 133 }
134 }
135
583fad99 136 upgrade_log_finish();
137
138 if ($updated_plugins) {
173cc1c3 139 print_continue($return);
140 die;
141 }
142}
143
88a7228a 144/**
145 * Find and check all modules and load them up or upgrade them if necessary
146 *
147 * @uses $db
148 * @uses $CFG
149 * @param string $return The url to prompt the user to continue to
150 * @todo Finish documenting this function
151 */
173cc1c3 152function upgrade_activity_modules($return) {
173cc1c3 153
e69ef14b 154 global $CFG, $db;
173cc1c3 155
88a7228a 156 if (!$mods = get_list_of_plugins('mod') ) {
157 error('No modules installed!');
173cc1c3 158 }
159
583fad99 160 $updated_modules = false;
161 $strmodulesetup = get_string('modulesetup');
162
173cc1c3 163 foreach ($mods as $mod) {
164
88a7228a 165 if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it
173cc1c3 166 continue;
167 }
168
88a7228a 169 $fullmod = $CFG->dirroot .'/mod/'. $mod;
173cc1c3 170
171 unset($module);
172
88a7228a 173 if ( is_readable($fullmod .'/version.php')) {
174 include_once($fullmod .'/version.php'); // defines $module with version etc
173cc1c3 175 } else {
88a7228a 176 notify('Module '. $mod .': '. $fullmod .'/version.php was not readable');
173cc1c3 177 continue;
178 }
179
d6eb06b6 180 $oldupgrade = false;
181 $newupgrade = false;
88a7228a 182 if ( is_readable($fullmod .'/db/'. $CFG->dbtype .'.php')) {
d6eb06b6 183 include_once($fullmod .'/db/'. $CFG->dbtype .'.php'); // defines old upgrading function
184 $oldupgrade = true;
185 }
186 if ( is_readable($fullmod .'/db/upgrade.php') && $CFG->xmldb_enabled) {
187 include_once($fullmod .'/db/upgrade.php'); // defines new upgrading function
188 $newupgrade = true;
173cc1c3 189 }
190
191 if (!isset($module)) {
192 continue;
193 }
194
195 if (!empty($module->requires)) {
196 if ($module->requires > $CFG->version) {
197 $info->modulename = $mod;
198 $info->moduleversion = $module->version;
199 $info->currentmoodle = $CFG->version;
200 $info->requiremoodle = $module->requires;
583fad99 201 if (!$updated_modules) {
202 print_header($strmodulesetup, $strmodulesetup, $strmodulesetup, '',
203 '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>',
204 false, '&nbsp;', '&nbsp;');
205 }
206 upgrade_log_start();
173cc1c3 207 notify(get_string('modulerequirementsnotmet', 'error', $info));
583fad99 208 $updated_modules = true;
173cc1c3 209 unset($info);
210 continue;
211 }
212 }
213
214 $module->name = $mod; // The name MUST match the directory
215
88a7228a 216 if ($currmodule = get_record('modules', 'name', $module->name)) {
173cc1c3 217 if ($currmodule->version == $module->version) {
218 // do nothing
219 } else if ($currmodule->version < $module->version) {
d6eb06b6 220 /// If versions say that we need to upgrade but no upgrade files are available, notify and continue
221 if (!$oldupgrade && !$newupgrade) {
222 notify('Upgrade files ' . $mod . ': ' . $fullmod . '/db/' . $CFG->dbtype . '.php or ' .
223 $fullmod . '/db/upgrade.php were not readable');
224 continue;
225 }
583fad99 226 if (!$updated_modules) {
a36f058e 227 print_header($strmodulesetup, $strmodulesetup, $strmodulesetup, '',
228 '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>',
229 false, '&nbsp;', '&nbsp;');
173cc1c3 230 }
583fad99 231 upgrade_log_start();
88a7228a 232 print_heading($module->name .' module needs upgrading');
d6eb06b6 233
234 /// Run de old and new upgrade functions for the module
235 $oldupgrade_function = $module->name . '_upgrade';
236 $newupgrade_function = 'xmldb_' . $module->name . '_upgrade';
237
238 /// First, the old function if exists
239 $oldupgrade_status = true;
240 if ($oldupgrade && function_exists($oldupgrade_function)) {
241 $db->debug = true;
242 $oldupgrade_status = $oldupgrade_function($currmodule->version, $module);
ba05965e 243 } else if ($oldupgrade) {
d6eb06b6 244 notify ('Upgrade function ' . $oldupgrade_function . ' was not available in ' .
245 $mod . ': ' . $fullmod . '/db/' . $CFG->dbtype . '.php');
d6eb06b6 246 }
247
248 /// Then, the new function if exists and the old one was ok
249 $newupgrade_status = true;
ba05965e 250 if ($newupgrade && function_exists($newupgrade_function) && $oldupgrade_status) {
d6eb06b6 251 $db->debug = true;
252 $newupgrade_status = $newupgrade_function($currmodule->version, $module);
ba05965e 253 } else if ($newupgrade) {
d6eb06b6 254 notify ('Upgrade function ' . $newupgrade_function . ' was not available in ' .
255 $mod . ': ' . $fullmod . '/db/upgrade.php');
d6eb06b6 256 }
257
258 /// Now analyze upgrade results
668896e5 259 if ($oldupgrade_status && $newupgrade_status) { // No upgrading failed
d6eb06b6 260 $db->debug=false;
261 // OK so far, now update the modules record
262 $module->id = $currmodule->id;
263 if (! update_record('modules', $module)) {
264 error('Could not update '. $module->name .' record in modules table!');
173cc1c3 265 }
d6eb06b6 266 remove_dir($CFG->dataroot . '/cache', true); // flush cache
267 notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
268 echo '<hr />';
269 } else {
270 $db->debug=false;
271 notify('Upgrading '. $module->name .' from '. $currmodule->version .' to '. $module->version .' FAILED!');
173cc1c3 272 }
bbbf2d40 273
d6eb06b6 274 /// Update the capabilities table?
bbbf2d40 275 if (!update_capabilities('mod/'.$module->name)) {
276 error('Could not update '.$module->name.' capabilities!');
277 }
278
173cc1c3 279 $updated_modules = true;
bbbf2d40 280
173cc1c3 281 } else {
583fad99 282 upgrade_log_start();
88a7228a 283 error('Version mismatch: '. $module->name .' can\'t downgrade '. $currmodule->version .' -> '. $module->version .' !');
173cc1c3 284 }
285
286 } else { // module not installed yet, so install it
583fad99 287 if (!$updated_modules) {
a36f058e 288 print_header($strmodulesetup, $strmodulesetup, $strmodulesetup, '',
289 '<script type="text/javascript" src="' . $CFG->wwwroot . '/lib/scroll_to_errors.js"></script>',
290 false, '&nbsp;', '&nbsp;');
173cc1c3 291 }
583fad99 292 upgrade_log_start();
173cc1c3 293 print_heading($module->name);
294 $updated_modules = true;
295 $db->debug = true;
296 @set_time_limit(0); // To allow slow databases to complete the long SQL
d6eb06b6 297
298 /// Both old .sql files and new install.xml are supported
299 /// but we priorize install.xml (XMLDB) if present
300 if (file_exists($fullmod . '/db/install.xml') && $CFG->xmldb_enabled) {
301 $status = install_from_xmldb_file($fullmod . '/db/install.xml'); //New method
302 } else {
303 $status = modify_database($fullmod .'/db/'. $CFG->dbtype .'.sql'); //Old method
304 }
305
306 /// Continue with the instalation, roles and other stuff
307 if ($status) {
173cc1c3 308 $db->debug = false;
88a7228a 309 if ($module->id = insert_record('modules', $module)) {
bbbf2d40 310 if (!update_capabilities('mod/'.$module->name)) {
311 error('Could not set up the capabilities for '.$module->name.'!');
312 }
a8f68426 313 notify(get_string('modulesuccess', '', $module->name), 'notifysuccess');
88a7228a 314 echo '<hr />';
173cc1c3 315 } else {
88a7228a 316 error($module->name .' module could not be added to the module list!');
173cc1c3 317 }
318 } else {
88a7228a 319 error($module->name .' tables could NOT be set up successfully!');
173cc1c3 320 }
321 }
e5bd4e58 322
323 /// Check submodules of this module if necessary
324
325 include_once($fullmod.'/lib.php'); // defines upgrading function
326
327 $submoduleupgrade = $module->name.'_upgrade_submodules';
328 if (function_exists($submoduleupgrade)) {
329 $submoduleupgrade();
330 }
331
332
333 /// Run any defaults or final code that is necessary for this module
334
a5c0990e 335 if ( is_readable($fullmod .'/defaults.php')) {
336 // Insert default values for any important configuration variables
9e6e7502 337 unset($defaults);
a5c0990e 338 include_once($fullmod .'/defaults.php');
f9a2e515 339 if (!empty($defaults)) {
340 foreach ($defaults as $name => $value) {
341 if (!isset($CFG->$name)) {
342 set_config($name, $value);
343 }
a5c0990e 344 }
345 }
346 }
173cc1c3 347 }
348
583fad99 349 upgrade_log_finish(); // finish logging if started
350
351 if ($updated_modules) {
173cc1c3 352 print_continue($return);
136f43dc 353 print_footer();
173cc1c3 354 die;
355 }
356}
357
f3221af9 358/**
359 * This function will return FALSE if the lock fails to be set (ie, if it's already locked)
80be7ee3 360 *
361 * @param string $name ?
362 * @param bool $value ?
363 * @param int $staleafter ?
364 * @param bool $clobberstale ?
365 * @todo Finish documenting this function
f3221af9 366 */
367function set_cron_lock($name,$value=true,$staleafter=7200,$clobberstale=false) {
368
369 if (empty($name)) {
370 mtrace("Tried to get a cron lock for a null fieldname");
371 return false;
372 }
373
374 if (empty($value)) {
375 set_config($name,0);
376 return true;
377 }
378
379 if ($config = get_record('config','name',$name)) {
380 if (empty($config->value)) {
381 set_config($name,time());
382 } else {
383 // check for stale.
384 if ((time() - $staleafter) > $config->value) {
385 mtrace("STALE LOCKFILE FOR $name - was $config->value");
386 if (!empty($clobberstale)) {
387 set_config($name,time());
388 return true;
389 }
390 } else {
391 return false; // was not stale - ie, we're ok to still be running.
392 }
393 }
394 }
395 else {
396 set_config($name,time());
397 }
398 return true;
399}
a597f8a8 400
fb06b255 401function print_progress($done, $total, $updatetime=5, $sleeptime=1, $donetext='') {
a597f8a8 402 static $starttime;
403 static $lasttime;
404
405 if (empty($starttime)) {
406 $starttime = $lasttime = time();
407 $lasttime = $starttime - $updatetime;
408 echo '<table width="500" cellpadding="0" cellspacing="0" align="center"><tr><td width="500">';
409 echo '<div id="bar" style="border-style:solid;border-width:1px;width:500px;height:50px;">';
410 echo '<div id="slider" style="border-style:solid;border-width:1px;height:48px;width:10px;background-color:green;"></div>';
411 echo '</div>';
412 echo '<div id="text" align="center" style="width:500px;"></div>';
413 echo '</td></tr></table>';
414 echo '</div>';
415 }
416
a597f8a8 417 $now = time();
418
419 if ($done && (($now - $lasttime) >= $updatetime)) {
420 $elapsedtime = $now - $starttime;
421 $projectedtime = (int)(((float)$total / (float)$done) * $elapsedtime) - $elapsedtime;
422 $percentage = format_float((float)$done / (float)$total, 2);
423 $width = (int)(500 * $percentage);
424
fb06b255 425 if ($projectedtime > 10) {
426 $projectedtext = ' Ending: '.format_time($projectedtime);
427 } else {
428 $projectedtext = '';
429 }
430
a597f8a8 431 echo '<script>';
fb06b255 432 echo 'document.getElementById("text").innerHTML = "'.addslashes($donetext).' '.$done.' done.'.$projectedtext.'";'."\n";
a597f8a8 433 echo 'document.getElementById("slider").style.width = \''.$width.'px\';'."\n";
434 echo '</script>';
435
436 $lasttime = $now;
437 sleep($sleeptime);
438 }
439}
583fad99 440
441////////////////////////////////////////////////
442/// upgrade logging functions
443////////////////////////////////////////////////
444
445$upgradeloghandle = false;
26c91c73 446$upgradelogbuffer = '';
447// I did not find out how to use static variable in callback function,
448// the problem was that I could not flush the static buffer :-(
449global $upgradeloghandle, $upgradelogbuffer;
583fad99 450
451/**
452 * Check if upgrade is already running.
453 *
454 * If anything goes wrong due to missing call to upgrade_log_finish()
455 * just restart the browser.
456 *
457 * @param string warning message indicating upgrade is already running
458 * @param int page reload timeout
459 */
460function upgrade_check_running($message, $timeout) {
461 if (!empty($_SESSION['upgraderunning'])) {
462 print_header();
463 redirect(me(), $message, $timeout);
464 }
465}
466
467/**
468 * Start logging of output into file (if not disabled) and
469 * prevent aborting and concurrent execution of upgrade script.
470 *
471 * Please note that you can not write into session variables after calling this function!
472 *
473 * This function may be called repeatedly.
474 */
475function upgrade_log_start() {
426a369b 476 global $CFG, $upgradeloghandle;
583fad99 477
478 if (!empty($_SESSION['upgraderunning'])) {
479 return; // logging already started
480 }
481
482 @ignore_user_abort(true); // ignore if user stops or otherwise aborts page loading
483 $_SESSION['upgraderunning'] = 1; // set upgrade indicator
426a369b 484 if (empty($CFG->dbsessions)) { // workaround for bug in adodb, db session can not be restarted
485 session_write_close(); // from now on user can reload page - will be displayed warning
486 }
583fad99 487 make_upload_directory('upgradelogs');
488 ob_start('upgrade_log_callback', 2); // function for logging to disk; flush each line of text ASAP
dedb2304 489 register_shutdown_function('upgrade_log_finish'); // in case somebody forgets to stop logging
583fad99 490}
491
492/**
493 * Terminate logging of output, flush all data, allow script aborting
494 * and reopen session for writing. Function error() does terminate the logging too.
495 *
496 * Please make sure that each upgrade_log_start() is properly terminated by
497 * this function or error().
498 *
499 * This function may be called repeatedly.
500 */
501function upgrade_log_finish() {
426a369b 502 global $CFG, $upgradeloghandle, $upgradelogbuffer;
583fad99 503
504 if (empty($_SESSION['upgraderunning'])) {
505 return; // logging already terminated
506 }
507
508 @ob_end_flush();
26c91c73 509 if ($upgradelogbuffer !== '') {
510 @fwrite($upgradeloghandle, $upgradelogbuffer);
40896537 511 $upgradelogbuffer = '';
26c91c73 512 }
513 if ($upgradeloghandle and ($upgradeloghandle !== 'error')) {
514 @fclose($upgradeloghandle);
40896537 515 $upgradeloghandle = false;
26c91c73 516 }
426a369b 517 if (empty($CFG->dbsessions)) {
518 @session_start(); // ignore header errors, we only need to reopen session
519 }
583fad99 520 $_SESSION['upgraderunning'] = 0; // clear upgrade indicator
521 if (connection_aborted()) {
522 die;
523 }
524 @ignore_user_abort(false);
525}
526
527/**
528 * Callback function for logging into files. Not more than one file is created per minute,
529 * upgrade session (terminated by upgrade_log_finish()) is always stored in one file.
530 *
531 * This function must not output any characters or throw warnigns and errors!
532 */
533function upgrade_log_callback($string) {
26c91c73 534 global $CFG, $upgradeloghandle, $upgradelogbuffer;
583fad99 535
536 if (empty($CFG->disableupgradelogging) and ($string != '') and ($upgradeloghandle !== 'error')) {
537 if ($upgradeloghandle or ($upgradeloghandle = @fopen($CFG->dataroot.'/upgradelogs/upg_'.date('Ymd-Hi').'.html', 'a'))) {
26c91c73 538 $upgradelogbuffer .= $string;
539 if (strlen($upgradelogbuffer) > 2048) { // 2kB write buffer
540 @fwrite($upgradeloghandle, $upgradelogbuffer);
541 $upgradelogbuffer = '';
542 }
583fad99 543 } else {
544 $upgradeloghandle = 'error';
545 }
546 }
547 return $string;
548}
549
9e6e7502 550?>