/** * Tests configuration renaming. */ public function testConfigurationRename() { $content_type = entity_create('node_type', array('type' => Unicode::strtolower($this->randomName(16)), 'name' => $this->randomName())); $content_type->save(); $staged_type = $content_type->type; $active = $this->container->get('config.storage'); $staging = $this->container->get('config.storage.staging'); $config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id(); // Emulate a staging operation. $this->copyConfig($active, $staging); // Change the machine name of the content type. $content_type->type = Unicode::strtolower($this->randomName(8)); $content_type->save(); $active_type = $content_type->type; $renamed_config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id(); $this->assertTrue($active->exists($renamed_config_name), 'The content type has the new name in the active store.'); $this->assertFalse($active->exists($config_name), "The content type's old name does not exist active store."); $this->configImporter()->reset(); $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('create')), 'There are no configuration items to create.'); $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('delete')), 'There are no configuration items to delete.'); $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('update')), 'There are no configuration items to update.'); // We expect that changing the machine name of the content type will // rename five configuration entities: the node type, the body field // instance, two entity form displays, and the entity view display. // @see \Drupal\node\Entity\NodeType::postSave() $expected = array('node.type.' . $active_type . '::node.type.' . $staged_type, 'entity.form_display.node.' . $active_type . '.default::entity.form_display.node.' . $staged_type . '.default', 'entity.view_display.node.' . $active_type . '.default::entity.view_display.node.' . $staged_type . '.default', 'entity.view_display.node.' . $active_type . '.teaser::entity.view_display.node.' . $staged_type . '.teaser', 'field.instance.node.' . $active_type . '.body::field.instance.node.' . $staged_type . '.body'); $renames = $this->configImporter()->getUnprocessedConfiguration('rename'); $this->assertIdentical($expected, $renames); $this->drupalGet('admin/config/development/configuration'); foreach ($expected as $rename) { $names = $this->configImporter()->getStorageComparer()->extractRenameNames($rename); $this->assertText(String::format('!source_name to !target_name', array('!source_name' => $names['old_name'], '!target_name' => $names['new_name']))); // Test that the diff link is present for each renamed item. $href = \Drupal::urlGenerator()->getPathFromRoute('config.diff', array('source_name' => $names['old_name'], 'target_name' => $names['new_name'])); $this->assertLinkByHref($href); $hrefs[$rename] = $href; } // Ensure that the diff works for each renamed item. foreach ($hrefs as $rename => $href) { $this->drupalGet($href); $names = $this->configImporter()->getStorageComparer()->extractRenameNames($rename); $config_entity_type = \Drupal::service('config.manager')->getEntityTypeIdByName($names['old_name']); $entity_type = \Drupal::entityManager()->getDefinition($config_entity_type); $old_id = ConfigEntityStorage::getIDFromConfigName($names['old_name'], $entity_type->getConfigPrefix()); $new_id = ConfigEntityStorage::getIDFromConfigName($names['new_name'], $entity_type->getConfigPrefix()); // Because table columns can be on multiple lines, need to assert a regex // pattern rather than normal text. $id_key = $entity_type->getKey('id'); $text = "{$id_key}: {$old_id}"; $this->assertTextPattern('/\\-\\s+' . preg_quote($text, '/') . '/', "'-{$text}' found."); $text = "{$id_key}: {$new_id}"; $this->assertTextPattern('/\\+\\s+' . preg_quote($text, '/') . '/', "'+{$text}' found."); } // Run the import. $this->drupalPostForm('admin/config/development/configuration', array(), t('Import all')); $this->assertText(t('There are no configuration changes.')); $this->assertFalse(entity_load('node_type', $active_type), 'The content no longer exists with the old name.'); $content_type = entity_load('node_type', $staged_type); $this->assertIdentical($staged_type, $content_type->type); }
/** * Ensures bundles that will be deleted are not in use. * * @param \Drupal\Core\Config\ConfigImporterEvent $event * The config import event. */ public function onConfigImporterValidate(ConfigImporterEvent $event) { foreach ($event->getChangelist('delete') as $config_name) { // Get the config entity type ID. This also ensure we are dealing with a // configuration entity. if ($entity_type_id = $this->configManager->getEntityTypeIdByName($config_name)) { $entity_type = $this->entityManager->getDefinition($entity_type_id); // Does this entity type define a bundle of another entity type. if ($bundle_of = $entity_type->getBundleOf()) { // Work out if there are entities with this bundle. $bundle_of_entity_type = $this->entityManager->getDefinition($bundle_of); $bundle_id = ConfigEntityStorage::getIDFromConfigName($config_name, $entity_type->getConfigPrefix()); $entity_query = $this->entityManager->getStorage($bundle_of)->getQuery(); $entity_ids = $entity_query->condition($bundle_of_entity_type->getKey('bundle'), $bundle_id)->accessCheck(FALSE)->range(0, 1)->execute(); if (!empty($entity_ids)) { $entity = $this->entityManager->getStorage($entity_type_id)->load($bundle_id); $event->getConfigImporter()->logError($this->t('Entities exist of type %entity_type and %bundle_label %bundle. These entities need to be deleted before importing.', array('%entity_type' => $bundle_of_entity_type->getLabel(), '%bundle_label' => $bundle_of_entity_type->getBundleLabel(), '%bundle' => $entity->label()))); } } } } }
/** * Gets the list of fields to purge before configuration synchronization. * * If, during a configuration synchronization, a field is being deleted and * the module that provides the field type is being uninstalled then the field * data must be purged before the module is uninstalled. Also, if deleted * fields exist whose field types are provided by modules that are being * uninstalled their data need to be purged too. * * @param array $extensions * The list of extensions that will be enabled after the configuration * synchronization has finished. * @param array $deletes * The configuration that will be deleted by the configuration * synchronization. * * @return \Drupal\field\Entity\FieldStorageConfig[] * An array of field storages that need purging before configuration can be * synchronized. */ public static function getFieldStoragesToPurge(array $extensions, array $deletes) { $providers = array_keys($extensions['module']); $providers[] = 'core'; $storages_to_delete = array(); // Gather fields that will be deleted during configuration synchronization // where the module that provides the field type is also being uninstalled. $field_storage_ids = array(); foreach ($deletes as $config_name) { $field_storage_config_prefix = \Drupal::entityManager()->getDefinition('field_storage_config')->getConfigPrefix(); if (strpos($config_name, $field_storage_config_prefix . '.') === 0) { $field_storage_ids[] = ConfigEntityStorage::getIDFromConfigName($config_name, $field_storage_config_prefix); } } if (!empty($field_storage_ids)) { $field_storages = \Drupal::entityQuery('field_storage_config')->condition('id', $field_storage_ids, 'IN')->condition('module', $providers, 'NOT IN')->execute(); if (!empty($field_storages)) { $storages_to_delete = FieldStorageConfig::loadMultiple($field_storages); } } // Gather deleted fields from modules that are being uninstalled. /** @var \Drupal\field\FieldStorageConfigInterface[] $field_storages */ $field_storages = entity_load_multiple_by_properties('field_storage_config', array('deleted' => TRUE, 'include_deleted' => TRUE)); foreach ($field_storages as $field_storage) { if (!in_array($field_storage->getTypeProvider(), $providers)) { $storages_to_delete[$field_storage->id()] = $field_storage; } } return $storages_to_delete; }