Problem/Motivation

Follow up to #3041659: Remove the layout tab from translations because Layout Builder does not support translations yet
That issue does not actually update existing layout fields to be non-translatable. New layout fields for bundles though will be marked as non-translatable.

#3041659 has detailed summary of why we cannot update the fields automatically without inspected the sites current configuration/content.

Proposed resolution

Update the layout fields to non translatable on a per bundle basis.
If all bundles that have layout overrides enabled for an entity type are able to have their fields updated to be non-translatable then update the layout field storage for the entity type to also be non-translatable.

It is not yet determine on what conditions we will be able to update a layout field for a bundle but possible solutions are

  1. !\Drupal::languageManager()->isMultilingual()
  2. !\Drupal::moduleHandler()->moduleExists('content_translation')
  3. There are no translations for a bundle, !default_langcode = 0(they enable translations but never used them)
  4. There no layout overrides for the bundle(they enable overrides but never used them)
  5. No single entity, across all of its revisions, has both a translation and a override

1,2 have the problem that may have translations that are orphaned because you disable a language but will be available if you re-add the same language. But this is probably a problem with content_translation in general. If you disable content translation are the field rows cleared out for each entity each time you save the entity? If so this would the same behavior. Will EntityQuery pick up these rows anyways?

3,4 seem the simplest query and probably could be queried by EntityQuery and we could update the field when either condition passes.

5. may not be possible to find with EntityQuery so we would have construct the query manually, #3041659-50: Remove the layout tab from translations because Layout Builder does not support translations yet has a version of this query tha t might work and has an update path test. But currently if testing 2 different cases when the bundle should not be an updated. It does not a test case for when bundle should be updated, no single entity has both a translation and a layout override.

3,4 won't be able to update as many sites but it probably safer because is simpler. It also has the advantage of being easier to understand so the documentation could shorter, more understandable. Also site owners would probably able to understand and predict whether there sites would match these conditions.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Comments

tedbow created an issue. See original summary.

tim.plunkett’s picture

Per discussion, tagging for now.

effulgentsia’s picture

Doing 3 and 4 is probably best. Those are simple queries. However, we'd need adequate test coverage for the different cases. So long as we're not blocked on issues with getting update path fixtures for these working, I think it's a great approach.

If the test fixtures for that are too problematic, and we want to go the route of 2 (or an and condition of 1 and 2), then we're in luck, because of #3043650: Exception when uninstalling CT when there are existing translations. If it weren't for that, there'd be the risk of orphaned translations, but since you can't uninstall CT if you have existing translations, we're (sort of) covered. Kind of sketchy relying on the fact that CT has a critical bug though. And I suppose there's the edge case possibility of someone having translations that they create via some other module (not via CT), though that's what anding the if with 1 would mitigate.

tedbow’s picture

Assigned: Unassigned » tedbow
Status: Active » Needs review
StatusFileSize
new31.29 KB

Here is try at 3 and 4.

some snippets from the patch

$test_cases = [
  'article' => [
    'has_translation' => TRUE,
    'has_layout' => FALSE,
    'nid' => 1,
    'vid' => 2,
    'title' => 'Test Article - Spanish title',
  ],
  'page' => [
    'has_translation' => FALSE,
    'has_layout' => TRUE,
    'nid' => 4,
    'vid' => 5,
    'title' => 'Page Test - Spanish title',
  ],
];
$expected_bundle_updates = [
      'article' => TRUE,
      'page' => TRUE,
    ];

Right now it only has 2 testes. Will add more

Currently I am only testing that field gets updated not any functionality of the entities. We may want to add that but wanted to keep as simple as possible for now.

tedbow’s picture

StatusFileSize
new12.61 KB
new34.8 KB

I wanted to varying the fixture set by test cases for which bundles have default and layouts. I don't think this is possible with a data provider method so I created a base class and then subclassed for each test case. This allows setting a $layoutBuilderTestCases that fixture has access to.

This reduces the number of fixture files that need to be setup.

plach’s picture

StatusFileSize
new470 bytes

This is looking great, the base test class is a nifty solution! I manually tested the fixtures by running tests and stopping them before the ::runUpdates() call: after manually running the update everything in the UI and in the database looks as expected.

  1. +++ b/core/modules/layout_builder/layout_builder.post_update.php
    @@ -186,3 +191,76 @@ function layout_builder_post_update_update_permissions() {
    +    $results = $query->execute();
    ...
    +  $results = $query->execute();
    

    If we array_intersect() the two full result sets we are actually implementing solution 5, I think. IMO we could do that, as this would further reduce the cases that are left out of luck. However we may need to pay attention to memory consumption, if the two result sets are very big (e.g. one million rows), here's the output of a test script I quickly put together:

    26661400 bytes
    33558608 bytes
    33558608 bytes
    33558608 bytes
    191301856 bytes
    
  2. +++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder-translation.php
    @@ -0,0 +1,150 @@
    +
    +/**
    + * Loop over test cases defined in the test class.
    + *
    + * @see \Drupal\Tests\layout_builder\Functional\Update\Translatability\MakeLayoutUntranslatableUpdatePathTestBase
    + */
    

    Nit: let's use regular inline comments (//) here.

  3. +++ b/core/modules/layout_builder/layout_builder.post_update.php
    @@ -9,6 +9,11 @@
    +use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
    +use Drupal\Core\Entity\Sql\DefaultTableMapping;
    

    Unused

  4. +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateCase1.php
    @@ -0,0 +1,46 @@
    +      'title' => 'Page Test - Spanish title',
    
    +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateCase3.php
    @@ -0,0 +1,46 @@
    +      'title' => 'Page Test - Spanish title',
    

    I think we can remove this line, it's not used in the two tests and makes things slightly confusing.

  5. +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/LayoutFieldTranslateUpdateCase3.php
    @@ -0,0 +1,46 @@
    +class LayoutFieldTranslateUpdateCase3 extends MakeLayoutUntranslatableUpdatePathTestBase {
    

    I couldn't help but noticing we are missing case 2: are you working on another test? :)

  6. +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/MakeLayoutUntranslatableUpdatePathTestBase.php
    @@ -0,0 +1,85 @@
    + * This method of testing is used instead of a @dataProvider method because test
    

    Can we use "data provider" instead, just in case some weird parsing bug leads to even weirder test failures?

  7. +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/MakeLayoutUntranslatableUpdatePathTestBase.php
    @@ -0,0 +1,85 @@
    +abstract class MakeLayoutUntranslatableUpdatePathTestBase extends UpdatePathTestBase {
    +
    +
    

    Nit: extra newline :)

tedbow’s picture

StatusFileSize
new6.17 KB
new34.53 KB

@plach thanks for review.

This is looking great, the base test class is a nifty solution!

Thanks! I will wasn't sure there was a better way but I think it makes the test fixtures simpler and adding more tests in the future if need not so drastic

  1. chatting with @plach and @effulgentsia about this. Not addressing now will in next patch.
  2. fixed
  3. fixed
  4. yep this was leftover from the previous issue. removed.
  5. Nope this local problem. Actually changed the names of the classes to LayoutFieldTranslateUpdateConfig and LayoutFieldTranslateUpdateConfigAndStorage to reflect that 1 case can update both the field config and the field storage.
  6. Good call. fixed
  7. fixed
plach’s picture

Patch looks good to me and works as advertised. This is RTBC as far as I'm concerned, unless we want to try and implement solution 5.

tedbow’s picture

StatusFileSize
new5.4 KB
new35.81 KB

I went over the patch with @effulgentsia(thanks!) and here are some changes from that.

All of the changes are refactoring and adding better comments to the changes to layout_builder.post_update.php.
There 3 only functional changes

  1. Adding ->accessCheck(FALSE) to the queries to ensure we check all entities
  2. Checking $entity_type->isTranslatable() and determining there are no translations if it is not.
  3. Checking $field_config->isTranslatable() before checking for translations or layout. This allows us to still update the field storage if a bundle was already configured to be non-translatable.
tim.plunkett’s picture

  1. +++ b/core/modules/layout_builder/layout_builder.post_update.php
    @@ -186,3 +189,107 @@ function layout_builder_post_update_update_permissions() {
    +    $update_field_storage = TRUE;
    ...
    +          $update_field_storage = FALSE;
    

    Would make me feel better if this started as FALSE and was later explicitly set to TRUE

  2. +++ b/core/modules/layout_builder/layout_builder.post_update.php
    @@ -186,3 +189,107 @@ function layout_builder_post_update_update_permissions() {
    +      // Set the field storage to untranslatable if possible. This removes all
    +      // layout fields for the entity type from the Content Translation
    

    "all layout fields" isn't it just one?

  3. +++ b/core/modules/layout_builder/layout_builder.post_update.php
    @@ -186,3 +189,107 @@ function layout_builder_post_update_update_permissions() {
    + *   The entity type.
    ...
    + *   The entity type.
    

    Nit: The entity type ID

  4. +++ b/core/modules/layout_builder/tests/src/Functional/Update/Translatability/MakeLayoutUntranslatableUpdatePathTestBase.php
    @@ -0,0 +1,84 @@
    +abstract class MakeLayoutUntranslatableUpdatePathTestBase extends UpdatePathTestBase {
    

    This is such a nice clean way to do this. +1

+1 to this being RTBC after those changes

tedbow’s picture

StatusFileSize
new35.86 KB
new2.88 KB

@tim.plunkett thanks for the review!

  1. fixed but had to change how it works slightly since any 1 bundle not being translatable has to stop the storage from being update. so changed to.
  2. $non_translatable_bundle_count = 0;
    Then each time we find that is either already non-translatable or that we update
    $non_translatable_bundle_count++;

    Then we only update the storage if
    if (count($field_infos[OverridesSectionStorage::FIELD_NAME]['bundles']) === $non_translatable_bundle_count) {

    Luckily we have test coverage for a cases for where we should and should NOT update the storage.

  3. No, we are in a nested loop so now are just dealing a particular entity type since the field storage is entity type level. There could be a layout field for each bundle so will be more than one.
  4. fixed
  5. thanks!
tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Works for me! Thanks so much

Adding credit

plach’s picture

+++ b/core/modules/layout_builder/layout_builder.post_update.php
@@ -186,3 +189,104 @@ function layout_builder_post_update_update_permissions() {
+        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;
+        }

Wouldn't it make sense to perform the same check on the field storage and skip looping over all the bundles if that's already FALSE?

  • effulgentsia committed 99382fb on 8.8.x
    Issue #3043651 by tedbow, plach, tim.plunkett, effulgentsia, xjm: Update...

  • effulgentsia committed 9bd2ebd on 8.7.x
    Issue #3043651 by tedbow, plach, tim.plunkett, effulgentsia, xjm: Update...
plach’s picture

Status: Reviewed & tested by the community » Fixed

@effulgentsia pointed out in Slack that unless some custom code changed the storage to FALSE it would always be TRUE, so I think committing a follow-up is not worth the effort.

effulgentsia’s picture

Version: 8.8.x-dev » 8.7.x-dev

Pushed to 8.8.x and 8.7.x.

The biggest benefit of this patch is that it updates things nicely for monolingual sites, so I updated the CR accordingly: https://www.drupal.org/node/3043164/revisions/view/11360219/11361603.

That it also handles some other easy cases, such as a bundle that enables layout overrides but doesn't have any, is a bonus.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.