diff --git a/core/modules/auto_updates/auto_updates.info.yml b/core/modules/auto_updates/auto_updates.info.yml
index 1914ec34e7..6e24fb72ea 100644
--- a/core/modules/auto_updates/auto_updates.info.yml
+++ b/core/modules/auto_updates/auto_updates.info.yml
@@ -1,7 +1,6 @@
name: 'Automatic Updates'
type: module
-description: 'Experimental module to develop automatic updates. Currently the module does not provide update functionality.'
-configure: auto_updates.settings
+description: 'Experimental module to develop automatic updates. Currently the module provides checks for update readiness but does not yet provide update functionality.'
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 003ddfd995..eabd5969f0 100644
--- a/core/modules/auto_updates/auto_updates.install
+++ b/core/modules/auto_updates/auto_updates.install
@@ -18,34 +18,34 @@ function auto_updates_requirements($phase) {
/** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */
$checker_manager = \Drupal::service('auto_updates.readiness_checker_manager');
- $requirements['auto_updates_readiness']['title'] = t('Update readiness checks');
+ $requirement['title'] = t('Update readiness checks');
$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_WARNING;
+ $requirement['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.');
+ $requirement['value'] = t('Your site has never checked if it is ready to apply automatic updates.');
if ($readiness_check_url->access()) {
- $requirements['auto_updates_readiness']['description'] = t('Run readiness checks manually.', [
+ $requirement['description'] = t('Run readiness checks now.', [
':link' => $readiness_check_url->toString(),
]);
}
}
elseif (!$checker_manager->hasRunRecently()) {
- $requirements['auto_updates_readiness']['severity'] = REQUIREMENT_WARNING;
+ $requirement['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.');
+ $requirement['value'] = t('Your site has not recently checked if it is ready to apply automatic updates.');
if ($readiness_check_url->access()) {
- $requirements['auto_updates_readiness']['description'] = t('Readiness checks were last run @time ago. Run readiness checks now.', [
+ $requirement['description'] = t('Readiness checks were last run @time ago. Run readiness checks now.', [
'@time' => $time_ago,
':url' => $readiness_check_url->toString(),
]);
}
else {
- $requirements['auto_updates_readiness']['description'] = t('Readiness checks were last run @time ago.', ['@time' => $time_ago]);
+ $requirement['description'] = t('Readiness checks were last run @time ago.', ['@time' => $time_ago]);
}
}
else {
@@ -53,15 +53,15 @@ function auto_updates_requirements($phase) {
$warning_results = $checker_manager->getWarnings();
$checker_results = array_merge($error_results, $warning_results);
if (!empty($checker_results)) {
- $requirements['auto_updates_readiness']['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
- $requirements['auto_updates_readiness']['value'] = new PluralTranslatableMarkup(count($checker_results), '@count check failed:', '@count checks failed:');
- $requirements['auto_updates_readiness']['description'] = [
+ $requirement['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
+ $requirement['value'] = new PluralTranslatableMarkup(count($checker_results), '@count check failed:', '@count checks failed:');
+ $requirement['description'] = [
'#theme' => 'item_list',
'#items' => $checker_results,
];
}
else {
- $requirements['auto_updates_readiness'] += [
+ $requirement += [
'severity' => REQUIREMENT_OK,
// @todo Link "automatic updates" to documentation in
// https://www.drupal.org/node/3168405.
@@ -69,5 +69,5 @@ function auto_updates_requirements($phase) {
];
}
}
- return $requirements;
+ return ['auto_updates_readiness' => $requirement];
}
diff --git a/core/modules/auto_updates/auto_updates.module b/core/modules/auto_updates/auto_updates.module
index 099592bcd5..712026d703 100644
--- a/core/modules/auto_updates/auto_updates.module
+++ b/core/modules/auto_updates/auto_updates.module
@@ -13,8 +13,7 @@
function auto_updates_page_top(array &$page_top) {
/** @var \Drupal\Core\Routing\AdminContext $admin_context */
$admin_context = \Drupal::service('router.admin_context');
- $route_match = \Drupal::routeMatch();
- if ($admin_context->isAdminRoute($route_match->getRouteObject()) && \Drupal::currentUser()->hasPermission('administer site configuration')) {
+ if ($admin_context->isAdminRoute() && \Drupal::currentUser()->hasPermission('administer site configuration')) {
$disabled_routes = [
'update.theme_update',
'system.theme_install',
@@ -28,15 +27,16 @@ function auto_updates_page_top(array &$page_top) {
'update.confirmation_page',
];
// These routes don't need additional nagging.
- if (in_array($route_match->getRouteName(), $disabled_routes, TRUE)) {
+ if (in_array(\Drupal::routeMatch()->getRouteName(), $disabled_routes, TRUE)) {
return;
}
/** @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');
- \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\Core\Path\CurrentPathStack $current_path */
+ $current_path = \Drupal::service('path.current');
+ \Drupal::messenger()->addError(t('Your site has not recently run an update readiness check. Run readiness checks now.', [
+ ':url' => Url::fromRoute('auto_updates.update_readiness')->setOption('query', ['destination' => $current_path->getPath()])->toString(),
]));
}
$results = $checker_manager->getErrors();
@@ -64,7 +64,6 @@ function auto_updates_page_top(array &$page_top) {
* Implements hook_cron().
*/
function auto_updates_cron() {
- $request_time = \Drupal::time()->getRequestTime();
/** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */
$checker_manager = \Drupal::service('auto_updates.readiness_checker_manager');
$checker_manager->getErrors();
diff --git a/core/modules/auto_updates/auto_updates.routing.yml b/core/modules/auto_updates/auto_updates.routing.yml
index b9ec3d6efc..7ddecdd4df 100644
--- a/core/modules/auto_updates/auto_updates.routing.yml
+++ b/core/modules/auto_updates/auto_updates.routing.yml
@@ -1,12 +1,3 @@
-auto_updates.status:
- path: '/admin/reports/auto_updates'
- defaults:
- _controller: '\Drupal\auto_updates\Controller\ReadinessCheckerController::status'
- _title: 'Update readiness status'
- requirements:
- _permission: 'administer software updates'
- options:
- _admin_route: TRUE
auto_updates.update_readiness:
path: '/admin/reports/auto_updates/readiness'
defaults:
@@ -14,5 +5,3 @@ auto_updates.update_readiness:
_title: 'Update readiness checking'
requirements:
_permission: 'administer software updates'
- options:
- _admin_route: TRUE
diff --git a/core/modules/auto_updates/auto_updates.services.yml b/core/modules/auto_updates/auto_updates.services.yml
index a13ff965f1..9555c77d71 100644
--- a/core/modules/auto_updates/auto_updates.services.yml
+++ b/core/modules/auto_updates/auto_updates.services.yml
@@ -3,7 +3,7 @@ services:
class: Drupal\auto_updates\ReadinessChecker\DiskSpace
arguments: ['%app.root%']
tags:
- - { name: readiness_checker}
+ - { name: readiness_checker }
auto_updates.readiness_checker_manager:
class: Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager
arguments: ['@keyvalue.expirable', '@config.factory', '@datetime.time']
diff --git a/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
index cf133e6d17..bb0b5d7f8c 100644
--- a/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
+++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
@@ -4,14 +4,11 @@
use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager;
use Drupal\Core\Controller\ControllerBase;
-use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\StringTranslation\TranslationInterface;
-use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\RedirectResponse;
/**
- * A controller for running Readiness Checkers.
+ * A controller for running readiness checkers.
*
* @internal
* Controller classes are internal.
@@ -25,13 +22,6 @@ class ReadinessCheckerController extends ControllerBase {
*/
protected $checkerManager;
- /**
- * The date formatter.
- *
- * @var \Drupal\Core\Datetime\DateFormatterInterface
- */
- protected $dateFormatter;
-
/**
* ReadinessCheckerController constructor.
*
@@ -39,13 +29,10 @@ class ReadinessCheckerController extends ControllerBase {
* The readiness checker manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
- * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
- * The date formatter service.
*/
- public function __construct(ReadinessCheckerManager $checker_manager, TranslationInterface $string_translation, DateFormatter $date_formatter) {
+ public function __construct(ReadinessCheckerManager $checker_manager, TranslationInterface $string_translation) {
$this->checkerManager = $checker_manager;
$this->setStringTranslation($string_translation);
- $this->dateFormatter = $date_formatter;
}
/**
@@ -54,62 +41,25 @@ public function __construct(ReadinessCheckerManager $checker_manager, Translatio
public static function create(ContainerInterface $container) {
return new static(
$container->get('auto_updates.readiness_checker_manager'),
- $container->get('string_translation'),
- $container->get('date.formatter')
+ $container->get('string_translation')
);
}
- /**
- * Displays a status report for automatic update readiness.
- *
- * @return mixed[]
- * The status report render array.
- */
- public function status() {
- $last_check_timestamp = $this->checkerManager->getMostRecentRunTime();
-
- $readiness_messages = $last_check_timestamp === NULL ?
- $this->t('Readiness checks have never been run.')
- : $this->t('Readiness checks were last run @time ago.', ['@time' => $this->dateFormatter->formatTimeDiffSince($last_check_timestamp)]);
- $return['last_run']['#markup'] = $readiness_messages . ' ' .
- $this->t('Manually run the readiness checks.',
- [
- ':url' => Url::fromRoute('auto_updates.update_readiness')->toString(),
- ]
- );
- $error_results = $this->checkerManager->getErrors();
- $warning_results = $this->checkerManager->getWarnings();
- $checker_results = array_merge($error_results, $warning_results);
- if ($checker_results) {
- $return['status']['#markup'] = $this->formatPlural(count($checker_results), '@count check failed:', '@count checks failed:');
- $return['results'] = [
- '#theme' => 'item_list',
- '#items' => $checker_results,
- ];
- }
- else {
- $return['status'] = [
- '#type' => 'container',
- '#markup' => $this->t('Your site is ready for automatic updates.'),
- ];
- }
-
- return $return;
- }
-
/**
* Run the readiness checkers.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect to the automatic updates settings page.
*/
- public function run(): RedirectResponse {
- if (!array_filter($this->checkerManager->getErrors(TRUE))) {
+ public function run() {
+ if (!$this->checkerManager->getErrors(TRUE) && !$this->checkerManager->getWarnings()) {
// @todo Link "automatic updates" to documentation in
// https://www.drupal.org/node/3168405.
+ // If there are no messages from the readiness checkers display a message
+ // that site is ready. If there are messages the page will display them.
$this->messenger()->addStatus($this->t('No issues found. Your site is ready for automatic updates'));
}
- return $this->redirect('auto_updates.status');
+ return $this->redirect('system.status');
}
}
diff --git a/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
index 3606f3667b..3b9d54731c 100644
--- a/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
+++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
@@ -15,7 +15,20 @@ class DiskSpace extends FileSystemBase {
*/
protected const MINIMUM_DISK_SPACE = 1073741824;
- protected const MEGABYTE_DIVISOR = 1000000;
+ /**
+ * Gets the free disk space.
+ *
+ * @param string $path
+ * The path to check.
+ *
+ * @throws \RuntimeException
+ * Thrown if the call to disk_free_space() fails.
+ */
+ protected static function getFreeSpace(string $path) {
+ if (!disk_free_space($path)) {
+ throw new \RuntimeException('disk_free_space() failed.');
+ }
+ }
/**
* {@inheritdoc}
@@ -28,32 +41,40 @@ public function getWarnings(): array {
* {@inheritdoc}
*/
public function getErrors(): array {
- if ($messages = parent::getErrors()) {
- // @todo Reimplemenet so that every class that extends FileSystem base
- // will not the same error.
- return $messages;
+ $has_valid_root = $this->hasValidRootPath();
+ $has_valid_vendor = $this->hasValidVendorPatch();
+ if (!$has_valid_root && !$has_valid_vendor) {
+ return [$this->t('Free disk space cannot be determined because the web root and vendor directories could not be located.')];
+ }
+ elseif (!$has_valid_root) {
+ return [$this->t('Free disk space cannot be determined because the web root directory could not be located.')];
+ }
+ if (!$this->hasValidVendorPatch()) {
+ return [$this->t('Free disk space cannot be determined because the vendor directory could not be located.')];
}
$messages = [];
- $minimum_megabytes = static::MINIMUM_DISK_SPACE / static::MEGABYTE_DIVISOR;
- if (!$this->areSameLogicalDisk($this->getRootPath(), $this->getVendorPath())) {
+ $minimum_megabytes = static::MINIMUM_DISK_SPACE / 1000000;
+ $root_path = $this->getRootPath();
+ $vendor_path = $this->getVendorPath();
+ if (!$this->areSameLogicalDisk($root_path, $vendor_path)) {
// If the root and vendor paths are not on the same logical disk check
// that each have at least half of the minimum required disk space.
- if (disk_free_space($this->getRootPath()) < (static::MINIMUM_DISK_SPACE / 2)) {
+ if (static::getFreeSpace($root_path) < (static::MINIMUM_DISK_SPACE / 2)) {
$messages[] = $this->t('Drupal root filesystem "@root" has insufficient space. There must be at least @space megabytes free.', [
- '@root' => $this->getRootPath(),
- '@space' => $minimum_megabytes,
+ '@root' => $root_path,
+ '@space' => $minimum_megabytes / 2,
]);
}
- if (disk_free_space($this->getVendorPath()) < (static::MINIMUM_DISK_SPACE / 2)) {
+ if (static::getFreeSpace($vendor_path) < (static::MINIMUM_DISK_SPACE / 2)) {
$messages[] = $this->t('Vendor filesystem "@vendor" has insufficient space. There must be at least @space megabytes free.', [
- '@vendor' => $this->getVendorPath(),
- '@space' => $minimum_megabytes,
+ '@vendor' => $vendor_path,
+ '@space' => $minimum_megabytes / 2,
]);
}
}
- elseif (disk_free_space($this->getRootPath()) < static::MINIMUM_DISK_SPACE) {
+ elseif (static::getFreeSpace($root_path) < static::MINIMUM_DISK_SPACE) {
$messages[] = $this->t('Logical disk "@root" has insufficient space. There must be at least @space megabytes free.', [
- '@root' => $this->getRootPath(),
+ '@root' => $root_path,
'@space' => $minimum_megabytes,
]);
}
diff --git a/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
index 529b2b31a8..8e58430035 100644
--- a/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
+++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
@@ -6,6 +6,9 @@
/**
* Base class for file system checkers.
+ *
+ * Readiness checkers that require knowing the web root and/or vendor
+ * directories to perform their checks should extend this class.
*/
abstract class FileSystemBase implements ReadinessCheckerInterface {
use StringTranslationTrait;
@@ -28,23 +31,27 @@ public function __construct(string $app_root) {
}
/**
- * {@inheritdoc}
+ * Determines if a valid root path can be located.
+ *
+ * @return bool
+ * TRUE if a valid root path can be determined, otherwise false.
*/
- public function getErrors(): array {
- $messages = [];
- if (!file_exists(implode(DIRECTORY_SEPARATOR, [$this->getRootPath(), 'core', 'core.api.php']))) {
- $messages[] = $this->t('The web root could not be located.');
- }
- if (!file_exists($this->getVendorPath() . DIRECTORY_SEPARATOR . 'autoload.php')) {
- $messages[] = $this->t('Vendor folder "@vendor" is not a valid directory. Alternate vendor folder locations are not currently supported.', [
- '@vendor' => $this->getVendorPath(),
- ]);
- }
- return $messages;
+ protected function hasValidRootPath() {
+ return file_exists(implode(DIRECTORY_SEPARATOR, [$this->getRootPath(), 'core', 'core.api.php']));
+ }
+
+ /**
+ * Determines if a valid vendor path can be located.
+ *
+ * @return bool
+ * TRUE if a valid root path can be determined, otherwise false.
+ */
+ protected function hasValidVendorPatch() {
+ return file_exists($this->getVendorPath() . DIRECTORY_SEPARATOR . 'autoload.php');
}
/**
- * Gets the root file path.
+ * Gets the absolute path at which Drupal is installed.
*
* @return string
* The root file path.
@@ -83,7 +90,7 @@ protected function areSameLogicalDisk(string $root, string $vendor): bool {
$root_statistics = stat($root);
$vendor_statistics = stat($vendor);
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.');
+ throw new \RuntimeException('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 cf745b23fe..c389e770ad 100644
--- a/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
+++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
@@ -14,14 +14,14 @@ class ReadinessCheckerManager {
/**
* Time (in seconds) since the last check after which we generate a warning.
*
- * Defaults to 1 day.
+ * The value is equal to 1 day.
*/
private const LAST_CHECKED_WARNING = 60 * 60 * 24;
/**
* The key/value storage.
*
- * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+ * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface
*/
protected $keyValueExpirable;
@@ -77,7 +77,7 @@ public function __construct(KeyValueExpirableFactoryInterface $key_value_expirab
*
* @return $this
*/
- public function addChecker(ReadinessCheckerInterface $checker, $priority = 0): ReadinessCheckerManager {
+ public function addChecker(ReadinessCheckerInterface $checker, int $priority = 0): ReadinessCheckerManager {
$this->checkersByPriority[$priority][] = $checker;
ksort($this->checkersByPriority);
return $this;
@@ -87,7 +87,8 @@ public function addChecker(ReadinessCheckerInterface $checker, $priority = 0): R
* Runs readiness checks.
*
* @param bool $refresh
- * Whether to refresh the results.
+ * (optional) Whether to refresh the results, defaults FALSE. If FALSE then
+ * cached results will be returned if available.
*
* @return string[][]
* A nested array of readiness check messages. The top level array is keyed
@@ -134,7 +135,7 @@ protected function run(bool $refresh = FALSE): array {
* The timestamp of the most recently completed run, or NULL if no run has
* been completed.
*/
- public function getMostRecentRunTime(): int {
+ public function getMostRecentRunTime():?int {
return $this->keyValueExpirable->get('readiness_check_timestamp');
}
@@ -156,7 +157,7 @@ protected function getSortedCheckers(): array {
* Gets the current checker service Ids.
*
* @return string
- * A concatenated list of checker service Ids delimited by '::'.
+ * A concatenated list of checker service IDs delimited by '::'.
*/
protected function getCurrentCheckerIds(): string {
$service_ids = [];
diff --git a/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
index 932d930967..4f6ef3d10e 100644
--- a/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
+++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
@@ -143,7 +143,7 @@ public function testReadinessCheckAfterInstall(): void {
$this->assertReadinessReportMatches('1 check failed: 😿Oh no! A hacker now owns your files!');
// Confirm the new message is displayed after running the checkers manually.
- $this->drupalGet('admin/config/auto_updates');
+ $this->drupalGet('admin/reports/auto_updates');
$this->clickLink('run the readiness checks');
$assert->pageTextContains('Security has been compromised. "pass123" was a bad password!');
$assert->pageTextNotContains('😿Oh no! A hacker now owns your files!');
diff --git a/core/modules/update/update.info.yml b/core/modules/update/update.info.yml
index 0c363f44c2..20b8eef0a5 100644
--- a/core/modules/update/update.info.yml
+++ b/core/modules/update/update.info.yml
@@ -3,6 +3,5 @@ type: module
description: 'Checks for available updates, and can securely install or update modules and themes via a web interface.'
version: VERSION
package: Core
-configure: update.settings
dependencies:
- drupal:file