diff --git a/database/updates.inc b/database/updates.inc index 9847ee136452c41095b598c9d8b8b98ddba47cb4..bbbade5ec9866b230181870d1bd417a347bc8b94 100644 --- a/database/updates.inc +++ b/database/updates.inc @@ -1458,8 +1458,9 @@ function system_update_169() { } } + // Note: 'access' table manually updated in update.php return _system_update_utf8(array( - 'access', 'accesslog', 'aggregator_category', + 'accesslog', 'aggregator_category', 'aggregator_category_feed', 'aggregator_category_item', 'aggregator_feed', 'aggregator_item', 'authmap', 'blocks', 'book', 'boxes', 'cache', 'comments', 'contact', @@ -1477,7 +1478,7 @@ function system_update_169() { } /** - * Converts tables to UTF-8 encoding. + * Converts a set of tables to UTF-8 encoding. * * This update is designed to be re-usable by contrib modules and is * used by system_update_169(). @@ -1513,51 +1514,11 @@ function _system_update_utf8($tables) { $_SESSION['update_utf8_total'] = count($tables); } - // Fetch remaining tables list + // Fetch remaining tables list and convert next table $list = &$_SESSION['update_utf8']; - $ret = array(); - - // Convert a table to UTF-8. - // We change all text columns to their correspending binary type, - // then back to text, but with a UTF-8 character set. - // See: http://dev.mysql.com/doc/refman/4.1/en/charset-conversion.html - $types = array('char' => 'binary', - 'varchar' => 'varbinary', - 'tinytext' => 'tinyblob', - 'text' => 'blob', - 'mediumtext' => 'mediumblob', - 'longtext' => 'longblob'); - - // Get next table in list - $table = array_shift($list); - $convert_to_binary = array(); - $convert_to_utf8 = array(); - - // Set table default charset - $ret[] = update_sql('ALTER TABLE {'. $table .'} DEFAULT CHARACTER SET utf8'); - - // Find out which columns need converting and build SQL statements - $result = db_query('SHOW FULL COLUMNS FROM {'. $table .'}'); - while ($column = db_fetch_array($result)) { - list($type) = explode('(', $column['Type']); - if (isset($types[$type])) { - $names = 'CHANGE `'. $column['Field'] .'` `'. $column['Field'] .'` '; - $attributes = ' DEFAULT '. ($column['Default'] == 'NULL' ? 'NULL ' : - "'". db_escape_string($column['Default']) ."' ") . - ($column['Null'] == 'YES' ? 'NULL' : 'NOT NULL'); - - $convert_to_binary[] = $names . preg_replace('/'. $type .'/i', $types[$type], $column['Type']) . $attributes; - $convert_to_utf8[] = $names . $column['Type'] .' CHARACTER SET utf8'. $attributes; - } - } - - if (count($convert_to_binary)) { - // Convert text columns to binary - $ret[] = update_sql('ALTER TABLE {'. $table .'} '. implode(', ', $convert_to_binary)); - // Convert binary columns to UTF-8 - $ret[] = update_sql('ALTER TABLE {'. $table .'} '. implode(', ', $convert_to_utf8)); - } + $ret = update_convert_table_utf8(array_shift($list)); + // Are we done? if (count($list) == 0) { unset($_SESSION['update_utf8']); diff --git a/misc/progress.js b/misc/progress.js index 84979d68fc06eeb887296df55e5ddce6206cb9b5..776e2b637082e16d70dfd756055b8697a3c270c1 100644 --- a/misc/progress.js +++ b/misc/progress.js @@ -8,11 +8,12 @@ * e.g. pb = new progressBar('myProgressBar'); * some_element.appendChild(pb.element); */ -function progressBar(id, callback, method) { +function progressBar(id, updateCallback, method, errorCallback) { var pb = this; this.id = id; this.method = method ? method : HTTPGet; - this.callback = callback; + this.updateCallback = updateCallback; + this.errorCallback = errorCallback; this.element = document.createElement('div'); this.element.id = id; @@ -41,8 +42,8 @@ progressBar.prototype.setProgress = function (percentage, message) { divs[i].innerHTML = message; } } - if (this.callback) { - this.callback(percentage, message, this); + if (this.updateCallback) { + this.updateCallback(percentage, message, this); } } @@ -82,13 +83,13 @@ progressBar.prototype.sendPing = function () { */ progressBar.prototype.receivePing = function (string, xmlhttp, pb) { if (xmlhttp.status != 200) { - return alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ pb.uri); + return pb.displayError('An HTTP error '+ xmlhttp.status +' occured.\n'+ pb.uri); } // Parse response var progress = parseJson(string); // Display errors if (progress.status == 0) { - alert(progress.data); + pb.displayError(progress.data); return; } @@ -97,3 +98,19 @@ progressBar.prototype.receivePing = function (string, xmlhttp, pb) { // Schedule next timer pb.timer = setTimeout(function() { pb.sendPing(); }, pb.delay); } + +/** + * Display errors on the page. + */ +progressBar.prototype.displayError = function (string) { + var error = document.createElement('div'); + error.className = 'error'; + error.appendChild(document.createTextNode(string)); + + this.element.style.display = 'none'; + this.element.parentNode.insertBefore(error, this.element); + + if (this.errorCallback) { + this.errorCallback(this); + } +} diff --git a/misc/update.js b/misc/update.js index 2586fbf505c2bd9a6574fca4c242cba299b05e0d..e595e4cb2da45de7de9e898367a0e65ffe24bc88 100644 --- a/misc/update.js +++ b/misc/update.js @@ -12,7 +12,11 @@ if (isJsEnabled()) { } } - var progress = new progressBar('updateprogress', updateCallback, HTTPPost); + errorCallback = function (pb) { + window.location = window.location.href.split('op=')[0] +'op=error'; + } + + var progress = new progressBar('updateprogress', updateCallback, HTTPPost, errorCallback); progress.setProgress(-1, 'Starting updates'); $('progress').appendChild(progress.element); progress.startMonitoring('update.php?op=do_update', 0); diff --git a/update.php b/update.php index 7275b63e43e136a07a48e8d41016b3a192e205a2..20c3e20214b91b9b57461c2709c37ae2e8c95659 100644 --- a/update.php +++ b/update.php @@ -420,6 +420,9 @@ function update_do_updates() { return array($percentage, isset($update['module']) ? 'Updating '. $update['module'] .' module' : 'Updating complete'); } +/** + * Perform updates for the JS version and return progress. + */ function update_do_update_page() { global $conf; @@ -430,24 +433,27 @@ function update_do_update_page() { return ''; } - // Any errors which happen would cause the result to not be parsed properly, - // so we need to supporess them. All errors are still logged. - if (!isset($conf['error_level'])) { - $conf['error_level'] = 0; - } - ini_set('display_errors', FALSE); - list($percentage, $message) = update_do_updates(); print drupal_to_js(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); } +/** + * Perform updates for the non-JS version and return the status page. + */ function update_progress_page_nojs() { $new_op = 'do_update_nojs'; if ($_SERVER['REQUEST_METHOD'] == 'GET') { + // Store a fallback redirect in case of a fatal PHP error + ob_start(); + print '<html><head><meta http-equiv="Refresh" content="0; URL=update.php?op=error"></head></html>'; + list($percentage, $message) = update_do_updates(); if ($percentage == 100) { $new_op = 'finished'; } + + // Updates succesful, remove fallback + ob_end_clean(); } else { // This is the first page so return some output immediately. @@ -463,15 +469,23 @@ function update_progress_page_nojs() { return $output; } -function update_finished_page() { +function update_finished_page($success) { drupal_set_title('Drupal database update'); // NOTE: we can't use l() here because the URL would point to 'update.php?q=admin'. $links[] = '<a href="'. base_path() .'">main page</a>'; $links[] = '<a href="'. base_path() .'?q=admin">administration pages</a>'; - $output = '<p>Updates were attempted. If you see no failures below, you may proceed happily to the <a href="index.php?q=admin">administration pages</a>. Otherwise, you may need to update your database manually. All errors have been <a href="index.php?q=admin/logs">logged</a>.</p>'; + + if ($success) { + $output = '<p>Updates were attempted. If you see no failures below, you may proceed happily to the <a href="index.php?q=admin">administration pages</a>. Otherwise, you may need to update your database manually. All errors have been <a href="index.php?q=admin/logs">logged</a>.</p>'; + } + else { + $output = '<p class="error">The update process did not complete. All errors have been <a href="index.php?q=admin/logs">logged</a>. You may need to check the <code>watchdog</code> table manually.'; + } + if ($GLOBALS['access_check'] == FALSE) { $output .= "<p><strong>Reminder: don't forget to set the <code>\$access_check</code> value at the top of <code>update.php</code> back to <code>TRUE</code>.</strong>"; } + $output .= theme('item_list', $links); // Output a list of queries executed @@ -546,8 +560,89 @@ function update_fix_system_table() { } } +// This code may be removed later. It is part of the Drupal 4.6 to 4.7 migration. +function update_fix_access_table() { + if (variable_get('update_access_fixed', FALSE)) { + return; + } + + switch ($GLOBALS['db_type']) { + // Only for MySQL 4.1+ + case 'mysqli': + break; + case 'mysql': + if (version_compare(mysql_get_server_info($GLOBALS['active_db']), '4.1.0', '<')) { + return; + } + break; + case 'pgsql': + return; + } + + // Convert access table to UTF-8 if needed. + $result = db_fetch_array(db_query('SHOW CREATE TABLE `access`')); + if (!preg_match('/utf8/i', array_pop($result))) { + update_convert_table_utf8('access'); + } + + // Don't run again + variable_set('update_access_fixed', TRUE); +} + +/** + * Convert a single MySQL table to UTF-8. + * + * We change all text columns to their correspending binary type, + * then back to text, but with a UTF-8 character set. + * See: http://dev.mysql.com/doc/refman/4.1/en/charset-conversion.html + */ +function update_convert_table_utf8($table) { + $ret = array(); + $types = array('char' => 'binary', + 'varchar' => 'varbinary', + 'tinytext' => 'tinyblob', + 'text' => 'blob', + 'mediumtext' => 'mediumblob', + 'longtext' => 'longblob'); + + // Get next table in list + $convert_to_binary = array(); + $convert_to_utf8 = array(); + + // Set table default charset + $ret[] = update_sql('ALTER TABLE {'. $table .'} DEFAULT CHARACTER SET utf8'); + + // Find out which columns need converting and build SQL statements + $result = db_query('SHOW FULL COLUMNS FROM {'. $table .'}'); + while ($column = db_fetch_array($result)) { + list($type) = explode('(', $column['Type']); + if (isset($types[$type])) { + $names = 'CHANGE `'. $column['Field'] .'` `'. $column['Field'] .'` '; + $attributes = ' DEFAULT '. ($column['Default'] == 'NULL' ? 'NULL ' : + "'". db_escape_string($column['Default']) ."' ") . + ($column['Null'] == 'YES' ? 'NULL' : 'NOT NULL'); + + $convert_to_binary[] = $names . preg_replace('/'. $type .'/i', $types[$type], $column['Type']) . $attributes; + $convert_to_utf8[] = $names . $column['Type'] .' CHARACTER SET utf8'. $attributes; + } + } + + if (count($convert_to_binary)) { + // Convert text columns to binary + $ret[] = update_sql('ALTER TABLE {'. $table .'} '. implode(', ', $convert_to_binary)); + // Convert binary columns to UTF-8 + $ret[] = update_sql('ALTER TABLE {'. $table .'} '. implode(', ', $convert_to_utf8)); + } + return $ret; +} + +// Some unavoidable errors happen because the database is not yet up to date. +// We suppress them to avoid confusion. All errors are still logged. +ini_set('display_errors', FALSE); + include_once './includes/bootstrap.inc'; update_fix_system_table(); +update_fix_access_table(); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); drupal_maintenance_theme(); @@ -567,7 +662,11 @@ function update_fix_system_table() { break; case 'finished': - $output = update_finished_page(); + $output = update_finished_page(true); + break; + + case 'error': + $output = update_finished_page(false); break; case 'do_update':