update.php 32 KB
Newer Older
1
<?php
Dries's avatar
   
Dries committed
2
// $Id$
Dries's avatar
   
Dries committed
3
4
5
6
7

/**
 * @file
 * Administrative page for handling updates from one Drupal version to another.
 *
8
 * Point your browser to "http://www.example.com/update.php" and follow the
Dries's avatar
   
Dries committed
9
10
11
 * instructions.
 *
 * If you are not logged in as administrator, you will need to modify the access
12
13
 * check statement inside your settings.php file. After finishing the upgrade,
 * be sure to open settings.php again, and change it back to its original state!
Dries's avatar
   
Dries committed
14
 */
Dries's avatar
   
Dries committed
15

16
/**
17
18
 * Add a column to a database using syntax appropriate for PostgreSQL.
 * Save result of SQL commands in $ret array.
19
20
 *
 * Note: when you add a column with NOT NULL and you are not sure if there are
21
22
23
24
25
 * already rows in the table, you MUST also add DEFAULT. Otherwise PostgreSQL
 * won't work when the table is not empty, and db_add_column() will fail.
 * To have an empty string as the default, you must use: 'default' => "''"
 * in the $attributes array. If NOT NULL and DEFAULT are set the PostgreSQL
 * version will set values of the added column in old rows to the
26
27
28
 * DEFAULT value.
 *
 * @param $ret
29
 *   Array to which results will be added.
30
 * @param $table
31
 *   Name of the table, without {}
32
 * @param $column
33
 *   Name of the column
34
 * @param $type
35
 *   Type of column
36
 * @param $attributes
37
38
 *   Additional optional attributes. Recognized attributes:
 *     not null => TRUE|FALSE
39
 *     default  => NULL|FALSE|value (the value must be enclosed in '' marks)
40
 * @return
41
 *   nothing, but modifies $ret parameter.
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
 */
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]";
Dries's avatar
   
Dries committed
58
    }
59
  }
60
61

  $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column $type");
62
  if (!empty($default)) {
Dries's avatar
Dries committed
63
    $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET $default");
64
65
  }
  if (!empty($not_null)) {
Dries's avatar
Dries committed
66
67
    if (!empty($default)) {
      $ret[] = update_sql("UPDATE {". $table ."} SET $column = $default_val");
68
    }
69
70
    $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET NOT NULL");
  }
Dries's avatar
   
Dries committed
71
72
}

73
/**
74
75
 * Change a column definition using syntax appropriate for PostgreSQL.
 * Save result of SQL commands in $ret array.
76
 *
77
78
79
 * Remember that changing a column definition involves adding a new column
 * and dropping an old one. This means that any indices, primary keys and
 * sequences from serial-type columns are dropped and might need to be
80
81
 * recreated.
 *
82
 * @param $ret
83
 *   Array to which results will be added.
84
 * @param $table
85
 *   Name of the table, without {}
86
 * @param $column
87
 *   Name of the column to change
88
 * @param $column_new
89
 *   New name for the column (set to the same as $column if you don't want to change the name)
90
 * @param $type
91
 *   Type of column
92
 * @param $attributes
93
94
95
 *   Additional optional attributes. Recognized attributes:
 *     not null => TRUE|FALSE
 *     default  => NULL|FALSE|value (with or without '', it won't be added)
96
 * @return
97
 *   nothing, but modifies $ret parameter.
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
 */
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"); }
122
  $ret[] = update_sql("ALTER TABLE {". $table ."} DROP ". $column ."_old");
123
124
125
}

/**
126
 * If the schema version for Drupal core is stored in the variables table
127
128
129
130
131
132
133
134
135
 * (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
136
    // temporary variables to prevent the updates from being executed twice and
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
    // 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;

    }
155
156
157
    // The schema_version column (added below) was changed during 4.7beta.
    // Update_170 is only for those beta users.
    variable_set('update_170_done', TRUE);
158
159
160
161
162
163
164
165
166
167

    $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
168
      '2005-04-14' => 128, '2005-05-06' => 128, '2005-05-07' => 128,
169
170
171
172
173
174
175
176
177
178
179
180
      '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,
    );

181
    // Add schema version column
182
183
184
    switch ($GLOBALS['db_type']) {
      case 'pgsql':
        $ret = array();
185
        db_add_column($ret, 'system', 'schema_version', 'smallint', array('not null' => TRUE, 'default' => -1));
186
187
188
189
        break;

      case 'mysql':
      case 'mysqli':
190
        db_query('ALTER TABLE {system} ADD schema_version smallint(3) not null default -1');
191
192
        break;
    }
193
194
    // Set all enabled (contrib) modules to schema version 0 (installed)
    db_query('UPDATE {system} SET schema_version = 0 WHERE status = 1');
195

196
    // Set schema version for core
197
    drupal_set_installed_schema_version('system', $sql_updates[$update_start]);
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
    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();

213
  if (drupal_get_installed_schema_version('system') < 130 && !variable_get('update_sessions_fixed', FALSE)) {
214
215
216
217
218
219
220
221
222
223
224
    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);
  }
}

225
226
227
228
/**
 * System update 115 changes the watchdog table, which breaks the update
 * script's ability to use logging. This changes the table appropriately.
 *
229
 * This code, including the 'update_watchdog_115_fixed' variable, may be removed
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
 * when update 115 is removed. It is part of the Drupal 4.5 to 4.7 migration.
 */
function update_fix_watchdog_115() {
  if (drupal_get_installed_schema_version('system') < 115 && !variable_get('update_watchdog_115_fixed', FALSE)) {
    if ($GLOBALS['db_type'] == 'mysql') {
      $ret[] = update_sql("ALTER TABLE {watchdog} ADD severity tinyint(3) unsigned NOT NULL default '0'");
    }
    else if ($GLOBALS['db_type'] == 'pgsql') {
      $ret[] = update_sql('ALTER TABLE {watchdog} ADD severity smallint');
      $ret[] = update_sql('UPDATE {watchdog} SET severity = 0');
      $ret[] = update_sql('ALTER TABLE {watchdog} ALTER COLUMN severity SET NOT NULL');
      $ret[] = update_sql('ALTER TABLE {watchdog} ALTER COLUMN severity SET DEFAULT 0');
    }

    variable_set('update_watchdog_115_fixed', TRUE);
  }
}

248
249
250
251
/**
 * System update 142 changes the watchdog table, which breaks the update
 * script's ability to use logging. This changes the table appropriately.
 *
252
 * This code, including the 'update_watchdog_fixed' variable, may be removed
253
254
255
 * when update 142 is removed. It is part of the Drupal 4.6 to 4.7 migration.
 */
function update_fix_watchdog() {
256
  if (drupal_get_installed_schema_version('system') < 142 && !variable_get('update_watchdog_fixed', FALSE)) {
257
258
    switch ($GLOBALS['db_type']) {
      case 'pgsql':
259
        $ret = array();
260
261
262
263
        db_add_column($ret, 'watchdog', 'referer', 'varchar(128)', array('not null' => TRUE, 'default' => "''"));
        break;
      case 'mysql':
      case 'mysqli':
264
        db_query("ALTER TABLE {watchdog} ADD COLUMN referer varchar(128) NOT NULL");
265
266
267
268
269
270
271
        break;
    }

    variable_set('update_watchdog_fixed', TRUE);
  }
}

272
273
274
275
276
277
278
279
/**
 * Perform one update and store the results which will later be displayed on
 * the finished page.
 *
 * @param $module
 *   The module whose update will be run.
 * @param $number
 *   The update number to run.
280
 * @param $context
Dries's avatar
Dries committed
281
 *   The batch context array
282
 */
283
284
285
function update_do_one($module, $number, &$context) {
  $function = $module .'_update_'. $number;
  if (function_exists($function)) {
286
    $ret = $function($context['sandbox']);
287
288
  }

289
  if (isset($ret['#finished'])) {
290
    $context['finished'] = $ret['#finished'];
291
292
    unset($ret['#finished']);
  }
293

294
295
  if (!isset($context['results'][$module])) {
    $context['results'][$module] = array();
296
  }
297
298
  if (!isset($context['results'][$module][$number])) {
    $context['results'][$module][$number] = array();
299
  }
300
  $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);;
301

302
  if ($context['finished'] == 1) {
303
304
305
    // Update the installed version
    drupal_set_installed_schema_version($module, $number);
  }
306

307
  $context['message'] = t('Updating @module module', array('@module' => $module));
308
309
310
}

function update_selection_page() {
311
312
  $output = '<p>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>Click Update to start the update process.</p>';
313

314
315
316
  drupal_set_title('Drupal database update');
  $output .= drupal_get_form('update_script_selection_form');

317
318
  update_task_list('select');

319
320
321
322
  return $output;
}

function update_script_selection_form() {
323
  $form = array();
324
  $form['start'] = array(
325
326
327
328
329
330
    '#tree' => TRUE,
    '#type' => 'fieldset',
    '#title' => 'Select versions',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
331
332
333
334

  // Ensure system.module's updates appear first
  $form['start']['system'] = array();

335
  foreach (module_list() as $module) {
336
337
338
    $updates = drupal_get_schema_versions($module);
    if ($updates !== FALSE) {
      $updates = drupal_map_assoc($updates);
339
      $updates[] = 'No updates available';
340
341
342
343
344
345
346
      $default = drupal_get_installed_schema_version($module);
      foreach (array_keys($updates) as $update) {
        if ($update > $default) {
          $default = $update;
          break;
        }
      }
347
348
349

      $form['start'][$module] = array(
        '#type' => 'select',
350
        '#title' => $module .' module',
351
        '#default_value' => $default,
352
353
354
355
356
357
358
        '#options' => $updates,
      );
    }
  }

  $form['has_js'] = array(
    '#type' => 'hidden',
359
    '#default_value' => FALSE,
360
    '#attributes' => array('id' => 'edit-has_js'),
361
362
  );
  $form['submit'] = array(
Steven Wittens's avatar
Steven Wittens committed
363
    '#type' => 'submit',
364
    '#value' => 'Update',
365
  );
366
  return $form;
367
368
}

369
370
371
372
function update_batch() {
  global $base_url;

  $operations = array();
373
  // Set the installed version so updates start at the correct place.
374
  foreach ($_POST['start'] as $module => $version) {
375
    drupal_set_installed_schema_version($module, $version - 1);
376
377
    $updates = drupal_get_schema_versions($module);
    $max_version = max($updates);
378
    if ($version <= $max_version) {
379
380
      foreach ($updates as $update) {
        if ($update >= $version) {
381
          $operations[] = array('update_do_one', array($module, $update));
382
        }
383
384
385
      }
    }
  }
386
387
388
389
  $batch = array(
    'operations' => $operations,
    'title' => 'Updating',
    'init_message' => 'Starting updates',
Dries's avatar
Dries committed
390
    'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
391
392
393
394
    'finished' => 'update_finished',
  );
  batch_set($batch);
  batch_process($base_url .'/update.php?op=results', $base_url .'/update.php');
395
396
}

397
398
399
400
401
402
function update_finished($success, $results, $operations) {
  // clear the caches in case the data has been updated.
  cache_clear_all('*', 'cache', TRUE);
  cache_clear_all('*', 'cache_page', TRUE);
  cache_clear_all('*', 'cache_filter', TRUE);
  drupal_clear_css_cache();
403
  drupal_clear_js_cache();
404

405
406
407
  $_SESSION['update_results'] = $results;
  $_SESSION['update_success'] = $success;
  $_SESSION['updates_remaining'] = $operations;
408
409
}

410
function update_results_page() {
411
412
  drupal_set_title('Drupal database update');
  // NOTE: we can't use l() here because the URL would point to 'update.php?q=admin'.
Dries's avatar
Dries committed
413
414
  $links[] = '<a href="'. base_path() .'">Main page</a>';
  $links[] = '<a href="'. base_path() .'?q=admin">Administration pages</a>';
415

416
  update_task_list();
417
  // Report end result
418
419
420
421
422
423
424
  if (module_exists('dblog')) {
    $log_message = ' All errors have been <a href="'. base_path() .'?q=admin/logs/dblog">logged</a>.';
  }
  else {
    $log_message = ' All errors have been logged.';
  }

425
  if ($_SESSION['update_success']) {
426
    $output = '<p>Updates were attempted. If you see no failures below, you may proceed happily to the <a href="'. base_path() .'?q=admin">administration pages</a>. Otherwise, you may need to update your database manually.'. $log_message .'</p>';
427
428
  }
  else {
429
    list($module, $version) = array_pop(reset($_SESSION['updates_remaining']));
430
431
432
433
434
    $output = '<p class="error">The update process was aborted prematurely while running <strong>update #'. $version .' in '. $module .'.module</strong>.'. $log_message;
    if (module_exists('dblog')) {
      $output .= ' You may need to check the <code>watchdog</code> database table manually.';
    }
    $output .= '</p>';
435
436
  }

437
438
  if (!empty($GLOBALS['update_free_access'])) {
    $output .= "<p><strong>Reminder: don't forget to set the <code>\$update_free_access</code> value in your <code>settings.php</code> file back to <code>FALSE</code>.</strong></p>";
439
  }
440

441
442
443
  $output .= theme('item_list', $links);

  // Output a list of queries executed
444
  if (!empty($_SESSION['update_results'])) {
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    $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>';
  }
468
469
  unset($_SESSION['update_results']);
  unset($_SESSION['update_success']);
470

471
  return $output;
472
473
}

474
function update_info_page() {
475
  update_task_list('info');
Steven Wittens's avatar
Steven Wittens committed
476
  drupal_set_title('Drupal database update');
477
478
479
480
481
482
483
484
485
486
  $output = '<p>Use this utility to update your database whenever a new release of Drupal or a module is installed.</p><p>For more detailed information, 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>';
  $output .= "<ol>\n";
  $output .= "<li><strong>Back up your database</strong>. This process will change your database values and in case of emergency you may need to revert to a backup.</li>\n";
  $output .= "<li><strong>Back up your code</strong>. Hint: when backing up module code, do not leave that backup in the 'modules' or 'sites/*/modules' directories as this may confuse Drupal's auto-discovery mechanism.</li>\n";
  $output .= '<li>Put your site into <a href="'. base_path() .'?q=admin/settings/site-maintenance">maintenance mode</a>.</li>'."\n";
  $output .= "<li>Install your new files in the appropriate location, as described in the handbook.</li>\n";
  $output .= "</ol>\n";
  $output .= "<p>When you have performed the steps above, you may proceed.</p>\n";
  $output .= '<form method="post" action="update.php?op=selection"><input type="submit" value="Continue" /></form>';
  $output .= "\n";
487
488
489
490
491
  return $output;
}

function update_access_denied_page() {
  drupal_set_title('Access denied');
492
  return '<p>Access denied. You are not authorized to access this page. Please log in as the admin user (the first user you created). If you cannot log in, you will have to edit <code>settings.php</code> to bypass this access check. To do this:</p>
493
<ol>
494
495
496
 <li>With a text editor find the settings.php file on your system. From the main Drupal directory that you installed all the files into, go to <code>sites/your_site_name</code> if such directory exists, or else to <code>sites/default</code> which applies otherwise.</li>
 <li>There is a line inside your settings.php file that says <code>$update_free_access = FALSE;</code>. Change it to <code>$update_free_access = TRUE;</code>.</li>
 <li>As soon as the update.php script is done, you must change the settings.php file back to its original form with <code>$update_free_access = FALSE;</code>.</li>
497
498
 <li>To avoid having this problem in future, remember to log in to your website as the admin user (the user you first created) before you backup your database at the beginning of the update process.</li>
</ol>';
499
}
500

501
// This code may be removed later. It is part of the Drupal 4.5 to 4.8 migration.
502
503
function update_fix_system_table() {
  drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
504
505
506
507
508
509
  $core_modules = array('aggregator', 'archive', 'block', 'blog', 'blogapi', 'book', 'comment', 'contact', 'drupal', 'filter', 'forum', 'help', 'legacy', 'locale', 'menu', 'node', 'page', 'path', 'ping', 'poll', 'profile', 'search', 'statistics', 'story', 'system', 'taxonomy', 'throttle', 'tracker', 'upload', 'user', 'watchdog');
  foreach ($core_modules as $module) {
    $old_path = "modules/$module.module";
    $new_path = "modules/$module/$module.module";
    db_query("UPDATE {system} SET filename = '%s' WHERE filename = '%s'", $new_path, $old_path);
  }
510
511
512
513
514
515
516
517
518
519
  $row = db_fetch_object(db_query_range('SELECT * FROM {system}', 0, 1));
  if (!isset($row->weight)) {
    $ret = array();
    switch ($GLOBALS['db_type']) {
      case 'pgsql':
        db_add_column($ret, 'system', 'weight', 'smallint', array('not null' => TRUE, 'default' => 0));
        $ret[] = update_sql('CREATE INDEX {system}_weight_idx ON {system} (weight)');
        break;
      case 'mysql':
      case 'mysqli':
520
        $ret[] = update_sql("ALTER TABLE {system} ADD weight tinyint(2) default '0' NOT NULL, ADD KEY (weight)");
521
522
523
524
525
        break;
    }
  }
}

526
// This code may be removed later. It is part of the Drupal 4.6 to 4.7 migration.
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
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.
546
  $result = db_fetch_array(db_query('SHOW CREATE TABLE {access}'));
547
  if (!preg_match('/utf8/i', array_pop($result))) {
Dries's avatar
Dries committed
548
    update_convert_table_utf8('access');
549
550
551
552
553
554
555
556
557
  }

  // Don't run again
  variable_set('update_access_fixed', TRUE);
}

/**
 * Convert a single MySQL table to UTF-8.
 *
558
 * We change all text columns to their corresponding binary type,
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
 * 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));
  }
Dries's avatar
Dries committed
599
  return $ret;
600
601
}

602
603
604
/**
 * Create tables for the split cache.
 *
Dries's avatar
Dries committed
605
 * This is part of the Drupal 4.7.x to 5.x migration.
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
 */
function update_create_cache_tables() {

  // If cache_filter exists, update is not necessary
  if (db_table_exists('cache_filter')) {
    return;
  }

  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("CREATE TABLE {cache_filter} (
        cid varchar(255) NOT NULL default '',
        data longblob,
        expire int NOT NULL default '0',
        created int NOT NULL default '0',
        headers text,
        PRIMARY KEY (cid),
        INDEX expire (expire)
      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
      $ret[] = update_sql("CREATE TABLE {cache_menu} (
        cid varchar(255) NOT NULL default '',
        data longblob,
        expire int NOT NULL default '0',
        created int NOT NULL default '0',
        headers text,
        PRIMARY KEY (cid),
        INDEX expire (expire)
      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
      $ret[] = update_sql("CREATE TABLE {cache_page} (
637
        cid varchar(255) BINARY NOT NULL default '',
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
        data longblob,
        expire int NOT NULL default '0',
         created int NOT NULL default '0',
        headers text,
        PRIMARY KEY (cid),
        INDEX expire (expire)
      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
      break;
    case 'pgsql':
      $ret[] = update_sql("CREATE TABLE {cache_filter} (
        cid varchar(255) NOT NULL default '',
        data bytea,
        expire int NOT NULL default '0',
        created int NOT NULL default '0',
        headers text,
        PRIMARY KEY (cid)
     )");
     $ret[] = update_sql("CREATE TABLE {cache_menu} (
       cid varchar(255) NOT NULL default '',
       data bytea,
       expire int NOT NULL default '0',
       created int NOT NULL default '0',
       headers text,
       PRIMARY KEY (cid)
     )");
     $ret[] = update_sql("CREATE TABLE {cache_page} (
       cid varchar(255) NOT NULL default '',
       data bytea,
       expire int NOT NULL default '0',
       created int NOT NULL default '0',
       headers text,
       PRIMARY KEY (cid)
     )");
     $ret[] = update_sql("CREATE INDEX {cache_filter}_expire_idx ON {cache_filter} (expire)");
     $ret[] = update_sql("CREATE INDEX {cache_menu}_expire_idx ON {cache_menu} (expire)");
     $ret[] = update_sql("CREATE INDEX {cache_page}_expire_idx ON {cache_page} (expire)");
     break;
  }
  return $ret;
}

679
680
681
682
683
684
685
686
687
688
689
690
/**
 * Create the batch table.
 *
 * This is part of the Drupal 5.x to 6.x migration.
 */
function update_create_batch_table() {

  // If batch table exists, update is not necessary
  if (db_table_exists('batch')) {
    return;
  }

691
692
693
694
695
696
697
698
699
700
701
  $schema['batch'] = array(
    'fields' => array(
      'bid'       => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
      'token'     => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE),
      'timestamp' => array('type' => 'int', 'not null' => TRUE),
      'batch'     => array('type' => 'text', 'not null' => FALSE, 'size' => 'big')
    ),
    'primary key' => array('bid'),
    'indexes' => array('token' => array('token')),
  );

702
  $ret = array();
703
  db_create_table($ret, 'batch', $schema['batch']);
704
705
706
  return $ret;
}

707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
/**
 * Disable anything in the {system} table that is not compatible with the
 * current version of Drupal core.
 */
function update_fix_compatibility() {
  $ret = array();
  $incompatible = array();
  $themes = system_theme_data();
  $modules = module_rebuild_cache();
  $query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
  while ($result = db_fetch_object($query)) {
    $name = $result->name;
    $file = array();
    if ($result->type == 'module' && isset($modules[$name])) {
      $file = $modules[$name];
    }
    else if ($result->type == 'theme' && isset($themes[$name])) {
      $file = $themes[$name];
    }
    if (!isset($file)
        || !isset($file->info['core'])
728
729
        || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY
        || version_compare(phpversion(), $file->info['php']) < 0) {
730
731
732
733
734
735
736
737
738
      $incompatible[] = $name;
    }
  }
  if (!empty($incompatible)) {
    $ret[] = update_sql("UPDATE {system} SET status = 0 WHERE name IN ('". implode("','", $incompatible) ."')");
  }
  return $ret;
}

739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
/**
 * Perform Drupal 5.x to 6.x updates that are required for update.php
 * to function properly.
 *
 * This function runs when update.php is run the first time for 6.x,
 * even before updates are selected or performed.  It is important
 * that if updates are not ultimately performed that no changes are
 * made which make it impossible to continue using the prior version.
 * Just adding columns is safe.  However, renaming the
 * system.description column to owner is not.  Therefore, we add the
 * system.owner column and leave it to system_update_6008() to copy
 * the data from description and remove description. The same for
 * renaming locales_target.locale to locales_target.language, which
 * will be finished by locale_update_6002().
 */
function update_fix_d6_requirements() {
  $ret = array();

  if (drupal_get_installed_schema_version('system') < 6000 && !variable_get('update_d6_requirements', FALSE)) {
    $spec = array('type' => 'int', 'size' => 'small', 'default' => 0, 'not null' => TRUE);
    db_add_field($ret, 'cache', 'serialized', $spec);
    db_add_field($ret, 'cache_filter', 'serialized', $spec);
    db_add_field($ret, 'cache_page', 'serialized', $spec);
    db_add_field($ret, 'cache_menu', 'serialized', $spec);

    db_add_field($ret, 'system', 'info', array('type' => 'text'));
    db_add_field($ret, 'system', 'owner', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
    if (db_table_exists('locales_target')) {
      db_add_field($ret, 'locales_target', 'language', array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => ''));
    }
    if (db_table_exists('locales_source')) {
      db_add_field($ret, 'locales_source', 'textgroup', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'default'));
771
      db_add_field($ret, 'locales_source', 'version', array('type' => 'varchar', 'length' => 20, 'not null' => TRUE, 'default' => 'none'));
772
773
    }
    variable_set('update_d6_requirements', TRUE);
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788

    // Create the cache_block table. See system_update_6027() for more details.
    $schema['cache_block'] = array(
      'fields' => array(
        'cid'        => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
        'data'       => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'),
        'expire'     => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
        'created'    => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
        'headers'    => array('type' => 'text', 'not null' => FALSE),
        'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0)
      ),
      'indexes' => array('expire' => array('expire')),
      'primary key' => array('cid'),
    );
    db_create_table($ret, 'cache_block', $schema['cache_block']);
789
790
791
792
793
  }

  return $ret;
}

794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
/**
 * Add the update task list to the current page.
 */
function update_task_list($active = NULL) {
  // Default list of tasks.
  $tasks = array(
    'info' => 'Overview',
    'select' => 'Select updates',
    'run' => 'Run updates',
    'finished' => 'Review log',
  );

  drupal_set_content('left', theme_task_list($tasks, $active));
}

809
// Some unavoidable errors happen because the database is not yet up-to-date.
810
// Our custom error handler is not yet installed, so we just suppress them.
811
812
ini_set('display_errors', FALSE);

813
include_once './includes/bootstrap.inc';
814
update_fix_system_table();
815

816
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
Steven Wittens's avatar
Steven Wittens committed
817
drupal_maintenance_theme();
818

819
820
821
// This must happen *after* drupal_bootstrap(), since it calls
// variable_(get|set), which only works after a full bootstrap.
update_fix_access_table();
822
update_create_cache_tables();
823
update_create_batch_table();
824

825
826
827
828
// Turn error reporting back on. From now on, only fatal errors (which are
// not passed through the error handler) will cause a message to be printed.
ini_set('display_errors', TRUE);

829
// Access check:
830
if (!empty($update_free_access) || $user->uid == 1) {
831

832
  include_once './includes/install.inc';
833
  include_once './includes/batch.inc';
834
  drupal_load_updates();
Dries's avatar
   
Dries committed
835

836
  update_fix_schema_version();
837
  update_fix_watchdog_115();
838
  update_fix_watchdog();
839
  update_fix_sessions();
840
  update_fix_d6_requirements();
841
  update_fix_compatibility();
842

843
844
  $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
  switch ($op) {
845
846
847
    // update.php ops
    case '':
      $output = update_info_page();
848
849
      break;

850
851
    case 'selection':
      $output = update_selection_page();
852
853
      break;

854
855
    case 'Update':
      update_batch();
856
857
      break;

858
859
    case 'results':
      $output = update_results_page();
860
861
      break;

862
    // Regular batch ops : defer to batch processing API
863
    default:
864
865
      update_task_list('run');
      $output = _batch_page();
866
      break;
Kjartan's avatar
Kjartan committed
867
868
869
  }
}
else {
870
  $output = update_access_denied_page();
871
}
872
if (isset($output) && $output) {
873
  print theme('maintenance_page', $output);
874
}