Problem

  • If menu links get customized (e.g., moved) then build and alter hooks may no longer apply.

Goal

  • Provide a reliable link structure and contents for build and alter hooks, while retaining the ability to customize the menu.

Comments

donquixote’s picture

Title: hook_admin_menu replaced() by hook_admin_menu_replacements() in 3.x ? » hook_admin_menu() replaced by hook_admin_menu_replacements() in 3.x ?

...

As we are talking about it, would it be possible and make sense to add legacy support for older modules that still use hook_admin_menu?

sun’s picture

Category: task » support
Status: Active » Fixed

It's hook_admin_menu_output_alter() now, see admin_menu.api.php for the latest API documentation.

donquixote’s picture

Hmm, I did a dsm($content) in my implementation, and $content['menu'] is a nested array with a bunch of numeric keys. I don't see a $content['links'], as suggested in the documentation.

What is the best way to work with the $content['menu'] array?
I want to add some submenu items under Site building > Menus > List menus (menu_editor module).

Of course I could just search through the tree and look at each href, to finally find the place I want to work with..

Status: Fixed » Closed (fixed)

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

donquixote’s picture

Category: support » task
Status: Closed (fixed) » Active

I think it needs better documentation. (see #3)

donquixote’s picture

Title: hook_admin_menu() replaced by hook_admin_menu_replacements() in 3.x ? » hook_admin_menu_output_alter() - improve documentation?

Title is misleading..
Maybe I will find the answer for myself, in this case I might close the issue. For now I just fix the title.

donquixote’s picture

As said in #502500-98: Re-add Create content and also in #3, I find the new hook a bit unconvenient, compared to the old one. I don't like to work with mlids as array keys.

So, what about something like this in hook_menu:

function mymodule_menu() {
  $items = array();
  if (module_exists('admin_menu')) {
    $types = ...;
    foreach ($types as $slug => $title) {
      $items["admin/content/node-add/$slug"] = array(
        'page callback' => 'drupal_goto',
        'page arguments' => array("node/add/$slug"),
        ...
      );
    }
  }
  return $items;
}

Later admin_menu has to look for drupal_goto page callbacks, and replace the item's path with the goto destination.
Alternatively, we could introduce a page callback 'admin_menu_goto_callback', to make sure we don't do unintended stuff.

donquixote’s picture

Title: hook_admin_menu_output_alter() - improve documentation? » Thoughts about the new hook_admin_menu_output_alter()
sun’s picture

I don't like to work with mlids as array keys.

The primary key of menu links is the menu link id (mlid). Only when keyed by mlid, we are technically fully correct and compatible with Drupal's menu system. I don't like it either, but that's the reality.

Later admin_menu has to look for drupal_goto page callbacks, and replace the item's path with the goto destination.
Alternatively, we could introduce a page callback 'admin_menu_goto_callback', to make sure we don't do unintended stuff.

I'm not sure what this would be good for. As mentioned over in #502500: Re-add Create content, that issue just needs someone to properly retrieve the already existing menu link tree for and below the path node/add and just put that into the renderable array structure. Extra bonus points for implementing that as node_admin_menu_output_alter().

When in doubt, then a look at menu_block module might help to find existing code that retrieves a partial menu link tree starting from a certain parent link.

donquixote’s picture

StatusFileSize
new4.12 KB

This works quite ok for menus items that are already part of system navigation.
I would like to use it for menu_editor, where this is not the case.

With hook_admin_menu_output_alter, if the items are not already in another menu, I have to do my own access checking, and build a structure exactly as it is supposed to look for the admin_menu output function. Well, maybe I can get used to that.

For now I have solved this with my own module, where you can see the drupal_goto idea in action.
The only difference is that I have to call admin_menu_output_alter to explicitly walk through those items and replace the link destination.
(see attachment)

--------------

As mentioned over in #502500: Re-add Create content, that issue just needs someone to properly retrieve the already existing menu link tree for and below the path node/add and just put that into the renderable array structure. Extra bonus points for implementing that as node_admin_menu_output_alter().

See #502500-97: Re-add Create content.
So far it fetches the full menu, instead of just a submenu. I could reuse some parts of menu_block, but I'm not sure if it produces the same structure that admin_menu uses.

sun’s picture

Status: Active » Postponed (maintainer needs more info)

Hm, I've looked at that code, and the first thing that crosses my mind is that it's fundamentally wrong to build module functionality around admin_menu's menu, it should work without admin_menu, too - i.e., a menu callback that statically invokes drupal_goto() as page callback is just weird. :)

Let's keep the discussion on "Create content" in the other issue.

In the end, I'm not sure what the purpose of this issue is...?

donquixote’s picture

The point is, I want to add items into the admin_menu tree that would belong somewhere else according to their paths.

Unfortunately, the core menu system does not allow to customize the "parent path" of a router item. The path of a router item is the only thing that defines where it is placed in the menu tree.

Now, for menu_editor, I want the menu edit form to be linked in two places:
1. In a local task at Site building > Menus > (pick a menu) > "Poweredit".
2. A shortcut in the admin menu under Site building > Menus > List menus > [menu name].

This used to be possible with the old hook_admin_menu(), but not with the core hook_menu or hook_menu_alter.
The closest I can get with the core menu system is a redirect, using a drupal_goto callback or a custom function that does the same. This works, but is not the ideal way to do things.

Fortunately, admin_menu provides a hook that allows to replace the path, after the item has already been placed somewhere in the tree. The item can now have a link path that does not have to reflect its position in the menu. However, we need a custom implementation of hook_admin_menu_output_alter to actually make this happen.

The idea was to automate this process, and always replace the path if the page callback is drupal_goto. This would happen before the access checking.

So, why not in core?
If we would be talking about Drupal core, I would rather suggest a dedicated functionality that allows to set either an optional "menu parent" (that can be different from the parent guessed from the array key) or an optional "path" (that can be different from the item's array key) directly in hook menu, or hook_menu_alter. Unfortunately, D6 is feature frozen (and D7 is too nowadays, or not?). And even if it wasn't, it would probably take half a year of discussion to get this feature.

As we are not talking about Drupal core, we can't fundamentally change how hook_menu or hook_menu_alter works, but we can use the existing hook protocol to express extra information.

sun’s picture

hook_menu() and hook_menu_alter() of Drupal core's menu system are entirely different things, compared to admin_menu's hook. Menu router items are automatically parented after their path components, that's true and won't change until D8 or D9, when we will entirely revamp core's menu system and completely separate menu router items from menu links. Very likely, menu links will have to be created manually then, and only once.

yes, admin_menu allows to alter links in the rendered menu, but you should primarily use this only to change CSS classes, add sub-links, or relocate certain links (although that should ideally be limited to "own" links).

That said, it looks like you just want to register your regular items/links via hook_menu(), using a path below admin/*, and use hook_admin_menu_output_alter() to move/copy/fork them into other locations of admin_menu.

donquixote’s picture

but you should primarily use this only to change CSS classes, add sub-links, or relocate certain links (although that should ideally be limited to "own" links).

Yes. This is why I was asking for something in addition to hook_admin_menu_output_alter, that happens earlier in the process, and where we can play with paths instead of mlids. As admin_menu is no longer involved in menu_rebuild (or is it?), I can only think of core hook_menu and hook_menu_alter.

A middle way would be a hook that fires before admin_menu_links_menu(), with the tree from menu_tree_all_data(). Or, if we can further split up menu_tree_all_data (which I already did somewhere), we could fire a hook somewhere in that process, before the access checking. We would still be dealing with mlids then, so I'm not sure if this would be much of a benefit.

That said, it looks like you just want to register your regular items/links via hook_menu(), using a path below admin/*, and use hook_admin_menu_output_alter() to move/copy/fork them into other locations of admin_menu.

Almost. The original router items are local tasks, with a parent that is not in system navigation, and is not supposed to be there either.
So, I would have to hook_menu_alter to put the parent items (admin/build/menu-customize and admin/build/menu-customize/[menu name]) into system navigation / admin menu, and then remove it again via hook_admin_menu_output_alter.
I think the drupal_goto approach is easier, because then I only have to change the #href.

donquixote’s picture

until D8 or D9, when we will entirely revamp core's menu system and completely separate menu router items from menu links

I have a slightly different opinion on this..

EDIT: My objections start in #13.
#653784-13: Separate out menu links from hook_menu

sun’s picture

I think you're still looking at this from a old/wrong perspective. menu_rebuild() is the wrong place to look at. I guess you still do that, because admin_menu 1.x used that bogus approach of duplicating the entire menu router into new records and building a menu out of that. End of story was, however, that people were not able to customize this menu in any way. All changes applied to administrative links were not taken over by admin_menu and there was no other way to change links in admin_menu. And effectively, that's the ultimate source of the troubles: 1.x thought of hard-coded router paths only, but admin_menu is rendering a menu link tree in reality. 3.x does just that. It renders a menu link tree, basically almost like any other menu is rendered.

Therefore, the presumption that there would be a valid menu link tree that's keyed by paths instead of mlids is wrong. Something like that does not exist. Only the menu system's menu router to menu link conversion logic is involved at some point, but that happens way before the menu tree is built. Ultimately, however, exactly this conversion is also the cause for further trouble, since administrative menu links are _not_ automatically updated/maintained anymore if site administrators are renaming/changing/moving administrative links. As soon as a menu link gets the 'customized' flag, the menu system is no longer able to maintain its parents, children, and properties. It's also no longer localized, even if it's just moved elsewhere. Plenty of reasons to entirely revamp the menu router, menu links, and menu tabs potentially separately, but perhaps for other reasons than yours, I'm not comfortable with the proposed solutions yet.

donquixote’s picture

Ultimately, however, exactly this conversion is also the cause for further trouble, since administrative menu links are _not_ automatically updated/maintained anymore if site administrators are renaming/changing/moving administrative links.

This is true, but: The reason for this conflict is not the menu_router to menu_links conversion. The real cause is the mix of hardcoded and hand-built structure.

In some way modules need a possibility to indicate menu tree locations and menu link titles for their configuration pages. In D6 and D7 this happens via hook_menu, and in D8 there might be a separate hook_menu_links. Or it could all be in hook_install, where modules would have to do things like menu_link_save() etc (which would seriously suck).

Think of display suite, or ubercart. Each of them have a very deep menu structure, which has to be defined in some way. And you seriously don't want a user to click around and build this structure herself.

So, let's assume this would all be in hook_menu_links() or in hook_install or something else. Now there can still be a user who wants to do his customizations via the menu links backend. We get the same conflict that we had with hook_menu in D6. Nothing changed. Code vs configuration.

Any solution we can find to solve this conflict will probably work just as well with old hook_menu.

My own solution to the problem is to never configure system nav or admin menu via the backend. And I imagine that most other people do the same.

Therefore, the presumption that there would be a valid menu link tree that's keyed by paths instead of mlids is wrong. Something like that does not exist.

I am aware of that.
Nevertheless, there exists a structure that the tree of menu links is based on. This structure lives in code, and code does not know about mlids.

I understand why the tree has to be keyed by mlid. On the other hand, every implementation of hook_admin_menu_output_alter will have to look for paths, if it wants to target specific items (as you can see very nicely in the "not a patch" in the other issue).

sun’s picture

All I can remotely imagine for this issue would be to introduce helper functions that aid in walking or altering the menu link tree, or rather, renderable array. That said, the new helper functions introduced for D7 over in #763376: Not validated form values appear in $form_state will likely help already and may be simply forked/backported to D6, if required.

donquixote’s picture

I think the helper functions in the attachment above and in the other issue are quite useful already.

sun’s picture

Title: Thoughts about the new hook_admin_menu_output_alter() » Simplify the structure of 'menu' in $content of hook_admin_menu_output_alter()
Version: 6.x-3.0-alpha4 » 7.x-3.x-dev
Component: Documentation » Code
Priority: Normal » Major
Status: Postponed (maintainer needs more info) » Active
Issue tags: +D7 stable release blocker

Patches like #1097312: "Tasks" and "Index" links appear on the top-level reveal that it's a pain to work with the menu link tree keys/structure provided by Drupal core.

We had to change it from $mlid keys to the full original menu tree keys in 7.x-3.x.

My current plan would be to replace $mlid with the $href of a link. Of course, it's a pretty wild assumption to believe that every $href is unique, but I do believe that one $href should be unique on a single tree level of the menu.

donquixote’s picture

If you want, you can look into dqx_adminmenu, which has always done it like that. This also allows to do modifications like "re-add create content" via the api, instead of being hardcoded in the menu building code.
Not sure if this helps.
(the D7 version is very rough-edge atm)

sun’s picture

So did you experience any kind of hiccups or pitfalls with keying by href?

Of course, it would be lovely if this change would also mean to get one step closer to making that dqx fork obsolete ;-)

Lastly, also note that #502500: Re-add Create content finally bit the dust.

donquixote’s picture

I did not experience any hickups, but this is mainly because dqx follows an entirely different philosophy.

Most importantly, it does not do anything with menu_links. It fetches directly from menu_router. As a result, if you work with the API, you do usually know quite well where the items are, because no use can mess with them.

It would be difficult to say, how well this works with the existing structure of admin_menu pulling from menu_links.

The main problem, I guess, is duplicate paths resulting in duplicate keys.
menu_router naturally delivers unique paths. Any duplicates can only come from plugins or, in your case, from users editing the menu.
In dqx, plugins can discard that "path = key" idea, and register their own custom submenus and custom links under made-up keys. Unless, they really want to override existing keys, in which case they will use the existing path/key.

Also notice the different way that the menu structure is kept in variables:
Instead of a deeply nested array, dqx uses two rather flat arrays:
$submenus, and $items.
The structure is all in $submenus, and basically that is a $parent_key => $child_keys map.
Yes, this could theoretically result in a loop, but it would be not too difficult to detect such a loop.

The good thing about these flat arrays is that plugins don't need to dive into a deeply nested structure.

Lastly, also note that #502500: Re-add Create content finally bit the dust.

I did notice :)

donquixote’s picture

Any duplicates can only come from plugins or, in your case, from users editing the menu.

I guess, duplicates can also come from the horrible menu_navigation_links_rebuild process. I remember seeing some duplicates in the admin/structure/menu. Or was it in themes?

For customized links, maybe the "customized" flag could be used as an indicator to key by mlid, or by href + mlid, whereas uncustomized links would be keyed by path. This would mean that any plugins can only target non-customized links.

The other approach would be to let the plugins kick in before menu_navigation_links_rebuild, so that any manual customization would be on top of that. (this is what admin_menu used to do in the past)

For my part, I once decided that any justified manual customization indicates a deficiency in the pre-built structure, and should rather be done by a plugin.

donquixote’s picture

Of course, it's a pretty wild assumption to believe that every $href is unique, but I do believe that one $href should be unique on a single tree level of the menu.

Menu rebuild will kill you when you sleep.

You need a "what happens if" strategy for duplicate keys, if you key by href or path, if you continue to fetch from menu links.
OR, you key by href + path, like this:
$items[$path][$mlid] = $item;
Then it would be up to plugin authors to decide which item they want to target.

sun’s picture

It appears that I just had to solve this for #1428584: Users expect "Add content" below "Content"

Is that what you had in mind?

sun’s picture

Status: Active » Fixed

Working with the new structure for another change clarified for me that using link hrefs as keys is 100% discoverable, alterable, workable, and intuitive.

Status: Fixed » Closed (fixed)

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

donquixote’s picture

Is that what you had in mind?

Hi,
sorry I didn't reply earlier.
I am going to comment, probably this is no different from what you were thinking yourself.

So what we do here is (simplified)

  • Fetch from menu_links, with mlid as keys (naturally), with user modifications
  • Use href as the key (actually the term href is misleading, better would be system path - but this is how the menu_links system names it. The structure is a deep nested array.
  • Apply some programmatic modifications, and mix with other href-keyed submenus.

I think this is fine, but for a user it will be possible to rearrange or modify the menu links in a way that the programmatic modifications no longer work.
For instance, changing the href of one the top-level items, or moving an item one level up or down, or into a different submenu - each of this can make the programmatic modifications stop working.

The assumption is that users modify the admin menu "at their own risk". I think that is fine.

sun’s picture

Title: Simplify the structure of 'menu' in $content of hook_admin_menu_output_alter() » Ensure reliable link structure and contents for alter hooks (to not break on customizations)
Priority: Major » Normal
Status: Closed (fixed) » Active
Issue tags: -D7 stable release blocker

Thanks, #29 clarifies an additional topic that is separate from the bare array keys. Better title.

If we find a solution for this (while retaining the ability to customize the menu), then that's going to be 4.x.

I've rewritten the summary.

sun’s picture

Issue summary: View changes

Replaced the summary.

truls1502’s picture

Issue summary: View changes
Status: Active » Postponed (maintainer needs more info)
Issue tags: +postponed2w

I am sorry for no reply until now.

There are many issues regarding this module admin_menu which is a bit difficult for us to follow up since some of the issues might be already outdated, or is already fixed by the module or any other modules or itself core which means that the problem might no longer need to be fixed.

We can see that the issue has been created for a few years ago, I hope it is okay for you that I am postponing the issue, and give you around two weeks. If you still face the problem, could you tell us the step by step when until you get the error message or what is frustrated you, and a list of modules you are using related to admin_menu and a screenshot that might help us? So it makes us easier to reproduce your issue.

However, after two weeks with no feedback - we will close this issue. So in case, you noticed it after the issue is closed, do not hesitate to reopen it like and fill information which is mentioned above.

So before giving us a feedback, do you mind to test it again with our latest 7.x-3.x-dev?

Thank you for understanding! :)

truls1502’s picture

Status: Postponed (maintainer needs more info) » Closed (cannot reproduce)
Issue tags: -postponed2w

This issue has been automatically marked as closed because it has not had recent activity after the last post.

However, if you or someone is still facing the same issue as described to the issue, could you please to re-open the issue by changing the status of the issue, and add an explanation with more details which can help us to reproduce your situation.

Again, thank you for your contributions! :)