Loading core/includes/install.core.inc +7 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Database\DatabaseExceptionWrapper; use Drupal\Core\Extension\Exception\UnknownExtensionException; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Extension\Requirement\RequirementSeverity; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormState; Loading @@ -32,7 +33,6 @@ use Drupal\Core\Site\Settings; use Drupal\Core\StringTranslation\Translator\FileTranslation; use Drupal\Core\StackMiddleware\ReverseProxyMiddleware; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Url; use Drupal\language\ConfigurableLanguageManagerInterface; Loading Loading @@ -423,6 +423,12 @@ function install_begin_request($class_loader, &$install_state): void { // already initialized. $kernel = new InstallerKernel($environment, $class_loader, FALSE); $kernel::bootEnvironment(); // Add list of all available profiles to the installation state. $listing = new ExtensionDiscovery($kernel->getAppRoot(), FALSE); $listing->setProfileDirectories([]); $install_state['profiles'] += $listing->scan('profile'); $kernel->setSitePath($site_path); $kernel->boot(); // Get the new version of the container from the kernel. This is now a Loading @@ -444,11 +450,6 @@ function install_begin_request($class_loader, &$install_state): void { $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 */ Loading core/lib/Drupal/Core/Installer/InstallerKernel.php +119 −3 Original line number Diff line number Diff line Loading @@ -3,6 +3,9 @@ namespace Drupal\Core\Installer; use Drupal\Core\DrupalKernel; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Extension\InfoParser; use Symfony\Component\DependencyInjection\ContainerInterface; /** Loading Loading @@ -102,13 +105,126 @@ protected function attachSynthetic(ContainerInterface $container): void { */ protected function getExtensions(): array { $extensions = parent::getExtensions() ?: []; if (!static::installationAttempted()) { return $extensions; } // Ensure that the System module is always available to the installer. $extensions['module']['system'] ??= 0; if (empty($extensions['profile']) && !empty($GLOBALS['install_state']) && ($profile = _install_select_profile($GLOBALS['install_state']))) { $extensions['profile'] = $profile; $extensions['module'][$profile] = 1000; if ($profile = $this->installGetProfile()) { $extensions['profile'] = $profile->getName(); if (!isset($extensions['module'][$profile->getName()])) { $extensions['module'][$profile->getName()] = 1000; } $theme = $profile->info['distribution']['install']['theme'] ?? 'claro'; $extensions['theme'][$theme] ??= 0; if ($theme !== 'claro') { // Need to check for base themes. foreach ($this->getBaseThemes($profile, $theme) as $base_theme) { $extensions['theme'][$base_theme] ??= 0; } } } // Ensure that the default theme is always available to the installer. else { $extensions['theme']['claro'] ??= 0; } return $extensions; } /** * Gets the base themes for a given theme. * * @param \Drupal\Core\Extension\Extension $profile * The profile being installed. * @param string $theme * The theme for installation. * * @return string[] * A list of base themes. */ private function getBaseThemes(Extension $profile, string $theme): array { $base_themes = []; // Find all the available themes. $listing = new ExtensionDiscovery($this->root); $listing->setProfileDirectories([$profile->getName() => $profile->getPath()]); $themes = $listing->scan('theme'); $info_parser = new InfoParser($this->root); $theme_info = $info_parser->parse($themes[$theme]->getPathname()); $base_theme = $theme_info['base theme'] ?? FALSE; while ($base_theme) { $base_themes[] = $base_theme; $theme_info = $info_parser->parse($themes[$base_theme]->getPathname()); $base_theme = $theme_info['base theme'] ?? FALSE; } return $base_themes; } /** * Gets the profile to be installed. * * @return string|null|\Drupal\Core\Extension\Extension * Returns NULL if no profile was selected or FALSE if the site has no * profile, or the profile extension object with the profile info added. * * @see _install_select_profile() */ private function installGetProfile(): null|false|Extension { global $install_state; $profile = NULL; if (empty($install_state['profiles'])) { throw new \RuntimeException('No profiles found.'); } // If there is only one profile available it will always be the one // selected. if (count($install_state['profiles']) == 1) { $profile = reset($install_state['profiles']); } // If a valid profile has already been selected, return the selection. if (array_key_exists('profile', $install_state['parameters'])) { $profile = $install_state['parameters']['profile']; if ($profile && isset($install_state['profiles'][$profile])) { $profile = $install_state['profiles'][$profile]; } } // Not using a profile. if ($profile === FALSE) { return $profile; } $info_parser = new InfoParser($this->root); if ($profile instanceof Extension) { $profile->info = $info_parser->parse($profile->getPathname()); return $profile; } $visible_profiles = []; // If any of the profiles are distribution profiles, select the first one. foreach ($install_state['profiles'] as $profile) { $profile->info = $info_parser->parse($profile->getPathname()); if (!empty($profile->info['distribution'])) { return $profile; } if (!isset($profile->info['hidden']) || !$profile->info['hidden']) { $visible_profiles[] = $profile; } } // If there is only one visible profile, select it. if (count($visible_profiles) == 1) { return $visible_profiles[0]; } return NULL; } } core/modules/system/tests/themes/test_installer_theme/test_installer_theme.info.yml 0 → 100644 +5 −0 Original line number Diff line number Diff line name: 'Custom installer theme' type: theme description: 'A custom installer theme to test that the installer can be themed.' base theme: claro version: VERSION core/modules/system/tests/themes/test_installer_theme/test_installer_theme.theme 0 → 100644 +15 −0 Original line number Diff line number Diff line <?php /** * @file * Contains hooks for a custom installer theme. */ declare(strict_types=1); /** * Implements hook_form_FORM_ID_alter(). */ function test_installer_theme_form_install_select_language_form_alter(array &$form): void { $form['function_name']['#markup'] = 'Added by custom installer theme.'; } core/tests/Drupal/FunctionalTests/Installer/DistributionProfileTest.php +7 −3 Original line number Diff line number Diff line Loading @@ -36,7 +36,7 @@ protected function prepareEnvironment(): void { 'distribution' => [ 'name' => 'My Distribution', 'install' => [ 'theme' => 'claro', 'theme' => 'test_installer_theme', 'finish_url' => '/root-user', ], ], Loading @@ -56,8 +56,12 @@ protected function setUpLanguage(): void { $this->assertSession()->pageTextContains($this->info['distribution']['name']); // Verify that the distribution name is used in the site title. $this->assertSession()->titleEquals('Choose language | ' . $this->info['distribution']['name']); // Verify that the requested theme is used. $this->assertSession()->responseContains($this->info['distribution']['install']['theme']); // Verify that the requested theme is used -- its modifications to this form // should be visible. $this->assertSession()->pageTextContains('Added by custom installer theme.'); // Verify the base theme CSS is loaded. $this->assertSession()->responseContains("claro/css/theme/install-page.css"); // Verify that the "Choose profile" step does not appear. $this->assertSession()->pageTextNotContains('profile'); Loading Loading
core/includes/install.core.inc +7 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Database\DatabaseExceptionWrapper; use Drupal\Core\Extension\Exception\UnknownExtensionException; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Extension\Requirement\RequirementSeverity; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormState; Loading @@ -32,7 +33,6 @@ use Drupal\Core\Site\Settings; use Drupal\Core\StringTranslation\Translator\FileTranslation; use Drupal\Core\StackMiddleware\ReverseProxyMiddleware; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Url; use Drupal\language\ConfigurableLanguageManagerInterface; Loading Loading @@ -423,6 +423,12 @@ function install_begin_request($class_loader, &$install_state): void { // already initialized. $kernel = new InstallerKernel($environment, $class_loader, FALSE); $kernel::bootEnvironment(); // Add list of all available profiles to the installation state. $listing = new ExtensionDiscovery($kernel->getAppRoot(), FALSE); $listing->setProfileDirectories([]); $install_state['profiles'] += $listing->scan('profile'); $kernel->setSitePath($site_path); $kernel->boot(); // Get the new version of the container from the kernel. This is now a Loading @@ -444,11 +450,6 @@ function install_begin_request($class_loader, &$install_state): void { $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 */ Loading
core/lib/Drupal/Core/Installer/InstallerKernel.php +119 −3 Original line number Diff line number Diff line Loading @@ -3,6 +3,9 @@ namespace Drupal\Core\Installer; use Drupal\Core\DrupalKernel; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Extension\InfoParser; use Symfony\Component\DependencyInjection\ContainerInterface; /** Loading Loading @@ -102,13 +105,126 @@ protected function attachSynthetic(ContainerInterface $container): void { */ protected function getExtensions(): array { $extensions = parent::getExtensions() ?: []; if (!static::installationAttempted()) { return $extensions; } // Ensure that the System module is always available to the installer. $extensions['module']['system'] ??= 0; if (empty($extensions['profile']) && !empty($GLOBALS['install_state']) && ($profile = _install_select_profile($GLOBALS['install_state']))) { $extensions['profile'] = $profile; $extensions['module'][$profile] = 1000; if ($profile = $this->installGetProfile()) { $extensions['profile'] = $profile->getName(); if (!isset($extensions['module'][$profile->getName()])) { $extensions['module'][$profile->getName()] = 1000; } $theme = $profile->info['distribution']['install']['theme'] ?? 'claro'; $extensions['theme'][$theme] ??= 0; if ($theme !== 'claro') { // Need to check for base themes. foreach ($this->getBaseThemes($profile, $theme) as $base_theme) { $extensions['theme'][$base_theme] ??= 0; } } } // Ensure that the default theme is always available to the installer. else { $extensions['theme']['claro'] ??= 0; } return $extensions; } /** * Gets the base themes for a given theme. * * @param \Drupal\Core\Extension\Extension $profile * The profile being installed. * @param string $theme * The theme for installation. * * @return string[] * A list of base themes. */ private function getBaseThemes(Extension $profile, string $theme): array { $base_themes = []; // Find all the available themes. $listing = new ExtensionDiscovery($this->root); $listing->setProfileDirectories([$profile->getName() => $profile->getPath()]); $themes = $listing->scan('theme'); $info_parser = new InfoParser($this->root); $theme_info = $info_parser->parse($themes[$theme]->getPathname()); $base_theme = $theme_info['base theme'] ?? FALSE; while ($base_theme) { $base_themes[] = $base_theme; $theme_info = $info_parser->parse($themes[$base_theme]->getPathname()); $base_theme = $theme_info['base theme'] ?? FALSE; } return $base_themes; } /** * Gets the profile to be installed. * * @return string|null|\Drupal\Core\Extension\Extension * Returns NULL if no profile was selected or FALSE if the site has no * profile, or the profile extension object with the profile info added. * * @see _install_select_profile() */ private function installGetProfile(): null|false|Extension { global $install_state; $profile = NULL; if (empty($install_state['profiles'])) { throw new \RuntimeException('No profiles found.'); } // If there is only one profile available it will always be the one // selected. if (count($install_state['profiles']) == 1) { $profile = reset($install_state['profiles']); } // If a valid profile has already been selected, return the selection. if (array_key_exists('profile', $install_state['parameters'])) { $profile = $install_state['parameters']['profile']; if ($profile && isset($install_state['profiles'][$profile])) { $profile = $install_state['profiles'][$profile]; } } // Not using a profile. if ($profile === FALSE) { return $profile; } $info_parser = new InfoParser($this->root); if ($profile instanceof Extension) { $profile->info = $info_parser->parse($profile->getPathname()); return $profile; } $visible_profiles = []; // If any of the profiles are distribution profiles, select the first one. foreach ($install_state['profiles'] as $profile) { $profile->info = $info_parser->parse($profile->getPathname()); if (!empty($profile->info['distribution'])) { return $profile; } if (!isset($profile->info['hidden']) || !$profile->info['hidden']) { $visible_profiles[] = $profile; } } // If there is only one visible profile, select it. if (count($visible_profiles) == 1) { return $visible_profiles[0]; } return NULL; } }
core/modules/system/tests/themes/test_installer_theme/test_installer_theme.info.yml 0 → 100644 +5 −0 Original line number Diff line number Diff line name: 'Custom installer theme' type: theme description: 'A custom installer theme to test that the installer can be themed.' base theme: claro version: VERSION
core/modules/system/tests/themes/test_installer_theme/test_installer_theme.theme 0 → 100644 +15 −0 Original line number Diff line number Diff line <?php /** * @file * Contains hooks for a custom installer theme. */ declare(strict_types=1); /** * Implements hook_form_FORM_ID_alter(). */ function test_installer_theme_form_install_select_language_form_alter(array &$form): void { $form['function_name']['#markup'] = 'Added by custom installer theme.'; }
core/tests/Drupal/FunctionalTests/Installer/DistributionProfileTest.php +7 −3 Original line number Diff line number Diff line Loading @@ -36,7 +36,7 @@ protected function prepareEnvironment(): void { 'distribution' => [ 'name' => 'My Distribution', 'install' => [ 'theme' => 'claro', 'theme' => 'test_installer_theme', 'finish_url' => '/root-user', ], ], Loading @@ -56,8 +56,12 @@ protected function setUpLanguage(): void { $this->assertSession()->pageTextContains($this->info['distribution']['name']); // Verify that the distribution name is used in the site title. $this->assertSession()->titleEquals('Choose language | ' . $this->info['distribution']['name']); // Verify that the requested theme is used. $this->assertSession()->responseContains($this->info['distribution']['install']['theme']); // Verify that the requested theme is used -- its modifications to this form // should be visible. $this->assertSession()->pageTextContains('Added by custom installer theme.'); // Verify the base theme CSS is loaded. $this->assertSession()->responseContains("claro/css/theme/install-page.css"); // Verify that the "Choose profile" step does not appear. $this->assertSession()->pageTextNotContains('profile'); Loading