install.php 44.9 KB
Newer Older
1 2 3 4 5
<?php
// $Id$

require_once './includes/install.inc';

6 7 8
/**
 * Global flag to indicate that site is in installation mode.
 */
9 10
define('MAINTENANCE_MODE', 'install');

11 12 13 14 15 16 17 18 19 20 21 22 23 24
/**
 * The Drupal installation happens in a series of steps. We begin by verifying
 * that the current environment meets our minimum requirements. We then go
 * on to verify that settings.php is properly configured. From there we
 * connect to the configured database and verify that it meets our minimum
 * requirements. Finally we can allow the user to select an installation
 * profile and complete the installation process.
 *
 * @param $phase
 *   The installation phase we should proceed to.
 */
function install_main() {
  require_once './includes/bootstrap.inc';
  drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
25

26 27 28
  // This must go after drupal_bootstrap(), which unsets globals!
  global $profile, $install_locale, $conf;

29
  require_once './modules/system/system.install';
30
  require_once './includes/file.inc';
31

32 33 34
  // Ensure correct page headers are sent (e.g. caching)
  drupal_page_header();

35 36 37
  // Set up $language, so t() caller functions will still work.
  drupal_init_language();

38 39 40 41 42 43 44 45 46 47 48
  // Load module basics (needed for hook invokes).
  include_once './includes/module.inc';
  $module_list['system']['filename'] = 'modules/system/system.module';
  $module_list['filter']['filename'] = 'modules/filter/filter.module';
  module_list(TRUE, FALSE, FALSE, $module_list);
  drupal_load('module', 'system');
  drupal_load('module', 'filter');

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

49 50 51 52
  // Check existing settings.php.
  $verify = install_verify_settings();

  if ($verify) {
53 54 55 56 57 58
    // Since we have a database connection, we use the normal cache system.
    // This is important, as the installer calls into the Drupal system for
    // the clean URL checks, so we should maintain the cache properly.
    require_once './includes/cache.inc';
    $conf['cache_inc'] = './includes/cache.inc';

59 60 61
    // Establish a connection to the database.
    require_once './includes/database.inc';
    db_set_active();
62

63
    // Check if Drupal is installed.
64 65
    $task = install_verify_drupal();
    if ($task == 'done') {
66 67 68
      install_already_done_error();
    }
  }
69
  else {
70 71 72 73 74 75 76
    // Since no persistent storage is available yet, and functions that check
    // for cached data will fail, we temporarily replace the normal cache
    // system with a stubbed-out version that short-circuits the actual
    // caching process and avoids any errors.
    require_once './includes/cache-install.inc';
    $conf['cache_inc'] = './includes/cache-install.inc';

77 78
    $task = NULL;
  }
79 80 81 82 83 84

  // Decide which profile to use.
  if (!empty($_GET['profile'])) {
    $profile = preg_replace('/[^a-zA-Z_0-9]/', '', $_GET['profile']);
  }
  elseif ($profile = install_select_profile()) {
85
    install_goto("install.php?profile=$profile");
86 87
  }
  else {
88
    install_no_profile_error();
89
  }
drumm's avatar
drumm committed
90

91 92 93
  // Load the profile.
  require_once "./profiles/$profile/$profile.profile";

drumm's avatar
drumm committed
94 95 96 97 98 99 100 101
  // Locale selection
  if (!empty($_GET['locale'])) {
    $install_locale = preg_replace('/[^a-zA-Z_0-9]/', '', $_GET['locale']);
  }
  elseif (($install_locale = install_select_locale($profile)) !== FALSE) {
    install_goto("install.php?profile=$profile&locale=$install_locale");
  }

102 103 104
  // Tasks come after the database is set up
  if (!$task) {
    // Check the installation requirements for Drupal and this profile.
105
    install_check_requirements($profile, $verify);
106

107 108
    // Verify existence of all required modules.
    $modules = drupal_verify_profile($profile, $install_locale);
109 110 111 112 113 114 115 116

    // If any error messages are set now, it means a requirement problem.
    $messages = drupal_set_message();
    if (!empty($messages['error'])) {
      install_task_list('requirements');
      drupal_set_title(st('Requirements problem'));
      print theme('install_page', '');
      exit;
117
    }
118

119 120 121 122 123
    // Change the settings.php information if verification failed earlier.
    // Note: will trigger a redirect if database credentials change.
    if (!$verify) {
      install_change_settings($profile, $install_locale);
    }
124

125 126 127 128 129 130
    // Install system.module.
    drupal_install_system();
    // Save the list of other modules to install for the 'profile-install'
    // task. variable_set() can be used now that system.module is installed
    // and drupal is bootstrapped.
    variable_set('install_profile_modules', array_diff($modules, array('system')));
131
  }
132

133 134
  // The database is set up, turn to further tasks.
  install_tasks($profile, $task);
135 136 137 138 139 140
}

/**
 * Verify if Drupal is installed.
 */
function install_verify_drupal() {
141
  // Read the variable manually using the @ so we don't trigger an error if it fails.
142
  $result = @db_query("SELECT value FROM {variable} WHERE name = '%s'", 'install_task');
143 144 145
  if ($result) {
    return unserialize(db_result($result));
  }
146 147 148 149 150 151 152 153 154
}

/**
 * Verify existing settings.php
 */
function install_verify_settings() {
  global $db_prefix, $db_type, $db_url;

  // Verify existing settings (if any).
155
  if (!empty($db_url)) {
156 157 158
    // We need this because we want to run form_get_errors.
    include_once './includes/form.inc';

159
    $url = parse_url(is_array($db_url) ? $db_url['default'] : $db_url);
160
    $db_user = urldecode($url['user']);
161
    $db_pass = isset($url['pass']) ? urldecode($url['pass']) : NULL;
162
    $db_host = urldecode($url['host']);
163
    $db_port = isset($url['port']) ? urldecode($url['port']) : '';
164
    $db_path = ltrim(urldecode($url['path']), '/');
165
    $settings_file = './' . conf_path(FALSE, TRUE) . '/settings.php';
166

167 168
    $form_state = array();
    _install_settings_form_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_port, $db_path, $settings_file, $form_state);
169 170 171 172 173 174 175 176 177 178
    if (!form_get_errors()) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Configure and rewrite settings.php.
 */
179 180
function install_change_settings($profile = 'default', $install_locale = '') {
  global $db_url, $db_type, $db_prefix;
181

182
  $url = parse_url(is_array($db_url) ? $db_url['default'] : $db_url);
183 184 185
  $db_user = isset($url['user']) ? urldecode($url['user']) : '';
  $db_pass = isset($url['pass']) ? urldecode($url['pass']) : '';
  $db_host = isset($url['host']) ? urldecode($url['host']) : '';
186
  $db_port = isset($url['port']) ? urldecode($url['port']) : '';
187
  $db_path = ltrim(urldecode($url['path']), '/');
188 189
  $conf_path = './' . conf_path(FALSE, TRUE);
  $settings_file = $conf_path . '/settings.php';
190 191 192

  // We always need this because we want to run form_get_errors.
  include_once './includes/form.inc';
193
  install_task_list('database');
194

195 196 197 198 199 200 201 202
  if ($db_url == 'mysql://username:password@localhost/databasename') {
    $db_user = $db_pass = $db_path = '';
  }
  elseif (!empty($db_url)) {
    // Do not install over a configured settings.php.
    install_already_done_error();
  }

203
  $output = drupal_get_form('install_settings_form', $profile, $install_locale, $settings_file, $db_url, $db_type, $db_prefix, $db_user, $db_pass, $db_host, $db_port, $db_path);
drumm's avatar
drumm committed
204
  drupal_set_title(st('Database configuration'));
205 206 207
  print theme('install_page', $output);
  exit;
}
208

209

210 211 212
/**
 * Form API array definition for install_settings.
 */
213
function install_settings_form(&$form_state, $profile, $install_locale, $settings_file, $db_url, $db_type, $db_prefix, $db_user, $db_pass, $db_host, $db_port, $db_path) {
214 215 216
  if (empty($db_host)) {
    $db_host = 'localhost';
  }
217
  $db_types = drupal_detect_database_types();
218 219 220 221 222 223

  // If both 'mysql' and 'mysqli' are available, we disable 'mysql':
  if (isset($db_types['mysqli'])) {
    unset($db_types['mysql']);
  }

224 225
  if (count($db_types) == 0) {
    $form['no_db_types'] = array(
drumm's avatar
drumm committed
226
      '#value' => st('Your web server does not appear to support any common database types. Check with your hosting provider to see if they offer any databases that <a href="@drupal-databases">Drupal supports</a>.', array('@drupal-databases' => 'http://drupal.org/node/270#database')),
227 228 229 230 231
    );
  }
  else {
    $form['basic_options'] = array(
      '#type' => 'fieldset',
drumm's avatar
drumm committed
232
      '#title' => st('Basic options'),
233
      '#description' => '<p>' . st('To set up your @drupal database, enter the following information.', array('@drupal' => drupal_install_profile_name())) . '</p>',
234 235 236 237 238
    );

    if (count($db_types) > 1) {
      $form['basic_options']['db_type'] = array(
        '#type' => 'radios',
drumm's avatar
drumm committed
239
        '#title' => st('Database type'),
240
        '#required' => TRUE,
241 242
        '#options' => $db_types,
        '#default_value' => ($db_type ? $db_type : current($db_types)),
243
        '#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_name())),
244
      );
245
      $db_path_description = st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_name()));
246 247 248
    }
    else {
      if (count($db_types) == 1) {
249
        $db_types = array_values($db_types);
250 251 252 253
        $form['basic_options']['db_type'] = array(
          '#type' => 'hidden',
          '#value' => $db_types[0],
        );
254
        $db_path_description = st('The name of the %db_type database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('%db_type' => $db_types[0], '@drupal' => drupal_install_profile_name()));
255 256 257 258 259 260
      }
    }

    // Database name
    $form['basic_options']['db_path'] = array(
      '#type' => 'textfield',
drumm's avatar
drumm committed
261
      '#title' => st('Database name'),
262 263 264 265 266 267 268 269 270 271
      '#default_value' => $db_path,
      '#size' => 45,
      '#maxlength' => 45,
      '#required' => TRUE,
      '#description' => $db_path_description
    );

    // Database username
    $form['basic_options']['db_user'] = array(
      '#type' => 'textfield',
drumm's avatar
drumm committed
272
      '#title' => st('Database username'),
273 274 275 276 277 278 279 280 281
      '#default_value' => $db_user,
      '#size' => 45,
      '#maxlength' => 45,
      '#required' => TRUE,
    );

    // Database username
    $form['basic_options']['db_pass'] = array(
      '#type' => 'password',
drumm's avatar
drumm committed
282
      '#title' => st('Database password'),
283 284 285 286 287 288 289
      '#default_value' => $db_pass,
      '#size' => 45,
      '#maxlength' => 45,
    );

    $form['advanced_options'] = array(
      '#type' => 'fieldset',
drumm's avatar
drumm committed
290
      '#title' => st('Advanced options'),
291 292
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
293
      '#description' => st("These options are only necessary for some sites. If you're not sure what you should enter here, leave the default settings or check with your hosting provider.")
294 295 296 297 298
    );

    // Database host
    $form['advanced_options']['db_host'] = array(
      '#type' => 'textfield',
drumm's avatar
drumm committed
299
      '#title' => st('Database host'),
300 301 302 303
      '#default_value' => $db_host,
      '#size' => 45,
      '#maxlength' => 45,
      '#required' => TRUE,
drumm's avatar
drumm committed
304
      '#description' => st('If your database is located on a different server, change this.'),
305 306
    );

307 308 309 310 311 312 313 314 315 316
    // Database port
    $form['advanced_options']['db_port'] = array(
      '#type' => 'textfield',
      '#title' => st('Database port'),
      '#default_value' => $db_port,
      '#size' => 45,
      '#maxlength' => 45,
      '#description' => st('If your database server is listening to a non-standard port, enter its number.'),
    );

317
    // Table prefix
318
    $prefix = ($profile == 'default') ? 'drupal_' : $profile . '_';
319 320
    $form['advanced_options']['db_prefix'] = array(
      '#type' => 'textfield',
321
      '#title' => st('Table prefix'),
322 323 324
      '#default_value' => $db_prefix,
      '#size' => 45,
      '#maxlength' => 45,
325
      '#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_name(), '%prefix' => $prefix)),
326 327 328 329
    );

    $form['save'] = array(
      '#type' => 'submit',
330
      '#value' => st('Save and continue'),
331 332 333 334 335
    );

    $form['errors'] = array();
    $form['settings_file'] = array('#type' => 'value', '#value' => $settings_file);
    $form['_db_url'] = array('#type' => 'value');
336
    $form['#action'] = "install.php?profile=$profile" . ($install_locale ? "&locale=$install_locale" : '');
337
    $form['#redirect'] = FALSE;
338
  }
339
  return $form;
340
}
341

342 343 344
/**
 * Form API validate for install_settings form.
 */
345
function install_settings_form_validate($form, &$form_state) {
346
  global $db_url;
347
  _install_settings_form_validate($form_state['values']['db_prefix'], $form_state['values']['db_type'], $form_state['values']['db_user'], $form_state['values']['db_pass'], $form_state['values']['db_host'], $form_state['values']['db_port'], $form_state['values']['db_path'], $form_state['values']['settings_file'], $form_state, $form);
348 349 350 351 352
}

/**
 * Helper function for install_settings_validate.
 */
353
function _install_settings_form_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_port, $db_path, $settings_file, &$form_state, $form = NULL) {
354 355
  global $db_url;

356
  // Verify the table prefix
357
  if (!empty($db_prefix) && is_string($db_prefix) && !preg_match('/^[A-Za-z0-9_.]+$/', $db_prefix)) {
358
    form_set_error('db_prefix', st('The database table prefix you have entered, %db_prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%db_prefix' => $db_prefix)), 'error');
359 360
  }

361 362 363 364
  if (!empty($db_port) && !is_numeric($db_port)) {
    form_set_error('db_port', st('Database port must be a number.'));
  }

365 366
  // Check database type
  if (!isset($form)) {
367 368
    $_db_url = is_array($db_url) ? $db_url['default'] : $db_url;
    $db_type = substr($_db_url, 0, strpos($_db_url, '://'));
369 370 371
  }
  $databases = drupal_detect_database_types();
  if (!in_array($db_type, $databases)) {
372
    form_set_error('db_type', st("In your %settings_file file you have configured @drupal to use a %db_type server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_name(), '%db_type' => $db_type)));
373 374 375
  }
  else {
    // Verify
376
    $db_url = $db_type . '://' . urlencode($db_user) . ($db_pass ? ':' . urlencode($db_pass) : '') . '@' . ($db_host ? urlencode($db_host) : 'localhost') . ($db_port ? ":$db_port" : '') . '/' . urlencode($db_path);
377
    if (isset($form)) {
378
      form_set_value($form['_db_url'], $db_url, $form_state);
379 380 381
    }
    $success = array();

382
    $function = 'drupal_test_' . $db_type;
383 384
    if (!$function($db_url, $success)) {
      if (isset($success['CONNECT'])) {
385
        form_set_error('db_type', st('In order for Drupal to work, and to continue with the installation process, you must resolve all permission issues reported above. We were able to verify that we have permission for the following commands: %commands. For more help with configuring your database server, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.', array('%commands' => implode($success, ', '))));
386 387 388 389 390 391 392 393 394 395 396
      }
      else {
        form_set_error('db_type', '');
      }
    }
  }
}

/**
 * Form API submit for install_settings form.
 */
397
function install_settings_form_submit($form, &$form_state) {
drumm's avatar
drumm committed
398
  global $profile, $install_locale;
399 400 401

  // Update global settings array and save
  $settings['db_url'] = array(
402
    'value'    => $form_state['values']['_db_url'],
403 404 405
    'required' => TRUE,
  );
  $settings['db_prefix'] = array(
406
    'value'    => $form_state['values']['db_prefix'],
407 408 409 410 411
    'required' => TRUE,
  );
  drupal_rewrite_settings($settings);

  // Continue to install profile step
412
  install_goto("install.php?profile=$profile" . ($install_locale ? "&locale=$install_locale" : ''));
413 414 415
}

/**
416 417 418
 * Find all .profile files.
 */
function install_find_profiles() {
Dries's avatar
Dries committed
419
  return file_scan_directory('./profiles', '\.profile$', array('.', '..', 'CVS'), 0, TRUE, 'name', 0);
420 421 422 423
}

/**
 * Allow admin to select which profile to install.
424 425 426 427 428 429 430
 *
 * @return
 *   The selected profile.
 */
function install_select_profile() {
  include_once './includes/form.inc';

431
  $profiles = install_find_profiles();
432 433 434 435 436 437 438 439
  // Don't need to choose profile if only one available.
  if (sizeof($profiles) == 1) {
    $profile = array_pop($profiles);
    require_once $profile->filename;
    return $profile->name;
  }
  elseif (sizeof($profiles) > 1) {
    foreach ($profiles as $profile) {
440
      if (!empty($_POST['profile']) && ($_POST['profile'] == $profile->name)) {
441 442 443
        return $profile->name;
      }
    }
444

445
    install_task_list('profile-select');
446

drumm's avatar
drumm committed
447
    drupal_set_title(st('Select an installation profile'));
448
    print theme('install_page', drupal_get_form('install_select_profile_form', $profiles));
449 450 451 452
    exit;
  }
}

453 454 455
/**
 * Form API array definition for the profile selection form.
 */
456
function install_select_profile_form(&$form_state, $profiles) {
457 458 459
  foreach ($profiles as $profile) {
    include_once($profile->filename);
    // Load profile details.
460
    $function = $profile->name . '_profile_details';
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    if (function_exists($function)) {
      $details = $function();
    }
    // If set, used defined name. Otherwise use file name.
    $name = isset($details['name']) ? $details['name'] : $profile->name;
    $form['profile'][$name] = array(
      '#type' => 'radio',
      '#value' => 'default',
      '#return_value' => $profile->name,
      '#title' => $name,
      '#description' => isset($details['description']) ? $details['description'] : '',
      '#parents' => array('profile'),
    );
  }
  $form['submit'] =  array(
    '#type' => 'submit',
477
    '#value' => st('Save and continue'),
drumm's avatar
drumm committed
478 479 480 481 482
  );
  return $form;
}

/**
483 484 485
 * Find all .po files for the current profile.
 */
function install_find_locales($profilename) {
486
  $locales = file_scan_directory('./profiles/' . $profilename . '/translations', '\.po$', array('.', '..', 'CVS'), 0, FALSE);
487 488 489 490 491 492
  array_unshift($locales, (object) array('name' => 'en'));
  return $locales;
}

/**
 * Allow admin to select which locale to use for the current profile.
drumm's avatar
drumm committed
493 494 495 496 497 498 499 500
 *
 * @return
 *   The selected language.
 */
function install_select_locale($profilename) {
  include_once './includes/file.inc';
  include_once './includes/form.inc';

501 502
  // Find all available locales.
  $locales = install_find_locales($profilename);
drumm's avatar
drumm committed
503

504 505 506 507 508 509
  // If only the built-in (English) language is available,
  // and we are using the default profile, inform the user
  // that the installer can be localized. Otherwise we assume
  // the user know what he is doing.
  if (count($locales) == 1) {
    if ($profilename == 'default') {
510 511 512
      install_task_list('locale-select');
      drupal_set_title(st('Choose language'));
      if (!empty($_GET['localize'])) {
513 514 515 516 517 518 519
        $output = '<p>' . st('With the addition of an appropriate translation package, this installer is capable of proceeding in another language of your choice. To install and use Drupal in a language other than English:') . '</p>';
        $output .= '<ul><li>' . st('Determine if <a href="@translations" target="_blank">a translation of this Drupal version</a> is available in your language of choice. A translation is provided via a translation package; each translation package enables the display of a specific version of Drupal in a specific language. Not all languages are available for every version of Drupal.', array('@translations' => 'http://drupal.org/project/translations')) . '</li>';
        $output .= '<li>' . st('If an alternative translation package of your choice is available, download and extract its contents to your Drupal root directory.') . '</li>';
        $output .= '<li>' . st('Return to choose language using the second link below and select your desired language from the displayed list. Reloading the page allows the list to automatically adjust to the presence of new translation packages.') . '</li>';
        $output .= '</ul><p>' . st('Alternatively, to install and use Drupal in English, or to defer the selection of an alternative language until after installation, select the first link below.') . '</p>';
        $output .= '<p>' . st('How should the installation continue?') . '</p>';
        $output .= '<ul><li><a href="install.php?profile=' . $profilename . '&amp;locale=en">' . st('Continue installation in English') . '</a></li><li><a href="install.php?profile=' . $profilename . '">' . st('Return to choose a language') . '</a></li></ul>';
520 521
      }
      else {
522
        $output = '<ul><li><a href="install.php?profile=' . $profilename . '&amp;locale=en">' . st('Install Drupal in English') . '</a></li><li><a href="install.php?profile=' . $profilename . '&amp;localize=true">' . st('Learn how to install Drupal in other languages') . '</a></li></ul>';
523
      }
524 525 526 527 528
      print theme('install_page', $output);
      exit;
    }
    // One language, but not the default profile, assume
    // the user knows what he is doing.
drumm's avatar
drumm committed
529
    return FALSE;
530 531
  }
  else {
532
    // Allow profile to pre-select the language, skipping the selection.
533
    $function = $profilename . '_profile_details';
534 535 536 537 538 539 540 541 542 543 544
    if (function_exists($function)) {
      $details = $function();
      if (isset($details['language'])) {
        foreach ($locales as $locale) {
          if ($details['language'] == $locale->name) {
            return $locale->name;
          }
        }
      }
    }

drumm's avatar
drumm committed
545 546 547 548 549 550
    foreach ($locales as $locale) {
      if ($_POST['locale'] == $locale->name) {
        return $locale->name;
      }
    }

551
    install_task_list('locale-select');
drumm's avatar
drumm committed
552

553
    drupal_set_title(st('Choose language'));
drumm's avatar
drumm committed
554 555 556 557 558
    print theme('install_page', drupal_get_form('install_select_locale_form', $locales));
    exit;
  }
}

559 560 561
/**
 * Form API array definition for language selection.
 */
562
function install_select_locale_form(&$form_state, $locales) {
drumm's avatar
drumm committed
563
  include_once './includes/locale.inc';
564
  $languages = _locale_get_predefined_list();
drumm's avatar
drumm committed
565 566 567 568
  foreach ($locales as $locale) {
    // Try to use verbose locale name
    $name = $locale->name;
    if (isset($languages[$name])) {
569
      $name = $languages[$name][0] . (isset($languages[$name][1]) ? ' ' . st('(@language)', array('@language' => $languages[$name][1])) : '');
drumm's avatar
drumm committed
570 571 572 573 574
    }
    $form['locale'][$locale->name] = array(
      '#type' => 'radio',
      '#return_value' => $locale->name,
      '#default_value' => ($locale->name == 'en' ? TRUE : FALSE),
575
      '#title' => $name . ($locale->name == 'en' ? ' ' . st('(built-in)') : ''),
drumm's avatar
drumm committed
576 577 578 579 580
      '#parents' => array('locale')
    );
  }
  $form['submit'] =  array(
    '#type' => 'submit',
581
    '#value' => st('Select language'),
582 583 584 585
  );
  return $form;
}

586 587 588 589
/**
 * Show an error page when there are no profiles available.
 */
function install_no_profile_error() {
590
  install_task_list('profile-select');
drumm's avatar
drumm committed
591
  drupal_set_title(st('No profiles available'));
592
  print theme('install_page', '<p>' . st('We were unable to find any installer profiles. Installer profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.') . '</p>');
593 594 595 596 597 598 599 600
  exit;
}


/**
 * Show an error page when Drupal has already been installed.
 */
function install_already_done_error() {
601 602
  global $base_url;

drumm's avatar
drumm committed
603
  drupal_set_title(st('Drupal already installed'));
604
  print theme('install_page', st('<ul><li>To start over, you must empty your existing database.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To upgrade an existing installation, proceed to the <a href="@base-url/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url)));
605 606 607 608
  exit;
}

/**
609
 * Tasks performed after the database is initialized.
610
 */
611
function install_tasks($profile, $task) {
612
  global $base_url, $install_locale;
613 614

  // Bootstrap newly installed Drupal, while preserving existing messages.
615
  $messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : '';
616 617 618
  drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  $_SESSION['messages'] = $messages;

619
  // URL used to direct page requests.
620
  $url = $base_url . '/install.php?locale=' . $install_locale . '&profile=' . $profile;
621 622

  // Build a page for final tasks.
623
  if (empty($task)) {
624 625
    variable_set('install_task', 'profile-install');
    $task = 'profile-install';
626 627
  }

628 629 630
  // We are using a list of if constructs here to allow for
  // passing from one task to the other in the same request.

631 632 633 634 635 636 637 638 639 640 641 642
  // Install profile modules.
  if ($task == 'profile-install') {
    $modules = variable_get('install_profile_modules', array());
    $files = module_rebuild_cache();
    variable_del('install_profile_modules');
    $operations = array();
    foreach ($modules as $module) {
      $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name']));
    }
    $batch = array(
      'operations' => $operations,
      'finished' => '_install_profile_batch_finished',
643 644
      'title' => st('Installing @drupal', array('@drupal' => drupal_install_profile_name())),
      'error_message' => st('The installation has encountered an error.'),
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
    );
    // Start a batch, switch to 'profile-install-batch' task. We need to
    // set the variable here, because batch_process() redirects.
    variable_set('install_task', 'profile-install-batch');
    batch_set($batch);
    batch_process($url, $url);
  }
  // We are running a batch install of the profile's modules.
  // This might run in multiple HTTP requests, constantly redirecting
  // to the same address, until the batch finished callback is invoked
  // and the task advances to 'locale-initial-import'.
  if ($task == 'profile-install-batch') {
    include_once 'includes/batch.inc';
    $output = _batch_page();
  }

661 662 663 664 665 666 667 668 669 670
  // Import interface translations for the enabled modules.
  if ($task == 'locale-initial-import') {
    if (!empty($install_locale) && ($install_locale != 'en')) {
      include_once 'includes/locale.inc';
      // Enable installation language as default site language.
      locale_add_language($install_locale, NULL, NULL, NULL, NULL, NULL, 1, TRUE);
      // Collect files to import for this language.
      $batch = locale_batch_by_language($install_locale, '_install_locale_initial_batch_finished');
      if (!empty($batch)) {
        // Remember components we cover in this batch set.
671
        variable_set('install_locale_batch_components', $batch['#components']);
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
        // Start a batch, switch to 'locale-batch' task. We need to
        // set the variable here, because batch_process() redirects.
        variable_set('install_task', 'locale-initial-batch');
        batch_set($batch);
        batch_process($url, $url);
      }
    }
    // Found nothing to import or not foreign language, go to next task.
    $task = 'configure';
  }
  if ($task == 'locale-initial-batch') {
    include_once 'includes/batch.inc';
    include_once 'includes/locale.inc';
    $output = _batch_page();
  }
687

688 689 690 691 692 693
  if ($task == 'configure') {
    if (variable_get('site_name', FALSE) || variable_get('site_mail', FALSE)) {
      // Site already configured: This should never happen, means re-running
      // the installer, possibly by an attacker after the 'install_task' variable
      // got accidentally blown somewhere. Stop it now.
      install_already_done_error();
694
    }
695 696 697 698 699 700
    $form = drupal_get_form('install_configure_form', $url);

    if (!variable_get('site_name', FALSE) && !variable_get('site_mail', FALSE)) {
      // Not submitted yet: Prepare to display the form.
      $output = $form;
      drupal_set_title(st('Configure site'));
701

702
      // Warn about settings.php permissions risk
703 704
      $settings_dir = './' . conf_path();
      $settings_file = $settings_dir . '/settings.php';
705
      if (!drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file($settings_dir, FILE_NOT_WRITABLE, 'dir')) {
706
        drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, please consult the <a href="@handbook_url">online handbook</a>.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/getting-started')), 'error');
707 708 709 710 711
      }
      else {
        drupal_set_message(st('All necessary changes to %dir and %file have been made. They have been set to read-only for security.', array('%dir' => $settings_dir, '%file' => $settings_file)));
      }

712
      // Add JavaScript validation.
713
      _user_password_dynamic_validation();
714
      drupal_add_js(drupal_get_path('module', 'system') . '/system.js', 'module');
715 716 717 718 719 720 721 722 723 724 725
      // We add these strings as settings because JavaScript translation does not
      // work on install time.
      drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail')), 'cleanURL' => array('success' => st('Your server has been successfully tested to support this feature.'), 'failure' => st('Your system configuration does not currently support this feature. The <a href="http://drupal.org/node/15365">handbook page on Clean URLs</a> has additional troubleshooting information.'), 'testing' => st('Testing clean URLs...'))), 'setting');
      drupal_add_js('
// Global Killswitch
if (Drupal.jsEnabled) {
  $(document).ready(function() {
    Drupal.cleanURLsInstallCheck();
    Drupal.setDefaultTimezone();
  });
}', 'inline');
726 727
      // Build menu to allow clean URL check.
      menu_rebuild();
728
    }
729

730 731
    else {
      $task = 'profile';
732
    }
733
  }
734

735
  // If found an unknown task or the 'profile' task, which is
736 737 738
  // reserved for profiles, hand over the control to the profile,
  // so it can run any number of custom tasks it defines.
  if (!in_array($task, install_reserved_tasks())) {
739
    $function = $profile . '_profile_tasks';
740
    if (function_exists($function)) {
741
      // The profile needs to run more code, maybe even more tasks.
742
      // $task is sent through as a reference and may be changed!
743
      $output = $function($task, $url);
744 745
    }

746
    // If the profile doesn't move on to a new task we assume
747
    // that it is done.
748
    if ($task == 'profile') {
749
      $task = 'profile-finished';
750
    }
751
  }
752

753 754 755
  // Profile custom tasks are done, so let the installer regain
  // control and proceed with importing the remaining translations.
  if ($task == 'profile-finished') {
756 757
    if (!empty($install_locale) && ($install_locale != 'en')) {
      include_once 'includes/locale.inc';
758 759 760 761 762
      // Collect files to import for this language. Skip components
      // already covered in the initial batch set.
      $batch = locale_batch_by_language($install_locale, '_install_locale_remaining_batch_finished', variable_get('install_locale_batch_components', array()));
      // Remove temporary variable.
      variable_del('install_locale_batch_components');
763
      if (!empty($batch)) {
764
        // Start a batch, switch to 'locale-remaining-batch' task. We need to
765
        // set the variable here, because batch_process() redirects.
766
        variable_set('install_task', 'locale-remaining-batch');
767
        batch_set($batch);
768
        batch_process($url, $url);
769
      }
770
    }
771
    // Found nothing to import or not foreign language, go to next task.
772
    $task = 'finished';
773
  }
774
  if ($task == 'locale-remaining-batch') {
775 776
    include_once 'includes/batch.inc';
    include_once 'includes/locale.inc';
777
    $output = _batch_page();
778
  }
779

780 781 782 783
  // Display a 'finished' page to user.
  if ($task == 'finished') {
    drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name())));
    $messages = drupal_set_message();
784 785
    $output = '<p>' . st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) . '</p>';
    $output .= '<p>' . (isset($messages['error']) ? st('Please review the messages above before continuing on to <a href="@url">your new site</a>.', array('@url' => url(''))) : st('You may now visit <a href="@url">your new site</a>.', array('@url' => url('')))) . '</p>';
786 787
    $task = 'done';
  }
788

789 790
  // The end of the install process. Remember profile used.
  if ($task == 'done') {
791 792
    // Rebuild menu and registry to get content type links registered by the
    // profile, and possibly any other menu items created through the tasks.
793
    menu_rebuild();
794 795 796 797

    // Register actions declared by any modules.
    actions_synchronize();

798 799 800 801
    // Randomize query-strings on css/js files, to hide the fact that
    // this is a new install, not upgraded yet.
    _drupal_flush_css_js();

802
    variable_set('install_profile', $profile);
803
  }
804 805 806 807 808

  // Set task for user, and remember the task in the database.
  install_task_list($task);
  variable_set('install_task', $task);

809 810 811 812 813
  // Output page, if some output was required. Otherwise it is possible
  // that we are printing a JSON page and theme output should not be there.
  if (isset($output)) {
    print theme('maintenance_page', $output);
  }
814 815
}

816 817 818 819 820 821 822 823 824 825 826
/**
 * Batch callback for batch installation of modules.
 */
function _install_module_batch($module, $module_name, &$context) {
  _drupal_install_module($module);
  // We enable the installed module right away, so that the module will be
  // loaded by drupal_bootstrap in subsequent batch requests, and other
  // modules possibly depending on it can safely perform their installation
  // steps.
  module_enable(array($module));
  $context['results'][] = $module;
827
  $context['message'] = 'Installed ' . $module_name . ' module.';
828 829 830 831 832 833 834 835 836 837 838
}

/**
 * Finished callback for the modules install batch.
 *
 * Advance installer task to language import.
 */
function _install_profile_batch_finished($success, $results) {
  variable_set('install_task', 'locale-initial-import');
}

839 840
/**
 * Finished callback for the first locale import batch.
841
 *
842 843 844 845 846 847 848 849
 * Advance installer task to the configure screen.
 */
function _install_locale_initial_batch_finished($success, $results) {
  variable_set('install_task', 'configure');
}

/**
 * Finished callback for the second locale import batch.
850
 *
851 852 853 854 855 856
 * Advance installer task to the finished screen.
 */
function _install_locale_remaining_batch_finished($success, $results) {
  variable_set('install_task', 'finished');
}

857 858 859 860
/**
 * The list of reserved tasks to run in the installer.
 */
function install_reserved_tasks() {
861
  return array('configure', 'profile-install', 'profile-install-batch', 'locale-initial-import', 'locale-initial-batch', 'profile-finished', 'locale-remaining-batch', 'finished', 'done');
862 863
}

864
/**
865
 * Check installation requirements and report any errors.
866
 */
867
function install_check_requirements($profile, $verify) {
868

869
  // If Drupal is not set up already, we need to create a settings file.
870 871
  if (!$verify) {
    $writable = FALSE;
872 873
    $conf_path = './' . conf_path(FALSE, TRUE);
    $settings_file = $conf_path . '/settings.php';
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
    $file = $conf_path;
    // Verify that the directory exists.
    if (drupal_verify_install_file($conf_path, FILE_EXIST, 'dir')) {
      // Check to see if a settings.php already exists.
      if (drupal_verify_install_file($settings_file, FILE_EXIST)) {
        // If it does, make sure it is writable.
        $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE);
        $file = $settings_file;
      }
      else {
        // If not, make sure the directory is.
        $writable = drupal_verify_install_file($conf_path, FILE_READABLE|FILE_WRITABLE, 'dir');
      }
    }

    if (!$writable) {
890
      drupal_set_message(st('The @drupal installer requires write permissions to %file during the installation process. If you are unsure how to grant file permissions, please consult the <a href="@handbook_url">online handbook</a>.', array('@drupal' => drupal_install_profile_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'error');
891
    }
892
  }
893 894 895 896 897 898 899 900 901 902 903 904

  // Check the other requirements.
  $requirements = drupal_check_profile($profile);
  $severity = drupal_requirements_severity($requirements);

  // If there are issues, report them.
  if ($severity == REQUIREMENT_ERROR) {

    foreach ($requirements as $requirement) {
      if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
        $message = $requirement['description'];
        if (isset($requirement['value']) && $requirement['value']) {
905
          $message .= ' (' . st('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) . ')';
906 907 908 909 910
        }
        drupal_set_message($message, 'error');
      }
    }
  }
911 912 913 914 915 916
  if ($severity == REQUIREMENT_WARNING) {

    foreach ($requirements as $requirement) {
      if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_WARNING) {
        $message = $requirement['description'];
        if (isset($requirement['value']) && $requirement['value']) {
917
          $message .= ' (' . st('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) . ')';
918 919 920 921
        }
        drupal_set_message($message, 'warning');
      }
    }
922
  }
923 924
}

925 926 927 928 929 930
/**
 * Add the installation task list to the current page.
 */
function install_task_list($active = NULL) {
  // Default list of tasks.
  $tasks = array(
931 932 933 934 935 936 937
    'profile-select'        => st('Choose profile'),
    'locale-select'         => st('Choose language'),
    'requirements'          => st('Verify requirements'),
    'database'              => st('Set up database'),
    'profile-install-batch' => st('Install profile'),
    'locale-initial-batch'  => st('Set up translations'),
    'configure'             => st('Configure site'),
938
  );
Dries's avatar
Dries committed
939

940 941
  $profiles = install_find_profiles();
  $profile = isset($_GET['profile']) && isset($profiles[$_GET['profile']]) ? $_GET['profile'] : '.';
942 943
  $locales = install_find_locales($profile);

944 945
  // If we have only one profile, remove 'Choose profile'
  // and rename 'Install profile'.