From ad528c7375bf71d71604ba1264b14f05e7ab096b Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Mon, 18 Mar 2019 09:28:56 +0000
Subject: [PATCH] Issue #2599228 by tstoeckler, Taran2L, poornachandran, catch,
 vacho, johan.s, Berdir, hchonov, osman, tim.plunkett, GoZ, Voidtek, neetu
 morwani, psf_, alexpott, dxvargas, nkoporec, plach, andy@andyhawks.com,
 joey-santiago: Programmatically created translatable content type returns SQL
 error on content creation

(cherry picked from commit 46424a89ef6386336a7806095f1494e2b79d7992)
---
 .../content_translation.module                | 58 ++++++++++--
 .../content_translation.services.yml          |  5 +-
 .../d7_entity_translation_settings.yml        |  5 --
 .../migrations/d7_node_translation.yml        |  2 -
 .../src/ContentTranslationManager.php         | 24 ++---
 .../src/ContentTranslationUpdatesManager.php  | 60 ++-----------
 .../entity_test.entity_test_bundle.test.yml   |  6 ++
 ..._settings.entity_test_with_bundle.test.yml | 17 ++++
 .../ContentTranslationModuleInstallTest.php   | 88 +++++++++++++++++++
 .../d6_language_content_settings.yml          |  2 -
 ...e_content_taxonomy_vocabulary_settings.yml |  2 -
 .../d7_language_content_settings.yml          |  2 -
 .../src/Entity/EntityTestWithBundle.php       |  2 +
 13 files changed, 183 insertions(+), 90 deletions(-)
 create mode 100644 core/modules/content_translation/tests/modules/content_translation_test/config/install/entity_test.entity_test_bundle.test.yml
 create mode 100644 core/modules/content_translation/tests/modules/content_translation_test/config/install/language.content_settings.entity_test_with_bundle.test.yml
 create mode 100644 core/modules/content_translation/tests/src/Kernel/ContentTranslationModuleInstallTest.php

diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index d6f6f0c60399..50d7b846d89e 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -16,6 +16,7 @@
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\language\ContentLanguageSettingsInterface;
 
 /**
  * Implements hook_help().
@@ -172,27 +173,72 @@ function content_translation_entity_type_alter(array &$entity_types) {
 /**
  * Implements hook_ENTITY_TYPE_insert().
  *
- * Clear the bundle information cache so that the bundle's translatability will
- * be set properly.
+ * Installs Content Translation's field storage definitions for the target
+ * entity type, if required.
+ *
+ * Also clears the bundle information cache so that the bundle's translatability
+ * will be set properly.
  *
  * @see content_translation_entity_bundle_info_alter()
+ * @see \Drupal\content_translation\ContentTranslationManager::isEnabled()
  */
-function content_translation_language_content_settings_insert(EntityInterface $entity) {
+function content_translation_language_content_settings_insert(ContentLanguageSettingsInterface $settings) {
+  if ($settings->getThirdPartySetting('content_translation', 'enabled', FALSE)) {
+    _content_translation_install_field_storage_definitions($settings->getTargetEntityTypeId());
+  }
+
   \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
 }
 
 /**
  * Implements hook_ENTITY_TYPE_update().
  *
- * Clear the bundle information cache so that the bundle's translatability will
- * be changed properly.
+ * Installs Content Translation's field storage definitions for the target
+ * entity type, if required.
+ *
+ * Also clears the bundle information cache so that the bundle's translatability
+ * will be changed properly.
  *
  * @see content_translation_entity_bundle_info_alter()
+ * @see \Drupal\content_translation\ContentTranslationManager::isEnabled()
  */
-function content_translation_language_content_settings_update(EntityInterface $entity) {
+function content_translation_language_content_settings_update(ContentLanguageSettingsInterface $settings) {
+  $original_settings = $settings->original;
+  if ($settings->getThirdPartySetting('content_translation', 'enabled', FALSE)
+    && !$original_settings->getThirdPartySetting('content_translation', 'enabled', FALSE)
+  ) {
+    _content_translation_install_field_storage_definitions($settings->getTargetEntityTypeId());
+  }
   \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
 }
 
+/**
+ * Installs Content Translation's fields for a given entity type.
+ *
+ * @param string $entity_type_id
+ *   The entity type ID.
+ *
+ * @todo Generalize this code in https://www.drupal.org/node/2346013.
+ */
+function _content_translation_install_field_storage_definitions($entity_type_id) {
+  /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */
+  $field_manager = \Drupal::service('entity_field.manager');
+  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $schema_repository */
+  $schema_repository = \Drupal::service('entity.last_installed_schema.repository');
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+
+  $field_manager->useCaches(FALSE);
+  $storage_definitions = $field_manager->getFieldStorageDefinitions($entity_type_id);
+  $field_manager->useCaches(TRUE);
+  $installed_storage_definitions = $schema_repository->getLastInstalledFieldStorageDefinitions($entity_type_id);
+  foreach (array_diff_key($storage_definitions, $installed_storage_definitions) as $storage_definition) {
+    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition */
+    if ($storage_definition->getProvider() == 'content_translation') {
+      $definition_update_manager->installFieldStorageDefinition($storage_definition->getName(), $entity_type_id, 'content_translation', $storage_definition);
+    }
+  }
+}
+
 /**
  * Implements hook_entity_bundle_info_alter().
  */
diff --git a/core/modules/content_translation/content_translation.services.yml b/core/modules/content_translation/content_translation.services.yml
index 71b800ec005e..dae66869ff31 100644
--- a/core/modules/content_translation/content_translation.services.yml
+++ b/core/modules/content_translation/content_translation.services.yml
@@ -29,10 +29,9 @@ services:
 
   content_translation.manager:
     class: Drupal\content_translation\ContentTranslationManager
-    arguments: ['@entity_type.manager', '@content_translation.updates_manager', '@entity_type.bundle.info']
+    arguments: ['@entity_type.manager', '@entity_type.bundle.info']
 
   content_translation.updates_manager:
     class: Drupal\content_translation\ContentTranslationUpdatesManager
     arguments: ['@entity_type.manager', '@entity.definition_update_manager', '@entity_field.manager', '@entity.last_installed_schema.repository']
-    tags:
-      - { name: event_subscriber }
+    deprecated: The "%service_id%" service is deprecated. Definitions are updated automatically now so no replacement is needed. See https://www.drupal.org/node/2973222.
diff --git a/core/modules/content_translation/migrations/d7_entity_translation_settings.yml b/core/modules/content_translation/migrations/d7_entity_translation_settings.yml
index bd82446339d2..7ea34a390ded 100644
--- a/core/modules/content_translation/migrations/d7_entity_translation_settings.yml
+++ b/core/modules/content_translation/migrations/d7_entity_translation_settings.yml
@@ -25,11 +25,6 @@ process:
   third_party_settings/content_translation/bundle_settings/untranslatable_fields_hide: untranslatable_fields_hide
 destination:
   plugin: entity:language_content_settings
-  content_translation_update_definitions:
-    - comment
-    - node
-    - taxonomy_term
-    - user
 migration_dependencies:
   optional:
     - d7_comment_type
diff --git a/core/modules/content_translation/migrations/d7_node_translation.yml b/core/modules/content_translation/migrations/d7_node_translation.yml
index 90a101fec13d..6e89fb827e1c 100644
--- a/core/modules/content_translation/migrations/d7_node_translation.yml
+++ b/core/modules/content_translation/migrations/d7_node_translation.yml
@@ -33,8 +33,6 @@ process:
 destination:
   plugin: entity:node
   translations: true
-  content_translation_update_definitions:
-    - node
   destination_module: content_translation
 migration_dependencies:
   required:
diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php
index 5149318f01ce..eaa64a7f918d 100644
--- a/core/modules/content_translation/src/ContentTranslationManager.php
+++ b/core/modules/content_translation/src/ContentTranslationManager.php
@@ -17,7 +17,10 @@ class ContentTranslationManager implements ContentTranslationManagerInterface, B
   /**
    * {@inheritdoc}
    */
-  protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
+  protected $deprecatedProperties = [
+    'entityManager' => 'entity.manager',
+    'updatesManager' => 'content_translation.updates_manager',
+  ];
 
   /**
    * The entity type bundle info provider.
@@ -33,28 +36,19 @@ class ContentTranslationManager implements ContentTranslationManagerInterface, B
    */
   protected $entityTypeManager;
 
-  /**
-   * The updates manager.
-   *
-   * @var \Drupal\content_translation\ContentTranslationUpdatesManager
-   */
-  protected $updatesManager;
-
   /**
    * Constructs a ContentTranslationManageAccessCheck object.
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager.
-   * @param \Drupal\content_translation\ContentTranslationUpdatesManager $updates_manager
-   *   The updates manager.
    * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
    *   The entity type bundle info provider.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, ContentTranslationUpdatesManager $updates_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, $entity_type_bundle_info) {
     $this->entityTypeManager = $entity_type_manager;
-    $this->updatesManager = $updates_manager;
-    if (!$entity_type_bundle_info) {
-      @trigger_error('The entity_type.bundle.info service must be passed to ContentTranslationManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
+
+    if (!($entity_type_bundle_info instanceof EntityTypeBundleInfoInterface)) {
+      @trigger_error('The entity_type.bundle.info service should be passed to ContentTranslationManager::__construct() instead of the content_translation.updates_manager service since 8.7.0. This will be required in Drupal 9.0.0. See https://www.drupal.org/node/2549139 and https://www.drupal.org/node/2973222.', E_USER_DEPRECATED);
       $entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
     }
     $this->entityTypeBundleInfo = $entity_type_bundle_info;
@@ -104,8 +98,6 @@ public function getSupportedEntityTypes() {
   public function setEnabled($entity_type_id, $bundle, $value) {
     $config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
     $config->setThirdPartySetting('content_translation', 'enabled', $value)->save();
-    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
-    $this->updatesManager->updateDefinitions([$entity_type_id => $entity_type]);
   }
 
   /**
diff --git a/core/modules/content_translation/src/ContentTranslationUpdatesManager.php b/core/modules/content_translation/src/ContentTranslationUpdatesManager.php
index d62a164ed864..9bb7fd07974a 100644
--- a/core/modules/content_translation/src/ContentTranslationUpdatesManager.php
+++ b/core/modules/content_translation/src/ContentTranslationUpdatesManager.php
@@ -2,28 +2,22 @@
 
 namespace Drupal\content_translation;
 
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Config\ConfigEvents;
-use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
 use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\migrate\Event\MigrateEvents;
-use Drupal\migrate\Event\MigrateImportEvent;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+@trigger_error('\Drupal\content_translation\ContentTranslationUpdatesManager is scheduled for removal in Drupal 9.0.0. Definitions are updated automatically now so no replacement is needed. See https://www.drupal.org/node/2973222.', E_USER_DEPRECATED);
 
 /**
  * Provides the logic needed to update field storage definitions when needed.
+ *
+ * @deprecated in Drupal 8.7.x, to be removed before Drupal 9.0.0.
+ *   Definitions are updated automatically now so no replacement is needed.
+ *
+ * @see https://www.drupal.org/node/2973222
  */
-class ContentTranslationUpdatesManager implements EventSubscriberInterface {
-  use DeprecatedServicePropertyTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
+class ContentTranslationUpdatesManager {
 
   /**
    * The entity field manager.
@@ -69,12 +63,10 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Ent
     $this->entityTypeManager = $entity_type_manager;
     $this->updateManager = $update_manager;
     if (!$entity_field_manager) {
-      @trigger_error('The entity_field.manager service must be passed to ContentTranslationUpdatesManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
       $entity_field_manager = \Drupal::service('entity_field.manager');
     }
     $this->entityFieldManager = $entity_field_manager;
     if (!$entity_last_installed_schema_repository) {
-      @trigger_error('The entity.last_installed_schema.repository service must be passed to ContentTranslationUpdatesManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
       $entity_last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
     }
     $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository;
@@ -88,8 +80,6 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Ent
    */
   public function updateDefinitions(array $entity_types) {
     // Handle field storage definition creation, if needed.
-    // @todo Generalize this code in https://www.drupal.org/node/2346013.
-    // @todo Handle initial values in https://www.drupal.org/node/2346019.
     if ($this->updateManager->needsUpdates()) {
       foreach ($entity_types as $entity_type_id => $entity_type) {
         $storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
@@ -104,38 +94,4 @@ public function updateDefinitions(array $entity_types) {
     }
   }
 
-  /**
-   * Listener for the ConfigImporter import event.
-   */
-  public function onConfigImporterImport() {
-    $entity_types = array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
-      return $entity_type->isTranslatable();
-    });
-    $this->updateDefinitions($entity_types);
-  }
-
-  /**
-   * Listener for migration imports.
-   */
-  public function onMigrateImport(MigrateImportEvent $event) {
-    $migration = $event->getMigration();
-    $configuration = $migration->getDestinationConfiguration();
-    $entity_types = NestedArray::getValue($configuration, ['content_translation_update_definitions']);
-    if ($entity_types) {
-      $entity_types = array_intersect_key($this->entityTypeManager->getDefinitions(), array_flip($entity_types));
-      $this->updateDefinitions($entity_types);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getSubscribedEvents() {
-    $events[ConfigEvents::IMPORT][] = ['onConfigImporterImport', 60];
-    if (class_exists('\Drupal\migrate\Event\MigrateEvents')) {
-      $events[MigrateEvents::POST_IMPORT][] = ['onMigrateImport'];
-    }
-    return $events;
-  }
-
 }
diff --git a/core/modules/content_translation/tests/modules/content_translation_test/config/install/entity_test.entity_test_bundle.test.yml b/core/modules/content_translation/tests/modules/content_translation_test/config/install/entity_test.entity_test_bundle.test.yml
new file mode 100644
index 000000000000..83720bb8b81f
--- /dev/null
+++ b/core/modules/content_translation/tests/modules/content_translation_test/config/install/entity_test.entity_test_bundle.test.yml
@@ -0,0 +1,6 @@
+langcode: en
+status: true
+dependencies: {  }
+id: test
+label: null
+description: null
diff --git a/core/modules/content_translation/tests/modules/content_translation_test/config/install/language.content_settings.entity_test_with_bundle.test.yml b/core/modules/content_translation/tests/modules/content_translation_test/config/install/language.content_settings.entity_test_with_bundle.test.yml
new file mode 100644
index 000000000000..da9f0355d8c8
--- /dev/null
+++ b/core/modules/content_translation/tests/modules/content_translation_test/config/install/language.content_settings.entity_test_with_bundle.test.yml
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - entity_test.entity_test_bundle.test
+  module:
+    - content_translation
+third_party_settings:
+  content_translation:
+    enabled: true
+    bundle_settings:
+      untranslatable_fields_hide: '0'
+id: entity_test_with_bundle.test
+target_entity_type_id: entity_test_with_bundle
+target_bundle: test
+default_langcode: site_default
+language_alterable: true
diff --git a/core/modules/content_translation/tests/src/Kernel/ContentTranslationModuleInstallTest.php b/core/modules/content_translation/tests/src/Kernel/ContentTranslationModuleInstallTest.php
new file mode 100644
index 000000000000..9caca99fe920
--- /dev/null
+++ b/core/modules/content_translation/tests/src/Kernel/ContentTranslationModuleInstallTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\Tests\content_translation\Kernel;
+
+use Drupal\entity_test\Entity\EntityTestWithBundle;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests content translation for modules that provide translatable bundles.
+ *
+ * @group content_translation
+ */
+class ContentTranslationModuleInstallTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'content_translation',
+    'content_translation_test',
+    'entity_test',
+    'language',
+    'user',
+  ];
+
+  /**
+   * The content translation manager.
+   *
+   * @var \Drupal\content_translation\ContentTranslationManagerInterface
+   */
+  protected $contentTranslationManager;
+
+  /**
+   * The language code of the source language for this test.
+   *
+   * @var string
+   */
+  protected $sourceLangcode = 'en';
+
+  /**
+   * The language code of the translation language for this test.
+   *
+   * @var string
+   */
+  protected $translationLangcode = 'af';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('entity_test_with_bundle');
+    ConfigurableLanguage::createFromLangcode($this->translationLangcode)->save();
+
+    $this->contentTranslationManager = $this->container->get('content_translation.manager');
+  }
+
+  /**
+   * Test that content translation fields are created upon module installation.
+   */
+  public function testFieldUpdates() {
+    // The module ships a translatable bundle of the 'entity_test_with_bundle'
+    // entity type.
+    $this->installConfig(['content_translation_test']);
+
+    $entity = EntityTestWithBundle::create([
+      'type' => 'test',
+      'langcode' => $this->sourceLangcode,
+    ]);
+    $entity->save();
+
+    // Add a translation with some translation metadata.
+    $translation = $entity->addTranslation($this->translationLangcode);
+    $translation_metadata = $this->contentTranslationManager->getTranslationMetadata($translation);
+    $translation_metadata->setSource($this->sourceLangcode)->setOutdated(TRUE);
+    $translation->save();
+
+    // Make sure the translation metadata has been saved correctly.
+    $entity = EntityTestWithBundle::load($entity->id());
+    $translation = $entity->getTranslation($this->translationLangcode);
+    $translation_metadata = $this->contentTranslationManager->getTranslationMetadata($translation);
+    $this->assertSame($this->sourceLangcode, $translation_metadata->getSource());
+    $this->assertSame(TRUE, $translation_metadata->isOutdated());
+  }
+
+}
diff --git a/core/modules/language/migrations/d6_language_content_settings.yml b/core/modules/language/migrations/d6_language_content_settings.yml
index 6d9171ae32de..0eb7bbecbc7b 100644
--- a/core/modules/language/migrations/d6_language_content_settings.yml
+++ b/core/modules/language/migrations/d6_language_content_settings.yml
@@ -40,8 +40,6 @@ process:
       2: true
 destination:
   plugin: entity:language_content_settings
-  content_translation_update_definitions:
-    - node
 migration_dependencies:
   required:
     - d6_node_type
diff --git a/core/modules/language/migrations/d6_language_content_taxonomy_vocabulary_settings.yml b/core/modules/language/migrations/d6_language_content_taxonomy_vocabulary_settings.yml
index 9af288c3bbb7..29a459e8d630 100644
--- a/core/modules/language/migrations/d6_language_content_taxonomy_vocabulary_settings.yml
+++ b/core/modules/language/migrations/d6_language_content_taxonomy_vocabulary_settings.yml
@@ -46,8 +46,6 @@ process:
     source: language
 destination:
   plugin: entity:language_content_settings
-  content_translation_update_definitions:
-    - taxonomy_term
 migration_dependencies:
   required:
     - d6_taxonomy_vocabulary
diff --git a/core/modules/language/migrations/d7_language_content_settings.yml b/core/modules/language/migrations/d7_language_content_settings.yml
index 9428f42d4dfd..58e547ae5f1a 100644
--- a/core/modules/language/migrations/d7_language_content_settings.yml
+++ b/core/modules/language/migrations/d7_language_content_settings.yml
@@ -43,8 +43,6 @@ process:
       4: true
 destination:
   plugin: entity:language_content_settings
-  content_translation_update_definitions:
-    - node
 migration_dependencies:
   required:
     - d7_node_type
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestWithBundle.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestWithBundle.php
index e6cb5428b97c..42d26e0e0a0a 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestWithBundle.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestWithBundle.php
@@ -25,8 +25,10 @@
  *     },
  *   },
  *   base_table = "entity_test_with_bundle",
+ *   data_table = "entity_test_with_bundle_field_data",
  *   admin_permission = "administer entity_test_with_bundle content",
  *   persistent_cache = FALSE,
+ *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
-- 
GitLab