From 3b0dacd94476c5b00be76d848e8ca94909451acd Mon Sep 17 00:00:00 2001 From: "Theresa.Grannum" <theresa.grannum@3688861.no-reply.drupal.org> Date: Wed, 5 Oct 2022 21:03:35 +0000 Subject: [PATCH] Issue #3312669 by phenaproxima, Theresa.Grannum: Add help text explaining how to set an alternate port for cron --- automatic_updates.module | 5 +++ automatic_updates.services.yml | 1 + src/Validator/CronServerValidator.php | 35 +++++++++++++---- .../CronServerValidatorTest.php | 38 ++++++++++++++++++- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/automatic_updates.module b/automatic_updates.module index e793fd50cf..d3fe134f8d 100644 --- a/automatic_updates.module +++ b/automatic_updates.module @@ -30,6 +30,11 @@ function automatic_updates_help($route_name, RouteMatchInterface $route_match) { $output .= '<h3>' . t('Requirements') . '</h3>'; $output .= '<p>' . t('Automatic Updates requires a Composer executable whose version satisfies <code>@version</code>, and PHP must have permission to run it. The path to the executable may be set in the <code>package_manager.settings:executables.composer</code> config setting, or it will be automatically detected.', ['@version' => ComposerExecutableValidator::MINIMUM_COMPOSER_VERSION_CONSTRAINT]) . '</p>'; $output .= '<p>' . t('For more information, see the <a href=":automatic-updates-documentation">online documentation for the Automatic Updates module</a>.', [':automatic-updates-documentation' => 'https://www.drupal.org/docs/8/update/automatic-updates']) . '</p>'; + $output .= '<p id="cron-alternate-port">' . t('If your site is running on the built-in PHP web server, unattended (i.e., cron) updates may not work without one of the following workarounds:') . '</p>'; + $output .= '<ul>'; + $output .= '<li>' . t('Use a multithreaded web server, such as Apache, NGINX, or on Windows, IIS.') . '</li>'; + $output .= '<li>' . t('Run another instance of the built-in PHP web server on a different port and configure automatic updates accordingly: <code>$config["automatic_updates.settings"]["cron_port"] = $alternate_port_number;</code>') . '</li>'; + $output .= '</ul>'; return $output; } } diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml index e78d85265d..00eb7f862e 100644 --- a/automatic_updates.services.yml +++ b/automatic_updates.services.yml @@ -166,6 +166,7 @@ services: arguments: - '@request_stack' - '@config.factory' + - '@module_handler' tags: - { name: event_subscriber } logger.channel.automatic_updates: diff --git a/src/Validator/CronServerValidator.php b/src/Validator/CronServerValidator.php index 5c4b9cbdcc..5d2ce9fc46 100644 --- a/src/Validator/CronServerValidator.php +++ b/src/Validator/CronServerValidator.php @@ -5,8 +5,10 @@ namespace Drupal\automatic_updates\Validator; use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\Event\ReadinessCheckEvent; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Http\RequestStack; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Url; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreOperationStageEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -37,6 +39,13 @@ final class CronServerValidator implements EventSubscriberInterface { */ protected $configFactory; + /** + * The module handler service. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + /** * The type of interface between the web server and the PHP runtime. * @@ -54,10 +63,13 @@ final class CronServerValidator implements EventSubscriberInterface { * The request stack service. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory service. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler service. */ - public function __construct(RequestStack $request_stack, ConfigFactoryInterface $config_factory) { + public function __construct(RequestStack $request_stack, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler) { $this->request = $request_stack->getCurrentRequest(); $this->configFactory = $config_factory; + $this->moduleHandler = $module_handler; } /** @@ -79,13 +91,22 @@ final class CronServerValidator implements EventSubscriberInterface { $alternate_port = intval($alternate_port) ?: $current_port; if (static::$serverApi === 'cli-server' && $current_port === $alternate_port) { - // @todo Explain how to fix this problem on our help page, and link to it, - // in https://drupal.org/i/3312669. - $event->addError([ - $this->t('Your site appears to be running on the built-in PHP web server on port @port. Drupal cannot be automatically updated with this configuration unless the site can also be reached on an alternate port.', [ - '@port' => $current_port, - ]), + $message = $this->t('Your site appears to be running on the built-in PHP web server on port @port. Drupal cannot be automatically updated with this configuration unless the site can also be reached on an alternate port.', [ + '@port' => $current_port, ]); + if ($this->moduleHandler->moduleExists('help')) { + $url = Url::fromRoute('help.page') + ->setRouteParameter('name', 'automatic_updates') + ->setOption('fragment', 'cron-alternate-port') + ->toString(); + + $message = $this->t('@message See <a href=":url">the Automatic Updates help page</a> for more information on how to resolve this.', [ + '@message' => $message, + ':url' => $url, + ]); + } + + $event->addError([$message]); } } diff --git a/tests/src/Kernel/ReadinessValidation/CronServerValidatorTest.php b/tests/src/Kernel/ReadinessValidation/CronServerValidatorTest.php index 5c13b4de05..ab0fa728e7 100644 --- a/tests/src/Kernel/ReadinessValidation/CronServerValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/CronServerValidatorTest.php @@ -5,6 +5,7 @@ namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation; use Drupal\automatic_updates\CronUpdater; use Drupal\automatic_updates\Validator\CronServerValidator; use Drupal\Core\Logger\RfcLogLevel; +use Drupal\Core\Url; use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\ValidationResult; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; @@ -68,7 +69,7 @@ class CronServerValidatorTest extends AutomaticUpdatesKernelTestBase { } /** - * Tests server configuration validation for unattended updates. + * Tests server validation for unattended updates. * * @param bool $alternate_port * Whether or not an alternate port should be set. @@ -118,4 +119,39 @@ class CronServerValidatorTest extends AutomaticUpdatesKernelTestBase { } } + /** + * Tests server validation for unattended updates with Help enabled. + * + * @param bool $alternate_port + * Whether or not an alternate port should be set. + * @param string $server_api + * The value of the PHP_SAPI constant, as known to the validator. + * @param string[] $cron_modes + * The cron modes to test with. Can contain any of + * \Drupal\automatic_updates\CronUpdater::DISABLED, + * \Drupal\automatic_updates\CronUpdater::SECURITY, and + * \Drupal\automatic_updates\CronUpdater::ALL. + * @param \Drupal\package_manager\ValidationResult[] $expected_results + * The expected validation results. + * + * @dataProvider providerCronServerValidation + */ + public function testHelpLink(bool $alternate_port, string $server_api, array $cron_modes, array $expected_results): void { + $this->enableModules(['help']); + + $url = Url::fromRoute('help.page') + ->setRouteParameter('name', 'automatic_updates') + ->setOption('fragment', 'cron-alternate-port') + ->toString(); + + foreach ($expected_results as $i => $result) { + $messages = []; + foreach ($result->getMessages() as $message) { + $messages[] = "$message See <a href=\"$url\">the Automatic Updates help page</a> for more information on how to resolve this."; + } + $expected_results[$i] = ValidationResult::createError($messages); + } + $this->testCronServerValidation($alternate_port, $server_api, $cron_modes, $expected_results); + } + } -- GitLab