Problem/Motivation

@matsbla wrote at #2860097-162: Ensure that content translations can be moderated independently:

I tested patch from #157 and to me it seems like after I apply it I'm not any longer able to delete drafts for languages that didn't yet have any published translations. I tested both using the "delete" operation in Translation tab, the "delete" button in the draft form, and the "delete" button in the local task bar. Every time I get the status message like: [French] translation of the Article [blablabla] has been deleted. But when I go back to the translation tab again the draft is still there.

Maybe it should be possible to discard a draft without deleting the last published version of the language. Right now it doesn't seem like we do have that choice.

@plach indicates this is a known limitation, which is a problem only if you enable Content Moderation. It's due to a limitation in the low-level Entity/Field API infrastructure: in the storage a translation removed in a pending revision is indistinguishable from a new translation added in a pending revision.

This is not introduced by #2860097, it is exposed by #2860097!

Proposed resolution

Fix fully in 8.6
By introducing the necessary infrastructure in Entity/Field API (see #2945956: Allow removing translations in pending revisions).
Fix cosmetically in 8.5 (during beta)
By hiding the delete links when the latest translation-affecting revision is a pending revision. This should be an acceptable compromise, since a pending revision is normally not accessible to regular users anyway.

Remaining tasks

  • Validate the proposed solution
  • Write a patch
  • Reviews

User interface changes

API changes

None.

Data model changes

None.

CommentFileSizeAuthor
#41 Translations_of_Test_1_10_EN___Drupal_8_x.png115.14 KBplach
#41 ct-revision_translation_deletion-2940890-41.patch28.41 KBplach
#41 ct-revision_translation_deletion-2940890-41.interdiff.txt13.31 KBplach
#36 interdiff.txt1.78 KBeffulgentsia
#36 ct-revision_translation_deletion-2940890-36.patch32.49 KBeffulgentsia
#35 ct-revision_translation_deletion-2940890-35.patch32.13 KBeffulgentsia
#34 ct-revision_translation_deletion-2940890-34.patch32.06 KBplach
#34 ct-revision_translation_deletion-2940890-34.interdiff.txt860 bytesplach
#32 ct-revision_translation_deletion-2940890-32.patch32.04 KBplach
#32 ct-revision_translation_deletion-2940890-32.interdiff.txt0 bytesplach
#31 ct-revision_translation_deletion-2940890-31.review.patch32.04 KBplach
#31 ct-revision_translation_deletion-2940890-31.patch35.75 KBplach
#31 ct-revision_translation_deletion-2940890-31.interdiff.txt705 bytesplach
#28 ct-revision_translation_deletion-2940890-28.interdiff.txt798 bytesplach
#28 ct-revision_translation_deletion-2940890-28.patch35.59 KBplach
#27 ct-revision_translation_deletion-2940890-27.patch35.59 KBplach
#27 ct-revision_translation_deletion-2940890-27.interdiff.txt1.7 KBplach
#25 ct-revision_translation_deletion-2940890-24.patch35.63 KBplach
#25 ct-revision_translation_deletion-2940890-24.interdiff.txt2.99 KBplach
#24 ct-revision_translation_deletion-2940890-23.patch35.27 KBplach
#24 ct-revision_translation_deletion-2940890-23.interdiff.txt9.53 KBplach
#22 ct-revision_translation_deletion-2940890-22.patch31.8 KBplach
#22 ct-revision_translation_deletion-2940890-22.interdiff.txt2.81 KBplach
#20 ct-revision_translation_deletion-2940890-20.patch31.75 KBplach
#20 ct-revision_translation_deletion-2940890-20.test.patch16.02 KBplach
#20 ct-revision_translation_deletion-2940890-20.interdiff.txt820 bytesplach
#18 ct-revision_translation_deletion-2940890-18.patch31.53 KBplach
#18 ct-revision_translation_deletion-2940890-18.interdiff.txt21.98 KBplach
#18 Edit_Moderated_Test_1_8_IT__Italian_translation____Drupal_8_x.png88.01 KBplach
#16 ct-revision_translation_deletion-2940890-16-FIX_ROOT_CAUSE.patch22.83 KBwim leers
#16 interdiff-FIX_ROOT_CAUSE.txt703 byteswim leers
#16 ct-revision_translation_deletion-2940890-16-FIX_SYMPTOM.patch22.59 KBwim leers
#16 interdiff-FIX_SYMPTOM.txt965 byteswim leers
#16 interdiff-enable_commented_test.txt1.12 KBwim leers
#15 Screenshot from 2018-02-19 16-39-08.png25.25 KBmatsbla
#13 Translations_of_Test_1_1_EN___Drupal_8_x.png123.26 KBplach
#13 Fullscreen_19_02_18_15_42.png102.61 KBplach
#13 ct-revision_translation_deletion-2940890-12.interdiff.txt11.46 KBplach
#13 ct-revision_translation_deletion-2940890-12.patch22.12 KBplach
#10 ct-revision_translation_deletion-2940890-10.patch13.26 KBplach
#7 ct-revision_translation_deletion-2940890-7.patch3.75 KBplach

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Please credit @matsbla & @plach.

timmillwood’s picture

Issue tags: +DrupalWTF
matsbla’s picture

I tested again now, it looks like even though I after deleting the translation see it in the translation overview page I'm not able to view it anymore. When I click to view the translation I just see the original language. If I use the operation link to try delete the language a second time I'm in fact sent to a page to delete the original language (the whole node).

I've also made another observation that might be a little bit related to this. If I add a draft for a new translation to a node and look at it I'm also being sent to view the original language. I can see the draft inside the "Latest version" tab, but when I click "View" I see original language, and inside "Revision" tab I see revision for original language. It seems like the draft just inherit whatever it doesn't have from the original language. Maybe it would make sense to not display "view" tab and "revision" tab as the draft for a new translation doesn't have those thing yet. Just like hiding "Delete" button these things could be hidden too.

Still I think there should be a "Discard" button to be able to trash a draft you do not want to keep.

plach’s picture

Still I think there should be a "Discard" button to be able to trash a draft you do not want to keep.

Agreed this would be ideal, but it's just nearly impossible to implement properly without a deleted flag for each revision translation. In fact right now when you create a new revision as a draft, the default revision has no entry for that translation. So right now in the storage a new draft translation and a removed translation are basically indistinguishable.

plach’s picture

Title: [PP-1] Follow-up for #2860097: Draft revisions of translations can not be deleted » Follow-up for #2860097: Draft revisions of translations can not be deleted
Status: Postponed » Active
plach’s picture

Status: Active » Needs review
Issue tags: +Needs tests, +Usability
StatusFileSize
new3.75 KB

Here's an attempt to fix this while preserving the ability to delete translations: this patch is hiding the delete link only when dealing with a pending revision, which means that translations can be removed only in default revisions. This should be an acceptable compromise, since a pending revision is normally not accessible to regular users anyway.

matsbla’s picture

I tested the patch, however I'm still able to see the delete link when dealing with a pending revision.
When I click delete it deletes the published version of the translation, but keep the draft revision.

After the published version is deleted I still see a "view" tab. When I click on that I see the node it it's original language, which to me seem a little bit strange.

plach’s picture

Do you mean the delete tab?

plach’s picture

Posting some additional test coverage. I have lingering failure I was not able to troubleshoot yet. @Wim Leers said he would look into it.

plach’s picture

Issue summary: View changes

Updated IS

Status: Needs review » Needs work

The last submitted patch, 10: ct-revision_translation_deletion-2940890-10.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

plach’s picture

Still a few rough edges to smooth out, but this should be close to complete. Adding screenshots of the proposed solution.

@matsbla, it would be great if you could test this again.

plach’s picture

@matsbla:

After the published version is deleted I still see a "view" tab. When I click on that I see the node it it's original language, which to me seem a little bit strange.

This is "by design": it's how core works right now, not something this patch is introducing, I believe. It should be the regular behavior when a translation does not exist yet.

matsbla’s picture

StatusFileSize
new25.25 KB

Okay I get it now, I was looking at the edit form, there is still a delete button there. Earlier when I click on it I delete the published version of the translation, but in last patch I come to an "Access denied" page.

I'm a little bit confused about the solution for this issue. The issue is named "Draft revisions of translations can not be deleted", but after I apply this patch I'm not able to delete/discard a draft. With this approach to me it seems like I need to first publish a draft and first then I'm able to delete the translation. But I can't delete a draft and still keep current published version.

This is "by design": it's how core works right now, not something this patch is introducing

I understand that it is not introduced here, but is this really how it is intended to work? I find it quite confusing that I see another language at the "view" button, I would find it more logical if I'm not able to click a "view" button at all if there is no published translation to view. Anyway I just mentioned the detail as it seemed related earlier when I did the initial tests.

wim leers’s picture

I have lingering failure I was not able to troubleshoot yet. @Wim Leers said he would look into it.

You won’t believe this. When you test manually, it works fine. In the test, it fails, because Dynamic Page Cache (DPC) is caching it. But why is DPC caching it only in the test, not in manual testing? Well, in manual testing, you do that in the Standard install profile. And guess what standard_install() does?

  // Enable the admin theme.
  \Drupal::configFactory()->getEditable('node.settings')->set('use_admin_theme', TRUE)->save(TRUE);

This line is what tells Drupal to use the admin theme for node editing. Including the translations/revisions overviews.

To make those use the admin theme, \Drupal\node\EventSubscriber\NodeAdminRouteSubscriber::alterRoutes() sets the _admin_route=TRUE flag.

As a precaution for not-quite-as-vetted code that often makes it into admin backends (since you need elevated privileges to access the admin backend), it was decided that DPC could go into Drupal core if it didn’t attempt to cache responses for admin routes.

But here’s the thing: use_admin_theme in node.settings is OFF BY DEFAULT, but TURNED ON BY STANDARD AND THEREFORE ON BY DEFAULT IN MOST CASES. So the tests were running without that route being marked an admin one, and hence DPC was running, and hence it was being cached.
I’ve long cursed that stupid flag and it’s weird dissonance between default and “real default”

But of course it should also work when use_admin_theme is not enabled. Which is what this test is testing. And sadly, that is a bug in core.

So there are two possible solutions:

  1. Fix the symptom:
        $this->config('node.settings')->set('use_admin_theme', '1')->save();
        $this->container->get('router.builder')->rebuild();
    

    (f.e. \Drupal\Tests\shortcut\Functional\ShortcutLinksTest::testShortcutQuickLink) does this too.

  2. Fix the root cause:
    Cache::invalidateTags($revision->getCacheTagsToInvalidate());
    

    when deleting a revision

Both versions attached.

plach’s picture

@matsbla:

Okay I get it now, I was looking at the edit form, there is still a delete button there. Earlier when I click on it I delete the published version of the translation, but in last patch I come to an "Access denied" page.

Yep, I forgot about that button, it will need to be hidden as well.

I'm a little bit confused about the solution for this issue. The issue is named "Draft revisions of translations can not be deleted", but after I apply this patch I'm not able to delete/discard a draft. With this approach to me it seems like I need to first publish a draft and first then I'm able to delete the translation. But I can't delete a draft and still keep current published version.

So the goal here is to implement something we can ship in 8.5. I agree the solution is suboptimal, but it always lets you make a translation unavailable to end users, which is the main goal after all. You can delete the draft by going to the revision list page, once you do that, you can delete the translation in a default revision, if you wish to do so.

plach’s picture

Priority: Normal » Major
Issue summary: View changes
plach’s picture

The last submitted patch, 20: ct-revision_translation_deletion-2940890-20.test.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

plach’s picture

wim leers’s picture

Status: Needs review » Needs work

I don't know enough about the Entity/Field API and Content Translation implications and architecture to RTBC this patch, but I can review it. Especially for the many cacheability-related things.

  1. +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,103 @@
    + * @internal This additional access checker only aims to prevent deletions in
    ...
    + * @todo Remove this once https://www.drupal.org/node/2945956 is fixed.
    

    👍 This is a temporary work-around, and hence there's both a @todo and an @internal, great.

  2. +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,103 @@
    +    list($entity_type_id, ) = explode('.', $requirement);
    

    Nit: list($foo, ) looks weird.

  3. +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,103 @@
    +    if ($entity->isDefaultTranslation()) {
    

    The values in $entity are affecting the entity result. So we need this line before the quoted line:

    $result->addCacheableDependency($entity);
    
  4. +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,103 @@
    +    if (!$content_translation_manager->isEnabled($entity_type_id, $entity->bundle())) {
    

    In theory, isEnabled() should return a cacheable boolean (one with cacheability metadata). But that's out of scope here.

    The access result here depends on a LanguageContentSettings config entity. We can construct its cache tag manually for now: $result->addCacheTags(['language_content_settings:' . $entity_type_id . '.' . $entity->bundle()]);

  5. +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,103 @@
    +    $result = AccessResult::forbidden();
    

    The result still depends on $entity. But because this is overwriting the access result, that cacheability is lost.

    Fixable by doing

    $result = $result->andIf(AccessResult::forbidden());
    
  6. +++ b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php
    @@ -109,6 +109,13 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
    +          /** @var \Drupal\Core\Access\AccessResultInterface $delete_access */
    +          $delete_access = \Drupal::service('content_translation.delete_access')->checkAccess($entity);
    

    But the service this is calling is going to be removed, so does this need a @todo too?

  7. +++ b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php
    @@ -109,6 +109,13 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
    +          // no break
    

    Woah! This is tricky.

  8. +++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
    @@ -227,24 +236,31 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
    +            $cacheability = $cacheability->merge(CacheableMetadata::createFromObject($delete_route_access));
    

    This can be much simpler:

    $cacheability =
     $cacheability->addCacheableDependency($delete_route_access);
    
  9. +++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
    @@ -227,24 +236,31 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
    +              $cacheability = $cacheability
    +                ->merge(CacheableMetadata::createFromObject($delete_access))
    +                ->merge(CacheableMetadata::createFromObject($translation_access));
    

    Same here:

    $cacheability = $cacheability
      ->addCacheableDependency($delete_access)
      ->addCacheableDependency($translation_access);
  10. +++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
    @@ -227,24 +236,31 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
    +              if ($entity->access('delete') && $entity_type->hasLinkTemplate('delete-form')) {
    

    This re-runs the same access check. Can be $delete_access->isAllowed().

  11. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationPendingRevisionsTest.php
    @@ -0,0 +1,366 @@
    +   * Modules to enable.
    +   *
    +   * @var array
    

    Nit: inheritdoc

  12. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationPendingRevisionsTest.php
    @@ -0,0 +1,366 @@
    +   * @var array
    

    Nit: string[]

  13. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationPendingRevisionsTest.php
    @@ -0,0 +1,366 @@
    +    if ($entity->access('delete', $this->loggedInUser)) {
    +      $url = $entity->toUrl('delete-form');
    +    }
    +    else {
    +      $url = $entity->toUrl('drupal:content-translation-delete');
    +      $url->setRouteParameter('language', $entity->language()->getId());
    

    Why not always the latter?

plach’s picture

Status: Needs work » Needs review
StatusFileSize
new9.53 KB
new35.27 KB

@Wim Leers:

Great review, thanks!

This should address #23.

13: Because depending on the permissions, CT exposes different alternative routes in the UI: if a user has no edit permissions, a stripped down edit form or a translation deletion form is presented.

plach’s picture

Another minor clean-up.

timmillwood’s picture

Generally looks fine, only a couple of minor nit picks.

I also wonder, would uploading a test-only patch help explain / prove the issue?

  1. +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,116 @@
    +   *   The parametrized route.
    

    Should this be parameterized?

  2. +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,116 @@
    +  public function checkAccess(ContentEntityInterface $entity) {
    

    This method seems a bit longer than it needs to be, but makes it nice and easy to read.

  3. +++ b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php
    @@ -109,12 +107,15 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
    +          // @todo Remove this logic once https://www.drupal.org/node/2945956 is
    +          //   fixed.
    

    Maybe open an issue postponed on #2945956: Allow removing translations in pending revisions to remove this logic, then replace the comment with a single line @todo Remove in https://www.drupal.org/node/#######.

plach’s picture

1: Not sure what you are asking :)
3: I think #2945956: Allow removing translations in pending revisions is the place to remove those lines, reworded comments to clarify that.

plach’s picture

Addressed #26.1: I didn't realize there was a typo.

timmillwood’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @plach, looks good to me!

effulgentsia’s picture

I'm afraid this patch makes more changes than I'm personally comfortable committing during the 8.5 RC phase. I'm leaving this as RTBC in case another committer is more comfortable with it.

From what I can tell, it would be ok to commit this or something like it into a patch release. It's not ideal for 8.5.0 to ship with Delete links that appear to work (all the way up to the final status message of "[French] translation of the Article [blablabla] has been deleted."), but don't actually end in a state where it's been deleted. However, as far as I can tell, there's no data loss or corruption or integrity violation: just a status message that wasn't accurate, so I think it's not that bad to fix this in 8.5.1 instead.

For me, it would be easier to commit this (between 8.5.0 and 8.5.1) if the following were split into separate issues:

  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
    @@ -281,6 +282,13 @@ public function createRevision(RevisionableInterface $entity, $default = TRUE, $
    +      // Make sure we do not inadvertently recreate removed translations.
    +      foreach (array_diff_key($new_revision->getTranslationLanguages(), $translation_languages) as $langcode => $language) {
    +        if ($langcode !== $active_langcode) {
    +          $new_revision->removeTranslation($langcode);
    +        }
    +      }
    

    We need to be really careful with changes to ContentEntityStorageBase in patch releases (or during RC), since it's very prone to unintended consequences. So, would it be possible to split this into its own issue for more eyes on it?

  2. +++ b/core/modules/content_translation/src/ContentTranslationHandler.php
    @@ -576,7 +584,7 @@ public function entityFormSharedElements($element, FormStateInterface $form_stat
    -    if ($display_warning && !$form_state->isSubmitted() && !$form_state->isRebuilding()) {
    +    if ($display_warning && $this->isMessageDisplayable($form_state)) {
    ...
    +  protected function isMessageDisplayable(FormStateInterface $form_state) {
    +    return !$form_state->getUserInput();
    +  }
    

    I don't understand yet why it's desirable to change this from checking submitted/rebuilding to checking user input. Could this be split into its own issue for clarity?

If someone else wants to commit this without needing the above split out, that's fine too.

plach’s picture

Title: Follow-up for #2860097: Draft revisions of translations can not be deleted » [PP-1] Follow-up for #2860097: Draft revisions of translations can not be deleted
Status: Reviewed & tested by the community » Postponed
StatusFileSize
new705 bytes
new35.75 KB
new32.04 KB

Split the CESB changes out to #2949619: Removed revision translations may reappear when creating a new revision and made it critical, since I realized that that bug introduces data integrity issues. This is now postponed on that one.

I also reverted the unrelated changes to the message display, that were meaning to prevent the message from appearing in some edge cases (e.g. when uploading a file via AJAX or coming back from a node preview). I will create a separate issue for that.

I think it would be way better to commit this before 8.5.0 because, even if technically this bug does not introduce data integrity issues, allowing to delete translations in pending revisions will bring the DB in a state from which the CT UI can no longer work properly (and a future update adding a flag for deleted translations cannot deal with). For sure the fix included in this patch would not restore the UI functionality in those cases.

plach’s picture

Title: [PP-1] Follow-up for #2860097: Draft revisions of translations can not be deleted » Follow-up for #2860097: Draft revisions of translations can not be deleted
Status: Postponed » Reviewed & tested by the community
StatusFileSize
new0 bytes
new32.04 KB

The blocker was committed, here's a reroll. Back to RTBC.

plach’s picture

plach’s picture

@effulgentsia suggested a fix in Slack

effulgentsia’s picture

effulgentsia’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new32.49 KB
new1.78 KB
+++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
@@ -0,0 +1,116 @@
+    if (!ContentTranslationManager::isPendingRevisionSupportEnabled($entity->getEntityTypeId(), $entity->bundle())) {

With #2949710: Pending revisions may become unavailable when untranslatable fields affect all translations committed, this now needs cache dependencies added to the access result. Here's a patch that does it one way. Not sure if there's a better way.

gábor hojtsy’s picture

I think showing the warning on the edit form is strange. I went to edit, its not a warning about editing but about a tab the system is not showing. Why would I care, I am not here to delete? If we put up warnings about things you did not want to do, how do we expect people to read them (when they are actually useful)? :)

I think showing on the translation page may be more accurate, although you may not be going there to delete either, if you don't find the delete button in the tabs, I think you'll go there next.

effulgentsia’s picture

Priority: Major » Critical

I think it would be way better to commit this before 8.5.0 because, even if technically this bug does not introduce data integrity issues, allowing to delete translations in pending revisions will bring the DB in a state from which the CT UI can no longer work properly

Is this true with the current UI in HEAD? If so, what are the steps that surface the db being in a non-functional state?

(and a future update adding a flag for deleted translations cannot deal with). For sure the fix included in this patch would not restore the UI functionality in those cases.

I'm tentatively raising this issue to Critical for this. Even if HEAD's current UI is masking a fundamental db problem that occurs when you delete a translation with a pending revision, if it's true that the db still gets into a state that will create known future data loss problems for us, then we probably do need to get this into 8.5.0 or figure out what to do if we don't.

effulgentsia’s picture

Status: Needs review » Needs work

NW for #37. Unless a consensus emerges to commit #36 anyway, in which case set this back to NR or RTBC as appropriate.

effulgentsia’s picture

FWIW, I reviewed #36 (and earlier iterations), and would feel comfortable committing it (to both 8.6.x and 8.5.x) once #36's interdiff is reviewed by someone and #37 is addressed. I'm heading to bed now though, and won't be up when the 8.5.0 commit freeze starts in a couple hours, so if another committer in a more convenient timezone feels comfortable committing it once those things are addressed, please do! Otherwise, I'll check in with the release managers tomorrow to see if it's eligible for commit during the freeze.

plach’s picture

This addresses #37 and reworks the test to leverage the new ContentTranslationPendingRevisionTestBase class the was recently committed.

The interdiff form #36 looks correct to me.

gábor hojtsy’s picture

UI looks good to me now. As good as it can get in this issue. :)

wim leers’s picture

I specifically reviewed the #36 interdiff because it touches on cacheability.

+++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
@@ -80,16 +81,23 @@
+    // Add the cache dependencies used by
+    // ContentTranslationManager::isPendingRevisionSupportEnabled().
+    if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
+      foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
+        $result->addCacheableDependency($workflow);
+      }
+    }

This is just adding every Workflow config entity that uses the content_moderation WorkflowType plugin as a dependency.

That's not what I see happen in ::isPendingRevisionSupportEnabled(): that is iterating over all of them, yes, but only to find the Workflow entities that also happen to affect the given entity type + bundle. IOW: isPendingRevisionSupportEnabled() is trying to find which Workflow entity is the one that enables support for pending revisions. So in principle, we should only be adding that one Workflow config entity as a dependency here, not all of them.

But it looks like the idea here is that Workflow A could stop handling this entity type+bundle, and Workflow B could start doing so. Or maybe multiple could do so at the same time? Can multiple Workflows apply to the same entity type+bundle?
If that is the case, then it makes some sense to depend on all Workflow config entities.

Although I think that the cacheability here could be much tighter/more optimized than it is right now, this would definitely work correctly. I think it may be adding dependencies on too many things, but that will just result in unnecessary invalidations, not in incorrect behavior.
Optimizing this can be deferred to a follow-up and is likely to be rather pointless since all of this logic is supposed to be superseded in #2940575: Document the scope and purpose of pending revisions anyway.

(The coupling to the content_moderation module here is questionable, but I'm sure that has been raised before. It's also not this issue that introduces it, so out of scope here. And fortunately, \Drupal\content_translation\ContentTranslationManager::isPendingRevisionSupportEnabled() is marked @internal 👍.)

wim leers’s picture

Status: Needs review » Reviewed & tested by the community
  1. +++ b/core/modules/content_translation/content_translation.services.yml
    @@ -9,6 +9,12 @@ services:
    +  content_translation.delete_access:
    
    +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,124 @@
    + * @internal This additional access checker only aims to prevent deletions in
    + *   pending revisions until we are able to flag revision translations as
    + *   deleted.
    + *
    + * @todo Remove this in https://www.drupal.org/node/2945956.
    + */
    +class ContentTranslationDeleteAccess implements AccessInterface {
    

    The class is @internal, can we could also have marked the service private. But that's sadly something we don't really do at all in Drupal core yet, so there's no point holding a critical issue up for that.

  2. +++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
    @@ -0,0 +1,124 @@
    +    // Add the cache dependencies used by
    +    // ContentTranslationManager::isPendingRevisionSupportEnabled().
    +    if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
    +      foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
    +        $result->addCacheableDependency($workflow);
    +      }
    +    }
    

    I'd say that we should add documentation indicating this can and should be improved. But as said in the previous comment: isPendingRevisionSupportEnabled() is guaranteed to change anyway (and is marked internal). Plus this entire class is also marked as internal. So I feel fine with this.

  3. Reviewed the test refactoring in #41 on top of the very recently added \Drupal\Tests\content_translation\Functional\ContentTranslationPendingRevisionTestBase — looks excellent 👍 Far less repetition, nice.
  4. Reviewed the "cannot delete" warning message code (Gábor already reviewed the UI). Looks good.

I've reviewed everything since the previous RTBC in #29, after reading all bits of discussion. This is ready
again. The two primary concerns for un-RTBC'ing were @effulgentsia's in #38: the ContentEntityStorageBase changes should be handled in another issue (they were: #2949619: Removed revision translations may reappear when creating a new revision) and the message/message checking logic (message improved, shown in a more relevant place, and with none of the previously questionable logic). Those have definitely been addressed. 👏

Back to RTBC!

gábor hojtsy’s picture

Title: Follow-up for #2860097: Draft revisions of translations can not be deleted » Don’t allow deleting revision translations in pending revisions

  • Gábor Hojtsy committed 2a1c593 on 8.6.x
    Issue #2940890 by plach, Wim Leers, effulgentsia, matsbla, timmillwood:...

  • Gábor Hojtsy committed 8e302d1 on 8.5.x
    Issue #2940890 by plach, Wim Leers, effulgentsia, matsbla, timmillwood:...
gábor hojtsy’s picture

Version: 8.6.x-dev » 8.5.x-dev
Status: Reviewed & tested by the community » Fixed

Thanks everyone. This was really close to not making it to 8.5.0.

plach’s picture

Yay, thanks to all the people that spent (lunch ;) time on this!

wim leers’s picture

Status: Fixed » Closed (fixed)

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

yonailo’s picture

This patch does not seem to be applied to the 9.x branch, why ?