Skip to content
Snippets Groups Projects
Commit 34ce3718 authored by Kunal Sachdev's avatar Kunal Sachdev Committed by Ted Bowman
Browse files

Issue #3280168 by kunal.sachdev: StagedDatabaseUpdateValidator should check themes also

parent 66cbcf2f
No related branches found
No related tags found
1 merge request!319Issue #3280168: StagedDatabaseUpdateValidator should check themes also
...@@ -108,6 +108,7 @@ services: ...@@ -108,6 +108,7 @@ services:
arguments: arguments:
- '@package_manager.path_locator' - '@package_manager.path_locator'
- '@extension.list.module' - '@extension.list.module'
- '@extension.list.theme'
- '@string_translation' - '@string_translation'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
......
...@@ -6,6 +6,7 @@ use Drupal\automatic_updates\CronUpdater; ...@@ -6,6 +6,7 @@ use Drupal\automatic_updates\CronUpdater;
use Drupal\automatic_updates\Updater; use Drupal\automatic_updates\Updater;
use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ThemeExtensionList;
use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\package_manager\Event\PreApplyEvent; use Drupal\package_manager\Event\PreApplyEvent;
...@@ -37,6 +38,13 @@ class StagedDatabaseUpdateValidator implements EventSubscriberInterface { ...@@ -37,6 +38,13 @@ class StagedDatabaseUpdateValidator implements EventSubscriberInterface {
*/ */
protected $moduleList; protected $moduleList;
/**
* The theme list service.
*
* @var \Drupal\Core\Extension\ThemeExtensionList
*/
protected $themeList;
/** /**
* Constructs a StagedDatabaseUpdateValidator object. * Constructs a StagedDatabaseUpdateValidator object.
* *
...@@ -44,12 +52,15 @@ class StagedDatabaseUpdateValidator implements EventSubscriberInterface { ...@@ -44,12 +52,15 @@ class StagedDatabaseUpdateValidator implements EventSubscriberInterface {
* The path locator service. * The path locator service.
* @param \Drupal\Core\Extension\ModuleExtensionList $module_list * @param \Drupal\Core\Extension\ModuleExtensionList $module_list
* The module list service. * The module list service.
* @param \Drupal\Core\Extension\ThemeExtensionList $theme_list
* The theme list service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* The string translation service. * The string translation service.
*/ */
public function __construct(PathLocator $path_locator, ModuleExtensionList $module_list, TranslationInterface $translation) { public function __construct(PathLocator $path_locator, ModuleExtensionList $module_list, ThemeExtensionList $theme_list, TranslationInterface $translation) {
$this->pathLocator = $path_locator; $this->pathLocator = $path_locator;
$this->moduleList = $module_list; $this->moduleList = $module_list;
$this->themeList = $theme_list;
$this->setStringTranslation($translation); $this->setStringTranslation($translation);
} }
...@@ -65,19 +76,23 @@ class StagedDatabaseUpdateValidator implements EventSubscriberInterface { ...@@ -65,19 +76,23 @@ class StagedDatabaseUpdateValidator implements EventSubscriberInterface {
return; return;
} }
$invalid_modules = []; $invalid_extensions = [];
// Although \Drupal\automatic_updates\Validator\StagedProjectsValidator // Although \Drupal\automatic_updates\Validator\StagedProjectsValidator
// should prevent non-core modules from being added, updated, or removed in // should prevent non-core modules from being added, updated, or removed in
// the staging area, we check all installed modules so as not to rely on the // the staging area, we check all installed modules so as not to rely on the
// presence of StagedProjectsValidator. // presence of StagedProjectsValidator.
foreach ($this->moduleList->getAllInstalledInfo() as $name => $info) { foreach ($this->moduleList->getAllInstalledInfo() as $module_name => $module_info) {
if ($this->hasStagedUpdates($stage, $this->moduleList->get($name))) { if ($this->hasStagedUpdates($stage, $this->moduleList->get($module_name))) {
$invalid_modules[] = $info['name']; $invalid_extensions[] = $module_info['name'];
} }
} }
foreach ($this->themeList->getAllInstalledInfo() as $theme_name => $theme_info) {
if ($invalid_modules) { if ($this->hasStagedUpdates($stage, $this->themeList->get($theme_name))) {
$event->addError($invalid_modules, $this->t('The update cannot proceed because possible database updates have been detected in the following modules.')); $invalid_extensions[] = $theme_info['name'];
}
}
if ($invalid_extensions) {
$event->addError($invalid_extensions, $this->t('The update cannot proceed because possible database updates have been detected in the following extensions.'));
} }
} }
...@@ -126,18 +141,18 @@ class StagedDatabaseUpdateValidator implements EventSubscriberInterface { ...@@ -126,18 +141,18 @@ class StagedDatabaseUpdateValidator implements EventSubscriberInterface {
* *
* @param string $root_dir * @param string $root_dir
* The root directory of the Drupal code base. * The root directory of the Drupal code base.
* @param \Drupal\Core\Extension\Extension $module * @param \Drupal\Core\Extension\Extension $extension
* The module to check. * The module to check.
* *
* @return string[] * @return string[]
* The hashes of the module's .install and .post_update.php files, in that * The hashes of the module's .install and .post_update.php files, in that
* order, if they exist. The array will be keyed by file extension. * order, if they exist. The array will be keyed by file extension.
*/ */
protected function getHashes(string $root_dir, Extension $module): array { protected function getHashes(string $root_dir, Extension $extension): array {
$path = implode(DIRECTORY_SEPARATOR, [ $path = implode(DIRECTORY_SEPARATOR, [
$root_dir, $root_dir,
$module->getPath(), $extension->getPath(),
$module->getName(), $extension->getName(),
]); ]);
$hashes = []; $hashes = [];
......
...@@ -49,12 +49,19 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase { ...@@ -49,12 +49,19 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase {
$virtual_active_dir = $this->container->get('package_manager.path_locator') $virtual_active_dir = $this->container->get('package_manager.path_locator')
->getProjectRoot(); ->getProjectRoot();
// Copy the .install and .post_update.php files from every installed module, // Copy the .install and .post_update.php files from all extensions used in
// in the *actual* Drupal code base that is running this test, into the // this test class, in the *actual* Drupal code base that is running this
// virtual project (i.e., the active directory). // test, into the virtual project (i.e., the active directory).
$module_list = $this->container->get('module_handler')->getModuleList(); $module_list = $this->container->get('extension.list.module');
foreach ($module_list as $name => $module) { $extensions = [];
$path = $module->getPath(); $extensions['system'] = $module_list->get('system');
$extensions['views'] = $module_list->get('views');
$extensions['package_manager_bypass'] = $module_list->get('package_manager_bypass');
$theme_list = $this->container->get('extension.list.theme');
$extensions['automatic_updates_theme'] = $theme_list->get('automatic_updates_theme');
$extensions['automatic_updates_theme_with_updates'] = $theme_list->get('automatic_updates_theme_with_updates');
foreach ($extensions as $name => $extension) {
$path = $extension->getPath();
@mkdir("$virtual_active_dir/$path", 0777, TRUE); @mkdir("$virtual_active_dir/$path", 0777, TRUE);
foreach (static::SUFFIXES as $suffix) { foreach (static::SUFFIXES as $suffix) {
...@@ -68,18 +75,23 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase { ...@@ -68,18 +75,23 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase {
* Tests that no errors are raised if staged files have no DB updates. * Tests that no errors are raised if staged files have no DB updates.
*/ */
public function testNoUpdates(): void { public function testNoUpdates(): void {
// Since we're testing with a modified version of Views, it should not be // Since we're testing with a modified version of 'views' and
// installed. // 'automatic_updates_theme_with_updates', these should not be installed.
$this->assertFalse($this->container->get('module_handler')->moduleExists('views')); $this->assertFalse($this->container->get('module_handler')->moduleExists('views'));
$this->assertFalse($this->container->get('theme_handler')->themeExists('automatic_updates_theme_with_updates'));
// Create bogus staged versions of Views' .install and .post_update.php // Create bogus staged versions of Views' and
// files. Since it's not installed, the changes should not raise any // Automatic Updates Theme with Updates .install and .post_update.php files.
// validation errors. // Since these extensions are not installed, the changes should not raise
// any validation errors.
$updater = $this->container->get('automatic_updates.cron_updater'); $updater = $this->container->get('automatic_updates.cron_updater');
$module_dir = $updater->getStageDirectory() . '/core/modules/views'; $module_list = $this->container->get('extension.list.module')->getList();
mkdir($module_dir, 0777, TRUE); $theme_list = $this->container->get('extension.list.theme')->getList();
$module_dir = $updater->getStageDirectory() . '/' . $module_list['views']->getPath();
$theme_dir = $updater->getStageDirectory() . '/' . $theme_list['automatic_updates_theme_with_updates']->getPath();
foreach (static::SUFFIXES as $suffix) { foreach (static::SUFFIXES as $suffix) {
file_put_contents("$module_dir/views.$suffix", $this->randomString()); file_put_contents("$module_dir/views.$suffix", $this->randomString());
file_put_contents("$theme_dir/automatic_updates_theme_with_updates.$suffix", $this->randomString());
} }
$updater->apply(); $updater->apply();
...@@ -112,19 +124,25 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase { ...@@ -112,19 +124,25 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase {
* @dataProvider providerFileChanged * @dataProvider providerFileChanged
*/ */
public function testFileChanged(string $suffix, bool $delete): void { public function testFileChanged(string $suffix, bool $delete): void {
/** @var \Drupal\Tests\automatic_updates\Kernel\ReadinessValidation\TestCronUpdater $updater */ /** @var \Drupal\automatic_updates\CronUpdater $updater */
$updater = $this->container->get('automatic_updates.cron_updater'); $updater = $this->container->get('automatic_updates.cron_updater');
$theme_installer = $this->container->get('theme_installer');
$file = $updater->getStageDirectory() . "/core/modules/system/system.$suffix"; $theme_installer->install(['automatic_updates_theme_with_updates']);
$theme = $this->container->get('theme_handler')
->getTheme('automatic_updates_theme_with_updates');
$module_file = $updater->getStageDirectory() . "/core/modules/system/system.$suffix";
$theme_file = $updater->getStageDirectory() . "/{$theme->getPath()}/automatic_updates_theme_with_updates.$suffix";
if ($delete) { if ($delete) {
unlink($file); unlink($module_file);
unlink($theme_file);
} }
else { else {
file_put_contents($file, $this->randomString()); file_put_contents($module_file, $this->randomString());
file_put_contents($theme_file, $this->randomString());
} }
$expected_results = [ $expected_results = [
ValidationResult::createError(['System'], t('The update cannot proceed because possible database updates have been detected in the following modules.')), ValidationResult::createError(['System', 'Automatic Updates Theme With Updates'], t('The update cannot proceed because possible database updates have been detected in the following extensions.')),
]; ];
try { try {
...@@ -142,21 +160,28 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase { ...@@ -142,21 +160,28 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase {
public function testUpdatesAddedInStage(): void { public function testUpdatesAddedInStage(): void {
$module = $this->container->get('module_handler') $module = $this->container->get('module_handler')
->getModule('package_manager_bypass'); ->getModule('package_manager_bypass');
$theme_installer = $this->container->get('theme_installer');
$theme_installer->install(['automatic_updates_theme']);
$theme = $this->container->get('theme_handler')
->getTheme('automatic_updates_theme');
/** @var \Drupal\Tests\automatic_updates\Kernel\ReadinessValidation\TestCronUpdater $updater */ /** @var \Drupal\automatic_updates\CronUpdater $updater */
$updater = $this->container->get('automatic_updates.cron_updater'); $updater = $this->container->get('automatic_updates.cron_updater');
foreach (static::SUFFIXES as $suffix) { foreach (static::SUFFIXES as $suffix) {
$file = sprintf('%s/%s/%s.%s', $updater->getStageDirectory(), $module->getPath(), $module->getName(), $suffix); $module_file = sprintf('%s/%s/%s.%s', $updater->getStageDirectory(), $module->getPath(), $module->getName(), $suffix);
// The file we're creating shouldn't already exist in the staging area $theme_file = sprintf('%s/%s/%s.%s', $updater->getStageDirectory(), $theme->getPath(), $theme->getName(), $suffix);
// The files we're creating shouldn't already exist in the staging area
// unless it's a file we actually ship, which is a scenario covered by // unless it's a file we actually ship, which is a scenario covered by
// ::testFileChanged(). // ::testFileChanged().
$this->assertFileDoesNotExist($file); $this->assertFileDoesNotExist($module_file);
file_put_contents($file, $this->randomString()); $this->assertFileDoesNotExist($theme_file);
file_put_contents($module_file, $this->randomString());
file_put_contents($theme_file, $this->randomString());
} }
$expected_results = [ $expected_results = [
ValidationResult::createError(['Package Manager Bypass'], t('The update cannot proceed because possible database updates have been detected in the following modules.')), ValidationResult::createError(['Package Manager Bypass', 'Automatic Updates Theme'], t('The update cannot proceed because possible database updates have been detected in the following extensions.')),
]; ];
try { try {
......
name: Automatic Updates Theme
type: theme
description: 'Empty theme for tests.'
package: Testing
base theme: false
name: Automatic Updates Theme With Updates
type: theme
description: 'Empty theme with updates for tests.'
package: Testing
base theme: false
<?php
/**
* @file
* Blank .install file.
*
* @see \Drupal\Tests\automatic_updates\Kernel\ReadinessValidation\StagedDatabaseUpdateValidatorTest
*/
<?php
/**
* @file
* Blank .post_update file.
*
* @see \Drupal\Tests\automatic_updates\Kernel\ReadinessValidation\StagedDatabaseUpdateValidatorTest
*/
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment