Problem/Motivation
Drupal versions (8.6.4) prior to changes in issue #2946402: Content moderation incorrectly always assumes a language is being added when the default language of an entity is being changed being committed at this commit, content moderation created duplicate content_moderation_state_field_revision table records when changing a content item's language. These duplicate table records then cause an error after updating to 8.6.4 or later that included the above changes.
Steps To Reproduce
Steps taken from comment #19, see it for more details.
- Start with Drupal 8.6.2
- Create content and save as published with French language
- Edit and change language to English, save and publish
- Update to 8.6.7 or later and process updates
- Edit same node and attempt to save
- Observe the reported error, "A translation already exists for the specified language (en)."
Proposed resolution
As recommended in comment #22 and #29, use a hook_post_update_NAME() to delete dud translation data.
Remaining tasks
- Write hook_post_update_NAME() to delete dud translation
- Tests, fixtures?
User interface changes
None
API changes
None
Data model changes
None
Release notes snippet
TBD
Originally Reported
Attempting to save a page in English (default language).
When I click to save it I get the following error:
The website encountered an unexpected error. Please try again later.Drupal\Core\Entity\EntityStorageException: A translation already exists for the specified language (en). in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 783 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
Drupal\Core\Entity\Plugin\DataType\EntityAdapter->onChange('langcode') (Line: 77)
Drupal\Core\TypedData\Plugin\DataType\ItemList->setValue(Array, 1) (Line: 107)
Drupal\Core\Field\FieldItemList->setValue(Array) (Line: 1089)
Drupal\Core\Entity\ContentEntityBase->__set('langcode', 'en') (Line: 188)
Drupal\content_moderation\EntityOperations->updateOrCreateFromEntity(Object) (Line: 149)
Drupal\content_moderation\EntityOperations->entityUpdate(Object) (Line: 100)
content_moderation_entity_update(Object)
call_user_func_array('content_moderation_entity_update', Array) (Line: 403)
Drupal\Core\Extension\ModuleHandler->invokeAll('entity_update', Array) (Line: 206)
Drupal\Core\Entity\EntityStorageBase->invokeHook('update', Object) (Line: 756)
Drupal\Core\Entity\ContentEntityStorageBase->invokeHook('update', Object) (Line: 507)
Drupal\Core\Entity\EntityStorageBase->doPostSave(Object, 1) (Line: 641)
Drupal\Core\Entity\ContentEntityStorageBase->doPostSave(Object, 1) (Line: 432)
Drupal\Core\Entity\EntityStorageBase->save(Object) (Line: 774)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save(Object) (Line: 390)
Drupal\Core\Entity\Entity->save() (Line: 281)
Drupal\node\NodeForm->save(Array, Object)
call_user_func_array(Array, Array) (Line: 111)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 51)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 589)
Drupal\Core\Form\FormBuilder->processForm('node_landing_page_edit_form', Array, Object) (Line: 318)
Drupal\Core\Form\FormBuilder->buildForm('node_landing_page_edit_form', Object) (Line: 93)
Drupal\Core\Controller\FormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 582)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 151)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 99)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 78)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 38)
Drupal\webprofiler\StackMiddleware\WebprofilerMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 669)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
I expect it to save the node.
Comments
Comment #2
sam152 commentedThis sounds a lot like #2946402: Content moderation incorrectly always assumes a language is being added when the default language of an entity is being changed. Can you please re-test this against 8.6.x or 8.7.x HEAD? If you can reproduce it, can you please provide detailed instructions on how to reproduce it from scratch, including modules installed, translation settings configured, content created etc.
Comment #3
AMDandy commentedThis occurred after updating from 8.6.3 to 8.6.4 with some existing nodes (not all). It occurs even when I open the node and attempt to immediately save it and it also happens when I try to revert to a previous revision. We haven't seen it on new nodes but we also aren't trying to change the default language.
I'm attaching composer.lock so you can see the versions of modules installed. I saw an open issue with Acquia Content Hub that reported this same symptom but it occurs even after uninstalling ACH. I'll try against 8.7.x HEAD and I'll see if I can get it to trigger on new content but it hasn't happened yet.
Comment #4
sam152 commentedOkay, then it's possible the fix for the other issue broke this for you, but without actually being able to put a breakpoint in your project, I need to be able to reproduce it on my machine. It's possible the steps to reproduce include a combination of "content is created", "xyz moderation/translation is enabled", "content is edited".
Comment #5
AMDandy commentedI know this isn't actionable but I wanted to conform that this was caused by the above issue. I reversed the commit (a0c92ff) and the node saved. I reapplied the commit and the node that had just saved failed with the same error.
Comment #6
jrockowitz commentedI am also seeing the issue and can confirm that it does seem to be a regression caused by #2946402: Content moderation incorrectly always assumes a language is being added when the default language of an entity is being changed.
My client has already upgraded to 8.6.4 and needs a temporary solution. The attached patch reverts only the code changes.
I will try my best to figure out how to reproduce this issue and document it here.
Comment #7
sam152 commentedThanks for taking the time to write up some instructions to reproduce this, I still haven't been able to.
Comment #8
justclint commentedWe are also experiencing this after the 8.6.4 update. Just updated to 8.6.5 but issue remains. Applied patch in #6 and it seems to be working for us. Thanks @jrockowitz for that.
Comment #9
berdirI did run into this as well. Created a duplicate before I found this: #3024816: EntityOperations::updateOrCreateFromEntity(): A translation already exists for the specified language.
From the code, it would happen if the moderated entity default translation changes to one that already exists, possibly because it was deleted and re-created.
Comment #10
ejanus commentedHi, I'm on Drupal 8.6.5. I had the same issue and the patch fixed the save error above.
However, now, when editing a specific translation, the field content shows up with the default (english) language and global fields that are not translatable show up. Essentially, it appears the whole step of loading translation info doesn't happen.
Comment #11
sam152 commented@Berdir Hm, I can test that scenario, but we do clean up the composite entities as translations are deleted.
Comment #12
ejanus commentedDisregard my previous comment about the issue persisting for me. It had to do with my environment and appears to be unrelated. Specifically, how I was handling language negotiation settings.
Comment #13
AMDandy commented@ejanus could you give me a little more info so I can see if it's our environment as well?
Comment #14
idflood commentedI also did hit this issue on a client website. I'm still not sure why this happened, but the only page where this occured is one of the earliest created. All recent pages does not have this issue (at least for now).
The "patch" in #6 also made the issue disappear.
Note that the only real line to change is to comment the `$content_moderation_state->langcode = $entity_langcode;`. Once this is commented the issue disappear.
As @Berdir commented, if I inspect the `$content_moderation_state` variable before the error happen the `langcode` is "de" even if I'm editing the "fr" version. I don't know what the editor made with this page but I guess it was first created in german and when the language was changed to french the $content_moderation_state langcode was not properly updated.
Comment #15
sam152 commentedIs it possible this is being caused by dud translation data that was created before folks updated to include #2946402: Content moderation incorrectly always assumes a language is being added when the default language of an entity is being changed. If you created an 'fr' page, then changed the language of that page to 'en' and then attempted to add a 'fr' translation, that may be causing this?
If that's the case it would be good to prove it by examining the entity tables for both node and content_moderat_state. The following two queries would probably provide enough information:
Where
99999is the ID of the node that is broken.If this is NOT based on historical data from before that fix, I still can't reproduce it in HEAD, so if anyone can do that, working towards some clear instructions would be awesome.
Comment #16
dimr commentedWith my system totally updated, I have tried that Querys and this was the result, in a test machine I have tried to remove those result and after doing it the error dissapear in that id.
But It doesn't disappear all the errors related. I still getting this error
The entity cannot be translated since it is language neutral (und).That appears in a content that was created as a Not Specified language, but later it was changed to a German when we try to edit the German version or to add translation I get this error.
The patch #6 didn't fix my error.
Comment #17
AMDandy commentedThis is the result of a node with the error. Not sure how to decode it.
Comment #18
kamkejj commentedWe ran into this error on an existing site that's been updated over the last two years. This is what I'm seeing with a specific node. Looking at the database table content_moderation_state_field_revision using the query in a previous comment we can see that the values from default_langcode are not correct.
We have English (en) and French (fr) languages and for some reason en is not set as the default with a value of 1 even though the site configuration is set to English as the default. The fr language is set as the default, but shouldn't be.
One solution seems to be to change the default_langcode to 1 for en and 0 for fr in the table content_moderation_state_field_revision. Then when trying to save the en content it now works where it errored before.
Don't know how these got messed up, but maybe during an upgrade or something.
Comment #19
jasonawantHi,
I can reproduce this following the steps outlined below. The project was using Drupal 8.6.2 with English as default language with French as second language. The content type was using a content moderation workflow.
I'm surprised that when changing languages in step 3 below that the node table then has two records as seen in Step 3.4.Never mind, this is standard node revisioning.select * from content_moderation_state_field_revision where content_entity_id = '99999' order by revision_id desc;32978 39215 fr 1 editorial published node 99999 52440 1 1select nid, vid, langcode from node_field_revision where nid = '99999';99999 52436 frselect * from content_moderation_state_field_revision where content_entity_id = '99999' order by revision_id desc;select nid, vid, langcode from node_field_revision where nid = '99999';Here's more info about the node table.
select * from node_field_revision where nid = '99999'\G"Comment #20
jasonawantWhen I repeat the above steps with 8.6.7 and new a node, step 3 produces different records in the content_moderation_state_field_revision table. And, the resulting error does not repeat in steps 5 and 6 above.
select * from content_moderation_state_field_revision where content_entity_id = '99999' order by revision_id desc;Comment #21
jasonawantI was able to reproduce the issue following the above steps using standard install profile with content moderation, workflow and content translation modules enabled along with configuring the basic page content type to be moderated and translated after adding French language. B/c #2946402: Content moderation incorrectly always assumes a language is being added when the default language of an entity is being changed was added into 8.6.4, I dropped back to 8.6.3, created content following the above steps and then updated to 8.6.7.
When changing the language, it appears that an additional content_moderation_state_field_revision record is created for the content's original language.
By removing the, what I think is erroneous, fr content_moderation_state_field_revision record that has revision_translation_affected NULL and update the latest en content_moderation_state_field_revision record's default_langcode value to 1 or TRUE resolves the issue as @kamkejj described in #18. I suspect removing the fr content_moderation_state_field_revision record that has revision_translation_affected NULL may not be necessary.
I think this is at least a major bug, so I'm escalating per priority page definition.
I haven't looked into the code changes that is surfacing this issue. Hopefully, this can be resolved via a change to logic instead of cleaning up data.
Comment #22
sam152 commentedThanks @jasonawant. I think that more than confirms my suspicions in #15. I think ultimately it would be good to clean up the dud data instead of adjusting the logic, then at least all of the installations of content moderation would be in a known predictable state and we don't have to account for or test features against having dud languages floating around.
Great steps to reproduce in #19, next step should be creating a database fixture with the invalid data created before 8.6.2.
Also agree with the "Major" classification.
Comment #23
sam152 commentedComment #24
jasonawantI'm not quite sure the best path to take to move this forward. I followed the directions found on Writing Automated Update Tests for Drupal 8 page to generate a fixture from a 8.6.2 site with a single node with the dud moderation data. Let me know if this isn't correct. It looks like this file could be simpler and only contain specific content. Instead, it appears to contain the entire db.
What are the next steps, write an update hook that updates the content_moderation_state_field_revision records?
Comment #25
pavlosdanWe came across this today as well.
Not sure how the invalid translations were created.
If we need a quick fix would a couple of blanket db_queries to set the default default_langcode in the content_moderation_state_field_revision table to 0 where the langcode is a language that is not supposed to be default and one to the default_langcode to 1 where the langcode is the default language be enough to sort this out?We tested this on a single piece of content that we needed fixed urgently and it seemed to do the trick.Edit: A blanket query for all entries where the default langcode is not the one it's supposed to be is not a good solution. It causes some of the content to break with another error (empty state value).
Comment #26
johnchqueThis is my attempt, not sure if it is the right approach.
Well, updating the default translation is not supported by the API. How should we proceed?
Comment #27
tguilpain commentedUsing patch from #26, getting the following when running the update (en is default language, zh-hans is second language):
Failed: LogicException: The default translation flag cannot be changed (zh-hans). in Drupal\Core\Entity\ContentEntityBase->onChange() [error]
(line 831 of docroot/core/lib/Drupal/Core/Entity/ContentEntityBase.php).
As @yongt9412 says, changing this is not supported by the API. Seems like running DB queries is the only available solution at the moment.
Comment #28
r.e.younan commentedFollowing @Thibaud G comment in #27 to fix the database, these are the SQL needed to verify and update the
default_langcodein the tablecontent_moderation_state_field_revisionto match thedefault_langcodein the tablenode_field_data.Important: Always backup your database before running any SQL statements.
Following SQL query will list the nodes with mismatched
default_langcodeTo fix the database if the previous statements returned results (and after taking your verified backups):
Comment #29
sam152 commentedMaybe it would be simpler to batch over every ContentModerationState entity instead, and compare to being able to load an associated entity and language? Might make for a lot simpler batching logic.
Changing the default language can also be done by simply loading the default entity and updating the 'langcode' field.
This is something we should also make sure we have extensive testing for, since it's such a tricky data integrity problem.
Comment #30
AMDandy commented#28 allowed us to restore the commit that I had reverted.
Comment #31
faibyshevFor me, #28 doesn't help.
Because after this changes some nodes become unpublished. And it is not ok to have many unpublished nodes.
Comment #32
tguilpain commentedHere is what we ended using to fix this in our system in production. After extensive testing, we were not able to find any problem with the impacted nodes anymore. Based on #28.
to put in a post update hook:
Note that you ll need to adapt this to the languages installed on your website, as they are hardcoded in the code above. English is for us the default language, and Chinese the only other language we have, that is not the default.
Also note that the problem we had with #28 was that it was only updating the default revision, so any existing forward revision was still wrong and creating issues. Thus, our code above fixes the default language and all the existing revisions of the affected nodes.
Comment #33
dksdev01 commentedWhat happens if the content is not created in the default language... I guess SQL suggestion above will not be valid for those nodes. I guess we need a stable solution to this problem. I guess d.o core team should look into this on urgent basis. Thanks
Comment #34
idflood commentedI manually applied the solution in #28 on a production website and it fixed the issue on all the pages where this error occured. After checking all the pages in all translations I didn't found any unpublished node like in #31, maybe because all affected nodes I had where published.
Comment #35
johnchqueAbout #29 I tried by updating the code to load the default entity and update its langcode field, I get the same error as the reported here, that there is a translation already for that language. We need to do something for the revisions still I believe, maybe use queries already?
Comment #36
johnchqueTest leftover. :/
Comment #37
alonaoneill commentedComment #38
kostyashupenko#36 patch looks like doesn't need to be rerolled against 8.6.x
Comment #39
mbovan commentedI was trying to work on top of #35 in order to find a way to remove dud data and update the
default_langcodeproperty in affected content moderation state entities. So far, I haven't found a way to avoid API restrictions and errors that are the reason for this issue as described in #27 and #26.I would like to know if #32 is an acceptable solution and good starting point in order to commit this issue?
Comment #40
vacho commentedNo needs reroll
Comment #42
michael_l commentedI updated Drupal from 8.5.15 to 8.7.1 and now I'm facing the same problem.
The site is multilingual (/en, /de).
used PHP-Version: 7.1.25
I tried to implement the patch from #36, but when I run the 'database update script' it fails and reports another error:
Error: Call to undefined method stdClass::setNewRevision() in content_moderation_post_update_update_default_langcode() (line 252 of /home/user/www.domain.com/core/modules/content_moderation/content_moderation.post_update.php) ...Does anyone has the same problem, or what is the best solution to fix this issue yet?
Comment #43
yogeshmpawarSetting back to Needs Review & triggering bots.
Comment #45
mbovan commentedThis patch aims to prevent the exception error from the issue description by matching the content moderation state entity language from the current language of the moderated entity (
ContentModerationStateEntity::loadFromModeratedEntity).Let's see the tests...
No interdiff as it's not related to the idea from #26.
Comment #46
jasonawantI've updated the issue description; hope this helps clarifies and makes it easier for others to jump in.
The last patch goes in a different direction that previously described in comment #22. The issue description outlines this path forward instead of changing logic.
The patch in #36 throws an error.
Throws "Error: Call to undefined method stdClass::setNewRevision()"; Likely caused by a missing $default_entity that has sense been deleted.
Comment #47
jasonawantFound some time to look more closely at this. This parts do not make sense to me from the patch in #36 comment.
This variable name is confusing. I think $content_moderation_state_storage->loadMultipleRevisions(array_keys($result)); returns \Drupal\content_moderation\Entity\ContentModerationState entities.
If I understand this correctly, for $entity_type_id of node, we have \Drupal\node\NodeStorage as $storage. Then, this is trying to use the id of the previously loaded \Drupal\content_moderation\Entity\ContentModerationState entity as if it's the Node's ID to load the node. Is that right? Seems that if we are trying to load the Node or other entity, we should use the content_entity_id value of the moderation state entity.
Comment #48
acontia commentedThe queries on #28 solved the issue for me.
I have ran it in multiple sites, all of them with "en" as source language of the corrupted nodes, and different translations on each site.
Comment #49
maximpodorov commentedThe following code works for me (if content moderation is used for nodes only):
Comment #51
sam152 commentedTentatively closing this as outdated. I know there was no committed fix for this, however a really large update process might be riskier for the majority of sites which likely weren't affected by this bug.
No we're a couple of releases down the line, hopefully most affected installations found solutions in this thread for dealing with the problem.
If anyone feels strongly this should remain open, please feel free to do so.