diff --git a/automatic_updates_extensions/automatic_updates_extensions.services.yml b/automatic_updates_extensions/automatic_updates_extensions.services.yml index a8cadd779827b68688099d410b1ce355a9951dbe..17f0095f3ee299aebe9ede9f32162bd0077aa95c 100644 --- a/automatic_updates_extensions/automatic_updates_extensions.services.yml +++ b/automatic_updates_extensions/automatic_updates_extensions.services.yml @@ -17,6 +17,14 @@ services: - '@string_translation' tags: - { name: event_subscriber } + automatic_updates_extensions.validator.packages_type: + class: Drupal\automatic_updates_extensions\Validator\UpdatePackagesTypeValidator + arguments: + - '@string_translation' + - '@extension.list.module' + - '@extension.list.theme' + tags: + - { name: event_subscriber } automatic_updates_extensions.validator.target_release: class: Drupal\automatic_updates_extensions\Validator\UpdateReleaseValidator tags: diff --git a/automatic_updates_extensions/src/Validator/UpdatePackagesTypeValidator.php b/automatic_updates_extensions/src/Validator/UpdatePackagesTypeValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..0d593a0ddfd7ff7562f3ca93af6e2befbe35ff72 --- /dev/null +++ b/automatic_updates_extensions/src/Validator/UpdatePackagesTypeValidator.php @@ -0,0 +1,94 @@ +<?php + +namespace Drupal\automatic_updates_extensions\Validator; + +use Drupal\automatic_updates_extensions\ExtensionUpdater; +use Drupal\Core\Extension\Extension; +use Drupal\Core\Extension\ModuleExtensionList; +use Drupal\Core\Extension\ThemeExtensionList; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Core\Utility\ProjectInfo; +use Drupal\package_manager\Event\PreCreateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Validates the type of updated packages. + */ +class UpdatePackagesTypeValidator implements EventSubscriberInterface { + + use StringTranslationTrait; + + /** + * The module list service. + * + * @var \Drupal\Core\Extension\ModuleExtensionList + */ + protected $moduleList; + + /** + * The theme list service. + * + * @var \Drupal\Core\Extension\ThemeExtensionList + */ + protected $themeList; + + /** + * Constructs a UpdatePackagesTypeValidator object. + * + * @param \Drupal\Core\StringTranslation\TranslationInterface $translation + * The translation service. + * @param \Drupal\Core\Extension\ModuleExtensionList $module_list + * The module list service. + * @param \Drupal\Core\Extension\ThemeExtensionList $theme_list + * The theme list service. + */ + public function __construct(TranslationInterface $translation, ModuleExtensionList $module_list, ThemeExtensionList $theme_list) { + $this->setStringTranslation($translation); + $this->moduleList = $module_list; + $this->themeList = $theme_list; + } + + /** + * Validates that updated packages are only modules or themes. + * + * @param \Drupal\package_manager\Event\PreCreateEvent $event + * The event object. + */ + public function checkPackagesAreOnlyThemesOrModules(PreCreateEvent $event): void { + $stage = $event->getStage(); + if ($stage instanceof ExtensionUpdater) { + $package_versions = $stage->getPackageVersions(); + $invalid_projects = []; + $extension_list = array_merge($this->themeList->getList(), $this->moduleList->getList()); + $project_info = new ProjectInfo(); + $all_projects = array_map( + function (Extension $extension) use ($project_info): string { + return $project_info->getProjectName($extension); + }, + $extension_list + ); + foreach (['production', 'dev'] as $package_type) { + foreach ($package_versions[$package_type] as $package => $version) { + $update_project = str_replace('drupal/', '', $package); + if ($update_project === 'drupal' || !in_array($update_project, $all_projects)) { + $invalid_projects[] = $update_project; + } + } + } + if ($invalid_projects) { + $event->addError($invalid_projects, $this->t('Only Drupal Modules or Drupal Themes can be updated, therefore the following projects cannot be updated:')); + } + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'checkPackagesAreOnlyThemesOrModules', + ]; + } + +} diff --git a/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php b/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php index fd5cc781e88148719ed78d52c2f67d2f8992b19c..e664d5d5c33f4e873ad206e87d379209fe876632 100644 --- a/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php +++ b/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php @@ -28,7 +28,11 @@ class ExtensionUpdaterTest extends AutomaticUpdatesKernelTestBase { * {@inheritdoc} */ protected function setUp(): void { + // This test doesn't need to validate that the test projects used are in the + // codebase. Therefore, we need to disable the following validators that + // require real Drupal projects. $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release'; + $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_type'; parent::setUp(); $this->installEntitySchema('user'); } diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php index 6f8b88b45f51c4a8b6779ee4156055c021ddb349..ae19a6db45d4274426ff703c60fb29fd41da81cc 100644 --- a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php +++ b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php @@ -31,6 +31,10 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi // secure and supported. Therefore, we need to disable the update release // validator that validates updated projects are secure and supported. $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release'; + // In this test, we don't focus on validating that the updated projects are + // only themes or modules. Therefore, we need to disable the update packages + // type validator. + $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_type'; parent::setUp(); $this->createTestProject(); $this->activeDir = $this->container->get('package_manager.path_locator') diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..95fc564bcb55e12db0bb38b446f3de7ca38ce322 --- /dev/null +++ b/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php @@ -0,0 +1,84 @@ +<?php + +namespace Drupal\Tests\automatic_updates_extensions\Kernel\Validator; + +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\ValidationResult; +use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsKernelTestBase; + +/** + * Validates the type of updated packages. + * + * @coversDefaultClass \Drupal\automatic_updates_extensions\Validator\UpdatePackagesTypeValidator + * + * @group automatic_updates_extensions + */ +class UpdatePackagesTypeValidatorTest extends AutomaticUpdatesExtensionsKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // In this test, we don't focus on validating that the updated projects are + // secure and supported. Therefore, we need to disable the update release + // validator that validates updated projects are secure and supported. + $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release'; + $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_installed_with_composer'; + parent::setUp(); + $this->createTestProject(); + } + + /** + * Data provider for testUpdatePackagesAreOnlyThemesOrModules(). + * + * @return array + * Test cases for testUpdatePackagesAreOnlyThemesOrModules(). + */ + public function providerUpdatePackagesAreOnlyThemesOrModules(): array { + return [ + 'non existing project updated' => [ + [ + 'non_existing_project' => '9.8.1', + ], + [ValidationResult::createError(['non_existing_project'], t('Only Drupal Modules or Drupal Themes can be updated, therefore the following projects cannot be updated:'))], + ], + 'non existing project, test module and test theme updated' => [ + [ + 'non_existing_project' => '9.8.1', + 'test_module_project' => '9.8.1', + 'test_theme_project' => '9.8.1', + ], + [ValidationResult::createError(['non_existing_project'], t('Only Drupal Modules or Drupal Themes can be updated, therefore the following projects cannot be updated:'))], + ], + 'drupal updated' => [ + [ + 'drupal' => '9.8.1', + ], + [ValidationResult::createError(['drupal'], t('Only Drupal Modules or Drupal Themes can be updated, therefore the following projects cannot be updated:'))], + ], + ]; + } + + /** + * Tests the packages installed with composer during pre-create. + * + * @param array $projects + * The projects to install. + * @param array $expected_results + * The expected validation results. + * + * @dataProvider providerUpdatePackagesAreOnlyThemesOrModules + */ + public function testUpdatePackagesAreOnlyThemesOrModules(array $projects, array $expected_results): void { + $module_info = ['project' => 'test_module_project']; + $this->config('update_test.settings') + ->set("system_info.aaa_automatic_updates_test", $module_info) + ->save(); + $theme_info = ['project' => 'test_theme_project']; + $this->config('update_test.settings') + ->set("system_info.automatic_updates_theme", $theme_info) + ->save(); + $this->assertUpdateResults($projects, $expected_results, PreCreateEvent::class); + } + +}