Issue #3001430 by alexpott, _utsavsharma, Oscaner, lauriii, smustgrave, ifux, deviantintegral, pameeela: Unable to uninstall base theme and subtheme via config sync at the same time

(cherry picked from commit fab2025f20)
merge-requests/3352/head
catch 2023-01-30 10:56:47 +00:00
parent 7ea72c959b
commit 95a2a252c2
14 changed files with 129 additions and 28 deletions

View File

@ -2353,7 +2353,8 @@ function install_config_import_batch() {
\Drupal::service('module_installer'),
\Drupal::service('theme_handler'),
\Drupal::service('string_translation'),
\Drupal::service('extension.list.module')
\Drupal::service('extension.list.module'),
\Drupal::service('extension.list.theme')
);
try {
@ -2425,7 +2426,8 @@ function install_config_revert_install_changes() {
\Drupal::service('module_installer'),
\Drupal::service('theme_handler'),
\Drupal::service('string_translation'),
\Drupal::service('extension.list.module')
\Drupal::service('extension.list.module'),
\Drupal::service('extension.list.theme')
);
try {
$config_importer->import();

View File

@ -6,6 +6,7 @@ use Drupal\Core\Config\Importer\MissingContentEvent;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeExtensionList;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
@ -166,6 +167,13 @@ class ConfigImporter {
*/
protected $moduleExtensionList;
/**
* The theme extension list.
*
* @var \Drupal\Core\Extension\ThemeExtensionList
*/
protected $themeExtensionList;
/**
* Constructs a configuration import object.
*
@ -190,8 +198,10 @@ class ConfigImporter {
* The string translation service.
* @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module
* The module extension list.
* @param \Drupal\Core\Extension\ThemeExtensionList $extension_list_theme
* The theme extension list.
*/
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, TranslationInterface $string_translation, ModuleExtensionList $extension_list_module) {
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, TranslationInterface $string_translation, ModuleExtensionList $extension_list_module, ThemeExtensionList $extension_list_theme = NULL) {
$this->moduleExtensionList = $extension_list_module;
$this->storageComparer = $storage_comparer;
$this->eventDispatcher = $event_dispatcher;
@ -202,6 +212,11 @@ class ConfigImporter {
$this->moduleInstaller = $module_installer;
$this->themeHandler = $theme_handler;
$this->stringTranslation = $string_translation;
if ($extension_list_theme === NULL) {
@trigger_error('Calling ' . __METHOD__ . ' without the $extension_list_theme argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3284397', E_USER_DEPRECATED);
$extension_list_theme = \Drupal::service('extension.list.theme');
}
$this->themeExtensionList = $extension_list_theme;
foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
$this->processedConfiguration[$collection] = $this->storageComparer->getEmptyChangelist();
}
@ -384,7 +399,7 @@ class ConfigImporter {
$this->moduleExtensionList->reset();
// Get a list of modules with dependency weights as values.
$module_data = $this->moduleExtensionList->getList();
// Set the actual module weights.
// Use the actual module weights.
$module_list = array_combine(array_keys($module_data), array_keys($module_data));
$module_list = array_map(function ($module) use ($module_data) {
return $module_data[$module]->sort;
@ -429,9 +444,24 @@ class ConfigImporter {
$this->extensionChangelist['module']['install'][] = $new_extensions['profile'];
}
// Get a list of themes with dependency weights as values.
$theme_data = $this->themeExtensionList->getList();
// Use the actual theme weights.
$theme_list = array_combine(array_keys($theme_data), array_keys($theme_data));
$theme_list = array_map(function ($theme) use ($theme_data) {
return $theme_data[$theme]->sort;
}, $theme_list);
array_multisort(array_values($theme_list), SORT_ASC, array_keys($theme_list), SORT_DESC, $theme_list);
// Work out what themes to install and to uninstall.
$this->extensionChangelist['theme']['install'] = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme']));
$this->extensionChangelist['theme']['uninstall'] = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme']));
$uninstall = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme']));
$this->extensionChangelist['theme']['uninstall'] = array_intersect(array_keys($theme_list), $uninstall);
// Ensure that installed themes are sorted in exactly the reverse order
// (with dependencies installed first, and themes of the same weight sorted
// in alphabetical order).
$install = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme']));
$theme_list = array_reverse($theme_list);
$this->extensionChangelist['theme']['install'] = array_intersect(array_keys($theme_list), $install);
}
/**

View File

@ -199,17 +199,13 @@ class ConfigImportSubscriber extends ConfigImportValidateEventSubscriberBase {
// Get all themes including those that are not installed.
$theme_data = $this->getThemeData();
$module_data = $this->moduleExtensionList->getList();
$installs = $config_importer->getExtensionChangelist('theme', 'install');
foreach ($installs as $key => $theme) {
if (!isset($theme_data[$theme])) {
$config_importer->logError($this->t('Unable to install the %theme theme since it does not exist.', ['%theme' => $theme]));
// Remove non-existing installs from the list so we can validate theme
// dependencies later.
unset($installs[$key]);
}
$nonexistent_themes = array_keys(array_diff_key($core_extension['theme'], $theme_data));
foreach ($nonexistent_themes as $theme) {
$config_importer->logError($this->t('Unable to install the %theme theme since it does not exist.', ['%theme' => $theme]));
}
// Ensure that all themes being installed have their dependencies met.
$installs = $config_importer->getExtensionChangelist('theme', 'install');
foreach ($installs as $theme) {
$module_dependencies = $theme_data[$theme]->module_dependencies;
// $theme_data[$theme]->requires contains both theme and module

View File

@ -17,6 +17,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeExtensionList;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
@ -104,6 +105,13 @@ class ConfigSingleImportForm extends ConfirmFormBase {
*/
protected $moduleExtensionList;
/**
* The theme extension list.
*
* @var \Drupal\Core\Extension\ThemeExtensionList
*/
protected $themeExtensionList;
/**
* The module installer.
*
@ -150,8 +158,10 @@ class ConfigSingleImportForm extends ConfirmFormBase {
* The theme handler.
* @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module
* The module extension list.
* @param \Drupal\Core\Extension\ThemeExtensionList $extension_list_theme
* The theme extension list.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, StorageInterface $config_storage, RendererInterface $renderer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, ModuleExtensionList $extension_list_module) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, StorageInterface $config_storage, RendererInterface $renderer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, ModuleExtensionList $extension_list_module, ThemeExtensionList $extension_list_theme = NULL) {
$this->entityTypeManager = $entity_type_manager;
$this->configStorage = $config_storage;
$this->renderer = $renderer;
@ -165,6 +175,11 @@ class ConfigSingleImportForm extends ConfirmFormBase {
$this->moduleInstaller = $module_installer;
$this->themeHandler = $theme_handler;
$this->moduleExtensionList = $extension_list_module;
if ($extension_list_theme === NULL) {
@trigger_error('Calling ' . __METHOD__ . ' without the $extension_list_theme argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3284397', E_USER_DEPRECATED);
$extension_list_theme = \Drupal::service('extension.list.theme');
}
$this->themeExtensionList = $extension_list_theme;
}
/**
@ -182,7 +197,8 @@ class ConfigSingleImportForm extends ConfirmFormBase {
$container->get('module_handler'),
$container->get('module_installer'),
$container->get('theme_handler'),
$container->get('extension.list.module')
$container->get('extension.list.module'),
$container->get('extension.list.theme')
);
}
@ -370,7 +386,8 @@ class ConfigSingleImportForm extends ConfirmFormBase {
$this->moduleInstaller,
$this->themeHandler,
$this->getStringTranslation(),
$this->moduleExtensionList
$this->moduleExtensionList,
$this->themeExtensionList
);
try {

View File

@ -11,6 +11,7 @@ use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeExtensionList;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Form\FormBase;
@ -121,6 +122,13 @@ class ConfigSync extends FormBase {
*/
protected $importTransformer;
/**
* The theme extension list.
*
* @var \Drupal\Core\Extension\ThemeExtensionList
*/
protected $themeExtensionList;
/**
* Constructs the object.
*
@ -150,8 +158,10 @@ class ConfigSync extends FormBase {
* The module extension list
* @param \Drupal\Core\Config\ImportStorageTransformer $import_transformer
* The import transformer service.
* @param \Drupal\Core\Extension\ThemeExtensionList $extension_list_theme
* The theme extension list.
*/
public function __construct(StorageInterface $sync_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, RendererInterface $renderer, ModuleExtensionList $extension_list_module, ImportStorageTransformer $import_transformer) {
public function __construct(StorageInterface $sync_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, RendererInterface $renderer, ModuleExtensionList $extension_list_module, ImportStorageTransformer $import_transformer, ThemeExtensionList $extension_list_theme = NULL) {
$this->syncStorage = $sync_storage;
$this->activeStorage = $active_storage;
$this->snapshotStorage = $snapshot_storage;
@ -165,6 +175,11 @@ class ConfigSync extends FormBase {
$this->renderer = $renderer;
$this->moduleExtensionList = $extension_list_module;
$this->importTransformer = $import_transformer;
if ($extension_list_theme === NULL) {
@trigger_error('Calling ' . __METHOD__ . ' without the $extension_list_theme argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3284397', E_USER_DEPRECATED);
$extension_list_theme = \Drupal::service('extension.list.theme');
}
$this->themeExtensionList = $extension_list_theme;
}
/**
@ -184,7 +199,8 @@ class ConfigSync extends FormBase {
$container->get('theme_handler'),
$container->get('renderer'),
$container->get('extension.list.module'),
$container->get('config.import_transformer')
$container->get('config.import_transformer'),
$container->get('extension.list.theme')
);
}
@ -357,7 +373,8 @@ class ConfigSync extends FormBase {
$this->moduleInstaller,
$this->themeHandler,
$this->getStringTranslation(),
$this->moduleExtensionList
$this->moduleExtensionList,
$this->themeExtensionList
);
if ($config_importer->alreadyImporting()) {
$this->messenger()->addStatus($this->t('Another request may be synchronizing configuration already.'));

View File

@ -53,7 +53,8 @@ class ConfigUninstallViaCliImportTest extends KernelTestBase {
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module')
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
}

View File

@ -58,7 +58,8 @@ class ContentTranslationConfigImportTest extends KernelTestBase {
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module')
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
}

View File

@ -49,7 +49,8 @@ class OverriddenConfigImportTest extends KernelTestBase {
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module')
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
}

View File

@ -54,7 +54,8 @@ class ConfigImportRecreateTest extends KernelTestBase {
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module')
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
}

View File

@ -63,7 +63,8 @@ class ConfigImportRenameValidationTest extends KernelTestBase {
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module')
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
}

View File

@ -65,7 +65,8 @@ class ConfigImporterMissingContentTest extends KernelTestBase {
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module')
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
}

View File

@ -687,6 +687,37 @@ class ConfigImporterTest extends KernelTestBase {
}
}
/**
* Tests installing a base themes and sub themes during configuration import.
*
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
*/
public function testInstallBaseAndSubThemes() {
$sync = $this->container->get('config.storage.sync');
$extensions = $sync->read('core.extension');
$extensions['theme']['test_basetheme'] = 0;
$extensions['theme']['test_subtheme'] = 0;
$extensions['theme']['test_subsubtheme'] = 0;
$sync->write('core.extension', $extensions);
$config_importer = $this->configImporter();
$config_importer->import();
$this->assertTrue($this->container->get('theme_handler')->themeExists('test_basetheme'));
$this->assertTrue($this->container->get('theme_handler')->themeExists('test_subsubtheme'));
$this->assertTrue($this->container->get('theme_handler')->themeExists('test_subtheme'));
// Test uninstalling them.
$extensions = $sync->read('core.extension');
unset($extensions['theme']['test_basetheme']);
unset($extensions['theme']['test_subsubtheme']);
unset($extensions['theme']['test_subtheme']);
$sync->write('core.extension', $extensions);
$config_importer = $this->configImporter();
$config_importer->import();
$this->assertFalse($this->container->get('theme_handler')->themeExists('test_basetheme'));
$this->assertFalse($this->container->get('theme_handler')->themeExists('test_subsubtheme'));
$this->assertFalse($this->container->get('theme_handler')->themeExists('test_subtheme'));
}
/**
* Tests install profile validation during configuration import.
*

View File

@ -64,7 +64,8 @@ class ContentEntityNullStorageTest extends KernelTestBase {
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module')
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
// Delete the contact message in sync.

View File

@ -37,7 +37,8 @@ trait ConfigTestTrait {
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation'),
$this->container->get('extension.list.module')
$this->container->get('extension.list.module'),
$this->container->get('extension.list.theme')
);
}
// Always recalculate the changelist when called.