Newer
Older
Dries Buytaert
committed
<?php
// $Id$
Angie Byron
committed
/**
* Root directory of Drupal installation.
*/
Angie Byron
committed
define('DRUPAL_ROOT', getcwd());
Angie Byron
committed
require_once DRUPAL_ROOT . '/includes/install.inc';
Dries Buytaert
committed
/**
* Global flag to indicate that site is in installation mode.
*/
define('MAINTENANCE_MODE', 'install');
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Global flag to indicate that a task should not be run during the current
* installation request.
Dries Buytaert
committed
*
Dries Buytaert
committed
* This can be used to skip running an installation task when certain
* conditions are met, even though the task may still show on the list of
* installation tasks presented to the user. For example, the Drupal installer
* uses this flag to skip over the database configuration form when valid
* database connection information is already available from settings.php. It
* also uses this flag to skip language import tasks when the installation is
* being performed in English.
Dries Buytaert
committed
*/
Dries Buytaert
committed
define('INSTALL_TASK_SKIP', 1);
/**
* Global flag to indicate that a task should be run on each installation
* request that reaches it.
*
* This is primarily used by the Drupal installer for bootstrap-related tasks.
*/
define('INSTALL_TASK_RUN_IF_REACHED', 2);
/**
* Global flag to indicate that a task should be run on each installation
* request that reaches it, until the database is set up and we are able to
* record the fact that it already ran.
*
* This is the default method for running tasks and should be used for most
* tasks that occur after the database is set up; these tasks will then run
* once and be marked complete once they are successfully finished. For
* example, the Drupal installer uses this flag for the batch installation of
* modules on the new site, and also for the configuration form that collects
* basic site information and sets up the site maintenance account.
Dries Buytaert
committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
*/
define('INSTALL_TASK_RUN_IF_NOT_COMPLETED', 3);
/**
* Install Drupal either interactively or via an array of passed-in settings.
*
* The Drupal installation happens in a series of steps, which may be spread
* out over multiple page requests. Each request begins by trying to determine
* the last completed installation step (also known as a "task"), if one is
* available from a previous request. Control is then passed to the task
* handler, which processes the remaining tasks that need to be run until (a)
* an error is thrown, (b) a new page needs to be displayed, or (c) the
* installation finishes (whichever happens first).
*
* @param $settings
* An optional array of installation settings. Leave this empty for a normal,
* interactive, browser-based installation intended to occur over multiple
* page requests. Alternatively, if an array of settings is passed in, the
* installer will attempt to use it to perform the installation in a single
* page request (optimized for the command line) and not send any output
* intended for the web browser. See install_state_defaults() for a list of
* elements that are allowed to appear in this array.
*
* @see install_state_defaults()
*/
function install_drupal($settings = array()) {
global $install_state;
// Initialize the installation state with the settings that were passed in,
// as well as a boolean indicating whether or not this is an interactive
// installation.
$interactive = empty($settings);
$install_state = $settings + array('interactive' => $interactive) + install_state_defaults();
try {
// Begin the page request. This adds information about the current state of
// the Drupal installation to the passed-in array.
install_begin_request($install_state);
// Based on the installation state, run the remaining tasks for this page
// request, and collect any output.
$output = install_run_tasks($install_state);
}
catch (Exception $e) {
// When an installation error occurs, either send the error to the web
// browser or pass on the exception so the calling script can use it.
if ($install_state['interactive']) {
install_display_output($e->getMessage(), $install_state);
}
else {
throw $e;
}
}
// All available tasks for this page request are now complete. Interactive
// installations can send output to the browser or redirect the user to the
// next page.
if ($install_state['interactive']) {
if ($install_state['parameters_changed']) {
// Redirect to the correct page if the URL parameters have changed.
install_goto(install_redirect_url($install_state));
}
elseif (isset($output)) {
// Display a page only if some output is available. Otherwise it is
// possible that we are printing a JSON page and theme output should
// not be shown.
install_display_output($output, $install_state);
}
}
}
/**
* Return an array of default settings for the global installation state.
*
* The installation state is initialized with these settings at the beginning
* of each page request. They may evolve during the page request, but they are
* initialized again once the next request begins.
*
* Non-interactive Drupal installations can override some of these default
* settings by passing in an array to the installation script, most notably
* 'parameters' (which contains one-time parameters such as 'profile' and
* 'locale' that are normally passed in via the URL) and 'forms' (which can
* be used to programmatically submit forms during the installation; the keys
* of each element indicate the name of the installation task that the form
* submission is for, and the values are used as the $form_state['values']
* array that is passed on to the form submission via drupal_form_submit()).
*
* @see drupal_form_submit()
*/
function install_state_defaults() {
$defaults = array(
// The current task being processed.
'active_task' => NULL,
// The last task that was completed during the previous installation
// request.
'completed_task' => NULL,
// This becomes TRUE only when Drupal's system module is installed.
'database_tables_exist' => FALSE,
// An array of forms to be programmatically submitted during the
// installation. The keys of each element indicate the name of the
// installation task that the form submission is for, and the values are
// used as the $form_state['values'] array that is passed on to the form
// submission via drupal_form_submit().
'forms' => array(),
// This becomes TRUE only at the end of the installation process, after
// all available tasks have been completed and Drupal is fully installed.
// It is used by the installer to store correct information in the database
// about the completed installation, as well as to inform theme functions
// that all tasks are finished (so that the task list can be displayed
// correctly).
'installation_finished' => FALSE,
// Whether or not this installation is interactive. By default this will
// be set to FALSE if settings are passed in to install_drupal().
'interactive' => TRUE,
// An array of available languages for the installation.
'locales' => array(),
// An array of parameters for the installation, pre-populated by the URL
// or by the settings passed in to install_drupal(). This is primarily
// used to store 'profile' (the name of the chosen installation profile)
// and 'locale' (the name of the chosen installation language), since
// these settings need to persist from page request to page request before
// the database is available for storage.
'parameters' => array(),
// Whether or not the parameters have changed during the current page
// request. For interactive installations, this will trigger a page
// redirect.
'parameters_changed' => FALSE,
// An array of information about the chosen installation profile. This will
// be filled in based on the profile's .info file.
'profile_info' => array(),
// An array of available installation profiles.
'profiles' => array(),
// An array of server variables that will be substituted into the global
// $_SERVER array via drupal_override_server_variables(). Used by
// non-interactive installations only.
'server' => array(),
// This becomes TRUE only when a valid database connection can be
// established.
'settings_verified' => FALSE,
// Installation tasks can set this to TRUE to force the page request to
// end (even if there is no themable output), in the case of an interactive
// installation. This is needed only rarely; for example, it would be used
// by an installation task that prints JSON output rather than returning a
// themed page. The most common example of this is during batch processing,
// but the Drupal installer automatically takes care of setting this
// parameter properly in that case, so that individual installation tasks
// which implement the batch API do not need to set it themselves.
'stop_page_request' => FALSE,
// Installation tasks can set this to TRUE to indicate that the task should
// be run again, even if it normally wouldn't be. This can be used, for
// example, if a single task needs to be spread out over multiple page
// requests, or if it needs to perform some validation before allowing
// itself to be marked complete. The most common examples of this are batch
// processing and form submissions, but the Drupal installer automatically
// takes care of setting this parameter properly in those cases, so that
// individual installation tasks which implement the batch API or form API
// do not need to set it themselves.
'task_not_complete' => FALSE,
// A list of installation tasks which have already been performed during
// the current page request.
'tasks_performed' => array(),
);
return $defaults;
}
/**
* Begin an installation request, modifying the installation state as needed.
*
* This function performs commands that must run at the beginning of every page
* request. It throws an exception if the installation should not proceed.
*
* @param $install_state
* An array of information about the current installation state. This is
* modified with information gleaned from the beginning of the page request.
*/
function install_begin_request(&$install_state) {
// Allow command line scripts to override server variables used by Drupal.
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
if (!$install_state['interactive']) {
drupal_override_server_variables($install_state['server']);
}
Dries Buytaert
committed
// The user agent header is used to pass a database prefix in the request when
Dries Buytaert
committed
// running tests. However, for security reasons, it is imperative that no
Dries Buytaert
committed
// installation be permitted using such a prefix.
Dries Buytaert
committed
if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
Dries Buytaert
committed
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
Dries Buytaert
committed
exit;
Dries Buytaert
committed
}
Dries Buytaert
committed
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
Dries Buytaert
committed
// This must go after drupal_bootstrap(), which unsets globals!
Dries Buytaert
committed
global $conf;
Angie Byron
committed
require_once DRUPAL_ROOT . '/modules/system/system.install';
Dries Buytaert
committed
require_once DRUPAL_ROOT . '/includes/common.inc';
Angie Byron
committed
require_once DRUPAL_ROOT . '/includes/file.inc';
Dries Buytaert
committed
require_once DRUPAL_ROOT . '/includes/path.inc';
Gábor Hojtsy
committed
// Set up $language, so t() caller functions will still work.
Dries Buytaert
committed
drupal_language_initialize();
Gábor Hojtsy
committed
// Load module basics (needed for hook invokes).
Angie Byron
committed
include_once DRUPAL_ROOT . '/includes/module.inc';
Angie Byron
committed
include_once DRUPAL_ROOT . '/includes/session.inc';
Dries Buytaert
committed
include_once DRUPAL_ROOT . '/includes/entity.inc';
$module_list['system']['filename'] = 'modules/system/system.module';
$module_list['filter']['filename'] = 'modules/filter/filter.module';
Dries Buytaert
committed
$module_list['user']['filename'] = 'modules/user/user.module';
module_list(TRUE, FALSE, FALSE, $module_list);
drupal_load('module', 'system');
drupal_load('module', 'filter');
Dries Buytaert
committed
drupal_load('module', 'user');
// Load the cache infrastructure with the Fake Cache. Switch to the database cache
// later if possible.
require_once DRUPAL_ROOT . '/includes/cache.inc';
require_once DRUPAL_ROOT . '/includes/cache-install.inc';
$conf['cache_inc'] = 'includes/cache.inc';
$conf['cache_default_class'] = 'DrupalFakeCache';
Dries Buytaert
committed
// Prepare for themed output, if necessary. We need to run this at the
// beginning of the page request to avoid a different theme accidentally
// getting set.
if ($install_state['interactive']) {
drupal_maintenance_theme();
}
Dries Buytaert
committed
// Check existing settings.php.
Dries Buytaert
committed
$install_state['settings_verified'] = install_verify_settings();
Dries Buytaert
committed
Dries Buytaert
committed
if ($install_state['settings_verified']) {
Gábor Hojtsy
committed
// Since we have a database connection, we use the normal cache system.
// This is important, as the installer calls into the Drupal system for
// the clean URL checks, so we should maintain the cache properly.
unset($conf['cache_default_class']);
Gábor Hojtsy
committed
Dries Buytaert
committed
// Initialize the database system. Note that the connection
Dries Buytaert
committed
// won't be initialized until it is actually requested.
Angie Byron
committed
require_once DRUPAL_ROOT . '/includes/database/database.inc';
Gábor Hojtsy
committed
Dries Buytaert
committed
// Verify the last completed task in the database, if there is one.
$task = install_verify_completed_task();
Dries Buytaert
committed
}
Dries Buytaert
committed
else {
$task = NULL;
Dries Buytaert
committed
// Since previous versions of Drupal stored database connection information
// in the 'db_url' variable, we should never let an installation proceed if
// this variable is defined and the settings file was not verified above
// (otherwise we risk installing over an existing site whose settings file
// has not yet been updated).
if (!empty($GLOBALS['db_url'])) {
throw new Exception(install_already_done_error());
}
Dries Buytaert
committed
}
Dries Buytaert
committed
Dries Buytaert
committed
// Modify the installation state as appropriate.
$install_state['completed_task'] = $task;
$install_state['database_tables_exist'] = !empty($task);
// Add any installation parameters passed in via the URL.
$install_state['parameters'] += $_GET;
// Validate certain core settings that are used throughout the installation.
if (!empty($install_state['parameters']['profile'])) {
$install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']);
Dries Buytaert
committed
}
Dries Buytaert
committed
if (!empty($install_state['parameters']['locale'])) {
$install_state['parameters']['locale'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['locale']);
Dries Buytaert
committed
}
Dries Buytaert
committed
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
}
/**
* Run all tasks for the current installation request.
*
* In the case of an interactive installation, all tasks will be attempted
* until one is reached that has output which needs to be displayed to the
* user, or until a page redirect is required. Otherwise, tasks will be
* attempted until the installation is finished.
*
* @param $install_state
* An array of information about the current installation state. This is
* passed along to each task, so it can be modified if necessary.
* @return
* HTML output from the last completed task.
*/
function install_run_tasks(&$install_state) {
do {
// Obtain a list of tasks to perform. The list of tasks itself can be
// dynamic (e.g., some might be defined by the installation profile,
// which is not necessarily known until the earlier tasks have run),
// so we regenerate the remaining tasks based on the installation state,
// each time through the loop.
$tasks_to_perform = install_tasks_to_perform($install_state);
// Run the first task on the list.
reset($tasks_to_perform);
$task_name = key($tasks_to_perform);
$task = array_shift($tasks_to_perform);
$install_state['active_task'] = $task_name;
$original_parameters = $install_state['parameters'];
$output = install_run_task($task, $install_state);
$install_state['parameters_changed'] = ($install_state['parameters'] != $original_parameters);
// Store this task as having been performed during the current request,
// and save it to the database as completed, if we need to and if the
// database is in a state that allows us to do so. Also mark the
// installation as 'done' when we have run out of tasks.
if (!$install_state['task_not_complete']) {
$install_state['tasks_performed'][] = $task_name;
$install_state['installation_finished'] = empty($tasks_to_perform);
if ($install_state['database_tables_exist'] && ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished'])) {
drupal_install_initialize_database();
variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
}
}
// Stop when there are no tasks left. In the case of an interactive
// installation, also stop if we have some output to send to the browser,
// the URL parameters have changed, or an end to the page request was
// specifically called for.
$finished = empty($tasks_to_perform) || ($install_state['interactive'] && (isset($output) || $install_state['parameters_changed'] || $install_state['stop_page_request']));
} while (!$finished);
return $output;
}
/**
* Run an individual installation task.
*
* @param $task
* An array of information about the task to be run.
* @param $install_state
* An array of information about the current installation state. This is
* passed in by reference so that it can be modified by the task.
* @return
* The output of the task function, if there is any.
*/
function install_run_task($task, &$install_state) {
$function = $task['function'];
if ($task['type'] == 'form') {
require_once DRUPAL_ROOT . '/includes/form.inc';
if ($install_state['interactive']) {
// For interactive forms, build the form and ensure that it will not
// redirect, since the installer handles its own redirection only after
// marking the form submission task complete.
$form_state = array(
// We need to pass $install_state by reference in order for forms to
// modify it, since the form API will use it in call_user_func_array(),
// which requires that referenced variables be passed explicitly.
'args' => array(&$install_state),
Dries Buytaert
committed
'no_redirect' => TRUE,
);
$form = drupal_build_form($function, $form_state);
// If a successful form submission did not occur, the form needs to be
// rendered, which means the task is not complete yet.
if (empty($form_state['executed'])) {
$install_state['task_not_complete'] = TRUE;
return drupal_render($form);
}
// Otherwise, return nothing so the next task will run in the same
// request.
return;
}
else {
// For non-interactive forms, submit the form programmatically with the
// values taken from the installation state. Throw an exception if any
// errors were encountered.
$form_state = array('values' => !empty($install_state['forms'][$function]) ? $install_state['forms'][$function] : array());
drupal_form_submit($function, $form_state, $install_state);
$errors = form_get_errors();
if (!empty($errors)) {
throw new Exception(implode("\n", $errors));
}
}
Dries Buytaert
committed
}
Dries Buytaert
committed
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
elseif ($task['type'] == 'batch') {
// Start a new batch based on the task function, if one is not running
// already.
$current_batch = variable_get('install_current_batch');
if (!$install_state['interactive'] || !$current_batch) {
$batch = $function($install_state);
if (empty($batch)) {
// If the task did some processing and decided no batch was necessary,
// there is nothing more to do here.
return;
}
batch_set($batch);
// For interactive batches, we need to store the fact that this batch
// task is currently running. Otherwise, we need to make sure the batch
// will complete in one page request.
if ($install_state['interactive']) {
variable_set('install_current_batch', $function);
}
else {
$batch =& batch_get();
$batch['progressive'] = FALSE;
}
// Process the batch. For progressive batches, this will redirect.
// Otherwise, the batch will complete.
batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state));
}
// If we are in the middle of processing this batch, keep sending back
// any output from the batch process, until the task is complete.
elseif ($current_batch == $function) {
include_once DRUPAL_ROOT . '/includes/batch.inc';
$output = _batch_page();
// The task is complete when we try to access the batch page and receive
// FALSE in return, since this means we are at a URL where we are no
// longer requesting a batch ID.
if ($output === FALSE) {
// Return nothing so the next task will run in the same request.
variable_del('install_current_batch');
return;
}
else {
// We need to force the page request to end if the task is not
// complete, since the batch API sometimes prints JSON output
// rather than returning a themed page.
$install_state['task_not_complete'] = $install_state['stop_page_request'] = TRUE;
return $output;
}
}
}
Dries Buytaert
committed
Dries Buytaert
committed
else {
// For normal tasks, just return the function result, whatever it is.
return $function($install_state);
Dries Buytaert
committed
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
}
/**
* Return a list of tasks to perform during the current installation request.
*
* Note that the list of tasks can change based on the installation state as
* the page request evolves (for example, if an installation profile hasn't
* been selected yet, we don't yet know which profile tasks need to be run).
*
* @param $install_state
* An array of information about the current installation state.
* @return
* A list of tasks to be performed, with associated metadata.
*/
function install_tasks_to_perform($install_state) {
// Start with a list of all currently available tasks.
$tasks = install_tasks($install_state);
foreach ($tasks as $name => $task) {
// Remove any tasks that were already performed or that never should run.
// Also, if we started this page request with an indication of the last
// task that was completed, skip that task and all those that come before
// it, unless they are marked as always needing to run.
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)) {
unset($tasks[$name]);
}
if (!empty($install_state['completed_task']) && $name == $install_state['completed_task']) {
$completed_task_found = TRUE;
}
Dries Buytaert
committed
return $tasks;
}
Dries Buytaert
committed
/**
* Return a list of all tasks the installer currently knows about.
*
* This function will return tasks regardless of whether or not they are
* intended to run on the current page request. However, the list can change
* based on the installation state (for example, if an installation profile
* hasn't been selected yet, we don't yet know which profile tasks will be
* available).
*
* @param $install_state
* An array of information about the current installation state.
* @return
* A list of tasks, with associated metadata.
*/
function install_tasks($install_state) {
// Determine whether translation import tasks will need to be performed.
$needs_translations = count($install_state['locales']) > 1 && !empty($install_state['parameters']['locale']) && $install_state['parameters']['locale'] != 'en';
Dries Buytaert
committed
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
// Start with the core installation tasks that run before handing control
// to the install profile.
$tasks = array(
'install_select_profile' => array(
'display_name' => st('Choose profile'),
'display' => count($install_state['profiles']) != 1,
'run' => INSTALL_TASK_RUN_IF_REACHED,
),
'install_select_locale' => array(
'display_name' => st('Choose language'),
'run' => INSTALL_TASK_RUN_IF_REACHED,
),
'install_load_profile' => array(
'run' => INSTALL_TASK_RUN_IF_REACHED,
),
'install_verify_requirements' => array(
'display_name' => st('Verify requirements'),
),
'install_settings_form' => array(
'display_name' => st('Set up database'),
'type' => 'form',
'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
),
'install_system_module' => array(
),
'install_bootstrap_full' => array(
'run' => INSTALL_TASK_RUN_IF_REACHED,
),
'install_profile_modules' => array(
'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Install profile'),
'type' => 'batch',
),
'install_import_locales' => array(
'display_name' => st('Set up translations'),
'display' => $needs_translations,
'type' => 'batch',
'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
),
'install_configure_form' => array(
'display_name' => st('Configure site'),
'type' => 'form',
),
);
Dries Buytaert
committed
Dries Buytaert
committed
// Now add any tasks defined by the installation profile.
if (!empty($install_state['parameters']['profile'])) {
Angie Byron
committed
$function = $install_state['parameters']['profile'] . '_install_tasks';
Dries Buytaert
committed
if (function_exists($function)) {
$result = $function($install_state);
if (is_array($result)) {
$tasks += $result;
}
Dries Buytaert
committed
}
Dries Buytaert
committed
}
// Finish by adding the remaining core tasks.
$tasks += array(
'install_import_locales_remaining' => array(
'display_name' => st('Finish translations'),
'display' => $needs_translations,
'type' => 'batch',
'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
),
'install_finished' => array(
'display_name' => st('Finished'),
),
);
Angie Byron
committed
// Allow the installation profile to modify the full list of tasks.
if (!empty($install_state['parameters']['profile'])) {
$profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile';
if (is_file($profile_file)) {
include_once $profile_file;
$function = $install_state['parameters']['profile'] . '_install_tasks_alter';
if (function_exists($function)) {
$function($tasks, $install_state);
}
}
}
Dries Buytaert
committed
// Fill in default parameters for each task before returning the list.
foreach ($tasks as $task_name => &$task) {
$task += array(
'display_name' => NULL,
'display' => !empty($task['display_name']),
'type' => 'normal',
'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED,
'function' => $task_name,
);
}
return $tasks;
}
Dries Buytaert
committed
Dries Buytaert
committed
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
/**
* Return a list of tasks that should be displayed to the end user.
*
* The output of this function is a list suitable for sending to
* theme_task_list().
*
* @param $install_state
* An array of information about the current installation state.
* @return
* A list of tasks, with keys equal to the machine-readable task name and
* values equal to the name that should be displayed.
*
* @see theme_task_list()
*/
function install_tasks_to_display($install_state) {
$displayed_tasks = array();
foreach (install_tasks($install_state) as $name => $task) {
if ($task['display']) {
$displayed_tasks[$name] = $task['display_name'];
}
}
return $displayed_tasks;
}
/**
* Return the URL that should be redirected to during an installation request.
*
* The output of this function is suitable for sending to install_goto().
*
* @param $install_state
* An array of information about the current installation state.
* @return
* The URL to redirect to.
*
* @see install_full_redirect_url()
*/
function install_redirect_url($install_state) {
Dries Buytaert
committed
return 'install.php?' . drupal_http_build_query($install_state['parameters']);
Dries Buytaert
committed
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
}
/**
* Return the complete URL that should be redirected to during an installation
* request.
*
* @param $install_state
* An array of information about the current installation state.
* @return
* The complete URL to redirect to.
*
* @see install_redirect_url()
*/
function install_full_redirect_url($install_state) {
global $base_url;
return $base_url . '/' . install_redirect_url($install_state);
}
/**
* Display themed installer output and end the page request.
*
* Installation tasks should use drupal_set_title() to set the desired page
* title, but otherwise this function takes care of theming the overall page
* output during every step of the installation.
*
* @param $output
* The content to display on the main part of the page.
* @param $install_state
* An array of information about the current installation state.
*/
function install_display_output($output, $install_state) {
drupal_page_header();
// Only show the task list if there is an active task; otherwise, the page
// request has ended before tasks have even been started, so there is nothing
// meaningful to show.
if (isset($install_state['active_task'])) {
// Let the theming function know when every step of the installation has
// been completed.
$active_task = $install_state['installation_finished'] ? NULL : $install_state['active_task'];
Dries Buytaert
committed
drupal_add_region_content('sidebar_first', theme('task_list', array('items' => install_tasks_to_display($install_state), 'active' => $active_task)));
Dries Buytaert
committed
}
Dries Buytaert
committed
print theme($install_state['database_tables_exist'] ? 'maintenance_page' : 'install_page', array('content' => $output));
Dries Buytaert
committed
exit;
}
/**
* Installation task; verify the requirements for installing Drupal.
*
* @param $install_state
* An array of information about the current installation state.
* @return
* A themed status report, or an exception if there are requirement errors.
* Otherwise, no output is returned, so that the next task can be run
* in the same page request.
*/
function install_verify_requirements(&$install_state) {
Angie Byron
committed
// Check the installation requirements for Drupal and this profile.
$requirements = install_check_requirements($install_state);
Steven Wittens
committed
Angie Byron
committed
// Verify existence of all required modules.
$requirements += drupal_verify_profile($install_state);
Gábor Hojtsy
committed
Angie Byron
committed
// Check the severity of the requirements reported.
$severity = drupal_requirements_severity($requirements);
Angie Byron
committed
Angie Byron
committed
if ($severity == REQUIREMENT_ERROR) {
if ($install_state['interactive']) {
drupal_set_title(st('Requirements problem'));
Dries Buytaert
committed
$status_report = theme('status_report', array('requirements' => $requirements));
Angie Byron
committed
$status_report .= st('Check the error messages and <a href="!url">proceed with the installation</a>.', array('!url' => request_uri()));
Angie Byron
committed
return $status_report;
}
else {
// Throw an exception showing all unmet requirements.
$failures = array();
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
$failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description'];
Dries Buytaert
committed
}
}
Angie Byron
committed
throw new Exception(implode("\n\n", $failures));
Dries Buytaert
committed
}
Angie Byron
committed
}
Dries Buytaert
committed
}
Dries Buytaert
committed
Dries Buytaert
committed
/**
* Installation task; install the Drupal system module.
*
* @param $install_state
* An array of information about the current installation state.
*/
function install_system_module(&$install_state) {
Angie Byron
committed
// Install system.module.
drupal_install_system();
// Save the list of other modules to install for the upcoming tasks.
// variable_set() can be used now that system.module is installed and
// Drupal is bootstrapped.
$modules = $install_state['profile_info']['dependencies'];
// The install profile is also a module, which needs to be installed
// after all the dependencies have been installed.
$modules[] = drupal_get_profile();
variable_set('install_profile_modules', array_diff($modules, array('system')));
$install_state['database_tables_exist'] = TRUE;
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Verify and return the last installation task that was completed.
*
* @return
* The last completed task, if there is one. An exception is thrown if Drupal
* is already installed.
Dries Buytaert
committed
*/
Dries Buytaert
committed
function install_verify_completed_task() {
Dries Buytaert
committed
try {
Angie Byron
committed
if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) {
$task = unserialize($result->fetchField());
Dries Buytaert
committed
}
}
Dries Buytaert
committed
// Do not trigger an error if the database query fails, since the database
// might not be set up yet.
Dries Buytaert
committed
catch (Exception $e) {
Dries Buytaert
committed
}
Dries Buytaert
committed
if (isset($task)) {
if ($task == 'done') {
throw new Exception(install_already_done_error());
}
return $task;
}
Dries Buytaert
committed
}
/**
* Verify existing settings.php
*/
function install_verify_settings() {
Dries Buytaert
committed
global $db_prefix, $databases;
Dries Buytaert
committed
// Verify existing settings (if any).
Dries Buytaert
committed
if (!empty($databases)) {
$database = $databases['default']['default'];
Dries Buytaert
committed
drupal_static_reset('conf_path');
$settings_file = './' . conf_path(FALSE) . '/settings.php';
Dries Buytaert
committed
$errors = install_database_errors($database, $settings_file);
if (empty($errors)) {
Dries Buytaert
committed
return TRUE;
}
}
return FALSE;
}
/**
Dries Buytaert
committed
* Installation task; define a form to configure and rewrite settings.php.
*
* @param $form_state
* An associative array containing the current state of the form.
* @param $install_state
* An array of information about the current installation state.
* @return
* The form API definition for the database configuration form.
Dries Buytaert
committed
*/
function install_settings_form($form, &$form_state, &$install_state) {
Dries Buytaert
committed
global $databases, $db_prefix;
Dries Buytaert
committed
$profile = $install_state['parameters']['profile'];
$install_locale = $install_state['parameters']['locale'];
Dries Buytaert
committed
Dries Buytaert
committed
drupal_static_reset('conf_path');
$conf_path = './' . conf_path(FALSE);
$settings_file = $conf_path . '/settings.php';
Dries Buytaert
committed
$database = isset($databases['default']['default']) ? $databases['default']['default'] : array();
drupal_set_title(st('Database configuration'));
Dries Buytaert
committed
$drivers = drupal_detect_database_types();
Dries Buytaert
committed
Dries Buytaert
committed
if (!$drivers) {
Dries Buytaert
committed
// There is no point submitting the form if there are no database drivers
// at all, so throw an exception here.
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')));
}
else {
$form['basic_options'] = array(
'#type' => 'fieldset',
);
$form['basic_options']['driver'] = array(
'#type' => 'radios',
'#title' => st('Database type'),
'#required' => TRUE,
'#options' => $drivers,
'#default_value' => !empty($database['driver']) ? $database['driver'] : current(array_keys($drivers)),
'#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_name())),
);
Dries Buytaert
committed
if (count($drivers) == 1) {
$form['basic_options']['driver']['#disabled'] = TRUE;
$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)));
}
// Database name
Dries Buytaert
committed
$form['basic_options']['database'] = array(
'#type' => 'textfield',
Dries Buytaert
committed
'#default_value' => empty($database['database']) ? '' : $database['database'],
'#size' => 45,
'#required' => TRUE,
'#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())),
);
// Database username
Dries Buytaert
committed
$form['basic_options']['username'] = array(
'#type' => 'textfield',
Dries Buytaert
committed
'#default_value' => empty($database['username']) ? '' : $database['username'],
'#size' => 45,
);
Dries Buytaert
committed
// Database password
Dries Buytaert
committed
$form['basic_options']['password'] = array(
'#type' => 'password',
Dries Buytaert
committed
'#default_value' => empty($database['password']) ? '' : $database['password'],
'#size' => 45,
);
$form['advanced_options'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#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.")
);
// Database host
Dries Buytaert
committed
$form['advanced_options']['host'] = array(
'#type' => 'textfield',
Dries Buytaert
committed
'#default_value' => empty($database['host']) ? 'localhost' : $database['host'],
'#size' => 45,
Dries Buytaert
committed
// Hostnames can be 255 characters long.
'#maxlength' => 255,
'#required' => TRUE,
'#description' => st('If your database is located on a different server, change this.'),
);
// Database port
Dries Buytaert
committed
$form['advanced_options']['port'] = array(
'#type' => 'textfield',
'#title' => st('Database port'),
Dries Buytaert
committed
'#default_value' => empty($database['port']) ? '' : $database['port'],
'#size' => 45,
Dries Buytaert
committed
// The maximum port number is 65536, 5 digits.
'#maxlength' => 5,
'#description' => st('If your database server is listening to a non-standard port, enter its number.'),
);
// Table prefix
Dries Buytaert
committed
$db_prefix = ($profile == 'default') ? 'drupal_' : $profile . '_';
$form['advanced_options']['db_prefix'] = array(
'#type' => 'textfield',
'#title' => st('Table prefix'),
Dries Buytaert
committed
'#default_value' => '',
'#size' => 45,
Dries Buytaert
committed
'#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)),
);
$form['save'] = array(
'#type' => 'submit',
Gábor Hojtsy
committed
'#value' => st('Save and continue'),
);
$form['errors'] = array();
$form['settings_file'] = array('#type' => 'value', '#value' => $settings_file);
Dries Buytaert
committed
$form['_database'] = array('#type' => 'value');
}
Dries Buytaert
committed
return $form;
Dries Buytaert
committed
}
Dries Buytaert
committed
Dries Buytaert
committed
/**
* Form API validate for install_settings form.
*/
Dries Buytaert
committed
function install_settings_form_validate($form, &$form_state) {
Dries Buytaert
committed
form_set_value($form['_database'], $form_state['values'], $form_state);
$errors = install_database_errors($form_state['values'], $form_state['values']['settings_file']);
foreach ($errors as $name => $message) {
form_set_error($name, $message);
}
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Check a database connection and return any errors.
Dries Buytaert
committed
*/
Dries Buytaert
committed
function install_database_errors($database, $settings_file) {
Dries Buytaert
committed
global $databases;
Dries Buytaert
committed
$errors = array();
// Verify the table prefix
Dries Buytaert
committed
if (!empty($database['db_prefix']) && is_string($database['db_prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['db_prefix'])) {
Dries Buytaert
committed
$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']));
Dries Buytaert
committed
}
Dries Buytaert
committed
if (!empty($database['port']) && !is_numeric($database['port'])) {
Dries Buytaert
committed
$errors['db_port'] = st('Database port must be a number.');
Dries Buytaert
committed
// Check database type
Dries Buytaert
committed
$database_types = drupal_detect_database_types();
$driver = $database['driver'];
if (!isset($database_types[$driver])) {
Dries Buytaert
committed
$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']));
Dries Buytaert
committed
}
else {
Dries Buytaert
committed
// Run tasks associated with the database type. Any errors are caught in the
// calling function
Angie Byron
committed
$databases['default']['default'] = $database;
Angie Byron
committed
// Just changing the global doesn't get the new information processed.
// We tell tell the Database class to re-parse $databases.
Database::parseConnectionInfo();
Dries Buytaert
committed
try {
db_run_tasks($database['driver']);
}
catch (DatabaseTaskException $e) {
Dries Buytaert
committed
// These are generic errors, so we do not have any specific key of the
// database connection array to attach them to; therefore, we just put
// them in the error array with standard numeric keys.
$errors[] = $e->getMessage();
Dries Buytaert
committed
}
}
Dries Buytaert
committed
return $errors;
Dries Buytaert
committed
}
/**
* Form API submit for install_settings form.
*/
Dries Buytaert
committed
function install_settings_form_submit($form, &$form_state) {
Dries Buytaert
committed
global $install_state;
Dries Buytaert
committed
Dries Buytaert
committed
$database = array_intersect_key($form_state['values']['_database'], array_flip(array('driver', 'database', 'username', 'password', 'host', 'port')));
Dries Buytaert
committed
// Update global settings array and save
Dries Buytaert
committed
$settings['databases'] = array(
'value' => array('default' => array('default' => $database)),
Dries Buytaert
committed
'required' => TRUE,
);
$settings['db_prefix'] = array(
Dries Buytaert
committed
'value' => $form_state['values']['db_prefix'],
Dries Buytaert
committed
'required' => TRUE,