diff --git a/core/lib/Drupal/Core/Recipe/Recipe.php b/core/lib/Drupal/Core/Recipe/Recipe.php index 89e7b0ed742de6e452dee26a1a1e527be99d562d..c9f5f055932c9ec98bb5a403d4c580105862a56c 100644 --- a/core/lib/Drupal/Core/Recipe/Recipe.php +++ b/core/lib/Drupal/Core/Recipe/Recipe.php @@ -383,14 +383,23 @@ private static function validateConfigActions(mixed $value, ExecutionContextInte $configurator = new RecipeConfigurator($recipe_being_validated['recipes'] ?? [], $include_path); + /** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */ + $module_list = \Drupal::service('extension.list.module'); // The config provider must either be an already-installed module or theme, // or an extension being installed by this recipe or a recipe it depends on. $all_extensions = [ - ...array_keys(\Drupal::service('extension.list.module')->getAllInstalledInfo()), + ...array_keys($module_list->getAllInstalledInfo()), ...array_keys(\Drupal::service('extension.list.theme')->getAllInstalledInfo()), ...$recipe_being_validated['install'] ?? [], ...$configurator->listAllExtensions(), ]; + // Explicitly treat required modules as installed, even if Drupal isn't + // installed yet, because we know they WILL be installed. + foreach ($module_list->getAllAvailableInfo() as $name => $info) { + if (!empty($info['required'])) { + $all_extensions[] = $name; + } + } if (!in_array($config_provider, $all_extensions, TRUE)) { $context->addViolation('Config actions cannot be applied to %config_name because the %config_provider extension is not installed, and is not installed by this recipe or any of the recipes it depends on.', [ diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/RecipeTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/RecipeTest.php index 9db4abe5709ce80c4d80463ce66052d0fc1ea2fd..b63861276ff34092cfb8ddc522fa3a2a03d99144 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/RecipeTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/RecipeTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Recipe\RecipeFileException; use Drupal\Core\Recipe\RecipePreExistingConfigException; use Drupal\Core\Recipe\RecipeRunner; +use Drupal\FunctionalTests\Core\Recipe\RecipeTestTrait; use Drupal\KernelTests\KernelTestBase; /** @@ -16,6 +17,8 @@ */ class RecipeTest extends KernelTestBase { + use RecipeTestTrait; + /** * {@inheritdoc} */ @@ -80,4 +83,19 @@ public function testExampleRecipe(): void { $this->assertSame($this->config('text.settings')->get('default_summary_length'), 700); } + public function testImplicitlyRequiredModule(): void { + $this->disableModules(['user']); + $recipe = $this->createRecipe([ + 'name' => 'Actions on config from required module', + 'config' => [ + 'actions' => [ + 'user.role.authenticated' => [ + 'grantPermission' => 'access administration pages', + ], + ], + ], + ]); + $this->assertIsObject($recipe); + } + }