Newer
Older
Angie Byron
committed
<?php
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Install\TaskException;
use Drupal\Core\Language\Language;
Larry Garfield
committed
catch
committed
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
Larry Garfield
committed
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Larry Garfield
committed
catch
committed
use Guzzle\Http\Exception\RequestException;
Angie Byron
committed
/**
* @file
* API functions for installing Drupal.
*/
/**
Jennifer Hodgdon
committed
* Do not run the task during the current installation request.
Angie Byron
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.
*/
const INSTALL_TASK_SKIP = 1;
Angie Byron
committed
/**
Jennifer Hodgdon
committed
* Run the task on each installation request that reaches it.
Angie Byron
committed
*
* This is primarily used by the Drupal installer for bootstrap-related tasks.
*/
const INSTALL_TASK_RUN_IF_REACHED = 2;
Angie Byron
committed
/**
Jennifer Hodgdon
committed
* Run the task on each installation request until the database is set up.
Angie Byron
committed
*
* 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.
*/
const INSTALL_TASK_RUN_IF_NOT_COMPLETED = 3;
Angie Byron
committed
/**
Dries Buytaert
committed
* Installs Drupal either interactively or via an array of passed-in settings.
Angie Byron
committed
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
*
* 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;
}
}
// After execution, all tasks might be complete, in which case
// $install_state['installation_finished'] is TRUE. In case the last task
// has been processed, remove the global $install_state, so other code can
// reliably check whether it is running during the installer.
// @see drupal_installation_attempted()
$state = $install_state;
if (!empty($install_state['installation_finished'])) {
unset($GLOBALS['install_state']);
}
Angie Byron
committed
// 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 ($state['interactive']) {
if ($state['parameters_changed']) {
Angie Byron
committed
// Redirect to the correct page if the URL parameters have changed.
install_goto(install_redirect_url($state));
Angie Byron
committed
}
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, $state);
Angie Byron
committed
}
}
}
/**
Dries Buytaert
committed
* Returns an array of default settings for the global installation state.
Angie Byron
committed
*
* 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
* 'langcode' that are normally passed in via the URL) and 'forms' (which can
Angie Byron
committed
* 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 a valid config directory is created or
// detected.
'config_verified' => FALSE,
Angie Byron
committed
// This becomes TRUE only when Drupal's system module is installed.
'database_tables_exist' => FALSE,
// This becomes TRUE only when a valid database connection can be
// established.
'database_verified' => FALSE,
// Whether a translation file for the selected language will be downloaded
// from the translation server.
'download_translation' => FALSE,
Angie Byron
committed
// 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 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 'langcode' (the code of the chosen installation language), since
Angie Byron
committed
// 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.yml file.
Angie Byron
committed
'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(),
// The server URL where the interface translation files can be downloaded.
// Tokens in the pattern will be replaced by appropriate values for the
// required translation file.
'server_pattern' => 'http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po',
// This becomes TRUE only when a valid settings.php file is written
// (containing both valid database connection information and a valid
// config directory).
Angie Byron
committed
'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(),
// An array of translation files URIs available for the installation. Keyed
// by the translation language code.
'translations' => array(),
Angie Byron
committed
);
return $defaults;
}
/**
Jennifer Hodgdon
committed
* Begins an installation request, modifying the installation state as needed.
Angie Byron
committed
*
* 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) {
Dries Buytaert
committed
// Add any installation parameters passed in via the URL.
if ($install_state['interactive']) {
$install_state['parameters'] += $_GET;
}
Dries Buytaert
committed
// 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']);
}
if (!empty($install_state['parameters']['langcode'])) {
$install_state['parameters']['langcode'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['langcode']);
Dries Buytaert
committed
}
Angie Byron
committed
// Allow command line scripts to override server variables used by Drupal.
Angie Byron
committed
require_once __DIR__ . '/bootstrap.inc';
Larry Garfield
committed
Angie Byron
committed
if (!$install_state['interactive']) {
drupal_override_server_variables($install_state['server']);
}
// The user agent header is used to pass a database prefix in the request when
// running tests. However, for security reasons, it is imperative that no
// installation be permitted using such a prefix.
elseif (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
Angie Byron
committed
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
exit;
}
catch
committed
// Initialize conf_path().
// This primes the site path to be used during installation. By not requiring
// settings.php, a bare site folder can be prepared in the /sites directory,
// which will be used for installing Drupal.
conf_path(FALSE);
Angie Byron
committed
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
Larry Garfield
committed
// A request object from the HTTPFoundation to tell us about the request.
$request = Request::createFromGlobals();
Angie Byron
committed
// This must go after drupal_bootstrap(), which unsets globals!
global $conf;
Nate Lampton
committed
require_once DRUPAL_ROOT . '/core/modules/system/system.install';
require_once DRUPAL_ROOT . '/core/includes/common.inc';
require_once DRUPAL_ROOT . '/core/includes/file.inc';
require_once DRUPAL_ROOT . '/core/includes/install.inc';
require_once DRUPAL_ROOT . '/core/includes/schema.inc';
require_once DRUPAL_ROOT . '/' . settings()->get('path_inc', 'core/includes/path.inc');
Angie Byron
committed
// Load module basics (needed for hook invokes).
Nate Lampton
committed
include_once DRUPAL_ROOT . '/core/includes/module.inc';
include_once DRUPAL_ROOT . '/core/includes/session.inc';
require_once DRUPAL_ROOT . '/core/includes/entity.inc';
Angie Byron
committed
catch
committed
// Determine whether the configuration system is ready to operate.
$install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY);
// Check existing settings.php.
$install_state['database_verified'] = install_verify_database_settings();
$install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified'];
catch
committed
// If it is not, replace the configuration storage with the InstallStorage
// implementation, for the following reasons:
// - The first call into drupal_container() will try to set up the regular
// runtime configuration storage, using the CachedStorage by default. It
// calls config_get_config_directory() to retrieve the config directory to
// use, but that throws an exception, since $config_directories is not
// defined since there is no settings.php yet. If there is a prepared
// settings.php already, then the returned directory still cannot be used,
// because it does not necessarily exist. The installer ensures that it
// exists and is writeable in a later step.
// - The installer outputs maintenance theme pages and performs many other
// operations, which try to load configuration. Since there is no active
// configuration yet, and because the configuration system does not have a
// notion of default values at runtime, data is missing in many places. The
// lack of data does not trigger errors, but results in a broken user
// interface (e.g., missing page title, etc).
// - The actual configuration data to read during installation is essentially
// the default configuration provided by the installation profile and
// modules (most notably System module). The InstallStorage therefore reads
// from the default configuration directories of extensions.
// This override is reverted as soon as the config directory and the
// database has been set up successfully.
catch
committed
// @see drupal_install_config_directories()
// @see install_settings_form_submit()
if ($install_state['settings_verified']) {
$kernel = new DrupalKernel('install', FALSE, drupal_classloader(), FALSE);
$kernel->boot();
}
else {
catch
committed
// @todo Move into a proper Drupal\Core\DependencyInjection\InstallContainerBuilder.
$container = new ContainerBuilder();
Angie Byron
committed
$container->register('event_dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher');
catch
committed
$container->register('config.storage', 'Drupal\Core\Config\InstallStorage');
$container->register('config.context.factory', 'Drupal\Core\Config\Context\ConfigContextFactory')
->addArgument(new Reference('event_dispatcher'));
$container->register('config.context', 'Drupal\Core\Config\Context\ContextInterface')
->setFactoryService(new Reference('config.context.factory'))
->setFactoryMethod('get');
catch
committed
$container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
->addArgument(new Reference('config.storage'))
->addArgument(new Reference('config.context'));
Dries Buytaert
committed
// Register the 'language_manager' service.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
Dries Buytaert
committed
foreach (array('bootstrap', 'config', 'cache', 'menu', 'page', 'path') as $bin) {
$container
->register("cache.$bin", 'Drupal\Core\Cache\MemoryBackend')
->addArgument($bin);
}
Dries Buytaert
committed
// The install process cannot use the database lock backend since the database
// is not fully up, so we use a null backend implementation during the
// installation process. This will also speed up the installation process.
// The site being installed will use the real lock backend when doing AJAX
// requests but, except for a WSOD, there is no chance for a a lock to stall
// (as opposed to the cache backend) so we can afford having a null
// implementation here.
$container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
// Register a module handler for managing enabled modules.
$container
->register('module_handler', 'Drupal\Core\Extension\ModuleHandler');
catch
committed
// Register the Guzzle HTTP client for fetching translation files from a
// remote translation server such as localization.drupal.org.
$container->register('http_default_client', 'Guzzle\Http\Client')
->addArgument(NULL)
->addArgument(array(
'curl.CURLOPT_TIMEOUT' => 30.0,
'curl.CURLOPT_MAXREDIRS' => 3,
))
->addMethodCall('setUserAgent', array('Drupal (+http://drupal.org/)'));
Dries Buytaert
committed
// Register the expirable key value store used by form cache.
$container
->register('keyvalue.expirable', 'Drupal\Core\KeyValueStore\KeyValueExpirableFactory')
->addArgument(new Reference('service_container'));
$container
->register('keyvalue.expirable.null', 'Drupal\Core\KeyValueStore\KeyValueNullExpirableFactory');
$conf['keyvalue_expirable_default'] = 'keyvalue.expirable.null';
Drupal::setContainer($container);
catch
committed
}
Angie Byron
committed
// Set up $language, so t() caller functions will still work.
Angie Byron
committed
Nate Lampton
committed
require_once DRUPAL_ROOT . '/core/includes/ajax.inc';
$module_handler = drupal_container()->get('module_handler');
if (!$module_handler->moduleExists('system')) {
// Override the module list with a minimal set of modules.
$module_handler->setModuleList(array('system' => 'core/modules/system/system.module'));
}
$module_handler->load('system');
Angie Byron
committed
Nate Lampton
committed
require_once DRUPAL_ROOT . '/core/includes/cache.inc';
Angie Byron
committed
Dries Buytaert
committed
// Prepare for themed output. We need to run this at the beginning of the
// page request to avoid a different theme accidentally getting set. (We also
// need to run it even in the case of command-line installations, to prevent
// any code in the installer that happens to initialize the theme system from
// accessing the database before it is set up yet.)
drupal_maintenance_theme();
Angie Byron
committed
if ($install_state['database_verified']) {
Angie Byron
committed
// Initialize the database system. Note that the connection
// won't be initialized until it is actually requested.
require_once DRUPAL_ROOT . '/core/includes/database.inc';
Angie Byron
committed
// Verify the last completed task in the database, if there is one.
$task = install_verify_completed_task();
}
else {
$task = NULL;
// Do not install over a configured settings.php.
if (!empty($GLOBALS['databases'])) {
Angie Byron
committed
throw new Exception(install_already_done_error());
}
}
// Modify the installation state as appropriate.
$install_state['completed_task'] = $task;
$install_state['database_tables_exist'] = !empty($task);
// Add the list of available profiles to the installation state.
$install_state['profiles'] += drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
Angie Byron
committed
}
/**
Dries Buytaert
committed
* Runs all tasks for the current installation request.
Angie Byron
committed
*
* 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.
Dries Buytaert
committed
*
Angie Byron
committed
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
* @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'])) {
catch
committed
variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
Angie Byron
committed
}
}
// 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;
}
/**
Dries Buytaert
committed
* Runs an individual installation task.
Angie Byron
committed
*
* @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.
Dries Buytaert
committed
*
Angie Byron
committed
* @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') {
Nate Lampton
committed
require_once DRUPAL_ROOT . '/core/includes/form.inc';
Angie Byron
committed
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
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.
'build_info' => array('args' => array(&$install_state)),
'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.
Angie Byron
committed
$form_state = array(
'values' => !empty($install_state['forms'][$function]) ? $install_state['forms'][$function] : 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.
'build_info' => array('args' => array(&$install_state)),
);
drupal_form_submit($function, $form_state);
Angie Byron
committed
$errors = form_get_errors();
if (!empty($errors)) {
throw new Exception(implode("\n", $errors));
}
}
}
elseif ($task['type'] == 'batch') {
// Start a new batch based on the task function, if one is not running
// already.
catch
committed
$current_batch = variable_get('install_current_batch');
Angie Byron
committed
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']) {
catch
committed
variable_set('install_current_batch', $function);
Angie Byron
committed
}
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) {
Nate Lampton
committed
include_once DRUPAL_ROOT . '/core/includes/batch.inc';
Angie Byron
committed
$output = _batch_page();
// Because Batch API now returns a JSON response for intermediary steps,
Niklas Fiekas
committed
// but the installer doesn't handle Response objects yet, just send the
// output here and emulate the old model.
// @todo Replace this when we refactor the installer to use a request-
// response workflow.
if ($output instanceof Response) {
$output->send();
Angie Byron
committed
// 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) {
Angie Byron
committed
// Return nothing so the next task will run in the same request.
catch
committed
variable_del('install_current_batch');
Angie Byron
committed
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;
}
}
}
else {
// For normal tasks, just return the function result, whatever it is.
return $function($install_state);
}
}
/**
Dries Buytaert
committed
* Returns a list of tasks to perform during the current installation request.
Angie Byron
committed
*
* 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.
Dries Buytaert
committed
*
Angie Byron
committed
* @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;
}
}
return $tasks;
}
/**
Dries Buytaert
committed
* Returns a list of all tasks the installer currently knows about.
Angie Byron
committed
*
* 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.
Dries Buytaert
committed
*
Angie Byron
committed
* @return
* A list of tasks, with associated metadata.
*/
function install_tasks($install_state) {
// Determine whether a translation file must be imported during the
// 'install_import_translations' task. Import when a non-English language is
// available and selected.
$needs_translations = count($install_state['translations']) > 1 && !empty($install_state['parameters']['langcode']) && $install_state['parameters']['langcode'] != 'en';
// Determine whether a translation file must be downloaded during the
// 'install_download_translation' task. Download when a non-English language
// is selected, but no translation is yet in the translations directory.
$needs_download = isset($install_state['parameters']['langcode']) && !isset($install_state['translations'][$install_state['parameters']['langcode']]) && $install_state['parameters']['langcode'] != 'en';
Angie Byron
committed
catch
committed
// Start with the core installation tasks that run before handing control
Jennifer Hodgdon
committed
// to the installation profile.
Angie Byron
committed
$tasks = array(
'install_select_language' => array(
'display_name' => st('Choose language'),
'run' => INSTALL_TASK_RUN_IF_REACHED,
),
'install_download_translation' => array(
'run' => $needs_download ? INSTALL_TASK_RUN_IF_REACHED : INSTALL_TASK_SKIP,
),
Angie Byron
committed
'install_select_profile' => array(
'display_name' => st('Choose profile'),
'display' => count($install_state['profiles']) != 1,
'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',
// Even though the form only allows the user to enter database settings,
// we still need to display it if settings.php is invalid in any way,
// since the form submit handler is where settings.php is rewritten.
Angie Byron
committed
'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
),
'install_base_system' => array(
Angie Byron
committed
),
'install_bootstrap_full' => array(
'run' => INSTALL_TASK_RUN_IF_REACHED,
),
'install_profile_modules' => array(
Jennifer Hodgdon
committed
'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Installation profile'),
Angie Byron
committed
'type' => 'batch',
),
'install_import_translations' => array(
Angie Byron
committed
'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',
),
);
// Now add any tasks defined by the installation profile.
if (!empty($install_state['parameters']['profile'])) {
// Load the profile install file, because it is not always loaded when
// hook_install_tasks() is invoked (e.g. batch processing).
$profile = $install_state['parameters']['profile'];
$profile_install_file = dirname($install_state['profiles'][$profile]->uri) . '/' . $profile . '.install';
if (file_exists($profile_install_file)) {
include_once $profile_install_file;
}
Angie Byron
committed
$function = $install_state['parameters']['profile'] . '_install_tasks';
if (function_exists($function)) {
$result = $function($install_state);
if (is_array($result)) {
$tasks += $result;
}
}
}
// Finish by adding the remaining core tasks.
$tasks += array(
'install_import_translations_remaining' => array(
Angie Byron
committed
'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'),
),
);
// Allow the installation profile to modify the full list of tasks.
if (!empty($install_state['parameters']['profile'])) {
$profile = $install_state['parameters']['profile'];
$profile_file = $install_state['profiles'][$profile]->uri;
if (file_exists($profile_file)) {
Angie Byron
committed
include_once $profile_file;
$function = $install_state['parameters']['profile'] . '_install_tasks_alter';
if (function_exists($function)) {
$function($tasks, $install_state);
}
}
}
// 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
* Returns a list of tasks that should be displayed to the end user.
Angie Byron
committed
*
* 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.
Dries Buytaert
committed
*
Angie Byron
committed
* @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;
}
/**
Dries Buytaert
committed
* Returns the URL that should be redirected to during an installation request.
Angie Byron
committed
*
* The output of this function is suitable for sending to install_goto().
*
* @param $install_state
* An array of information about the current installation state.
Dries Buytaert
committed
*
Angie Byron
committed
* @return
* The URL to redirect to.
*
* @see install_full_redirect_url()
*/
function install_redirect_url($install_state) {
Nate Lampton
committed
return 'core/install.php?' . drupal_http_build_query($install_state['parameters']);
Angie Byron
committed
}
/**
Dries Buytaert
committed
* Returns the complete URL redirected to during an installation request.
Angie Byron
committed
*
* @param $install_state
* An array of information about the current installation state.
Dries Buytaert
committed
*
Angie Byron
committed
* @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);
}
/**
Dries Buytaert
committed
* Displays themed installer output and ends the page request.
Angie Byron
committed
*
* 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();
catch
committed
// Prevent install.php from being indexed when installed in a sub folder.
// robots.txt rules are not read if the site is within domain.com/subfolder
// resulting in /subfolder/install.php being found through search engines.
// When settings.php is writeable this can be used via an external database
// leading a malicious user to gain php access to the server.
$noindex_meta_tag = array(
'#tag' => 'meta',
'#attributes' => array(
'name' => 'robots',
'content' => 'noindex, nofollow',
),
);
drupal_add_html_head($noindex_meta_tag, 'install_meta_robots');
Angie Byron
committed
// 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'];
drupal_add_region_content('sidebar_first', theme('task_list', array('items' => install_tasks_to_display($install_state), 'active' => $active_task)));
}
Dries Buytaert
committed
print theme('install_page', array('content' => $output));
Angie Byron
committed
exit;
}
/**
Jennifer Hodgdon
committed
* Verifies the requirements for installing Drupal.
Angie Byron
committed
*
* @param $install_state
* An array of information about the current installation state.
Dries Buytaert
committed
*
Angie Byron
committed
* @return
* A themed status report, or an exception if there are requirement errors.
*/
function install_verify_requirements(&$install_state) {
// Check the installation requirements for Drupal and this profile.
$requirements = install_check_requirements($install_state);
// Verify existence of all required modules.
$requirements += drupal_verify_profile($install_state);
return install_display_requirements($install_state, $requirements);
Angie Byron
committed
}
/**
* Installation task; install the base functionality Drupal needs to bootstrap.
Angie Byron
committed
*
* @param $install_state
* An array of information about the current installation state.
*/
function install_base_system(&$install_state) {
Angie Byron
committed
// Install system.module.
drupal_install_system();
Dries Buytaert
committed
// Call file_ensure_htaccess() to ensure that all of Drupal's standard
// directories (e.g., the public files directory and config directory) have
// appropriate .htaccess files. These directories will have already been
// created by this point in the installer, since Drupal creates them during
// the install_verify_requirements() task. Note that we cannot call
// file_ensure_access() any earlier than this, since it relies on
// system.module in order to work.
file_ensure_htaccess();
Dries Buytaert
committed
// Enable the user module so that sessions can be recorded during the
// upcoming bootstrap step.
module_enable(array('user'), FALSE);
Angie Byron
committed
// Save the list of other modules to install for the upcoming tasks.
Dries Buytaert
committed
// variable_set() can be used now that system.module is installed.
Angie Byron
committed
$modules = $install_state['profile_info']['dependencies'];
Jennifer Hodgdon
committed
// The installation profile is also a module, which needs to be installed
Angie Byron
committed
// after all the dependencies have been installed.
$modules[] = drupal_get_profile();
state()->set('install_profile_modules', array_diff($modules, array('system')));
Angie Byron
committed
$install_state['database_tables_exist'] = TRUE;
}
/**
Jennifer Hodgdon
committed
* Verifies and returns the last installation task that was completed.
Angie Byron
committed
*
* @return
* The last completed task, if there is one. An exception is thrown if Drupal
* is already installed.
*/
function install_verify_completed_task() {
catch
committed
try {
if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) {
$task = unserialize($result->fetchField());
}
}
// Do not trigger an error if the database query fails, since the database
// might not be set up yet.
catch (Exception $e) {
}
if (isset($task)) {
Angie Byron
committed
if ($task == 'done') {
throw new Exception(install_already_done_error());
}
return $task;
}
}
/**
* Verifies that settings.php specifies a valid database connection.
Angie Byron
committed
*/
function install_verify_database_settings() {
Dries Buytaert
committed
global $databases;
if (!empty($databases)) {
Angie Byron
committed
$database = $databases['default']['default'];
drupal_static_reset('conf_path');
$settings_file = './' . conf_path(FALSE) . '/settings.php';
$errors = install_database_errors($database, $settings_file);
if (empty($errors)) {
return TRUE;
}
}
return FALSE;
}
/**
Jennifer Hodgdon
committed
* Form constructor for a form to configure and rewrite settings.php.
Angie Byron
committed
*
* @param $install_state
* An array of information about the current installation state.
Dries Buytaert
committed
*
Jennifer Hodgdon
committed
* @see install_settings_form_validate()
* @see install_settings_form_submit()
* @ingroup forms
Angie Byron
committed
*/
function install_settings_form($form, &$form_state, &$install_state) {
Dries Buytaert
committed
global $databases;
Angie Byron
committed
$profile = $install_state['parameters']['profile'];
drupal_static_reset('conf_path');
$conf_path = './' . conf_path(FALSE);
$settings_file = $conf_path . '/settings.php';
$database = isset($databases['default']['default']) ? $databases['default']['default'] : array();
drupal_set_title(st('Database configuration'));
Angie Byron
committed
$drivers = drupal_get_database_types();
Angie Byron
committed
$drivers_keys = array_keys($drivers);
Angie Byron
committed
$form['driver'] = array(
'#type' => 'radios',
'#title' => st('Database type'),
'#required' => TRUE,
Angie Byron
committed
'#default_value' => !empty($database['driver']) ? $database['driver'] : current($drivers_keys),
'#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_distribution_name())),