You are here

function install_begin_request in Drupal 9

Same name and namespace in other branches
  1. 8 core/includes/install.core.inc \install_begin_request()
  2. 7 includes/install.core.inc \install_begin_request()

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.

Parameters

$class_loader: The class loader. Normally Composer's ClassLoader, as included by the front controller, but may also be decorated; e.g., \Symfony\Component\ClassLoader\ApcClassLoader.

$install_state: An array of information about the current installation state. This is modified with information gleaned from the beginning of the page request.

See also

install_drupal()

1 call to install_begin_request()
install_drupal in core/includes/install.core.inc
Installs Drupal either interactively or via an array of passed-in settings.

File

core/includes/install.core.inc, line 298
API functions for installing Drupal.

Code

function install_begin_request($class_loader, &$install_state) {
  $request = Request::createFromGlobals();

  // 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']);
  }

  // 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.
  // 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.
  $user_agent = $request->cookies
    ->get('SIMPLETEST_USER_AGENT') ?: $request->server
    ->get('HTTP_USER_AGENT');
  if ($install_state['interactive'] && strpos($user_agent, 'simpletest') !== FALSE && !drupal_valid_test_ua()) {
    header($request->server
      ->get('SERVER_PROTOCOL') . ' 403 Forbidden');
    exit;
  }
  if ($install_state['interactive'] && drupal_valid_test_ua()) {

    // Set the default timezone. While this doesn't cause any tests to fail, PHP
    // complains if 'date.timezone' is not set in php.ini. The Australia/Sydney
    // timezone is chosen so all tests are run using an edge case scenario
    // (UTC+10  and DST). This choice is made to prevent timezone related
    // regressions and reduce the fragility of the testing system in general.
    date_default_timezone_set('Australia/Sydney');
  }
  $site_path = empty($install_state['site_path']) ? DrupalKernel::findSitePath($request, FALSE) : $install_state['site_path'];
  Settings::initialize(dirname(__DIR__, 2), $site_path, $class_loader);

  // Ensure that procedural dependencies are loaded as early as possible,
  // since the error/exception handlers depend on them.
  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__ . '/form.inc';
  require_once __DIR__ . '/batch.inc';

  // Load module basics (needed for hook invokes).
  include_once __DIR__ . '/module.inc';

  // Create a minimal mocked container to support calls to t() in the pre-kernel
  // base system verification code paths below. The strings are not actually
  // used or output for these calls.
  // @todo Separate API level checks from UI-facing error messages.
  $container = new ContainerBuilder();
  $container
    ->setParameter('language.default_values', Language::$defaultValues);
  $container
    ->register('language.default', 'Drupal\\Core\\Language\\LanguageDefault')
    ->addArgument('%language.default_values%');
  $container
    ->register('string_translation', 'Drupal\\Core\\StringTranslation\\TranslationManager')
    ->addArgument(new Reference('language.default'));

  // Register the stream wrapper manager.
  $container
    ->register('stream_wrapper_manager', 'Drupal\\Core\\StreamWrapper\\StreamWrapperManager')
    ->addMethodCall('setContainer', [
    new Reference('service_container'),
  ]);
  $container
    ->register('file_system', 'Drupal\\Core\\File\\FileSystem')
    ->addArgument(new Reference('stream_wrapper_manager'))
    ->addArgument(Settings::getInstance())
    ->addArgument((new LoggerChannelFactory())
    ->get('file'));

  // Register the class loader so contrib and custom database drivers can be
  // autoloaded.
  // @see drupal_get_database_types()
  $container
    ->set('class_loader', $class_loader);
  \Drupal::setContainer($container);

  // Determine whether base system services are ready to operate.
  try {
    $sync_directory = Settings::get('config_sync_directory', FALSE);
    $install_state['config_verified'] = file_exists($sync_directory);
  } catch (Exception $e) {
    $install_state['config_verified'] = FALSE;
  }
  $install_state['database_verified'] = install_verify_database_settings($site_path);

  // A valid settings.php has database settings and a hash_salt value. Other
  // settings will be checked by system_requirements().
  $install_state['settings_verified'] = $install_state['database_verified'] && (bool) Settings::get('hash_salt', FALSE);
  if ($install_state['settings_verified']) {
    try {
      $system_schema = system_schema();
      end($system_schema);
      $table = key($system_schema);
      $install_state['base_system_verified'] = Database::getConnection()
        ->schema()
        ->tableExists($table);
    } catch (DatabaseExceptionWrapper $e) {

      // The last defined table of the base system_schema() does not exist yet.
      // $install_state['base_system_verified'] defaults to FALSE, so the code
      // following below will use the minimal installer service container.
      // As soon as the base system is verified here, the installer operates in
      // a full and regular Drupal environment, without any kind of exceptions.
    }
  }

  // Replace services with in-memory and null implementations. This kernel is
  // replaced with a regular one in drupal_install_system().
  if (!$install_state['base_system_verified']) {
    $environment = 'install';
    $GLOBALS['conf']['container_service_providers']['InstallerServiceProvider'] = 'Drupal\\Core\\Installer\\InstallerServiceProvider';
  }
  else {
    $environment = 'prod';
    $GLOBALS['conf']['container_service_providers']['InstallerServiceProvider'] = 'Drupal\\Core\\Installer\\NormalInstallerServiceProvider';
  }
  $GLOBALS['conf']['container_service_providers']['InstallerConfigOverride'] = 'Drupal\\Core\\Installer\\ConfigOverride';

  // Note, InstallerKernel::createFromRequest() is not used because Settings is
  // already initialized.
  $kernel = new InstallerKernel($environment, $class_loader, FALSE);
  $kernel::bootEnvironment();
  $kernel
    ->setSitePath($site_path);
  $kernel
    ->boot();
  $container = $kernel
    ->getContainer();

  // If Drupal is being installed behind a proxy, configure the request.
  ReverseProxyMiddleware::setSettingsOnRequest($request, Settings::getInstance());

  // Register the file translation service.
  if (isset($GLOBALS['config']['locale.settings']['translation']['path'])) {
    $directory = $GLOBALS['config']['locale.settings']['translation']['path'];
  }
  else {
    $directory = $site_path . '/files/translations';
  }

  /** @var \Drupal\Core\File\FileSystemInterface $file_system */
  $file_system = $container
    ->get('file_system');
  $container
    ->set('string_translator.file_translation', new FileTranslation($directory, $file_system));
  $container
    ->get('string_translation')
    ->addTranslator($container
    ->get('string_translator.file_translation'));

  // Add list of all available profiles to the installation state.
  $listing = new ExtensionDiscovery($container
    ->getParameter('app.root'));
  $listing
    ->setProfileDirectories([]);
  $install_state['profiles'] += $listing
    ->scan('profile');

  /** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */
  $module_list = \Drupal::service('extension.list.module');

  /** @var \Drupal\Core\Extension\ProfileExtensionList $profile_list */
  $profile_list = \Drupal::service('extension.list.profile');

  // Prime \Drupal\Core\Extension\ExtensionList::getPathname()'s static cache.
  foreach ($install_state['profiles'] as $name => $profile) {

    // Profile path is set in both module and profile lists. See
    // \Drupal\Core\Config\ConfigInstaller::getProfileStorages for example.
    $profile_list
      ->setPathname($name, $profile
      ->getPathname());
    $module_list
      ->setPathname($name, $profile
      ->getPathname());
  }
  if ($profile = _install_select_profile($install_state)) {
    $install_state['parameters']['profile'] = $profile;
    install_load_profile($install_state);
    if (isset($install_state['profile_info']['distribution']['install']['theme'])) {
      $install_state['theme'] = $install_state['profile_info']['distribution']['install']['theme'];
    }
  }

  // Before having installed the system module and being able to do a module
  // rebuild, prime the module list static cache with the system module's
  // location.
  // @todo Remove as part of https://www.drupal.org/node/2186491
  $module_list
    ->setPathname('system', 'core/modules/system/system.info.yml');

  // Use the language from profile configuration if available.
  if (!empty($install_state['config_install_path']) && $install_state['config']['system.site']) {
    $install_state['parameters']['langcode'] = $install_state['config']['system.site']['default_langcode'];
  }
  elseif (isset($install_state['profile_info']['distribution']['langcode'])) {

    // Otherwise, Use the language from the profile configuration, if available,
    // to override the language previously set in the parameters.
    $install_state['parameters']['langcode'] = $install_state['profile_info']['distribution']['langcode'];
  }

  // Set the default language to the selected language, if any.
  if (isset($install_state['parameters']['langcode'])) {
    $default_language = new Language([
      'id' => $install_state['parameters']['langcode'],
    ]);
    $container
      ->get('language.default')
      ->set($default_language);
    \Drupal::translation()
      ->setDefaultLangcode($install_state['parameters']['langcode']);
  }

  // Override the module list with a minimal set of modules.
  $module_handler = \Drupal::moduleHandler();
  if (!$module_handler
    ->moduleExists('system')) {
    $module_handler
      ->addModule('system', 'core/modules/system');
  }
  if ($profile && !$module_handler
    ->moduleExists($profile)) {
    $module_handler
      ->addProfile($profile, $install_state['profiles'][$profile]
      ->getPath());
  }

  // Load all modules and perform request related initialization.
  $kernel
    ->preHandle($request);

  // Initialize a route on this legacy request similar to
  // \Drupal\Core\DrupalKernel::prepareLegacyRequest() since normal routing
  // will not happen.
  $request->attributes
    ->set(RouteObjectInterface::ROUTE_OBJECT, new Route('<none>'));
  $request->attributes
    ->set(RouteObjectInterface::ROUTE_NAME, '<none>');

  // 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']) {

    // Do not install over an existing installation. The call to
    // install_verify_database_ready() will throw an AlreadyInstalledException
    // if this is the case.
    install_verify_database_ready();
  }

  // Verify the last completed task in the database, if there is one.
  $task = install_verify_completed_task();

  // Ensure that the active configuration is empty before installation starts.
  if ($install_state['config_verified'] && empty($task)) {
    if (count($kernel
      ->getConfigStorage()
      ->listAll())) {
      $task = NULL;
      throw new AlreadyInstalledException($container
        ->get('string_translation'));
    }
  }

  // Modify the installation state as appropriate.
  $install_state['completed_task'] = $task;
}