| Drupal | PHP Cross Reference | Content Management Systems |
1 <?php 2 3 /** 4 * @file 5 * API functions for installing Drupal. 6 */ 7 8 /** 9 * Do not run the task during the current installation request. 10 * 11 * This can be used to skip running an installation task when certain 12 * conditions are met, even though the task may still show on the list of 13 * installation tasks presented to the user. For example, the Drupal installer 14 * uses this flag to skip over the database configuration form when valid 15 * database connection information is already available from settings.php. It 16 * also uses this flag to skip language import tasks when the installation is 17 * being performed in English. 18 */ 19 define('INSTALL_TASK_SKIP', 1); 20 21 /** 22 * Run the task on each installation request until the database is set up. 23 * 24 * This is primarily used by the Drupal installer for bootstrap-related tasks. 25 */ 26 define('INSTALL_TASK_RUN_IF_REACHED', 2); 27 28 /** 29 * Global flag to indicate that a task should be run on each installation 30 * request that reaches it, until the database is set up and we are able to 31 * record the fact that it already ran. 32 * 33 * This is the default method for running tasks and should be used for most 34 * tasks that occur after the database is set up; these tasks will then run 35 * once and be marked complete once they are successfully finished. For 36 * example, the Drupal installer uses this flag for the batch installation of 37 * modules on the new site, and also for the configuration form that collects 38 * basic site information and sets up the site maintenance account. 39 */ 40 define('INSTALL_TASK_RUN_IF_NOT_COMPLETED', 3); 41 42 /** 43 * Installs Drupal either interactively or via an array of passed-in settings. 44 * 45 * The Drupal installation happens in a series of steps, which may be spread 46 * out over multiple page requests. Each request begins by trying to determine 47 * the last completed installation step (also known as a "task"), if one is 48 * available from a previous request. Control is then passed to the task 49 * handler, which processes the remaining tasks that need to be run until (a) 50 * an error is thrown, (b) a new page needs to be displayed, or (c) the 51 * installation finishes (whichever happens first). 52 * 53 * @param $settings 54 * An optional array of installation settings. Leave this empty for a normal, 55 * interactive, browser-based installation intended to occur over multiple 56 * page requests. Alternatively, if an array of settings is passed in, the 57 * installer will attempt to use it to perform the installation in a single 58 * page request (optimized for the command line) and not send any output 59 * intended for the web browser. See install_state_defaults() for a list of 60 * elements that are allowed to appear in this array. 61 * 62 * @see install_state_defaults() 63 */ 64 function install_drupal($settings = array()) { 65 global $install_state; 66 // Initialize the installation state with the settings that were passed in, 67 // as well as a boolean indicating whether or not this is an interactive 68 // installation. 69 $interactive = empty($settings); 70 $install_state = $settings + array('interactive' => $interactive) + install_state_defaults(); 71 try { 72 // Begin the page request. This adds information about the current state of 73 // the Drupal installation to the passed-in array. 74 install_begin_request($install_state); 75 // Based on the installation state, run the remaining tasks for this page 76 // request, and collect any output. 77 $output = install_run_tasks($install_state); 78 } 79 catch (Exception $e) { 80 // When an installation error occurs, either send the error to the web 81 // browser or pass on the exception so the calling script can use it. 82 if ($install_state['interactive']) { 83 install_display_output($e->getMessage(), $install_state); 84 } 85 else { 86 throw $e; 87 } 88 } 89 // All available tasks for this page request are now complete. Interactive 90 // installations can send output to the browser or redirect the user to the 91 // next page. 92 if ($install_state['interactive']) { 93 if ($install_state['parameters_changed']) { 94 // Redirect to the correct page if the URL parameters have changed. 95 install_goto(install_redirect_url($install_state)); 96 } 97 elseif (isset($output)) { 98 // Display a page only if some output is available. Otherwise it is 99 // possible that we are printing a JSON page and theme output should 100 // not be shown. 101 install_display_output($output, $install_state); 102 } 103 } 104 } 105 106 /** 107 * Returns an array of default settings for the global installation state. 108 * 109 * The installation state is initialized with these settings at the beginning 110 * of each page request. They may evolve during the page request, but they are 111 * initialized again once the next request begins. 112 * 113 * Non-interactive Drupal installations can override some of these default 114 * settings by passing in an array to the installation script, most notably 115 * 'parameters' (which contains one-time parameters such as 'profile' and 116 * 'locale' that are normally passed in via the URL) and 'forms' (which can 117 * be used to programmatically submit forms during the installation; the keys 118 * of each element indicate the name of the installation task that the form 119 * submission is for, and the values are used as the $form_state['values'] 120 * array that is passed on to the form submission via drupal_form_submit()). 121 * 122 * @see drupal_form_submit() 123 */ 124 function install_state_defaults() { 125 $defaults = array( 126 // The current task being processed. 127 'active_task' => NULL, 128 // The last task that was completed during the previous installation 129 // request. 130 'completed_task' => NULL, 131 // This becomes TRUE only when Drupal's system module is installed. 132 'database_tables_exist' => FALSE, 133 // An array of forms to be programmatically submitted during the 134 // installation. The keys of each element indicate the name of the 135 // installation task that the form submission is for, and the values are 136 // used as the $form_state['values'] array that is passed on to the form 137 // submission via drupal_form_submit(). 138 'forms' => array(), 139 // This becomes TRUE only at the end of the installation process, after 140 // all available tasks have been completed and Drupal is fully installed. 141 // It is used by the installer to store correct information in the database 142 // about the completed installation, as well as to inform theme functions 143 // that all tasks are finished (so that the task list can be displayed 144 // correctly). 145 'installation_finished' => FALSE, 146 // Whether or not this installation is interactive. By default this will 147 // be set to FALSE if settings are passed in to install_drupal(). 148 'interactive' => TRUE, 149 // An array of available languages for the installation. 150 'locales' => array(), 151 // An array of parameters for the installation, pre-populated by the URL 152 // or by the settings passed in to install_drupal(). This is primarily 153 // used to store 'profile' (the name of the chosen installation profile) 154 // and 'locale' (the name of the chosen installation language), since 155 // these settings need to persist from page request to page request before 156 // the database is available for storage. 157 'parameters' => array(), 158 // Whether or not the parameters have changed during the current page 159 // request. For interactive installations, this will trigger a page 160 // redirect. 161 'parameters_changed' => FALSE, 162 // An array of information about the chosen installation profile. This will 163 // be filled in based on the profile's .info file. 164 'profile_info' => array(), 165 // An array of available installation profiles. 166 'profiles' => array(), 167 // An array of server variables that will be substituted into the global 168 // $_SERVER array via drupal_override_server_variables(). Used by 169 // non-interactive installations only. 170 'server' => array(), 171 // This becomes TRUE only when a valid database connection can be 172 // established. 173 'settings_verified' => FALSE, 174 // Installation tasks can set this to TRUE to force the page request to 175 // end (even if there is no themable output), in the case of an interactive 176 // installation. This is needed only rarely; for example, it would be used 177 // by an installation task that prints JSON output rather than returning a 178 // themed page. The most common example of this is during batch processing, 179 // but the Drupal installer automatically takes care of setting this 180 // parameter properly in that case, so that individual installation tasks 181 // which implement the batch API do not need to set it themselves. 182 'stop_page_request' => FALSE, 183 // Installation tasks can set this to TRUE to indicate that the task should 184 // be run again, even if it normally wouldn't be. This can be used, for 185 // example, if a single task needs to be spread out over multiple page 186 // requests, or if it needs to perform some validation before allowing 187 // itself to be marked complete. The most common examples of this are batch 188 // processing and form submissions, but the Drupal installer automatically 189 // takes care of setting this parameter properly in those cases, so that 190 // individual installation tasks which implement the batch API or form API 191 // do not need to set it themselves. 192 'task_not_complete' => FALSE, 193 // A list of installation tasks which have already been performed during 194 // the current page request. 195 'tasks_performed' => array(), 196 ); 197 return $defaults; 198 } 199 200 /** 201 * Begins an installation request, modifying the installation state as needed. 202 * 203 * This function performs commands that must run at the beginning of every page 204 * request. It throws an exception if the installation should not proceed. 205 * 206 * @param $install_state 207 * An array of information about the current installation state. This is 208 * modified with information gleaned from the beginning of the page request. 209 */ 210 function install_begin_request(&$install_state) { 211 // Add any installation parameters passed in via the URL. 212 $install_state['parameters'] += $_GET; 213 214 // Validate certain core settings that are used throughout the installation. 215 if (!empty($install_state['parameters']['profile'])) { 216 $install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']); 217 } 218 if (!empty($install_state['parameters']['locale'])) { 219 $install_state['parameters']['locale'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['locale']); 220 } 221 222 // Allow command line scripts to override server variables used by Drupal. 223 require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; 224 if (!$install_state['interactive']) { 225 drupal_override_server_variables($install_state['server']); 226 } 227 228 // The user agent header is used to pass a database prefix in the request when 229 // running tests. However, for security reasons, it is imperative that no 230 // installation be permitted using such a prefix. 231 if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) { 232 header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); 233 exit; 234 } 235 236 drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); 237 238 // This must go after drupal_bootstrap(), which unsets globals! 239 global $conf; 240 241 require_once DRUPAL_ROOT . '/modules/system/system.install'; 242 require_once DRUPAL_ROOT . '/includes/common.inc'; 243 require_once DRUPAL_ROOT . '/includes/file.inc'; 244 require_once DRUPAL_ROOT . '/includes/install.inc'; 245 require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc'); 246 247 // Load module basics (needed for hook invokes). 248 include_once DRUPAL_ROOT . '/includes/module.inc'; 249 include_once DRUPAL_ROOT . '/includes/session.inc'; 250 251 // Set up $language, so t() caller functions will still work. 252 drupal_language_initialize(); 253 254 include_once DRUPAL_ROOT . '/includes/entity.inc'; 255 require_once DRUPAL_ROOT . '/includes/ajax.inc'; 256 $module_list['system']['filename'] = 'modules/system/system.module'; 257 $module_list['user']['filename'] = 'modules/user/user.module'; 258 module_list(TRUE, FALSE, FALSE, $module_list); 259 drupal_load('module', 'system'); 260 drupal_load('module', 'user'); 261 262 // Load the cache infrastructure using a "fake" cache implementation that 263 // does not attempt to write to the database. We need this during the initial 264 // part of the installer because the database is not available yet. We 265 // continue to use it even when the database does become available, in order 266 // to preserve consistency between interactive and command-line installations 267 // (the latter complete in one page request and therefore are forced to 268 // continue using the cache implementation they started with) and also 269 // because any data put in the cache during the installer is inherently 270 // suspect, due to the fact that Drupal is not fully set up yet. 271 require_once DRUPAL_ROOT . '/includes/cache.inc'; 272 require_once DRUPAL_ROOT . '/includes/cache-install.inc'; 273 $conf['cache_default_class'] = 'DrupalFakeCache'; 274 275 // Prepare for themed output. We need to run this at the beginning of the 276 // page request to avoid a different theme accidentally getting set. (We also 277 // need to run it even in the case of command-line installations, to prevent 278 // any code in the installer that happens to initialize the theme system from 279 // accessing the database before it is set up yet.) 280 drupal_maintenance_theme(); 281 282 // Check existing settings.php. 283 $install_state['settings_verified'] = install_verify_settings(); 284 285 if ($install_state['settings_verified']) { 286 // Initialize the database system. Note that the connection 287 // won't be initialized until it is actually requested. 288 require_once DRUPAL_ROOT . '/includes/database/database.inc'; 289 290 // Verify the last completed task in the database, if there is one. 291 $task = install_verify_completed_task(); 292 } 293 else { 294 $task = NULL; 295 296 // Do not install over a configured settings.php. Check the 'db_url' 297 // variable in addition to 'databases', since previous versions of Drupal 298 // used that (and we do not want to allow installations on an existing site 299 // whose settings file has not yet been updated). 300 if (!empty($GLOBALS['databases']) || !empty($GLOBALS['db_url'])) { 301 throw new Exception(install_already_done_error()); 302 } 303 } 304 305 // Modify the installation state as appropriate. 306 $install_state['completed_task'] = $task; 307 $install_state['database_tables_exist'] = !empty($task); 308 } 309 310 /** 311 * Runs all tasks for the current installation request. 312 * 313 * In the case of an interactive installation, all tasks will be attempted 314 * until one is reached that has output which needs to be displayed to the 315 * user, or until a page redirect is required. Otherwise, tasks will be 316 * attempted until the installation is finished. 317 * 318 * @param $install_state 319 * An array of information about the current installation state. This is 320 * passed along to each task, so it can be modified if necessary. 321 * 322 * @return 323 * HTML output from the last completed task. 324 */ 325 function install_run_tasks(&$install_state) { 326 do { 327 // Obtain a list of tasks to perform. The list of tasks itself can be 328 // dynamic (e.g., some might be defined by the installation profile, 329 // which is not necessarily known until the earlier tasks have run), 330 // so we regenerate the remaining tasks based on the installation state, 331 // each time through the loop. 332 $tasks_to_perform = install_tasks_to_perform($install_state); 333 // Run the first task on the list. 334 reset($tasks_to_perform); 335 $task_name = key($tasks_to_perform); 336 $task = array_shift($tasks_to_perform); 337 $install_state['active_task'] = $task_name; 338 $original_parameters = $install_state['parameters']; 339 $output = install_run_task($task, $install_state); 340 $install_state['parameters_changed'] = ($install_state['parameters'] != $original_parameters); 341 // Store this task as having been performed during the current request, 342 // and save it to the database as completed, if we need to and if the 343 // database is in a state that allows us to do so. Also mark the 344 // installation as 'done' when we have run out of tasks. 345 if (!$install_state['task_not_complete']) { 346 $install_state['tasks_performed'][] = $task_name; 347 $install_state['installation_finished'] = empty($tasks_to_perform); 348 if ($install_state['database_tables_exist'] && ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished'])) { 349 variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name); 350 } 351 } 352 // Stop when there are no tasks left. In the case of an interactive 353 // installation, also stop if we have some output to send to the browser, 354 // the URL parameters have changed, or an end to the page request was 355 // specifically called for. 356 $finished = empty($tasks_to_perform) || ($install_state['interactive'] && (isset($output) || $install_state['parameters_changed'] || $install_state['stop_page_request'])); 357 } while (!$finished); 358 return $output; 359 } 360 361 /** 362 * Runs an individual installation task. 363 * 364 * @param $task 365 * An array of information about the task to be run. 366 * @param $install_state 367 * An array of information about the current installation state. This is 368 * passed in by reference so that it can be modified by the task. 369 * 370 * @return 371 * The output of the task function, if there is any. 372 */ 373 function install_run_task($task, &$install_state) { 374 $function = $task['function']; 375 376 if ($task['type'] == 'form') { 377 require_once DRUPAL_ROOT . '/includes/form.inc'; 378 if ($install_state['interactive']) { 379 // For interactive forms, build the form and ensure that it will not 380 // redirect, since the installer handles its own redirection only after 381 // marking the form submission task complete. 382 $form_state = array( 383 // We need to pass $install_state by reference in order for forms to 384 // modify it, since the form API will use it in call_user_func_array(), 385 // which requires that referenced variables be passed explicitly. 386 'build_info' => array('args' => array(&$install_state)), 387 'no_redirect' => TRUE, 388 ); 389 $form = drupal_build_form($function, $form_state); 390 // If a successful form submission did not occur, the form needs to be 391 // rendered, which means the task is not complete yet. 392 if (empty($form_state['executed'])) { 393 $install_state['task_not_complete'] = TRUE; 394 return drupal_render($form); 395 } 396 // Otherwise, return nothing so the next task will run in the same 397 // request. 398 return; 399 } 400 else { 401 // For non-interactive forms, submit the form programmatically with the 402 // values taken from the installation state. Throw an exception if any 403 // errors were encountered. 404 $form_state = array( 405 'values' => !empty($install_state['forms'][$function]) ? $install_state['forms'][$function] : array(), 406 // We need to pass $install_state by reference in order for forms to 407 // modify it, since the form API will use it in call_user_func_array(), 408 // which requires that referenced variables be passed explicitly. 409 'build_info' => array('args' => array(&$install_state)), 410 ); 411 drupal_form_submit($function, $form_state); 412 $errors = form_get_errors(); 413 if (!empty($errors)) { 414 throw new Exception(implode("\n", $errors)); 415 } 416 } 417 } 418 419 elseif ($task['type'] == 'batch') { 420 // Start a new batch based on the task function, if one is not running 421 // already. 422 $current_batch = variable_get('install_current_batch'); 423 if (!$install_state['interactive'] || !$current_batch) { 424 $batch = $function($install_state); 425 if (empty($batch)) { 426 // If the task did some processing and decided no batch was necessary, 427 // there is nothing more to do here. 428 return; 429 } 430 batch_set($batch); 431 // For interactive batches, we need to store the fact that this batch 432 // task is currently running. Otherwise, we need to make sure the batch 433 // will complete in one page request. 434 if ($install_state['interactive']) { 435 variable_set('install_current_batch', $function); 436 } 437 else { 438 $batch =& batch_get(); 439 $batch['progressive'] = FALSE; 440 } 441 // Process the batch. For progressive batches, this will redirect. 442 // Otherwise, the batch will complete. 443 batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state)); 444 } 445 // If we are in the middle of processing this batch, keep sending back 446 // any output from the batch process, until the task is complete. 447 elseif ($current_batch == $function) { 448 include_once DRUPAL_ROOT . '/includes/batch.inc'; 449 $output = _batch_page(); 450 // The task is complete when we try to access the batch page and receive 451 // FALSE in return, since this means we are at a URL where we are no 452 // longer requesting a batch ID. 453 if ($output === FALSE) { 454 // Return nothing so the next task will run in the same request. 455 variable_del('install_current_batch'); 456 return; 457 } 458 else { 459 // We need to force the page request to end if the task is not 460 // complete, since the batch API sometimes prints JSON output 461 // rather than returning a themed page. 462 $install_state['task_not_complete'] = $install_state['stop_page_request'] = TRUE; 463 return $output; 464 } 465 } 466 } 467 468 else { 469 // For normal tasks, just return the function result, whatever it is. 470 return $function($install_state); 471 } 472 } 473 474 /** 475 * Returns a list of tasks to perform during the current installation request. 476 * 477 * Note that the list of tasks can change based on the installation state as 478 * the page request evolves (for example, if an installation profile hasn't 479 * been selected yet, we don't yet know which profile tasks need to be run). 480 * 481 * @param $install_state 482 * An array of information about the current installation state. 483 * 484 * @return 485 * A list of tasks to be performed, with associated metadata. 486 */ 487 function install_tasks_to_perform($install_state) { 488 // Start with a list of all currently available tasks. 489 $tasks = install_tasks($install_state); 490 foreach ($tasks as $name => $task) { 491 // Remove any tasks that were already performed or that never should run. 492 // Also, if we started this page request with an indication of the last 493 // task that was completed, skip that task and all those that come before 494 // it, unless they are marked as always needing to run. 495 if ($task['run'] == INSTALL_TASK_SKIP || in_array($name, $install_state['tasks_performed']) || (!empty($install_state['completed_task']) && empty($completed_task_found) && $task['run'] != INSTALL_TASK_RUN_IF_REACHED)) { 496 unset($tasks[$name]); 497 } 498 if (!empty($install_state['completed_task']) && $name == $install_state['completed_task']) { 499 $completed_task_found = TRUE; 500 } 501 } 502 return $tasks; 503 } 504 505 /** 506 * Returns a list of all tasks the installer currently knows about. 507 * 508 * This function will return tasks regardless of whether or not they are 509 * intended to run on the current page request. However, the list can change 510 * based on the installation state (for example, if an installation profile 511 * hasn't been selected yet, we don't yet know which profile tasks will be 512 * available). 513 * 514 * @param $install_state 515 * An array of information about the current installation state. 516 * 517 * @return 518 * A list of tasks, with associated metadata. 519 */ 520 function install_tasks($install_state) { 521 // Determine whether translation import tasks will need to be performed. 522 $needs_translations = count($install_state['locales']) > 1 && !empty($install_state['parameters']['locale']) && $install_state['parameters']['locale'] != 'en'; 523 524 // Start with the core installation tasks that run before handing control 525 // to the installation profile. 526 $tasks = array( 527 'install_select_profile' => array( 528 'display_name' => st('Choose profile'), 529 'display' => count($install_state['profiles']) != 1, 530 'run' => INSTALL_TASK_RUN_IF_REACHED, 531 ), 532 'install_select_locale' => array( 533 'display_name' => st('Choose language'), 534 'run' => INSTALL_TASK_RUN_IF_REACHED, 535 ), 536 'install_load_profile' => array( 537 'run' => INSTALL_TASK_RUN_IF_REACHED, 538 ), 539 'install_verify_requirements' => array( 540 'display_name' => st('Verify requirements'), 541 ), 542 'install_settings_form' => array( 543 'display_name' => st('Set up database'), 544 'type' => 'form', 545 'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED, 546 ), 547 'install_system_module' => array( 548 ), 549 'install_bootstrap_full' => array( 550 'run' => INSTALL_TASK_RUN_IF_REACHED, 551 ), 552 'install_profile_modules' => array( 553 'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Install profile'), 554 'type' => 'batch', 555 ), 556 'install_import_locales' => array( 557 'display_name' => st('Set up translations'), 558 'display' => $needs_translations, 559 'type' => 'batch', 560 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, 561 ), 562 'install_configure_form' => array( 563 'display_name' => st('Configure site'), 564 'type' => 'form', 565 ), 566 ); 567 568 // Now add any tasks defined by the installation profile. 569 if (!empty($install_state['parameters']['profile'])) { 570 // Load the profile install file, because it is not always loaded when 571 // hook_install_tasks() is invoked (e.g. batch processing). 572 $profile_install_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.install'; 573 if (file_exists($profile_install_file)) { 574 include_once $profile_install_file; 575 } 576 $function = $install_state['parameters']['profile'] . '_install_tasks'; 577 if (function_exists($function)) { 578 $result = $function($install_state); 579 if (is_array($result)) { 580 $tasks += $result; 581 } 582 } 583 } 584 585 // Finish by adding the remaining core tasks. 586 $tasks += array( 587 'install_import_locales_remaining' => array( 588 'display_name' => st('Finish translations'), 589 'display' => $needs_translations, 590 'type' => 'batch', 591 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, 592 ), 593 'install_finished' => array( 594 'display_name' => st('Finished'), 595 ), 596 ); 597 598 // Allow the installation profile to modify the full list of tasks. 599 if (!empty($install_state['parameters']['profile'])) { 600 $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile'; 601 if (file_exists($profile_file)) { 602 include_once $profile_file; 603 $function = $install_state['parameters']['profile'] . '_install_tasks_alter'; 604 if (function_exists($function)) { 605 $function($tasks, $install_state); 606 } 607 } 608 } 609 610 // Fill in default parameters for each task before returning the list. 611 foreach ($tasks as $task_name => &$task) { 612 $task += array( 613 'display_name' => NULL, 614 'display' => !empty($task['display_name']), 615 'type' => 'normal', 616 'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED, 617 'function' => $task_name, 618 ); 619 } 620 return $tasks; 621 } 622 623 /** 624 * Returns a list of tasks that should be displayed to the end user. 625 * 626 * The output of this function is a list suitable for sending to 627 * theme_task_list(). 628 * 629 * @param $install_state 630 * An array of information about the current installation state. 631 * 632 * @return 633 * A list of tasks, with keys equal to the machine-readable task name and 634 * values equal to the name that should be displayed. 635 * 636 * @see theme_task_list() 637 */ 638 function install_tasks_to_display($install_state) { 639 $displayed_tasks = array(); 640 foreach (install_tasks($install_state) as $name => $task) { 641 if ($task['display']) { 642 $displayed_tasks[$name] = $task['display_name']; 643 } 644 } 645 return $displayed_tasks; 646 } 647 648 /** 649 * Returns the URL that should be redirected to during an installation request. 650 * 651 * The output of this function is suitable for sending to install_goto(). 652 * 653 * @param $install_state 654 * An array of information about the current installation state. 655 * 656 * @return 657 * The URL to redirect to. 658 * 659 * @see install_full_redirect_url() 660 */ 661 function install_redirect_url($install_state) { 662 return 'install.php?' . drupal_http_build_query($install_state['parameters']); 663 } 664 665 /** 666 * Returns the complete URL redirected to during an installation request. 667 * 668 * @param $install_state 669 * An array of information about the current installation state. 670 * 671 * @return 672 * The complete URL to redirect to. 673 * 674 * @see install_redirect_url() 675 */ 676 function install_full_redirect_url($install_state) { 677 global $base_url; 678 return $base_url . '/' . install_redirect_url($install_state); 679 } 680 681 /** 682 * Displays themed installer output and ends the page request. 683 * 684 * Installation tasks should use drupal_set_title() to set the desired page 685 * title, but otherwise this function takes care of theming the overall page 686 * output during every step of the installation. 687 * 688 * @param $output 689 * The content to display on the main part of the page. 690 * @param $install_state 691 * An array of information about the current installation state. 692 */ 693 function install_display_output($output, $install_state) { 694 drupal_page_header(); 695 // Only show the task list if there is an active task; otherwise, the page 696 // request has ended before tasks have even been started, so there is nothing 697 // meaningful to show. 698 if (isset($install_state['active_task'])) { 699 // Let the theming function know when every step of the installation has 700 // been completed. 701 $active_task = $install_state['installation_finished'] ? NULL : $install_state['active_task']; 702 drupal_add_region_content('sidebar_first', theme('task_list', array('items' => install_tasks_to_display($install_state), 'active' => $active_task))); 703 } 704 print theme('install_page', array('content' => $output)); 705 exit; 706 } 707 708 /** 709 * Verifies the requirements for installing Drupal. 710 * 711 * @param $install_state 712 * An array of information about the current installation state. 713 * 714 * @return 715 * A themed status report, or an exception if there are requirement errors. 716 * If there are only requirement warnings, a themed status report is shown 717 * initially, but the user is allowed to bypass it by providing 'continue=1' 718 * in the URL. Otherwise, no output is returned, so that the next task can be 719 * run in the same page request. 720 */ 721 function install_verify_requirements(&$install_state) { 722 // Check the installation requirements for Drupal and this profile. 723 $requirements = install_check_requirements($install_state); 724 725 // Verify existence of all required modules. 726 $requirements += drupal_verify_profile($install_state); 727 728 // Check the severity of the requirements reported. 729 $severity = drupal_requirements_severity($requirements); 730 731 // If there are errors, always display them. If there are only warnings, skip 732 // them if the user has provided a URL parameter acknowledging the warnings 733 // and indicating a desire to continue anyway. See drupal_requirements_url(). 734 if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($install_state['parameters']['continue']))) { 735 if ($install_state['interactive']) { 736 drupal_set_title(st('Requirements problem')); 737 $status_report = theme('status_report', array('requirements' => $requirements)); 738 $status_report .= st('Check the error messages and <a href="!url">proceed with the installation</a>.', array('!url' => check_url(drupal_requirements_url($severity)))); 739 return $status_report; 740 } 741 else { 742 // Throw an exception showing any unmet requirements. 743 $failures = array(); 744 foreach ($requirements as $requirement) { 745 // Skip warnings altogether for non-interactive installations; these 746 // proceed in a single request so there is no good opportunity (and no 747 // good method) to warn the user anyway. 748 if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) { 749 $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description']; 750 } 751 } 752 if (!empty($failures)) { 753 throw new Exception(implode("\n\n", $failures)); 754 } 755 } 756 } 757 } 758 759 /** 760 * Installation task; install the Drupal system module. 761 * 762 * @param $install_state 763 * An array of information about the current installation state. 764 */ 765 function install_system_module(&$install_state) { 766 // Install system.module. 767 drupal_install_system(); 768 769 // Enable the user module so that sessions can be recorded during the 770 // upcoming bootstrap step. 771 module_enable(array('user'), FALSE); 772 773 // Save the list of other modules to install for the upcoming tasks. 774 // variable_set() can be used now that system.module is installed. 775 $modules = $install_state['profile_info']['dependencies']; 776 777 // The installation profile is also a module, which needs to be installed 778 // after all the dependencies have been installed. 779 $modules[] = drupal_get_profile(); 780 781 variable_set('install_profile_modules', array_diff($modules, array('system'))); 782 $install_state['database_tables_exist'] = TRUE; 783 } 784 785 /** 786 * Verifies and returns the last installation task that was completed. 787 * 788 * @return 789 * The last completed task, if there is one. An exception is thrown if Drupal 790 * is already installed. 791 */ 792 function install_verify_completed_task() { 793 try { 794 if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) { 795 $task = unserialize($result->fetchField()); 796 } 797 } 798 // Do not trigger an error if the database query fails, since the database 799 // might not be set up yet. 800 catch (Exception $e) { 801 } 802 if (isset($task)) { 803 if ($task == 'done') { 804 throw new Exception(install_already_done_error()); 805 } 806 return $task; 807 } 808 } 809 810 /** 811 * Verifies the existing settings in settings.php. 812 */ 813 function install_verify_settings() { 814 global $databases; 815 816 // Verify existing settings (if any). 817 if (!empty($databases) && install_verify_pdo()) { 818 $database = $databases['default']['default']; 819 drupal_static_reset('conf_path'); 820 $settings_file = './' . conf_path(FALSE) . '/settings.php'; 821 $errors = install_database_errors($database, $settings_file); 822 if (empty($errors)) { 823 return TRUE; 824 } 825 } 826 return FALSE; 827 } 828 829 /** 830 * Verifies the PDO library. 831 */ 832 function install_verify_pdo() { 833 // PDO was moved to PHP core in 5.2.0, but the old extension (targeting 5.0 834 // and 5.1) is still available from PECL, and can still be built without 835 // errors. To verify that the correct version is in use, we check the 836 // PDO::ATTR_DEFAULT_FETCH_MODE constant, which is not available in the 837 // PECL extension. 838 return extension_loaded('pdo') && defined('PDO::ATTR_DEFAULT_FETCH_MODE'); 839 } 840 841 /** 842 * Form constructor for a form to configure and rewrite settings.php. 843 * 844 * @param $install_state 845 * An array of information about the current installation state. 846 * 847 * @see install_settings_form_validate() 848 * @see install_settings_form_submit() 849 * @ingroup forms 850 */ 851 function install_settings_form($form, &$form_state, &$install_state) { 852 global $databases; 853 $profile = $install_state['parameters']['profile']; 854 $install_locale = $install_state['parameters']['locale']; 855 856 drupal_static_reset('conf_path'); 857 $conf_path = './' . conf_path(FALSE); 858 $settings_file = $conf_path . '/settings.php'; 859 $database = isset($databases['default']['default']) ? $databases['default']['default'] : array(); 860 861 drupal_set_title(st('Database configuration')); 862 863 $drivers = drupal_get_database_types(); 864 $drivers_keys = array_keys($drivers); 865 866 $form['driver'] = array( 867 '#type' => 'radios', 868 '#title' => st('Database type'), 869 '#required' => TRUE, 870 '#default_value' => !empty($database['driver']) ? $database['driver'] : current($drivers_keys), 871 '#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_distribution_name())), 872 ); 873 if (count($drivers) == 1) { 874 $form['driver']['#disabled'] = TRUE; 875 $form['driver']['#description'] .= ' ' . st('Your PHP configuration only supports a single database type, so it has been automatically selected.'); 876 } 877 878 // Add driver specific configuration options. 879 foreach ($drivers as $key => $driver) { 880 $form['driver']['#options'][$key] = $driver->name(); 881 882 $form['settings'][$key] = $driver->getFormOptions($database); 883 $form['settings'][$key]['#prefix'] = '<h2 class="js-hide">' . st('@driver_name settings', array('@driver_name' => $driver->name())) . '</h2>'; 884 $form['settings'][$key]['#type'] = 'container'; 885 $form['settings'][$key]['#tree'] = TRUE; 886 $form['settings'][$key]['advanced_options']['#parents'] = array($key); 887 $form['settings'][$key]['#states'] = array( 888 'visible' => array( 889 ':input[name=driver]' => array('value' => $key), 890 ) 891 ); 892 } 893 894 $form['actions'] = array('#type' => 'actions'); 895 $form['actions']['save'] = array( 896 '#type' => 'submit', 897 '#value' => st('Save and continue'), 898 '#limit_validation_errors' => array( 899 array('driver'), 900 array(isset($form_state['input']['driver']) ? $form_state['input']['driver'] : current($drivers_keys)), 901 ), 902 '#submit' => array('install_settings_form_submit'), 903 ); 904 905 $form['errors'] = array(); 906 $form['settings_file'] = array('#type' => 'value', '#value' => $settings_file); 907 908 return $form; 909 } 910 911 /** 912 * Form validation handler for install_settings_form(). 913 * 914 * @see install_settings_form_submit() 915 */ 916 function install_settings_form_validate($form, &$form_state) { 917 $driver = $form_state['values']['driver']; 918 $database = $form_state['values'][$driver]; 919 $database['driver'] = $driver; 920 921 // TODO: remove when PIFR will be updated to use 'db_prefix' instead of 922 // 'prefix' in the database settings form. 923 $database['prefix'] = $database['db_prefix']; 924 unset($database['db_prefix']); 925 926 $form_state['storage']['database'] = $database; 927 $errors = install_database_errors($database, $form_state['values']['settings_file']); 928 foreach ($errors as $name => $message) { 929 form_set_error($name, $message); 930 } 931 } 932 933 /** 934 * Checks a database connection and returns any errors. 935 */ 936 function install_database_errors($database, $settings_file) { 937 global $databases; 938 $errors = array(); 939 940 // Check database type. 941 $database_types = drupal_get_database_types(); 942 $driver = $database['driver']; 943 if (!isset($database_types[$driver])) { 944 $errors['driver'] = st("In your %settings_file file you have configured @drupal to use a %driver server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_distribution_name(), '%driver' => $driver)); 945 } 946 else { 947 // Run driver specific validation 948 $errors += $database_types[$driver]->validateDatabaseSettings($database); 949 950 // Run tasks associated with the database type. Any errors are caught in the 951 // calling function. 952 $databases['default']['default'] = $database; 953 // Just changing the global doesn't get the new information processed. 954 // We tell tell the Database class to re-parse $databases. 955 Database::parseConnectionInfo(); 956 957 try { 958 db_run_tasks($driver); 959 } 960 catch (DatabaseTaskException $e) { 961 // These are generic errors, so we do not have any specific key of the 962 // database connection array to attach them to; therefore, we just put 963 // them in the error array with standard numeric keys. 964 $errors[$driver . '][0'] = $e->getMessage(); 965 } 966 } 967 return $errors; 968 } 969 970 /** 971 * Form submission handler for install_settings_form(). 972 * 973 * @see install_settings_form_validate() 974 */ 975 function install_settings_form_submit($form, &$form_state) { 976 global $install_state; 977 978 // Update global settings array and save. 979 $settings['databases'] = array( 980 'value' => array('default' => array('default' => $form_state['storage']['database'])), 981 'required' => TRUE, 982 ); 983 $settings['drupal_hash_salt'] = array( 984 'value' => drupal_hash_base64(drupal_random_bytes(55)), 985 'required' => TRUE, 986 ); 987 drupal_rewrite_settings($settings); 988 // Indicate that the settings file has been verified, and check the database 989 // for the last completed task, now that we have a valid connection. This 990 // last step is important since we want to trigger an error if the new 991 // database already has Drupal installed. 992 $install_state['settings_verified'] = TRUE; 993 $install_state['completed_task'] = install_verify_completed_task(); 994 } 995 996 /** 997 * Finds all .profile files. 998 */ 999 function install_find_profiles() { 1000 return file_scan_directory('./profiles', '/\.profile$/', array('key' => 'name')); 1001 } 1002 1003 /** 1004 * Selects which profile to install. 1005 * 1006 * @param $install_state 1007 * An array of information about the current installation state. The chosen 1008 * profile will be added here, if it was not already selected previously, as 1009 * will a list of all available profiles. 1010 * 1011 * @return 1012 * For interactive installations, a form allowing the profile to be selected, 1013 * if the user has a choice that needs to be made. Otherwise, an exception is 1014 * thrown if a profile cannot be chosen automatically. 1015 */ 1016 function install_select_profile(&$install_state) { 1017 $install_state['profiles'] += install_find_profiles(); 1018 if (empty($install_state['parameters']['profile'])) { 1019 // Try to find a profile. 1020 $profile = _install_select_profile($install_state['profiles']); 1021 if (empty($profile)) { 1022 // We still don't have a profile, so display a form for selecting one. 1023 // Only do this in the case of interactive installations, since this is 1024 // not a real form with submit handlers (the database isn't even set up 1025 // yet), rather just a convenience method for setting parameters in the 1026 // URL. 1027 if ($install_state['interactive']) { 1028 include_once DRUPAL_ROOT . '/includes/form.inc'; 1029 drupal_set_title(st('Select an installation profile')); 1030 $form = drupal_get_form('install_select_profile_form', $install_state['profiles']); 1031 return drupal_render($form); 1032 } 1033 else { 1034 throw new Exception(install_no_profile_error()); 1035 } 1036 } 1037 else { 1038 $install_state['parameters']['profile'] = $profile; 1039 } 1040 } 1041 } 1042 1043 /** 1044 * Selects an installation profile from a list or from a $_POST submission. 1045 */ 1046 function _install_select_profile($profiles) { 1047 if (sizeof($profiles) == 0) { 1048 throw new Exception(install_no_profile_error()); 1049 } 1050 // Don't need to choose profile if only one available. 1051 if (sizeof($profiles) == 1) { 1052 $profile = array_pop($profiles); 1053 // TODO: is this right? 1054 require_once DRUPAL_ROOT . '/' . $profile->uri; 1055 return $profile->name; 1056 } 1057 else { 1058 foreach ($profiles as $profile) { 1059 if (!empty($_POST['profile']) && ($_POST['profile'] == $profile->name)) { 1060 return $profile->name; 1061 } 1062 } 1063 } 1064 } 1065 1066 /** 1067 * Form constructor for the profile selection form. 1068 * 1069 * @param $form_state 1070 * Array of metadata about state of form processing. 1071 * @param $profile_files 1072 * Array of .profile files, as returned from file_scan_directory(). 1073 * 1074 * @ingroup forms 1075 */ 1076 function install_select_profile_form($form, &$form_state, $profile_files) { 1077 $profiles = array(); 1078 $names = array(); 1079 1080 foreach ($profile_files as $profile) { 1081 // TODO: is this right? 1082 include_once DRUPAL_ROOT . '/' . $profile->uri; 1083 1084 $details = install_profile_info($profile->name); 1085 // Don't show hidden profiles. This is used by to hide the testing profile, 1086 // which only exists to speed up test runs. 1087 if ($details['hidden'] === TRUE) { 1088 continue; 1089 } 1090 $profiles[$profile->name] = $details; 1091 1092 // Determine the name of the profile; default to file name if defined name 1093 // is unspecified. 1094 $name = isset($details['name']) ? $details['name'] : $profile->name; 1095 $names[$profile->name] = $name; 1096 } 1097 1098 // Display radio buttons alphabetically by human-readable name, but always 1099 // put the core profiles first (if they are present in the filesystem). 1100 natcasesort($names); 1101 if (isset($names['minimal'])) { 1102 // If the expert ("Minimal") core profile is present, put it in front of 1103 // any non-core profiles rather than including it with them alphabetically, 1104 // since the other profiles might be intended to group together in a 1105 // particular way. 1106 $names = array('minimal' => $names['minimal']) + $names; 1107 } 1108 if (isset($names['standard'])) { 1109 // If the default ("Standard") core profile is present, put it at the very 1110 // top of the list. This profile will have its radio button pre-selected, 1111 // so we want it to always appear at the top. 1112 $names = array('standard' => $names['standard']) + $names; 1113 } 1114 1115 foreach ($names as $profile => $name) { 1116 $form['profile'][$name] = array( 1117 '#type' => 'radio', 1118 '#value' => 'standard', 1119 '#return_value' => $profile, 1120 '#title' => $name, 1121 '#description' => isset($profiles[$profile]['description']) ? $profiles[$profile]['description'] : '', 1122 '#parents' => array('profile'), 1123 ); 1124 } 1125 $form['actions'] = array('#type' => 'actions'); 1126 $form['actions']['submit'] = array( 1127 '#type' => 'submit', 1128 '#value' => st('Save and continue'), 1129 ); 1130 return $form; 1131 } 1132 1133 /** 1134 * Find all .po files for the current profile. 1135 */ 1136 function install_find_locales($profilename) { 1137 $locales = file_scan_directory('./profiles/' . $profilename . '/translations', '/\.po$/', array('recurse' => FALSE)); 1138 array_unshift($locales, (object) array('name' => 'en')); 1139 foreach ($locales as $key => $locale) { 1140 // The locale (file name) might be drupal-7.2.cs.po instead of cs.po. 1141 $locales[$key]->langcode = preg_replace('!^(.+\.)?([^\.]+)$!', '\2', $locale->name); 1142 // Language codes cannot exceed 12 characters to fit into the {languages} 1143 // table. 1144 if (strlen($locales[$key]->langcode) > 12) { 1145 unset($locales[$key]); 1146 } 1147 } 1148 return $locales; 1149 } 1150 1151 /** 1152 * Installation task; select which locale to use for the current profile. 1153 * 1154 * @param $install_state 1155 * An array of information about the current installation state. The chosen 1156 * locale will be added here, if it was not already selected previously, as 1157 * will a list of all available locales. 1158 * 1159 * @return 1160 * For interactive installations, a form or other page output allowing the 1161 * locale to be selected or providing information about locale selection, if 1162 * a locale has not been chosen. Otherwise, an exception is thrown if a 1163 * locale cannot be chosen automatically. 1164 */ 1165 function install_select_locale(&$install_state) { 1166 // Find all available locales. 1167 $profilename = $install_state['parameters']['profile']; 1168 $locales = install_find_locales($profilename); 1169 $install_state['locales'] += $locales; 1170 1171 if (!empty($_POST['locale'])) { 1172 foreach ($locales as $locale) { 1173 if ($_POST['locale'] == $locale->langcode) { 1174 $install_state['parameters']['locale'] = $locale->langcode; 1175 return; 1176 } 1177 } 1178 } 1179 1180 if (empty($install_state['parameters']['locale'])) { 1181 // If only the built-in (English) language is available, and we are 1182 // performing an interactive installation, inform the user that the 1183 // installer can be localized. Otherwise we assume the user knows what he 1184 // is doing. 1185 if (count($locales) == 1) { 1186 if ($install_state['interactive']) { 1187 drupal_set_title(st('Choose language')); 1188 if (!empty($install_state['parameters']['localize'])) { 1189 $output = '<p>Follow these steps to translate Drupal into your language:</p>'; 1190 $output .= '<ol>'; 1191 $output .= '<li>Download a translation from the <a href="http://localize.drupal.org/download" target="_blank">translation server</a>.</li>'; 1192 $output .= '<li>Place it into the following directory: 1193 <pre> 1194 /profiles/' . $profilename . '/translations/ 1195 </pre></li>'; 1196 $output .= '</ol>'; 1197 $output .= '<p>For more information on installing Drupal in different languages, visit the <a href="http://drupal.org/localize" target="_blank">drupal.org handbook page</a>.</p>'; 1198 $output .= '<p>How should the installation continue?</p>'; 1199 $output .= '<ul>'; 1200 $output .= '<li><a href="install.php?profile=' . $profilename . '">Reload the language selection page after adding translations</a></li>'; 1201 $output .= '<li><a href="install.php?profile=' . $profilename . '&locale=en">Continue installation in English</a></li>'; 1202 $output .= '</ul>'; 1203 } 1204 else { 1205 include_once DRUPAL_ROOT . '/includes/form.inc'; 1206 $elements = drupal_get_form('install_select_locale_form', $locales, $profilename); 1207 $output = drupal_render($elements); 1208 } 1209 return $output; 1210 } 1211 // One language, but not an interactive installation. Assume the user 1212 // knows what he is doing. 1213 $locale = current($locales); 1214 $install_state['parameters']['locale'] = $locale->name; 1215 return; 1216 } 1217 else { 1218 // Allow profile to pre-select the language, skipping the selection. 1219 $function = $profilename . '_profile_details'; 1220 if (function_exists($function)) { 1221 $details = $function(); 1222 if (isset($details['language'])) { 1223 foreach ($locales as $locale) { 1224 if ($details['language'] == $locale->name) { 1225 $install_state['parameters']['locale'] = $locale->name; 1226 return; 1227 } 1228 } 1229 } 1230 } 1231 1232 // We still don't have a locale, so display a form for selecting one. 1233 // Only do this in the case of interactive installations, since this is 1234 // not a real form with submit handlers (the database isn't even set up 1235 // yet), rather just a convenience method for setting parameters in the 1236 // URL. 1237 if ($install_state['interactive']) { 1238 drupal_set_title(st('Choose language')); 1239 include_once DRUPAL_ROOT . '/includes/form.inc'; 1240 $elements = drupal_get_form('install_select_locale_form', $locales, $profilename); 1241 return drupal_render($elements); 1242 } 1243 else { 1244 throw new Exception(st('Sorry, you must select a language to continue the installation.')); 1245 } 1246 } 1247 } 1248 } 1249 1250 /** 1251 * Form constructor for the language selection form. 1252 * 1253 * @ingroup forms 1254 */ 1255 function install_select_locale_form($form, &$form_state, $locales, $profilename) { 1256 include_once DRUPAL_ROOT . '/includes/iso.inc'; 1257 $languages = _locale_get_predefined_list(); 1258 foreach ($locales as $locale) { 1259 $name = $locale->langcode; 1260 if (isset($languages[$name])) { 1261 $name = $languages[$name][0] . (isset($languages[$name][1]) ? ' ' . st('(@language)', array('@language' => $languages[$name][1])) : ''); 1262 } 1263 $form['locale'][$locale->langcode] = array( 1264 '#type' => 'radio', 1265 '#return_value' => $locale->langcode, 1266 '#default_value' => $locale->langcode == 'en' ? 'en' : '', 1267 '#title' => $name . ($locale->langcode == 'en' ? ' ' . st('(built-in)') : ''), 1268 '#parents' => array('locale') 1269 ); 1270 } 1271 if (count($locales) == 1) { 1272 $form['help'] = array( 1273 '#markup' => '<p><a href="install.php?profile=' . $profilename . '&localize=true">' . st('Learn how to install Drupal in other languages') . '</a></p>', 1274 ); 1275 } 1276 $form['actions'] = array('#type' => 'actions'); 1277 $form['actions']['submit'] = array( 1278 '#type' => 'submit', 1279 '#value' => st('Save and continue'), 1280 ); 1281 return $form; 1282 } 1283 1284 /** 1285 * Indicates that there are no profiles available. 1286 */ 1287 function install_no_profile_error() { 1288 drupal_set_title(st('No profiles available')); 1289 return st('We were unable to find any installation profiles. Installation 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.'); 1290 } 1291 1292 /** 1293 * Indicates that Drupal has already been installed. 1294 */ 1295 function install_already_done_error() { 1296 global $base_url; 1297 1298 drupal_set_title(st('Drupal already installed')); 1299 return 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)); 1300 } 1301 1302 /** 1303 * Loads information about the chosen profile during installation. 1304 * 1305 * @param $install_state 1306 * An array of information about the current installation state. The loaded 1307 * profile information will be added here, or an exception will be thrown if 1308 * the profile cannot be loaded. 1309 */ 1310 function install_load_profile(&$install_state) { 1311 $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile'; 1312 if (file_exists($profile_file)) { 1313 include_once $profile_file; 1314 $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['locale']); 1315 } 1316 else { 1317 throw new Exception(st('Sorry, the profile you have chosen cannot be loaded.')); 1318 } 1319 } 1320 1321 /** 1322 * Performs a full bootstrap of Drupal during installation. 1323 * 1324 * @param $install_state 1325 * An array of information about the current installation state. 1326 */ 1327 function install_bootstrap_full(&$install_state) { 1328 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); 1329 } 1330 1331 /** 1332 * Installs required modules via a batch process. 1333 * 1334 * @param $install_state 1335 * An array of information about the current installation state. 1336 * 1337 * @return 1338 * The batch definition. 1339 */ 1340 function install_profile_modules(&$install_state) { 1341 $modules = variable_get('install_profile_modules', array()); 1342 $files = system_rebuild_module_data(); 1343 variable_del('install_profile_modules'); 1344 1345 // Always install required modules first. Respect the dependencies between 1346 // the modules. 1347 $required = array(); 1348 $non_required = array(); 1349 // Although the profile module is marked as required, it needs to go after 1350 // every dependency, including non-required ones. So clear its required 1351 // flag for now to allow it to install late. 1352 $files[$install_state['parameters']['profile']]->info['required'] = FALSE; 1353 // Add modules that other modules depend on. 1354 foreach ($modules as $module) { 1355 if ($files[$module]->requires) { 1356 $modules = array_merge($modules, array_keys($files[$module]->requires)); 1357 } 1358 } 1359 $modules = array_unique($modules); 1360 foreach ($modules as $module) { 1361 if (!empty($files[$module]->info['required'])) { 1362 $required[$module] = $files[$module]->sort; 1363 } 1364 else { 1365 $non_required[$module] = $files[$module]->sort; 1366 } 1367 } 1368 arsort($required); 1369 arsort($non_required); 1370 1371 $operations = array(); 1372 foreach ($required + $non_required as $module => $weight) { 1373 $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name'])); 1374 } 1375 $batch = array( 1376 'operations' => $operations, 1377 'title' => st('Installing @drupal', array('@drupal' => drupal_install_profile_distribution_name())), 1378 'error_message' => st('The installation has encountered an error.'), 1379 'finished' => '_install_profile_modules_finished', 1380 ); 1381 return $batch; 1382 } 1383 1384 /** 1385 * Imports languages via a batch process during installation. 1386 * 1387 * @param $install_state 1388 * An array of information about the current installation state. 1389 * 1390 * @return 1391 * The batch definition, if there are language files to import. 1392 */ 1393 function install_import_locales(&$install_state) { 1394 include_once DRUPAL_ROOT . '/includes/locale.inc'; 1395 $install_locale = $install_state['parameters']['locale']; 1396 1397 include_once DRUPAL_ROOT . '/includes/iso.inc'; 1398 $predefined = _locale_get_predefined_list(); 1399 if (!isset($predefined[$install_locale])) { 1400 // Drupal does not know about this language, so we prefill its values with 1401 // our best guess. The user will be able to edit afterwards. 1402 locale_add_language($install_locale, $install_locale, $install_locale, LANGUAGE_LTR, '', '', TRUE, TRUE); 1403 } 1404 else { 1405 // A known predefined language, details will be filled in properly. 1406 locale_add_language($install_locale, NULL, NULL, NULL, '', '', TRUE, TRUE); 1407 } 1408 1409 // Collect files to import for this language. 1410 $batch = locale_batch_by_language($install_locale, NULL); 1411 if (!empty($batch)) { 1412 // Remember components we cover in this batch set. 1413 variable_set('install_locale_batch_components', $batch['#components']); 1414 return $batch; 1415 } 1416 } 1417 1418 /** 1419 * Form constructor for a form to configure the new site. 1420 * 1421 * @param $install_state 1422 * An array of information about the current installation state. 1423 * 1424 * @see install_configure_form_validate() 1425 * @see install_configure_form_submit() 1426 * @ingroup forms 1427 */ 1428 function install_configure_form($form, &$form_state, &$install_state) { 1429 drupal_set_title(st('Configure site')); 1430 1431 // Warn about settings.php permissions risk 1432 $settings_dir = conf_path(); 1433 $settings_file = $settings_dir . '/settings.php'; 1434 // Check that $_POST is empty so we only show this message when the form is 1435 // first displayed, not on the next page after it is submitted. (We do not 1436 // want to repeat it multiple times because it is a general warning that is 1437 // not related to the rest of the installation process; it would also be 1438 // especially out of place on the last page of the installer, where it would 1439 // distract from the message that the Drupal installation has completed 1440 // successfully.) 1441 if (empty($_POST) && (!drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_dir, FILE_NOT_WRITABLE, 'dir'))) { 1442 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, consult the <a href="@handbook_url">online handbook</a>.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'warning'); 1443 } 1444 1445 drupal_add_js(drupal_get_path('module', 'system') . '/system.js'); 1446 // Add JavaScript time zone detection. 1447 drupal_add_js('misc/timezone.js'); 1448 // We add these strings as settings because JavaScript translation does not 1449 // work during installation. 1450 drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))), 'setting'); 1451 drupal_add_js('jQuery(function () { Drupal.cleanURLsInstallCheck(); });', 'inline'); 1452 // Add JS to show / hide the 'Email administrator about site updates' elements 1453 drupal_add_js('jQuery(function () { Drupal.hideEmailAdministratorCheckbox() });', 'inline'); 1454 // Build menu to allow clean URL check. 1455 menu_rebuild(); 1456 1457 // Cache a fully-built schema. This is necessary for any invocation of 1458 // index.php because: (1) setting cache table entries requires schema 1459 // information, (2) that occurs during bootstrap before any module are 1460 // loaded, so (3) if there is no cached schema, drupal_get_schema() will 1461 // try to generate one but with no loaded modules will return nothing. 1462 // 1463 // This logically could be done during the 'install_finished' task, but the 1464 // clean URL check requires it now. 1465 drupal_get_schema(NULL, TRUE); 1466 1467 // Return the form. 1468 return _install_configure_form($form, $form_state, $install_state); 1469 } 1470 1471 /** 1472 * Installation task; import remaining languages via a batch process. 1473 * 1474 * @param $install_state 1475 * An array of information about the current installation state. 1476 * 1477 * @return 1478 * The batch definition, if there are language files to import. 1479 */ 1480 function install_import_locales_remaining(&$install_state) { 1481 include_once DRUPAL_ROOT . '/includes/locale.inc'; 1482 // Collect files to import for this language. Skip components already covered 1483 // in the initial batch set. 1484 $install_locale = $install_state['parameters']['locale']; 1485 $batch = locale_batch_by_language($install_locale, NULL, variable_get('install_locale_batch_components', array())); 1486 // Remove temporary variable. 1487 variable_del('install_locale_batch_components'); 1488 return $batch; 1489 } 1490 1491 /** 1492 * Finishes importing files at end of installation. 1493 * 1494 * @param $install_state 1495 * An array of information about the current installation state. 1496 * 1497 * @return 1498 * A message informing the user that the installation is complete. 1499 */ 1500 function install_finished(&$install_state) { 1501 drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_distribution_name())), PASS_THROUGH); 1502 $messages = drupal_set_message(); 1503 $output = '<p>' . st('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '</p>'; 1504 $output .= '<p>' . (isset($messages['error']) ? st('Review the messages above before visiting <a href="@url">your new site</a>.', array('@url' => url(''))) : st('<a href="@url">Visit your new site</a>.', array('@url' => url('')))) . '</p>'; 1505 1506 // Flush all caches to ensure that any full bootstraps during the installer 1507 // do not leave stale cached data, and that any content types or other items 1508 // registered by the installation profile are registered correctly. 1509 drupal_flush_all_caches(); 1510 1511 // Remember the profile which was used. 1512 variable_set('install_profile', drupal_get_profile()); 1513 1514 // Installation profiles are always loaded last 1515 db_update('system') 1516 ->fields(array('weight' => 1000)) 1517 ->condition('type', 'module') 1518 ->condition('name', drupal_get_profile()) 1519 ->execute(); 1520 1521 // Cache a fully-built schema. 1522 drupal_get_schema(NULL, TRUE); 1523 1524 // Run cron to populate update status tables (if available) so that users 1525 // will be warned if they've installed an out of date Drupal version. 1526 // Will also trigger indexing of profile-supplied content or feeds. 1527 drupal_cron_run(); 1528 1529 return $output; 1530 } 1531 1532 /** 1533 * Batch callback for batch installation of modules. 1534 */ 1535 function _install_module_batch($module, $module_name, &$context) { 1536 // Install and enable the module right away, so that the module will be 1537 // loaded by drupal_bootstrap in subsequent batch requests, and other 1538 // modules possibly depending on it can safely perform their installation 1539 // steps. 1540 module_enable(array($module), FALSE); 1541 $context['results'][] = $module; 1542 $context['message'] = st('Installed %module module.', array('%module' => $module_name)); 1543 } 1544 1545 /** 1546 * 'Finished' callback for module installation batch. 1547 */ 1548 function _install_profile_modules_finished($success, $results, $operations) { 1549 // Flush all caches to complete the module installation process. Subsequent 1550 // installation tasks will now have full access to the profile's modules. 1551 drupal_flush_all_caches(); 1552 } 1553 1554 /** 1555 * Checks installation requirements and reports any errors. 1556 */ 1557 function install_check_requirements($install_state) { 1558 $profile = $install_state['parameters']['profile']; 1559 1560 // Check the profile requirements. 1561 $requirements = drupal_check_profile($profile); 1562 1563 // If Drupal is not set up already, we need to create a settings file. 1564 if (!$install_state['settings_verified']) { 1565 $writable = FALSE; 1566 $conf_path = './' . conf_path(FALSE, TRUE); 1567 $settings_file = $conf_path . '/settings.php'; 1568 $default_settings_file = './sites/default/default.settings.php'; 1569 $file = $conf_path; 1570 $exists = FALSE; 1571 // Verify that the directory exists. 1572 if (drupal_verify_install_file($conf_path, FILE_EXIST, 'dir')) { 1573 // Check if a settings.php file already exists. 1574 $file = $settings_file; 1575 if (drupal_verify_install_file($settings_file, FILE_EXIST)) { 1576 // If it does, make sure it is writable. 1577 $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE); 1578 $exists = TRUE; 1579 } 1580 } 1581 1582 // If default.settings.php does not exist, or is not readable, throw an 1583 // error. 1584 if (!drupal_verify_install_file($default_settings_file, FILE_EXIST|FILE_READABLE)) { 1585 $requirements['default settings file exists'] = array( 1586 'title' => st('Default settings file'), 1587 'value' => st('The default settings file does not exist.'), 1588 'severity' => REQUIREMENT_ERROR, 1589 'description' => st('The @drupal installer requires that the %default-file file not be modified in any way from the original download.', array('@drupal' => drupal_install_profile_distribution_name(), '%default-file' => $default_settings_file)), 1590 ); 1591 } 1592 // Otherwise, if settings.php does not exist yet, we can try to copy 1593 // default.settings.php to create it. 1594 elseif (!$exists) { 1595 $copied = drupal_verify_install_file($conf_path, FILE_EXIST|FILE_WRITABLE, 'dir') && @copy($default_settings_file, $settings_file); 1596 if ($copied) { 1597 // If the new settings file has the same owner as default.settings.php, 1598 // this means default.settings.php is owned by the webserver user. 1599 // This is an inherent security weakness because it allows a malicious 1600 // webserver process to append arbitrary PHP code and then execute it. 1601 // However, it is also a common configuration on shared hosting, and 1602 // there is nothing Drupal can do to prevent it. In this situation, 1603 // having settings.php also owned by the webserver does not introduce 1604 // any additional security risk, so we keep the file in place. 1605 if (fileowner($default_settings_file) === fileowner($settings_file)) { 1606 $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE); 1607 $exists = TRUE; 1608 } 1609 // If settings.php and default.settings.php have different owners, this 1610 // probably means the server is set up "securely" (with the webserver 1611 // running as its own user, distinct from the user who owns all the 1612 // Drupal PHP files), although with either a group or world writable 1613 // sites directory. Keeping settings.php owned by the webserver would 1614 // therefore introduce a security risk. It would also cause a usability 1615 // problem, since site owners who do not have root access to the file 1616 // system would be unable to edit their settings file later on. We 1617 // therefore must delete the file we just created and force the 1618 // administrator to log on to the server and create it manually. 1619 else { 1620 $deleted = @drupal_unlink($settings_file); 1621 // We expect deleting the file to be successful (since we just 1622 // created it ourselves above), but if it fails somehow, we set a 1623 // variable so we can display a one-time error message to the 1624 // administrator at the bottom of the requirements list. We also try 1625 // to make the file writable, to eliminate any conflicting error 1626 // messages in the requirements list. 1627 $exists = !$deleted; 1628 if ($exists) { 1629 $settings_file_ownership_error = TRUE; 1630 $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE); 1631 } 1632 } 1633 } 1634 } 1635 1636 // If settings.php does not exist, throw an error. 1637 if (!$exists) { 1638 $requirements['settings file exists'] = array( 1639 'title' => st('Settings file'), 1640 'value' => st('The settings file does not exist.'), 1641 'severity' => REQUIREMENT_ERROR, 1642 'description' => st('The @drupal installer requires that you create a settings file as part of the installation process. Copy the %default_file file to %file. More details about installing Drupal are available in <a href="@install_txt">INSTALL.txt</a>.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'INSTALL.txt')), 1643 ); 1644 } 1645 else { 1646 $requirements['settings file exists'] = array( 1647 'title' => st('Settings file'), 1648 'value' => st('The %file file exists.', array('%file' => $file)), 1649 ); 1650 // If settings.php is not writable, throw an error. 1651 if (!$writable) { 1652 $requirements['settings file writable'] = array( 1653 'title' => st('Settings file'), 1654 'value' => st('The settings file is not writable.'), 1655 'severity' => REQUIREMENT_ERROR, 1656 'description' => st('The @drupal installer requires write permissions to %file during the installation process. If you are unsure how to grant file permissions, consult the <a href="@handbook_url">online handbook</a>.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')), 1657 ); 1658 } 1659 else { 1660 $requirements['settings file'] = array( 1661 'title' => st('Settings file'), 1662 'value' => st('The settings file is writable.'), 1663 ); 1664 } 1665 if (!empty($settings_file_ownership_error)) { 1666 $requirements['settings file ownership'] = array( 1667 'title' => st('Settings file'), 1668 'value' => st('The settings file is owned by the web server.'), 1669 'severity' => REQUIREMENT_ERROR, 1670 'description' => st('The @drupal installer failed to create a settings file with proper file ownership. Log on to your web server, remove the existing %file file, and create a new one by copying the %default_file file to %file. More details about installing Drupal are available in <a href="@install_txt">INSTALL.txt</a>. If you have problems with the file permissions on your server, consult the <a href="@handbook_url">online handbook</a>.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'INSTALL.txt', '@handbook_url' => 'http://drupal.org/server-permissions')), 1671 ); 1672 } 1673 } 1674 } 1675 return $requirements; 1676 } 1677 1678 /** 1679 * Form constructor for a site configuration form. 1680 * 1681 * @param $install_state 1682 * An array of information about the current installation state. 1683 * 1684 * @see install_configure_form() 1685 * @see install_configure_form_validate() 1686 * @see install_configure_form_submit() 1687 * @ingroup forms 1688 */ 1689 function _install_configure_form($form, &$form_state, &$install_state) { 1690 include_once DRUPAL_ROOT . '/includes/locale.inc'; 1691 1692 $form['site_information'] = array( 1693 '#type' => 'fieldset', 1694 '#title' => st('Site information'), 1695 '#collapsible' => FALSE, 1696 ); 1697 $form['site_information']['site_name'] = array( 1698 '#type' => 'textfield', 1699 '#title' => st('Site name'), 1700 '#required' => TRUE, 1701 '#weight' => -20, 1702 ); 1703 $form['site_information']['site_mail'] = array( 1704 '#type' => 'textfield', 1705 '#title' => st('Site e-mail address'), 1706 '#default_value' => ini_get('sendmail_from'), 1707 '#description' => st("Automated e-mails, such as registration information, will be sent from this address. Use an address ending in your site's domain to help prevent these e-mails from being flagged as spam."), 1708 '#required' => TRUE, 1709 '#weight' => -15, 1710 ); 1711 $form['admin_account'] = array( 1712 '#type' => 'fieldset', 1713 '#title' => st('Site maintenance account'), 1714 '#collapsible' => FALSE, 1715 ); 1716 1717 $form['admin_account']['account']['#tree'] = TRUE; 1718 $form['admin_account']['account']['name'] = array('#type' => 'textfield', 1719 '#title' => st('Username'), 1720 '#maxlength' => USERNAME_MAX_LENGTH, 1721 '#description' => st('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'), 1722 '#required' => TRUE, 1723 '#weight' => -10, 1724 '#attributes' => array('class' => array('username')), 1725 ); 1726 1727 $form['admin_account']['account']['mail'] = array('#type' => 'textfield', 1728 '#title' => st('E-mail address'), 1729 '#maxlength' => EMAIL_MAX_LENGTH, 1730 '#required' => TRUE, 1731 '#weight' => -5, 1732 ); 1733 $form['admin_account']['account']['pass'] = array( 1734 '#type' => 'password_confirm', 1735 '#required' => TRUE, 1736 '#size' => 25, 1737 '#weight' => 0, 1738 ); 1739 1740 $form['server_settings'] = array( 1741 '#type' => 'fieldset', 1742 '#title' => st('Server settings'), 1743 '#collapsible' => FALSE, 1744 ); 1745 1746 $countries = country_get_list(); 1747 $form['server_settings']['site_default_country'] = array( 1748 '#type' => 'select', 1749 '#title' => st('Default country'), 1750 '#empty_value' => '', 1751 '#default_value' => variable_get('site_default_country', NULL), 1752 '#options' => $countries, 1753 '#description' => st('Select the default country for the site.'), 1754 '#weight' => 0, 1755 ); 1756 1757 $form['server_settings']['date_default_timezone'] = array( 1758 '#type' => 'select', 1759 '#title' => st('Default time zone'), 1760 '#default_value' => date_default_timezone_get(), 1761 '#options' => system_time_zones(), 1762 '#description' => st('By default, dates in this site will be displayed in the chosen time zone.'), 1763 '#weight' => 5, 1764 '#attributes' => array('class' => array('timezone-detect')), 1765 ); 1766 1767 $form['server_settings']['clean_url'] = array( 1768 '#type' => 'hidden', 1769 '#default_value' => 0, 1770 '#attributes' => array('id' => 'edit-clean-url', 'class' => array('install')), 1771 ); 1772 1773 $form['update_notifications'] = array( 1774 '#type' => 'fieldset', 1775 '#title' => st('Update notifications'), 1776 '#collapsible' => FALSE, 1777 ); 1778 $form['update_notifications']['update_status_module'] = array( 1779 '#type' => 'checkboxes', 1780 '#options' => array( 1781 1 => st('Check for updates automatically'), 1782 2 => st('Receive e-mail notifications'), 1783 ), 1784 '#default_value' => array(1, 2), 1785 '#description' => st('The system will notify you when updates and important security releases are available for installed components. Anonymous information about your site is sent to <a href="@drupal">Drupal.org</a>.', array('@drupal' => 'http://drupal.org')), 1786 '#weight' => 15, 1787 ); 1788 1789 $form['actions'] = array('#type' => 'actions'); 1790 $form['actions']['submit'] = array( 1791 '#type' => 'submit', 1792 '#value' => st('Save and continue'), 1793 '#weight' => 15, 1794 ); 1795 1796 return $form; 1797 } 1798 1799 /** 1800 * Form validation handler for install_configure_form(). 1801 * 1802 * @see install_configure_form_submit() 1803 */ 1804 function install_configure_form_validate($form, &$form_state) { 1805 if ($error = user_validate_name($form_state['values']['account']['name'])) { 1806 form_error($form['admin_account']['account']['name'], $error); 1807 } 1808 if ($error = user_validate_mail($form_state['values']['account']['mail'])) { 1809 form_error($form['admin_account']['account']['mail'], $error); 1810 } 1811 if ($error = user_validate_mail($form_state['values']['site_mail'])) { 1812 form_error($form['site_information']['site_mail'], $error); 1813 } 1814 } 1815 1816 /** 1817 * Form submission handler for install_configure_form(). 1818 * 1819 * @see install_configure_form_validate() 1820 */ 1821 function install_configure_form_submit($form, &$form_state) { 1822 global $user; 1823 1824 variable_set('site_name', $form_state['values']['site_name']); 1825 variable_set('site_mail', $form_state['values']['site_mail']); 1826 variable_set('date_default_timezone', $form_state['values']['date_default_timezone']); 1827 variable_set('site_default_country', $form_state['values']['site_default_country']); 1828 1829 // Enable update.module if this option was selected. 1830 if ($form_state['values']['update_status_module'][1]) { 1831 module_enable(array('update'), FALSE); 1832 1833 // Add the site maintenance account's email address to the list of 1834 // addresses to be notified when updates are available, if selected. 1835 if ($form_state['values']['update_status_module'][2]) { 1836 variable_set('update_notify_emails', array($form_state['values']['account']['mail'])); 1837 } 1838 } 1839 1840 // We precreated user 1 with placeholder values. Let's save the real values. 1841 $account = user_load(1); 1842 $merge_data = array('init' => $form_state['values']['account']['mail'], 'roles' => !empty($account->roles) ? $account->roles : array(), 'status' => 1, 'timezone' => $form_state['values']['date_default_timezone']); 1843 user_save($account, array_merge($form_state['values']['account'], $merge_data)); 1844 // Load global $user and perform final login tasks. 1845 $user = user_load(1); 1846 user_login_finalize(); 1847 1848 if (isset($form_state['values']['clean_url'])) { 1849 variable_set('clean_url', $form_state['values']['clean_url']); 1850 } 1851 1852 // Record when this install ran. 1853 variable_set('install_time', $_SERVER['REQUEST_TIME']); 1854 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title