diff --git a/core/modules/auto_updates/auto_updates.info.yml b/core/modules/auto_updates/auto_updates.info.yml index 1914ec34e7..bb73ad023b 100644 --- a/core/modules/auto_updates/auto_updates.info.yml +++ b/core/modules/auto_updates/auto_updates.info.yml @@ -4,4 +4,3 @@ description: 'Experimental module to develop automatic updates. Currently the mo configure: auto_updates.settings package: Core (Experimental) version: VERSION -hidden: true diff --git a/core/modules/auto_updates/auto_updates.install b/core/modules/auto_updates/auto_updates.install index 1c9a1306fe..300d535483 100644 --- a/core/modules/auto_updates/auto_updates.install +++ b/core/modules/auto_updates/auto_updates.install @@ -7,7 +7,7 @@ use Drupal\Core\StringTranslation\PluralTranslatableMarkup; use Drupal\Core\Url; -use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface; +use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager; /** * Implements hook_requirements(). @@ -17,35 +17,35 @@ function auto_updates_requirements($phase) { return []; } - /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ + /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */ $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); if (!$checker_manager->isEnabled()) { return []; } $requirements['auto_updates_readiness']['title'] = t('Update readiness checks'); - $readiness_check = Url::fromRoute('auto_updates.update_readiness'); - $last_check_timestamp = $checker_manager->getTimestamp(); + $readiness_check_url = Url::fromRoute('auto_updates.update_readiness'); + $last_check_timestamp = $checker_manager->getMostRecentRunTime(); if ($last_check_timestamp === NULL) { - $requirements['auto_updates_readiness']['severity'] = REQUIREMENT_ERROR; + $requirements['auto_updates_readiness']['severity'] = REQUIREMENT_WARNING; // @todo Link "automatic updates" to documentation in // https://www.drupal.org/node/3168405. $requirements['auto_updates_readiness']['value'] = t('Your site has never checked if it is ready to apply automatic updates.'); - if ($readiness_check->access()) { + if ($readiness_check_url->access()) { $requirements['auto_updates_readiness']['description'] = t('Run readiness checks manually.', [ - ':link' => $readiness_check->toString(), + ':link' => $readiness_check_url->toString(), ]); } } elseif (!$checker_manager->hasRunRecently()) { - $requirements['auto_updates_readiness']['severity'] = REQUIREMENT_ERROR; + $requirements['auto_updates_readiness']['severity'] = REQUIREMENT_WARNING; $time_ago = \Drupal::service('date.formatter')->formatTimeDiffSince($last_check_timestamp); // @todo Link "automatic updates" to documentation in // https://www.drupal.org/node/3168405. $requirements['auto_updates_readiness']['value'] = t('Your site has not recently checked if it is ready to apply automatic updates.'); - if ($readiness_check->access()) { - $requirements['auto_updates_readiness']['description'] = t('Readiness checks were last run @time ago. Run readiness checks manually.', [ + if ($readiness_check_url->access()) { + $requirements['auto_updates_readiness']['description'] = t('Readiness checks were last run @time ago. Run readiness checks now.', [ '@time' => $time_ago, - ':link' => $readiness_check->toString(), + ':url' => $readiness_check_url->toString(), ]); } else { @@ -53,8 +53,8 @@ function auto_updates_requirements($phase) { } } else { - $error_results = $checker_manager->getResults(ReadinessCheckerManagerInterface::ERROR); - $warning_results = $checker_manager->getResults(ReadinessCheckerManagerInterface::WARNING); + $error_results = $checker_manager->getResults(ReadinessCheckerManager::ERROR); + $warning_results = $checker_manager->getResults(ReadinessCheckerManager::WARNING); $checker_results = array_merge($error_results, $warning_results); if (!empty($checker_results)) { $requirements['auto_updates_readiness']['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING; @@ -79,8 +79,8 @@ function auto_updates_requirements($phase) { /** * Implements hook_install(). */ -function auto_updates_install($is_syncing) { - /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ +function auto_updates_install() { + /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */ $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); $checker_manager->run(); } diff --git a/core/modules/auto_updates/auto_updates.module b/core/modules/auto_updates/auto_updates.module index 3bb1cad5a5..69d060891b 100644 --- a/core/modules/auto_updates/auto_updates.module +++ b/core/modules/auto_updates/auto_updates.module @@ -6,7 +6,7 @@ */ use Drupal\Core\Url; -use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface; +use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager; /** * Implements hook_page_top(). @@ -29,10 +29,10 @@ function auto_updates_page_top(array &$page_top) { 'update.confirmation_page', ]; // These routes don't need additional nagging. - if (in_array(\Drupal::routeMatch()->getRouteName(), $disabled_routes, TRUE)) { + if (in_array($route_match->getRouteName(), $disabled_routes, TRUE)) { return; } - /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ + /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */ $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); if (!$checker_manager->hasRunRecently()) { $readiness_settings = Url::fromRoute('auto_updates.settings'); @@ -40,7 +40,7 @@ function auto_updates_page_top(array &$page_top) { ':url' => $readiness_settings->toString(), ])); } - $results = $checker_manager->getResults(ReadinessCheckerManagerInterface::ERROR); + $results = $checker_manager->getResults(ReadinessCheckerManager::ERROR); if ($results) { // @todo Link "automatic updates" to documentation in // https://www.drupal.org/node/3168405. @@ -49,7 +49,7 @@ function auto_updates_page_top(array &$page_top) { \Drupal::messenger()->addError($message); } } - $results = $checker_manager->getResults(ReadinessCheckerManagerInterface::WARNING); + $results = $checker_manager->getResults(ReadinessCheckerManager::WARNING); if ($results) { // @todo Link "automatic updates" to documentation in // https://www.drupal.org/node/3168405. @@ -66,9 +66,9 @@ function auto_updates_page_top(array &$page_top) { */ function auto_updates_cron() { $request_time = \Drupal::time()->getRequestTime(); - /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ + /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */ $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); - $last_check = $checker_manager->getTimestamp(); + $last_check = $checker_manager->getMostRecentRunTime(); // Only allow cron to run once every hour. if ($last_check && ($request_time - $last_check) < 3600) { return; @@ -79,8 +79,8 @@ function auto_updates_cron() { /** * Implements hook_modules_installed(). */ -function auto_updates_modules_installed($modules) { - /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ +function auto_updates_modules_installed() { + /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */ $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); if ($checker_manager->clearStaleResults()) { $checker_manager->run(); diff --git a/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php index bd3aac3fb8..56ee8dfb21 100644 --- a/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php @@ -2,12 +2,13 @@ namespace Drupal\auto_updates\Controller; -use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface; +use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager; use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\StringTranslation\TranslationInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * A controller for running Readiness Checkers. @@ -20,7 +21,7 @@ class ReadinessCheckerController extends ControllerBase { /** * The readiness checker manager. * - * @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface + * @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager */ protected $checkerManager; @@ -32,9 +33,9 @@ class ReadinessCheckerController extends ControllerBase { * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation * The string translation service. */ - public function __construct(ReadinessCheckerManagerInterface $checker_manager, TranslationInterface $string_translation) { + public function __construct(ReadinessCheckerManager $checker_manager, TranslationInterface $string_translation) { $this->checkerManager = $checker_manager; - $this->stringTranslation = $string_translation; + $this->setStringTranslation($string_translation); } /** @@ -53,7 +54,7 @@ public static function create(ContainerInterface $container) { * @return \Symfony\Component\HttpFoundation\RedirectResponse * A redirect to the automatic updates settings page. */ - public function run() { + public function run(): RedirectResponse { if (!array_filter($this->checkerManager->run())) { // @todo Link "automatic updates" to documentation in // https://www.drupal.org/node/3168405. diff --git a/core/modules/auto_updates/src/Form/SettingsForm.php b/core/modules/auto_updates/src/Form/SettingsForm.php index 96f634dfcb..07530515fd 100644 --- a/core/modules/auto_updates/src/Form/SettingsForm.php +++ b/core/modules/auto_updates/src/Form/SettingsForm.php @@ -9,13 +9,15 @@ /** * Settings form for Automatic Updates. + * + * @internal */ class SettingsForm extends ConfigFormBase { /** * The readiness checker manager. * - * @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface + * @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager */ protected $checkerManager; @@ -57,7 +59,7 @@ public function getFormId() { */ public function buildForm(array $form, FormStateInterface $form_state) { $config = $this->config('auto_updates.settings'); - $last_check_timestamp = $this->checkerManager->getTimestamp(); + $last_check_timestamp = $this->checkerManager->getMostRecentRunTime(); $form['enable_readiness_checks'] = [ '#type' => 'checkbox', '#title' => $this->t('Check the readiness of automatically updating the site.'), diff --git a/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php index 2cc5684d4a..81997d2305 100644 --- a/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php @@ -13,12 +13,9 @@ class DiskSpace extends FileSystemBase { * @todo Determine how much the minimum should be now that we will be using * Composer in https://www.drupal.org/node/3166416. */ - const MINIMUM_DISK_SPACE = 1073741824; + protected const MINIMUM_DISK_SPACE = 1073741824; - /** - * Megabyte divisor. - */ - const MEGABYTE_DIVISOR = 1000000; + protected const MEGABYTE_DIVISOR = 1000000; /** * {@inheritdoc} diff --git a/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php index 8c4da6a22b..61675f0333 100644 --- a/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php @@ -86,11 +86,17 @@ protected function getVendorPath(): string { * * @return bool * TRUE if they are on the same file system, FALSE otherwise. + * + * @throws \Exception + * Thrown if the an error is found trying get the directory information. */ protected function areSameLogicalDisk(string $root, string $vendor): bool { $root_statistics = stat($root); $vendor_statistics = stat($vendor); - return $root_statistics && $vendor_statistics && $root_statistics['dev'] === $vendor_statistics['dev']; + if ($root_statistics === FALSE || $vendor_statistics === FALSE) { + throw new \Exception('Unable to determine if the root and vendor directories are on the same logic disk.'); + } + return $root_statistics['dev'] === $vendor_statistics['dev']; } } diff --git a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php index 2d855d3190..54b95a2db6 100644 --- a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php @@ -9,7 +9,18 @@ /** * Defines a manager to run readiness checkers. */ -class ReadinessCheckerManager implements ReadinessCheckerManagerInterface { +class ReadinessCheckerManager { + + + /** + * Error category. + */ + const ERROR = 'error'; + + /** + * Warning category. + */ + const WARNING = 'warning'; /** * Time (in seconds) since the last check after which we generate a warning. @@ -66,9 +77,20 @@ public function __construct(KeyValueFactoryInterface $key_value, ConfigFactoryIn } /** - * {@inheritdoc} + * Appends a checker to the checker chain. + * + * @param \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerInterface $checker + * The checker interface to be appended to the checker chain. + * @param string $category + * (optional) The category of check. Defaults to 'warning'. + * @param int $priority + * (optional) The priority of the checker being added. Defaults to 0. + * Readiness checkers with larger priorities will run first within a + * category. + * + * @return $this */ - public function addChecker(ReadinessCheckerInterface $checker, string $category = 'warning', $priority = 0): ReadinessCheckerManagerInterface { + public function addChecker(ReadinessCheckerInterface $checker, string $category = 'warning', $priority = 0): ReadinessCheckerManager { if (!in_array($category, $this->getCategories(), TRUE)) { throw new \InvalidArgumentException(sprintf('Readiness checker category "%s" is invalid. Use "%s" instead.', $category, implode('" or "', $this->getCategories()))); } @@ -77,7 +99,12 @@ public function addChecker(ReadinessCheckerInterface $checker, string $category } /** - * {@inheritdoc} + * Runs readiness checks. + * + * @return string[][] + * A nested array of readiness check messages. The top level array is keyed + * by category and the next level array is an array of translatable strings + * for the category. */ public function run(): array { if (!$this->isEnabled()) { @@ -104,7 +131,14 @@ public function run(): array { } /** - * {@inheritdoc} + * Gets the results of the most recent run. + * + * @param string $category + * The category of check. + * + * @return array + * An array of translatable messages if any checks fail, otherwise an empty + * array. */ public function getResults(string $category): array { if ($this->isEnabled()) { @@ -116,7 +150,10 @@ public function getResults(string $category): array { } /** - * {@inheritdoc} + * Clears readiness checker results if the available checkers have changed. + * + * @return bool + * TRUE if the results were cleared, otherwise FALSE. */ public function clearStaleResults(): bool { $results = $this->keyValue->get('readiness_check_results'); @@ -128,21 +165,31 @@ public function clearStaleResults(): bool { } /** - * {@inheritdoc} + * Gets the timestamp of the most recent run. + * + * @return int|null + * The timestamp of the most recently completed run, or NULL if no run has + * been completed. */ - public function getTimestamp(): int { + public function getMostRecentRunTime(): int { return $this->keyValue->get('readiness_check_timestamp'); } /** - * {@inheritdoc} + * Determines if readiness checks are enabled. + * + * @return bool + * TRUE if enabled, otherwise FALSE. */ public function isEnabled(): bool { return $this->configFactory->get('auto_updates.settings')->get('enable_readiness_checks'); } /** - * {@inheritdoc} + * Gets the checker categories. + * + * @return string[] + * The checkers categories. */ public function getCategories(): array { return [self::ERROR, self::WARNING]; @@ -185,10 +232,13 @@ protected function getCurrentCheckerIds(): string { } /** - * {@inheritdoc} + * Determines whether the readiness checkers have been run recently. + * + * @return bool + * TRUE if the checkers have been run recently, otherwise FALSE. */ public function hasRunRecently(): bool { - return $this->time->getRequestTime() <= $this->getTimestamp() + self::LAST_CHECKED_WARNING; + return $this->time->getRequestTime() <= $this->getMostRecentRunTime() + self::LAST_CHECKED_WARNING; } } diff --git a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php deleted file mode 100644 index 51923de26e..0000000000 --- a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php +++ /dev/null @@ -1,99 +0,0 @@ -assertSession(); $page = $this->getSession()->getPage(); @@ -80,7 +80,7 @@ public function testReadinessChecksStatusReport() { $this->drupalLogin($this->checkerRunnerUser); $this->drupalGet('admin/reports/status'); $this->assertReadinessReportMatches('Your site has not recently checked if it is ready to apply automatic updates.' - . ' Readiness checks were last run %s ago. Run readiness checks manually.'); + . ' Readiness checks were last run %s ago. Run readiness checks now.'); $this->container->get('state')->set(TestChecker::STATE_KEY, 'OMG 🚒. Your server is on 🔥!'); // Run the readiness checks. @@ -139,7 +139,7 @@ public function testReadinessChecksStatusReport() { /** * Tests installing a module with a checker before installing auto_updates. */ - public function testReadinessCheckAfterInstall() { + public function testReadinessCheckAfterInstall(): void { $assert = $this->assertSession(); $this->drupalLogin($this->checkerRunnerUser); @@ -174,6 +174,23 @@ public function testReadinessCheckAfterInstall() { $this->assertReadinessReportMatches('1 check failed: Security has been compromised. "pass123" was a bad password!'); } + /** + * Tests that checker message for an uninstalled module is not displayed. + */ + public function testReadinessCheckerUninstall(): void { + $assert = $this->assertSession(); + $this->drupalLogin($this->checkerRunnerUser); + + $this->container->get('state')->set(TestChecker::STATE_KEY, '😲Your site is running on Commodore 64! Not powerful enough to do updates!'); + $this->container->get('module_installer')->install(['auto_updates', 'auto_updates_test']); + $this->drupalGet('admin/reports/status'); + $this->assertReadinessReportMatches('1 check failed: 😲Your site is running on Commodore 64! Not powerful enough to do updates!'); + + $this->container->get('module_installer')->uninstall(['auto_updates_test']); + $this->drupalGet('admin/reports/status'); + $assert->pageTextNotContains('1 check failed: 😲Your site is running on Commodore 64! Not powerful enough to do updates!'); + } + /** * Tests that the readiness checks are run on cron. */ diff --git a/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php b/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php index e1c7dd0f3f..d128c46c5f 100644 --- a/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php +++ b/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php @@ -64,7 +64,7 @@ class TestDiskSpace extends DiskSpace { } /** - * Class TestDiskSpaceNonSameDisk. + * A test checker that overrides TestDiskSpace to fake different logical disks. */ class TestDiskSpaceNonSameDisk extends TestDiskSpace {