Just got a nasty surprise after breadcrumbs/page title stop working at 8 levels, and after a bit of digging into the code noticed the menu system has a maximum depth set using MENU_MAX_DEPTH = 9.

Can anyone expand on the future of this, and is this going to be increased or made more dynamic in the future.

While nesting this deep seems crazy, a simple global regional breakdown quickly consumes these... I have estimated that I am going to need 14 levels on a current project.

Eg: Home › Regions › Australasia › New Zealand › Canterbury › Castle Hill Basin > ...

Thx in advance

Comments

pwolanin’s picture

Version: 6.2 » 6.x-dev

Do you really want to put all these levels in the menu? I'm not sure that's going to look very good in a block. Did you consider using taxonomy instead? You'd have to generate the BC (or maybe there is an existing module for it), but that would also give you term pages and such.

The hard limit is a tradeoff - every level requires an int column in the DB. So, you could potentially change this on your own install by adding more columns to {menu_links} (i.e. p10, p11, p12, p13, p14) and changing the MENU_MAX_DEPTH to match.

Whether 9 is the right number is subject to debate - it probably won't be changed in 6.x unless there is a groundswell of people hitting this limit. For 7.x, the possibilities are more open.

Alan D.’s picture

Version: 6.x-dev » 7.x-dev
Priority: Normal » Minor

Thx for the response.

I found very little on this when searching for the issue. I only discovered this after seeing that the title was not being correctly set on a couple deeper items. It actually took a bit of time tracking this down debugging from drupal_set_title to see what was the cause!

The menu is generated via a taxonomy, vocab/..../p2_tid/p1_tid/tid that has custom additions to make them pseudo-nodes with a blurb, and some custom fields indicated the level, ... Only the first and last items are actually used for the callback. The rest are for the breadcrumbs and path.

The menu is dynamically loaded via a drop down menu, with a secondary level that will be displayed via a custom view showing the next couple levels in a multicolumn list. I was hoping to do this without having to generate the custom breadcrumbs and title, thus the query about the depth. This is also related to SEO in that any number of the terms in the path could be used to search for the particular destination.

I had a quick look at menu.inc and I'm holding off hacking into it, but would prefer this to if path > 8, trim less important terms or if path > 8 set_breadcrumbs, ...

Bumping down the priority

pwolanin’s picture

ok - there are also a few queries that would need to be modified I think - at the least, the insert/update queries for links.

Alan D.’s picture

Thx Peter

I keep track of the changes and post these for others if they need them. Currently minor priority as many more features to implement in the next few weeks...

Alan D.’s picture

A quick investigation shows that the number is used very dynamically. The developers look extremely close to be able to define a user configurable max depth, and have everything else dynamically generated. Fingers crossed for the future.

For those that simply can not wait, try the following. Report back with any errors. :) This is for 15 levels.

THIS IS FOR DRUPAL 6.2 ONLY.

Update the menu links table: (Postgres users note the phpmyadmin special quotes)

ALTER TABLE `menu_links` ADD `p10` INT( 10 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `p9` ;
ALTER TABLE `menu_links` ADD `p11` INT( 10 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `p10` ;
ALTER TABLE `menu_links` ADD `p12` INT( 10 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `p11` ;
ALTER TABLE `menu_links` ADD `p13` INT( 10 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `p12` ;
ALTER TABLE `menu_links` ADD `p14` INT( 10 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `p13` ;
ALTER TABLE `menu_links` ADD `p15` INT( 10 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `p14` ;
 ALTER TABLE `menu_links` DROP INDEX `menu_parents` ,
ADD INDEX `menu_parents` ( `menu_name` , `p1` , `p2` , `p3` , `p4` , `p5` , `p6` , `p7` , `p8` , `p9` , `p10` , `p11` , `p12` , `p13` , `p14` , `p15` ) ; 

And the following patches for Drupal 6.2

<?php
### Eclipse Workspace Patch 1.0
#P drupal-6.2
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.255.2.11
diff -u -r1.255.2.11 menu.inc
--- includes/menu.inc 9 Apr 2008 21:11:44 -0000 1.255.2.11
+++ includes/menu.inc 27 Jun 2008 14:30:24 -0000
@@ -158,13 +158,13 @@
  /**
  * The maximum number of path elements for a menu callback
  */
-define('MENU_MAX_PARTS', 7);
+define('MENU_MAX_PARTS', 13);
 
 
 /**
  * The maximum depth of a menu links tree - matches the number of p columns.
  */
-define('MENU_MAX_DEPTH', 9);
+define('MENU_MAX_DEPTH', 15);
 
 
 /**
@@ -816,7 +816,7 @@
         SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
         FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
         WHERE ml.menu_name = '%s'". $where ."
-        ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
+        ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC, p10 ASC, p11 ASC, p12 ASC, p13 ASC, p14 ASC, p15 ASC", $args), $parents);
       $data['node_links'] = array();
       menu_tree_collect_node_links($data['tree'], $data['node_links']);
       // Cache the data, if it is not already in the cache.
@@ -880,12 +880,12 @@
             $args[] = '<front>';
             $placeholders .= ", '%s'";
           }
-          $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM {menu_links} WHERE menu_name = '%s' AND link_path IN (". $placeholders .")", $args));
+          $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14 FROM {menu_links} WHERE menu_name = '%s' AND link_path IN (". $placeholders .")", $args));
 
           if (empty($parents)) {
             // If no link exists, we may be on a local task that's not in the links.
             // TODO: Handle the case like a local task on a specific node in the menu.
-            $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['tab_root']));
+            $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['tab_root']));
           }
           // We always want all the top-level links with plid == 0.
           $parents[] = '0';
@@ -923,7 +923,7 @@
           SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
           FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
           WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .")
-          ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
+          ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC, p10 ASC, p11 ASC, p12 ASC, p13 ASC, p14 ASC, p15 ASC", $args), $parents);
         $data['node_links'] = array();
         menu_tree_collect_node_links($data['tree'], $data['node_links']);
         // Cache the data, if it is not already in the cache.
@@ -1145,7 +1145,7 @@
  */
 function drupal_help_arg($arg = array()) {
   // Note - the number of empty elements should be > MENU_MAX_PARTS.
-  return $arg + array('', '', '', '', '', '', '', '', '', '', '', '');
+  return $arg + array('', '', '', '', '', '', '', '', '', '', '', '','','','','','','');
 }
 
 /**
@@ -1945,11 +1945,13 @@
     router_path = '%s', hidden = %d, external = %d, has_children = %d,
     expanded = %d, weight = %d, depth = %d,
     p1 = %d, p2 = %d, p3 = %d, p4 = %d, p5 = %d, p6 = %d, p7 = %d, p8 = %d, p9 = %d,
+    p10 = %d, p11 = %d, p12 = %d, p13 = %d, p14 = %d, p15 = %d,
     module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d",
     $item['menu_name'], $item['plid'], $item['link_path'],
     $item['router_path'], $item['hidden'], $item['_external'], $item['has_children'],
     $item['expanded'], $item['weight'],  $item['depth'],
     $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'], $item['p7'], $item['p8'], $item['p9'],
+    $item['p10'], $item['p11'], $item['p12'], $item['p13'], $item['p14'], $item['p15'],
     $item['module'],  $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']);
   // Check the has_children status of the parent.
   _menu_update_parental_status($item);
?>
<?php
### Eclipse Workspace Patch 1.0
#P drupal-6.2
Index: modules/menu/menu.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.admin.inc,v
retrieving revision 1.26.2.3
diff -u -r1.26.2.3 menu.admin.inc
--- modules/menu/menu.admin.inc 11 Feb 2008 15:12:53 -0000  1.26.2.3
+++ modules/menu/menu.admin.inc 27 Jun 2008 14:35:40 -0000
@@ -32,7 +32,7 @@
     SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
     FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
     WHERE ml.menu_name = '%s'
-    ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
+    ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC, p10 ASC, p11 ASC, p12 ASC, p13 ASC, p14 ASC, p15 ASC";
   $result = db_query($sql, $menu['menu_name']);
   $tree = menu_tree_data($result);
   $node_links = array();
?>

And not sure if needed, the system install. Guessing that this is only required if patched pre-installation. Anyone?

<?php
### Eclipse Workspace Patch 1.0
#P drupal-6.2
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.238.2.1
diff -u -r1.238.2.1 system.install
--- modules/system/system.install 8 Feb 2008 17:07:55 -0000 1.238.2.1
+++ modules/system/system.install 27 Jun 2008 14:38:36 -0000
@@ -928,6 +928,42 @@
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0),
+      'p10' => array(
+        'description' => t('The ninth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p11' => array(
+        'description' => t('The ninth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p12' => array(
+        'description' => t('The ninth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p13' => array(
+        'description' => t('The ninth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p14' => array(
+        'description' => t('The ninth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p15' => array(
+        'description' => t('The ninth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
       'updated' => array(
         'description' => t('Flag that indicates that this link was generated during the update from Drupal 5.'),
         'type' => 'int',
@@ -940,7 +976,7 @@
       'menu_plid_expand_child' => array(
         'menu_name', 'plid', 'expanded', 'has_children'),
       'menu_parents' => array(
-        'menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+        'menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'p10', 'p11', 'p12', 'p13', 'p14', 'p15'),
       'router_path' => array(array('router_path', 128)),
       ),
     'primary key' => array('mlid'),
@@ -1681,12 +1717,18 @@
       'p7'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
       'p8'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
       'p9'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p10'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p11'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p12'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p13'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p14'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p15'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
       'updated'      => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
     ),
     'indexes' => array(
       'path_menu'              => array(array('link_path', 128), 'menu_name'),
       'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'),
-      'menu_parents'           => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+      'menu_parents'           => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'p10', 'p11', 'p12', 'p13', 'p14', 'p15'),
       'router_path'            => array(array('router_path', 128)),
     ),
     'primary key' => array('mlid'),
?>

Currently I have only tested on a menu that is generated from a hacked taxonomy menu, but the things are working well so far.

A word of warning about the YUI Menu module. This will crash if you run this on a larger menu system, maybe unless you have a real fast server with heaps of memory to spare. The way that they generate the menu involves multiple recursive menu get tree calls. 15 levels and 250 items simply killed my install until I disabled it.

The book module will also require patching if you use this. I don't so I didn't attempt it.

It would pay to search for the three terms to make sure that other modules don't require modifications: "MENU_MAX_PARTS", "MENU_MAX_DEPTH", and "p4"

Cheers

pwolanin’s picture

Don't change MENU_MAX_PARTS! That's totally unrelated to the links.

Also, there is probably a query or two in book module that would need to change.

Alan D.’s picture

If MENU_MAX_PARTS is not changed, the breadcrumbs and page title don't get set to the final menu item. They get chopped off at 7 items:

For example the page "http://rctopos6/region/4/17/20/35/42/46/50/52/72/73/74"

points to

"Home › Regions › Australasia › New Zealand › Canterbury › Castle Hill Basin › Flock Hill Station › Flock Hill › The Far Side › The Figurehead Group › test 10 › test 11 {title}test 12"

But changing MENU_MAX_PARTS back to seven cause the default menu created items to fail:

"Home › Regions › Australasia › New Zealand › Canterbury › Castle Hill Basin › Flock Hill Station {title} Flock Hill"

Being 2am after a long week, I'm not reading the router bits that clearly, but the path needs to be split into the length that these elements require? Doing a test of a full 15 items deep, I had to change this to 15 as well without strange things happening to the menu.

The taxonomy menu generates the items like this:

<?php
      $items['region/4/17/20/35/42/46/50/52/72/73/74'] = array(
        'access arguments'    => array('access content'),
        'file'            => 'taxonomy_menu.inc',
        'page callback'   => '_taxonomy_menu_page',
        'title'           => t($vocab->name),
        'weight'          => $vocab->weight
      );

?>

thus the strong coupling between the two maybe? This is my first look at the menu system outside of generating menu items.

pwolanin’s picture

Right, MENU_MAX_PARTS controls how many elements a path in the router has.

If you are actually making paths like this - I think you are doing something wrong, and are potentially going to have performance problems.

Note - you SHOULD NOT, NOT, NOT - be putting items in the router just to get a link. They are decoupled - items in the router should define page callbacks. What you are doing above will mean your PHP will quickly exhaust its memory.

use the menu module interface - or in code with menu_link_save().

Alan D.’s picture

Thx Peter

I rewrote my already modified taxonomy_menu module to take into account your warning. I think I only nuked the installation 8 or 9 times before getting things right.

menu_link_save triggered a recursive loop from hook_menu, so I assigned the custom menu tree creation to a registered shut down function via register_shutdown_function. This brings up the need for a post-build hook, but there are only 4 or so items whose router path differs from their link_path, so that'd be a bit of over-engineerring I think ;)

pwolanin’s picture

why in the world would you call menu_link_save from hook_menu? Obviously my explanation above was not clear. I'd call it from hook_taxonomy or some such if that's what you are trying to achieve.

Alan D.’s picture

That was simply how the tax_menu had done it processing. A link similar to reset to alphabetical would probably make more sense with hooks on insert and delete operations. I have had problems with stale data when saving data via hook_taxonomy when moving things around via the dnd list terms page. Different project, but similar need to keep track of the exact hierarchical nature of the items. Well, maybe not stale, but only certain items triggered the update operation, so sections in the chain didn't have their paths updated.

bty, the increase in the menu_max_depth is working great so far.

pwolanin’s picture

@Alan D. - if it was doing any such thing in hook_menu in D6, it's broken and incorrectly updated from D5.

Alan D.’s picture

I meant to say "where" not "how". This is where they generated the router links for the entire tree, which I modified and added (incorrectly) the menu set links

pwolanin’s picture

right, but if they are adding lots of router items for the one existing callback, that's incorrect. They should use func_get_args in this case.

also, there should be no t() in there.

I'd suggest you file a bug report against that module - I would not use it if that's how it's working.

steve.colson’s picture

I would just like to chime in to say having a statically set maximum is quite contrary to the whole point of a flexible system like Drupal to begin with. Who is anyone here to say what anyone else's business needs are with regards to their information architecture and how to display it? There will always be another site where they need MAX_MENU_DEPTH + 1. If you're that worried about performance, you could easily serialize an array of menu parents and have a single 'p' column; that said, complex and large menu structures slowing down a system is what caching is for.

ainigma32’s picture

Status: Active » Fixed

Can anyone expand on the future of this, and is this going to be increased or made more dynamic in the future.

Looks like the answer was given - no and no - so I'm setting this to fixed.

Feel free to reopen if you want to pursue this any further.

- Arie

Status: Fixed » Closed (fixed)

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

natuk’s picture

Priority: Minor » Normal
Status: Closed (fixed) » Active

I am sorry to reopen this after a month but I, too, think that such a limitation on the number of levels is unreasonable. In more than one cases of Drupal installations I need a lot deeper hierarchies and have been stuck to D5 because of this. I agree with stephen.colson about alternative ways of achieving performance.

ainigma32’s picture

No problem, like I said: "Feel free" ;-)

I would suggest you also take this up on IRC and/or the developers mailing list.

- Arie

ainigma32’s picture

Status: Active » Postponed (maintainer needs more info)

@natuk: Any progress on this?

- Arie

ainigma32’s picture

Status: Postponed (maintainer needs more info) » Fixed

Looks like natuk won't be posting any feedback so I'm setting this to fixed.

Feel free to reopen if you think that is wrong.

- Arie

Alan D.’s picture

Category: support » feature
Priority: Normal » Minor
Status: Fixed » Active

Hi Arie

I've bumped this back to active, just so the issue isn't forgotten about, and any future development of the menu system may allow this issue to be resolved.

I am fairly sure that this will only ever effect < 0.1% of users, so setting priority to minor. It would be nice but there are workarounds - like manually creating deep menus and storing the structure in the taxonomy.

natuk’s picture

Sorry for the delay in following this up. I have posted a message in the developer's list:

http://lists.drupal.org/pipermail/development/2009-February/031875.html

I got a couple of answers which I think confirm my scepticism on this limitation. I was pointed to this thread:

http://drupal.org/node/344019

as a possible way to resolve the limitation and sort out the tree-structuring in drupal in general. So I too agree that the issue should not be marked as fixed, but keep it open until the limitation is shifted, hopefully with this new proposed implementation for hierarchies.

greenSkin’s picture

I'm am developing a module that I need a bigger menu depth for. Is there a way currently for a module developer to programmatically increase the MAX_MENU_DEPTH? I'm looking to also keep the dynamic menu breadcrumbs intact.

My max menu depth would look something like this:
'admin/{module}/accounts/%/folders/%/contacts/%/edit'

Alan D.’s picture

It requires a core hack, I made the depth 15 for a project that required it, see above.

Firstly, do you really need it?

"{module}/accounts" could be reduced to just a single item "{module}_accounts", and if the contracts are assigned to accounts, have you thought about "accounts/%/contracts?folder=xxxx"

There is another issue with load arguments that you may need to know about, these need to be passed down to every child router item after being used for an item, (for "project" in the example below), and all wildcard loaders need to share the same arguments as these can only be defined once.

For an inhouse cmr, our router paths were something like this:

<?php
  $items['projects/%project/job/%job/work_entries/%work/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'load arguments' => array('%map', '%index'),
    'weight' => -10,
  );

function job_load($nid, $map, $index) {
  $project = $map[1];
  if ($node = node_load(array('nid' => $nid))) {
    if ($node->status && $node->type == 'job' && $project->nid == $node->field_project[0]['nid'] && node_access('view', $node)) {
      return $node;
    }
  }
  return FALSE;
}
?>
greenSkin’s picture

I would really like to maintain the menu structure as much as possible to the example I gave. I could change '{module}/accounts' to '{module}_accounts' but feels messy and it only brings me down to 8 elements whereas I need to be at no more then 7. The path workflow would really work best by following the 'admin/{module}/accounts/%/folders/%/contacts/%/edit' structure. Each account can have multiple folders and each folder can have multiple contacts, so this is the best way to manage them. I use the tabs 'list' and 'add' for each the accounts, folders, and contacts pages which is really the main issue for the max menu depth for me.

Also @Alan, I didn't quite understand what you meant about an issue with load arguments, if you could elaborate or point me to another issue that talks more in-depth about the issue.

Sorry, I'm off topic, I should start a separate issue. I would like to see there not necessarily be a limit of elements in the menu router unless it can be changed programatically. I understand something is in store in Drupal 7 where this is not an issue?

Alan D.’s picture

@greenSkin That was just an aside that gave me headaches when having multiple loaders in a nested menu router path. Hope these give more insight, but these are not an issue for standard wildcard placeholders, just the wildcard loaders, see http://drupal.org/node/316971 & http://drupal.org/node/367343

sun.core’s picture

Version: 7.x-dev » 8.x-dev
Jaypan’s picture

Bump

Not sure if this is changed in 7 or not, but it should be. It's causing me headaches right now. I see no valid reason for limiting us on the depth. I have a situation right now where I need 10.

CKoch’s picture

Is there any means of neatly updating the menu limit that would work? We've hit this limit many times, and having to fake local-tasks in the heirarchy for the depth travelsal is has hit a number of projjects.

pwolanin’s picture

In order to remove the limit we would need to totally rewrite the hierarchy handling. This will almost certainly not happen for Drupal 7, which is why this is flagged for Drupal 8.

Are you coming to Drupalcon and participating in the core conversations?

Can you commit to working on making such a change work and doing performance and other tests?

wjaspers’s picture

Why not just throw out the p1, p2....etc implementation on the menu_link tables. It's totally illogical, and limiting to boot.

Just create a few weak entities (table....not a drupal entity), and compile the menu link with both chunks of data where required:

menu links

  • menuid (menuname) [PRIMARY KEY#]
  • mlid (menu link id) [PRIMARY KEY#]
  • link_path
  • router_path
  • link_title
  • options
  • module
  • hidden
  • external
  • expanded

menu_structure

  • menuid (menuname) [FOREIGN KEY#]
  • mlid (menu link id) [FOREIGN KEY#]
  • plid (parent link id) [FOREIGN KEY]
  • weight

NOTE: the # means make this a compound key. If you really want to get creative, push an md5 or sha1 hash as an extra field onto the end of menu_links, and use that at the foreign key in menu_structure.

In this way, its drastically easier to find link siblings, parents, and children, weights are managed properly in EACH relationship, and, depending on how the key structure is defined in (menu_structure), menu items can have multiple parents.

casey’s picture

subscribe

wjaspers’s picture

Thinking a little more about #32, if Menu's were entities, we could construct things with hierarchy and make them field-able. Other thoughts?

pwolanin’s picture

@wjaspers - please describe in detail the algorithms you would use for retrieving and manipulating tree structures from SQL, and compare their efficiency to the implementation we have now.

wjaspers’s picture

I guess I can't explain an algorithm behind it, but, here goes:
Taking the tree out of the single table design opens menus to deeper capacity.
They become easier to manage.
They become easier to query.
The table design is easier to understand.

Most importantly:
Less overhead is required to build out a single menu because fewer columns are required per tuple (row) of the database.
(If I can find the article, one of the folks over at MySQL (before Oracle bought MySQL AB) explained how much faster indexes work when there aren't a slew of them on just one table.

Is there efficiency information available in the current Drupal doc?

Alan D.’s picture

Sadly the tree structure has it draw-backs too.

For example, what sql would you use to generate a tree of the menu items? This normally is done via "select level x", then foreach item "select level y where pmid = x". So you need to do hundreds of i/o operations which are terribly slow....

I'd love to hear alternatives for getting the entire tree efficiently, as I'm implementing this model in a regional database ATM. When displaying a tree of only a few thousand items makes my processor shudder as it goes into overdrive!

pwolanin’s picture

We researched several alternative algorithms when we first wrote this code. What we have is a variant of the materialized path, where we are using columns for each path element instead of a string representation.

So, the schema we have now is very efficient for selecting trees and sub-trees as well as for getting partially expanded tress as we show in menu block. But it has a fixed depth limit. that's the trade-off.

Since this is designed for navigation, having a site structure more than 9 levels deep seemed like bad for UX in any case. One also has to consider the size of the SQL index being generated.

EclipseGC has an alternative hierarchy system with an extra table of ancestor relationships, though I'm not sure how well that performs or scales, nor whether it's easy to relocate a sub-tree.

tedbow’s picture

I created a related issue to document the path parts limit in hook_menu docs. #1983832: Document the limit of 9 parts for paths

YesCT’s picture

Gábor Hojtsy’s picture

Issue tags: +WSCCI, +D8MI, +language-config

So looks like we need to rely on custom GET arguments instead of path parts on the core proposed config translation module due to this limitation. Any chance a fairy is working on this one? :)

fubhy’s picture

There is a good chance that we can remove the p1, p2, p3, p4, ... stuff from menu links by adding a generic solution for hierarchical data. I am particularly peering at either a Nested Sets or a Closure Table solution. Both have their advantages and disadvantages. @amateescu/@DamZ/@fgm got some code for D7 involving entity reference (it's the "Tree" module) which we could probably introduce to D8. Not sure how that would comply with the API freeze rules though... Gabor?!

At the moment it looks like menu links are really the only thing that block the removal of that MAX_PARTS barrier. Routing does not actually care about the number of parts/does not really need a limit. All it cares about is the number of parts. So unless I am mistaken or missing something here we could solve all this by re-factoring our hierarchy solution for menu links.

fubhy’s picture

I persuaded @amateescu to pick up his work and comment about his work on a unified solution for hierarchical data structures: #1915056: Use entity reference for taxonomy parents

Battleplan included.

Gábor Hojtsy’s picture

The only way to get a tab is to have a menu link via hook_menu, so although you can define a 10+ component path, you cannot expose it as a tab.

fubhy’s picture

You are looking for this issue then: #2004334: Separate Tabs (MENU_LOCAL_TASK) from hook_menu()

@pwolanin wrote a patch that makes local tasks (tabs) plugins that target route names instead of hook menu entries/menu item paths. I hope we can get that committed soon.

Crell’s picture

Issue summary: View changes
Issue tags: -WSCCI

This doesn't need to be WSCCI, since menu and routing are now distinct.

dawehner’s picture

Well, due to the fact that there is still a max router depth, its still relevant, IMHO.

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.

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.

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.

pwolanin’s picture

There is no router max depth now is there?

mstrelan’s picture

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

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

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

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

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

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

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

wombatbuddy’s picture

Just in case, if someone needs a menu with unlimited depth, than you can use 'Hierarchical Taxonomy Menu' module.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

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

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

afsch’s picture

I've been dealing with this menu functionality and found a temporary solution that could help other developers.
The requirement here was to limit the menu depth to 5, in case this is not valid then show an error message. This validation works for add/edit menu item via Menu form and Node form.

Here is the code:
File: `my_module.module`


use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_alter().
 */
function my_module_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if (array_key_exists('menu_parent', $form) || array_key_exists('menu', $form)) {
    $form['#validate'][] = '_my_module_validate_menu_depth';
  }
}

/**
 * Helper validation function for menu item depth.
 */
function _my_module_validate_menu_depth($form, FormStateInterface &$form_state) {
  $values = $form_state->getValues();

  // This condition is for Menu Link form.
  if (array_key_exists('menu_parent', $values) && str_contains($values['menu_parent'], 'menu_link_content')) {
    $parent = $values['menu_parent'];
    $field_name = 'menu_parent';
  }
  // The case for Node form.
  elseif (array_key_exists('menu', $values) && str_contains($values['menu']['menu_parent'], 'menu_link_content')) {
    $parent = $values['menu']['menu_parent'];
    $field_name = 'menu][menu_parent';
  }
  else {
    return;
  }

  // Gets the depth of the current entity.
  $deep = _get_menu_item_depth($parent, 2);

  if ($deep > 5) {
    $form_state->setErrorByName($field_name, t('Nested menu items cannot be more than 5 levels deep.'));
  }
}

/**
 * Helper function to get the current menu_item deep.
 */
function _get_menu_item_depth($parent_id, $counter = 1) {
  $split = explode(':', $parent_id);
  $uuid = end($split);

  /** @var Drupal\menu_link_content\MenuLinkContentStorage $menu_content_storage */
  $menu_content_storage = \Drupal::entityTypeManager()->getStorage('menu_link_content');

  $connection = \Drupal::service('database');

  $result = $connection->select('menu_link_content', 'm')
    ->fields('m', ['id'])
    ->condition('m.uuid', $uuid)
    ->execute()
    ->fetchAll();

  $link_id = $result[0]->id;

  /** @var Drupal\menu_link_content\Entity\MenuLinkContent $menu_link */
  $menu_link = $menu_content_storage->load($link_id);
  $parent_id = $menu_link->getParentId();

  if ($parent_id !== '') {
    $counter++;
    $counter = _get_menu_item_depth($parent_id, $counter);
  }

  return $counter;
}

Related ticket https://www.drupal.org/project/drupal/issues/2302043

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

klonos’s picture

Should we move any comments that are still relevant in this issue over to #2302043: Make the maximum menu depth configurable and then close this one as duplicate/outdated?

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.