diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php index 0be6732acac933ad3209bc835eecc696dfb90797..f91671d3f9ab24478935ce458d082f214463789f 100644 --- a/src/Controller/InstallerController.php +++ b/src/Controller/InstallerController.php @@ -8,24 +8,29 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Url; use Drupal\package_manager\Exception\StageException; +use Drupal\package_manager\StatusCheckTrait; use Drupal\project_browser\ActivatorInterface; use Drupal\project_browser\ComposerInstaller\Installer; use Drupal\project_browser\EnabledSourceHandler; use Drupal\project_browser\InstallState; use Drupal\project_browser\ProjectBrowser\Project; use Drupal\project_browser_test\Plugin\ProjectBrowserSource\ProjectBrowserTestMock; +use Drupal\system\SystemManager; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Defines a controller to install projects via UI. */ final class InstallerController extends ControllerBase { + use StatusCheckTrait; + /** * No require or install in progress for a given module. * @@ -61,6 +66,7 @@ final class InstallerController extends ControllerBase { private readonly LoggerInterface $logger, private readonly ActivatorInterface $activator, private readonly InstallState $installState, + private readonly EventDispatcherInterface $eventDispatcher, ) {} /** @@ -78,6 +84,7 @@ final class InstallerController extends ControllerBase { $container->get('logger.channel.project_browser'), $container->get(ActivatorInterface::class), $container->get(InstallState::class), + $container->get(EventDispatcherInterface::class), ); } @@ -323,6 +330,16 @@ final class InstallerController extends ControllerBase { return $this->lockedResponse($message, $unlock_url); } + // Ensure the environment is ready to use Package Manager. + ['errors' => $errors, 'warnings' => $warnings] = $this->validatePackageManager(); + if ($warnings) { + $this->logger->warning(implode("\n", $warnings)); + } + if ($errors) { + $error_message = '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>'; + return $this->errorResponse(new StageException($this->installer, $error_message)); + } + try { $stage_id = $this->installer->create(); } @@ -464,4 +481,30 @@ final class InstallerController extends ControllerBase { return $response ?? new JsonResponse(['status' => 0]); } + /** + * Checks if the environment meets Package Manager install requirements. + * + * @return array[] + * An array with two sub-elements: + * - errors: The validation messages with an "error" severity. + * - warnings: All other validation messages, which are probably warnings. + */ + private function validatePackageManager(): array { + $results = [ + 'errors' => [], + 'warnings' => [], + ]; + foreach ($this->runStatusCheck($this->installer, $this->eventDispatcher) as $result) { + $group = $result->severity === SystemManager::REQUIREMENT_ERROR + ? 'errors' + : 'warnings'; + + if ($result->summary) { + $results[$group][] = $result->summary; + } + $results[$group] = array_merge($results[$group], $result->messages); + } + return $results; + } + } diff --git a/src/Element/ProjectBrowser.php b/src/Element/ProjectBrowser.php index 00d0dee06d5e4b5570747a4878692213fb96081e..254d547618533c20dd38c9ee5a726931fd49cc88 100644 --- a/src/Element/ProjectBrowser.php +++ b/src/Element/ProjectBrowser.php @@ -13,7 +13,6 @@ use Drupal\Core\Render\Attribute\RenderElement; use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\ElementInterface; use Drupal\Core\Render\Element\RenderElementBase; -use Drupal\project_browser\InstallReadiness; use Drupal\project_browser\Plugin\ProjectBrowserSourceInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -30,7 +29,6 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn public function __construct( private readonly string $pluginId, private readonly mixed $pluginDefinition, - private readonly ?InstallReadiness $installReadiness, private readonly ModuleHandlerInterface $moduleHandler, private readonly ConfigFactoryInterface $configFactory, private readonly UuidInterface $uuid, @@ -55,13 +53,9 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { - $install_readiness = $container->get(InstallReadiness::class, ContainerInterface::NULL_ON_INVALID_REFERENCE); - assert(is_null($install_readiness) || $install_readiness instanceof InstallReadiness); - return new static( $plugin_id, $plugin_definition, - $install_readiness, $container->get(ModuleHandlerInterface::class), $container->get(ConfigFactoryInterface::class), $container->get(UuidInterface::class), @@ -127,25 +121,11 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn if (is_int($max_selections) && $max_selections <= 0) { throw new \InvalidArgumentException('$max_selections must be a positive integer or NULL.'); } - - $package_manager = [ - 'available' => $this->configFactory->get('project_browser.admin_settings')->get('allow_ui_install') && is_object($this->installReadiness), - 'errors' => [], - 'warnings' => [], - 'status_checked' => FALSE, - ]; - // @todo Fix https://www.drupal.org/node/3494512 to avoid adding - // hard-coded values. #techdebt - if ($source->getPluginId() !== 'recipes' && $package_manager['available'] && is_object($this->installReadiness)) { - $package_manager = array_merge($package_manager, $this->installReadiness->validatePackageManager()); - $package_manager['status_checked'] = TRUE; - } - $sort_options = $source->getSortOptions(); return [ 'module_path' => $this->moduleHandler->getModule('project_browser')->getPath(), 'default_plugin_id' => $source->getPluginId(), - 'package_manager' => $package_manager, + 'package_manager' => $this->configFactory->get('project_browser.admin_settings')->get('allow_ui_install') && $this->moduleHandler->moduleExists('package_manager'), 'max_selections' => $max_selections, 'current_path' => '/' . $this->currentPath->getPath(), 'instances' => [ diff --git a/src/InstallReadiness.php b/src/InstallReadiness.php deleted file mode 100644 index c3e6ae0407e48e205821570a2dbeb8e080c15058..0000000000000000000000000000000000000000 --- a/src/InstallReadiness.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -namespace Drupal\project_browser; - -use Drupal\package_manager\StatusCheckTrait; -use Drupal\project_browser\ComposerInstaller\Installer; -use Drupal\system\SystemManager; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; - -/** - * Defines Installer service. - */ -class InstallReadiness { - - use StatusCheckTrait; - - public function __construct( - private readonly Installer $installer, - private readonly EventDispatcherInterface $eventDispatcher, - ) {} - - /** - * Checks if the environment meets Package Manager install requirements. - * - * @return array[] - * errors - an array of messages with severity 2 - * messages - all other messages below severity 2 (warnings) - */ - public function validatePackageManager() { - $errors = []; - $warnings = []; - foreach ($this->runStatusCheck($this->installer, $this->eventDispatcher) as $result) { - $messages = $result->messages; - $summary = $result->summary; - if ($summary) { - array_unshift($messages, $summary); - } - $text = implode("\n", $messages); - - if ($result->severity === SystemManager::REQUIREMENT_ERROR) { - $errors[] = $text; - } - else { - $warnings[] = $text; - } - } - return [ - 'errors' => $errors, - 'warnings' => $warnings, - ]; - } - - /** - * Checks if the installer is available. - * - * @return bool - * If the installer is currently available. - */ - public function installerAvailable() { - return $this->installer->isAvailable(); - } - -} diff --git a/src/ProjectBrowserServiceProvider.php b/src/ProjectBrowserServiceProvider.php index 595a6a4d922cf210a188cc7dd3bc03c330476c50..1dcd23e1d11eff1b86d7a8d5da97ccabec0fa513 100644 --- a/src/ProjectBrowserServiceProvider.php +++ b/src/ProjectBrowserServiceProvider.php @@ -41,9 +41,6 @@ class ProjectBrowserServiceProvider extends ServiceProviderBase { ->setArgument('$keyValueFactory', new Reference('keyvalue')) ->setAutowired(TRUE); - $container->register(InstallReadiness::class, InstallReadiness::class) - ->setAutowired(TRUE); - $container->register(CoreNotUpdatedValidator::class, CoreNotUpdatedValidator::class) ->addTag('event_subscriber') ->setAutowired(TRUE); diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 8027f477d5716190e8c5bc1eaeb350d90a124a48..252132cb62651d7b2b0f2dfc5fdd969dafb7d84e 100644 Binary files a/sveltejs/public/build/bundle.js and b/sveltejs/public/build/bundle.js differ diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 12e6f84478a3785c493a3b22f5cc6b21035b4c04..da849d5c90149ad2b157ea1e813ac8bb8ebbf0e5 100644 Binary files a/sveltejs/public/build/bundle.js.map and b/sveltejs/public/build/bundle.js.map differ diff --git a/sveltejs/src/DetailModal.svelte b/sveltejs/src/DetailModal.svelte index aa530ab202d79b3ecc6d80b88dd07bfb7e612d22..3744187dc0e795617ad2b5f91cc1772c3d983d46 100644 --- a/sveltejs/src/DetailModal.svelte +++ b/sveltejs/src/DetailModal.svelte @@ -58,7 +58,7 @@ </div> <div class="pb-detail-modal__sidebar"> <!-- <Image sources={project.logo} class="pb-detail-modal__project-logo" /> --> - {#if PACKAGE_MANAGER.available} + {#if PACKAGE_MANAGER} <div class="pb-detail-modal__view-commands pb-detail-modal__sidebar_element" > diff --git a/sveltejs/src/Project/ActionButton.svelte b/sveltejs/src/Project/ActionButton.svelte index e00da38a34abe07182c1be4b31d59fbe9ce5a4ed..28f59602c9e040ba30dbc7db08981fd60f459844 100644 --- a/sveltejs/src/Project/ActionButton.svelte +++ b/sveltejs/src/Project/ActionButton.svelte @@ -67,7 +67,7 @@ </ProjectStatusIndicator> {:else} <span> - {#if PACKAGE_MANAGER.available && PACKAGE_MANAGER.errors.length === 0} + {#if PACKAGE_MANAGER} {#if isInInstallList && !processMultipleProjects} <ProjectButtonBase> <LoadingEllipsis /> diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index b525b10790e5ad698007e97970d97b22f3b09c40..ab0c555b5b055d7248c155e6c3d315d07f2e541a 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -118,28 +118,6 @@ if (data[source].error && data[source].error.length) { messenger.add(data[source].error, { type: 'error' }); } - - if ( - PACKAGE_MANAGER.available && - (PACKAGE_MANAGER.errors.length || PACKAGE_MANAGER.warnings.length) - ) { - if (PACKAGE_MANAGER.errors.length) { - PACKAGE_MANAGER.errors.forEach((e) => { - messenger.add(`Unable to download modules via the UI: ${e}`, { - type: 'error', - }); - }); - } - - if (PACKAGE_MANAGER.warnings.length) { - PACKAGE_MANAGER.warnings.forEach((e) => { - messenger.add( - `There may be issues which effect downloading modules: ${e}`, - { type: 'warning' }, - ); - }); - } - } } else { rows = []; rowsCount = 0; diff --git a/sveltejs/src/ProjectGrid.svelte b/sveltejs/src/ProjectGrid.svelte index 351f46e0c137ef657f039938f038735b282052da..cd2898b2dfa873d90f9bbedf0604ce56a426314a 100644 --- a/sveltejs/src/ProjectGrid.svelte +++ b/sveltejs/src/ProjectGrid.svelte @@ -72,7 +72,7 @@ > <slot rows={visibleRows} /> </ul> - {#if PACKAGE_MANAGER.available && processMultipleProjects} + {#if PACKAGE_MANAGER && processMultipleProjects} <ProcessInstallListButton /> {/if} {/if} diff --git a/tests/modules/project_browser_test/src/ProjectBrowserTestServiceProvider.php b/tests/modules/project_browser_test/src/ProjectBrowserTestServiceProvider.php index d70305429cc4e78c08bb7cfc00eacbb2b669b48b..91b9b303888087736ff84d47b4a4c20538b5e9f1 100644 --- a/tests/modules/project_browser_test/src/ProjectBrowserTestServiceProvider.php +++ b/tests/modules/project_browser_test/src/ProjectBrowserTestServiceProvider.php @@ -6,7 +6,6 @@ namespace Drupal\project_browser_test; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderBase; -use Drupal\project_browser\InstallReadiness; /** * Overrides the module installer service. @@ -17,9 +16,8 @@ class ProjectBrowserTestServiceProvider extends ServiceProviderBase { * {@inheritdoc} */ public function alter(ContainerBuilder $container): void { - // The InstallReadiness service is defined by ProjectBrowserServiceProvider - // if Package Manager is installed. - if ($container->hasDefinition(InstallReadiness::class)) { + assert(is_array($container->getParameter('container.modules'))); + if (array_key_exists('package_manager', $container->getParameter('container.modules'))) { $container->register(TestInstallReadiness::class, TestInstallReadiness::class) ->setAutowired(TRUE) ->addTag('event_subscriber'); diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php index d7f459c28bfcdf8b3bf43810b143bf4fef545c13..b3dadd9ced80a7386e50a48ff745b1f7e78c7fea 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php @@ -40,6 +40,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { 'package_manager_test_validation', 'project_browser', 'project_browser_test', + 'dblog', ]; /** @@ -67,6 +68,7 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $this->drupalLogin($this->drupalCreateUser([ 'administer modules', 'administer site configuration', + 'access site reports', ])); } @@ -294,14 +296,17 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $assert_session = $this->assertSession(); $this->drupalGet('admin/modules/browse/project_browser_test_mock'); - $settings = $this->getDrupalSettings(); - $this->assertTrue($settings['project_browser']['package_manager']['status_checked']); $this->svelteInitHelper('text', 'Cream cheese on a bagel'); - $assert_session->statusMessageContains("Simulate an error message for the project browser.", 'error'); $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)'; + $download_button = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button.pb__action_button"); + $this->assertNotEmpty($download_button); + $this->assertSame('Install Cream cheese on a bagel', $download_button->getText()); + $download_button->click(); + $this->assertSame('Installing', $download_button->getText()); + $this->assertTrue($assert_session->waitForText('Simulate an error message for the project browser.')); $download_button_text = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button.pb__action_button") ?->getText(); - $this->assertSame('View Commands for Cream cheese on a bagel', $download_button_text); + $this->assertSame('Install Cream cheese on a bagel', $download_button_text); } /** @@ -316,7 +321,6 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $page = $this->getSession()->getPage(); $this->drupalGet('admin/modules/browse/project_browser_test_mock'); $this->svelteInitHelper('text', 'Cream cheese on a bagel'); - $assert_session->statusMessageContains("Simulate a warning message for the project browser.", 'warning'); $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)'; $download_button = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button.pb__action_button"); $this->assertNotEmpty($download_button); @@ -328,6 +332,8 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { return $button->getText() === 'Cream cheese on a bagel is Installed'; }); $this->assertTrue($installed_action); + $this->drupalGet('admin/reports/dblog'); + $assert_session->pageTextContains('Simulate a warning message for the project browser.'); } /**