diff -u b/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php --- b/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -14,6 +14,7 @@ use Drupal\Core\DependencyInjection\DependencySerialization; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Lock\LockBackendInterface; +use Drupal\Core\StringTranslation\TranslationManager; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -120,6 +121,13 @@ protected $themeHandler; /** + * The string translation service. + * + * @var \Drupal\Core\StringTranslation\TranslationManager + */ + protected $translationManager; + + /** * Flag set to import system.theme during processing theme enable and disables. * * @var bool @@ -151,8 +159,10 @@ * The module handler * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler + * @param \Drupal\Core\StringTranslation\TranslationManager $translation_manager + * The string translation service. */ - public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) { + public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, TranslationManager $translation_manager) { $this->storageComparer = $storage_comparer; $this->eventDispatcher = $event_dispatcher; $this->configManager = $config_manager; @@ -160,6 +170,7 @@ $this->typedConfigManager = $typed_config; $this->moduleHandler = $module_handler; $this->themeHandler = $theme_handler; + $this->translationManager = $translation_manager; $this->processedConfiguration = $this->storageComparer->getEmptyChangelist(); $this->processedExtensions = $this->getEmptyExtensionsProcessedList(); } @@ -494,7 +505,7 @@ } } catch (\Exception $e) { - $this->logError(String::format('Unexpected error during import with operation @op for @name: @message', array('@op' => $op, '@name' => $name, '@message' => $e->getMessage()))); + $this->logError($this->translationManager->translate('Unexpected error during import with operation @op for @name: @message', array('@op' => $op, '@name' => $name, '@message' => $e->getMessage()))); // Error for that operation was logged, mark it as processed so that // the import can continue. $this->setProcessedConfiguration($op, $name); @@ -571,7 +582,7 @@ if (!$target_exists) { // The configuration has already been deleted. For example, a field // is automatically deleted if all the instances are. - $this->setProcessed($op, $name); + $this->setProcessedConfiguration($op, $name); return FALSE; } break; @@ -585,10 +596,11 @@ $entity_type = $this->configManager->getEntityManager()->getDefinition($entity_type_id); $entity = $entity_storage->load($entity_storage->getIDFromConfigName($name, $entity_type->getConfigPrefix())); $entity->delete(); - $this->logError(String::format('Deleted and replaced configuration entity "@name"', array('@name' => $name))); + $this->logError($this->translationManager->translate('Deleted and replaced configuration entity "@name"', array('@name' => $name))); } else { $this->storageComparer->getTargetStorage()->delete($name); + $this->logError($this->translationManager->translate('Deleted and replaced configuration "@name"', array('@name' => $name))); } return TRUE; } @@ -596,7 +608,11 @@ case 'update': if (!$target_exists) { - $this->logError(String::format('Update target "@name" is missing.', array('@name' => $name))); + $this->logError($this->translationManager->translate('Update target "@name" is missing.', array('@name' => $name))); + // Mark as processed so that the synchronisation continues. Once the + // the current synchronisation is complete it will show up as a + // create. + $this->setProcessedConfiguration($op, $name); return FALSE; } break; @@ -755,4 +771,5 @@ $this->moduleHandler = \Drupal::moduleHandler(); $this->themeHandler = \Drupal::service('theme_handler'); + $this->translationManager = \Drupal::service('string_translation'); } } diff -u b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php --- b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -250,7 +250,8 @@ $this->lock, $this->typedConfigManager, $this->moduleHandler, - $this->themeHandler + $this->themeHandler, + $this->translationManager() ); if ($config_importer->alreadyImporting()) { drupal_set_message($this->t('Another request may be synchronizing configuration already.')); diff -u b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php --- b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php @@ -65,7 +65,8 @@ $this->container->get('lock'), $this->container->get('config.typed'), $this->container->get('module_handler'), - $this->container->get('theme_handler') + $this->container->get('theme_handler'), + $this->container->get('string_translation') ); } @@ -302,6 +303,7 @@ function testSecondaryUpdateDeletedDeleterFirst() { $name_deleter = 'config_test.dynamic.deleter'; $name_deletee = 'config_test.dynamic.deletee'; + $name_other = 'config_test.dynamic.other'; $storage = $this->container->get('config.storage'); $staging = $this->container->get('config.storage.staging'); $uuid = $this->container->get('uuid'); @@ -329,16 +331,48 @@ $values_deletee['label'] = 'Updated Deletee'; $staging->write($name_deletee, $values_deletee); + // Ensure that import will continue after the error. + $values_other = array( + 'id' => 'other', + 'label' => 'Other', + 'weight' => 0, + 'uuid' => $uuid->generate(), + // Add a dependency on deleter, to make sure that is synced first. This + // will also be synced after the deletee due to alphabetical ordering. + 'dependencies' => array( + 'entity' => array($name_deleter), + ) + ); + $storage->write($name_other, $values_other); + $values_other['label'] = 'Updated other'; + $staging->write($name_other, $values_other); + + // Check update changelist order. + $updates = $this->configImporter->reset()->getStorageComparer()->getChangelist('update'); + $expected = array( + $name_deleter, + $name_deletee, + $name_other, + ); + $this->assertIdentical($expected, $updates); + // Import. - $this->configImporter->reset()->import(); + $this->configImporter->import(); $entity_storage = \Drupal::entityManager()->getStorage('config_test'); $deleter = $entity_storage->load('deleter'); $this->assertEqual($deleter->id(), 'deleter'); $this->assertEqual($deleter->uuid(), $values_deleter['uuid']); $this->assertEqual($deleter->label(), $values_deleter['label']); - // @todo The deletee entity does not exist as the update failed. Should the - // importer attempt a create instead? + + // The deletee was deleted in + // \Drupal\config_test\Entity\ConfigTest::postSave(). + $this->assertFalse($entity_storage->load('deletee')); + + $other = $entity_storage->load('other'); + $this->assertEqual($other->id(), 'other'); + $this->assertEqual($other->uuid(), $values_other['uuid']); + $this->assertEqual($other->label(), $values_other['label']); $logs = $this->configImporter->getErrors(); $this->assertEqual(count($logs), 1); @@ -389,6 +423,49 @@ // @todo The deletee entity does not exist as the update worked but the // entity was deleted after that. There is also no log message as this // happened outside of the config importer. + $this->assertFalse($entity_storage->load('deletee')); + $logs = $this->configImporter->getErrors(); + $this->assertEqual(count($logs), 0); + } + + /** + * Tests that secondary deletes for deleted files work as expected. + */ + function testSecondaryDeletedDeleteeSecond() { + $name_deleter = 'config_test.dynamic.deleter'; + $name_deletee = 'config_test.dynamic.deletee'; + $storage = $this->container->get('config.storage'); + + $uuid = $this->container->get('uuid'); + + $values_deleter = array( + 'id' => 'deleter', + 'label' => 'Deleter', + 'weight' => 0, + 'uuid' => $uuid->generate(), + // Add a dependency on deletee, to make sure this delete is synced first. + 'dependencies' => array( + 'entity' => array($name_deletee), + ), + ); + $storage->write($name_deleter, $values_deleter); + $values_deletee = array( + 'id' => 'deletee', + 'label' => 'Deletee', + 'weight' => 0, + 'uuid' => $uuid->generate(), + ); + $storage->write($name_deletee, $values_deletee); + + // Import. + $this->configImporter->reset()->import(); + + $entity_storage = \Drupal::entityManager()->getStorage('config_test'); + $this->assertFalse($entity_storage->load('deleter')); + $this->assertFalse($entity_storage->load('deletee')); + // The deletee entity does not exist as the delete worked and although the + // delete occurred in \Drupal\config_test\Entity\ConfigTest::postDelete() + // this does not matter. $logs = $this->configImporter->getErrors(); $this->assertEqual(count($logs), 0); } diff -u b/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php --- b/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php @@ -128,6 +128,19 @@ /** * {@inheritdoc} */ + public static function postDelete(EntityStorageInterface $storage, array $entities) { + parent::postDelete($storage, $entities); + foreach ($entities as $entity) { + if ($entity->id() == 'deleter') { + $deletee = $storage->load('deletee'); + $deletee->delete(); + } + } + } + + /** + * {@inheritdoc} + */ public function calculateDependencies() { parent::calculateDependencies(); foreach ($this->test_dependencies as $type => $deps) { only in patch2: unchanged: --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php @@ -95,6 +95,9 @@ public function testInstallUninstall() { // Import the configuration thereby re-installing all the modules. $this->configImporter()->import(); + // Check that there are no errors. + $this->assertIdentical($this->configImporter()->getErrors(), array()); + // Check that all modules that were uninstalled are now reinstalled. $this->assertModules(array_keys($modules_to_uninstall), TRUE); foreach($modules_to_uninstall as $module => $info) { only in patch2: unchanged: --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php @@ -59,7 +59,8 @@ public function setUp() { $this->container->get('lock'), $this->container->get('config.typed'), $this->container->get('module_handler'), - $this->container->get('theme_handler') + $this->container->get('theme_handler'), + $this->container->get('string_translation') ); } only in patch2: unchanged: --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -1527,7 +1527,8 @@ public function configImporter() { $this->container->get('lock'), $this->container->get('config.typed'), $this->container->get('module_handler'), - $this->container->get('theme_handler') + $this->container->get('theme_handler'), + $this->container->get('string_translation') ); } // Always recalculate the changelist when called.