Problem/Motivation

In a project we discovered a potential bug regarding file translations:

* 3 languages: german, french and italian
* base language german ( same error with english base language )

Steps to reproduce :
* enable all language modules
* make project 3 lingual
* create a content type with a file field ( not image )
* make this content type translatable ( including the file )
* create a german base one and translate it into french ( also replace the file )
* translate it also into italian ( also replacing the file )
* edit the german node again and you see that the german file is gone

Discoveries :
* While the initial translation into french works, the file will be removed from the file_usage table. The status of the first file in file_managed will be set to 0
* The french translation file will be created using the german langcode "de" in the file_managed table ( perhaps thats correct but its strange )
* The translation into italian then even deletes the german file in the file_managed table
* If you add the file again to the german node after it got deleted it works like expected.

* Additional problem: after adding one translation and then deleting that again, the file you uploaded from the translation is not removed from the file_managed and file_usage table, but it is from the file system ..

There is data loss in this issue.

Proposed resolution

This makes two changes to fix the mentioned bugs:

* The FileFieldItemList class should never look at anything but the current translation. There are only old values if the translation already existed.
* We have to call delete() on field items that are part of deleted translations, so that they work as expected. We've already seen this problem in other issues, e.g. #2539634: PathItem::delete() never runs because the path field type is a computed field in disguise

Remaining tasks

User interface changes

None.

API changes

None.

Data model changes

None.

CommentFileSizeAuthor
#18 2639352-18.patch8.18 KBswentel
#18 interdiff.txt836 bytesswentel
#13 2639352-13.patch8.15 KBswentel
#13 interdiff-2639352.txt800 bytesswentel
#5 2639352-5.patch5.65 KBswentel
#11 interdiff.txt2.53 KBswentel
#11 2639352-11.patch8.18 KBswentel
#16 interdiff.txt3.17 KBswentel
#16 2639352-16.patch8.14 KBswentel
#23 2639352-23.patch6.72 KBBerdir
#23 2639352-23-interdiff.txt1.03 KBBerdir
#25 2639352-25-interdiff.txt2.46 KBtduong
#25 2639352-25.patch8.22 KBtduong
#26 2639352-23-26-interdiff.txt5.54 KBtduong
#26 2639352-26.patch10.26 KBtduong
#27 2639352-26-test_only.patch7.53 KBtduong
#27 2639352-26.patch10.26 KBtduong
#32 2639352-26-32-interdiff.txt1.33 KBtduong
#32 2639352-32.patch10.75 KBtduong
#37 2639352-32-37-interdiff.txt4.5 KBtduong
#37 2639352-37.patch15.58 KBtduong
#39 2639352-37-39-interdiff.txt3.32 KBtduong
#39 2639352-39.patch15.46 KBtduong
#41 2639352-39-41-interdiff.txt1.61 KBtduong
#41 2639352-41.patch15.47 KBtduong
#43 2639352-41-43-interdiff.txt1.36 KBtduong
#43 2639352-43.patch15.63 KBtduong
#46 2639352-43-46-interdiff.txt1.02 KBtduong
#46 2639352-46.patch15.64 KBtduong
#47 2639352-46-47-interdiff.txt825 bytestduong
#47 2639352-47.patch15.63 KBtduong
#49 2639352-47-49-interdiff.txt587 bytestduong
#49 2639352-49.patch15.64 KBtduong
#54 interdiff.txt1.82 KBswentel
#54 2639352-54.patch15.53 KBswentel
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

yobottehg created an issue. See original summary.

cilefen’s picture

Priority: Major » Critical

This is a thoughtful report and it is Critical unless someone can show it is not.

swentel’s picture

Issue tags: +D8MI

Could reproduce the data loss, but with an additional detail for point 1: the file isn't deleted from file_managed when creating the first translation, the entry is still there, but the status is set to 0 (however, at some point, cron will clean this up of course). When creating the second translation, it's gone from the managed_file table, as well as from the file system. What's even more annoying is that there's still a reference in the dedicated reference table of the field to a non existing record.

So you definitely need to test this with 3 translations, subtle things start kicking in at the first translation, the second translation is even worse.

Trying to write a test to expose the failure.

(edit, changed the first sentence regarding the detail, should recheck the usage table at some point)

swentel’s picture

Issue summary: View changes
swentel’s picture

Status: Active » Needs review
FileSize
5.65 KB

Test that exposes the data loss.

swentel’s picture

Pasting the asserts that will fail in the code for reference.

  1. +++ b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
    @@ -0,0 +1,155 @@
    +    // Ensure the file status of the first file permanent.
    +    $file = File::load($first_fid);
    +    $this->assertTrue($file->isPermanent());
    

    This fails after the first translation

  2. +++ b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
    @@ -0,0 +1,155 @@
    +    // Ensure the first file still exists. Calling isPermanent() would crash
    +    // the test suite.
    +    $file = File::load($first_fid);
    +    $this->assertTrue(!empty($file), 'First file still exists.');
    +    // This inspects the HTML after the post of the translation, the file
    +    // should be displayed on the original node.
    +    $this->assertRaw('file--mime-text-plain');
    +
    

    This fails even more after the second translation

swentel’s picture

Some basic debugging: open FileUsageBase and comment out lines 36 & 37 and all will be fine (naturally). It's called from DatabaseFileUsageBackend(). We need to prevent decrementing it on the first translation here.

Status: Needs review » Needs work

The last submitted patch, 5: 2639352-5.patch, failed testing.

swentel’s picture

swentel’s picture

Issue summary: View changes

Additional problem: after adding one translation and then deleting it again, the file you uploaded from the translation is not removed from the file_managed and file_usage table, but it is from the file system ..

swentel’s picture

Status: Needs work » Needs review
FileSize
8.18 KB
2.53 KB

This makes the test pass, however, I don't think the fix is right enough as I think it won't remove the file from the system in case you would edit the original source node, maybe other tests catch this one already, or I'll try to add additional tests.

The last submitted patch, 11: 2639352-11.patch, failed testing.

swentel’s picture

The last submitted patch, 13: 2639352-13.patch, failed testing.

Gábor Hojtsy’s picture

Title: File Translation issue » File records, files themselves lost in translation
Issue summary: View changes
Issue tags: +language-content, +sprint, +Needs issue summary update

Wow. Nice find. Adding tags. Adding issue summary template. Please fill in proposed resolution as well.

swentel’s picture

This should fix the fails in UserPictureTest - updated the proposed solution in the IS.
Added some references in the patch to a good movie :)

swentel’s picture

Note, as I've said already, I think the current patch now prevents from deleting the file from the system when you're deleting it from the source node (and probably from the translated node too), so we'll need an additional test and fix too.

swentel’s picture

Better assert

Status: Needs review » Needs work

The last submitted patch, 16: 2639352-16.patch, failed testing.

The last submitted patch, 18: 2639352-18.patch, failed testing.

swentel’s picture

Ok, getting highly confused now, $translation_sync seems to be empty - back to drawing board it seems.

Gábor Hojtsy’s picture

Berdir’s picture

Status: Needs work » Needs review
FileSize
6.72 KB
1.03 KB

I might be missing something here, but this makes the test pass. Lets see what else happens.

Basically, since we're called for every translation, we shouldn't fall back to the original translation of the entity. If the translation didn't exist, then there are no original ID's.

Wondering if there are reverse problems, with not removing usages when translations are removed or if this really works. Go testbot.

swentel’s picture

Issue summary: View changes

Amazing, good to have a fresh mind look at this one, thanks :)

Wondering if there are reverse problems, with not removing usages when translations are removed

I've been wondering this myself already too, I think it might be good to write additional tests for that, just to be sure.

tduong’s picture

Just added a fourth language test.

tduong’s picture

FileSize
5.54 KB
10.26 KB

Sorry, mismatched the instruction for the new test to be add. Restarted:

  • make sure to call the delete method for field items of removed translations in ContentEntityStorageBase::invokeFieldMethod()

Test:

  • edit second translation, replacing file
  • check 1./3. files untouched, new 2. file as permanent, old 2. file as temporary
  • delete third translation
  • check 1./new 2. files untouched, 3. file as temporary
tduong’s picture

Added test only patch.

The last submitted patch, 27: 2639352-26-test_only.patch, failed testing.

Berdir’s picture

swentel’s picture

Looks great. Since we're calling a lot of things on original here, I guess I just have one more question: I'm not sure exactly what happens in case we delete the 'original/source' node, does another one become the source ? Haven't looked exactly how/if content_translation handles that, so maybe just one additional test which deletes the original one then at the end - or maybe I'm even asking a stupid question :)

Berdir’s picture

Status: Needs review » Needs work

AFAIK, it's not possible to delete the source translation, that will delete the whole entity. Haven't tried, but I've seen issues about allowing that :)

That said, deleting the source translation and making sure that all files are then temporary makes sense.

I've also asked @plach to review this.

tduong’s picture

Status: Needs work » Needs review
FileSize
1.33 KB
10.75 KB

Added test deleting all translations.

dawehner’s picture

IMHO we should also add a dedicated test outside of the file module to ensure that those methods are executed.

+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -453,6 +454,20 @@ protected function invokeFieldMethod($method, ContentEntityInterface $entity) {
+    // We need to call the delete method for field items of removed
+    // translations.
+    if ($method == 'postSave' && !empty($entity->original)) {
+      $original_langcodes = array_keys($entity->original->getTranslationLanguages(FALSE));
+      foreach (array_diff($original_langcodes, $langcodes) as $removed_langcode) {
+        $translation = $entity->original->getTranslation($removed_langcode);

It is not entirely obvious that removing an entire entity will let $entity->getTranslationLanguages() return an empty list of translations, so really a dedicated test for that itself would be nice.

Berdir’s picture

Status: Needs review » Needs work

Yeah, a dedicated test makes sense, will see where we could do that. Maybe EntityTranslationTest.

It is not entirely obvious that removing an entire entity will let $entity->getTranslationLanguages() return an empty list of translations, so really a dedicated test for that itself would be nice.

It's not entirely obvious because there is no such thing. You can't save an entity without any translations, there's always the source translation that can't be removed. The only way to do that is to delete the whole entity and then the delete() methods are called directly.

Gábor Hojtsy’s picture

@swentel: indeed, its currently not possible to delete the original language variant without deleting the whole entity with all the translations. There is an issue to allow to reassign the default translation to some other translation within the entity. See #2485499: Allow source translations to be removed and #2443991: Allow default_langcode field value to be changed.

Berdir’s picture

I'd expect that won't allow you to actually delete a source translation either, you could just switch to another to make that the source and then delete the old source language?

So I think nothing changes for our case here and if it really does, then we now have enough test coverage here so it would fail and would have to be updated.

tduong’s picture

Status: Needs work » Needs review
FileSize
4.5 KB
15.58 KB

Uploaded patch:

  • added delete() in Drupal\entity_test\Plugin\Field\FieldType\FieldTestItem
  • dedicated test in Drupal\system\Tests\Entity\EntityTranslationTest
Berdir’s picture

Status: Needs review » Needs work

Feedback on the comments.

  1. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -810,4 +812,90 @@ public function testFieldEntityReference() {
    +    // Create entities for both translatable and untranslatable test fields.
    +    $values = array(
    +      'name' => $this->randomString(),
    

    We create a single entity, with both fields, not for.

  2. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -810,4 +812,90 @@ public function testFieldEntityReference() {
    +    // Remove the second and third langcodes form the translatable test field.
    +    $entity->removeTranslation('l1');
    

    We remove it from the entity, not the field.

  3. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -810,4 +812,90 @@ public function testFieldEntityReference() {
    +    // Ensure that for the translatable test field the second and third langcodes
    +    // are in the deleted languages list, except for the first one.
    +    // The untranslatable test field should be untouched.
    

    the "except ..." part doesn't really make sense to me, just leave that out.

    I'd also move the comment about untranslatable to the line where you assert that.

  4. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -810,4 +812,90 @@ public function testFieldEntityReference() {
    +    // Remove all translations for both test fields.
    +    $entity->delete();
    

    I'd write this instead:

    // Delete the entity, which removes all remaining translations.

  5. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -810,4 +812,90 @@ public function testFieldEntityReference() {
    +    // Check if the default and the first langcode for the translatable test
    +    // field is deleted.
    +    $actual = \Drupal::state()->get('entity_test.delete.translatable_test_field');
    

    // All languages have been deleted now.

  6. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -810,4 +812,90 @@ public function testFieldEntityReference() {
    +    // Check if the default langcode for the untranslatable test field is deleted.
    +    $actual = \Drupal::state()->get('entity_test.delete.untranslatable_test_field');
    +    $expected_untranslatable = ['en'];
    

    // The untranslatable field is shared and only deleted once, for the default lancode.

  7. +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/FieldTestItem.php
    @@ -115,4 +115,15 @@ protected function mustResave() {
    +    $language_to_delete = $this->getLangcode();
    +    $deleted_languages = \Drupal::state()->get('entity_test.delete.' . $this->getFieldDefinition()->getName()) ?: [];
    +    $deleted_languages[] = $language_to_delete;
    +    \Drupal::state()->set('entity_test.delete.' . $this->getFieldDefinition()->getName(), $deleted_languages);
    

    I think to_delete is a bit confusing. It's already being deleted. I'd just leave that variable out and put \Drupal::state() directly on the line where you add it to the array.

tduong’s picture

Status: Needs work » Needs review
FileSize
3.32 KB
15.46 KB

Done.

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -869,30 +869,29 @@
     
    -    // Remove the second and third langcodes form the translatable test field.
    +    // Remove the second and third langcodes form the entity.
    

    nitpick: form => from. missed that before.

  2. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -869,30 +869,29 @@
     
         // Ensure that for the translatable test field the second and third langcodes
    -    // are in the deleted languages list, except for the first one.
    -    // The untranslatable test field should be untouched.
    +    // are in the deleted languages list.
         $actual = \Drupal::state()->get('entity_test.delete.translatable_test_field');
    

    first line unfortunately still > 80 characters.

  3. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -869,30 +869,29 @@
     
    -    // Check if the default langcode for the untranslatable test field is deleted.
    +    // The untranslatable field is shared and only deleted once, for the default lancode.
    

    same here, > 80 characters.

tduong’s picture

Status: Needs work » Needs review
FileSize
1.61 KB
15.47 KB

Refactored.

Status: Needs review » Needs work

The last submitted patch, 41: 2639352-41.patch, failed testing.

tduong’s picture

Status: Needs work » Needs review
FileSize
1.36 KB
15.63 KB

Sorted $actual and $expected arrays for test comparison.

plach’s picture

Overall this makes a lot of sense to me, thanks!

I have only one serious remark. It may be valid or not, but would be good to check.

  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
    @@ -453,6 +454,20 @@ protected function invokeFieldMethod($method, ContentEntityInterface $entity) {
    +      $original_langcodes = array_keys($entity->original->getTranslationLanguages(FALSE));
    

    I'm wondering whether the FALSE argument here may be problematic when the default language is changed as part of the save operation. Also because $langcodes includes the default language.

  2. +++ b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
    @@ -0,0 +1,209 @@
    + * Uploads files to translated nodes
    

    Missing trailing dot.

  3. +++ b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
    @@ -0,0 +1,209 @@
    +    // Add a second and third language.
    +    $edit = array();
    +    $edit['predefined_langcode'] = 'fr';
    +    $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
    +
    +    $edit = array();
    +    $edit['predefined_langcode'] = 'nl';
    +    $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
    +
    +    // Enable translation for "Basic page" nodes.
    +    $edit = array(
    +      'entity_types[node]' => 1,
    +      'settings[node][page][translatable]' => 1,
    +      "settings[node][page][fields][$this->fieldName]" => 1,
    +    );
    +    $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
    +    \Drupal::entityManager()->clearCachedDefinitions();
    

    We've written this code so many times, it should really become a trait sooner or later :)

  4. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
    @@ -810,4 +812,96 @@ public function testFieldEntityReference() {
    +    // default lancode.
    

    typo

Berdir’s picture

1. My understanding now is that that's currently not possible. That said, there's no reason to pass FALSE there, I think we just copied that from somewhere. So it should work even if you did that.

tduong’s picture

FileSize
1.02 KB
15.64 KB

Fixed #44.2 and #44.4. May the trait task be a new issue ?

tduong’s picture

FileSize
825 bytes
15.63 KB

#44.1 done.

swentel’s picture

I think the trait is fine for another issue imo.

Only one small nitpick before RTBC.

+++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
@@ -810,4 +812,96 @@ public function testFieldEntityReference() {
+  /**
+   * Tests if entity translation statuses are correct after removing two translation.
+   */

Shouldn't go over 80 chars

tduong’s picture

FileSize
587 bytes
15.64 KB

Rerolled and done.

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

I would personally have shortened the sentence to "Tests entity translation statuses after removing two translation." to fit on one line, but that sounds like a nitpick with a critical issue :) Fix looks good and the additional test coverage is great.

swentel’s picture

Or maybe "Tests entity translation statuses after removing two translations" (plural) :)

plach’s picture

1. My understanding now is that that's currently not possible.

You can't change the default_langcode flag, but you can change the value associated to langcode for the default language, i.e. the default entity language can be modified.

I think the trait is fine for another issue imo.

Yup, it was only a consideration :)

catch’s picture

    Sorry to hold this up on comments, but this one confused me. Patch looks great otherwise.

  1. +++ b/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
    @@ -0,0 +1,209 @@
    +    // Ensure the first file still exists. Calling isPermanent() would crash
    +    // the test suite.
    +    $file = File::load($first_fid);
    +    $this->assertTrue(!empty($file) && $file->isPermanent(), 'First file still exists and is permanent.');
    

    This says isPermanent would crash the test suite, but then it calls it. Does it mean 'if the file did not exist'? Also isn't $file && $file->isPermanent() enough of a check?

swentel’s picture

The reason I added that was because I wanted to have more asserts after this one in the initial test-only patch to prove that more things started to go wrong. Oherwise, the test suite crashed on the fact that $file didn't exist at all and calling that method would crash it.

Of course, now with the fixes in place, we can actually just call $file->isPermanent() directly and even remove that $file check.
New patch, cleaning up those asserts a bit, still RTBC I think.

Berdir’s picture

Yes, that makes sense.

Gábor Hojtsy’s picture

Looks good, yay!

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.1.x and cherry-picked to 8.0.x. Thanks!

  • catch committed a5db924 on 8.1.x
    Issue #2639352 by tduong, swentel, Berdir: File records, files...

Status: Fixed » Closed (fixed)

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

Gábor Hojtsy’s picture

Issue tags: -sprint

Yay, woot!

azinck’s picture

lawxen’s picture

I'm working on a project that someone else did,An error was encountered on commerce checkout completion, what I did is just add an file field(multi value) to order type.

Error mesaage:

The website encountered an unexpected error. Please try again later.
Error: Call to a member function hasTranslation() on null in Drupal\file\Plugin\Field\FieldType\FileFieldItemList->postSave() (line 54 of core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php).
Drupal\file\Plugin\Field\FieldType\FileFieldItemList->postSave(1)
call_user_func_array(Array, Array) (Line: 938)
Drupal\Core\Entity\ContentEntityStorageBase->invokeFieldMethod('postSave', Object, 1) (Line: 970)
Drupal\Core\Entity\ContentEntityStorageBase->invokeFieldPostSave(Object, 1) (Line: 896)
Drupal\Core\Entity\ContentEntityStorageBase->invokeHook('update', Object) (Line: 56)
Drupal\commerce\CommerceContentEntityStorage->invokeHook('update', Object) (Line: 64)
Drupal\commerce_order\OrderStorage->invokeHook('update', Object) (Line: 598)
Drupal\Core\Entity\EntityStorageBase->doPostSave(Object, 1) (Line: 781)
Drupal\Core\Entity\ContentEntityStorageBase->doPostSave(Object, 1) (Line: 523)
Drupal\Core\Entity\EntityStorageBase->save(Object) (Line: 804)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save(Object) (Line: 339)
Drupal\Core\Entity\EntityBase->save() (Line: 429)
Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowBase->submitForm(Array, Object) (Line: 614)
Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowWithPanesBase->submitForm(Array, Object)
call_user_func_array(Array, Array) (Line: 114)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 52)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 597)
Drupal\Core\Form\FormBuilder->processForm('commerce_checkout_flow_multistep_default', Array, Object) (Line: 325)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 224)
Drupal\Core\Form\FormBuilder->getForm(Object, 'review') (Line: 143)
Drupal\commerce_checkout\Controller\CheckoutController->formPage(Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 580)
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: 169)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 38)
Drupal\webprofiler\StackMiddleware\WebprofilerMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 718)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

I haven't got what cause this problem, but the code change of this issue's commit has some flaw here:
if ($original->hasTranslation($langcode))
It didn't check whether the $original is null.

lawxen’s picture