diff --git a/automatic_updates.install b/automatic_updates.install index 0ceb563d16aae65882decbf6d1b68d6bb66a1a10..59e5b49b176ca6655600ca1a6ae1fcb644c0f337 100644 --- a/automatic_updates.install +++ b/automatic_updates.install @@ -6,7 +6,6 @@ */ use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface; -use Drupal\automatic_updates\ReadinessChecker\Vendor; use Drupal\Core\StringTranslation\PluralTranslatableMarkup; use Drupal\Core\Url; @@ -16,7 +15,12 @@ use Drupal\Core\Url; function automatic_updates_requirements($phase) { // Mimic the functionality of the vendor checker procedurally since class // loading isn't available pre module install. - if (!file_exists(implode(DIRECTORY_SEPARATOR, [DRUPAL_ROOT, 'vendor', 'autoload.php']))) { + $vendor_autoloader = [ + DRUPAL_ROOT, + 'vendor', + 'autoload.php', + ]; + if (!file_exists(implode(DIRECTORY_SEPARATOR, $vendor_autoloader))) { return [ 'not installable' => [ 'title' => t('Automatic Updates'), diff --git a/automatic_updates.module b/automatic_updates.module index 3bb5271545ed443a52c513b4cfe7b7c1419b11e4..78de7ab724fc2210b7bd1e64e1e3c205b6b4234b 100644 --- a/automatic_updates.module +++ b/automatic_updates.module @@ -6,6 +6,7 @@ */ use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface; +use Drupal\update\UpdateManagerInterface; /** * Implements hook_page_top(). @@ -84,19 +85,27 @@ function automatic_updates_cron() { if ($config->get('enable_cron_updates')) { \Drupal::service('update.manager')->refreshUpdateData(); $available = update_get_available(TRUE); - $data = update_calculate_project_data($available); - $not_recommended = $data['drupal']['existing_version'] !== $data['drupal']['recommended']; + $projects = update_calculate_project_data($available); + $not_recommended_version = $projects['drupal']['status'] !== UpdateManagerInterface::CURRENT; + $dev_core = strpos(\Drupal::VERSION, '-dev') !== FALSE; + $security_update = in_array($projects['drupal']['status'], [UpdateManagerInterface::NOT_SECURE, UpdateManagerInterface::REVOKED], TRUE); + $recommended_release = $projects['drupal']['releases'][$projects['drupal']['recommended']]; + $major_upgrade = $recommended_release['version_major'] !== $projects['drupal']['existing_major']; + // Don't automatically update major version bumps or a dev version of core. + if ($major_upgrade || $dev_core) { + return; + } if ($config->get('enable_cron_security_updates')) { - if ($not_recommended && isset($data['drupal']['security updates'])) { + if ($not_recommended_version && $security_update) { /** @var \Drupal\automatic_updates\Services\UpdateInterface $updater */ $updater = \Drupal::service('automatic_updates.update'); - $updater->update('drupal', 'core', \Drupal::VERSION, $data['drupal']['latest_version']); + $updater->update('drupal', 'core', \Drupal::VERSION, $recommended_release['version']); } } - elseif ($not_recommended) { + elseif ($not_recommended_version) { /** @var \Drupal\automatic_updates\Services\UpdateInterface $updater */ $updater = \Drupal::service('automatic_updates.update'); - $updater->update('drupal', 'core', \Drupal::VERSION, $data['drupal']['latest_version']); + $updater->update('drupal', 'core', \Drupal::VERSION, $recommended_release['version']); } } } diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php index ff0d3fcd9d62a69ca5346d8be9fca6078d1014a6..8e3bb2f87b40b7c719872774459c135f1d9bb3c0 100644 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -5,6 +5,7 @@ namespace Drupal\automatic_updates\Form; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Drupal\update\UpdateManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -112,10 +113,12 @@ class SettingsForm extends ConfigFormBase { $this->updateManager->refreshUpdateData(); $available = update_get_available(TRUE); - $data = update_calculate_project_data($available); - $not_recommended = $data['drupal']['existing_version'] !== $data['drupal']['recommended']; - $security_update = isset($data['drupal']['security updates']); - $no_dev_core = strpos(\Drupal::VERSION, '-dev') === FALSE; + $projects = update_calculate_project_data($available); + $not_recommended_version = $projects['drupal']['status'] !== UpdateManagerInterface::CURRENT; + $not_dev_core = strpos(\Drupal::VERSION, '-dev') === FALSE; + $security_update = in_array($projects['drupal']['status'], [UpdateManagerInterface::NOT_SECURE, UpdateManagerInterface::REVOKED], TRUE); + $recommended_release = $projects['drupal']['releases'][$projects['drupal']['recommended']]; + $major_upgrade = $recommended_release['version_major'] !== $projects['drupal']['existing_major']; $form['experimental'] = [ '#type' => 'details', '#title' => $this->t('Experimental'), @@ -125,22 +128,31 @@ class SettingsForm extends ConfigFormBase { ], ], ]; - if ($not_recommended && $security_update && $no_dev_core) { - $form['experimental']['security'] = [ - '#type' => 'html_tag', - '#tag' => 'p', - '#value' => $this->t('A security update is available for your version of Drupal.'), - ]; + if ($not_recommended_version && $not_dev_core) { + if ($security_update) { + $form['experimental']['security'] = [ + '#type' => 'html_tag', + '#tag' => 'p', + '#value' => $this->t('A security update is available for your version of Drupal.'), + ]; + } + if ($major_upgrade) { + $form['experimental']['major_version'] = [ + '#type' => 'html_tag', + '#tag' => 'p', + '#value' => $this->t('This update is a major version update which means that it may not be backwards compatible with your currently running version. It is recommended that you read the release notes and proceed at your own risk.'), + ]; + } } $update_text = $this->t('Your site is running %version of Drupal core. No recommended update is available at this time.</i>', ['%version' => \Drupal::VERSION]); - if ($not_recommended && $no_dev_core) { + if ($not_recommended_version && $not_dev_core) { $update_text = $this->t('Even with all that caution, if you want to try it out, <a href="@link">manually update now</a>.', [ '@link' => Url::fromRoute('automatic_updates.inplace-update', [ 'project' => 'drupal', 'type' => 'core', 'from' => \Drupal::VERSION, - 'to' => $data['drupal']['latest_version'], + 'to' => $recommended_release['version'], ])->toString(), ]); } diff --git a/src/ProjectInfoTrait.php b/src/ProjectInfoTrait.php index 638c9369949f6e3c8eea417c887b4ae6790ed034..28bb3514682ddedf885d4befdd74a160842c286b 100644 --- a/src/ProjectInfoTrait.php +++ b/src/ProjectInfoTrait.php @@ -40,7 +40,7 @@ trait ProjectInfoTrait { $file_paths = $this->getExtensionList($extension_type)->getPathnames(); $infos = $this->getExtensionList($extension_type)->getAllAvailableInfo(); array_walk($infos, function (array &$info, $key) use ($file_paths) { - $info['packaged'] = isset($info['project']) ? $info['project'] : FALSE; + $info['packaged'] = isset($info['datestamp']) ? $info['datestamp'] : FALSE; $info['install path'] = $file_paths[$key] ? dirname($file_paths[$key]) : ''; $info['project'] = $this->getProjectName($key, $info); $info['version'] = $this->getExtensionVersion($info); diff --git a/tests/src/Build/InPlaceUpdateTest.php b/tests/src/Build/InPlaceUpdateTest.php index e3994c6d376f0dccb703dd2ea0cfa6180c5eccc8..4e147086cf8848e7462fd0bbbe19bb78f0f0a806 100644 --- a/tests/src/Build/InPlaceUpdateTest.php +++ b/tests/src/Build/InPlaceUpdateTest.php @@ -52,46 +52,13 @@ class InPlaceUpdateTest extends QuickStartTestBase { * @dataProvider coreVersionsProvider */ public function testCoreUpdate($from_version, $to_version) { - $this->copyCodebase(); - // We have to fetch the tags for this shallow repo. It might not be a - // shallow clone, therefore we use executeCommand instead of assertCommand. - $this->executeCommand('git fetch --unshallow --tags'); - $this->executeCommand('git reset HEAD --hard'); - $this->assertCommandSuccessful(); - $this->executeCommand("git checkout $from_version -f"); - $this->assertCommandSuccessful(); - $fs = new SymfonyFilesystem(); - $fs->chmod($this->getWorkspaceDirectory() . '/sites/default', 0700, 0000); - $this->executeCommand('COMPOSER_DISCARD_CHANGES=true composer install --no-dev --no-interaction'); - $this->assertErrorOutputContains('Generating autoload files'); - $this->installQuickStart('minimal'); - - // Currently, this test has to use extension_discovery_scan_tests so we can - // install test modules. - $fs->chmod($this->getWorkspaceDirectory() . '/sites/default/settings.php', 0640, 0000); - file_put_contents($this->getWorkspaceDirectory() . '/sites/default/settings.php', '$settings[\'extension_discovery_scan_tests\'] = TRUE;' . PHP_EOL, FILE_APPEND); - - // Log in so that we can install modules. - $this->formLogin($this->adminUsername, $this->adminPassword); - $this->moduleInstall('update'); - $this->moduleInstall('automatic_updates'); - $this->moduleInstall('test_automatic_updates'); - - // Confirm we are running correct Drupal version. - $finder = new Finder(); - $finder->files()->in($this->getWorkspaceDirectory())->path('core/lib/Drupal.php'); - $finder->contains("/const VERSION = '$from_version'/"); - $this->assertTrue($finder->hasResults()); + $this->installCore($from_version); // Assert files slated for deletion still exist. foreach ($this->getDeletions('drupal', $from_version, $to_version) as $deletion) { $this->assertFileExists($this->getWorkspaceDirectory() . DIRECTORY_SEPARATOR . $deletion); } - // Assert that the site is functional before updating. - $this->visit(); - $this->assertDrupalVisit(); - // Update the site. $assert = $this->visit("/test_automatic_updates/in-place-update/drupal/core/$from_version/$to_version") ->assertSession(); @@ -118,6 +85,7 @@ class InPlaceUpdateTest extends QuickStartTestBase { * @dataProvider contribProjectsProvider */ public function testContribUpdate($project, $project_type, $from_version, $to_version) { + $this->markTestSkipped('Contrib updates are not currently supported'); $this->copyCodebase(); $fs = new SymfonyFilesystem(); $fs->chmod($this->getWorkspaceDirectory() . '/sites/default', 0700, 0000); @@ -175,6 +143,77 @@ class InPlaceUpdateTest extends QuickStartTestBase { } } + /** + * Test in-place update via cron run. + * + * @covers ::update + * @see automatic_updates_cron() + */ + public function testCronCoreUpdate() { + $this->installCore('8.7.6'); + $filesystem = new SymfonyFilesystem(); + $filesystem->chmod($this->getWorkspaceDirectory() . '/sites/default', 0750, 0000); + $settings_php = $this->getWorkspaceDirectory() . '/sites/default/settings.php'; + $filesystem->chmod($settings_php, 0640); + $filesystem->appendToFile($settings_php, PHP_EOL . '$config[\'automatic_updates.settings\'][\'enable_cron_updates\'] = TRUE;' . PHP_EOL); + $mink = $this->visit('/admin/config/system/cron'); + $mink->getSession()->getPage()->findButton('Run cron')->submit(); + $mink->assertSession()->pageTextContains('Cron ran successfully.'); + + // Assert that the update worked. + $this->assertDrupalVisit(); + $finder = new Finder(); + $finder->files()->in($this->getWorkspaceDirectory())->path('core/lib/Drupal.php'); + $finder->notContains("/const VERSION = '8.7.6'/"); + $finder->contains("/const VERSION = '8.7./"); + $this->assertTrue($finder->hasResults()); + } + + /** + * Prepare core for testing. + * + * @param string $starting_version + * The starting version. + */ + protected function installCore($starting_version) { + $this->copyCodebase(); + // We have to fetch the tags for this shallow repo. It might not be a + // shallow clone, therefore we use executeCommand instead of assertCommand. + $this->executeCommand('git fetch --unshallow --tags'); + $this->executeCommand('git reset HEAD --hard'); + $this->assertCommandSuccessful(); + $this->executeCommand("git checkout $starting_version -f"); + $this->assertCommandSuccessful(); + $this->executeCommand('git reset HEAD --hard'); + $this->assertCommandSuccessful(); + $fs = new SymfonyFilesystem(); + $fs->chmod($this->getWorkspaceDirectory() . '/sites/default', 0700, 0000); + $this->executeCommand('COMPOSER_DISCARD_CHANGES=true composer install --no-dev --no-interaction'); + $this->assertErrorOutputContains('Generating autoload files'); + $this->installQuickStart('minimal'); + + // Currently, this test has to use extension_discovery_scan_tests so we can + // install test modules. + $fs->chmod($this->getWorkspaceDirectory() . '/sites/default/settings.php', 0640, 0000); + file_put_contents($this->getWorkspaceDirectory() . '/sites/default/settings.php', '$settings[\'extension_discovery_scan_tests\'] = TRUE;' . PHP_EOL, FILE_APPEND); + + // Log in so that we can install modules. + $this->formLogin($this->adminUsername, $this->adminPassword); + $this->moduleInstall('update'); + $this->moduleInstall('automatic_updates'); + $this->moduleInstall('test_automatic_updates'); + + // Confirm we are running correct Drupal version. + $finder = new Finder(); + $finder->files()->in($this->getWorkspaceDirectory())->path('core/lib/Drupal.php'); + $finder->contains("/const VERSION = '$starting_version'/"); + $this->assertTrue($finder->hasResults()); + + // Assert that the site is functional after install. + $this->visit(); + $this->assertDrupalVisit(); + } + /** * Core versions data provider. */ @@ -183,26 +222,6 @@ class InPlaceUpdateTest extends QuickStartTestBase { 'from' => '8.7.0', 'to' => '8.7.1', ]; - $datum[] = [ - 'from' => '8.7.1', - 'to' => '8.7.2', - ]; - $datum[] = [ - 'from' => '8.7.2', - 'to' => '8.7.3', - ]; - $datum[] = [ - 'from' => '8.7.3', - 'to' => '8.7.4', - ]; - $datum[] = [ - 'from' => '8.7.4', - 'to' => '8.7.5', - ]; - $datum[] = [ - 'from' => '8.7.5', - 'to' => '8.7.6', - ]; $datum[] = [ 'from' => '8.7.6', 'to' => '8.7.7', diff --git a/tests/src/Build/ModifiedFilesTest.php b/tests/src/Build/ModifiedFilesTest.php index ff79969527967692175bbdd38c0a9b62b878c7a2..57a5f8073c2c0b8d1ed2e1948534c5bc43440a32 100644 --- a/tests/src/Build/ModifiedFilesTest.php +++ b/tests/src/Build/ModifiedFilesTest.php @@ -59,6 +59,7 @@ class ModifiedFilesTest extends QuickStartTestBase { * @dataProvider contribProjectsProvider */ public function testContribModified($project, $project_type, $version, array $modifications = []) { + $this->markTestSkipped('Contrib updates are not currently supported'); $this->copyCodebase(); // Download the project. @@ -125,11 +126,9 @@ class ModifiedFilesTest extends QuickStartTestBase { // Currently, this test has to use extension_discovery_scan_tests so we can // install test modules. $this->symfonyFileSystem->chmod($this->getWorkspaceDirectory() . '/sites/default', 0750, 0000); - $this->symfonyFileSystem->chmod($this->getWorkspaceDirectory() . '/sites/default/settings.php', 0640, 0000); $settings_php = $this->getWorkspaceDirectory() . '/sites/default/settings.php'; + $this->symfonyFileSystem->chmod($settings_php, 0640); $this->symfonyFileSystem->appendToFile($settings_php, PHP_EOL . '$settings[\'extension_discovery_scan_tests\'] = TRUE;' . PHP_EOL); - // Intentionally mark composer.json and composer.lock as ignored. - $this->symfonyFileSystem->appendToFile($settings_php, PHP_EOL . '$config[\'automatic_updates.settings\'][\'ignored_paths\'] = "composer.json\ncomposer.lock\nmodules/custom/*\nthemes/custom/*\nprofiles/custom/*";' . PHP_EOL); // Restart server for config override to apply. $this->stopServer();