$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']); } // 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']) { // Redirect to the correct page if the URL parameters have changed. install_goto(install_redirect_url($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, $state); } } } /** * Returns 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 * 'langcode' 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 a valid config directory is created or // detected. 'config_verified' => FALSE, // 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, // 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, // The mode for directories that are created during install. 'mode' => NULL, // 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 // 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. '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). '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(), ); return $defaults; } /** * Begins 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) { // A request object from the HTTPFoundation to tell us about the request. $request = Request::createFromGlobals(); // Create a minimal container so that t() and $request will work. This // container will be overriden but it's needed for the very early installation // process when database tasks run. $container = new ContainerBuilder(); $container->set('request', $request); \Drupal::setContainer($container); // Add any installation parameters passed in via the URL. if ($install_state['interactive']) { $install_state['parameters'] += $request->query->all(); } // 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']); } // Allow command line scripts to override server variables used by Drupal. require_once __DIR__ . '/bootstrap.inc'; if (!$install_state['interactive']) { drupal_override_server_variables($install_state['server']); } // 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); drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); // If the hash salt leaks, it becomes possible to forge a valid testing user // agent, install a new copy of Drupal, and take over the original site. To // avoid this yet allow for automated testing of the installer, make sure // there is also a special test-specific settings.php overriding conf_path(). // _drupal_load_test_overrides() sets the simpletest_conf_path in-memory // setting in this case. if ($install_state['interactive'] && drupal_valid_test_ua() && !settings()->get('simpletest_conf_path')) { header($request->server->get('SERVER_PROTOCOL') . ' 403 Forbidden'); exit; } // If we have a language selected and it is not yet saved in the system // (eg. pre-database data screens we are unable to persistently store // the default language), we should set language_default so the proper // language is used to display installer pages as early as possible. // The language list is stored in configuration and cannot be saved either // until later in the process. Language negotiation bootstrapping needs // the new default language to be in the list though, so inject it in. if (!empty($install_state['parameters']['langcode']) && language_default()->id != $install_state['parameters']['langcode']) { $GLOBALS['conf']['language_default'] = array('id' => $install_state['parameters']['langcode']); $languages = &drupal_static('language_list'); $languages[$install_state['parameters']['langcode']] = new Language($GLOBALS['conf']['language_default']); } require_once __DIR__ . '/../modules/system/system.install'; require_once __DIR__ . '/common.inc'; require_once __DIR__ . '/file.inc'; require_once __DIR__ . '/install.inc'; require_once __DIR__ . '/schema.inc'; require_once __DIR__ . '/../../' . settings()->get('path_inc', 'core/includes/path.inc'); // Load module basics (needed for hook invokes). include_once __DIR__ . '/module.inc'; include_once __DIR__ . '/session.inc'; require_once __DIR__ . '/entity.inc'; // 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); // Register the translation services. install_register_translation_service($container); \Drupal::setContainer($container); // Check existing settings.php. $install_state['database_verified'] = install_verify_database_settings(); $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified']; // 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. // @see drupal_install_config_directories() // @see install_settings_form_submit() if ($install_state['settings_verified']) { $kernel = new DrupalKernel('install', drupal_classloader(), FALSE); $kernel->boot(); $container = $kernel->getContainer(); // Add the file translation service to the container. $container->set('string_translator.file_translation', install_file_translation_service()); $container->get('string_translation')->addTranslator($container->get('string_translator.file_translation')); } else { // @todo Move into a proper Drupal\Core\DependencyInjection\InstallContainerBuilder. $container = new ContainerBuilder(); $container->register('event_dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher'); $container->register('config.storage', 'Drupal\Core\Config\InstallStorage'); $container->register('config.context.factory', 'Drupal\Core\Config\Context\ConfigContextFactory') ->addArgument(new Reference('event_dispatcher')) ->addArgument(new Reference('uuid')); $container->register('config.context', 'Drupal\Core\Config\Context\ContextInterface') ->setFactoryService(new Reference('config.context.factory')) ->setFactoryMethod('get'); $container->register('config.storage.schema', 'Drupal\Core\Config\Schema\SchemaStorage'); $container->register('config.typed', 'Drupal\Core\Config\TypedConfigManager') ->addArgument(new Reference('config.storage')) ->addArgument(new Reference('config.storage.schema')); $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') ->addArgument(new Reference('config.storage')) ->addArgument(new Reference('config.context')) ->addArgument(new Reference('config.typed')); // Register the 'language_manager' service. $container ->register('language_manager', 'Drupal\Core\Language\LanguageManager') ->addArgument(NULL); // Register the translation services. install_register_translation_service($container); foreach (array('bootstrap', 'config', 'cache', 'menu', 'page', 'path') as $bin) { $container ->register("cache.$bin", 'Drupal\Core\Cache\MemoryBackend') ->addArgument($bin); } // 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'); $container ->register('theme.registry', 'Drupal\Core\Theme\Registry') ->addArgument(new Reference('cache.cache')) ->addArgument(new Reference('lock')) ->addArgument(new Reference('module_handler')) ->addTag('needs_destruction'); // Register a module handler for managing enabled modules. $container ->register('module_handler', 'Drupal\Core\Extension\ModuleHandler'); // 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/)')); $container->register('settings', 'Drupal\Component\Utility\Settings') ->setFactoryClass('Drupal\Component\Utility\Settings') ->setFactoryMethod('getSingleton'); $container ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory'); $container ->register('keyvalue.expirable', 'Drupal\Core\KeyValueStore\KeyValueNullExpirableFactory'); $container->register('state', 'Drupal\Core\KeyValueStore\State') ->addArgument(new Reference('keyvalue')); // Register Twig template engine for use during install. CoreServiceProvider::registerTwig($container); $container->register('url_generator', 'Drupal\Core\Routing\NullGenerator'); $container->register('form_builder', 'Drupal\Core\Form\FormBuilder') ->addArgument(new Reference('module_handler')) ->addArgument(new Reference('keyvalue.expirable')) ->addArgument(new Reference('event_dispatcher')) ->addArgument(new Reference('url_generator')) ->addArgument(new Reference('string_translation')) ->addArgument(new Reference('csrf_token', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)) ->addArgument(new Reference('http_kernel', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)) ->addMethodCall('setRequest', array(new Reference('request'))); // Register UUID. CoreServiceProvider::registerUuid($container); // Register the CSS and JavaScript asset collection renderers. $container->register('asset.css.collection_renderer', 'Drupal\Core\Asset\CssCollectionRenderer') ->addArgument(new Reference('state')); $container->register('asset.js.collection_renderer', 'Drupal\Core\Asset\JsCollectionRenderer') ->addArgument(new Reference('state')); // Register the info parser. $container->register('info_parser', 'Drupal\Core\Extension\InfoParser'); $container->register('theme_handler', 'Drupal\Core\Extension\ThemeHandler') ->addArgument(new Reference('config.factory')) ->addArgument(new Reference('module_handler')) ->addArgument(new Reference('cache.cache')) ->addArgument(new Reference('info_parser')); } // Set the request in the kernel to the new created Request above // so it is available to the rest of the installation process. $container->set('request', $request); \Drupal::setContainer($container); // Set up $language, so t() caller functions will still work. drupal_language_initialize(); // Add in installation language if present. if (isset($install_state['parameters']['langcode'])) { \Drupal::translation()->setDefaultLangcode($install_state['parameters']['langcode']); } require_once __DIR__ . '/ajax.inc'; $module_handler = \Drupal::moduleHandler(); 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'); require_once __DIR__ . '/cache.inc'; // 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(); if ($install_state['database_verified']) { // Initialize the database system. Note that the connection // won't be initialized until it is actually requested. require_once __DIR__ . '/database.inc'; // 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'])) { throw new Exception(install_already_done_error()); } } // Ensure that the active configuration directory is empty before installation // starts. if ($install_state['config_verified'] && empty($task)) { $config = glob(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY) . '/*.' . FileStorage::getFileExtension()); if (!empty($config)) { $task = NULL; 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'); } /** * Runs 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::state()->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; } /** * Runs 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 __DIR__ . '/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. '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. $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); $errors = form_get_errors($form_state); 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. $current_batch = \Drupal::state()->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']) { \Drupal::state()->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. $response = batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state)); if ($response instanceof Response) { // Save $_SESSION data from batch. drupal_session_commit(); // Send the response. $response->send(); exit; } } // 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 __DIR__ . '/batch.inc'; $output = _batch_page(\Drupal::request()); // Because Batch API now returns a JSON response for intermediary steps, // 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(); $output = NULL; } // 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. \Drupal::state()->delete('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; } } } else { // For normal tasks, just return the function result, whatever it is. return $function($install_state); } } /** * Returns 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; } } return $tasks; } /** * Returns 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 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'; // Start with the core installation tasks that run before handing control // to the installation profile. $tasks = array( 'install_select_language' => array( 'display_name' => t('Choose language'), 'run' => INSTALL_TASK_RUN_IF_REACHED, ), 'install_download_translation' => array( 'run' => $needs_download ? INSTALL_TASK_RUN_IF_REACHED : INSTALL_TASK_SKIP, ), 'install_select_profile' => array( 'display_name' => t('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' => t('Verify requirements'), ), 'install_settings_form' => array( 'display_name' => t('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. 'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED, ), 'install_base_system' => array( ), 'install_bootstrap_full' => array( 'run' => INSTALL_TASK_RUN_IF_REACHED, ), 'install_profile_modules' => array( 'display_name' => count($install_state['profiles']) == 1 ? t('Install site') : t('Installation profile'), 'type' => 'batch', ), 'install_import_translations' => array( 'display_name' => t('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' => t('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 DRUPAL_ROOT . '/' . $profile_install_file; } $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( 'display_name' => t('Finish translations'), 'display' => $needs_translations, 'type' => 'batch', 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, ), 'install_update_configuration_translations' => array( 'display_name' => t('Translate configuration'), 'display' => $needs_translations, 'type' => 'batch', 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, ), 'install_finished' => array( 'display_name' => t('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)) { include_once DRUPAL_ROOT . '/' . $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; } /** * Returns 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; } /** * Returns 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) { return 'core/install.php?' . drupal_http_build_query($install_state['parameters']); } /** * Returns the complete URL 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); } /** * Displays themed installer output and ends 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(); // 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'); // 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, 'variant' => 'install'))); } print theme('install_page', array('content' => $output)); exit; } /** * Verifies 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. */ 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); } /** * Installation task; install the base functionality Drupal needs to bootstrap. * * @param $install_state * An array of information about the current installation state. */ function install_base_system(&$install_state) { // Install system.module. drupal_install_system(); // 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(); // Enable the user module so that sessions can be recorded during the // upcoming bootstrap step. \Drupal::moduleHandler()->install(array('user'), FALSE); // Save the list of other modules to install for the upcoming tasks. // State can be set to the database now that system.module is installed. $modules = $install_state['profile_info']['dependencies']; // The installation profile is also a module, which needs to be installed // after all the dependencies have been installed. $modules[] = drupal_get_profile(); \Drupal::state()->set('install_profile_modules', array_diff($modules, array('system'))); $install_state['database_tables_exist'] = TRUE; } /** * Verifies and returns 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. */ function install_verify_completed_task() { try { $task = \Drupal::state()->get('install_task'); } // 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)) { if ($task == 'done') { throw new Exception(install_already_done_error()); } return $task; } } /** * Verifies that settings.php specifies a valid database connection. */ function install_verify_database_settings() { global $databases; if (!empty($databases)) { $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; } /** * Form constructor for a form to configure and rewrite settings.php. * * @param $install_state * An array of information about the current installation state. * * @see install_settings_form_validate() * @see install_settings_form_submit() * @ingroup forms */ function install_settings_form($form, &$form_state, &$install_state) { global $databases; 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(t('Database configuration')); $drivers = drupal_get_database_types(); $drivers_keys = array_keys($drivers); $form['driver'] = array( '#type' => 'radios', '#title' => t('Database type'), '#required' => TRUE, '#default_value' => !empty($database['driver']) ? $database['driver'] : current($drivers_keys), ); if (count($drivers) == 1) { $form['driver']['#disabled'] = TRUE; } // Add driver specific configuration options. foreach ($drivers as $key => $driver) { $form['driver']['#options'][$key] = $driver->name(); $form['settings'][$key] = $driver->getFormOptions($database); $form['settings'][$key]['#prefix'] = '
Translations will be downloaded from the Drupal Translation website. ' . 'If you do not want this, select English.
', '#states' => array( 'invisible' => array( 'select[name="langcode"]' => array('value' => 'en'), ), ), ); } $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Save and continue'), '#button_type' => 'primary', ); return $form; } /** * Download a translation file for the selected langaguage. * * @param array $install_state * An array of information about the current installation state. * * @return string * A themed status report, or an exception if there are requirement errors. * Upon successful download the page is reloaded and no output is returned. */ function install_download_translation(&$install_state) { // Check whether all conditions are met to download. Download the translation // if possible. $requirements = install_check_translations($install_state); if ($output = install_display_requirements($install_state, $requirements)) { return $output; } // The download was successful, reload the page in the new lanagage. install_goto(install_redirect_url($install_state)); } /** * Attempts to get a file using a HTTP request and to store it locally. * * @param string $uri * The URI of the file to grab. * @param string $destination * Stream wrapper URI specifying where the file should be placed. If a * directory path is provided, the file is saved into that directory under its * original name. If the path contains a filename as well, that one will be * used instead. * * @return boolean * TRUE on success, FALSE on failure. */ function install_retrieve_file($uri, $destination) { $parsed_url = parse_url($uri); if (is_dir(drupal_realpath($destination))) { // Prevent URIs with triple slashes when gluing parts together. $path = str_replace('///', '//', "$destination/") . drupal_basename($parsed_url['path']); } else { $path = $destination; } try { $request = \Drupal::httpClient()->get($uri, array('Accept' => 'text/plain')); $data = $request->send()->getBody(TRUE); if (empty($data)) { return FALSE; } } catch (RequestException $e) { return FALSE; } return file_put_contents($path, $data) !== FALSE; } /** * Checks if the localization server can be contacted. * * @param string $uri * The URI to contact. * * @return string * TRUE if the URI was contacted successfully, FALSE if not. */ function install_check_localization_server($uri) { try { $request = \Drupal::httpClient()->head($uri); $request->send(); return TRUE; } catch (RequestException $e) { return FALSE; } } /** * Gets the core release version and release alternatives for localization. * * In case core is a development version or the translation file for the release * is not available, fall back to the latest stable release. For example, * 8.2-dev might fall back to 8.1 and 8.0-dev might fall back to 7.0. Fallback * is required because the localization server only provides translation files * for stable releases. * * @param string $version * (optional) Version of core trying to find translation files for. * * @return array * Array of release data. Each array element is an associative array with: * - core: Core compatibility version (e.g., 8.x). * - version: Release version (e.g., 8.1). */ function install_get_localization_release($version = \Drupal::VERSION) { $releases = array(); $alternatives = array(); // The version string is broken up into: // - major: Major version (e.g., "8"). // - minor: Minor version (e.g., "0"). // - extra: Extra version info (e.g., "alpha2"). // - extra_text: The text part of "extra" (e.g., "alpha"). // - extra_number: The number part of "extra" (e.g., "2"). $info = _install_get_version_info($version); // Check if the version is a regular stable release (no 'rc', 'beta', 'alpha', // 'dev', etc.) if (!isset($info['extra_text'])) { // First version alternative: the current version. $alternatives[] = $version; // Point-releases: previous minor release (e.g., 8.2 falls back to 8.1). if ($info['minor'] > 0) { $alternatives[]= $info['major'] . '.' . ($info['minor'] - 1); } // Zero release: first release candidate (e.g., 8.0 falls back to 8.0-rc1). else { $alternatives[] = $info['major'] . '.0-rc1'; } } else { switch ($info['extra_text']) { // Alpha release: current alpha or previous alpha release (e.g. 8.0-alpha2 // falls back to 8.0-alpha1). case 'alpha': $alternatives[] = $version; if ($info['extra_number'] > 1) { $alternatives[] = $info['major'] . '.0-alpha' . ($info['extra_number'] - 1); } break; // Beta release: current beta or previous beta release (e.g. 8.0-beta2 // falls back to 8.0-beta1) or first alpha release. case 'beta': $alternatives[] = $version; if ($info['extra_number'] > 1) { $alternatives[] = $info['major'] . '.0-beta' . ($info['extra_number'] - 1); } else { $alternatives[] = $info['major'] . '.0-alpha2'; } break; // Dev release: the previous point release (e.g., 8.2-dev falls back to // 8.1) or any unstable release (e.g., 8.0-dev falls back to 8.0-rc1, // 8.0-beta1 or 8.0-alpha2) case 'dev': if ($info['minor'] >= 1) { $alternatives[] = $info['major'] . '.' . ($info['minor'] - 1); } else { $alternatives[] = $info['major'] . '.0-rc1'; $alternatives[] = $info['major'] . '.0-beta1'; $alternatives[] = $info['major'] . '.0-alpha2'; } break; // Release candidate: the current or previous release candidate (e.g., // 8.0-rc2 falls back to 8.0-rc1) or the first beta release. case 'rc': $alternatives[] = $version; if ($info['extra_number'] >= 2) { $alternatives[] = $info['major'] . '.0-rc' . ($info['extra_number'] - 1); } else { $alternatives[] = $info['major'] . '.0-beta1'; } break; } } // All releases may a fallback to the previous major release (e.g., 8.1 falls // back to 7.0, 8.x-dev falls back to 7.0). This will probably only be used // for early dev releases or languages with an inactive translation team. $alternatives[] = $info['major'] - 1 . '.0'; foreach ($alternatives as $alternative) { list($core) = explode('.', $alternative); $releases[] = array( 'core' => $core . '.x', 'version' => $alternative, ); } return $releases; } /** * Extracts version information from a drupal core version string. * * @param string $version * Version info string (e.g., 8.0, 8.1, 8.0-dev, 8.0-unstable1, 8.0-alpha2, * 8.0-beta3, and 8.0-rc4). * * * @return array * Associative array of version info: * - major: Major version (e.g., "8"). * - minor: Minor version (e.g., "0"). * - extra: Extra version info (e.g., "alpha2"). * - extra_text: The text part of "extra" (e.g., "alpha"). * - extra_number: The number part of "extra" (e.g., "2"). */ function _install_get_version_info($version) { preg_match('/ ( (?P' . t('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '
'; // Ensure the URL that is generated for the home page does not have 'install.php' // in it. $request = Request::createFromGlobals(); $generator = \Drupal::urlGenerator(); $generator->setBasePath(preg_replace('#/core$#', '', $request->getBasePath()) . '/' ); $generator->setScriptPath(''); $url = $generator->generateFromPath(''); $output .= '' . (isset($messages['error']) ? t('Review the messages above before visiting your new site.', array('@url' => $url)) : t('Visit your new site.', array('@url' => $url))) . '
'; // Run cron to populate update status tables (if available) so that users // will be warned if they've installed an out of date Drupal version. // Will also trigger indexing of profile-supplied content or feeds. drupal_cron_run(); // Save a snapshot of the initially installed configuration. $active = \Drupal::service('config.storage'); $snapshot = \Drupal::service('config.storage.snapshot'); config_import_create_snapshot($active, $snapshot); return $output; } /** * Batch callback for batch installation of modules. */ function _install_module_batch($module, $module_name, &$context) { // Install and enable the module right away, so that the module will be // loaded by drupal_bootstrap in subsequent batch requests, and other // modules possibly depending on it can safely perform their installation // steps. \Drupal::moduleHandler()->install(array($module), FALSE); $context['results'][] = $module; $context['message'] = t('Installed %module module.', array('%module' => $module_name)); } /** * 'Finished' callback for module installation batch. */ function _install_profile_modules_finished($success, $results, $operations) { // Flush all caches to complete the module installation process. Subsequent // installation tasks will now have full access to the profile's modules. drupal_flush_all_caches(); } /** * Checks installation requirements and reports any errors. */ function install_check_translations($install_state) { $requirements = array(); $readable = FALSE; $writable = FALSE; // @todo: Make this configurable. $files_directory = conf_path() . '/files'; $translations_directory = conf_path() . '/files/translations'; $translations_directory_exists = FALSE; $translation_available = FALSE; $online = FALSE; // First attempt to create or make writable the files directory. file_prepare_directory($files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS, $install_state['mode']); // Then, attempt to create or make writable the translations directory. file_prepare_directory($translations_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS, $install_state['mode']); // Get values so the requirements errors can be specific. if (drupal_verify_install_file($translations_directory, FILE_EXIST|FILE_WRITABLE, 'dir')) { $readable = is_readable($translations_directory); $writable = is_writable($translations_directory); $translations_directory_exists = TRUE; } // Build URLs for the translation file and the translation server. $releases = install_get_localization_release(); $langcode = $install_state['parameters']['langcode']; $translation_urls = array(); foreach ($releases as $release) { $variables = array( '%project' => 'drupal', '%version' => $release['version'], '%core' => $release['core'], '%language' => $langcode, ); $translation_urls[] = strtr($install_state['server_pattern'], $variables); } $elements = parse_url(reset($translation_urls)); $server_url = $elements['scheme'] . '://' . $elements['host']; // Build the language name for display. $languages = LanguageManager::getStandardLanguageList(); $language = isset($languages[$langcode]) ? $languages[$langcode][0] : $langcode; // Check if any of the desired translation files are available or if the // translation server can be reached. In other words, check if we are online // and have an internet connection. foreach ($translation_urls as $translation_url) { if ($translation_available = install_check_localization_server($translation_url)) { $online = TRUE; break; } } if (!$translation_available) { if (install_check_localization_server($server_url)) { $online = TRUE; } } // If the translations directory does not exists, throw an error. if (!$translations_directory_exists) { $requirements['translations directory exists'] = array( 'title' => t('Translations directory'), 'value' => t('The translations directory does not exist.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The installer requires that you create a translations directory as part of the installation process. Create the directory %translations_directory . More details about installing Drupal are available in INSTALL.txt.', array('%translations_directory' => $translations_directory, '@install_txt' => base_path() . 'core/INSTALL.txt')), ); } else { $requirements['translations directory exists'] = array( 'title' => t('Translations directory'), 'value' => t('The directory %translations_directory exists.', array('%translations_directory' => $translations_directory)), ); // If the translations directory is not readable, throw an error. if (!$readable) { $requirements['translations directory readable'] = array( 'title' => t('Translations directory'), 'value' => t('The translations directory is not readable.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The installer requires read permissions to %translations_directory at all times. If you are unsure how to grant file permissions, consult the online handbook.', array('%translations_directory' => $translations_directory, '@handbook_url' => 'http://drupal.org/server-permissions')), ); } // If translations directory is not writable, throw an error. if (!$writable) { $requirements['translations directory writable'] = array( 'title' => t('Translations directory'), 'value' => t('The translations directory is not writable.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The installer requires write permissions to %translations_directory during the installation process. If you are unsure how to grant file permissions, consult the online handbook.', array('%translations_directory' => $translations_directory, '@handbook_url' => 'http://drupal.org/server-permissions')), ); } else { $requirements['translations directory writable'] = array( 'title' => t('Translations directory'), 'value' => t('The translations directory is writable.'), ); } } // If the translations server can not be contacted, throw an error. if (!$online) { $requirements['online'] = array( 'title' => t('Internet'), 'value' => t('The translation server is offline.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The installer requires to contact the translation server to download a translation file. Check your internet connection and verify that your website can reach the translation server at !server_url.', array('!server_url' => $server_url)), ); } else { $requirements['online'] = array( 'title' => t('Internet'), 'value' => t('The translation server is online.'), ); // If translation file is not found at the translation server, throw an // error. if (!$translation_available) { $requirements['translation available'] = array( 'title' => t('Translation'), 'value' => t('The %language translation is not available.', array('%language' => $language)), 'severity' => REQUIREMENT_ERROR, 'description' => t('The %language translation file is not available at the translation server. Choose a different language or select English and translate your website later.', array('%language' => $language, '!url' => check_url($_SERVER['SCRIPT_NAME']))), ); } else { $requirements['translation available'] = array( 'title' => t('Translation'), 'value' => t('The %language translation is available.', array('%language' => $language)), ); } } if ($translations_directory_exists && $readable && $writable && $translation_available) { $translation_downloaded = install_retrieve_file($translation_url, $translations_directory); if (!$translation_downloaded) { $requirements['translation downloaded'] = array( 'title' => t('Translation'), 'value' => t('The %language translation could not be downloaded.', array('%language' => $language)), 'severity' => REQUIREMENT_ERROR, 'description' => t('The %language translation file could not be downloaded. Choose a different language or select English and translate your website later.', array('%language' => $language, '!url' => check_url($_SERVER['SCRIPT_NAME']))), ); } } return $requirements; } /** * Checks installation requirements and reports any errors. */ function install_check_requirements($install_state) { $profile = $install_state['parameters']['profile']; // Check the profile requirements. $requirements = drupal_check_profile($profile, $install_state); // If Drupal is not set up already, we need to create a settings file. if (!$install_state['settings_verified']) { $readable = FALSE; $writable = FALSE; $conf_path = './' . conf_path(FALSE, TRUE); $settings_file = $conf_path . '/settings.php'; $default_settings_file = './sites/default/default.settings.php'; $file = $conf_path; $exists = FALSE; // Verify that the directory exists. if (drupal_verify_install_file($conf_path, FILE_EXIST, 'dir')) { // Check if a settings.php file already exists. $file = $settings_file; if (drupal_verify_install_file($settings_file, FILE_EXIST)) { // If it does, make sure it is writable. $readable = drupal_verify_install_file($settings_file, FILE_READABLE); $writable = drupal_verify_install_file($settings_file, FILE_WRITABLE); $exists = TRUE; } } // If default.settings.php does not exist, or is not readable, throw an // error. if (!drupal_verify_install_file($default_settings_file, FILE_EXIST|FILE_READABLE)) { $requirements['default settings file exists'] = array( 'title' => t('Default settings file'), 'value' => t('The default settings file does not exist.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The @drupal installer requires that the %default-file file not be modified in any way from the original download.', array('@drupal' => drupal_install_profile_distribution_name(), '%default-file' => $default_settings_file)), ); } // Otherwise, if settings.php does not exist yet, we can try to copy // default.settings.php to create it. elseif (!$exists) { $copied = drupal_verify_install_file($conf_path, FILE_EXIST|FILE_WRITABLE, 'dir') && @copy($default_settings_file, $settings_file); if ($copied) { // If the new settings file has the same owner as default.settings.php, // this means default.settings.php is owned by the webserver user. // This is an inherent security weakness because it allows a malicious // webserver process to append arbitrary PHP code and then execute it. // However, it is also a common configuration on shared hosting, and // there is nothing Drupal can do to prevent it. In this situation, // having settings.php also owned by the webserver does not introduce // any additional security risk, so we keep the file in place. if (fileowner($default_settings_file) === fileowner($settings_file)) { $readable = drupal_verify_install_file($settings_file, FILE_READABLE); $writable = drupal_verify_install_file($settings_file, FILE_WRITABLE); $exists = TRUE; } // If settings.php and default.settings.php have different owners, this // probably means the server is set up "securely" (with the webserver // running as its own user, distinct from the user who owns all the // Drupal PHP files), although with either a group or world writable // sites directory. Keeping settings.php owned by the webserver would // therefore introduce a security risk. It would also cause a usability // problem, since site owners who do not have root access to the file // system would be unable to edit their settings file later on. We // therefore must delete the file we just created and force the // administrator to log on to the server and create it manually. else { $deleted = @drupal_unlink($settings_file); // We expect deleting the file to be successful (since we just // created it ourselves above), but if it fails somehow, we set a // variable so we can display a one-time error message to the // administrator at the bottom of the requirements list. We also try // to make the file writable, to eliminate any conflicting error // messages in the requirements list. $exists = !$deleted; if ($exists) { $settings_file_ownership_error = TRUE; $readable = drupal_verify_install_file($settings_file, FILE_READABLE); $writable = drupal_verify_install_file($settings_file, FILE_WRITABLE); } } } } // If settings.php does not exist, throw an error. if (!$exists) { $requirements['settings file exists'] = array( 'title' => t('Settings file'), 'value' => t('The settings file does not exist.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The @drupal installer requires that you create a settings file as part of the installation process. Copy the %default_file file to %file. More details about installing Drupal are available in INSTALL.txt.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'core/INSTALL.txt')), ); } else { $requirements['settings file exists'] = array( 'title' => t('Settings file'), 'value' => t('The %file file exists.', array('%file' => $file)), ); // If settings.php is not readable, throw an error. if (!$readable) { $requirements['settings file readable'] = array( 'title' => t('Settings file'), 'value' => t('The settings file is not readable.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('@drupal requires read permissions to %file at all times. If you are unsure how to grant file permissions, consult the online handbook.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')), ); } // If settings.php is not writable, throw an error. if (!$writable) { $requirements['settings file writable'] = array( 'title' => t('Settings file'), 'value' => t('The settings file is not writable.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The @drupal installer requires write permissions to %file during the installation process. If you are unsure how to grant file permissions, consult the online handbook.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')), ); } else { $requirements['settings file'] = array( 'title' => t('Settings file'), 'value' => t('The settings file is writable.'), ); } if (!empty($settings_file_ownership_error)) { $requirements['settings file ownership'] = array( 'title' => t('Settings file'), 'value' => t('The settings file is owned by the web server.'), 'severity' => REQUIREMENT_ERROR, 'description' => t('The @drupal installer failed to create a settings file with proper file ownership. Log on to your web server, remove the existing %file file, and create a new one by copying the %default_file file to %file. More details about installing Drupal are available in INSTALL.txt. If you have problems with the file permissions on your server, consult the online handbook.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'core/INSTALL.txt', '@handbook_url' => 'http://drupal.org/server-permissions')), ); } } } return $requirements; } /** * Displays installation requirements. * * @param array $install_state * An array of information about the current installation state. * @param array $requirements * An array of requirements, in the same format as is returned by * hook_requirements(). * * @return * A themed status report, or an exception if there are requirement errors. * If there are only requirement warnings, a themed status report is shown * initially, but the user is allowed to bypass it by providing 'continue=1' * in the URL. Otherwise, no output is returned, so that the next task can be * run in the same page request. * * @thows \Exception */ function install_display_requirements($install_state, $requirements) { // Check the severity of the requirements reported. $severity = drupal_requirements_severity($requirements); // If there are errors, always display them. If there are only warnings, skip // them if the user has provided a URL parameter acknowledging the warnings // and indicating a desire to continue anyway. See drupal_requirements_url(). if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($install_state['parameters']['continue']))) { if ($install_state['interactive']) { drupal_set_title(t('Requirements problem')); $status_report = theme('status_report', array('requirements' => $requirements)); $status_report .= t('Check the messages and try again.', array('!url' => check_url(drupal_requirements_url($severity)))); return $status_report; } else { // Throw an exception showing any unmet requirements. $failures = array(); foreach ($requirements as $requirement) { // Skip warnings altogether for non-interactive installations; these // proceed in a single request so there is no good opportunity (and no // good method) to warn the user anyway. if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) { $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description']; } } if (!empty($failures)) { throw new \Exception(implode("\n\n", $failures)); } } } } /** * Form constructor for a site configuration form. * * @param $install_state * An array of information about the current installation state. * * @see install_configure_form() * @see install_configure_form_validate() * @see install_configure_form_submit() * @ingroup forms */ function _install_configure_form($form, &$form_state, &$install_state) { $form['site_information'] = array( '#type' => 'details', '#title' => t('Site information'), ); $form['site_information']['site_name'] = array( '#type' => 'textfield', '#title' => t('Site name'), '#required' => TRUE, '#weight' => -20, ); $form['site_information']['site_mail'] = array( '#type' => 'email', '#title' => t('Site e-mail address'), '#default_value' => ini_get('sendmail_from'), '#description' => t("Automated e-mails, such as registration information, will be sent from this address. Use an address ending in your site's domain to help prevent these e-mails from being flagged as spam."), '#required' => TRUE, '#weight' => -15, ); $form['admin_account'] = array( '#type' => 'details', '#title' => t('Site maintenance account'), ); $form['admin_account']['account']['#tree'] = TRUE; $form['admin_account']['account']['name'] = array('#type' => 'textfield', '#title' => t('Username'), '#maxlength' => USERNAME_MAX_LENGTH, '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'), '#required' => TRUE, '#weight' => -10, '#attributes' => array('class' => array('username')), ); $form['admin_account']['account']['mail'] = array( '#type' => 'email', '#title' => t('E-mail address'), '#required' => TRUE, '#weight' => -5, ); $form['admin_account']['account']['pass'] = array( '#type' => 'password_confirm', '#required' => TRUE, '#size' => 25, '#weight' => 0, ); $form['regional_settings'] = array( '#type' => 'details', '#title' => t('Regional settings'), ); $countries = \Drupal::service('country_manager')->getList(); $form['regional_settings']['site_default_country'] = array( '#type' => 'select', '#title' => t('Default country'), '#empty_value' => '', '#default_value' => \Drupal::config('system.date')->get('country.default'), '#options' => $countries, '#description' => t('Select the default country for the site.'), '#weight' => 0, ); $form['regional_settings']['date_default_timezone'] = array( '#type' => 'select', '#title' => t('Default time zone'), '#default_value' => date_default_timezone_get(), '#options' => system_time_zones(), '#description' => t('By default, dates in this site will be displayed in the chosen time zone.'), '#weight' => 5, '#attributes' => array('class' => array('timezone-detect')), ); $form['update_notifications'] = array( '#type' => 'details', '#title' => t('Update notifications'), ); $form['update_notifications']['update_status_module'] = array( '#type' => 'checkboxes', '#title' => t('Update notifications'), '#options' => array( 1 => t('Check for updates automatically'), 2 => t('Receive e-mail notifications'), ), '#default_value' => array(1, 2), '#description' => t('The system will notify you when updates and important security releases are available for installed components. Anonymous information about your site is sent to Drupal.org.', array('@drupal' => 'http://drupal.org')), '#weight' => 15, ); $form['update_notifications']['update_status_module'][2] = array( '#states' => array( 'visible' => array( 'input[name="update_status_module[1]"]' => array('checked' => TRUE), ), ), ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Save and continue'), '#weight' => 15, '#button_type' => 'primary', ); return $form; } /** * Form validation handler for install_configure_form(). * * @see install_configure_form_submit() */ function install_configure_form_validate($form, &$form_state) { if ($error = user_validate_name($form_state['values']['account']['name'])) { form_error($form['admin_account']['account']['name'], $form_state, $error); } } /** * Form submission handler for install_configure_form(). * * @see install_configure_form_validate() */ function install_configure_form_submit($form, &$form_state) { \Drupal::config('system.site') ->set('name', $form_state['values']['site_name']) ->set('mail', $form_state['values']['site_mail']) ->set('langcode', language_default()->id) ->save(); \Drupal::config('system.date') ->set('timezone.default', $form_state['values']['date_default_timezone']) ->set('country.default', $form_state['values']['site_default_country']) ->save(); // Enable update.module if this option was selected. if ($form_state['values']['update_status_module'][1]) { \Drupal::moduleHandler()->install(array('file', 'update'), FALSE); // Add the site maintenance account's email address to the list of // addresses to be notified when updates are available, if selected. if ($form_state['values']['update_status_module'][2]) { \Drupal::config('update.settings')->set('notification.emails', array($form_state['values']['account']['mail']))->save(); } } // We precreated user 1 with placeholder values. Let's save the real values. $account = user_load(1); $account->init = $account->mail = $form_state['values']['account']['mail']; $account->roles = $account->getRoles(); $account->activate(); $account->timezone = $form_state['values']['date_default_timezone']; $account->pass = $form_state['values']['account']['pass']; $account->name = $form_state['values']['account']['name']; $account->save(); // Load global $user and perform final login tasks. $account = user_load(1); user_login_finalize($account); // Record when this install ran. \Drupal::state()->set('install_time', $_SERVER['REQUEST_TIME']); }