diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php index a30fc7a80d2cbead75e222d986a1934343562b52..88213fafd7262fb1df9fe15104745622b6fabe14 100644 --- a/core/lib/Drupal/Core/Extension/module.api.php +++ b/core/lib/Drupal/Core/Extension/module.api.php @@ -1187,6 +1187,94 @@ function hook_requirements_alter(array &$requirements): void { unset($requirements['foo']); } +/** + * Check runtime requirements and do status reporting. + * + * Requirements are displayed on the 'Status report' (/admin/reports/status). + * + * Runtime requirements do not impact installation or updates of modules that + * define them. These requirements are only used to display information on the + * status report but do not impact site behavior. They can be used for more + * general status information like maintenance tasks and security issues. + * The returned requirements will be listed on the status report in the + * administration section, with an indication of the severity level. + * Moreover, any requirement with a severity of REQUIREMENT_ERROR will result in + * a notice on the 'Configuration' administration page (/admin/config). + * + * @return array + * An associative array where the keys are arbitrary but must be unique (it + * is suggested to use the module short name as a prefix) and the values are + * themselves associative arrays with the following elements: + * - title: The name of the requirement. + * - value: The current value (e.g., version, time, level, etc). + * - description: The description of the requirement/status. + * - severity: (optional) The requirement's severity level, one of: + * - REQUIREMENT_INFO: For info only. + * - REQUIREMENT_OK: The requirement is satisfied. + * - REQUIREMENT_WARNING: The requirement failed with a warning. + * - REQUIREMENT_ERROR: The requirement failed with an error. + * Defaults to REQUIREMENT_OK. + */ +function hook_runtime_requirements(): array { + $requirements = []; + + // Report Drupal version + $requirements['drupal'] = [ + 'title' => t('Drupal'), + 'value' => \Drupal::VERSION, + 'severity' => REQUIREMENT_INFO, + ]; + + // Test PHP version + $requirements['php'] = [ + 'title' => t('PHP'), + 'value' => Link::fromTextAndUrl(phpversion(), Url::fromRoute('system.php'))->toString(), + ]; + if (version_compare(phpversion(), \Drupal::MINIMUM_PHP) < 0) { + $requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version.', ['%version' => \Drupal::MINIMUM_PHP]); + $requirements['php']['severity'] = REQUIREMENT_ERROR; + } + + // Report cron status + $cron_last = \Drupal::state()->get('system.cron_last'); + $requirements['cron']['title'] = t('Cron maintenance tasks'); + if (is_numeric($cron_last)) { + $requirements['cron']['description'] = ''; + $requirements['cron']['value'] = t('Last run @time ago', ['@time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)]); + } + else { + $requirements['cron']['description'] = t('Cron has not run. It appears cron jobs have not been setup on your system. Check the help pages for <a href=":url">configuring cron jobs</a>.', [':url' => 'https://www.drupal.org/docs/administering-a-drupal-site/cron-automated-tasks/cron-automated-tasks-overview']); + $requirements['cron']['value'] = t('Never run'); + $requirements['cron']['severity'] = REQUIREMENT_ERROR; + } + $requirements['cron']['description'] .= ' ' . t('You can <a href=":cron">run cron manually</a>.', [':cron' => Url::fromRoute('system.run_cron')->toString()]); + + return $requirements; +} + +/** + * Alters runtime requirements data. + * + * Implementations are able to alter the title, value, description or the + * severity of certain requirements defined by hook_requirements() and + * hook_runtime_requirements() implementations, or even remove such entries. + * + * @param array $requirements + * The requirements data to be altered. + * + * @see hook_runtime_requirements() + */ +function hook_runtime_requirements_alter(array &$requirements): void { + // Change the title from 'PHP' to 'PHP version'. + $requirements['php']['title'] = t('PHP version'); + + // Decrease the 'update status' requirement severity from warning to info. + $requirements['update status']['severity'] = REQUIREMENT_INFO; + + // Remove a requirements entry. + unset($requirements['foo']); +} + /** * @} End of "addtogroup hooks". */ diff --git a/core/modules/file/file.install b/core/modules/file/file.install index bce27470f19d6eb8c342c28ef390cf3b292243f0..18f31ada4c4cf9fc7b47426784f293a4950ca2dc 100644 --- a/core/modules/file/file.install +++ b/core/modules/file/file.install @@ -57,54 +57,6 @@ function file_schema(): array { return $schema; } -/** - * Implements hook_requirements(). - * - * Display information about getting upload progress bars working. - */ -function file_requirements($phase): array { - $requirements = []; - - if ($phase != 'runtime') { - return $requirements; - } - - $server_software = \Drupal::request()->server->get('SERVER_SOFTWARE', ''); - - // Get the web server identity. - $is_nginx = preg_match("/Nginx/i", $server_software); - $is_apache = preg_match("/Apache/i", $server_software); - $fastcgi = $is_apache && ((str_contains($server_software, 'mod_fastcgi') || str_contains($server_software, 'mod_fcgi'))); - - // Check the uploadprogress extension is loaded. - if (extension_loaded('uploadprogress')) { - $value = t('Enabled (<a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress</a>)'); - $description = NULL; - } - else { - $value = t('Not enabled'); - $description = t('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.'); - } - - // Adjust the requirement depending on what the server supports. - if (!$is_apache && !$is_nginx) { - $value = t('Not enabled'); - $description = t('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php or Nginx with PHP-FPM.'); - } - elseif ($fastcgi) { - $value = t('Not enabled'); - $description = t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.'); - } - - $requirements['file_progress'] = [ - 'title' => t('Upload progress'), - 'value' => $value, - 'description' => $description, - ]; - - return $requirements; -} - /** * Implements hook_update_last_removed(). */ diff --git a/core/modules/file/src/Hook/FileRequirements.php b/core/modules/file/src/Hook/FileRequirements.php new file mode 100644 index 0000000000000000000000000000000000000000..df6ae2f42a11717328ddce5eda519c9e9be3d544 --- /dev/null +++ b/core/modules/file/src/Hook/FileRequirements.php @@ -0,0 +1,57 @@ +<?php + +namespace Drupal\file\Hook; + +use Drupal\Core\Hook\Attribute\Hook; +use Drupal\Core\StringTranslation\StringTranslationTrait; + +/** + * Requirements for the File module. + */ +class FileRequirements { + + use StringTranslationTrait; + + /** + * Implements hook_runtime_requirements(). + */ + #[Hook('runtime_requirements')] + public function runtime(): array { + $requirements = []; + $server_software = \Drupal::request()->server->get('SERVER_SOFTWARE', ''); + + // Get the web server identity. + $is_nginx = preg_match("/Nginx/i", $server_software); + $is_apache = preg_match("/Apache/i", $server_software); + $fastcgi = $is_apache && ((str_contains($server_software, 'mod_fastcgi') || str_contains($server_software, 'mod_fcgi'))); + + // Check the uploadprogress extension is loaded. + if (extension_loaded('uploadprogress')) { + $value = $this->t('Enabled (<a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress</a>)'); + $description = NULL; + } + else { + $value = $this->t('Not enabled'); + $description = $this->t('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.'); + } + + // Adjust the requirement depending on what the server supports. + if (!$is_apache && !$is_nginx) { + $value = $this->t('Not enabled'); + $description = $this->t('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php or Nginx with PHP-FPM.'); + } + elseif ($fastcgi) { + $value = $this->t('Not enabled'); + $description = $this->t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.'); + } + + $requirements['file_progress'] = [ + 'title' => $this->t('Upload progress'), + 'value' => $value, + 'description' => $description, + ]; + + return $requirements; + } + +} diff --git a/core/modules/file/tests/src/Kernel/RequirementsTest.php b/core/modules/file/tests/src/Kernel/RequirementsTest.php index 224616e089b8bc57714db9a0c42da563953b9a81..3cf85346bca4406853319262abfe83be2ccbf185 100644 --- a/core/modules/file/tests/src/Kernel/RequirementsTest.php +++ b/core/modules/file/tests/src/Kernel/RequirementsTest.php @@ -32,11 +32,10 @@ public function testUploadRequirements(): void { /** @var \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler */ $moduleHandler = $this->container->get('module_handler'); - $moduleHandler->loadInclude('file', 'install'); // Test unspecified server software. $this->setServerSoftware(NULL); - $requirements = \file_requirements('runtime'); + $requirements = $moduleHandler->invoke('file', 'runtime_requirements'); $this->assertNotEmpty($requirements); $this->assertEquals('Upload progress', (string) $requirements['file_progress']['title']); @@ -45,21 +44,21 @@ public function testUploadRequirements(): void { // Test Apache + mod_php. $this->setServerSoftware('Apache mod_php'); - $requirements = \file_requirements('runtime'); + $requirements = $moduleHandler->invoke('file', 'runtime_requirements'); $this->assertNotEmpty($requirements); $this->assertEquals('Not enabled', (string) $requirements['file_progress']['value']); $this->assertEquals('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.', (string) $requirements['file_progress']['description']); // Test Apache + mod_fastcgi. $this->setServerSoftware('Apache mod_fastcgi'); - $requirements = \file_requirements('runtime'); + $requirements = $moduleHandler->invoke('file', 'runtime_requirements'); $this->assertNotEmpty($requirements); $this->assertEquals('Not enabled', (string) $requirements['file_progress']['value']); $this->assertEquals('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.', (string) $requirements['file_progress']['description']); // Test Nginx. $this->setServerSoftware('Nginx'); - $requirements = \file_requirements('runtime'); + $requirements = $moduleHandler->invoke('file', 'runtime_requirements'); $this->assertNotEmpty($requirements); $this->assertEquals('Not enabled', (string) $requirements['file_progress']['value']); $this->assertEquals('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.', (string) $requirements['file_progress']['description']); diff --git a/core/modules/system/src/SystemManager.php b/core/modules/system/src/SystemManager.php index 9e3d61ef3f9f173b85c929f765f604551a03d47b..44305e80ef3044e5fc6a3666ab0ed35b9c42c1d7 100644 --- a/core/modules/system/src/SystemManager.php +++ b/core/modules/system/src/SystemManager.php @@ -107,7 +107,10 @@ public function listRequirements() { // Check run-time requirements and status information. $requirements = $this->moduleHandler->invokeAll('requirements', ['runtime']); + $runtime_requirements = $this->moduleHandler->invokeAll('runtime_requirements'); + $requirements = array_merge($requirements, $runtime_requirements); $this->moduleHandler->alter('requirements', $requirements); + $this->moduleHandler->alter('runtime_requirements', $requirements); uasort($requirements, function ($a, $b) { if (!isset($a['weight'])) { if (!isset($b['weight'])) { diff --git a/core/modules/system/tests/modules/module_runtime_requirements/module_runtime_requirements.info.yml b/core/modules/system/tests/modules/module_runtime_requirements/module_runtime_requirements.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..0c1567542e67c743f8d197b229eb1eb36596183a --- /dev/null +++ b/core/modules/system/tests/modules/module_runtime_requirements/module_runtime_requirements.info.yml @@ -0,0 +1,5 @@ +name: 'Test Hook Runtime Requirements' +type: module +description: 'Support module for testing hook_runtime_requirements().' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/module_runtime_requirements/src/Hook/ModuleRuntimeRequirementsHooks.php b/core/modules/system/tests/modules/module_runtime_requirements/src/Hook/ModuleRuntimeRequirementsHooks.php new file mode 100644 index 0000000000000000000000000000000000000000..0fa4b2f6f804235c6e3fd0a0e82fb8030b57cb70 --- /dev/null +++ b/core/modules/system/tests/modules/module_runtime_requirements/src/Hook/ModuleRuntimeRequirementsHooks.php @@ -0,0 +1,51 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\module_runtime_requirements\Hook; + +use Drupal\Core\Hook\Attribute\Hook; +use Drupal\Core\StringTranslation\StringTranslationTrait; + +/** + * Hook implementations for module_runtime_requirements. + */ +class ModuleRuntimeRequirementsHooks { + + use StringTranslationTrait; + + /** + * Implements hook_runtime_requirements(). + */ + #[Hook('runtime_requirements')] + public function runtimeRequirements(): array { + return [ + 'test.runtime.error' => [ + 'title' => $this->t('RuntimeError'), + 'value' => $this->t('None'), + 'description' => $this->t('Runtime Error.'), + 'severity' => REQUIREMENT_ERROR, + ], + 'test.runtime.error.alter' => [ + 'title' => $this->t('RuntimeError'), + 'value' => $this->t('None'), + 'description' => $this->t('Runtime Error.'), + 'severity' => REQUIREMENT_ERROR, + ], + ]; + } + + /** + * Implements hook_runtime_requirements_alter(). + */ + #[Hook('runtime_requirements_alter')] + public function runtimeRequirementsAlter(array &$requirements): void { + $requirements['test.runtime.error.alter'] = [ + 'title' => $this->t('RuntimeWarning'), + 'value' => $this->t('None'), + 'description' => $this->t('Runtime Warning.'), + 'severity' => REQUIREMENT_WARNING, + ]; + } + +} diff --git a/core/modules/system/tests/src/Kernel/System/RunTimeRequirementsTest.php b/core/modules/system/tests/src/Kernel/System/RunTimeRequirementsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6c5577601166cd4285e26d106c07807b7d0d28a4 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/System/RunTimeRequirementsTest.php @@ -0,0 +1,49 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\system\Kernel\System; + +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\KernelTests\KernelTestBase; + +/** + * Tests the effectiveness of hook_runtime_requirements(). + * + * @group system + */ +class RunTimeRequirementsTest extends KernelTestBase { + + use StringTranslationTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = ['system']; + + /** + * Tests hook_runtime_requirements() and hook_runtime_requirements_alter(). + */ + public function testRuntimeRequirements(): void { + // Enable the test module. + \Drupal::service('module_installer')->install(['module_runtime_requirements']); + $testRequirements = [ + 'title' => $this->t('RuntimeError'), + 'value' => $this->t('None'), + 'description' => $this->t('Runtime Error.'), + 'severity' => REQUIREMENT_ERROR, + ]; + $requirements = \Drupal::service('system.manager')->listRequirements()['test.runtime.error']; + $this->assertEquals($testRequirements, $requirements); + + $testRequirementsAlter = [ + 'title' => $this->t('RuntimeWarning'), + 'value' => $this->t('None'), + 'description' => $this->t('Runtime Warning.'), + 'severity' => REQUIREMENT_WARNING, + ]; + $requirementsAlter = \Drupal::service('system.manager')->listRequirements()['test.runtime.error.alter']; + $this->assertEquals($testRequirementsAlter, $requirementsAlter); + } + +}