Problem/Motivation

ContentEntityBase::hasTranslationChanges is not skipping the changed fields (changed and content_translation_changed), which is leading a wrong outcome if comparing e.g. a form submitted entity without any real changes, because without changes if we submit then the revision translation affected flag will be set because of the changed field but when comparing the revision there will be no visual diff and therefore the revision should not be listed as changed at all.

Proposed resolution

Exclude changed fields from the comparison in hasTranslationChanges.

Remaining tasks

Review.

User interface changes

none

API changes

none

Data model changes

none

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

hchonov created an issue. See original summary.

hchonov’s picture

Providing a failing test proving the needed change.

hchonov’s picture

And now the test together with the fix from the issue summary.

The last submitted patch, 2: 2615016_failing_test.patch, failed testing.

hchonov’s picture

Title: ContentEntityBase::hasTranslationChanges is not always checking the right revision field » ContentEntityBase::hasTranslationChanges is not always skipping the revision field when checking for changes
Issue summary: View changes
mkalkbrenner’s picture

Status: Needs review » Reviewed & tested by the community

Good catch. The fix is correct and the patch adds test coverage.

The last submitted patch, 2: 2615016_failing_test.patch, failed testing.

hchonov’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
4.24 KB

Actually there is more than this in this issue... The revision metadata fields should be skipped from checking as well, otherwise a new revision with updated revision timestamp or diffrent revision_uid will lead to returning a false result...

hchonov’s picture

FileSize
3.36 KB
hchonov’s picture

Title: ContentEntityBase::hasTranslationChanges is not always skipping the revision field when checking for changes » ContentEntityBase::hasTranslationChanges doesn't skip the revision metadata fields leading to wrong result
Issue summary: View changes
plach’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
    @@ -1173,10 +1173,20 @@ public function hasTranslationChanges() {
    +    $fields_do_not_compare = [
    

    Can we name this $skip_compare

  2. +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
    @@ -1173,10 +1173,20 @@ public function hasTranslationChanges() {
    +      if (isset($fields_do_not_compare[$field_name])) {
    

    I think this logic should be applied only between different revisions, can we have changes in revision metadata when updating the current revision instead of creating a new one? If so we should add test coverage for this change.

  3. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,99 @@
    +   * Tests the correct functionality of the hasTranslationChanges function.
    +   *
    +   */
    

    Surplus blank line

  4. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,99 @@
    +    // revision metadata field revision_timestamp
    ...
    +    // revision metadata field revision_uid
    ...
    +    // revision metadata field revision_log
    

    The first letter should be capitalized and the comment should have a trailing dot.

hchonov’s picture

@plach:
you are right of course!

I've made the changes, added the test coverage and updated the issue summary as well.

plach’s picture

Looks good now, only a typo to fix!

+++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
@@ -0,0 +1,146 @@
+    // Check that the metadata fields are been skipped when comparing same

have been

hchonov’s picture

mkalkbrenner’s picture

I wonder if we should also skip changed and content_translation_changed, because these fields are usually set to a new value as a consequence of the return value of hasTranslationChanges(). In ChangedItem::presave() the call of hasTranslationChanges() is skipped if the value of this field (named changed or content_translation_changed) has been changed already.
But if you think of any re-usage of hasTranslationChanges() somewhere else as part of the API, it makes sense to skip this field like the revision specific fields.

mkalkbrenner’s picture

Just a little bit of history:
hasTranslationChanges() was a part of ChangedItem unless @catch proposed to make it available as part of the API to be re-usable. I feel like we missed to explicit skip the "changed" field within in the isolated hasTranslationChanges() at this point. But I'm still thinking about this ...

mkalkbrenner’s picture

The second usage of this method in core is in ContentEntityStorageBase::populateAffectedRevisionTranslations(). In this case it would be wrong to set the revision_translation_affected flag is set if only the value of the changed field itself changed. Otherwise for nodes we will list to revisions of a translation with no changes except their changed date. And this date is not revertable.
So I'm convinced that it's correct to skip "changed" and "content_translation_changed" as well.

hchonov’s picture

Status: Needs review » Needs work

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

hchonov’s picture

Status: Needs work » Needs review

The last submitted patch, 2: 2615016_failing_test.patch, failed testing.

xjm’s picture

Issue tags: -rc target triage

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

amateescu’s picture

+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -1173,10 +1173,32 @@ public function hasTranslationChanges() {
       // @todo Avoid special-casing the following fields. See
       //    https://www.drupal.org/node/2329253.
-      if ($field_name == 'revision_translation_affected' || $field_name == 'revision_id') {

Can we also remove the @todo now?

hchonov’s picture

Actually if we decide us for this approach then we'll have to put the three other field names in, which were introduced in #2666808: Add a revisionable entity base class and log interface. Probably we should wait for #2248983: Define the revision metadata base fields in the entity annotation in order for the storage to create them only in the revision table and then use the approach from there for determining the revision metadata fields and then we remove the @todo?

amateescu’s picture

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

plach’s picture

Status: Needs review » Needs work
Issue tags: +D8 major triage deferred, +Needs issue summary update

This was discussed with @alexpott, @amateescu, @berdir, @cilefen, @fago and @xjm. We decided to defer triage because we were not sure about the steps to reproduce a significant problem. What issues does this cause? Can they be reproduced via the UI or only via some particular programmatic circumstances? It would be great if the IS provided more info about this.

(Sorry, it's been a long time since the last time I had a look to this code :)

Quick code review:

+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -1173,10 +1173,32 @@ public function hasTranslationChanges() {
+        'content_translation_changed' => TRUE

The core Entity API should know nothing about the Content Translation module: we are introducing a circular dependency here. We probably need a way to hook into this process and provide a list of fields.

tstoeckler’s picture

Title: ContentEntityBase::hasTranslationChanges doesn't skip the revision metadata fields leading to wrong result » [PP-1] ContentEntityBase::hasTranslationChanges doesn't skip the revision metadata fields leading to wrong result

Marking this as postponed on #2248983: Define the revision metadata base fields in the entity annotation in order for the storage to create them only in the revision table in the title, I still think we should continue iterating here in the meantime, so not setting the status to Postponed.

There is actually a separate bug that I have yet to open an issue for, that currently masks any real-world consequences as far as I can tell. This is still major to me, because it breaks a fundamental expectation when working with translations and revisions, but more on that later...

tstoeckler’s picture

So as far as I can tell #2808335: Changed time not updated when only non-translatable fields are changed on a translated entity masks this bug because revision metadata fields are not marked translatable, so that they are actually ignored for changed time tracking currently because of this. @hchonov please correct me if I am incorrect on this (or anything else).

Assuming that that other bug were fixed, what this bug would lead to is that as soon as someone enters a revision log message, the changed timestamp will be updated even though nothing of the actual entity was changed. This is annoying for any code that actually relies on proper changed time tracking, especially across revisions.

Having written that, however, I agree that this is probably not major.

I would like @hchonov to validate my assessment, though, before downgrading it.

tstoeckler’s picture

Issue tags: +Dublin2016
hchonov’s picture

hchonov’s picture

tstoeckler, yes, as the revision metadata fields are not translatable the bug is currently being masked... But as other issues which are major are waiting on this issue I would not downgrade it.

hchonov’s picture

This issue has to be fixed in order for the autosave_form module to work properly and be able to check for changes between the intermediate and the initial entity.

Rajab Natshah’s picture

+1 Testing

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Berdir’s picture

Title: [PP-1] ContentEntityBase::hasTranslationChanges doesn't skip the revision metadata fields leading to wrong result » ContentEntityBase::hasTranslationChanges doesn't skip the revision metadata fields leading to wrong result

#2808335: Changed time not updated when only non-translatable fields are changed on a translated entity is in, but the comment above said this blocks that, I guess it was meant the other way round?

mkalkbrenner’s picture

Status: Needs work » Needs review
hchonov’s picture

Title: ContentEntityBase::hasTranslationChanges doesn't skip the revision metadata fields leading to wrong result » ContentEntityBase::hasTranslationChanges should exclude the "changed" fields from the comparison
Issue summary: View changes
Issue tags: -Needs issue summary update, -blocker
FileSize
5.37 KB

@berdir well both issues got somehow merged. So that the other one is now in and #2248983: Define the revision metadata base fields in the entity annotation in order for the storage to create them only in the revision table will make use of the new method for retrieving the revision metadata fields here we have only one more thing to do - exclude the changed fields from comparison in hasTranslationChanges.

Additionally we have here a test that is checking hasTranslationChanges skipping the revision metadata fields and the changed timestamps so we should get it in as well.

I've changed the patch a lot so an interdiff does not make sense anymore.

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
    @@ -1331,11 +1332,11 @@ public function hasTranslationChanges() {
           //    https://www.drupal.org/node/2329253.
    -      if (in_array($field_name, $skip_fields, TRUE)) {
    +      if (in_array($field_name, $skip_fields, TRUE) || (($field = $this->get($field_name)) && ($field instanceof ChangedFieldItemList))) {
             continue;
           }
           if (!$definition->isComputed()) {
    -        $items = $this->get($field_name)->filterEmptyItems();
    +        $items = $field->filterEmptyItems();
    

    I find the way $field is used here a bit hard to understand. We conditionally assign it in the first if but there's nothing conditional about it, and get() would actually throw an exception in that case.

    So, can you move the $field = $this->get($field_name) either outside the loop or not use a temporary variable for it? $this->get($field_name) instanceof .. would IMHO be perfectly fine here as well.

  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,116 @@
    +/**
    + * @file
    + * Contains \Drupal\KernelTests\Core\Entity\ContentEntityHasChangesTest.
    + */
    

    remove this.

hchonov’s picture

Status: Needs work » Needs review
FileSize
5.04 KB
1.69 KB

I hope it looks better now :).

tstoeckler’s picture

Status: Needs review » Needs work

I think the patch itself looks fine, but I think we should add a code-comment to the added condition. Something like this, maybe?:

When saving entities in the user interface, the changed timestamp is automatically incremented by ContentEntityForm::submitForm() even if nothing was actually changed. Thus, the changed time needs to be ignored when determining whether there are any actual changes in the entity.

I have some minor notes as well, but marking needs work mostly for the adding of a comment. Doesn't have to be my comment, but something that explains why we are doing this would be rather important to add, IMO.

  1. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    + * Tests the hasTransationChanges function of content entities.
    

    Missing "l" in hasTranslationChanges. Also maybe "Tests ContentEntityBase::hasTranslationChanges()." would be clearer?

  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +    // Create a new node-type.
    +    NodeType::create([
    +      'type' => $this->bundle,
    +      'name' => $this->randomString(),
    +    ])->save();
    

    Why are you using "node" here and not some test entity type?

  3. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +  public function testhasTranslationChanges() {
    

    h -> H (for the first h ;-))

jofitz’s picture

Status: Needs work » Needs review
FileSize
5.36 KB
1.76 KB

Documentation changes from #42.

jofitz’s picture

Status: Needs review » Needs work

Returning to "Needs Work" for someone to address:

  1. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +    // Create a new node-type.
    +    NodeType::create([
    +      'type' => $this->bundle,
    +      'name' => $this->randomString(),
    +    ])->save();

    Why are you using "node" here and not some test entity type?

I tried using EntityTest::create() in place of NodeType::create(), but couldn't get it to work.

tstoeckler’s picture

Right, sorry that was a bit brief. Here is a more in detail description of what is needed.

  • +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +  public static $modules = ['system', 'user', 'node'];
    

    Replace node with entity_test here.

  • +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +    $this->installEntitySchema('node');
    ...
    +      ->getStorage('node');
    

    Replace node with entity_test_mulrev_changed here.

  • +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +    // Create a new node-type.
    +    NodeType::create([
    +      'type' => $this->bundle,
    +      'name' => $this->randomString(),
    +    ])->save();
    

    Remove this entirely.

  • +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +    /** @var \Drupal\node\NodeInterface $node */
    +    $node = $storage->create([
    

    Rename this variable and change the typehint to \Drupal\entity_test\Entity\EntityTestMulRevChanged

  • +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +      'type' => $this->bundle,
    

    Remove this.

  • +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
    @@ -0,0 +1,110 @@
    +      'title' => $this->randomString(),
    

    Change title to name here.

  • Pavan B S’s picture

    Status: Needs work » Needs review
    FileSize
    5.25 KB
    1.5 KB

    Made changes as suggested in comment #45. Applying the patch, please review.

    Status: Needs review » Needs work

    The last submitted patch, 46: 2615016-46.patch, failed testing.

    jofitz’s picture

    Status: Needs work » Needs review
    FileSize
    4.88 KB
    1.9 KB

    @tstoeckler I now see why we are using node rather than a test entity type: The tests rely on 3 functions that only exist on nodes - setRevisionAuthorId(), setRevisionCreationTime() and setRevisionLogMessage(). Theoretically we could remove the calls to these functions, but I think this would weaken the test (I have uploaded this patch as a proof of concept). I recommend returning to my patch in #43 as the solution.

    hchonov’s picture

    Status: Needs review » Needs work

    You should not remove the calls, but instead use a test entity implementing the RevisionLogInterface e.g. entity_test_revlog, the methods will be provided then but some have different names.

    tstoeckler’s picture

    We already have EntityTestWithRevisionLog however that is not translatable and does not have a changed field, so I guess we need to create a EntityTestMulRevChangedWithRevisionLog or something?

    hchonov’s picture

    Either we create a new entity type or we simply use the node entity type :).
    A new entity type will make the patch unnecessary bigger?

    jofitz’s picture

    Status: Needs work » Needs review
    FileSize
    6.91 KB
    6.04 KB
    5.81 KB

    IMO the new entity type is quite straightforward.

    tstoeckler’s picture

    Status: Needs review » Reviewed & tested by the community

    Wow, that looks pretty sweet @Jo Fitzgerald!!

    Thanks also for the two interdiffs, that was very helpful!

    +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChangedWithRevisionLog.php
    @@ -0,0 +1,46 @@
    + *   id = "entity_test_mulrev_chnged_revlog",
    

    Missing an "a" in "chnged". I wanted to mark this "needs work" for that, but I checked and I guess you intentionally left that out, because otherwise the ID would be longer than EntityTypeInterface::ID_MAX_LENGTH. That's pretty funny, to be honest. But I think you chose the best resolution for this, as the ID is still pretty readable and still fits with out existing naming pattern.

    ....so, RTBC! Thanks again!

    jofitz’s picture

    Aww, shucks, that comment has made my day. Thanks @tsoeckler!

    With hindsight I should've mentioned the missing 'a' in my comment, but you read my mind anyway!

    tstoeckler’s picture

    Hehe, well you gave yourself away a bit, because it was a bit too suspicious that you made that same mistake multiple times in different places ;-)

    hchonov’s picture

    Well you could remove the underscore between entity and test -> entitytest_mulrev_changed_revlog or simply entity_test_mul_changed_revlog.

    alexpott’s picture

    Status: Reviewed & tested by the community » Fixed

    Committed and pushed 4dde9c8 to 8.4.x and eb5cb0f to 8.3.x. Thanks!

    entity_test_mulrev_chnged_revlog is a bit weird but shouldn't hold up this patch.

    • alexpott committed 4dde9c8 on 8.4.x
      Issue #2615016 by hchonov, Jo Fitzgerald, Pavan B S, tstoeckler,...

    • alexpott committed eb5cb0f on 8.3.x
      Issue #2615016 by hchonov, Jo Fitzgerald, Pavan B S, tstoeckler,...

    Status: Fixed » Closed (fixed)

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