Problem/Motivation

If the user creates a custom menu link (i.e. a menu link content entity) the original user input is not retained in any way after we resolve it to a route. Thus, the link becomes invalid if the path resolves to another valid route if the original route goes away

For example,
(also see the use cases in the meta issue, "So, if the user types blog, store user-path:blog." under the heading "Menu link storage on the back-end to support these use cases")
before this issue the problem is when you:

make view

in the menu ui, make /blog menu item:
blog
is stored as route views.block.page_1

disable the page display
menu item still stores views.block.page_1

create a panel with path: blog
(it doesn't create a menu)
You expect the link to still work and point to /blog

menu still points to views.block.page_1
and it is broken

Proposed resolution

Store the user input by using a link field. The link field itself just has a UR, (maybe) title and options (may be description) stored.

Follow up: Decide when and how to update or resolve the route - e.g. can make these plugins participate in discovery and resolve the route then. Doing that in #2417423: Re-process the user-entered-paths for custom menu links when there is a menu rebuild

Remaining tasks

  • (done) Decide on implementation, do it.
  • (done) file follow-up issues
  • (done) update issue summary
  • (done) change record is drafted

User interface changes

N/A

API changes

Change to base table of entity.

Comments

webchick’s picture

On our branch maintainer call, we decided that since this is a hard-blocker to finalizing the menu links system, this is indeed a critical issue. Tagging.

dawehner’s picture

Issue summary: View changes
StatusFileSize
new1.06 KB

Alter the base table of the entity so that we story the user input and/or the "system" path (path after resolving aliases) in the entity.

On the call we agreed that we store the user input itself.

Let's start simple, and store what we put in.

Should we distinct clearly between the external url and the path of the input?

catch’s picture

Status: Postponed » Needs work

I don't think this is actually postponed on anything?

pwolanin’s picture

Indeed - not postponed - we can start making the new entity or converting the existing on to just take user input.

dawehner’s picture

State in HEAD is that you can already, from an API POV store the path in the url part, and store the route name separately, so the simplest imaginable way would be to
store it there and be done.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new7.13 KB

Another try. Let's not store route name and parameter to see how many failures we get. On the longrun we will switch to the link field here.

dawehner’s picture

Another try. Let's not store route name and parameter to see how many failures we get. On the longrun we will switch to the link field here.

Status: Needs review » Needs work

The last submitted patch, 6: 2406749-6.patch, failed testing.

webchick’s picture

Issue summary: View changes

Removing out-dated child/postponed information from issue summary.

dawehner’s picture

Title: Store and handle user path input in custom menu links » Use a link field for custom menu link
Issue summary: View changes

I think the proper plan would be following: use a link field. Adapted the issue title and IS for that.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new13.23 KB
new12.48 KB

Some progress

Status: Needs review » Needs work

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

dawehner’s picture

Assigned: Unassigned » dawehner

Working on it

dawehner’s picture

Assigned: dawehner » Unassigned
Status: Needs work » Needs review
StatusFileSize
new26.37 KB
new13.98 KB

Let's see how much is left.

amateescu’s picture

+++ b/core/modules/migrate_drupal/config/install/migrate.migration.d6_menu_links.yml
@@ -22,15 +22,8 @@ process:
+  'link/uri': link_path

Just a short observation for now: are you sure this is correct? I would think that 'link_path' needs some processing in order to become a URI.

Status: Needs review » Needs work

The last submitted patch, 14: 2406749-14.patch, failed testing.

gábor hojtsy’s picture

The link field has a title and a description on it (following #2413029: Change LinkItem schema to also store a description). The menu item link also has a title and a description on it. What is stored in these and how is translation handled? This looks confusing to me.

gábor hojtsy’s picture

Issue tags: +D8 upgrade path

Add missing tag.

gábor hojtsy’s picture

There is a bunch of discussion on #2235457: Use link field for shortcut entity on not using the link field's title and description. I guess that applies here also. However that RTBC patch adds the link field as translatable. Would that make sense here as well?

hussainweb’s picture

Status: Needs work » Needs review
StatusFileSize
new25.49 KB

Rerolling the patch in #14 after #2235457: Use link field for shortcut entity got in.

wim leers’s picture

#17: great point. amateescu said in #2235457-3: Use link field for shortcut entity: I'd prefer to not use the title property of the link field, mostly because I see it as a main property of the entity. — by that same reasoning, the menu link entity indeed should not use the link field's description property.
But, this is putting words in his mouth — I wonder what amateescu thinks? :)

wim leers’s picture

And related: #2413029-10: Change LinkItem schema to also store a description — the validity/necessity of adding a description property to the link field is now questionable AFAICT.

Status: Needs review » Needs work

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

gábor hojtsy’s picture

In #19, I incorrectly stated that #2235457: Use link field for shortcut entity had the link field translatable, that is not true. So I guess fine here to not have it translatable either.

amateescu’s picture

I didn't want to derail #2235457: Use link field for shortcut entity, which was finally ready and had some nice improvements for the link field, so I stayed away from the title debate, but my current thinking is exactly the opposite of what I wrote there almost a year ago :/

Some reasons for this change of heart:

  • a link is by definition composed of a title, a url and optionally other attributes
  • keeping the link and description fields separate comes with a small performance cost (more field api/typed data objects being instantiated and their definitions kept in memory), and menu items are quite sensitive in this regard (but shortcuts less so)
  • based on the same principle of the latest menu link meta (use a link field everywhere), it would be nice if we could also use the UI from #2407913: Discuss/define the minimal UX for adding menu links to entities everywhere, not only for menu links
  • it would also help contrib a bit, the menu_attributes module could turn into a generic link_attributes module

On the other hand, if we decide to use the title property of the link field, I'm afraid we'll have some more work to do in order to support specifying a field property (e.g. link__title) as the 'bundle' key in the entity type annotation, but maybe that's not too much work?

In the end, I'm not really decided on either approach so let's discuss a bit more :)

berdir’s picture

If you want to natively suggest field properties for entity keys, which should be easy enough (see ContentEntityBase::getEntityKey()), then I would suggest link.title, because that will just work with $entity_query->condition().

Also, was not aware that you changed your opinion on that topic (well, how could I ;)), so I silently supported the decision to keep title separate in the shortcut issue based on what i remembered from our old discussion there ;)

amateescu’s picture

If you want to natively suggest field properties for entity keys, which should be easy enough (see ContentEntityBase::getEntityKey()), then I would suggest link.title, because that will just work with $entity_query->condition().

I was just about to post exactly this but I refreshed the page and saw your comment :)

dawehner’s picture

If you want to natively suggest field properties for entity keys, which should be easy enough (see ContentEntityBase::getEntityKey()), then I would suggest link.title, because that will just work with $entity_query->condition().

Beside that it doesn't work for base fields, see #2415001: EntityQuery should be independent from schema for fields.

What I don't like is that once you add the title to the link field, you have potential limits on the way how you built your UI, which is bad. The data modelling should not affect the way how the UI is built and wise versa.

dawehner’s picture

Assigned: Unassigned » dawehner

Working on it for now.

xjm’s picture

Issue tags: +D8 Accelerate NJ
dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new27.86 KB
new3.39 KB

This will be green!

dawehner’s picture

Assigned: dawehner » Unassigned

Unassign it for now.

jibran’s picture

This url <-> uri thing big DrupalWTF can we have some clarity please.

  1. +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
    @@ -72,50 +75,15 @@ public function getTitle() {
       public function getUrl() {
    

    Shouldn't it be get uri?

  2. +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
    @@ -198,13 +151,24 @@ public function getChangedTime() {
    +        $definition['url'] = $url_object->getUri();
    
    +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
    @@ -198,6 +198,7 @@ public function extractFormValues(array &$form, FormStateInterface $form_state)
             $new_definition['url'] = $extracted->getUri();
    
    @@ -299,10 +274,8 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
    +    $entity->link->uri = $new_definition['url'];
    

    so url is uri :S

  3. +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
    @@ -328,22 +292,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
         // @todo Use a link field https://www.drupal.org/node/2302205.
    

    We can remove this now.

wim leers’s picture

I'll attempt explaining this.

URI is an identifier, URL is a locator. Roughly: URIs identify some resource, URLs allow you to locate (obtain) some resource.

IOW: we can use URIs internally as long as we translate them to URLs when presented to a browser; a browser doesn't understand entity: URIs for example.

pwolanin’s picture

LINK_INTERNAL should probably be LINK_GENERIC?

pwolanin’s picture

So, we have to be clear that these changes are temporary until we fix the link field to use a URI, but are ok on that basis.

-          ->condition('route_name', 'entity.node.canonical')
-          ->condition('route_parameters', serialize(array('node' => $node->id())))
+          ->condition('link__uri', 'node/' . $node->id())

Also, agreed that getUrl() should be getUrI() ugh - looks in the editor the same but URL to URI

wim leers’s picture

Status: Needs review » Needs work

Initial review:

  1. +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
    @@ -72,50 +75,15 @@ public function getTitle() {
       public function getUrl() {
    -    return $this->get('url')->value ?: NULL;
    +    return $this->get('link')->uri ?: NULL;
       }
    

    This no longer matches the behavior described by the docs:
    Returns the external URL if the menu link points to an external URL, otherwise NULL.

  2. +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
    @@ -72,50 +75,15 @@ public function getTitle() {
    +    return \Drupal::pathValidator()->getUrlIfValidWithoutAccessCheck($this->link->uri) ?: Url::fromRoute('<front>');
    

    The path validator should only be used for user-path: URIs I think?

    EDIT: d'oh, user-path: didn't land yet, so this doesn't make sense yet.

  3. +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
    @@ -188,7 +188,7 @@ public function extractFormValues(array &$form, FormStateInterface $form_state)
    -    $new_definition['url'] = NULL;
    +    $new_definition['url'] = $form_state->getValue('url');
    

    This looks wrong; this means that also for menu links that link by route will have url set in the definition.

  4. +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
    @@ -219,33 +220,7 @@ public function extractFormValues(array &$form, FormStateInterface $form_state)
    +    $default_value = $this->getEntity()->link->uri;
    

    Wonderful simplification here, if it's correct :)

  5. +++ b/core/modules/menu_link_content/src/Tests/LinksTest.php
    @@ -147,7 +147,7 @@ public function testCreateLink() {
    -    // Check the initial hierarchy.
    +    // Check the initial hierarchy
    

    Nit: Unintended change.

dawehner’s picture

+++ b/core/modules/menu_ui/menu_ui.module
@@ -212,8 +212,7 @@ function menu_ui_node_prepare_form(NodeInterface $node, $operation, FormStateInt
+          ->condition('link__uri', 'node/' . $node->id())

@@ -224,8 +223,7 @@ function menu_ui_node_prepare_form(NodeInterface $node, $operation, FormStateInt
+          ->condition('link__uri', 'node/' . $node->id())

Just in case someone wonders: This is caused by #2391217: Support base fields with multiple columns in entity queries

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new27.53 KB
new4.34 KB

And here is patch.

Status: Needs review » Needs work

The last submitted patch, 39: 2406749-39.patch, failed testing.

wim leers’s picture

  1. +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
    @@ -198,13 +144,24 @@ public function getChangedTime() {
    +    if ($url_object) {
    +      if ($url_object->isExternal()) {
    +        $definition['url'] = $url_object->getUri();
    +        $definition['options'] = $url_object->getOptions();
    +      }
    +      else {
    +        $definition['route_name'] = $url_object->getRouteName();
    +        $definition['route_parameters'] = $url_object->getRouteParameters();
    +        $definition['options'] = $url_object->getOptions();
    +      }
    +    }
    

    Nit: you could pull this out of these ifs and do it in both cases, unconditionally.

  2. +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
    @@ -327,23 +284,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +      ))
    +    ;
    

    Nit: Interesting code formatting :)

  3. +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
    @@ -219,33 +220,7 @@ public function extractFormValues(array &$form, FormStateInterface $form_state)
    +    $default_value = $this->getEntity()->link->uri;
    

    This causes a notice at /admin/structure/menu/manage/tools/add:
    Notice: Trying to get property of non-object in Drupal\menu_link_content\Form\MenuLinkContentForm->form() (line 223 of core/modules/menu_link_content/src/Form/MenuLinkContentForm.php).

  4. +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
    @@ -299,10 +274,8 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
    +    $entity->link->uri = $new_definition['url'];
    +    $entity->link->options = $new_definition['options'];
    

    This causes a warning upon saving:
    Warning: Creating default object from empty value in Drupal\menu_link_content\Form\MenuLinkContentForm->buildEntity() (line 277 of core/modules/menu_link_content/src/Form/MenuLinkContentForm.php

  5. +++ b/core/modules/menu_ui/menu_ui.module
    @@ -162,8 +162,8 @@ function menu_ui_node_save(EntityInterface $node) {
    +          // @todo Replace it with entity:// once possiblele
    

    entity:

yched’s picture

This is somewhat above my head, but looks like an awesome simplification.

I'm a bit surprised at the number of times MenuLinkContentForm ends up calling $this->extractFormValues(), but that's just me looking at code I never saw before, this is not added here :-)

No way to use a link widget instead of some custom $form logic here ? I guess probably not if the patch doesn't do it, but it looks like it hasn't been mentioned in the issue so far, so, just asking :-)

yched’s picture

+++ b/core/modules/menu_ui/menu_ui.module
@@ -212,8 +212,7 @@ function menu_ui_node_prepare_form(NodeInterface $node, $operation, FormStateInt
+          ->condition('link__uri', 'node/' . $node->id())
@@ -224,8 +223,7 @@ function menu_ui_node_prepare_form(NodeInterface $node, $operation, FormStateInt
+          ->condition('link__uri', 'node/' . $node->id())

Just in case someone wonders: This is caused by #2391217: Support base fields with multiple columns in entity queries

Yup - could we link to that issue in a @todo so that we can cleanup when that other issue is fixed ?

+++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
@@ -248,8 +248,7 @@ function testBreadCrumbs() {
+        'link__uri' => 'taxonomy/term/' . $term->id(),

Is that the same reason/bug here ? @todo as well ?

dawehner’s picture

Status: Needs work » Needs review
Issue tags: +D8 Accelerate
Related issues: +#2416955: Convert MenuLinkContent to use a link widget
StatusFileSize
new27.39 KB
new2.61 KB

Fixed the remaining review points ... and added a follow up for the FIeld

dawehner’s picture

StatusFileSize
new27.63 KB
new1.94 KB

No way to use a link widget instead of some custom $form logic here ? I guess probably not if the patch doesn't do it, but it looks like it hasn't been mentioned in the issue so far, so, just asking :-)

Well yeah, we thought that would be easy but it turned out it isn't at all. LinkWidget has additional problems on top of the existing LinkItem had.
Therefore we have an additional issue for that: #2416955: Convert MenuLinkContent to use a link widget

Is that the same reason/bug here ? @todo as well ?

Yeah it is, because ES::loadByProperties uses entity query.

The last submitted patch, 44: 2406749-42.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 45: 2406749-45.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new4.96 KB
new27.68 KB

Let's get it in.

Status: Needs review » Needs work

The last submitted patch, 48: 2406749-48.patch, failed testing.

hussainweb’s picture

I think you uploaded the README.txt instead of an interdiff. :)

hussainweb’s picture

Status: Needs work » Needs review
StatusFileSize
new27.64 KB

Rerolling the patch in #48 for conflicts from #2364157: Replace most existing _url calls with Url objects.

@dawehner: I think you uploaded another file by mistake instead of the interdiff. Please upload it if you think it is necessary.

kim.pepper’s picture

StatusFileSize
new1.36 KB

Interdiff of #45 and #48.

dawehner’s picture

StatusFileSize
new27.64 KB
new996 bytes

This can't be right.

amateescu’s picture

Issue tags: -D8 Accelerate
StatusFileSize
new27.52 KB
new2.37 KB

Just a couple of small fixes. The patch looks ready to me.

yesct’s picture

does the change in #53
the !
mean there is missing test coverage?

yesct’s picture

+++ b/core/modules/menu_link_content/src/MenuLinkContentAccessControlHandler.php
@@ -64,10 +64,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
-            // We allow access, but only if the link is accessible as well.

+++ b/core/modules/menu_ui/menu_ui.module
@@ -162,8 +162,8 @@ function menu_ui_node_save(EntityInterface $node) {
-          'route_name' => 'entity.node.canonical',
-          'route_parameters' => array('node' => $node->id()),
+          // @todo Replace it with entity: once possible.
+          'link' => ['uri' => 'node/' . $node->id()],

which issue would this todo be?

yesct’s picture

guessing this needs change record updates before it is rtbc.

yesct’s picture

StatusFileSize
new27.55 KB
new754 bytes

Here is the patch.

---
Also searched for which change records might need to be updated
"entity base table" returns no results https://www.drupal.org/list-changes/published?keywords_description=entit...

"entity table" https://www.drupal.org/list-changes/published?keywords_description=entit...
returns
Database schema of content entities is automatically generated based on entity type and field definitions https://www.drupal.org/node/2259243

this patch is changing code from previous menulinkNG issues so maybe there is something to change in
Menu links converted to plugins, including static, views, and content entity implementations https://www.drupal.org/node/2302069

still looking for change records.

yesct’s picture

Issue summary: View changes
Issue tags: -Needs change record updates

making a new mega change record for issues related to the meta, and putting in details from here into that.
initial guess at title: Configurable link field, short cut, menu links store user entered paths as URI (not routes or paths)
https://www.drupal.org/node/2417421

also updated the issue summary.

RavindraSingh’s picture

StatusFileSize
new39.63 KB

I think the added issue
#2417367: Use entity: in MenuLinkContentAccessControlHandler instead of 'link' => ['uri' => 'node/' . $node->id()], will be that @todo - doesn't require any work. link is not attribute in create entity thats why.
link avaialble

yesct’s picture

Status: Needs review » Reviewed & tested by the community

@RavindraSingh
Thanks for looking at this.
You are correct, link is not in head right now.

this patch is adding [link] to the array. so that issue will make sense when this gets committed.

----

I read through the patch a couple times, the summary is up to date, the change record draft made, and follow ups created. RTBC from me.

pwolanin’s picture

Comment change in #59 looks fine - I discussed in person with YesCT

jibran’s picture

+1 for RTBC.

webchick’s picture

Status: Reviewed & tested by the community » Fixed
16 files changed, 78 insertions, 232 deletions.

Mmm, that's a tasty diffstat! :)

Two questions when reading through. One: What's up with this?

+          // @todo Use entity: in the URI.
+          //   https://www.drupal.org/node/2417367
+          'link' => ['uri' => 'node/' . $node->id()],

We should be able to do that as of ~20 hours ago, no? However, reading #2417367: Use the new entity: URI scheme it looks like this was deliberately split off because it could be tricky to get tests passing. Fair enough. It's not different from what's in HEAD atm which is also not using entity: so I think this is ok.

Two: What's up with this?

+          // @todo Use link.uri once https://www.drupal.org/node/2391217 is in.
+          ->condition('link__uri', 'node/' . $node->id())

However, this is also covered with a @todo, and #2391217: Support base fields with multiple columns in entity queries seems to cover this case fine.

Everything else looks pretty straight-forward, so going to go ahead and get this in so we can continue to make progress.

Committed and pushed to 8.0.x. Thanks, all!!

  • webchick committed cb22bbd on 8.0.x
    Issue #2406749 by dawehner, YesCT, amateescu, hussainweb, kim.pepper,...
yesct’s picture

benjy’s picture

Just an FYI, this patch updated files in "core/modules/migrate_drupal/src/Tests/Table" which are automatically generated from a database dump but didn't update the database dump.

This has been fixed in #2410623: D6-D8 Custom Block Migration not visible in region

Status: Fixed » Closed (fixed)

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

sutharsan’s picture

I have written the unit tests in #2417359: Add test coverage for MenuLinkContentAccessControlHandler::accessCheck() when "We allow access, but only if the link is accessible as well." as requested in #58. I would appreciate a review to see whether it covers the case(s) affected by this issues.