/[drupal]/drupal/install.php
ViewVC logotype

Contents of /drupal/install.php

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.221 - (show annotations) (download) (as text)
Wed Nov 4 04:56:54 2009 UTC (3 weeks, 3 days ago) by webchick
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10
Changes since 1.220: +2 -2 lines
File MIME type: text/x-php
#367567 by sun, effulgentsia, yched, and quicksketch: Use AJAX framework for 'Add more' links.
1 <?php
2 // $Id: install.php,v 1.220 2009/10/27 06:07:38 webchick Exp $
3
4 /**
5 * Root directory of Drupal installation.
6 */
7 define('DRUPAL_ROOT', getcwd());
8
9 require_once DRUPAL_ROOT . '/includes/install.inc';
10
11 /**
12 * Global flag to indicate that site is in installation mode.
13 */
14 define('MAINTENANCE_MODE', 'install');
15
16 /**
17 * Global flag to indicate that a task should not be run during the current
18 * installation request.
19 *
20 * This can be used to skip running an installation task when certain
21 * conditions are met, even though the task may still show on the list of
22 * installation tasks presented to the user. For example, the Drupal installer
23 * uses this flag to skip over the database configuration form when valid
24 * database connection information is already available from settings.php. It
25 * also uses this flag to skip language import tasks when the installation is
26 * being performed in English.
27 */
28 define('INSTALL_TASK_SKIP', 1);
29
30 /**
31 * Global flag to indicate that a task should be run on each installation
32 * request that reaches it.
33 *
34 * This is primarily used by the Drupal installer for bootstrap-related tasks.
35 */
36 define('INSTALL_TASK_RUN_IF_REACHED', 2);
37
38 /**
39 * Global flag to indicate that a task should be run on each installation
40 * request that reaches it, until the database is set up and we are able to
41 * record the fact that it already ran.
42 *
43 * This is the default method for running tasks and should be used for most
44 * tasks that occur after the database is set up; these tasks will then run
45 * once and be marked complete once they are successfully finished. For
46 * example, the Drupal installer uses this flag for the batch installation of
47 * modules on the new site, and also for the configuration form that collects
48 * basic site information and sets up the site maintenance account.
49 */
50 define('INSTALL_TASK_RUN_IF_NOT_COMPLETED', 3);
51
52 /**
53 * Install Drupal either interactively or via an array of passed-in settings.
54 *
55 * The Drupal installation happens in a series of steps, which may be spread
56 * out over multiple page requests. Each request begins by trying to determine
57 * the last completed installation step (also known as a "task"), if one is
58 * available from a previous request. Control is then passed to the task
59 * handler, which processes the remaining tasks that need to be run until (a)
60 * an error is thrown, (b) a new page needs to be displayed, or (c) the
61 * installation finishes (whichever happens first).
62 *
63 * @param $settings
64 * An optional array of installation settings. Leave this empty for a normal,
65 * interactive, browser-based installation intended to occur over multiple
66 * page requests. Alternatively, if an array of settings is passed in, the
67 * installer will attempt to use it to perform the installation in a single
68 * page request (optimized for the command line) and not send any output
69 * intended for the web browser. See install_state_defaults() for a list of
70 * elements that are allowed to appear in this array.
71 *
72 * @see install_state_defaults()
73 */
74 function install_drupal($settings = array()) {
75 global $install_state;
76 // Initialize the installation state with the settings that were passed in,
77 // as well as a boolean indicating whether or not this is an interactive
78 // installation.
79 $interactive = empty($settings);
80 $install_state = $settings + array('interactive' => $interactive) + install_state_defaults();
81 try {
82 // Begin the page request. This adds information about the current state of
83 // the Drupal installation to the passed-in array.
84 install_begin_request($install_state);
85 // Based on the installation state, run the remaining tasks for this page
86 // request, and collect any output.
87 $output = install_run_tasks($install_state);
88 }
89 catch (Exception $e) {
90 // When an installation error occurs, either send the error to the web
91 // browser or pass on the exception so the calling script can use it.
92 if ($install_state['interactive']) {
93 install_display_output($e->getMessage(), $install_state);
94 }
95 else {
96 throw $e;
97 }
98 }
99 // All available tasks for this page request are now complete. Interactive
100 // installations can send output to the browser or redirect the user to the
101 // next page.
102 if ($install_state['interactive']) {
103 if ($install_state['parameters_changed']) {
104 // Redirect to the correct page if the URL parameters have changed.
105 install_goto(install_redirect_url($install_state));
106 }
107 elseif (isset($output)) {
108 // Display a page only if some output is available. Otherwise it is
109 // possible that we are printing a JSON page and theme output should
110 // not be shown.
111 install_display_output($output, $install_state);
112 }
113 }
114 }
115
116 /**
117 * Return an array of default settings for the global installation state.
118 *
119 * The installation state is initialized with these settings at the beginning
120 * of each page request. They may evolve during the page request, but they are
121 * initialized again once the next request begins.
122 *
123 * Non-interactive Drupal installations can override some of these default
124 * settings by passing in an array to the installation script, most notably
125 * 'parameters' (which contains one-time parameters such as 'profile' and
126 * 'locale' that are normally passed in via the URL) and 'forms' (which can
127 * be used to programmatically submit forms during the installation; the keys
128 * of each element indicate the name of the installation task that the form
129 * submission is for, and the values are used as the $form_state['values']
130 * array that is passed on to the form submission via drupal_form_submit()).
131 *
132 * @see drupal_form_submit()
133 */
134 function install_state_defaults() {
135 $defaults = array(
136 // The current task being processed.
137 'active_task' => NULL,
138 // The last task that was completed during the previous installation
139 // request.
140 'completed_task' => NULL,
141 // This becomes TRUE only when Drupal's system module is installed.
142 'database_tables_exist' => FALSE,
143 // An array of forms to be programmatically submitted during the
144 // installation. The keys of each element indicate the name of the
145 // installation task that the form submission is for, and the values are
146 // used as the $form_state['values'] array that is passed on to the form
147 // submission via drupal_form_submit().
148 'forms' => array(),
149 // This becomes TRUE only at the end of the installation process, after
150 // all available tasks have been completed and Drupal is fully installed.
151 // It is used by the installer to store correct information in the database
152 // about the completed installation, as well as to inform theme functions
153 // that all tasks are finished (so that the task list can be displayed
154 // correctly).
155 'installation_finished' => FALSE,
156 // Whether or not this installation is interactive. By default this will
157 // be set to FALSE if settings are passed in to install_drupal().
158 'interactive' => TRUE,
159 // An array of available languages for the installation.
160 'locales' => array(),
161 // An array of parameters for the installation, pre-populated by the URL
162 // or by the settings passed in to install_drupal(). This is primarily
163 // used to store 'profile' (the name of the chosen installation profile)
164 // and 'locale' (the name of the chosen installation language), since
165 // these settings need to persist from page request to page request before
166 // the database is available for storage.
167 'parameters' => array(),
168 // Whether or not the parameters have changed during the current page
169 // request. For interactive installations, this will trigger a page
170 // redirect.
171 'parameters_changed' => FALSE,
172 // An array of information about the chosen installation profile. This will
173 // be filled in based on the profile's .info file.
174 'profile_info' => array(),
175 // An array of available installation profiles.
176 'profiles' => array(),
177 // An array of server variables that will be substituted into the global
178 // $_SERVER array via drupal_override_server_variables(). Used by
179 // non-interactive installations only.
180 'server' => array(),
181 // This becomes TRUE only when a valid database connection can be
182 // established.
183 'settings_verified' => FALSE,
184 // Installation tasks can set this to TRUE to force the page request to
185 // end (even if there is no themable output), in the case of an interactive
186 // installation. This is needed only rarely; for example, it would be used
187 // by an installation task that prints JSON output rather than returning a
188 // themed page. The most common example of this is during batch processing,
189 // but the Drupal installer automatically takes care of setting this
190 // parameter properly in that case, so that individual installation tasks
191 // which implement the batch API do not need to set it themselves.
192 'stop_page_request' => FALSE,
193 // Installation tasks can set this to TRUE to indicate that the task should
194 // be run again, even if it normally wouldn't be. This can be used, for
195 // example, if a single task needs to be spread out over multiple page
196 // requests, or if it needs to perform some validation before allowing
197 // itself to be marked complete. The most common examples of this are batch
198 // processing and form submissions, but the Drupal installer automatically
199 // takes care of setting this parameter properly in those cases, so that
200 // individual installation tasks which implement the batch API or form API
201 // do not need to set it themselves.
202 'task_not_complete' => FALSE,
203 // A list of installation tasks which have already been performed during
204 // the current page request.
205 'tasks_performed' => array(),
206 );
207 return $defaults;
208 }
209
210 /**
211 * Begin an installation request, modifying the installation state as needed.
212 *
213 * This function performs commands that must run at the beginning of every page
214 * request. It throws an exception if the installation should not proceed.
215 *
216 * @param $install_state
217 * An array of information about the current installation state. This is
218 * modified with information gleaned from the beginning of the page request.
219 */
220 function install_begin_request(&$install_state) {
221 // Allow command line scripts to override server variables used by Drupal.
222 require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
223 if (!$install_state['interactive']) {
224 drupal_override_server_variables($install_state['server']);
225 }
226
227 // The user agent header is used to pass a database prefix in the request when
228 // running tests. However, for security reasons, it is imperative that no
229 // installation be permitted using such a prefix.
230 if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
231 header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
232 exit;
233 }
234
235 drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
236
237 // This must go after drupal_bootstrap(), which unsets globals!
238 global $conf;
239
240 require_once DRUPAL_ROOT . '/modules/system/system.install';
241 require_once DRUPAL_ROOT . '/includes/common.inc';
242 require_once DRUPAL_ROOT . '/includes/file.inc';
243 require_once DRUPAL_ROOT . '/includes/path.inc';
244
245 // Load module basics (needed for hook invokes).
246 include_once DRUPAL_ROOT . '/includes/module.inc';
247 include_once DRUPAL_ROOT . '/includes/session.inc';
248
249 // Set up $language, so t() caller functions will still work.
250 drupal_language_initialize();
251
252 include_once DRUPAL_ROOT . '/includes/entity.inc';
253 $module_list['system']['filename'] = 'modules/system/system.module';
254 $module_list['filter']['filename'] = 'modules/filter/filter.module';
255 $module_list['user']['filename'] = 'modules/user/user.module';
256 module_list(TRUE, FALSE, FALSE, $module_list);
257 drupal_load('module', 'system');
258 drupal_load('module', 'filter');
259 drupal_load('module', 'user');
260
261 // Load the cache infrastructure using a "fake" cache implementation that
262 // does not attempt to write to the database. We need this during the initial
263 // part of the installer because the database is not available yet. We
264 // continue to use it even when the database does become available, in order
265 // to preserve consistency between interactive and command-line installations
266 // (the latter complete in one page request and therefore are forced to
267 // continue using the cache implementation they started with) and also
268 // because any data put in the cache during the installer is inherently
269 // suspect, due to the fact that Drupal is not fully set up yet.
270 require_once DRUPAL_ROOT . '/includes/cache.inc';
271 require_once DRUPAL_ROOT . '/includes/cache-install.inc';
272 $conf['cache_inc'] = 'includes/cache.inc';
273 $conf['cache_default_class'] = 'DrupalFakeCache';
274
275 // Prepare for themed output, if necessary. We need to run this at the
276 // beginning of the page request to avoid a different theme accidentally
277 // getting set.
278 if ($install_state['interactive']) {
279 drupal_maintenance_theme();
280 }
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 // Since previous versions of Drupal stored database connection information
297 // in the 'db_url' variable, we should never let an installation proceed if
298 // this variable is defined and the settings file was not verified above
299 // (otherwise we risk installing over an existing site whose settings file
300 // has not yet been updated).
301 if (!empty($GLOBALS['db_url'])) {
302 throw new Exception(install_already_done_error());
303 }
304 }
305
306 // Modify the installation state as appropriate.
307 $install_state['completed_task'] = $task;
308 $install_state['database_tables_exist'] = !empty($task);
309
310 // Add any installation parameters passed in via the URL.
311 $install_state['parameters'] += $_GET;
312
313 // Validate certain core settings that are used throughout the installation.
314 if (!empty($install_state['parameters']['profile'])) {
315 $install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']);
316 }
317 if (!empty($install_state['parameters']['locale'])) {
318 $install_state['parameters']['locale'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['locale']);
319 }
320 }
321
322 /**
323 * Run all tasks for the current installation request.
324 *
325 * In the case of an interactive installation, all tasks will be attempted
326 * until one is reached that has output which needs to be displayed to the
327 * user, or until a page redirect is required. Otherwise, tasks will be
328 * attempted until the installation is finished.
329 *
330 * @param $install_state
331 * An array of information about the current installation state. This is
332 * passed along to each task, so it can be modified if necessary.
333 * @return
334 * HTML output from the last completed task.
335 */
336 function install_run_tasks(&$install_state) {
337 do {
338 // Obtain a list of tasks to perform. The list of tasks itself can be
339 // dynamic (e.g., some might be defined by the installation profile,
340 // which is not necessarily known until the earlier tasks have run),
341 // so we regenerate the remaining tasks based on the installation state,
342 // each time through the loop.
343 $tasks_to_perform = install_tasks_to_perform($install_state);
344 // Run the first task on the list.
345 reset($tasks_to_perform);
346 $task_name = key($tasks_to_perform);
347 $task = array_shift($tasks_to_perform);
348 $install_state['active_task'] = $task_name;
349 $original_parameters = $install_state['parameters'];
350 $output = install_run_task($task, $install_state);
351 $install_state['parameters_changed'] = ($install_state['parameters'] != $original_parameters);
352 // Store this task as having been performed during the current request,
353 // and save it to the database as completed, if we need to and if the
354 // database is in a state that allows us to do so. Also mark the
355 // installation as 'done' when we have run out of tasks.
356 if (!$install_state['task_not_complete']) {
357 $install_state['tasks_performed'][] = $task_name;
358 $install_state['installation_finished'] = empty($tasks_to_perform);
359 if ($install_state['database_tables_exist'] && ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished'])) {
360 drupal_install_initialize_database();
361 variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
362 }
363 }
364 // Stop when there are no tasks left. In the case of an interactive
365 // installation, also stop if we have some output to send to the browser,
366 // the URL parameters have changed, or an end to the page request was
367 // specifically called for.
368 $finished = empty($tasks_to_perform) || ($install_state['interactive'] && (isset($output) || $install_state['parameters_changed'] || $install_state['stop_page_request']));
369 } while (!$finished);
370 return $output;
371 }
372
373 /**
374 * Run an individual installation task.
375 *
376 * @param $task
377 * An array of information about the task to be run.
378 * @param $install_state
379 * An array of information about the current installation state. This is
380 * passed in by reference so that it can be modified by the task.
381 * @return
382 * The output of the task function, if there is any.
383 */
384 function install_run_task($task, &$install_state) {
385 $function = $task['function'];
386
387 if ($task['type'] == 'form') {
388 require_once DRUPAL_ROOT . '/includes/form.inc';
389 if ($install_state['interactive']) {
390 // For interactive forms, build the form and ensure that it will not
391 // redirect, since the installer handles its own redirection only after
392 // marking the form submission task complete.
393 $form_state = array(
394 // We need to pass $install_state by reference in order for forms to
395 // modify it, since the form API will use it in call_user_func_array(),
396 // which requires that referenced variables be passed explicitly.
397 'build_info' => array('args' => array(&$install_state)),
398 'no_redirect' => TRUE,
399 );
400 $form = drupal_build_form($function, $form_state);
401 // If a successful form submission did not occur, the form needs to be
402 // rendered, which means the task is not complete yet.
403 if (empty($form_state['executed'])) {
404 $install_state['task_not_complete'] = TRUE;
405 return drupal_render($form);
406 }
407 // Otherwise, return nothing so the next task will run in the same
408 // request.
409 return;
410 }
411 else {
412 // For non-interactive forms, submit the form programmatically with the
413 // values taken from the installation state. Throw an exception if any
414 // errors were encountered.
415 $form_state = array('values' => !empty($install_state['forms'][$function]) ? $install_state['forms'][$function] : array());
416 drupal_form_submit($function, $form_state, $install_state);
417 $errors = form_get_errors();
418 if (!empty($errors)) {
419 throw new Exception(implode("\n", $errors));
420 }
421 }
422 }
423
424 elseif ($task['type'] == 'batch') {
425 // Start a new batch based on the task function, if one is not running
426 // already.
427 $current_batch = variable_get('install_current_batch');
428 if (!$install_state['interactive'] || !$current_batch) {
429 $batch = $function($install_state);
430 if (empty($batch)) {
431 // If the task did some processing and decided no batch was necessary,
432 // there is nothing more to do here.
433 return;
434 }
435 batch_set($batch);
436 // For interactive batches, we need to store the fact that this batch
437 // task is currently running. Otherwise, we need to make sure the batch
438 // will complete in one page request.
439 if ($install_state['interactive']) {
440 variable_set('install_current_batch', $function);
441 }
442 else {
443 $batch =& batch_get();
444 $batch['progressive'] = FALSE;
445 }
446 // Process the batch. For progressive batches, this will redirect.
447 // Otherwise, the batch will complete.
448 batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state));
449 }
450 // If we are in the middle of processing this batch, keep sending back
451 // any output from the batch process, until the task is complete.
452 elseif ($current_batch == $function) {
453 include_once DRUPAL_ROOT . '/includes/batch.inc';
454 $output = _batch_page();
455 // The task is complete when we try to access the batch page and receive
456 // FALSE in return, since this means we are at a URL where we are no
457 // longer requesting a batch ID.
458 if ($output === FALSE) {
459 // Return nothing so the next task will run in the same request.
460 variable_del('install_current_batch');
461 return;
462 }
463 else {
464 // We need to force the page request to end if the task is not
465 // complete, since the batch API sometimes prints JSON output
466 // rather than returning a themed page.
467 $install_state['task_not_complete'] = $install_state['stop_page_request'] = TRUE;
468 return $output;
469 }
470 }
471 }
472
473 else {
474 // For normal tasks, just return the function result, whatever it is.
475 return $function($install_state);
476 }
477 }
478
479 /**
480 * Return a list of tasks to perform during the current installation request.
481 *
482 * Note that the list of tasks can change based on the installation state as
483 * the page request evolves (for example, if an installation profile hasn't
484 * been selected yet, we don't yet know which profile tasks need to be run).
485 *
486 * @param $install_state
487 * An array of information about the current installation state.
488 * @return
489 * A list of tasks to be performed, with associated metadata.
490 */
491 function install_tasks_to_perform($install_state) {
492 // Start with a list of all currently available tasks.
493 $tasks = install_tasks($install_state);
494 foreach ($tasks as $name => $task) {
495 // Remove any tasks that were already performed or that never should run.
496 // Also, if we started this page request with an indication of the last
497 // task that was completed, skip that task and all those that come before
498 // it, unless they are marked as always needing to run.
499 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)) {
500 unset($tasks[$name]);
501 }
502 if (!empty($install_state['completed_task']) && $name == $install_state['completed_task']) {
503 $completed_task_found = TRUE;
504 }
505 }
506 return $tasks;
507 }
508
509 /**
510 * Return a list of all tasks the installer currently knows about.
511 *
512 * This function will return tasks regardless of whether or not they are
513 * intended to run on the current page request. However, the list can change
514 * based on the installation state (for example, if an installation profile
515 * hasn't been selected yet, we don't yet know which profile tasks will be
516 * available).
517 *
518 * @param $install_state
519 * An array of information about the current installation state.
520 * @return
521 * A list of tasks, with associated metadata.
522 */
523 function install_tasks($install_state) {
524 // Determine whether translation import tasks will need to be performed.
525 $needs_translations = count($install_state['locales']) > 1 && !empty($install_state['parameters']['locale']) && $install_state['parameters']['locale'] != 'en';
526
527 // Start with the core installation tasks that run before handing control
528 // to the install profile.
529 $tasks = array(
530 'install_select_profile' => array(
531 'display_name' => st('Choose profile'),
532 'display' => count($install_state['profiles']) != 1,
533 'run' => INSTALL_TASK_RUN_IF_REACHED,
534 ),
535 'install_select_locale' => array(
536 'display_name' => st('Choose language'),
537 'run' => INSTALL_TASK_RUN_IF_REACHED,
538 ),
539 'install_load_profile' => array(
540 'run' => INSTALL_TASK_RUN_IF_REACHED,
541 ),
542 'install_verify_requirements' => array(
543 'display_name' => st('Verify requirements'),
544 ),
545 'install_settings_form' => array(
546 'display_name' => st('Set up database'),
547 'type' => 'form',
548 'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
549 ),
550 'install_system_module' => array(
551 ),
552 'install_bootstrap_full' => array(
553 'run' => INSTALL_TASK_RUN_IF_REACHED,
554 ),
555 'install_profile_modules' => array(
556 'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Install profile'),
557 'type' => 'batch',
558 ),
559 'install_import_locales' => array(
560 'display_name' => st('Set up translations'),
561 'display' => $needs_translations,
562 'type' => 'batch',
563 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
564 ),
565 'install_configure_form' => array(
566 'display_name' => st('Configure site'),
567 'type' => 'form',
568 ),
569 );
570
571 // Now add any tasks defined by the installation profile.
572 if (!empty($install_state['parameters']['profile'])) {
573 $function = $install_state['parameters']['profile'] . '_install_tasks';
574 if (function_exists($function)) {
575 $result = $function($install_state);
576 if (is_array($result)) {
577 $tasks += $result;
578 }
579 }
580 }
581
582 // Finish by adding the remaining core tasks.
583 $tasks += array(
584 'install_import_locales_remaining' => array(
585 'display_name' => st('Finish translations'),
586 'display' => $needs_translations,
587 'type' => 'batch',
588 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
589 ),
590 'install_finished' => array(
591 'display_name' => st('Finished'),
592 ),
593 );
594
595 // Allow the installation profile to modify the full list of tasks.
596 if (!empty($install_state['parameters']['profile'])) {
597 $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile';
598 if (is_file($profile_file)) {
599 include_once $profile_file;
600 $function = $install_state['parameters']['profile'] . '_install_tasks_alter';
601 if (function_exists($function)) {
602 $function($tasks, $install_state);
603 }
604 }
605 }
606
607 // Fill in default parameters for each task before returning the list.
608 foreach ($tasks as $task_name => &$task) {
609 $task += array(
610 'display_name' => NULL,
611 'display' => !empty($task['display_name']),
612 'type' => 'normal',
613 'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED,
614 'function' => $task_name,
615 );
616 }
617 return $tasks;
618 }
619
620 /**
621 * Return a list of tasks that should be displayed to the end user.
622 *
623 * The output of this function is a list suitable for sending to
624 * theme_task_list().
625 *
626 * @param $install_state
627 * An array of information about the current installation state.
628 * @return
629 * A list of tasks, with keys equal to the machine-readable task name and
630 * values equal to the name that should be displayed.
631 *
632 * @see theme_task_list()
633 */
634 function install_tasks_to_display($install_state) {
635 $displayed_tasks = array();
636 foreach (install_tasks($install_state) as $name => $task) {
637 if ($task['display']) {
638 $displayed_tasks[$name] = $task['display_name'];
639 }
640 }
641 return $displayed_tasks;
642 }
643
644 /**
645 * Return the URL that should be redirected to during an installation request.
646 *
647 * The output of this function is suitable for sending to install_goto().
648 *
649 * @param $install_state
650 * An array of information about the current installation state.
651 * @return
652 * The URL to redirect to.
653 *
654 * @see install_full_redirect_url()
655 */
656 function install_redirect_url($install_state) {
657 return 'install.php?' . drupal_http_build_query($install_state['parameters']);
658 }
659
660 /**
661 * Return the complete URL that should be redirected to during an installation
662 * request.
663 *
664 * @param $install_state
665 * An array of information about the current installation state.
666 * @return
667 * The complete URL to redirect to.
668 *
669 * @see install_redirect_url()
670 */
671 function install_full_redirect_url($install_state) {
672 global $base_url;
673 return $base_url . '/' . install_redirect_url($install_state);
674 }
675
676 /**
677 * Display themed installer output and end the page request.
678 *
679 * Installation tasks should use drupal_set_title() to set the desired page
680 * title, but otherwise this function takes care of theming the overall page
681 * output during every step of the installation.
682 *
683 * @param $output
684 * The content to display on the main part of the page.
685 * @param $install_state
686 * An array of information about the current installation state.
687 */
688 function install_display_output($output, $install_state) {
689 drupal_page_header();
690 // Only show the task list if there is an active task; otherwise, the page
691 // request has ended before tasks have even been started, so there is nothing
692 // meaningful to show.
693 if (isset($install_state['active_task'])) {
694 // Let the theming function know when every step of the installation has
695 // been completed.
696 $active_task = $install_state['installation_finished'] ? NULL : $install_state['active_task'];
697 drupal_add_region_content('sidebar_first', theme('task_list', array('items' => install_tasks_to_display($install_state), 'active' => $active_task)));
698 }
699 print theme($install_state['database_tables_exist'] ? 'maintenance_page' : 'install_page', array('content' => $output));
700 exit;
701 }
702
703 /**
704 * Installation task; verify the requirements for installing Drupal.
705 *
706 * @param $install_state
707 * An array of information about the current installation state.
708 * @return
709 * A themed status report, or an exception if there are requirement errors.
710 * Otherwise, no output is returned, so that the next task can be run
711 * in the same page request.
712 */
713 function install_verify_requirements(&$install_state) {
714 // Check the installation requirements for Drupal and this profile.
715 $requirements = install_check_requirements($install_state);
716
717 // Verify existence of all required modules.
718 $requirements += drupal_verify_profile($install_state);
719
720 // Check the severity of the requirements reported.
721 $severity = drupal_requirements_severity($requirements);
722
723 if ($severity == REQUIREMENT_ERROR) {
724 if ($install_state['interactive']) {
725 drupal_set_title(st('Requirements problem'));
726 $status_report = theme('status_report', array('requirements' => $requirements));
727 $status_report .= st('Check the error messages and <a href="!url">proceed with the installation</a>.', array('!url' => request_uri()));
728 return $status_report;
729 }
730 else {
731 // Throw an exception showing all unmet requirements.
732 $failures = array();
733 foreach ($requirements as $requirement) {
734 if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
735 $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description'];
736 }
737 }
738 throw new Exception(implode("\n\n", $failures));
739 }
740 }
741 }
742
743 /**
744 * Installation task; install the Drupal system module.
745 *
746 * @param $install_state
747 * An array of information about the current installation state.
748 */
749 function install_system_module(&$install_state) {
750 // Install system.module.
751 drupal_install_system();
752 // Save the list of other modules to install for the upcoming tasks.
753 // variable_set() can be used now that system.module is installed and
754 // Drupal is bootstrapped.
755 $modules = $install_state['profile_info']['dependencies'];
756
757 // The install profile is also a module, which needs to be installed
758 // after all the dependencies have been installed.
759 $modules[] = drupal_get_profile();
760
761 variable_set('install_profile_modules', array_diff($modules, array('system')));
762 $install_state['database_tables_exist'] = TRUE;
763 }
764
765 /**
766 * Verify and return the last installation task that was completed.
767 *
768 * @return
769 * The last completed task, if there is one. An exception is thrown if Drupal
770 * is already installed.
771 */
772 function install_verify_completed_task() {
773 try {
774 if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) {
775 $task = unserialize($result->fetchField());
776 }
777 }
778 // Do not trigger an error if the database query fails, since the database
779 // might not be set up yet.
780 catch (Exception $e) {
781 }
782 if (isset($task)) {
783 if ($task == 'done') {
784 throw new Exception(install_already_done_error());
785 }
786 return $task;
787 }
788 }
789
790 /**
791 * Verify existing settings.php
792 */
793 function install_verify_settings() {
794 global $db_prefix, $databases;
795
796 // Verify existing settings (if any).
797 if (!empty($databases)) {
798 $database = $databases['default']['default'];
799 drupal_static_reset('conf_path');
800 $settings_file = './' . conf_path(FALSE) . '/settings.php';
801 $errors = install_database_errors($database, $settings_file);
802 if (empty($errors)) {
803 return TRUE;
804 }
805 }
806 return FALSE;
807 }
808
809 /**
810 * Installation task; define a form to configure and rewrite settings.php.
811 *
812 * @param $form_state
813 * An associative array containing the current state of the form.
814 * @param $install_state
815 * An array of information about the current installation state.
816 * @return
817 * The form API definition for the database configuration form.
818 */
819 function install_settings_form($form, &$form_state, &$install_state) {
820 global $databases, $db_prefix;
821 $profile = $install_state['parameters']['profile'];
822 $install_locale = $install_state['parameters']['locale'];
823
824 drupal_static_reset('conf_path');
825 $conf_path = './' . conf_path(FALSE);
826 $settings_file = $conf_path . '/settings.php';
827 $database = isset($databases['default']['default']) ? $databases['default']['default'] : array();
828
829 drupal_set_title(st('Database configuration'));
830
831 $drivers = drupal_detect_database_types();
832
833 if (!$drivers) {
834 // There is no point submitting the form if there are no database drivers
835 // at all, so throw an exception here.
836 throw new Exception(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')));
837 }
838 else {
839 $form['basic_options'] = array(
840 '#type' => 'fieldset',
841 '#title' => st('Basic options'),
842 );
843
844 $form['basic_options']['driver'] = array(
845 '#type' => 'radios',
846 '#title' => st('Database type'),
847 '#required' => TRUE,
848 '#options' => $drivers,
849 '#default_value' => !empty($database['driver']) ? $database['driver'] : current(array_keys($drivers)),
850 '#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_name())),
851 );
852 if (count($drivers) == 1) {
853 $form['basic_options']['driver']['#disabled'] = TRUE;
854 $form['basic_options']['driver']['#description'] .= ' ' . st('Your PHP configuration only supports the %driver database type so it has been automatically selected.', array('%driver' => current($drivers)));
855 }
856
857 // Database name
858 $form['basic_options']['database'] = array(
859 '#type' => 'textfield',
860 '#title' => st('Database name'),
861 '#default_value' => empty($database['database']) ? '' : $database['database'],
862 '#size' => 45,
863 '#required' => TRUE,
864 '#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())),
865 );
866
867 // Database username
868 $form['basic_options']['username'] = array(
869 '#type' => 'textfield',
870 '#title' => st('Database username'),
871 '#default_value' => empty($database['username']) ? '' : $database['username'],
872 '#size' => 45,
873 );
874
875 // Database password
876 $form['basic_options']['password'] = array(
877 '#type' => 'password',
878 '#title' => st('Database password'),
879 '#default_value' => empty($database['password']) ? '' : $database['password'],
880 '#size' => 45,
881 );
882
883 $form['advanced_options'] = array(
884 '#type' => 'fieldset',
885 '#title' => st('Advanced options'),
886 '#collapsible' => TRUE,
887 '#collapsed' => TRUE,
888 '#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.")
889 );
890
891 // Database host
892 $form['advanced_options']['host'] = array(
893 '#type' => 'textfield',
894 '#title' => st('Database host'),
895 '#default_value' => empty($database['host']) ? 'localhost' : $database['host'],
896 '#size' => 45,
897 // Hostnames can be 255 characters long.
898 '#maxlength' => 255,
899 '#required' => TRUE,
900 '#description' => st('If your database is located on a different server, change this.'),
901 );
902
903 // Database port
904 $form['advanced_options']['port'] = array(
905 '#type' => 'textfield',
906 '#title' => st('Database port'),
907 '#default_value' => empty($database['port']) ? '' : $database['port'],
908 '#size' => 45,
909 // The maximum port number is 65536, 5 digits.
910 '#maxlength' => 5,
911 '#description' => st('If your database server is listening to a non-standard port, enter its number.'),
912 );
913
914 // Table prefix
915 $db_prefix = ($profile == 'default') ? 'drupal_' : $profile . '_';
916 $form['advanced_options']['db_prefix'] = array(
917 '#type' => 'textfield',
918 '#title' => st('Table prefix'),
919 '#default_value' => '',
920 '#size' => 45,
921 '#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' => $db_prefix)),
922 );
923
924 $form['save'] = array(
925 '#type' => 'submit',
926 '#value' => st('Save and continue'),
927 );
928
929 $form['errors'] = array();
930 $form['settings_file'] = array('#type' => 'value', '#value' => $settings_file);
931 $form['_database'] = array('#type' => 'value');
932 }
933 return $form;
934 }
935
936 /**
937 * Form API validate for install_settings form.
938 */
939 function install_settings_form_validate($form, &$form_state) {
940 form_set_value($form['_database'], $form_state['values'], $form_state);
941 $errors = install_database_errors($form_state['values'], $form_state['values']['settings_file']);
942 foreach ($errors as $name => $message) {
943 form_set_error($name, $message);
944 }
945 }
946
947 /**
948 * Check a database connection and return any errors.
949 */
950 function install_database_errors($database, $settings_file) {
951 global $databases;
952 $errors = array();
953 // Verify the table prefix
954 if (!empty($database['db_prefix']) && is_string($database['db_prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['db_prefix'])) {
955 $errors['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' => $database['db_prefix']));
956 }
957
958 if (!empty($database['port']) && !is_numeric($database['port'])) {
959 $errors['db_port'] = st('Database port must be a number.');
960 }
961
962 // Check database type
963 $database_types = drupal_detect_database_types();
964 $driver = $database['driver'];
965 if (!isset($database_types[$driver])) {
966 $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_name(), '%driver' => $database['driver']));
967 }
968 else {
969 // Run tasks associated with the database type. Any errors are caught in the
970 // calling function
971 $databases['default']['default'] = $database;
972 // Just changing the global doesn't get the new information processed.
973 // We tell tell the Database class to re-parse $databases.
974 Database::parseConnectionInfo();
975
976 try {
977 db_run_tasks($database['driver']);
978 }
979 catch (DatabaseTaskException $e) {
980 // These are generic errors, so we do not have any specific key of the
981 // database connection array to attach them to; therefore, we just put
982 // them in the error array with standard numeric keys.
983 $errors[] = $e->getMessage();
984 }
985 }
986 return $errors;
987 }
988
989 /**
990 * Form API submit for install_settings form.
991 */
992 function install_settings_form_submit($form, &$form_state) {
993 global $install_state;
994
995 $database = array_intersect_key($form_state['values']['_database'], array_flip(array('driver', 'database', 'username', 'password', 'host', 'port')));
996 // Update global settings array and save
997 $settings['databases'] = array(
998 'value' => array('default' => array('default' => $database)),
999 'required' => TRUE,
1000 );
1001 $settings['db_prefix'] = array(
1002 'value' => $form_state['values']['db_prefix'],
1003 'required' => TRUE,
1004 );
1005 drupal_rewrite_settings($settings);
1006 // Indicate that the settings file has been verified, and check the database
1007 // for the last completed task, now that we have a valid connection. This
1008 // last step is important since we want to trigger an error if the new
1009 // database already has Drupal installed.
1010 $install_state['settings_verified'] = TRUE;
1011 $install_state['completed_task'] = install_verify_completed_task();
1012 }
1013
1014 /**
1015 * Find all .profile files.
1016 */
1017 function install_find_profiles() {
1018 return file_scan_directory('./profiles', '/\.profile$/', array('key' => 'name'));
1019 }
1020
1021 /**
1022 * Installation task; select which profile to install.
1023 *
1024 * @param $install_state
1025 * An array of information about the current installation state. The chosen
1026 * profile will be added here, if it was not already selected previously, as
1027 * will a list of all available profiles.
1028 * @return
1029 * For interactive installations, a form allowing the profile to be selected,
1030 * if the user has a choice that needs to be made. Otherwise, an exception is
1031 * thrown if a profile cannot be chosen automatically.
1032 */
1033 function install_select_profile(&$install_state) {
1034 $install_state['profiles'] += install_find_profiles();
1035 if (empty($install_state['parameters']['profile'])) {
1036 // Try to find a profile.
1037 $profile = _install_select_profile($install_state['profiles']);
1038 if (empty($profile)) {
1039 // We still don't have a profile, so display a form for selecting one.
1040 // Only do this in the case of interactive installations, since this is
1041 // not a real form with submit handlers (the database isn't even set up
1042 // yet), rather just a convenience method for setting parameters in the
1043 // URL.
1044 if ($install_state['interactive']) {
1045 include_once DRUPAL_ROOT . '/includes/form.inc';
1046 drupal_set_title(st('Select an installation profile'));
1047 $form = drupal_get_form('install_select_profile_form', $install_state['profiles']);
1048 return drupal_render($form);
1049 }
1050 else {
1051 throw new Exception(install_no_profile_error());
1052 }
1053 }
1054 else {
1055 $install_state['parameters']['profile'] = $profile;
1056 }
1057 }
1058 }
1059
1060 /**
1061 * Helper function for automatically selecting an installation profile from a
1062 * list or from a selection passed in via $_POST.
1063 */
1064 function _install_select_profile($profiles) {
1065 if (sizeof($profiles) == 0) {
1066 throw new Exception(install_no_profile_error());
1067 }
1068 // Don't need to choose profile if only one available.
1069 if (sizeof($profiles) == 1) {
1070 $profile = array_pop($profiles);
1071 // TODO: is this right?
1072 require_once $profile->uri;
1073 return $profile->name;
1074 }
1075 else {
1076 foreach ($profiles as $profile) {
1077 if (!empty($_POST['profile']) && ($_POST['profile'] == $profile->name)) {
1078 return $profile->name;
1079 }
1080 }
1081 }
1082 }
1083
1084 /**
1085 * Form API array definition for the profile selection form.
1086 *
1087 * @param $form_state
1088 * Array of metadata about state of form processing.
1089 * @param $profile_files
1090 * Array of .profile files, as returned from file_scan_directory().
1091 */
1092 function install_select_profile_form($form, &$form_state, $profile_files) {
1093 $profiles = array();
1094 $names = array();
1095
1096 foreach ($profile_files as $profile) {
1097 // TODO: is this right?
1098 include_once DRUPAL_ROOT . '/' . $profile->uri;
1099
1100 $details = install_profile_info($profile->name);
1101 $profiles[$profile->name] = $details;
1102
1103 // Determine the name of the profile; default to file name if defined name
1104 // is unspecified.
1105 $name = isset($details['name']) ? $details['name'] : $profile->name;
1106 $names[$profile->name] = $name;
1107 }
1108
1109 // Display radio buttons alphabetically by human-readable name.
1110 natcasesort($names);
1111
1112 foreach ($names as $profile => $name) {
1113 $form['profile'][$name] = array(
1114 '#type' => 'radio',
1115 '#value' => 'default',
1116 '#return_value' => $profile,
1117 '#title' => $name,
1118 '#description' => isset($profiles[$profile]['description']) ? $profiles[$profile]['description'] : '',
1119 '#parents' => array('profile'),
1120 );
1121 }
1122 $form['submit'] = array(
1123 '#type' => 'submit',
1124 '#value' => st('Save and continue'),
1125 );
1126 return $form;
1127 }
1128
1129 /**
1130 * Find all .po files for the current profile.
1131 */
1132 function install_find_locales($profilename) {
1133 $locales = file_scan_directory('./profiles/' . $profilename . '/translations', '/\.po$/', array('recurse' => FALSE));
1134 array_unshift($locales, (object) array('name' => 'en'));
1135 return $locales;
1136 }
1137
1138 /**
1139 * Installation task; select which locale to use for the current profile.
1140 *
1141 * @param $install_state
1142 * An array of information about the current installation state. The chosen
1143 * locale will be added here, if it was not already selected previously, as
1144 * will a list of all available locales.
1145 * @return
1146 * For interactive installations, a form or other page output allowing the
1147 * locale to be selected or providing information about locale selection, if
1148 * a locale has not been chosen. Otherwise, an exception is thrown if a
1149 * locale cannot be chosen automatically.
1150 */
1151 function install_select_locale(&$install_state) {
1152 // Find all available locales.
1153 $profilename = $install_state['parameters']['profile'];
1154 $locales = install_find_locales($profilename);
1155 $install_state['locales'] += $locales;
1156 if (empty($install_state['parameters']['locale'])) {
1157 // If only the built-in (English) language is available, and we are using
1158 // the default profile and performing an interactive installation, inform
1159 // the user that the installer can be localized. Otherwise we assume the
1160 // user knows what he is doing.
1161 if (count($locales) == 1) {
1162 if ($profilename == 'default' && $install_state['interactive']) {
1163 drupal_set_title(st('Choose language'));
1164 if (!empty($install_state['parameters']['localize'])) {
1165 $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>';
1166 $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>';
1167 $output .= '<li>' . st('If an alternative translation package of your choice is available, download and extract its contents to your Drupal root directory.') . '</li>';
1168 $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>';
1169 $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>';
1170 $output .= '<p>' . st('How should the installation continue?') . '</p>';
1171 $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>';
1172 }
1173 else {
1174 $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>';
1175 }
1176 return $output;
1177 }
1178 // One language, but not the default profile or not an interactive
1179 // installation. Assume the user knows what he is doing.
1180 $locale = current($locales);
1181 $install_state['parameters']['locale'] = $locale->name;
1182 return;
1183 }
1184 else {
1185 // Allow profile to pre-select the language, skipping the selection.
1186 $function = $profilename . '_profile_details';
1187 if (function_exists($function)) {
1188 $details = $function();
1189 if (isset($details['language'])) {
1190 foreach ($locales as $locale) {
1191 if ($details['language'] == $locale->name) {
1192 $install_state['parameters']['locale'] = $locale->name;
1193 return;
1194 }
1195 }
1196 }
1197 }
1198
1199 if (!empty($_POST['locale'])) {
1200 foreach ($locales as $locale) {
1201 if ($_POST['locale'] == $locale->name) {
1202 $install_state['parameters']['locale'] = $locale->name;
1203 return;
1204 }
1205 }
1206 }
1207
1208 // We still don't have a locale, so display a form for selecting one.
1209 // Only do this in the case of interactive installations, since this is
1210 // not a real form with submit handlers (the database isn't even set up
1211 // yet), rather just a convenience method for setting parameters in the
1212 // URL.
1213 if ($install_state['interactive']) {
1214 drupal_set_title(st('Choose language'));
1215 include_once DRUPAL_ROOT . '/includes/form.inc';
1216 return drupal_render(drupal_get_form('install_select_locale_form', $locales));
1217 }
1218 else {
1219 throw new Exception(st('Sorry, you must select a language to continue the installation.'));
1220 }
1221 }
1222 }
1223 }
1224
1225 /**
1226 * Form API array definition for language selection.
1227 */
1228 function install_select_locale_form($form, &$form_state, $locales) {
1229 include_once DRUPAL_ROOT . '/includes/iso.inc';
1230 $languages = _locale_get_predefined_list();
1231 foreach ($locales as $locale) {
1232 // Try to use verbose locale name
1233 $name = $locale->name;
1234 if (isset($languages[$name])) {
1235 $name = $languages[$name][0] . (isset($languages[$name][1]) ? ' ' . st('(@language)', array('@language' => $languages[$name][1])) : '');
1236 }
1237 $form['locale'][$locale->name] = array(
1238