Problem/Motivation

#2198571: menu.inc must not unset MenuLink::menu_name and #2188097: "Comment forms" links to admin/content/comment showed that if a menu link's parent item does not exist, the system does not show it, but fails later on.

The result is that invalid and incomplete menu link entities are stored for missing parent_id and it is also very hard to debug where the problem is coming from (Assume that a module is disabled and another one implicitly depends on a menu link from it).

Proposed resolution

Throw an exception when a menu link's parent link does not exist, prevents it from being saved.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

sun’s picture

Xano’s picture

Status: Active » Needs review
FileSize
697 bytes

This works, but menu_router_rebuild() catches any exception, so except for a log message there is never any evidence of this validation failure.

dawehner’s picture

It would be kind of better to add this exception somewhere around

      else {
        if (empty($link['route_name']) && empty($link['link_path'])) {
          watchdog('error', 'Menu_link %machine_name does neither provide a route_name nor a link_path, so it got skipped.', array('%machine_name' => $machine_name));
          continue;
        }
        $menu_link = $menu_link_storage->createFromDefaultLink($link);
      }
      if (!empty($link['parent'])) {
        $children[$link['parent']][$machine_name] = $machine_name;
        $menu_link->parent = $link['parent'];
        if (empty($link['menu_name'])) {
          // Unset the default menu name so it's populated from the parent.
          unset($menu_link->menu_name);
        }
      }
      else {
        // A top level link - we need them to root our tree.
        $top_links[$machine_name] = $machine_name;
        $menu_link->plid = 0;
      }

in
menu_link_rebuild_defaults()

What about providing an actual named exception for non-existing parent and catch that one different, so we at least show some message?

Berdir’s picture

Category: Task » Bug report
Issue summary: View changes
Berdir’s picture

Issue summary: View changes

Too tired... updated the issue title and summary to clarify that the current behavior results in weird and invalid menu links being saved and notices being thrown (in HEAD) and changing to a bug report. With the validation/exception throwing as the suggested solution for

Result is the same but clarifies that this fixes an actual problem and isn't just a nice to have improvement :)

The link title is a similar case, you can right now save a menu link without a link title (for example, because you accidentally used 'title' when converting from hook_menu()), and it will happily save a menu link with a link title. I would assume that we can make the title required now, as we don't have callbacks and stuff like that anymore, so we should possibly validate and throw an exception for that too?

Xano’s picture

It would be kind of better to add this exception somewhere around (...) in menu_link_rebuild_defaults()

Why do you think that would be better? IMHO validation should happen as close to the input (which is the hook invocation) as possible, especially as what we're doing here is nothing application-level, but simply verify data integrity.

dawehner’s picture

Adding some tags

Xano’s picture

FileSize
1.77 KB
1.53 KB

I noticed the description key is not documented as either required or optional, so I added that this is optional, so the validation and documentation are in sync.

What about providing an actual named exception for non-existing parent and catch that one different, so we at least show some message?

We could, but I'd first like to know why menu_router_rebuild() catches any exception it receives in the first place, especially as exceptions are used for problems that must be fixed, before we make it re-throw only specific ones.

dawehner’s picture

Previously menu_router_rebuild() have been critical, as you could not rebuilt anything manually via the UI. This is not longer the case given that the menu_router is not the router anymore.

dawehner’s picture

+++ b/core/includes/menu.inc
@@ -2553,6 +2553,16 @@ function menu_link_get_defaults() {
+
+    // Check for required array keys.
+  if (!isset($link['link_title'])) {
+    throw new \Exception(sprintf('Menu link %s does not have a "link_title" key in its definition.', $machine_name));
+  }
+

We could also check for an accident use of 'title' instead of 'link_title'. have you also thought about checking that the route name is valid?

Xano’s picture

FileSize
3.29 KB
2.47 KB

We could also check for an accident use of 'title' instead of 'link_title'.

Redundant keys don't matter, do they?

have you also thought about checking that the route name is valid?

Good one! I added that in this patch.

I also moved the validation until after the invocation of the alter hook, to make absolutely sure the definitions are valid.

Xano’s picture

Title: Throw an exception if a menu link's parent does not exist » Validate default menu link definitions

Status: Needs review » Needs work

The last submitted patch, 11: drupal_2198619_11.patch, failed testing.

tim.plunkett’s picture

Issue tags: +Needs tests
+++ b/core/includes/menu.inc
@@ -2554,11 +2555,44 @@ function _menu_link_save_recursive($controller, $machine_name, &$children, &$lin
+      throw new \Exception(sprintf('Menu link %s does not have a "link_title" key in its definition.', $machine_name));
...
+      throw new \Exception(sprintf('Menu link %s must provide either a "route_name" or a "link_path" key in its definition.', $machine_name));
...
+        throw new \Exception(sprintf('Menu link %s specifies route name %s, but no route with that machine name exists.', $machine_name, $link['route_name']), 0, $e);

No typed exception for any of these? :(

Also shouldn't we be able to test this now?

Xano’s picture

Also shouldn't we be able to test this now?

Not yet, as the existing tests fail, and since the patch only introduced exceptions, the failure must be caused by one of those. However, I have not been able to get them to show up in the test results yet.

dawehner’s picture

They also don't appear in the watchdog?

Berdir’s picture

Priority: Normal » Major

Raising to major, for the reason #2198571: menu.inc must not unset MenuLink::menu_name was re-opened. Apparently providing a wrong parent now leads to a PDO exception and this is happening a lot now because we renamed the menu links so all contrib modules that are placing admin links are now broken.

Xano’s picture

Status: Needs work » Needs review
FileSize
3.09 KB

Re-roll. No changes.

Status: Needs review » Needs work

The last submitted patch, 18: drupal_2198619_18.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
3.2 KB

Found the problem, the two foreach's with by reference were messing with the links.

Status: Needs review » Needs work

The last submitted patch, 20: drupal_2198619_20.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review

20: drupal_2198619_20.patch queued for re-testing.

Xano’s picture

FileSize
3.6 KB
4.41 KB
pwolanin’s picture

Status: Needs review » Needs work

why are we using sprintf?

+      throw new \Exception(sprintf('link_path %s for menu link %s must be absolute.', $link['link_path'], $machine_name));
pwolanin’s picture

Also, there is code now that's incorrect that unsets menu name in certain cases during the save. I don't know that this patch makes sense in general.

I don't think validating the actual route is worth the code in term of time and memory.

dawehner’s picture

We should certainly validate the menu links in case we are in a development enviroment, much like we should do route definition validation in case we have that mode.

Xano’s picture

I haven't looked through #2226761: Change all default settings and config to fast/safe production values in detail yet, but that's in now (#26 looks like it referenced that issue).

mgifford’s picture

Status: Postponed » Active

Unblocked I think.

dawehner’s picture

Issue tags: +Needs reroll

Maybe this helps to trigger some initiative in xano :P

AjitS’s picture

Status: Active » Needs review
Issue tags: -Needs reroll
FileSize
74.9 KB

Plain reroll

Status: Needs review » Needs work

The last submitted patch, 34: validate_default_menu-2198619-34.patch, failed testing.

pwolanin’s picture

Sorry, but that's a completely incorrect re-roll In #34 - this patch needs to be rewritten since the menu link save code is no longer in menu.inc.

pwolanin’s picture

Status: Needs work » Fixed

In fact, I think the basic problem is fixed in MenuTreStorage::findParent()

It validates the parent there - if it doesn't exist, the link goes to the top level with an empty parent.

I'm pretty sure we handle invalid route name at render time.

Status: Fixed » Closed (fixed)

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