Problem/Motivation

Steps to reproduce:
1. Enable content moderation module and enable default workflow for article content type (at admin/config/workflow/workflows/manage/editorial)
1. Create article with alias /hello
2. Create a new draft of that article with filling in /hello2 as the url alias
3. The article is immediately accessible at /hello2 and not at /hello

Proposed resolution

Probably #2336597: Convert path aliases to full featured entities.

Alternatively add validation to stop people changing that value unless it's the default revision as a stopgap?

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#91 interdiff.txt2.62 KBamateescu
#91 2856363-91.patch7.4 KBamateescu
#83 2856363-83.test.patch3.97 KBplach
#79 2856363-78.test.patch0 bytesplach
#77 Screenshot_20170712_193523.png48.28 KBamateescu
#77 interdiff.txt6.8 KBamateescu
#77 2856363-77.patch7.53 KBamateescu
#66 Screenshot from 2017-06-27 08-36-26.png92.66 KBtimmillwood
#62 2856363-62.patch5.08 KBtimmillwood
#47 2856363-47.patch13.82 KBtimmillwood
#47 interdiff-2856363-47.txt1.4 KBtimmillwood
#45 interdiff-2856363.txt785 bytestimmillwood
#44 2856363-45.patch13.83 KBpk188
#42 2856363-42.patch13.82 KBtimmillwood
#42 interdiff-2856363-42.txt1.22 KBtimmillwood
#38 2856363-38.patch13.51 KBtimmillwood
#38 interdiff-2856363-38.txt1.87 KBtimmillwood
#37 2856363-37.patch13.37 KBtimmillwood
#37 interdiff-2856363-37.txt1.44 KBtimmillwood
#35 2856363-35.patch13.12 KBtimmillwood
#35 interdiff-2856363-35.txt3.32 KBtimmillwood
#32 2856363-32.patch12.69 KBtimmillwood
#32 interdiff-2856363-32.txt1.08 KBtimmillwood
#30 2856363-30.patch11.61 KBtimmillwood
#30 interdiff-2856363-30.txt10.64 KBtimmillwood
#27 2856363-27.patch7.67 KBtimmillwood
#27 interdiff-2856363-27.txt799 bytestimmillwood
#25 2856363-25.patch7.67 KBtimmillwood
#25 interdiff-2856363-25.txt7.76 KBtimmillwood
#23 2856363-23.patch7.31 KBtimmillwood
#23 interdiff-2856363-23.patch1.79 KBtimmillwood
#20 2856363-20.patch7.37 KBtimmillwood
#20 interdiff-2856363-20.txt6.59 KBtimmillwood
#14 2856363-prototype.patch1.74 KBMile23
#7 2856363-prototype.patch1.74 KBamateescu
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

catch created an issue. See original summary.

catch’s picture

Priority: Major » Critical

Bumping to critical for visibility since this is how we've been dealing with data integrity issues with experimental modules in general.

larowlan’s picture

Pretty sure menu link will be same problem

catch’s picture

cilefen’s picture

Issue tags: +Triaged D8 critical

I discussed this issue with @xjm, @cottser, @catch, @alexpott and @lauriii. We agreed this should be critical for the reasons @catch gave in #2.

Sam152’s picture

I think it would be great if #2336597: Convert path aliases to full featured entities solved this so you could create multiple revisions of a path alias. Realistically though, that seems like it's going in the direction of a whole other experimental module, which will need to be written and will have its own timeline to get to stable status. Given this is a critical data integrity issue, it risks the stable status of content_moderation.

Would everyone be comfortable with adding the validation to prevent alias changes on forward revisions as a means to downgrade this to a Major perhaps, with the view to fix it once the linked issue is resolved?

Another off the cuff suggestion would be to create aliases for the latest-version route for all forward revisions where paths change, eg "/node/1/latest" => "/new-path" then switch them over once the latest revision is published, but I think implementing and maintaining something like that would look a lot worse than some simple validation, especially since it's a stop-gap measure that we want to solve properly.

amateescu’s picture

Status: Active » Needs review
FileSize
1.74 KB

I was thinking about this problem yesterday and actually started to write a path_alias module for #2336597: Convert path aliases to full featured entities, but then I realized that node/1/revision/2/view is a perfectly valid system path, so we could solve this with much less effort than rewriting the path alias system by using the 'revision' entity url for draft revisions.

It would look something like the patch attached. The final patch is going to take a bit more effort to complete so I'd like to get some opinions first if this is an approach worth pursuing :)

Sam152’s picture

Sounds like a good option. One issue might be that the revision path doesn't exist for all entity types, hence the suggestion of using the "latest-version" route, which content_moderation adds.

Other than that, if this is easy to do, all we'd need to include in addition to roughly the approach in #7 is switch the revision alias over to the canonical one once that revision is published.

Sounds doable. +1 from me.

timmillwood’s picture

This obviously depends on the entity having a "revision" URL. In core that's only Node (because only Node and BlockContent are revisionable, and BlockContent isn't rendered as a full page anyway), but in contrib or custom? So I guess the final patch will need a check that the URL exists, but what do we do if it doesn't?

Other than that, it sounds like a good plan.

alexpott’s picture

This only works if the path alias is changing when creating the future draft revision - otherwise the old alias is updated to point at a revision that's not the default revision.

amateescu’s picture

@alexpott, yes, I said that the patch is incomplete, it was just a PoC to show everyone what the idea is :)

amateescu’s picture

@Sam152:

One issue might be that the revision path doesn't exist for all entity types, hence the suggestion of using the "latest-version" route, which content_moderation adds.

The bug that needs to be fixed here can be reproduced without Content Moderation, it's a general problem with core's forward revision support so we can not rely on a route added by CM.

@timmillwood:

So I guess the final patch will need a check that the URL exists, but what do we do if it doesn't?

We most likely need to throw a "incomplete entity definition" exception if the entity type is revisionable, has a canonical "view" route but lacks a "view revision" one.

jhodgdon’s picture

Another point: This will only work also if changing which revision is the current/published revision triggers the revision and its fields to be saved (and hence the postSave() method to be called) on both the revision that used to be current and the one that is now becoming current. Does that happen?

Mile23’s picture

Patch in #7 still applies. Re-uploading it here so CI can happen.

Needs a test, since there are instructions for reproducing the error in the IS.

amateescu’s picture

@Mile23, I specifically used the 'do no test' option for the patch in #7 so we don't waste CI resources on a patch that was simply meant as a PoC and a discussion starting point.

Sam152’s picture

I like the plan thus far. My feeling is that it's worth pursuing a more fleshed out implementation + tests.

We most likely need to throw a "incomplete entity definition" exception if the entity type is revisionable, has a canonical "view" route but lacks a "view revision" one.

I think we'd need to silently fall-back to the leaky behaviour for BC reasons, but the rest of the plan seems like the best that has been suggested yet.

timmillwood’s picture

Status: Needs review » Needs work

I've been testing the patch from #7 this morning and there are two big issues.

  1. $entity->isDefaultRevision() returns TRUE when it should be FALSE.
  2. $entity->toUrl('revision')->getInternalPath() is returning the canonical path not the revision path.

I think this is due to \Drupal\path\Plugin\Field\FieldType\PathItem::postSave being called before the entity has been saved.

Maybe this code needs to move to hook_entity_create and hook_entity_update?

timmillwood’s picture

it seems like hook_entity_create and hook_entity_update are too early too, and hook_entity_insert is too late because by then $entity->path->alias is empty.

amateescu’s picture

hook_entity_insert is too late because by then $entity->path->alias is empty.

We have to work with the assumption that #2846554: Make the PathItem field type actually computed and auto-load stored aliases will be committed sometime in the near future, at which point $entity->path->alias will have a value.

I think this is due to \Drupal\path\Plugin\Field\FieldType\PathItem::postSave being called before the entity has been saved.

I'm pretty sure that can not happen :)

timmillwood’s picture

Version: 8.3.x-dev » 8.4.x-dev
Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
6.59 KB
7.37 KB

Looks like a contrib module I had installed was causing the issues I mentioned in #17 and #18.

So here's an updated patch with a test.

Not sure I'm 100% happy with the logic, and think this is the best solution when no alias is added, but will think on it.

Status: Needs review » Needs work

The last submitted patch, 20: 2856363-20.patch, failed testing.

amateescu’s picture

I was talking to @timmillwood about this issue and a flaw in my proposal came up: there is nothing that could link multiple sources from the {url_alias} table to a single entity.

One possible solution for that would be to add another column to the table, entity_id and maybe even revision_id. How do others feel about that?

timmillwood’s picture

Status: Needs work » Needs review
FileSize
1.79 KB
7.31 KB

This new patch fixes the failing tests in #20.

I also think this addresses the critical issue, but it doesn't fix the general issue with URL aliases.

Currently the path field is only added by the Path module to Node and Taxonomy term entity types, as this issue only relates to forward revisions and Taxonomy terms are not revisionable, this issue only affects nodes.

If someone creates a custom entity which manually implements the path field then they would benefit from this patch (as long as they have a revision path). However if they create url aliases without using the path module then they'll still get this issue.

amateescu’s picture

@timmillwood, are you sure the changes to PathItem::postSave() from #20 and #23 are actually needed? Maybe the new test coverage exposed some problems with the code from #7?

  1. +++ b/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php
    @@ -28,7 +28,12 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
    -      $conditions = ['source' => '/' . $entity->urlInfo()->getInternalPath()];
    +      if ($entity->getEntityType()->isRevisionable() && !$entity->isDefaultRevision()) {
    +        $conditions = ['source' => '/' . $entity->toUrl('revision')->getInternalPath()];
    +      }
    +      else {
    +        $conditions = ['source' => '/' . $entity->toUrl()->getInternalPath()];
    +      }
    

    This implies that *every* non-default revision should have an alias, but that's not the case.

    We need an OR condition which falls back to the $entity->toUrl()->getInternalPath() source here :)

  2. +++ b/core/modules/path/tests/src/Functional/PathContentModerationTest.php
    @@ -0,0 +1,105 @@
    +class PathContentModerationTest extends BrowserTestBase {
    

    The test should have code comments to explain exactly what it's testing.

    And we should also test deleting revisions and whole entities as well.

timmillwood’s picture

@amateescu - As we discussed the changed between #7 and #23 were needed to the forward alias doesn't overwrite the default alias.

#24.1 - I have tried to add a workaround for this, but not too happy with it.

#24.2 - added comments.

Also updated the test / patch to fix a new issue I thought about. What if you create a default revision, then don't change the alias in the forward revision, you'll get two aliases the same, but with different sources. I am now loading the alias for the canonical path, and not creating a new one if one already exists.

When discussing this with @amateescu he pointed me to #2846554: Make the PathItem field type actually computed and auto-load stored aliases, so I wonder if it's worth postponing on that?

Status: Needs review » Needs work

The last submitted patch, 25: 2856363-25.patch, failed testing.

timmillwood’s picture

Status: Needs work » Needs review
FileSize
799 bytes
7.67 KB

Test didn't pass because of the widget.

dixon_’s picture

This issue was discussed during the Hard Problems meeting at DrupalCon in Baltimore. See notes and decisions here: https://docs.google.com/document/d/1-5B-Gndhklns20NZpGQul10525rFWuYB2NbT...

timmillwood’s picture

From the Workflow Initiative meeting last week (see #28) the action items included "Quick solution should move forward", which is this issue.

timmillwood’s picture

FileSize
10.64 KB
11.61 KB

This patch fixes #24.1 properly.
It also tests the widget returns the correct default value each time we load the edit form.
Then extends the test to test deleting an whole entity as well as deleting a single revision.

Deleting a whole entity resulted in the following change

-        $query->condition($field, $this->connection->escapeLike($value), 'LIKE');
+        $query->condition($field, $value, 'LIKE');

so we could delete all revision aliases with a source such as '/node/1/revisions/%'. I'm not really happy with this change.

Also deleting a single revision isn't working because \Drupal\path\Plugin\Field\FieldType\PathFieldItemList::delete is only called when deleting a whole entity. I'm wondering if this could be considered a core bug.

Status: Needs review » Needs work

The last submitted patch, 30: 2856363-30.patch, failed testing.

timmillwood’s picture

Status: Needs work » Needs review
FileSize
1.08 KB
12.69 KB

This duplicates some code, but fixes the issue of aliases still existing after deleting a revision.

timmillwood’s picture

Assigned: Unassigned » timmillwood

Looking at how #2846554: Make the PathItem field type actually computed and auto-load stored aliases might've changed things, and re-running tests for that reason too.

Status: Needs review » Needs work

The last submitted patch, 32: 2856363-32.patch, failed testing. View results

timmillwood’s picture

Assigned: timmillwood » Unassigned
Status: Needs work » Needs review
FileSize
3.32 KB
13.12 KB
vijaycs85’s picture

Issue summary: View changes

I did a manual testing and working as expected. We have revision with it's own path. Not sure if this is exactly same as proposed solution in issue summary. Here is some code review comments(mostly optional improvements).

  1. +++ b/core/lib/Drupal/Core/Path/AliasStorage.php
    @@ -160,7 +165,7 @@ public function delete($conditions) {
    -        $query->condition($field, $this->connection->escapeLike($value), 'LIKE');
    +        $query->condition($field, $value, 'LIKE');
    

    using "LIKE" without excapeLike?

  2. +++ b/core/modules/path/path.module
    @@ -86,8 +87,23 @@ function path_entity_translation_create(ContentEntityInterface $translation) {
    +function path_entity_revision_delete(EntityInterface $entity) {
    
    +++ b/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php
    @@ -32,6 +32,15 @@ public function delete() {
    +    $entity_type_id = $entity->getEntityTypeId();
    +    $source = str_replace('{' . $entity_type_id . '}', $entity->id(), $entity->getEntityType()->getLinkTemplate('revision'));
    +    $source = str_replace('{' . $entity_type_id . '_revision}', '%', $source);
    +    $revisionable_conditions = [
    +      'source' => $source,
    +      'langcode' => $entity->language()->getId(),
    ...
    +    \Drupal::service('path.alias_storage')->delete($revisionable_conditions);
    

    Looks like same code repeated. Is it worth having a helper?

  3. +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
    @@ -114,23 +114,33 @@ public function set($property_name, $value, $notify = TRUE) {
    +    if ($this->alias && (!$update || $source != $this->source)) {
    

    Can we have a comment here on what are we checking?

  4. +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
    @@ -164,12 +174,19 @@ protected function ensureLoaded() {
    +        if ($entity->getEntityType()->isRevisionable() && !$entity->isDefaultRevision()) {
    

    This is the typical condition to check if we are in non-default revision on a revisionable entity. Worth having it as a API in entity level(i.e. allows to check any additional checks in future in one location)?

  5. +++ b/core/modules/path/tests/src/Functional/PathContentModerationTest.php
    @@ -0,0 +1,152 @@
    +  public function testNodePathAlias() {
    

    Missing docblock.

timmillwood’s picture

FileSize
1.44 KB
13.37 KB

#36.1 - yes, LIKE without escapseLike() is correct. escapleLike removes all '%' and '_' characters, and we need them now. See \Drupal\Core\Database\Connection::escapeLike

#36.2 - I had considered this and wasn't sure a service for this would add much, and a helper function is a definite no. I'll give it more thought.

#36.3 - Added

#36.4 - Maybe we can do that in a followup? But not really convinced of the gain.

#36.5 - Added

timmillwood’s picture

FileSize
1.87 KB
13.51 KB

When thinking about #36.4 I decided that we need to depend on an entity type having a revision link template, not just being revisionable.

For #36.2 I wondered if we could extend AliasStorage, to create something like EntityAliasStorage, however I don't think this is feasible. We could add a another service, but I'm not sure it's needed at this stage.

timmillwood’s picture

Path module only works with Node, Taxonomy Term, and Media. Taxonomy Term is not revisionable yet, but Media is and doesn't have a revision link template. So will regress back to the functionality before this patch until it gets one. #2885486: Media entity is revisionable but doesn't have a revision link template is a follow up for this. Not making it a requirement as Media is experimental.

catch’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Path/AliasStorage.php
    @@ -131,7 +131,12 @@ public function load($conditions) {
    -        $select->condition($field, $this->connection->escapeLike($value), 'LIKE');
    +        if (is_array($value)) {
    +          $select->condition($field, $value, 'IN');
    +        }
    

    The IN query won't be case insensitive on postgres, which suggests missing test coverage here. Also supporting the workaround requiring this change makes me feel even more we should do the absolute minimal fix here. Either way shouldn't it be converted to multiple OR with LIKE instead of IN if we take this approach?

  2. +++ b/core/lib/Drupal/Core/Path/AliasStorage.php
    @@ -160,7 +165,7 @@ public function delete($conditions) {
             // Use LIKE for case-insensitive matching.
    -        $query->condition($field, $this->connection->escapeLike($value), 'LIKE');
    +        $query->condition($field, $value, 'LIKE');
           }
    

    Why is this no longer necessary here, but still necessary above?

timmillwood’s picture

#40.1 - I think an OR query could work. All we're trying to do here is simplify the code. We could so something like:

$alias = \Drupal::service('path.alias_storage')->load($conditions);
if (empty($alias)) {
  $alias = \Drupal::service('path.alias_storage')->load($revision_conditions);
}

#40.2 - This allows us to do real LIKE conditions with values such as '/node/1/revision/%'.

timmillwood’s picture

Status: Needs work » Needs review
FileSize
1.22 KB
13.82 KB

I hope this addresses the concerns in #40.

Status: Needs review » Needs work

The last submitted patch, 42: 2856363-42.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

pk188’s picture

Status: Needs work » Needs review
FileSize
13.83 KB
timmillwood’s picture

FileSize
785 bytes

Here's an interdiff for #44.

timmillwood’s picture

Status: Needs review » Needs work
+++ b/core/modules/path/tests/src/Functional/PathContentModerationTest.php
@@ -0,0 +1,155 @@
+    // Add another forward revision with no alias.
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->assertSession()->fieldValueEquals('path[0][alias]', '/revision-three');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', [
+      'title[0][value]' => 'revision four',
+      'path[0][alias]' => '/revision-four',
+    ], t('Save and Create New Draft'));

The patch in #44 is incorrect because this test is testing what happens when no path aliases is added and the field is left empty.

timmillwood’s picture

Status: Needs work » Needs review
FileSize
1.4 KB
13.82 KB

Fixing #42 and #44.

catch’s picture

Not sure my feedback on the revision path alias handling made it to this issue, must have been on a triage call or in irc or something, but I'm still not keen on the complexity it's adding here. Feels like a workaround when we could get the validation quick fix in for the critical + refactor path aliases fully later on.

timmillwood’s picture

@catch - I think this is a better fix than the validation, and isn't too disruptive. At least it gives a workable solution, where as the validation and the solution in #2766957: Forward revisions + translation UI can result in forked draft revisions prevent people from doing what they want.

Also, this patch is written, writing the validation quick fix would take more time, which would could be spending on the final solution or other issues.

timodwhit’s picture

Issue summary: View changes
timodwhit’s picture

Would this make paths fully revisionable? Does the patch here also support revert-ing a revision and preserving the audit trail?

It looks like it does but just want to verify.

timmillwood’s picture

I wouldn't go as far as saying it makes paths fully revisionable, but it allows you to have aliases on non-default revisions. I've not tested reverting so not 100% what happens.

Either way, this is much better than what we currently have, and better than preventing users from creating "draft" aliases.

timodwhit’s picture

Thanks @timmillwood

2 questions:
- Is the proposed data structure agreed upon? As in, will the revision path be the storage of the alias? I know there has been back and forth on the approach in the issue but would be good to have consensus as @timmillwood mentioned.
- If that is agreed upon, would it be okay to back port and post a patch for 8.3.x? This will help those looking to solve this issue in the short term until 8.4 is released. However, this is a little more complicated as ensureLoaded doesn't exist in 8.3 and I believe #2846554 is the source of that. Also, I do not want to create noise for this issue with, just want to give people a bandaid

timmillwood’s picture

I can't see this being back ported to 8.3, although you could backport it yourself and just patch 8.3 sites you run, this is done in many issues, where people share the 8.3 version of a patch.

timodwhit’s picture

@timmillwood, forsure!

With that though, for the sake of data parity/upgradability, etc, how certain do we feel that the revision template will be will be the proper identifier? Not trying to force too much of a decision, I just know that there was a PoC, other discussion and that developed into this patch. So just want to verify and update the issue description to reflect path forward.

timodwhit’s picture

@catch, I don't believe validation will work as a stop gap because of things like scheduled updates, or if content moderation is forced / always revisioned, or for companies that have a strict publishing policy must be a draft before being published, etc.

catch’s picture

@timodwhit it works as a stop gap because it stops the critical data integrity issue of changes leaking from drafts to the live site.

To do the things you want properly, we'd need #2336597: Convert path aliases to full featured entities and workspaces.

timodwhit’s picture

@catch, fully understand.

How would validation work if revisions are forced and the defaultRevision can't be edited? Or would the stop gap be revisions have to be toggable for the leaks to stop?

catch’s picture

Status: Needs review » Needs work

How would validation work if revisions are forced and the defaultRevision can't be edited?

You wouldn't be able to change the path alias. Someone that can edit the default revision directly would be able to.

Marking CNW for test coverage for reverting, and defining what the behaviour should actually be (although again, I think it's better to attack that with workspaces and not spend the effort here).

timmillwood’s picture

I have just been testing out reverting and this patch doesn't change the current state of reverting content with path aliases in HEAD, which I think it (maybe unrelatedly) broken.

Steps to reproduce (with or without Content Moderation enabled):

  1. Create a new node with the title "foo" and path alias "/foo".
  2. The path /foo will show the content.
  3. Edit the content, update the title to "foo2" and path alias "/foo2".
  4. The path /foo2 will show the content, /foo with throw a 404 error.
  5. Revert to the first revision.
  6. The path /foo2 will show the content (with title "foo"), /foo with throw a 404 error.
catch’s picture

I think that's 'by design' at the moment - path aliases are not in any way linked to the node itself except via the form, and aren't versioned at all. You can add a path alias for a node via the path UI, in the same way the book UI isn't versioned either.

What concerns me here is that this lack-of-relation is being further obscured by integrating with revision paths, whereas if we waited for workspaces, the idea of publishing or reverting an entire workspace would include path aliases, but without tying them to changes to nodes (for example it would include changes made via the path admin UI too).

timmillwood’s picture

Status: Needs work » Needs review
FileSize
5.08 KB

Taking this patch in the same direction as #2858434: Menu changes from node form leak into live site when creating draft revision and #2858431: Book storage and UI is not revision aware, changes to drafts leak into live site by throwing an error message on forward revisions, and preventing the URL alias from being updated.

timodwhit’s picture

Sorry if I'm not reading the patch right, but I don't think this will work in some cases and I think those cases are enough to justify thinking through this more.

If you have a workflow of:

- Draft/Published -> Draft
- Draft ->Published
- Published -> Archived
- Archived -> Published

You cannot edit the default revision.

Further, how would something like pathauto work in this? Can the title never change if the default revision isn't being edited and the path created off of node:title?

timodwhit’s picture

So just tried the patch on simplytest.me, looks as though you can save the node and are presented with an error.

This could be confusing but it doesn't stop the editing. Sorry for the confusion

timmillwood’s picture

I'm not sure I understand #63, but as you say in #64, it does work. To stop it being confusing it's vital we get the wording correct.

Pathauto could be an interesting one, but here we wouldn't be altering what Pathauto does or how it works, so it would carry on as it is.

timmillwood’s picture

I thought a screenshot might be useful here.

cilefen’s picture

From a usability perspective: What if the content author does not remember the original alias?

timmillwood’s picture

The original alias doesn't get lost / changed.

The only "lost" input is the alias they just entered before saving.

cilefen’s picture

Ok, good.

Bojhan’s picture

Issue tags: +Usability

Reviewed this with Roy, Gabor, Adam Hoenich and Andrei.

Thanks for raising this, this is quite tricky - but actually solvable. While the ideal solution involves actually raising a validation error and recovery upon submission, we thought given the engineering, and timespan it will effect the user (hopefully resolved with workspaces) and number of users it will affect that this is not worth it.

Therefor the message is for us, a good intermediate solution to continue with.

What the message should contain:

  • How to resolve the problem that occurred.
  • Show which fields we couldn't save/change.
  • What the brilliant values where, that you entered enabling users to recover the data that was lost
dawehner’s picture

I@m highly confused about this code change. I would have expected something which works on the field widget level, not being part of the storage API.

timmillwood’s picture

amateescu’s picture

@Bojhan, I implemented the proposed changes from the call in this issue: #2858431-51: Book storage and UI is not revision aware, changes to drafts leak into live site if you want to take a look :)

plach’s picture

Issue tags: +WI critical

This is marked as MUST-HAVE in the roadmap.

dawehner’s picture

"Might be", this is quite a vague reason :)

If I would work on this patch I would try to write a constrain validator, as this place allows you to also provide a human readable error message.

plach’s picture

Status: Needs review » Needs work
amateescu’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
7.53 KB
6.8 KB
48.28 KB

Here's the patch with a new constraint validator. And this is how it looks like, just like a regular entity form validation error:

dawehner’s picture

I try to understand what this means in the context of pathauto, which changes path aliases automatically. Given that pathauto should change its values just for published entities as well, right?

  1. +++ b/core/modules/path/src/Plugin/Validation/Constraint/PathAliasConstraint.php
    @@ -0,0 +1,19 @@
    + *   id = "PathAlias",
    
    +++ b/core/modules/path/src/Plugin/Validation/Constraint/PathAliasConstraintValidator.php
    @@ -0,0 +1,57 @@
    +class PathAliasConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
    

    I wonder whether we can find a better name, but I cannot come up with a good short name to be honest.

  2. +++ b/core/modules/path/src/Plugin/Validation/Constraint/PathAliasConstraintValidator.php
    @@ -0,0 +1,57 @@
    +      $original = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
    

    Isn't the right thing to do to use $entity->getLoadedRevisionId() to load the original?

plach’s picture

Looks great and works as advertised, attaching a test-only patch.


+++ b/core/modules/path/src/Plugin/Validation/Constraint/PathAliasConstraintValidator.php
@@ -0,0 +1,57 @@
+      $original = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());

I'm wondering whether it would make sense to use $entity->original if it is available.

Isn't the right thing to do to use $entity->getLoadedRevisionId() to load the original?

I think we need the "actual" default revision here: we don't want the user to change the alias wrt what's actually published, I think, even if it's different from the value in the revision being the default one when the form was instantiated.

Status: Needs review » Needs work

The last submitted patch, 79: 2856363-78.test.patch, failed testing. View results

dawehner’s picture

I think we need the "actual" default revision here: we don't want the user to change the alias wrt what's actually published, I think, even if it's different from the value in the revision being the default one when the form was instantiated.

Oh you are totally right here!

plach’s picture

Status: Needs work » Needs review

Failed as expected, patch to review is #77 (green!)

plach’s picture

timmillwood’s picture

  1. +++ b/core/modules/path/src/Plugin/Validation/Constraint/PathAliasConstraintValidator.php
    @@ -0,0 +1,57 @@
    +      $original = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
    

    I think $entity->original should work, but loadUnchanged() might give a more predictable result.

  2. +++ b/core/modules/path/tests/src/Functional/PathContentModerationTest.php
    @@ -0,0 +1,101 @@
    +      'title[0][value]' => 'forward revision',
    

    Forward revision? draft revision? pending version? :D

Status: Needs review » Needs work

The last submitted patch, 83: 2856363-83.test.patch, failed testing. View results

plach’s picture

Status: Needs work » Needs review

Failed as expected for reals now, patch to review is still #77.

plach’s picture

amateescu’s picture

I wonder whether we can find a better name, but I cannot come up with a good short name to be honest.

I also tried to figure out a better name but nothing came up..

I think $entity->original should work, but loadUnchanged() might give a more predictable result.

Well, it doesn't, I tried :) $entity->original is not set at that point.

plach’s picture

Status: Needs review » Reviewed & tested by the community

Let's get this in :)

Patch to commit is #77

yoroy’s picture

In light of #2875154: Clarify and document wording around drafts, revisions, published & friends, should we change all uses of "revision" to "version" in the user facing text? The screenshot in #77 uses both in a single message :)

amateescu’s picture

@yoroy, that's an excellent point! Now that the patch actually stops the form from being submitted I think we can also simplify the message a bit and drop the first sentence.

tstoeckler’s picture

+++ b/core/modules/path/src/Plugin/Validation/Constraint/PathAliasConstraintValidator.php
@@ -0,0 +1,57 @@
+      if ($value->alias != $original->path->alias) {

This is the only line in the constraint validator that is PathItem-specific.

I'm fine with deferring this to a follow-up, but I think we should be able to make this completely generic by using FieldItemListInterface::equals(), i.e. with the following:

$original_value = $original->get($value->getName());
if (!$value->equals($original_value)) {
  ...
}

Not sure if that would actually be useful to the related book or menu patches.

That said - again - I don't want to unnecessarily hold this up, as it would be important to get this one in, so would be fine with tackling that in a follow-up. But thought it would be useful to bring up since we have all the smart folks here in the issue ;-)

  • catch committed 5b9405d on 8.4.x
    Issue #2856363 by timmillwood, amateescu, plach, pk188, Mile23, catch,...
catch’s picture

Status: Reviewed & tested by the community » Fixed
amateescu’s picture

@tstoeckler, that's how I approached the problem initially, but book and menu_ui do not provide fields at all, they're doing old-style form alters so there's nothing really reusable here.

Status: Fixed » Closed (fixed)

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

Naikalongus’s picture

Assigned: Unassigned » Naikalongus
Category: Bug report » Feature request
Naikalongus’s picture

cilefen’s picture

Assigned: Naikalongus » Unassigned
Category: Feature request » Bug report