diff --git a/core/modules/auto_updates/auto_updates.install b/core/modules/auto_updates/auto_updates.install index 173d1ceaef..7994d74219 100644 --- a/core/modules/auto_updates/auto_updates.install +++ b/core/modules/auto_updates/auto_updates.install @@ -34,7 +34,7 @@ function auto_updates_requirements($phase) { ]); } } - elseif (\Drupal::time()->getRequestTime() > $last_check_timestamp + ReadinessCheckerManagerInterface::LAST_CHECKED_WARNING) { + elseif (!$checker_manager->hasRunRecently()) { $requirements['auto_updates_readiness']['severity'] = REQUIREMENT_ERROR; $time_ago = \Drupal::service('date.formatter')->formatTimeDiffSince($last_check_timestamp); $requirements['auto_updates_readiness']['value'] = t('Your site has not recently checked if it is ready to apply automatic updates.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']); diff --git a/core/modules/auto_updates/auto_updates.module b/core/modules/auto_updates/auto_updates.module index 70a68230aa..800549bec2 100644 --- a/core/modules/auto_updates/auto_updates.module +++ b/core/modules/auto_updates/auto_updates.module @@ -32,15 +32,14 @@ function auto_updates_page_top(array &$page_top) { if (in_array(\Drupal::routeMatch()->getRouteName(), $disabled_routes, TRUE)) { return; } - $last_check_timestamp = \Drupal::service('auto_updates.readiness_checker_manager')->timestamp(); - if (\Drupal::time()->getRequestTime() > $last_check_timestamp + ReadinessCheckerManagerInterface::LAST_CHECKED_WARNING) { + /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ + $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); + if (!$checker_manager->hasRunRecently()) { $readiness_settings = Url::fromRoute('auto_updates.settings'); \Drupal::messenger()->addError(t('Your site has not recently run an update readiness check. Administer automatic updates and run readiness checks manually.', [ ':url' => $readiness_settings->toString(), ])); } - /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */ - $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); $results = $checker_manager->getResults(ReadinessCheckerManagerInterface::ERROR); if ($results) { \Drupal::messenger()->addError(t('Your site is currently failing readiness checks for automatic updates. It cannot be automatically updated until further action is performed:', [':readiness_checks' => 'https://www.drupal.org/docs/8/update/auto-updates#readiness-checks'])); @@ -82,7 +81,7 @@ function auto_updates_cron() { function auto_updates_modules_installed($modules) { /** @var ReadinessCheckerManagerInterface $checker_manager */ $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager'); - if ($checker_manager->clearResultsStaleResults()) { + if ($checker_manager->clearStaleResults()) { $checker_manager->run(); } } diff --git a/core/modules/auto_updates/auto_updates.services.yml b/core/modules/auto_updates/auto_updates.services.yml index 461fbcd4fb..4b8c52678b 100644 --- a/core/modules/auto_updates/auto_updates.services.yml +++ b/core/modules/auto_updates/auto_updates.services.yml @@ -6,6 +6,6 @@ services: - { name: readiness_checker, category: error} auto_updates.readiness_checker_manager: class: Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager - arguments: ['@keyvalue', '@config.factory'] + arguments: ['@keyvalue', '@config.factory', '@datetime.time'] tags: - { name: service_collector, tag: readiness_checker, call: addChecker } diff --git a/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php index 69bd3f6b22..42bd0acda4 100644 --- a/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php @@ -10,12 +10,12 @@ class DiskSpace extends FilesystemBase { /** - * Minimum disk space (in bytes) is 10mb. + * Minimum disk space (in bytes) is 100mb. * * @todo Determine how much the minimum should be now that we will be using - * Composer. + * Composer in https://www.drupal.org/node/3166416. */ - const MINIMUM_DISK_SPACE = 10000000; + const MINIMUM_DISK_SPACE = 100000000; /** * Megabyte divisor. @@ -45,7 +45,7 @@ protected function diskSpaceCheck() { '@space' => $minimum_megabytes, ]); } - if (is_dir($this->getVendorPath()) && disk_free_space($this->getVendorPath()) < static::MINIMUM_DISK_SPACE) { + if (disk_free_space($this->getVendorPath()) < static::MINIMUM_DISK_SPACE) { $messages[] = $this->t('Vendor filesystem "@vendor" has insufficient space. There must be at least @space megabytes free.', [ '@vendor' => $this->getVendorPath(), '@space' => $minimum_megabytes, diff --git a/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php index 622b7795d1..9b5c4b0f3a 100644 --- a/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php @@ -17,13 +17,6 @@ abstract class FilesystemBase implements ReadinessCheckerInterface { */ protected $rootPath; - /** - * The vendor file path. - * - * @var string - */ - protected $vendorPath; - /** * Filesystem constructor. * @@ -38,10 +31,18 @@ public function __construct(string $app_root) { * {@inheritdoc} */ public function run() { + $messages = []; if (!file_exists($this->getRootPath() . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, ['core', 'core.api.php']))) { - return [$this->t('The web root could not be located.')]; + $messages[] = $this->t('The web root could not be located.'); + } + if (!is_dir($this->getVendorPath())) { + $messages[] = $this->t('Vendor folder "@vendor" is not a valid directory. Alternate vendor folder locations are not currently supported.', [ + '@vendor' => $this->getVendorPath(), + ]); + } + if ($messages) { + return $messages; } - return $this->doCheck(); } @@ -60,9 +61,6 @@ public function run() { * The root file path. */ protected function getRootPath() { - if (!$this->rootPath) { - $this->rootPath = (string) \Drupal::root(); - } return $this->rootPath; } @@ -73,10 +71,9 @@ protected function getRootPath() { * The vendor file path. */ protected function getVendorPath() { - if (!$this->vendorPath) { - $this->vendorPath = $this->getRootPath() . DIRECTORY_SEPARATOR . 'vendor'; - } - return $this->vendorPath; + // @todo Support finding the 'vendor' directory dynamically in + // https://www.drupal.org/node/3166435. + return $this->getRootPath() . DIRECTORY_SEPARATOR . 'vendor'; } /** diff --git a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php index e813912603..42e2059156 100644 --- a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php @@ -2,6 +2,7 @@ namespace Drupal\auto_updates\ReadinessChecker; +use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; @@ -10,6 +11,11 @@ */ class ReadinessCheckerManager implements ReadinessCheckerManagerInterface { + /** + * Last checked ago warning (in seconds). + */ + private const LAST_CHECKED_WARNING = 3600 * 24; + /** * The key/value storage. * @@ -34,6 +40,13 @@ class ReadinessCheckerManager implements ReadinessCheckerManagerInterface { */ protected $checkers = []; + /** + * The time service. + * + * @var \Drupal\Component\Datetime\TimeInterface + */ + protected $time; + /** * ReadinessCheckerManager constructor. * @@ -41,10 +54,13 @@ class ReadinessCheckerManager implements ReadinessCheckerManagerInterface { * The key/value service. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. + * @param \Drupal\Component\Datetime\TimeInterface $time + * The time service. */ - public function __construct(KeyValueFactoryInterface $key_value, ConfigFactoryInterface $config_factory) { + public function __construct(KeyValueFactoryInterface $key_value, ConfigFactoryInterface $config_factory, TimeInterface $time) { $this->keyValue = $key_value->get('auto_updates'); $this->configFactory = $config_factory; + $this->time = $time; } /** @@ -81,7 +97,7 @@ public function run() { 'checkers' => $this->getCurrentCheckerIds(), ] ); - $this->keyValue->set('readiness_check_timestamp', \Drupal::time()->getRequestTime()); + $this->keyValue->set('readiness_check_timestamp', $this->time->getRequestTime()); return $messages_by_category; } @@ -100,7 +116,7 @@ public function getResults($category) { /** * {@inheritdoc} */ - public function clearResultsStaleResults() { + public function clearStaleResults() { $results = $this->keyValue->get('readiness_check_results'); if (isset($results['checkers']) && $this->getCurrentCheckerIds() !== $results['checkers']) { $this->keyValue->delete('readiness_check_results'); @@ -133,10 +149,10 @@ public function getCategories() { /** * Sorts checkers according to priority. * - * @return \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerInterface[] - * A sorted array of checker objects. - * - * @todo Add more detail to @return. + * @return \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerInterface[][] + * A nested and sorted array of checker objects. The first level of the + * array is keyed by checker categories. The second level array is checker + * objects in the category order by priority. */ protected function getSortedCheckers() { $sorted = []; @@ -166,4 +182,11 @@ protected function getCurrentCheckerIds(): string { return implode('::', $service_ids); } + /** + * {@inheritdoc} + */ + public function hasRunRecently() { + return !($this->time->getRequestTime() > $this->timestamp() + self::LAST_CHECKED_WARNING); + } + } diff --git a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php index b794df6af0..be26011f3f 100644 --- a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php @@ -17,11 +17,6 @@ interface ReadinessCheckerManagerInterface { */ const WARNING = 'warning'; - /** - * Last checked ago warning (in seconds). - */ - const LAST_CHECKED_WARNING = 3600 * 24; - /** * Appends a checker to the checker chain. * @@ -90,6 +85,14 @@ public function getCategories(); * @return bool * Return TRUE if the results were cleared, otherwise returns FALSE. */ - public function clearResultsStaleResults(); + public function clearStaleResults(); + + /** + * Determines whether the readiness checkers have been run recently. + * + * @return bool + * True if the checkers have been run recently, otherwise false. + */ + public function hasRunRecently(); } 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 51a09e5aad..08066f8dcf 100644 --- a/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php +++ b/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php @@ -42,6 +42,13 @@ public function testDiskSpace() { $this->assertStringMatchesFormat('Drupal root filesystem "%s" has insufficient space. There must be at least %s megabytes free.', (string) $messages[0]); $this->assertStringMatchesFormat('Vendor filesystem "%s" has insufficient space. There must be at least %s megabytes free.', (string) $messages[1]); $this->assertStringMatchesFormat('Directory "%s" has insufficient space. There must be at least %s megabytes free.', (string) $messages[2]); + + // Web root and vendor path are invalid. + $disk_space = new DiskSpace("if_there_was_ever_a_folder_with_this_path_this_test_would_fail"); + $messages = $disk_space->run(); + $this->assertCount(2, $messages); + $this->assertEquals('The web root could not be located.', (string) $messages[0]); + $this->assertStringMatchesFormat('Vendor folder "if_there_was_ever_a_folder_with_this_path_this_test_would_fail/vendor" is not a valid directory. Alternate vendor folder locations are not currently supported.', (string) $messages[1]); } }