diff --git a/core/modules/layout_builder/layout_builder.post_update.php b/core/modules/layout_builder/layout_builder.post_update.php index bbb6df6beae0f444d2e52a2cfacfb8f0f6278658..cc6ae9095c93af553beab5f0199f845caaad7bf7 100644 --- a/core/modules/layout_builder/layout_builder.post_update.php +++ b/core/modules/layout_builder/layout_builder.post_update.php @@ -9,6 +9,9 @@ use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; use Drupal\layout_builder\TempStoreIdentifierInterface; use Drupal\user\Entity\Role; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\field\Entity\FieldConfig; +use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage; /** * Rebuild plugin dependencies for all entity view displays. @@ -186,3 +189,104 @@ function layout_builder_post_update_update_permissions() { } } } + +/** + * Set the layout builder field as non-translatable where possible. + */ +function layout_builder_post_update_make_layout_untranslatable() { + /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */ + $field_manager = \Drupal::service('entity_field.manager'); + $field_map = $field_manager->getFieldMap(); + foreach ($field_map as $entity_type_id => $field_infos) { + if (isset($field_infos[OverridesSectionStorage::FIELD_NAME]['bundles'])) { + $non_translatable_bundle_count = 0; + foreach ($field_infos[OverridesSectionStorage::FIELD_NAME]['bundles'] as $bundle) { + $field_config = FieldConfig::loadByName($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME); + if (!$field_config->isTranslatable()) { + $non_translatable_bundle_count++; + // The layout field is already configured to be non-translatable so it + // does not need to be updated. + continue; + } + if (_layout_builder_bundle_has_no_translations($entity_type_id, $bundle) || _layout_builder_bundle_has_no_layouts($entity_type_id, $bundle)) { + // Either none of the entities have layouts or none of them have + // translations. In either case it is safe to set the field to be + // non-translatable. + $field_config->setTranslatable(FALSE); + $field_config->save(); + $non_translatable_bundle_count++; + } + } + // Set the field storage to untranslatable if the field config for each + // bundle is now untranslatable. This removes layout fields for the + // entity type from the Content Translation configuration form. + if (count($field_infos[OverridesSectionStorage::FIELD_NAME]['bundles']) === $non_translatable_bundle_count) { + $field_storage = FieldStorageConfig::loadByName($entity_type_id, OverridesSectionStorage::FIELD_NAME); + $field_storage->setTranslatable(FALSE); + $field_storage->save(); + } + } + } +} + +/** + * Determines if there are zero layout overrides for the bundle. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle + * The bundle name. + * + * @return bool + * TRUE if there are zero layout overrides for the bundle, otherwise FALSE. + */ +function _layout_builder_bundle_has_no_layouts($entity_type_id, $bundle) { + $entity_type_manager = \Drupal::entityTypeManager(); + $entity_type = $entity_type_manager->getDefinition($entity_type_id); + $bundle_key = $entity_type->getKey('bundle'); + $query = $entity_type_manager->getStorage($entity_type_id)->getQuery(); + if ($bundle_key) { + $query->condition($bundle_key, $bundle); + } + $query->exists(OverridesSectionStorage::FIELD_NAME) + ->accessCheck(FALSE) + ->allRevisions() + ->range(0, 1); + $results = $query->execute(); + return empty($results); +} + +/** + * Determines if there are zero translations for the bundle. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle + * The bundle name. + * + * @return bool + * TRUE if there are zero translations for the bundle, otherwise FALSE. + */ +function _layout_builder_bundle_has_no_translations($entity_type_id, $bundle) { + $entity_type_manager = \Drupal::entityTypeManager(); + $entity_type = $entity_type_manager->getDefinition($entity_type_id); + if (!$entity_type->isTranslatable()) { + return TRUE; + } + $query = $entity_type_manager->getStorage($entity_type_id)->getQuery(); + $bundle_key = $entity_type->getKey('bundle'); + if ($entity_type->hasKey('default_langcode')) { + if ($bundle_key) { + $query->condition($bundle_key, $bundle); + } + $query->condition($entity_type->getKey('default_langcode'), 0) + ->accessCheck(FALSE) + ->allRevisions() + ->range(0, 1); + $results = $query->execute(); + return empty($results); + } + // A translatable entity type should always have a default_langcode key. If it + // doesn't we have no way to determine if there are translations. + return FALSE; +} diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder-field-block.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder-field-block.php index 51325dcda09728728eb18c330bf9e60386c1f916..b0f9cead47ca5c7670e8914c7d5a4ac74c9e224c 100644 --- a/core/modules/layout_builder/tests/fixtures/update/layout-builder-field-block.php +++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder-field-block.php @@ -42,7 +42,7 @@ $connection = Database::getConnection(); -// Enable Layout Builder on an existing entity view display. +// Add Layout Builder sections to an existing entity view display. $display = $connection->select('config') ->fields('config', ['data']) ->condition('collection', '') @@ -50,8 +50,6 @@ ->execute() ->fetchField(); $display = unserialize($display); -$display['third_party_settings']['layout_builder']['enabled'] = TRUE; -$display['third_party_settings']['layout_builder']['allow_custom'] = TRUE; $display['third_party_settings']['layout_builder']['sections'][] = $section_array; $connection->update('config') ->fields([ @@ -63,178 +61,6 @@ ->condition('name', 'core.entity_view_display.node.article.default') ->execute(); -// Add the layout builder field and field storage. -$connection->insert('config') - ->fields([ - 'collection', - 'name', - 'data', - ]) - ->values([ - 'collection' => '', - 'name' => 'field.field.node.article.layout_builder__layout', - 'data' => 'a:16:{s:4:"uuid";s:36:"3a7fb64f-d1cf-4fd5-bd07-9f81d893021a";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:2:{i:0;s:41:"field.storage.node.layout_builder__layout";i:1;s:17:"node.type.article";}s:6:"module";a:1:{i:0;s:14:"layout_builder";}}s:2:"id";s:35:"node.article.layout_builder__layout";s:10:"field_name";s:22:"layout_builder__layout";s:11:"entity_type";s:4:"node";s:6:"bundle";s:7:"article";s:5:"label";s:6:"Layout";s:11:"description";s:0:"";s:8:"required";b:0;s:12:"translatable";b:1;s:13:"default_value";a:0:{}s:22:"default_value_callback";s:0:"";s:8:"settings";a:0:{}s:10:"field_type";s:14:"layout_section";}', - ]) - ->values([ - 'collection' => '', - 'name' => 'field.storage.node.layout_builder__layout', - 'data' => 'a:16:{s:4:"uuid";s:36:"65b11331-3cd9-4c45-b7a3-6bcfbfd56c6e";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"module";a:2:{i:0;s:14:"layout_builder";i:1;s:4:"node";}}s:2:"id";s:27:"node.layout_builder__layout";s:10:"field_name";s:22:"layout_builder__layout";s:11:"entity_type";s:4:"node";s:4:"type";s:14:"layout_section";s:8:"settings";a:0:{}s:6:"module";s:14:"layout_builder";s:6:"locked";b:1;s:11:"cardinality";i:1;s:12:"translatable";b:1;s:7:"indexes";a:0:{}s:22:"persist_with_no_fields";b:0;s:14:"custom_storage";b:0;}', - ]) - ->execute(); -$connection->insert('key_value') - ->fields([ - 'collection', - 'name', - 'value', - ]) - ->values([ - 'collection' => 'config.entity.key_store.field_config', - 'name' => 'uuid:3a7fb64f-d1cf-4fd5-bd07-9f81d893021a', - 'value' => 'a:1:{i:0;s:47:"field.field.node.article.layout_builder__layout";}', - ]) - ->values([ - 'collection' => 'config.entity.key_store.field_storage_config', - 'name' => 'uuid:65b11331-3cd9-4c45-b7a3-6bcfbfd56c6e', - 'value' => 'a:1:{i:0;s:41:"field.storage.node.layout_builder__layout";}', - ]) - ->values([ - 'collection' => 'entity.storage_schema.sql', - 'name' => 'node.field_schema_data.layout_builder__layout', - 'value' => 'a:2:{s:28:"node__layout_builder__layout";a:4:{s:11:"description";s:51:"Data storage for node field layout_builder__layout.";s:6:"fields";a:7:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:30:"layout_builder__layout_section";a:4:{s:4:"type";s:4:"blob";s:4:"size";s:6:"normal";s:9:"serialize";b:1;s:8:"not null";b:0;}}s:11:"primary key";a:4:{i:0;s:9:"entity_id";i:1;s:7:"deleted";i:2;s:5:"delta";i:3;s:8:"langcode";}s:7:"indexes";a:2:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}}}s:37:"node_revision__layout_builder__layout";a:4:{s:11:"description";s:63:"Revision archive storage for node field layout_builder__layout.";s:6:"fields";a:7:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:30:"layout_builder__layout_section";a:4:{s:4:"type";s:4:"blob";s:4:"size";s:6:"normal";s:9:"serialize";b:1;s:8:"not null";b:0;}}s:11:"primary key";a:5:{i:0;s:9:"entity_id";i:1;s:11:"revision_id";i:2;s:7:"deleted";i:3;s:5:"delta";i:4;s:8:"langcode";}s:7:"indexes";a:2:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}}}}', - ]) - ->execute(); -$connection->update('key_value') - ->fields([ - 'collection' => 'entity.definitions.bundle_field_map', - 'name' => 'node', - 'value' => 'a:5:{s:11:"field_image";a:2:{s:4:"type";s:5:"image";s:7:"bundles";a:1:{s:7:"article";s:7:"article";}}s:7:"comment";a:2:{s:4:"type";s:7:"comment";s:7:"bundles";a:1:{s:7:"article";s:7:"article";}}s:10:"field_tags";a:2:{s:4:"type";s:16:"entity_reference";s:7:"bundles";a:1:{s:7:"article";s:7:"article";}}s:4:"body";a:2:{s:4:"type";s:17:"text_with_summary";s:7:"bundles";a:2:{s:4:"page";s:4:"page";s:7:"article";s:7:"article";}}s:22:"layout_builder__layout";a:2:{s:4:"type";s:14:"layout_section";s:7:"bundles";a:1:{s:7:"article";s:7:"article";}}}', - ]) - ->condition('collection', 'entity.definitions.bundle_field_map') - ->condition('name', 'node') - ->execute(); - -// Create tables for the layout builder field. -$connection->schema()->createTable('node__layout_builder__layout', [ - 'fields' => [ - 'bundle' => [ - 'type' => 'varchar_ascii', - 'not null' => TRUE, - 'length' => '128', - 'default' => '', - ], - 'deleted' => [ - 'type' => 'int', - 'not null' => TRUE, - 'size' => 'tiny', - 'default' => '0', - ], - 'entity_id' => [ - 'type' => 'int', - 'not null' => TRUE, - 'size' => 'normal', - 'unsigned' => TRUE, - ], - 'revision_id' => [ - 'type' => 'int', - 'not null' => TRUE, - 'size' => 'normal', - 'unsigned' => TRUE, - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'not null' => TRUE, - 'length' => '32', - 'default' => '', - ], - 'delta' => [ - 'type' => 'int', - 'not null' => TRUE, - 'size' => 'normal', - 'unsigned' => TRUE, - ], - 'layout_builder__layout_section' => [ - 'type' => 'blob', - 'not null' => FALSE, - 'size' => 'normal', - ], - ], - 'primary key' => [ - 'entity_id', - 'deleted', - 'delta', - 'langcode', - ], - 'indexes' => [ - 'bundle' => [ - 'bundle', - ], - 'revision_id' => [ - 'revision_id', - ], - ], - 'mysql_character_set' => 'utf8mb4', -]); -$connection->schema()->createTable('node_revision__layout_builder__layout', [ - 'fields' => [ - 'bundle' => [ - 'type' => 'varchar_ascii', - 'not null' => TRUE, - 'length' => '128', - 'default' => '', - ], - 'deleted' => [ - 'type' => 'int', - 'not null' => TRUE, - 'size' => 'tiny', - 'default' => '0', - ], - 'entity_id' => [ - 'type' => 'int', - 'not null' => TRUE, - 'size' => 'normal', - 'unsigned' => TRUE, - ], - 'revision_id' => [ - 'type' => 'int', - 'not null' => TRUE, - 'size' => 'normal', - 'unsigned' => TRUE, - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'not null' => TRUE, - 'length' => '32', - 'default' => '', - ], - 'delta' => [ - 'type' => 'int', - 'not null' => TRUE, - 'size' => 'normal', - 'unsigned' => TRUE, - ], - 'layout_builder__layout_section' => [ - 'type' => 'blob', - 'not null' => FALSE, - 'size' => 'normal', - ], - ], - 'primary key' => [ - 'entity_id', - 'revision_id', - 'deleted', - 'delta', - 'langcode', - ], - 'indexes' => [ - 'bundle' => [ - 'bundle', - ], - 'revision_id' => [ - 'revision_id', - ], - ], - 'mysql_character_set' => 'utf8mb4', -]); // Add the layout data to the node. $connection->insert('node__layout_builder__layout') ->fields([ diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder-field-schema.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder-field-schema.php new file mode 100644 index 0000000000000000000000000000000000000000..426107a676339df6eb5ba1162ce65370d63aeb01 --- /dev/null +++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder-field-schema.php @@ -0,0 +1,216 @@ +<?php + +/** + * @file + * Test fixture. + */ + +use Drupal\Core\Database\Database; + +$connection = Database::getConnection(); + +// Enable Layout Builder on an existing entity view display. +foreach (['article', 'page'] as $bundle) { + $display = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', "core.entity_view_display.node.$bundle.default") + ->execute() + ->fetchField(); + $display = unserialize($display); + $display['third_party_settings']['layout_builder']['enabled'] = TRUE; + $display['third_party_settings']['layout_builder']['allow_custom'] = TRUE; + $connection->update('config') + ->fields([ + 'data' => serialize($display), + 'collection' => '', + 'name' => "core.entity_view_display.node.$bundle.default", + ]) + ->condition('collection', '') + ->condition('name', "core.entity_view_display.node.$bundle.default") + ->execute(); +} + + +// Add the layout builder field and field storage. +$connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'field.field.node.article.layout_builder__layout', + 'data' => 'a:16:{s:4:"uuid";s:36:"3a7fb64f-d1cf-4fd5-bd07-9f81d893021a";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:2:{i:0;s:41:"field.storage.node.layout_builder__layout";i:1;s:17:"node.type.article";}s:6:"module";a:1:{i:0;s:14:"layout_builder";}}s:2:"id";s:35:"node.article.layout_builder__layout";s:10:"field_name";s:22:"layout_builder__layout";s:11:"entity_type";s:4:"node";s:6:"bundle";s:7:"article";s:5:"label";s:6:"Layout";s:11:"description";s:0:"";s:8:"required";b:0;s:12:"translatable";b:1;s:13:"default_value";a:0:{}s:22:"default_value_callback";s:0:"";s:8:"settings";a:0:{}s:10:"field_type";s:14:"layout_section";}', + ]) + ->values([ + 'collection' => '', + 'name' => 'field.field.node.page.layout_builder__layout', + 'data' => 'a:16:{s:4:"uuid";s:36:"6439079b-0f6f-43aa-8e08-1ae42ba1333f";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:2:{i:0;s:41:"field.storage.node.layout_builder__layout";i:1;s:14:"node.type.page";}s:6:"module";a:1:{i:0;s:14:"layout_builder";}}s:2:"id";s:32:"node.page.layout_builder__layout";s:10:"field_name";s:22:"layout_builder__layout";s:11:"entity_type";s:4:"node";s:6:"bundle";s:4:"page";s:5:"label";s:6:"Layout";s:11:"description";s:0:"";s:8:"required";b:0;s:12:"translatable";b:1;s:13:"default_value";a:0:{}s:22:"default_value_callback";s:0:"";s:8:"settings";a:0:{}s:10:"field_type";s:14:"layout_section";}', + ]) + ->values([ + 'collection' => '', + 'name' => 'field.storage.node.layout_builder__layout', + 'data' => 'a:16:{s:4:"uuid";s:36:"65b11331-3cd9-4c45-b7a3-6bcfbfd56c6e";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"module";a:2:{i:0;s:14:"layout_builder";i:1;s:4:"node";}}s:2:"id";s:27:"node.layout_builder__layout";s:10:"field_name";s:22:"layout_builder__layout";s:11:"entity_type";s:4:"node";s:4:"type";s:14:"layout_section";s:8:"settings";a:0:{}s:6:"module";s:14:"layout_builder";s:6:"locked";b:1;s:11:"cardinality";i:1;s:12:"translatable";b:1;s:7:"indexes";a:0:{}s:22:"persist_with_no_fields";b:0;s:14:"custom_storage";b:0;}', + ]) + ->execute(); +$connection->insert('key_value') + ->fields([ + 'collection', + 'name', + 'value', + ]) + ->values([ + 'collection' => 'config.entity.key_store.field_config', + 'name' => 'uuid:3a7fb64f-d1cf-4fd5-bd07-9f81d893021a', + 'value' => 'a:1:{i:0;s:47:"field.field.node.article.layout_builder__layout";}', + ]) + ->values([ + 'collection' => 'config.entity.key_store.field_config', + 'name' => 'uuid:6439079b-0f6f-43aa-8e08-1ae42ba1333f', + 'value' => 'a:1:{i:0;s:44:"field.field.node.page.layout_builder__layout";}";}', + ]) + ->values([ + 'collection' => 'config.entity.key_store.field_storage_config', + 'name' => 'uuid:65b11331-3cd9-4c45-b7a3-6bcfbfd56c6e', + 'value' => 'a:1:{i:0;s:41:"field.storage.node.layout_builder__layout";}', + ]) + ->values([ + 'collection' => 'entity.storage_schema.sql', + 'name' => 'node.field_schema_data.layout_builder__layout', + 'value' => 'a:2:{s:28:"node__layout_builder__layout";a:4:{s:11:"description";s:51:"Data storage for node field layout_builder__layout.";s:6:"fields";a:7:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:30:"layout_builder__layout_section";a:4:{s:4:"type";s:4:"blob";s:4:"size";s:6:"normal";s:9:"serialize";b:1;s:8:"not null";b:0;}}s:11:"primary key";a:4:{i:0;s:9:"entity_id";i:1;s:7:"deleted";i:2;s:5:"delta";i:3;s:8:"langcode";}s:7:"indexes";a:2:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}}}s:37:"node_revision__layout_builder__layout";a:4:{s:11:"description";s:63:"Revision archive storage for node field layout_builder__layout.";s:6:"fields";a:7:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:30:"layout_builder__layout_section";a:4:{s:4:"type";s:4:"blob";s:4:"size";s:6:"normal";s:9:"serialize";b:1;s:8:"not null";b:0;}}s:11:"primary key";a:5:{i:0;s:9:"entity_id";i:1;s:11:"revision_id";i:2;s:7:"deleted";i:3;s:5:"delta";i:4;s:8:"langcode";}s:7:"indexes";a:2:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}}}}', + ]) + ->execute(); +$connection->update('key_value') + ->fields([ + 'collection' => 'entity.definitions.bundle_field_map', + 'name' => 'node', + 'value' => 'a:5:{s:11:"field_image";a:2:{s:4:"type";s:5:"image";s:7:"bundles";a:1:{s:7:"article";s:7:"article";}}s:7:"comment";a:2:{s:4:"type";s:7:"comment";s:7:"bundles";a:1:{s:7:"article";s:7:"article";}}s:10:"field_tags";a:2:{s:4:"type";s:16:"entity_reference";s:7:"bundles";a:1:{s:7:"article";s:7:"article";}}s:4:"body";a:2:{s:4:"type";s:17:"text_with_summary";s:7:"bundles";a:2:{s:4:"page";s:4:"page";s:7:"article";s:7:"article";}}s:22:"layout_builder__layout";a:2:{s:4:"type";s:14:"layout_section";s:7:"bundles";a:2:{s:7:"article";s:7:"article";s:4:"page";s:4:"page";}}}', + ]) + ->condition('collection', 'entity.definitions.bundle_field_map') + ->condition('name', 'node') + ->execute(); + +// Create tables for the layout builder field. +$connection->schema()->createTable('node__layout_builder__layout', [ + 'fields' => [ + 'bundle' => [ + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ], + 'deleted' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ], + 'entity_id' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ], + 'revision_id' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ], + 'langcode' => [ + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ], + 'delta' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ], + 'layout_builder__layout_section' => [ + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'normal', + ], + ], + 'primary key' => [ + 'entity_id', + 'deleted', + 'delta', + 'langcode', + ], + 'indexes' => [ + 'bundle' => [ + 'bundle', + ], + 'revision_id' => [ + 'revision_id', + ], + ], + 'mysql_character_set' => 'utf8mb4', +]); +$connection->schema()->createTable('node_revision__layout_builder__layout', [ + 'fields' => [ + 'bundle' => [ + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ], + 'deleted' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ], + 'entity_id' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ], + 'revision_id' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ], + 'langcode' => [ + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ], + 'delta' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ], + 'layout_builder__layout_section' => [ + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'normal', + ], + ], + 'primary key' => [ + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'langcode', + ], + 'indexes' => [ + 'bundle' => [ + 'bundle', + ], + 'revision_id' => [ + 'revision_id', + ], + ], + 'mysql_character_set' => 'utf8mb4', +]); diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder-translation.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder-translation.php new file mode 100644 index 0000000000000000000000000000000000000000..19e3dff72223ce1aaaf97e6367152d61141e7f59 --- /dev/null +++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder-translation.php @@ -0,0 +1,147 @@ +<?php + +/** + * @file + * Test fixture. + */ + +use Drupal\Core\Database\Database; +use Drupal\layout_builder\Section; + +$section_array_default = [ + 'layout_id' => 'layout_onecol', + 'layout_settings' => [], + 'components' => [ + 'some-uuid' => [ + 'uuid' => 'some-uuid', + 'region' => 'content', + 'configuration' => [ + 'id' => 'system_powered_by_block', + 'label' => 'This is in English', + 'provider' => 'system', + 'label_display' => 'visible', + ], + 'additional' => [], + 'weight' => 0, + ], + ], +]; +$section_array_translation = $section_array_default; +$section_array_translation['components']['some-uuid']['configuration']['label'] = 'This is in Spanish'; + +$connection = Database::getConnection(); +$connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'language.content_settings.node.article', + 'data' => 'a:10:{s:4:"uuid";s:36:"450e592a-f451-4685-8f56-02b0f5107cb7";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:1:{i:0;s:17:"node.type.article";}s:6:"module";a:1:{i:0;s:19:"content_translation";}}s:20:"third_party_settings";a:1:{s:19:"content_translation";a:2:{s:7:"enabled";b:1;s:15:"bundle_settings";a:1:{s:26:"untranslatable_fields_hide";s:1:"0";}}}s:2:"id";s:12:"node.article";s:21:"target_entity_type_id";s:4:"node";s:13:"target_bundle";s:7:"article";s:16:"default_langcode";s:12:"site_default";s:18:"language_alterable";b:1;}', + ]) + ->values([ + 'collection' => '', + 'name' => 'language.content_settings.node.page', + 'data' => 'a:10:{s:4:"uuid";s:36:"2b8b721e-59e9-4b57-a026-c4444fd28196";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:1:{i:0;s:14:"node.type.page";}s:6:"module";a:1:{i:0;s:19:"content_translation";}}s:20:"third_party_settings";a:1:{s:19:"content_translation";a:2:{s:7:"enabled";b:1;s:15:"bundle_settings";a:1:{s:26:"untranslatable_fields_hide";s:1:"0";}}}s:2:"id";s:9:"node.page";s:21:"target_entity_type_id";s:4:"node";s:13:"target_bundle";s:4:"page";s:16:"default_langcode";s:12:"site_default";s:18:"language_alterable";b:1;}', + ]) + ->execute(); + +// Add Layout Builder sections to an existing entity view display. +$display = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.node.article.default') + ->execute() + ->fetchField(); +$display = unserialize($display); +$display['third_party_settings']['layout_builder']['sections'][] = $section_array_default; +$connection->update('config') + ->fields([ + 'data' => serialize($display), + 'collection' => '', + 'name' => 'core.entity_view_display.node.article.default', + ]) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.node.article.default') + ->execute(); + +$display = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.node.page.default') + ->execute() + ->fetchField(); +$display = unserialize($display); +$display['third_party_settings']['layout_builder']['sections'][] = $section_array_default; +$connection->update('config') + ->fields([ + 'data' => serialize($display), + 'collection' => '', + 'name' => 'core.entity_view_display.node.page.default', + ]) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.node.page.default') + ->execute(); + +// Loop over test cases defined in the test class. +// @see \Drupal\Tests\layout_builder\Functional\Update\Translatability\MakeLayoutUntranslatableUpdatePathTestBase +foreach ($this->layoutBuilderTestCases as $bundle => $test_case) { + if ($test_case['has_layout']) { + $values_en = [ + 'bundle' => $bundle, + 'deleted' => '0', + 'entity_id' => $test_case['nid'], + 'revision_id' => $test_case['vid'], + 'langcode' => 'en', + 'delta' => '0', + 'layout_builder__layout_section' => serialize(Section::fromArray($section_array_default)), + ]; + + // Add the layout data to the node. + $connection->insert('node__layout_builder__layout') + ->fields(array_keys($values_en)) + ->values($values_en) + ->execute(); + $connection->insert('node_revision__layout_builder__layout') + ->fields(array_keys($values_en)) + ->values($values_en) + ->execute(); + } + + if ($test_case['has_translation']) { + $node_field_data = $connection->select('node_field_data') + ->fields('node_field_data') + ->condition('nid', $test_case['nid']) + ->condition('vid', $test_case['vid']) + ->execute() + ->fetchAssoc(); + + $node_field_data['title'] = "Test: $bundle"; + $node_field_data['langcode'] = 'es'; + $node_field_data['default_langcode'] = 0; + $node_field_data['revision_translation_affected'] = 1; + $node_field_data['content_translation_source'] = 'en'; + $connection->insert('node_field_data') + ->fields(array_keys($node_field_data)) + ->values($node_field_data) + ->execute(); + + $node_field_revision = $connection->select('node_field_revision') + ->fields('node_field_revision') + ->condition('nid', $test_case['nid']) + ->condition('vid', $test_case['vid']) + ->execute() + ->fetchAssoc(); + $node_field_revision['title'] = "Test: $bundle"; + $node_field_revision['langcode'] = 'es'; + $node_field_revision['default_langcode'] = 0; + $node_field_revision['revision_translation_affected'] = 1; + $node_field_revision['content_translation_source'] = 'en'; + $connection->insert('node_field_revision') + ->fields(array_keys($node_field_revision)) + ->values($node_field_revision) + ->execute(); + } +} diff --git a/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderContextMappingUpdatePathTest.php b/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderContextMappingUpdatePathTest.php index aae5b1a500f074eb0df9c678b3b482416815d978..8d820e2791102306467dacafd69f488a7f04295d 100644 --- a/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderContextMappingUpdatePathTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderContextMappingUpdatePathTest.php @@ -19,6 +19,7 @@ protected function setDatabaseDumpFiles() { $this->databaseDumpFiles = [ __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz', __DIR__ . '/../../../fixtures/update/layout-builder.php', + __DIR__ . '/../../../fixtures/update/layout-builder-field-schema.php', __DIR__ . '/../../../fixtures/update/layout-builder-field-block.php', ]; } diff --git a/core/modules/layout_builder/tests/src/Functional/Update/TempstoreKeyUpdatePathTest.php b/core/modules/layout_builder/tests/src/Functional/Update/TempstoreKeyUpdatePathTest.php index 32406eeb7344ac0d03639005df8b1bd2c97775a0..17afb1ca82bc66ba62749af0c854e319c839cef1 100644 --- a/core/modules/layout_builder/tests/src/Functional/Update/TempstoreKeyUpdatePathTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Update/TempstoreKeyUpdatePathTest.php @@ -19,6 +19,7 @@ protected function setDatabaseDumpFiles() { $this->databaseDumpFiles = [ __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz', __DIR__ . '/../../../fixtures/update/layout-builder.php', + __DIR__ . '/../../../fixtures/update/layout-builder-field-schema.php', __DIR__ . '/../../../fixtures/update/layout-builder-field-block.php', __DIR__ . '/../../../fixtures/update/layout-builder-tempstore.php', ]; diff --git a/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateConfig.php b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..59e748a9ba0ef6db360bbe06644a750beff4d732 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateConfig.php @@ -0,0 +1,44 @@ +<?php + +namespace Drupal\Tests\layout_builder\Functional\Update\Translatability; + +/** + * A test case that updates 1 bundle's field but not both. + * + * @group layout_builder + * @group legacy + */ +class LayoutFieldTranslateUpdateConfig extends MakeLayoutUntranslatableUpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected $layoutBuilderTestCases = [ + 'article' => [ + 'has_translation' => TRUE, + 'has_layout' => TRUE, + 'nid' => 1, + 'vid' => 2, + ], + 'page' => [ + 'has_translation' => FALSE, + 'has_layout' => FALSE, + 'nid' => 4, + 'vid' => 5, + ], + ]; + + /** + * {@inheritdoc} + */ + protected $expectedBundleUpdates = [ + 'article' => FALSE, + 'page' => TRUE, + ]; + + /** + * {@inheritdoc} + */ + protected $expectedFieldStorageUpdate = FALSE; + +} diff --git a/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateConfigAndStorage.php b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateConfigAndStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..63e9dfbd20c368f5966004ffd030b7efc891bf6e --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateConfigAndStorage.php @@ -0,0 +1,44 @@ +<?php + +namespace Drupal\Tests\layout_builder\Functional\Update\Translatability; + +/** + * A test case that updates both bundles' fields. + * + * @group layout_builder + * @group legacy + */ +class LayoutFieldTranslateUpdateConfigAndStorage extends MakeLayoutUntranslatableUpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected $layoutBuilderTestCases = [ + 'article' => [ + 'has_translation' => TRUE, + 'has_layout' => FALSE, + 'nid' => 1, + 'vid' => 2, + ], + 'page' => [ + 'has_translation' => FALSE, + 'has_layout' => TRUE, + 'nid' => 4, + 'vid' => 5, + ], + ]; + + /** + * {@inheritdoc} + */ + protected $expectedBundleUpdates = [ + 'article' => TRUE, + 'page' => TRUE, + ]; + + /** + * {@inheritdoc} + */ + protected $expectedFieldStorageUpdate = TRUE; + +} diff --git a/core/modules/layout_builder/tests/src/Functional/Update/Translatability/MakeLayoutUntranslatableUpdatePathTestBase.php b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/MakeLayoutUntranslatableUpdatePathTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..7bb91f7c353baeb1537f4869ee98e9baff268849 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/MakeLayoutUntranslatableUpdatePathTestBase.php @@ -0,0 +1,84 @@ +<?php + +namespace Drupal\Tests\layout_builder\Functional\Update\Translatability; + +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage; + +/** + * Base class for upgrade path for translatable layouts. + * + * Each class that extends this class will test 1 case for including 2 content + * types. + * + * This method of testing is used instead of a data provider method because test + * fixtures do not have access to the provide data. This allows varying fixture + * data per test case. + * + * @see layout_builder_post_update_make_layout_untranslatable() + */ +abstract class MakeLayoutUntranslatableUpdatePathTestBase extends UpdatePathTestBase { + + /** + * Layout builder test cases. + * + * Keys are bundle names. Values are test cases including keys: + * - has_translation + * - has_layout + * - vid + * - nid + * + * @var array + */ + protected $layoutBuilderTestCases; + + /** + * Expectations of field updates by bundles. + * + * @var array + */ + protected $expectedBundleUpdates; + + /** + * Whether the field storage should be updated. + * + * @var bool + */ + protected $expectedFieldStorageUpdate; + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz', + __DIR__ . '/../../../../fixtures/update/layout-builder.php', + __DIR__ . '/../../../../fixtures/update/layout-builder-field-schema.php', + __DIR__ . '/../../../../fixtures/update/layout-builder-translation.php', + ]; + } + + /** + * Tests the upgrade path for translatable layouts. + * + * @see layout_builder_post_update_make_layout_untranslatable() + */ + public function testDisableTranslationOnLayouts() { + $this->runUpdates(); + foreach ($this->expectedBundleUpdates as $bundle => $field_update_expected) { + $this->assertEquals( + $field_update_expected, + !FieldConfig::loadByName('node', $bundle, OverridesSectionStorage::FIELD_NAME)->isTranslatable(), + $field_update_expected ? "Field on $bundle set to be non-translatable." : "Field on $bundle not set to non-translatable." + ); + } + + $this->assertEquals( + $this->expectedFieldStorageUpdate, + !FieldStorageConfig::loadByName('node', OverridesSectionStorage::FIELD_NAME)->isTranslatable() + ); + } + +}