update.php 14.3 KB
Newer Older
1
<?php
Dries's avatar
   
Dries committed
2
// $Id$
Dries's avatar
   
Dries committed
3

4
5
6
/**
 * Root directory of Drupal installation.
 */
7
define('DRUPAL_ROOT', getcwd());
8

Dries's avatar
   
Dries committed
9
10
11
12
/**
 * @file
 * Administrative page for handling updates from one Drupal version to another.
 *
13
 * Point your browser to "http://www.example.com/update.php" and follow the
Dries's avatar
   
Dries committed
14
15
16
 * instructions.
 *
 * If you are not logged in as administrator, you will need to modify the access
17
18
 * 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
19
 */
Dries's avatar
   
Dries committed
20

21
22
23
24
25
26
/**
 * Global flag to identify update.php run, and so avoid various unwanted
 * operations, such as hook_init() and hook_exit() invokes, css/js preprocessing
 * and translation, and solve some theming issues. This flag is checked on several
 * places in Drupal code (not just update.php).
 */
27
28
define('MAINTENANCE_MODE', 'update');

29
function update_selection_page() {
30
  drupal_set_title('Drupal database update');
31
  $output = drupal_render(drupal_get_form('update_script_selection_form'));
32

33
34
  update_task_list('select');

35
36
37
38
  return $output;
}

function update_script_selection_form() {
39
40
  $form = array();
  $count = 0;
41
  $form['start'] = array(
42
43
44
    '#tree' => TRUE,
    '#type' => 'fieldset',
    '#collapsed' => TRUE,
45
    '#collapsible' => TRUE,
46
  );
47
48
49
50

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

51
52
  $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
  foreach ($modules as $module => $schema_version) {
53
    $pending = array();
54
    $updates = drupal_get_schema_versions($module);
55
56
57
58
59
60
61
    // Skip incompatible module updates completely, otherwise test schema versions.
    if (!update_check_incompatibility($module) && $updates !== FALSE && $schema_version >= 0) {
      // module_invoke returns NULL for nonexisting hooks, so if no updates
      // are removed, it will == 0.
      $last_removed = module_invoke($module, 'update_last_removed');
      if ($schema_version < $last_removed) {
        $form['start'][$module] = array(
62
63
          '#title' => $module,
          '#item'  => '<em>' . $module . '</em> module can not be updated. Its schema version is ' . $schema_version . '. Updates up to and including ' . $last_removed . ' have been removed in this release. In order to update <em>' . $module . '</em> module, you will first <a href="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.',
64
65
66
67
68
          '#prefix' => '<div class="warning">',
          '#suffix' => '</div>',
        );
        continue;
      }
69
      $updates = drupal_map_assoc($updates);
70
      foreach (array_keys($updates) as $update) {
71
        if ($update > $schema_version) {
72
          // The description for an update comes from its Doxygen.
73
          $func = new ReflectionFunction($module . '_update_' . $update);
74
75
76
77
78
79
80
81
82
83
          $description = str_replace(array("\n", '*', '/'), '', $func->getDocComment());
          $pending[] = "$update - $description";
          if (!isset($default)) {
            $default = $update;
          }
        }
      }
      if (!empty($pending)) {
        if (!isset($default)) {
          $default = $schema_version;
84
        }
85
86
87
88
        $form['start'][$module] = array(
          '#type' => 'hidden',
          '#value' => $default,
        );
89
        $form['start'][$module . '_updates'] = array(
90
91
          '#markup' => theme('item_list', $pending, $module . ' module'),
        );
92
      }
93
    }
94
    unset($default);
95
    $count = $count + count($pending);
96
97
  }

98
  if (empty($count)) {
99
100
101
102
103
104
105
106
107
108
109
    drupal_set_message(t('No pending updates.'));
    unset($form);
    $form['links'] = array(
      '#markup' => theme('item_list', update_helpful_links()),
    );
  }
  else {
    $form['help'] = array(
      '#markup' => '<p>The version of Drupal you are updating from has been automatically detected.</p>',
      '#weight' => -5,
    );
110
    $form['start']['#title'] = strtr('!num pending updates', array('!num' => $count));
111
112
113
114
115
116
    $form['has_js'] = array(
      '#type' => 'hidden',
      '#default_value' => FALSE,
    );
    $form['submit'] = array(
      '#type' => 'submit',
117
      '#value' => 'Apply pending updates',
118
119
    );
  }
120
  return $form;
121
122
123
}


124
function update_helpful_links() {
125
  // NOTE: we can't use l() here because the URL would point to 'update.php?q=admin'.
126
  $links[] = '<a href="' . base_path() . '">Front page</a>';
127
  $links[] = '<a href="' . base_path() . '?q=admin">Administration pages</a>';
128
129
130
131
132
133
  return $links;
}

function update_results_page() {
  drupal_set_title('Drupal database update');
  $links = update_helpful_links();
134

135
  update_task_list();
136
  // Report end result
137
  if (module_exists('dblog')) {
138
    $log_message = ' All errors have been <a href="' . base_path() . '?q=admin/reports/dblog">logged</a>.';
139
140
141
142
143
  }
  else {
    $log_message = ' All errors have been logged.';
  }

144
  if ($_SESSION['update_success']) {
145
    $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>';
146
147
  }
  else {
148
    list($module, $version) = array_pop(reset($_SESSION['updates_remaining']));
149
    $output = '<p class="error">The update process was aborted prematurely while running <strong>update #' . $version . ' in ' . $module . '.module</strong>.' . $log_message;
150
151
152
153
    if (module_exists('dblog')) {
      $output .= ' You may need to check the <code>watchdog</code> database table manually.';
    }
    $output .= '</p>';
154
155
  }

156
157
  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>";
158
  }
159

160
161
162
  $output .= theme('item_list', $links);

  // Output a list of queries executed
163
  if (!empty($_SESSION['update_results'])) {
164
165
166
    $output .= '<div id="update-results">';
    $output .= '<h2>The following queries were executed</h2>';
    foreach ($_SESSION['update_results'] as $module => $updates) {
167
      $output .= '<h3>' . $module . ' module</h3>';
168
      foreach ($updates as $number => $queries) {
169
        if ($number != '#abort') {
170
          $output .= '<h4>Update #' . $number . '</h4>';
171
172
173
          $output .= '<ul>';
          foreach ($queries as $query) {
            if ($query['success']) {
174
              $output .= '<li class="success">' . $query['query'] . '</li>';
175
176
            }
            else {
177
              $output .= '<li class="failure"><strong>Failed:</strong> ' . $query['query'] . '</li>';
178
            }
179
          }
180
181
          if (!count($queries)) {
            $output .= '<li class="none">No queries</li>';
182
183
184
185
186
187
188
          }
        }
        $output .= '</ul>';
      }
    }
    $output .= '</div>';
  }
189
190
  unset($_SESSION['update_results']);
  unset($_SESSION['update_success']);
191

192
  return $output;
193
194
}

195
function update_info_page() {
196
197
  // Change query-strings on css/js files to enforce reload for all users.
  _drupal_flush_css_js();
198
199
  // Flush the cache of all data for the update status module.
  if (db_table_exists('cache_update')) {
200
    cache_clear_all('*', 'cache_update', TRUE);
201
  }
202

203
  update_task_list('info');
Steven Wittens's avatar
Steven Wittens committed
204
  drupal_set_title('Drupal database update');
205
  $token = drupal_get_token('update');
206
207
208
209
  $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";
210
  $output .= '<li>Put your site into <a href="' . base_path() . '?q=admin/settings/maintenance-mode">maintenance mode</a>.</li>' . "\n";
211
212
213
  $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";
214
  $output .= '<form method="post" action="update.php?op=selection&amp;token=' . $token . '"><p><input type="submit" value="Continue" /></p></form>';
215
  $output .= "\n";
216
217
218
219
220
  return $output;
}

function update_access_denied_page() {
  drupal_set_title('Access denied');
221
  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>
222
<ol>
223
224
225
 <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>
226
227
 <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>';
228
}
229

230
231
232
233
234
235
/**
 * Add the update task list to the current page.
 */
function update_task_list($active = NULL) {
  // Default list of tasks.
  $tasks = array(
236
    'requirements' => 'Verify requirements',
237
    'info' => 'Overview',
238
    'select' => 'Review updates',
239
240
241
242
    'run' => 'Run updates',
    'finished' => 'Review log',
  );

243
  drupal_add_region_content('sidebar_first', theme('task_list', $tasks, $active));
244
245
}

246
/**
247
248
 * Returns (and optionally stores) extra requirements that only apply during
 * particular parts of the update.php process.
249
 */
250
251
252
253
function update_extra_requirements($requirements = NULL) {
  static $extra_requirements = array();
  if (isset($requirements)) {
    $extra_requirements += $requirements;
254
  }
255
  return $extra_requirements;
256
257
258
}

/**
259
 * Check update requirements and report any errors.
260
 */
261
262
263
264
265
266
267
268
269
270
271
272
273
274
function update_check_requirements() {
  // Check the system module and update.php requirements only.
  $requirements = module_invoke('system', 'requirements', 'update');
  $requirements += update_extra_requirements();
  $severity = drupal_requirements_severity($requirements);

  // If there are issues, report them.
  if ($severity == REQUIREMENT_ERROR) {
    update_task_list('requirements');
    drupal_set_title('Requirements problem');
    $status_report = theme('status_report', $requirements);
    $status_report .= 'Please check the error messages and <a href="' . request_uri() . '">try again</a>.';
    print theme('update_page', $status_report);
    exit();
275
  }
276
277
}

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

282
283
// We prepare a minimal bootstrap for the update requirements check to avoid
// reaching the PHP memory limit.
284
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
285
286
require_once DRUPAL_ROOT . '/includes/update.inc';

287
update_prepare_d7_bootstrap();
288

289
290
291
// Determine if the current user has access to run update.php.
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
$update_access_allowed = !empty($update_free_access) || $user->uid == 1;
292

293
294
295
296
297
// Only allow the requirements check to proceed if the current user has access
// to run updates (since it may expose sensitive information about the site's
// configuration).
$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
if (empty($op) && $update_access_allowed) {
298
299
300
  require_once DRUPAL_ROOT . '/includes/install.inc';
  require_once DRUPAL_ROOT . '/includes/file.inc';
  require_once DRUPAL_ROOT . '/modules/system/system.install';
301
302

  // Load module basics.
303
  include_once DRUPAL_ROOT . '/includes/module.inc';
304
305
  $module_list['system']['filename'] = 'modules/system/system.module';
  $module_list['filter']['filename'] = 'modules/filter/filter.module';
306
  module_list(TRUE, FALSE, $module_list);
307
308
309
310
  drupal_load('module', 'system');
  drupal_load('module', 'filter');

  // Set up $language, since the installer components require it.
311
  drupal_language_initialize();
312
313
314
315
316

  // Set up theme system for the maintenance page.
  drupal_maintenance_theme();

  // Check the update requirements for Drupal.
317
  update_check_requirements();
318

319
  // Redirect to the update information page if all requirements were met.
320
321
  install_goto('update.php?op=info');
}
322

323
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
Steven Wittens's avatar
Steven Wittens committed
324
drupal_maintenance_theme();
325

326
327
328
329
// 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);

330
331
// Only proceed with updates if the user is allowed to run them.
if ($update_access_allowed) {
332

333
334
  include_once DRUPAL_ROOT . '/includes/install.inc';
  include_once DRUPAL_ROOT . '/includes/batch.inc';
335
  drupal_load_updates();
Dries's avatar
   
Dries committed
336

337
  update_fix_d7_requirements();
338
  update_fix_compatibility();
339

340
341
  $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
  switch ($op) {
342
    // update.php ops
343

344
    case 'selection':
345
346
347
348
      if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) {
        $output = update_selection_page();
        break;
      }
349

350
    case 'Apply pending updates':
351
      if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) {
352
        update_batch($_POST['start'], $base_url . '/update.php?op=results', $base_url . '/update.php');
353
354
355
356
357
        break;
      }

    case 'info':
      $output = update_info_page();
358
359
      break;

360
361
    case 'results':
      $output = update_results_page();
362
363
      break;

364
    // Regular batch ops : defer to batch processing API
365
    default:
366
367
      update_task_list('run');
      $output = _batch_page();
368
      break;
Kjartan's avatar
Kjartan committed
369
370
371
  }
}
else {
372
  $output = update_access_denied_page();
373
}
374
if (isset($output) && $output) {
375
376
377
  // We defer the display of messages until all updates are done.
  $progress_page = ($batch = batch_get()) && isset($batch['running']);
  print theme('update_page', $output, !$progress_page);
378
}