From 007eaf3583c6d9b16853f59d34afa7ee458623dd Mon Sep 17 00:00:00 2001
From: catch <git config --global user.email catch@35733.no-reply.drupal.org>
Date: Sun, 28 Dec 2014 13:57:47 +0000
Subject: [PATCH] Issue #2392351 by alexpott, swentel: When an entity bundle
 config gets deleted, entities of that bundle break

---
 core/core.services.yml                        |  5 ++
 .../Core/Config/ConfigImporterEvent.php       | 19 +++++
 .../Event/BundleConfigImportValidate.php      | 79 +++++++++++++++++++
 .../config/src/Tests/ConfigImportUITest.php   | 44 +++++++++++
 4 files changed, 147 insertions(+)
 create mode 100644 core/lib/Drupal/Core/Entity/Event/BundleConfigImportValidate.php

diff --git a/core/core.services.yml b/core/core.services.yml
index 328acd8fd4b4..9291712c0235 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -322,6 +322,11 @@ services:
   entity.form_builder:
     class: Drupal\Core\Entity\EntityFormBuilder
     arguments: ['@entity.manager', '@form_builder']
+  entity.bundle_config_import_validator:
+    class: Drupal\Core\Entity\Event\BundleConfigImportValidate
+    arguments: ['@config.manager', '@entity.manager']
+    tags:
+      - { name: event_subscriber }
   plugin.manager.block:
     class: Drupal\Core\Block\BlockManager
     parent: default_plugin_manager
diff --git a/core/lib/Drupal/Core/Config/ConfigImporterEvent.php b/core/lib/Drupal/Core/Config/ConfigImporterEvent.php
index caef42e930f1..8286813254bf 100644
--- a/core/lib/Drupal/Core/Config/ConfigImporterEvent.php
+++ b/core/lib/Drupal/Core/Config/ConfigImporterEvent.php
@@ -37,4 +37,23 @@ public function getConfigImporter() {
     return $this->configImporter;
   }
 
+  /**
+   * Gets the list of changes that will be imported.
+   *
+   * @param string $op
+   *   (optional) A change operation. Either delete, create or update. If
+   *   supplied the returned list will be limited to this operation.
+   * @param string $collection
+   *   (optional) The collection to get the changelist for. Defaults to the
+   *   default collection.
+   *
+   * @return array
+   *   An array of config changes that are yet to be imported.
+   *
+   * @see \Drupal\Core\Config\StorageComparerInterface::getChangelist()
+   */
+  public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) {
+    return $this->configImporter->getStorageComparer()->getChangelist($op, $collection);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Event/BundleConfigImportValidate.php b/core/lib/Drupal/Core/Entity/Event/BundleConfigImportValidate.php
new file mode 100644
index 000000000000..58ca1a2c53e4
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Event/BundleConfigImportValidate.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Event\BundleConfigImportValidate
+ */
+
+namespace Drupal\Core\Entity\Event;
+
+use Drupal\Core\Config\ConfigImporterEvent;
+use Drupal\Core\Config\ConfigImportValidateEventSubscriberBase;
+use Drupal\Core\Config\ConfigManagerInterface;
+use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\Core\Entity\EntityManagerInterface;
+
+/**
+ * Entity config importer validation event subscriber.
+ */
+class BundleConfigImportValidate extends ConfigImportValidateEventSubscriberBase {
+
+  /**
+   * The config manager.
+   *
+   * @var \Drupal\Core\Config\ConfigManagerInterface
+   */
+  protected $configManager;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs the event subscriber.
+   *
+   * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
+   *   The config manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(ConfigManagerInterface $config_manager, EntityManagerInterface $entity_manager) {
+    $this->configManager = $config_manager;
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * 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())));
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php
index 1403a4cd9f62..00fd4bfa2bb7 100644
--- a/core/modules/config/src/Tests/ConfigImportUITest.php
+++ b/core/modules/config/src/Tests/ConfigImportUITest.php
@@ -393,4 +393,48 @@ function testImportErrorLog() {
     $this->assertText(t('There are no configuration changes to import.'));
   }
 
+  /**
+   * Tests the config importer cannot delete bundles with existing entities.
+   *
+   * @see \Drupal\Core\Entity\Event\BundleConfigImportValidate
+   */
+  public function testEntityBundleDelete() {
+    \Drupal::service('module_installer')->install(array('node'));
+    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
+
+    $node_type = $this->drupalCreateContentType();
+    $node = $this->drupalCreateNode(array('type' => $node_type->id()));
+    $this->drupalGet('admin/config/development/configuration');
+    // The node type, body field and entity displays will be scheduled for
+    // removal.
+    $this->assertText(format_string('node.type.@type', array('@type' => $node_type->id())));
+    $this->assertText(format_string('field.field.node.@type.body', array('@type' => $node_type->id())));
+    $this->assertText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id())));
+    $this->assertText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id())));
+    $this->assertText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id())));
+
+    // Attempt to import configuration and verify that an error message appears
+    // and the node type, body field and entity displays are still scheduled for
+    // removal.
+    $this->drupalPostForm(NULL, array(), t('Import all'));
+    $validation_message = t('Entities exist of type %entity_type and %bundle_label %bundle. These entities need to be deleted before importing.', array('%entity_type' => $node->getEntityType()->getLabel(), '%bundle_label' => $node->getEntityType()->getBundleLabel(), '%bundle' => $node_type->label()));
+    $this->assertRaw($validation_message);
+    $this->assertText(format_string('node.type.@type', array('@type' => $node_type->id())));
+    $this->assertText(format_string('field.field.node.@type.body', array('@type' => $node_type->id())));
+    $this->assertText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id())));
+    $this->assertText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id())));
+    $this->assertText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id())));
+
+    // Delete the node and try to import again.
+    $node->delete();
+    $this->drupalPostForm(NULL, array(), t('Import all'));
+    $this->assertNoRaw($validation_message);
+    $this->assertText(t('There are no configuration changes to import.'));
+    $this->assertNoText(format_string('node.type.@type', array('@type' => $node_type->id())));
+    $this->assertNoText(format_string('field.field.node.@type.body', array('@type' => $node_type->id())));
+    $this->assertNoText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id())));
+    $this->assertNoText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id())));
+    $this->assertNoText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id())));
+  }
+
 }
-- 
GitLab