summaryrefslogtreecommitdiff
path: root/update.php
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2005-12-06 09:25:22 +0000
committerDries Buytaert <dries@buytaert.net>2005-12-06 09:25:22 +0000
commita19acb219e249693357daeb037165eaf19a70b33 (patch)
treec7d04eacff376e51303c55e773153b8c898086a8 /update.php
parente31f7abd42b4905ec0aa4961c7d920e373c012d9 (diff)
downloadbrdo-a19acb219e249693357daeb037165eaf19a70b33.tar.gz
brdo-a19acb219e249693357daeb037165eaf19a70b33.tar.bz2
- Patch #35924 by Neil: improved the update system.
Diffstat (limited to 'update.php')
-rw-r--r--update.php557
1 files changed, 499 insertions, 58 deletions
diff --git a/update.php b/update.php
index 9f9269b4a..a76587ce7 100644
--- a/update.php
+++ b/update.php
@@ -17,49 +17,347 @@
// Enforce access checking?
$access_check = TRUE;
-if (!ini_get("safe_mode")) {
- set_time_limit(180);
+
+define('SCHEMA', 0);
+define('SCHEMA_MIN', 1);
+
+/**
+ * Includes install files.
+ */
+function update_include_install_files() {
+ // The system module (Drupal core) is currently a special case
+ include_once './database/updates.inc';
+
+ foreach (module_list() as $module) {
+ $install_file = './'. drupal_get_path('module', $module) .'/'. $module .'.install';
+ if (is_file($install_file)) {
+ include_once $install_file;
+ }
+ }
}
-include_once './database/updates.inc';
+function update_sql($sql) {
+ $result = db_query($sql);
+ return array('success' => $result !== FALSE, 'query' => check_plain($sql));
+}
-function update_data($start) {
- global $sql_updates;
- $output = '';
- $sql_updates = array_slice($sql_updates, ($start-- ? $start : 0));
- foreach ($sql_updates as $date => $func) {
- $output .= '<h3 class="update">'. $date .'</h3><pre class="update">';
- $ret = $func();
- foreach ($ret as $return) {
- $output .= $return[1];
+/**
+ * Adds a column to a database. Uses syntax appropriate for PostgreSQL.
+ * Saves result of SQL commands in $ret array.
+ *
+ * Note: when you add a column with NOT NULL and you are not sure if there are
+ * rows in table already, you MUST also add DEFAULT. Otherwise PostgreSQL won't
+ * work if the table is not empty. If NOT NULL and DEFAULT is set the
+ * PostgreSQL version will set values of the added column in old rows to the
+ * DEFAULT value.
+ *
+ * @param $ret
+ * Array to which results will be added.
+ * @param $table
+ * Name of the table, without {}
+ * @param $column
+ * Name of the column
+ * @param $type
+ * Type of column
+ * @param $attributes
+ * Additional optional attributes. Recognized atributes:
+ * - not null => TRUE/FALSE
+ * - default => NULL/FALSE/value (with or without '', it wont' be added)
+ * @return
+ * nothing, but modifies $ret parametr.
+ */
+function db_add_column(&$ret, $table, $column, $type, $attributes = array()) {
+ if (array_key_exists('not null', $attributes) and $attributes['not null']) {
+ $not_null = 'NOT NULL';
+ }
+ if (array_key_exists('default', $attributes)) {
+ if (is_null($attributes['default'])) {
+ $default_val = 'NULL';
+ $default = 'default NULL';
+ }
+ elseif ($attributes['default'] === FALSE) {
+ $default = '';
+ }
+ else {
+ $default_val = "$attributes[default]";
+ $default = "default $attributes[default]";
}
- variable_set("update_start", $date);
- $output .= "</pre>\n";
}
- db_query('DELETE FROM {cache}');
- return $output;
+
+ $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column $type");
+ if ($default) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET $default"); }
+ if ($not_null) {
+ if ($default) { $ret[] = update_sql("UPDATE {". $table ."} SET $column = $default_val"); }
+ $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET NOT NULL");
+ }
}
-function update_selection_page() {
- global $sql_updates;
+/**
+ * Changes a column definition. Uses syntax appropriate for PostgreSQL.
+ * Saves result of SQL commands in $ret array.
+ *
+ * @param $ret
+ * Array to which results will be added.
+ * @param $table
+ * Name of the table, without {}
+ * @param $column
+ * Name of the column to change
+ * @param $column_new
+ * New name for the column (set to the same as $column if you don't want to change the name)
+ * @param $type
+ * Type of column
+ * @param $attributes
+ * Additional optional attributes. Recognized atributes:
+ * - not null => TRUE/FALSE
+ * - default => NULL/FALSE/value (with or without '', it wont' be added)
+ * @return
+ * nothing, but modifies $ret parametr.
+ */
+function db_change_column(&$ret, $table, $column, $column_new, $type, $attributes = array()) {
+ if (array_key_exists('not null', $attributes) and $attributes['not null']) {
+ $not_null = 'NOT NULL';
+ }
+ if (array_key_exists('default', $attributes)) {
+ if (is_null($attributes['default'])) {
+ $default_val = 'NULL';
+ $default = 'default NULL';
+ }
+ elseif ($attributes['default'] === FALSE) {
+ $default = '';
+ }
+ else {
+ $default_val = "$attributes[default]";
+ $default = "default $attributes[default]";
+ }
+ }
+
+ $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $column TO ". $column ."_old");
+ $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column_new $type");
+ $ret[] = update_sql("UPDATE {". $table ."} SET $column_new = ". $column ."_old");
+ if ($default) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET $default"); }
+ if ($not_null) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET NOT NULL"); }
+ // We don't drop columns for now
+ // $ret[] = update_sql("ALTER TABLE {". $table ."} DROP ". $column ."_old");
+}
+
+/**
+ * If the schema version for Drupal core is stored in the the variables table
+ * (4.6.x and earlier) move it to the schema_version column of the system
+ * table.
+ *
+ * This function may be removed when update 156 is removed, which is the last
+ * update in the 4.6 to 4.7 migration.
+ */
+function update_fix_schema_version() {
+ if ($update_start = variable_get('update_start', FALSE)) {
+ // Some updates were made to the 4.6 branch and 4.7 branch. This sets
+ // temporary variables to provent the updates from being executed twice and
+ // throwing errors.
+ switch ($update_start) {
+ case '2005-04-14':
+ variable_set('update_132_done', TRUE);
+ break;
+
+ case '2005-05-06':
+ variable_set('update_132_done', TRUE);
+ variable_set('update_135_done', TRUE);
+ break;
+
+ case '2005-05-07':
+ variable_set('update_132_done', TRUE);
+ variable_set('update_135_done', TRUE);
+ variable_set('update_137_done', TRUE);
+ break;
+
+ }
+
+ $sql_updates = array(
+ '2004-10-31: first update since Drupal 4.5.0 release' => 110,
+ '2004-11-07' => 111, '2004-11-15' => 112, '2004-11-28' => 113,
+ '2004-12-05' => 114, '2005-01-07' => 115, '2005-01-14' => 116,
+ '2005-01-18' => 117, '2005-01-19' => 118, '2005-01-20' => 119,
+ '2005-01-25' => 120, '2005-01-26' => 121, '2005-01-27' => 122,
+ '2005-01-28' => 123, '2005-02-11' => 124, '2005-02-23' => 125,
+ '2005-03-03' => 126, '2005-03-18' => 127, '2005-03-21' => 128,
+ // The following three updates were made on the 4.6 branch
+ '2005-04-14' => 129, '2005-05-06' => 129, '2005-05-07' => 129,
+ '2005-04-08: first update since Drupal 4.6.0 release' => 129,
+ '2005-04-10' => 130, '2005-04-11' => 131, '2005-04-14' => 132,
+ '2005-04-24' => 133, '2005-04-30' => 134, '2005-05-06' => 135,
+ '2005-05-08' => 136, '2005-05-09' => 137, '2005-05-10' => 138,
+ '2005-05-11' => 139, '2005-05-12' => 140, '2005-05-22' => 141,
+ '2005-07-29' => 142, '2005-07-30' => 143, '2005-08-08' => 144,
+ '2005-08-15' => 145, '2005-08-25' => 146, '2005-09-07' => 147,
+ '2005-09-18' => 148, '2005-09-27' => 149, '2005-10-15' => 150,
+ '2005-10-23' => 151, '2005-10-28' => 152, '2005-11-03' => 153,
+ '2005-11-14' => 154, '2005-11-27' => 155, '2005-12-03' => 156,
+ );
+
+ switch ($GLOBALS['db_type']) {
+ case 'pgsql':
+ $ret = array();
+ db_add_column($ret, 'system', 'schema_version', 'int2', array('not null' => TRUE));
+ break;
+
+ case 'mysql':
+ case 'mysqli':
+ db_query('ALTER TABLE {system} ADD schema_version smallint(2) unsigned not null');
+ break;
+ }
+
+ update_set_installed_version('system', $sql_updates[$update_start]);
+ variable_del('update_start');
+ }
+}
+
+/**
+ * System update 130 changes the sessions table, which breaks the update
+ * script's ability to use session variables. This changes the table
+ * appropriately.
+ *
+ * This code, including the 'update_sessions_fixed' variable, may be removed
+ * when update 130 is removed. It is part of the Drupal 4.6 to 4.7 migration.
+ */
+function update_fix_sessions() {
+ $ret = array();
+
+ if (update_get_installed_version('system') < 130 && !variable_get('update_sessions_fixed', FALSE)) {
+ if ($GLOBALS['db_type'] == 'mysql') {
+ db_query("ALTER TABLE {sessions} ADD cache int(11) NOT NULL default '0' AFTER timestamp");
+ }
+ elseif ($GLOBALS['db_type'] == 'pgsql') {
+ db_add_column($ret, 'sessions', 'cache', 'int', array('default' => 0, 'not null' => TRUE));
+ }
+
+ variable_set('update_sessions_fixed', TRUE);
+ }
+}
+
+/**
+ * System update 142 changes the watchdog table, which breaks the update
+ * script's ability to use logging. This changes the table appropriately.
+ *
+ * This code, including the 'update_watchdog_fixed' variable, may be removed
+ * when update 142 is removed. It is part of the Drupal 4.6 to 4.7 migration.
+ */
+function update_fix_watchdog() {
+ if (update_get_installed_version('system') < 142 && !variable_get('update_watchdog_fixed', FALSE)) {
+ switch ($GLOBALS['db_type']) {
+ case 'pgsql':
+ db_add_column($ret, 'watchdog', 'referer', 'varchar(128)', array('not null' => TRUE, 'default' => "''"));
+ break;
+ case 'mysql':
+ case 'mysqli':
+ $ret[] = db_query("ALTER TABLE {watchdog} ADD COLUMN referer varchar(128) NOT NULL");
+ break;
+ }
+
+ variable_set('update_watchdog_fixed', TRUE);
+ }
+}
+
+function update_data($module, $number) {
+ $ret = module_invoke($module, 'update_'. $number);
+
+ // Save the query and results for display by update_finished_page().
+ if (!isset($_SESSION['update_results'])) {
+ $_SESSION['update_results'] = array();
+ }
+ else if (!isset($_SESSION['update_results'][$module])) {
+ $_SESSION['update_results'][$module] = array();
+ }
+ $_SESSION['update_results'][$module][$number] = $ret;
+
+ // Update the installed version
+ update_set_installed_version($module, $number);
+}
+
+/**
+ * Returns an array of availiable schema versions for a module.
+ *
+ * @param $module
+ * A module name.
+ * @return
+ * If the module has updates, an array of available updates. Otherwise,
+ * FALSE.
+ */
+function update_get_versions($module) {
+ if (!($max = module_invoke($module, 'version', SCHEMA))) {
+ return FALSE;
+ }
+ if (!($min = module_invoke($module, 'version', SCHEMA_MIN))) {
+ $min = 1;
+ }
+ return range($min, $max);
+}
+
+/**
+ * Returns the currently installed schema version for a module.
+ *
+ * @param $module
+ * A module name.
+ * @return
+ * The currently installed schema version.
+ */
+function update_get_installed_version($module, $reset = FALSE) {
+ static $versions;
- $start = variable_get("update_start", 0);
- $i = 1;
- foreach ($sql_updates as $date => $sql) {
- $dates[$i++] = $date;
- if ($date == $start) {
- $selected = $i;
+ if ($reset) {
+ unset($versions);
+ }
+
+ if (!$versions) {
+ $versions = array();
+ $result = db_query("SELECT name, schema_version FROM {system} WHERE type = 'module'");
+ while ($row = db_fetch_object($result)) {
+ $versions[$row->name] = $row->schema_version;
}
}
- $dates[$i] = "No updates available";
- // make update form and output it.
+ return $versions[$module];
+}
+
+/**
+ * Update the installed version information for a module.
+ *
+ * @param $module
+ * A module name.
+ * @param $version
+ * The new schema version.
+ */
+function update_set_installed_version($module, $version) {
+ db_query("UPDATE {system} SET schema_version = %d WHERE name = '%s'", $version, $module);
+}
+
+function update_selection_page() {
+ $output = '<p>'. t('The version of Drupal you are updating from has been automatically detected. You can select a different version, but you should not need to.') .'</p>';
+ $output .= '<p>'. t('Click Update to start the update process.') .'</p>';
+
+ $form = array();
$form['start'] = array(
- '#type' => 'select',
- '#title' => t('Perform updates from'),
- '#default_value' => (isset($selected) ? $selected : -1),
- '#options' => $dates,
- '#description' => t('This defaults to the first available update since the last update you performed.')
+ '#tree' => TRUE,
+ '#type' => 'fieldset',
+ '#title' => 'Select versions',
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ foreach (module_list() as $module) {
+ if (module_hook($module, 'version')) {
+ $updates = drupal_map_assoc(update_get_versions($module));
+ $updates[] = 'No updates available';
+
+ $form['start'][$module] = array(
+ '#type' => 'select',
+ '#title' => t('%name module', array('%name' => $module)),
+ '#default_value' => array_search(update_get_installed_version($module), $updates) + 1,
+ '#options' => $updates,
+ );
+ }
+ }
+
+ $form['has_js'] = array(
+ '#type' => 'hidden',
+ '#default_value' => FALSE
);
$form['submit'] = array(
'#type' => 'submit',
@@ -67,21 +365,150 @@ function update_selection_page() {
);
drupal_set_title('Drupal database update');
- return drupal_get_form('update_script_selection_form', $form);
+ drupal_add_js('misc/update.js');
+ $output .= drupal_get_form('update_script_selection_form', $form);
+
+ return $output;
+}
+
+function update_update_page() {
+ // Set the installed version so updates start at the correct place.
+ $_SESSION['update_remaining'] = array();
+ foreach ($_POST['edit']['start'] as $module => $version) {
+ update_set_installed_version($module, $version - 1);
+ $max_version = max(update_get_versions($module));
+ if ($version <= $max_version) {
+ foreach (range($version, $max_version) as $update) {
+ $_SESSION['update_remaining'][] = array('module' => $module, 'version' => $update);
+ }
+ }
+ }
+ // Keep track of total number of updates
+ $_SESSION['update_total'] = count($_SESSION['update_remaining']);
+
+ if ($_POST['edit']['has_js']) {
+ return update_progress_page();
+ }
+ else {
+ return update_progress_page_nojs();
+ }
+}
+
+function update_progress_page() {
+ drupal_add_js('misc/progress.js');
+ drupal_add_js('misc/update.js');
+
+ drupal_set_title('Updating');
+ $output = '<div id="progress"></div>';
+ $output .= '<p>Updating your site will take a few seconds.</p>';
+ return $output;
}
+/**
+ * Perform updates for one second or until finished.
+ *
+ * @return
+ * An array indicating the status after doing updates. The first element is
+ * the overall percent finished. The second element is a status message.
+ */
function update_do_updates() {
- $edit = $_POST['edit'];
+ foreach ($_SESSION['update_remaining'] as $key => $update) {
+ update_data($update['module'], $update['version']);
+ unset($_SESSION['update_remaining'][$key]);
+ if (timer_read('page') > 1000) {
+ break;
+ }
+ }
+
+ if ($_SESSION['update_total']) {
+ $percent = floor(($_SESSION['update_total'] - count($_SESSION['update_remaining'])) / $_SESSION['update_total'] * 100);
+ }
+ else {
+ $percent = 100;
+ }
+ return array($percent, 'Updating '. $update['module'] .' module');
+}
+
+function update_do_update_page() {
+ global $conf;
+
+ // HTTP Post required
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ drupal_set_message('HTTP Post is required.', 'error');
+ drupal_set_title('Error');
+ 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);
+
+ print implode('|', update_do_updates());
+}
+
+function update_progress_page_nojs() {
+ $new_op = 'do_update_nojs';
+ if ($_SERVER['REQUEST_METHOD'] == 'GET') {
+ list($percent, $message) = update_do_updates();
+ if ($percent == 100) {
+ $new_op = 'finished';
+ }
+ }
+ else {
+ // This is the first page so return some output immediately.
+ $percent = 0;
+ $message = 'Starting updates...';
+ }
+
+ drupal_set_html_head('<meta http-equiv="Refresh" content="0; URL=update.php?op='. $new_op .'">');
+ drupal_set_title('Updating');
+ $output = theme('progress_bar', $percent, $message);
+ $output .= '<p>Updating your site will take a few seconds.</p>';
+
+ return $output;
+}
+
+function update_finished_page() {
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=\"index.php\">main page</a>";
- $links[] = "<a href=\"index.php?q=admin\">administration pages</a>";
- $output = theme('item_list', $links);
- $output .= update_data($edit['start']);
- $output .= '<p>Updates were attempted. If you see no failures above, you may proceed happily to the <a href="index.php?q=admin">administration pages</a>. Otherwise, you may need to update your database manually.</p>';
+ $links[] = '<a href="">main page</a>';
+ $links[] = '<a href="?q=admin">administration pages</a>';
+ $output = '<p>Updates were attempted. If you see no failures below, you may proceed happily to the <a href="?q=admin">administration pages</a>. Otherwise, you may need to update your database manually. All errors have been <a href="?q=admin/logs">logged</a>.</p>';
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
+ if ($_SESSION['update_results']) {
+ $output .= '<div id="update-results">';
+ $output .= '<h2>The following queries were executed</h2>';
+ foreach ($_SESSION['update_results'] as $module => $updates) {
+ $output .= '<h3>'. $module .' module</h3>';
+ foreach ($updates as $number => $queries) {
+ $output .= '<h4>Update #'. $number .'</h4>';
+ $output .= '<ul>';
+ foreach ($queries as $query) {
+ if ($query['success']) {
+ $output .= '<li class="success">'. $query['query'] .'</li>';
+ }
+ else {
+ $output .= '<li class="failure"><strong>Failed:</strong> '. $query['query'] .'</li>';
+ }
+ }
+ if (!count($queries)) {
+ $output .= '<li class="none">No queries</li>';
+ }
+ $output .= '</ul>';
+ }
+ }
+ $output .= '</div>';
+ unset($_SESSION['update_results']);
+ }
+
return $output;
}
@@ -90,7 +517,7 @@ function update_info_page() {
$output = "<ol>\n";
$output .= "<li>Use this script to <strong>upgrade an existing Drupal installation</strong>. You don't need this script when installing Drupal from scratch.</li>";
$output .= "<li>Before doing anything, backup your database. This process will change your database and its values, and some things might get lost.</li>\n";
- $output .= "<li>Update your Drupal sources, check the notes below and <a href=\"update.php?op=update\">run the database upgrade script</a>. Don't upgrade your database twice as it may cause problems.</li>\n";
+ $output .= "<li>Update your Drupal sources, check the notes below and <a href=\"update.php?op=selection\">run the database upgrade script</a>. Don't upgrade your database twice as it may cause problems.</li>\n";
$output .= "<li>Go through the various administration pages to change the existing and new settings to your liking.</li>\n";
$output .= "</ol>";
$output .= '<p>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.</p>';
@@ -109,34 +536,48 @@ function update_access_denied_page() {
}
include_once './includes/bootstrap.inc';
+drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
drupal_maintenance_theme();
-if (isset($_GET["op"])) {
- drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+// Access check:
+if (($access_check == FALSE) || ($user->uid == 1)) {
+ update_include_install_files();
- // Access check:
- if (($access_check == 0) || ($user->uid == 1)) {
- $op = isset($_POST['op']) ? $_POST['op'] : '';
- switch ($op) {
- case t('Update'):
- $output = update_do_updates();
- break;
+ update_fix_schema_version();
+ update_fix_sessions();
+ update_fix_watchdog();
- default:
- $output = update_selection_page();
- break;
- }
- }
- else {
- $output = update_access_denied_page();
+ $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
+ switch ($op) {
+ case 'Update':
+ $output = update_update_page();
+ break;
+
+ case 'finished':
+ $output = update_finished_page();
+ break;
+
+ case 'do_update':
+ $output = update_do_update_page();
+ break;
+
+ case 'do_update_nojs':
+ $output = update_progress_page_nojs();
+ break;
+
+ case 'selection':
+ $output = update_selection_page();
+ break;
+
+ default:
+ $output = update_info_page();
+ break;
}
}
else {
- drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
- $output = update_info_page();
+ $output = update_access_denied_page();
}
if (isset($output)) {
print theme('maintenance_page', $output);
}
-