diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 14acf8d..6c6b5cf 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -11,6 +11,7 @@ use Drupal\Core\Installer\Exception\AlreadyInstalledException; use Drupal\Core\Installer\Exception\InstallerException; use Drupal\Core\Installer\Exception\NoProfilesException; +use Drupal\Core\Installer\Exception\TooManyDistributionsException; use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManager; @@ -1211,10 +1212,14 @@ function _install_select_profile(&$install_state) { return $profile; } } - // Check for a distribution. - if ($distribution = \Drupal::service('kernel')->getDistribution()) { - return $distribution; + // Check for a distribution. If the site has more than one distribution force + // the user to choose which will ensure that settings.php has to be written. + try { + if ($distribution = \Drupal::service('kernel')->getDistribution()) { + return $distribution; + } } + catch (TooManyDistributionsException $e) {} // Get all visible (not hidden) profiles. $visible_profiles = array_filter($install_state['profiles'], function ($profile) { @@ -2306,9 +2311,16 @@ function install_write_profile($install_state) { // However, if we're dealing with a distribution and the profile is not // writable do not write the value to settings.php if the current value is not // set. - if ($settings_value == '' && \Drupal::service('kernel')->getDistribution() && !is_writable(\Drupal::service('site.path') . '/settings.php')) { + $distribution = FALSE; + try { + $distribution = \Drupal::service('kernel')->getDistribution(); + } + catch (TooManyDistributionsException $e) {} + + if ($settings_value == '' && $distribution && !is_writable(\Drupal::service('site.path') . '/settings.php')) { $need_to_write = FALSE; } + if ($need_to_write) { // Remember the profile which was used. $settings['settings']['install_profile'] = (object) array( diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 164865e..5708e2e 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -21,6 +21,7 @@ use Drupal\Core\Extension\InfoParser; use Drupal\Core\File\MimeType\MimeTypeGuesser; use Drupal\Core\Http\TrustedHostsRequestFactory; +use Drupal\Core\Installer\Exception\TooManyDistributionsException; use Drupal\Core\Language\Language; use Drupal\Core\PageCache\RequestPolicyInterface; use Drupal\Core\PhpStorage\PhpStorageFactory; @@ -1465,13 +1466,18 @@ public function getDistribution() { $listing = new ExtensionDiscovery($this->root); $listing->setProfileDirectories(array()); $info_parser = new InfoParser(); + $distributions = []; foreach ($listing->scan('profile') as $profile) { $info = $info_parser->parse($profile->getPathname()); if (!empty($info['distribution'])) { - return $profile->getName(); + $distributions[] = $profile->getName(); } } - return NULL; + // There can be only one. + if (count($distributions) > 1) { + throw new TooManyDistributionsException('A site can only have one distribution, multiple installation profiles discovered: ' . implode(', ', $distributions)); + } + return reset($distributions); } } diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index 287102b..237eb17 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -140,8 +140,12 @@ public function loadLegacyIncludes(); * If multiple profiles are distributions, then the first discovered profile * will be selected. See https://www.drupal.org/node/2210443. * - * @return string|null - * The machine name of any discovered distribution. + * @return string|FALSE + * The machine name of any discovered distribution. FALSE if there are no + * distributions. + * + * @throws \Drupal\Core\Installer\Exception\TooManyDistributionsException + * Thrown when a site has more than one distribution installation profile. */ public function getDistribution(); diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php index 3238ce6..d05a954 100644 --- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php +++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php @@ -485,4 +485,11 @@ protected function getInfoParser() { return $this->infoParser; } + /** + * Reset the discovered files. + */ + public static function reset() { + static::$files = []; + } + } diff --git a/core/lib/Drupal/Core/Installer/Exception/TooManyDistributionsException.php b/core/lib/Drupal/Core/Installer/Exception/TooManyDistributionsException.php new file mode 100644 index 0000000..5350726 --- /dev/null +++ b/core/lib/Drupal/Core/Installer/Exception/TooManyDistributionsException.php @@ -0,0 +1,16 @@ +assertEqual(\Drupal::installProfile(), 'mydistro'); $this->assertNull(Settings::get('install_profile'), 'The install profile has not been written to settings.php.'); + + $this->rebuildContainer(); + $this->pass('Container can be rebuilt even though distribution is not written to settings.php.'); + + // Create another installation profile which is a distrubtion. + $info = array( + 'type' => 'profile', + 'core' => \Drupal::CORE_COMPATIBILITY, + 'name' => 'Distribution profile 2', + 'distribution' => array( + 'name' => 'My Distribution 2', + 'install' => array( + 'theme' => 'bartik', + ), + ), + ); + // File API functions are not available yet. + $path = $this->siteDirectory . '/profiles/mydistro2'; + mkdir($path, 0777, TRUE); + file_put_contents("$path/mydistro2.info.yml", Yaml::encode($info)); + + // Test that a site will multiple distributions will get an exception when + // rebuilding the container. In order to do this we need to reset the + // discovered files in ExtensionDiscovery. + try { + ExtensionDiscovery::reset(); + $this->rebuildContainer(); + $this->fail('TooManyDistributionsException exception thrown.'); + } + catch (TooManyDistributionsException $e) { + $this->pass('TooManyDistributionsException exception thrown.'); + $this->assertEqual('A site can only have one distribution, multiple installation profiles discovered: mydistro, mydistro2', $e->getMessage()); + } + } } diff --git a/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php b/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php new file mode 100644 index 0000000..4418c32 --- /dev/null +++ b/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php @@ -0,0 +1,106 @@ + 'profile', + 'core' => \Drupal::CORE_COMPATIBILITY, + 'name' => $name .' profile', + 'distribution' => array( + 'name' => $name, + 'install' => array( + 'theme' => 'bartik', + ), + ), + ); + // File API functions are not available yet. + $path = $this->siteDirectory . '/profiles/' . $name; + mkdir($path, 0777, TRUE); + file_put_contents("$path/$name.info.yml", Yaml::encode($info)); + } + // Install the first distribution. + $this->profile = 'distribution_one'; + + parent::setUp(); + } + + /** + * Overrides InstallerTest::setUpLanguage(). + */ + protected function setUpLanguage() { + $this->assertNoRaw('distribution_one'); + $this->assertNoRaw('distribution_two'); + // Verify that the "Choose profile" step appears. + $this->assertText('Choose profile'); + + parent::setUpLanguage(); + } + + /** + * Overrides InstallerTest::setUpProfile(). + */ + protected function setUpProfile() { + $this->assertText('distribution_one'); + $this->assertText('distribution_two'); + parent::setUpProfile(); + } + + /** + * Confirms that the installation succeeded. + */ + public function testInstalled() { + $this->assertUrl('user/1'); + $this->assertResponse(200); + // Confirm that we are logged-in after installation. + $this->assertText($this->rootUser->getUsername()); + + // Confirm that Drupal recognizes this distribution as the current profile. + $this->assertEqual(\Drupal::installProfile(), 'distribution_one'); + $this->assertEqual(Settings::get('install_profile'), 'distribution_one', 'The install profile has been written to settings.php.'); + + // Test that a site will multiple distributions will get an exception when + // calling \Drupal\Core\DrupalKernel::getDistribution(). + try { + $this->container->get('kernel')->getDistribution(); + $this->fail('TooManyDistributionsException exception thrown.'); + } + catch (TooManyDistributionsException $e) { + $this->pass('TooManyDistributionsException exception thrown.'); + } + // To mirror the test in DistributionProfileExistingSettingsTest we reset + // the discovered files in ExtensionDiscovery. + // @see \Drupal\system\Tests\Installer\DistributionProfileExistingSettingsTest::testInstalled() + ExtensionDiscovery::reset(); + $this->rebuildContainer(); + $this->pass('Container can be rebuilt as distribution is written to settings.php.'); + } + +}