diff --git a/automatic_updates.install b/automatic_updates.install index 59e5b49b176ca6655600ca1a6ae1fcb644c0f337..9a2e68821f96cd009333bbe65772015915590b07 100644 --- a/automatic_updates.install +++ b/automatic_updates.install @@ -31,7 +31,7 @@ function automatic_updates_requirements($phase) { ]; } if ($phase !== 'runtime') { - return; + return NULL; } $requirements = []; diff --git a/automatic_updates.module b/automatic_updates.module index 98c5b52d2a746905f1edef2c5f1430475a929f75..3959d1769ec50ddafb6ea39f44b5fc7308f11c64 100644 --- a/automatic_updates.module +++ b/automatic_updates.module @@ -68,6 +68,13 @@ function automatic_updates_page_top(array &$page_top) { * Implements hook_cron(). */ function automatic_updates_cron() { + $state = \Drupal::state(); + $request_time = \Drupal::time()->getRequestTime(); + $last_check = $state->get('automatic_updates.last_check', 0); + // Only allow cron to run once every hour. + if (($request_time - $last_check) < 3600) { + return; + } // In-place updates won't function for dev releases of Drupal core. $dev_core = strpos(\Drupal::VERSION, '-dev') !== FALSE; /** @var \Drupal\Core\Config\ImmutableConfig $config */ @@ -104,6 +111,7 @@ function automatic_updates_cron() { foreach ($checker->getCategories() as $category) { $checker->run($category); } + $state->set('automatic_updates.last_check', \Drupal::time()->getCurrentTime()); } /** diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml index 2361032411f6b75584d685e9bdbe4303a08aee98..35402f431bf4a7e29a636b1309f48dd92090a386 100644 --- a/automatic_updates.services.yml +++ b/automatic_updates.services.yml @@ -56,8 +56,8 @@ services: automatic_updates.readonly_checker: class: Drupal\automatic_updates\ReadinessChecker\ReadOnlyFilesystem arguments: - - '@logger.channel.automatic_updates' - '@app.root' + - '@logger.channel.automatic_updates' - '@file_system' tags: - { name: readiness_checker, priority: 100, category: error } diff --git a/src/Controller/ReadinessCheckerController.php b/src/Controller/ReadinessCheckerController.php index 29f253d0c4c7ac5e55cd0b8ddabc8ea4ce6d7a45..869c6b25e844a19fd55223c4af93746525a6a62e 100644 --- a/src/Controller/ReadinessCheckerController.php +++ b/src/Controller/ReadinessCheckerController.php @@ -51,8 +51,9 @@ class ReadinessCheckerController extends ControllerBase { public function run() { $messages = []; foreach ($this->checker->getCategories() as $category) { - $messages = array_merge($this->checker->run($category), $messages); + $messages[] = $this->checker->run($category); } + $messages = array_merge(...$messages); if (empty($messages)) { $this->messenger()->addStatus($this->t('No issues found. Your site is completely ready for <a href="@readiness_checks">automatic updates</a>.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks'])); } diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php index d4bbf8d1580a31edc0e892a0e44279baf60290a5..b18a9d404200c471afd79474005ad1a907b6d111 100644 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -76,7 +76,7 @@ class SettingsForm extends ConfigFormBase { $form['psa'] = [ '#type' => 'details', - '#title' => $this->t('Public serivice announcements'), + '#title' => $this->t('Public service announcements'), '#open' => TRUE, ]; $form['psa']['description'] = [ diff --git a/src/IgnoredPathsTrait.php b/src/IgnoredPathsTrait.php index 31ae1a288f0da30fe6d9392487033e1a4106d1d0..6ae7c439297565256f43edeb8a6ba9e844e21cb5 100644 --- a/src/IgnoredPathsTrait.php +++ b/src/IgnoredPathsTrait.php @@ -21,6 +21,7 @@ trait IgnoredPathsTrait { if ($this->getPathMatcher()->matchPath($file_path, $paths)) { return TRUE; } + return FALSE; } /** diff --git a/src/ProjectInfoTrait.php b/src/ProjectInfoTrait.php index 28bb3514682ddedf885d4befdd74a160842c286b..845a0648c0a2537f5a3b5a5605a83ee0635af243 100644 --- a/src/ProjectInfoTrait.php +++ b/src/ProjectInfoTrait.php @@ -70,7 +70,7 @@ trait ProjectInfoTrait { return $info['version']; } // Handle experimental modules from core. - if (strpos($info['install path'], "core") === 0) { + if (strpos($info['install path'], 'core') === 0) { return $this->getExtensionList('module')->get('system')->info['version']; } \Drupal::logger('automatic_updates')->error('Version cannot be located for @extension', ['@extension' => $extension_name]); @@ -145,6 +145,7 @@ trait ProjectInfoTrait { catch (\Throwable $exception) { \Drupal::logger('automatic_updates')->error('Composer.json could not be located for @extension', ['@extension' => $extension_name]); } + return NULL; } } diff --git a/src/ReadinessChecker/DiskSpace.php b/src/ReadinessChecker/DiskSpace.php index 7ce40c13f828be83a4c9306cecdd05201ed2c8f7..984031e030c440b2c783ece7d17085c3301b0a34 100644 --- a/src/ReadinessChecker/DiskSpace.php +++ b/src/ReadinessChecker/DiskSpace.php @@ -2,6 +2,8 @@ namespace Drupal\automatic_updates\ReadinessChecker; +use Drupal\Component\FileSystem\FileSystem as FileSystemComponent; + /** * Disk space checker. */ @@ -52,6 +54,13 @@ class DiskSpace extends Filesystem { '@space' => static::MINIMUM_DISK_SPACE / static::MEGABYTE_DIVISOR, ]); } + $temp = FileSystemComponent::getOsTemporaryDirectory(); + if (disk_free_space($temp) < static::MINIMUM_DISK_SPACE) { + $messages[] = $this->t('Directory "@temp" has insufficient space. There must be at least @space megabytes free.', [ + '@temp' => $temp, + '@space' => static::MINIMUM_DISK_SPACE / static::MEGABYTE_DIVISOR, + ]); + } return $messages; } diff --git a/src/ReadinessChecker/ModifiedFiles.php b/src/ReadinessChecker/ModifiedFiles.php index 12dda13ddb30ade415aaee55ea2430df2afa6f67..a4ede9dec96ae9ed2c5ba8916a6335aab033cb29 100644 --- a/src/ReadinessChecker/ModifiedFiles.php +++ b/src/ReadinessChecker/ModifiedFiles.php @@ -65,9 +65,9 @@ class ModifiedFiles implements ReadinessCheckerInterface { */ public function __construct(ModifiedFilesInterface $modified_files, ExtensionList $modules, ExtensionList $profiles, ExtensionList $themes) { $this->modifiedFiles = $modified_files; - $this->modules = $modules; - $this->profiles = $profiles; - $this->themes = $themes; + $this->module = $modules; + $this->profile = $profiles; + $this->theme = $themes; } /** diff --git a/src/ReadinessChecker/ReadOnlyFilesystem.php b/src/ReadinessChecker/ReadOnlyFilesystem.php index c82cc8d1e3293f7e7980baa0c2554629179c4866..82cc3545b97ddc8be6a5ab3e0b995e9c8a474288 100644 --- a/src/ReadinessChecker/ReadOnlyFilesystem.php +++ b/src/ReadinessChecker/ReadOnlyFilesystem.php @@ -29,16 +29,16 @@ class ReadOnlyFilesystem extends Filesystem { /** * ReadOnlyFilesystem constructor. * - * @param \Psr\Log\LoggerInterface $logger - * The logger. * @param string $app_root * The app root. + * @param \Psr\Log\LoggerInterface $logger + * The logger. * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system service. */ - public function __construct(LoggerInterface $logger, $app_root, FileSystemInterface $file_system) { + public function __construct($app_root, LoggerInterface $logger, FileSystemInterface $file_system) { + parent::__construct($app_root); $this->logger = $logger; - $this->rootPath = (string) $app_root; $this->fileSystem = $file_system; } diff --git a/src/Services/AutomaticUpdatesPsa.php b/src/Services/AutomaticUpdatesPsa.php index 31c4d0ae9d1129efc67587a1ff930db7d4baaa43..4160282a020bf3da479b94368982aa6432acbb5b 100644 --- a/src/Services/AutomaticUpdatesPsa.php +++ b/src/Services/AutomaticUpdatesPsa.php @@ -135,8 +135,8 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { } try { - $json_payload = json_decode($response); - if (!is_null($json_payload)) { + $json_payload = json_decode($response, FALSE); + if ($json_payload !== NULL) { foreach ($json_payload as $json) { if ($json->is_psa && ($json->type === 'core' || $this->isValidExtension($json->type, $json->project))) { $messages[] = $this->message($json->title, $json->link); @@ -192,15 +192,15 @@ class AutomaticUpdatesPsa implements AutomaticUpdatesPsaInterface { */ protected function contribParser(array &$messages, $json) { $extension_version = $this->{$json->type}->getAllAvailableInfo()[$json->project]['version']; - $json->insecure = array_filter(array_map(function ($version) { + $json->insecure = array_filter(array_map(static function ($version) { $version_array = explode('-', $version, 2); if ($version_array && $version_array[0] === \Drupal::CORE_COMPATIBILITY) { return isset($version_array[1]) ? $version_array[1] : NULL; } - elseif (count($version_array) === 1) { + if (count($version_array) === 1) { return $version_array[0]; } - elseif (count($version_array) === 2 && $version_array[1] === 'dev') { + if (count($version_array) === 2 && $version_array[1] === 'dev') { return $version; } }, $json->insecure)); diff --git a/src/Services/InPlaceUpdate.php b/src/Services/InPlaceUpdate.php index f1d6daf8ff0e80e50019a530995776ac16778e6d..1194a24d642f08bb4b6a0224c76eb9eda4b09211 100644 --- a/src/Services/InPlaceUpdate.php +++ b/src/Services/InPlaceUpdate.php @@ -120,7 +120,7 @@ class InPlaceUpdate implements UpdateInterface { $this->fileSystem = $file_system; $this->httpClient = $http_client; $this->rootPath = (string) $app_root; - $this->vendorPath = $this->rootPath . DIRECTORY_SEPARATOR . 'vendor'; + $this->vendorPath = $this->rootPath . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR; $project_root = drupal_get_path('module', 'automatic_updates'); require_once $project_root . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; } @@ -173,7 +173,7 @@ class InPlaceUpdate implements UpdateInterface { protected function getArchive($project_name, $from_version, $to_version) { $quasi_patch = $this->getQuasiPatchFileName($project_name, $from_version, $to_version); $url = $this->buildUrl($project_name, $quasi_patch); - $temp_directory = $this->getTempDirectory(); + $temp_directory = FileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR; $destination = $this->fileSystem->getDestinationFilename($temp_directory . $quasi_patch, FileSystemInterface::EXISTS_REPLACE); $this->doGetResource($url, $destination); $csig_file = $quasi_patch . '.csig'; @@ -226,6 +226,7 @@ class InPlaceUpdate implements UpdateInterface { } $this->stripFileDirectoryPath($archive_file); } + unset($archive_file); if ($intersection = array_intersect($files, $archive_files)) { $this->logger->error('Can not update because %count files are modified: %paths', [ '%count' => count($intersection), @@ -524,7 +525,7 @@ class InPlaceUpdate implements UpdateInterface { * The real path of a file. */ protected function getProjectRealPath($file_path, $project_root) { - if (substr($file_path, 0, 6) === 'vendor/') { + if (strpos($file_path, 'vendor' . DIRECTORY_SEPARATOR) === 0) { return $this->vendorPath . substr($file_path, 7); } return rtrim($project_root, '/\\') . DIRECTORY_SEPARATOR . $file_path; @@ -569,7 +570,7 @@ class InPlaceUpdate implements UpdateInterface { } /** - * Clear opcode cache on successful update. + * Clear Opcode cache on successful update. */ protected function clearOpcodeCache() { if (function_exists('opcache_reset')) { diff --git a/src/Services/Notify.php b/src/Services/Notify.php index 30e8294c60c5e152136ba0dd68fc61c54b3a3f71..41b84a50b3ec18027a64d1ee79a518a0198c4416 100644 --- a/src/Services/Notify.php +++ b/src/Services/Notify.php @@ -150,6 +150,9 @@ class Notify implements NotifyInterface { * The email address where the message will be sent. * @param array $params * Parameters to build the email. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ protected function doSend($to, array $params) { $users = $this->entityTypeManager->getStorage('user') diff --git a/tests/modules/test_automatic_updates/src/Controller/ModifiedFilesController.php b/tests/modules/test_automatic_updates/src/Controller/ModifiedFilesController.php index e1c6622f5cca0ae190c4f2e4417037cd45453ff4..87874b7132919b1ceb46b58f1af30a5080a4cb1a 100644 --- a/tests/modules/test_automatic_updates/src/Controller/ModifiedFilesController.php +++ b/tests/modules/test_automatic_updates/src/Controller/ModifiedFilesController.php @@ -56,14 +56,14 @@ class ModifiedFilesController extends ControllerBase { // Special edge case for core. if ($project_type === 'core') { $infos = $this->getInfos('module'); - $extensions = array_filter($infos, function (array $info) { + $extensions = array_filter($infos, static function (array $info) { return $info['project'] === 'drupal'; }); } // Filter for the main project. else { $infos = $this->getInfos($project_type); - $extensions = array_filter($infos, function (array $info) use ($extension, $project_type) { + $extensions = array_filter($infos, static function (array $info) use ($extension, $project_type) { return $info['install path'] === "{$project_type}s/contrib/$extension"; }); } diff --git a/tests/src/Build/InPlaceUpdateTest.php b/tests/src/Build/InPlaceUpdateTest.php index 4e147086cf8848e7462fd0bbbe19bb78f0f0a806..59d0b2f9638d3d5b4ab6dfed327dbea1a15dc678 100644 --- a/tests/src/Build/InPlaceUpdateTest.php +++ b/tests/src/Build/InPlaceUpdateTest.php @@ -258,7 +258,7 @@ class InPlaceUpdateTest extends QuickStartTestBase { $this->deletions = []; $http_client = new Client(); $filesystem = new SymfonyFilesystem(); - $this->deletionsDestination = DrupalFileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . "$project-" . rand(10000, 99999) . microtime(TRUE); + $this->deletionsDestination = DrupalFileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . "$project-" . mt_rand(10000, 99999) . microtime(TRUE); $filesystem->mkdir($this->deletionsDestination); $file_name = "$project-$from_version-to-$to_version.zip"; $zip_file = $this->deletionsDestination . DIRECTORY_SEPARATOR . $file_name; diff --git a/tests/src/Kernel/ReadinessChecker/CronFrequencyTest.php b/tests/src/Kernel/ReadinessChecker/CronFrequencyTest.php index a7d16441d33d24288c81b0385fb8529e8e95c89f..6bef7cc2640262e842da08b9cdaf87fe87db1b3a 100644 --- a/tests/src/Kernel/ReadinessChecker/CronFrequencyTest.php +++ b/tests/src/Kernel/ReadinessChecker/CronFrequencyTest.php @@ -39,7 +39,7 @@ class CronFrequencyTest extends KernelTestBase { ->set('interval', 21600) ->save(); $messages = $this->container->get('automatic_updates.cron_frequency')->run(); - $this->assertEquals('Cron is not set to run frequently enough. <a href="/admin/config/system/cron">Configure it</a> to run at least every 3 hours or disable automated cron and run it via an external scheduling system.', $messages[0]); + self::assertEquals('Cron is not set to run frequently enough. <a href="/admin/config/system/cron">Configure it</a> to run at least every 3 hours or disable automated cron and run it via an external scheduling system.', $messages[0]); } } diff --git a/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php b/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php index 4e573f55d94d05914c6d86a6c20566c2cac12b41..a3b499947ff629729c2ce4f3239116ac21d096a3 100644 --- a/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php +++ b/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php @@ -33,12 +33,12 @@ class DiskSpaceTest extends KernelTestBase { // Out of space. $disk_space = new TestDiskSpace($this->container->get('app.root')); $messages = $disk_space->run(); - $this->assertCount(1, $messages); + $this->assertCount(2, $messages); // Out of space not the same logical disk. $disk_space = new TestDiskSpaceNonSameDisk($this->container->get('app.root')); $messages = $disk_space->run(); - $this->assertCount(2, $messages); + $this->assertCount(3, $messages); } } diff --git a/tests/src/Kernel/ReadinessChecker/OpcodeCacheTest.php b/tests/src/Kernel/ReadinessChecker/OpcodeCacheTest.php index 3f211056425179a146b81e81bd2bf501c57210e6..385ee77ec09c75dfb73e0591e427e3f7ddcb0dee 100644 --- a/tests/src/Kernel/ReadinessChecker/OpcodeCacheTest.php +++ b/tests/src/Kernel/ReadinessChecker/OpcodeCacheTest.php @@ -28,7 +28,7 @@ class OpcodeCacheTest extends KernelTestBase { $messages = $this->container->get('automatic_updates.opcode_cache')->run(); if ($failure) { $this->assertNotEmpty($messages); - $this->assertEquals((string) $messages[0], 'Automatic updates cannot run via CLI when opcode file cache is enabled.'); + self::assertEquals((string) $messages[0], 'Automatic updates cannot run via CLI when opcode file cache is enabled.'); } else { $this->assertEmpty($messages); diff --git a/tests/src/Kernel/ReadinessChecker/PendingDbUpdatesTest.php b/tests/src/Kernel/ReadinessChecker/PendingDbUpdatesTest.php index e2c2104ed4fd526250f91bd5bb839728b8e93dab..2a93b5e6415ccf20fb782e757722b146ec6470ec 100644 --- a/tests/src/Kernel/ReadinessChecker/PendingDbUpdatesTest.php +++ b/tests/src/Kernel/ReadinessChecker/PendingDbUpdatesTest.php @@ -27,7 +27,7 @@ class PendingDbUpdatesTest extends KernelTestBase { $this->assertEmpty($messages); $messages = (new TestPendingDbUpdates())->run(); - $this->assertEquals('There are pending database updates, therefore updates cannot be applied. Please run update.php.', $messages[0]); + self::assertEquals('There are pending database updates, therefore updates cannot be applied. Please run update.php.', $messages[0]); } } diff --git a/tests/src/Kernel/ReadinessChecker/PhpSapiTest.php b/tests/src/Kernel/ReadinessChecker/PhpSapiTest.php index 1bc09c10781bb17edd6e747a6c1c016e925cc424..32052ac30b0ca3f8f195f773301a4bb79ebbdbe4 100644 --- a/tests/src/Kernel/ReadinessChecker/PhpSapiTest.php +++ b/tests/src/Kernel/ReadinessChecker/PhpSapiTest.php @@ -29,7 +29,7 @@ class PhpSapiTest extends KernelTestBase { $this->container->get('state')->set('automatic_updates.php_sapi', 'foo'); $messages = $this->container->get('automatic_updates.php_sapi')->run(); - $this->assertEquals('PHP changed from running as "foo" to "cli". This can lead to inconsistent and misleading results.', $messages[0]); + self::assertEquals('PHP changed from running as "foo" to "cli". This can lead to inconsistent and misleading results.', $messages[0]); } } diff --git a/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php b/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php index 1c62a96c61d9fc044501996e542dcc3212c34c23..567a1f63c39d8165bc551a33ba319e3b4beb747b 100644 --- a/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php +++ b/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php @@ -57,8 +57,8 @@ class ReadOnlyTest extends KernelTestBase { $app_root = $this->container->get('app.root'); $readonly = $this->getMockBuilder(ReadOnlyFilesystem::class) ->setConstructorArgs([ - $this->createMock(LoggerInterface::class), $app_root, + $this->createMock(LoggerInterface::class), $filesystem, ]) ->setMethods([ @@ -88,13 +88,13 @@ class ReadOnlyTest extends KernelTestBase { // Test can't locate drupal. $messages = $readonly->run(); - $this->assertEquals([$this->t('The web root could not be located.')], $messages); + self::assertEquals([$this->t('The web root could not be located.')], $messages); // Test same logical disk. $expected_messages = []; $expected_messages[] = $this->t('Logical disk at "@app_root" is read only. Updates to Drupal cannot be applied against a read only file system.', ['@app_root' => $app_root]); $messages = $readonly->run(); - $this->assertEquals($expected_messages, $messages); + self::assertEquals($expected_messages, $messages); // Test read-only. $expected_messages = []; @@ -105,11 +105,11 @@ class ReadOnlyTest extends KernelTestBase { '@vendor' => $app_root . DIRECTORY_SEPARATOR . 'vendor', ]); $messages = $readonly->run(); - $this->assertEquals($expected_messages, $messages); + self::assertEquals($expected_messages, $messages); // Test delete fails. $messages = $readonly->run(); - $this->assertEquals($expected_messages, $messages); + self::assertEquals($expected_messages, $messages); } } diff --git a/tests/src/Kernel/ReadinessChecker/SupportedPhpVersionTest.php b/tests/src/Kernel/ReadinessChecker/SupportedPhpVersionTest.php index f8d394cd7a947dc6ee72c94dcbb65d34c2b5dbfc..c66f6624763e8e5c5eb85ded9d7427570080fbe0 100644 --- a/tests/src/Kernel/ReadinessChecker/SupportedPhpVersionTest.php +++ b/tests/src/Kernel/ReadinessChecker/SupportedPhpVersionTest.php @@ -37,9 +37,9 @@ class SupportedPhpVersionTest extends KernelTestBase { // Unsupported versions. $messages = (new TestBlacklistPhp72Versions())->run(); - $this->assertEquals('PHP 7.2.0, 7.2.1 and 7.2.2 have issues with opcache that breaks signature validation. Please upgrade to a newer version of PHP to ensure assurance and security for package signing.', $messages[0]); + self::assertEquals('PHP 7.2.0, 7.2.1 and 7.2.2 have issues with opcache that breaks signature validation. Please upgrade to a newer version of PHP to ensure assurance and security for package signing.', $messages[0]); $messages = (new TestMinimumPhpVersion())->run(); - $this->assertEquals('This site is running on an unsupported version of PHP. It cannot be updated. Please update to at least PHP 7.0.8.', $messages[0]); + self::assertEquals('This site is running on an unsupported version of PHP. It cannot be updated. Please update to at least PHP 7.0.8.', $messages[0]); } } diff --git a/tests/src/Kernel/ReadinessChecker/VendorTest.php b/tests/src/Kernel/ReadinessChecker/VendorTest.php index d93dd6fcd58c977a173261bc59b8c8258fc4c4d8..e7ab4c5abc41be637c4ade1bd1d1320d9c34f15b 100644 --- a/tests/src/Kernel/ReadinessChecker/VendorTest.php +++ b/tests/src/Kernel/ReadinessChecker/VendorTest.php @@ -46,7 +46,7 @@ class VendorTest extends KernelTestBase { ); $expected_messages = []; $expected_messages[] = $this->t('The vendor folder could not be located.'); - $this->assertEquals($expected_messages, $missing_vendor->run()); + self::assertEquals($expected_messages, $missing_vendor->run()); } }