Problem/Motivation
In Drupal 5, it was possible to arbitrarily set the active trail using the menu_set_location() function. That is, you could place a View at /services/events that listed all nodes of type event, and then make all of your event nodes appear as if they lived at /services/events/*. That would in turn affect what menu paths in a menu block are open, what the breadcrumbs are, etc. without having thousands of menu entries or having each of those event nodes showing up in the expanded menu in addition to the parent page.
After the menu API was redesigned in Drupal 6, this was no longer possible. From first glance, it would seem as if menu_set_active_trail() should do that, but it actually only affects breadcrumbs, essentially making it a duplicate function of drupal_set_breadcrumb(). :p
Proposed resolution
There's no API that can be "fixed" to allow this essential functionality, so we need to add some very, very small API additions, menu_tree_set_path() and menu_tree_get_path() which can store/retrieve an optional single url path per menu. Then menu_tree_page_data() would need a 3 line change to check if someone has set a path for the menu tree it is building.
In order to make creating tests for this new API easier, a new test class was created called MenuWebTestCase that takes code out of the existing MenuBreadcrumbTestCase and generalizes it.
The entire patch is 10 lines of new API code, 3 lines of alterations to existing code, and a metric ton of new documentation and breadcrumb tests refactoring.
Remaining tasks
After this patch is backported to Drupal 7, D8 should consider removing menu_set_active_trail().
API changes
These two new functions are added. Full documentation for them is in the patch.
Set the path for determining the active trail of the specified menu tree.
menu_tree_set_path($menu_name, $path = NULL)
Get the path for determining the active trail of the specified menu tree.
menu_tree_get_path($menu_name)
Original report by Crell
In Drupal 5, it was possible to arbitrarily set the active trail using the menu_set_location() function. That is, you could place a View at /services/events that listed all nodes of type event, and then make all of your event nodes appear as if they lived at /services/events/*. That would in turn affect what menu paths in a menu block are open, what the breadcrumbs are, etc. without having thousands of menu entries or having each of those event nodes showing up in the expanded menu in addition to the parent page.
In Drupal 6, with the menu API redesign, that is no longer possible. The only way to force a menu location is to actually have a real entry in the menu_links table. I discussed this issue at length with chx, and the best suggestion he had was to create a new menu item programmatically that's flagged as "hidden" (which is apparently an undocumented flag for menu_link_save()) for each possible page that we want to "dynamically" place in the tree. That means that if you have 100,000 event nodes, you'll have 100,000 records in the menu_links table. There is also the challenge then of dealing with pages that are not nodes, and therefore do not have a hook_nodeapi equivalent to manage such links; that in turn means you're populating menu_links entirely manually with no way to extend or prune it dynamically as content changes, which is a serious problem.
chx also indicated that it may be possible to hack core and inject a new alter hook into menu_tree_page_data() after the first $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4 ...")); line, which could then be altered per URL to simulate, in the menu cache, a page having parents that it really doesn't.
Neither of these is really an acceptable solution, and this is a major, serious functionality regression from Drupal 5. I don't think it's responsible to release Drupal 7 until we have a good, real solution again. (Unmanaged thousands of links or tricksy, funky alters do not count as a good, real solution.)
There is the menu_set_active_trail() function, but after walking through it in code and further discussion with chx, we concluded that nothing actually makes use of that active trail except for breadcrumbs. Breadcrumbs also respond to drupal_set_breadcrumb(), however, so those two functions are effectively duplicates of each other in practice. That's actually quite bad, since aside from being confusing menu_set_active_trail() implies with all its heart that it affects anything that involves the active trail in the menu, when in fact it doesn't actually do anything except affect breadcrumbs (which are screwed up to begin with in Drupal 6).
My gut instinct, therefore, is to make menu_set_active_trail() actually useful and have menu-related code rely on that rather than on menu_tree_page_data(), but I don't know the menu system well enough to say if that's the most appropriate solution. I just know that it is critical that we find a solution, as right now there are things that were trivial in Drupal 5 that are impossible in Drupal 6/7.
Comments
Comment #1
damien tournoud commentedmenu_set_item?
Comment #2
chx commentedThis goooblybegedook caused the functionality to be missed from D6.
You want the menu open as if the current page would have a link under a certain parent.
That's all.
Comment #3
davyvdb commentedhttp://drupal.org/node/545052
Comment #4
Crell commentedAssociating a node with a menu item is only part of what was missing, so this is not a dupe.
Comment #5
damien tournoud commentedIn a hook_init():
I don't see any drawback to this, but maybe there are some.
Comment #6
Crell commentedDoesn't that require that there is already a menu item to be loaded?
Comment #7
Nick Lewis commentedRE #5 -- I do, its a really strange way to accomplish a common need.
Comment #8
moshe weitzman commentedknown workaround. can't be critical.
Comment #9
Crell commentedWhat known workaround? Does DamZ's suggestion even work? A major regression in a core subsystem is not minor. I still consider this critical, but I'll meet you half way.
Comment #10
davyvdb commentedDamien's solution works great. I use it on pretty much every site I'm rolling out these days. It is kind of a weird solution though, I know. I got it from the menu trails module.
Maybe the menu system should support something like this with a cleaner solution, but for now I don't think this is a bug and isn't really a feature request since we can do it.
Comment #11
kiphaas7 commentedsub
Comment #12
Nick Lewis commentedNo, this is only a very partial workaround -- and its one that only seems to work under the simplest conditions.
For one reason or another, hook_nodeapi(when $op = 'view', $page = TRUE) is where I remember it working best.
I think this because that state is relatively late in the page building processing so modules that may have tried to influence the active trail beforehand are overruled. There are contribs that attempt to deal with it -- but they only work for node paths -- and only half the time.
I actually think this is still a critical bug. However, I won't touch the status until I investigate it again in d7, and can provide an actual analysis based on code. My guess is that a correct solution may involve a hook since menu trails are so sensitive to what stage in the bootstrap -> page render process you are in especially given that many pages (e.g. page manager in panels) are not quite the same pages you'd assume.
Update: Okay -- more info. Panels node overrides were broken for a mysterious reason (very common when using this technique). This ended up being the solution. Pretty ugly -- but there's no alternatives known.
They have a similar work around for OG. If there's a better hook that works then hook_init -- i haven't found it.
Comment #13
Crell commentedGiven how easy this was in D5 and D6/D7 has only a mostly-working ugly work around, I consider this still a critical regression bug.
Comment #14
Nick Lewis commentedWas looking into this.. set up a menu:
-node/1
--taxonomy/term/2
---node/3 [this is our test of correct behavior]
Used the alleged workaround to set taxonomy/term/2 as the parent in nodeapi from node/2/view
Sometimes you gotta post images to really show how f#cked things are.
1. Okay, so picture one is the correct behavior where a node is really a term page's parent. Note the breadcrumbs.
2. Picture two is the behavior you get when you do this hack. Note the incorrect breadcrumb. The breadcrumb thinks its actually on taxonomy/term/2 not node/2 -- and its quite correct about that. After all we set an active menu item, not the parent of the current item! And there's no way to set the parent!
3. Picture three is a total WTF -- that's the actual page that picture 2 pretended to be. Note the breadcrumb is busted because taxonomy_term_page sets its breadcrumb in the page callback.
So no, we cannot set a term parent, we can only expand a menu tree by pretending to *be* the parent item. This behavior is total WTF. I agree with crell: setting it back to critical ( a regression from d5 to d6).
Note that Chx said this all along: http://drupal.org/node/520106#comment-1814730
Comment #15
bdragon commentedsub
Comment #16
Nick Lewis commentedSo after speaking with Crell on IRC we came full circle to a place in the code that chx had happened to mention earlier: http://api.drupal.org/api/function/menu_tree_page_data/6
My view is that the main problem with menu_set_active_trail is its name not that it doesn't work. I'm cooking up a solution that involves a hook ( a hook because this function fires and forgets and other functions cannot influence its return value at a certain point).
The goal is to make the functions that receive data from http://api.drupal.org/api/function/menu_tree_page_data/6 believe that an items in the database when in fact its not. Must. Find. Chx.
Comment #17
Nick Lewis commentedThis patch merely shows two methods i'm experimenting with for setting a menu parent dynamically. This approach could be madness, so please review the place I'm setting the trail.
While menu_set_dynamic_parent($system_path) (not set on the name!) looks better from a DX standpoint, I actually lean more towards the hook method. The main reason being that menu_set_dynamic_parent() might seem like it could be called anywhere, and that's not at all the case. If we use a hook then themers won't be tempted to confuse themselves while looking up docs and doesn't require you understand how the menu is built from start to finish.
One thing to note is that this doesn't alter the breadcrumb trail unlike the hack method. I'm investigating that as well...
Comment #18
Nick Lewis commentedTwo bugs are quite related -- unclear yet whether they dupe since breadcrumbs are currently separate from the mechanism that apparently decides whether an active trail expands a menu tree:
#689890: Breadcrumbs don't try hard enough.
#576290: Breadcrumbs don't work for dynamic paths & local tasks
Comment #19
sunsubscribing
Comment #20
Nick Lewis commentedMarking this issue as a duplicate since a one line modification to sun's patch: #576290: Breadcrumbs don't work for dynamic paths & local tasks allows a technique to set the node's trail to the admin menu with both menu tree and breadcrumbs synced perfectly. [see: http://drupal.org/node/576290#comment-2515376]
Comment #21
Nick Lewis commentedWe'll have a fix once #576290: Breadcrumbs don't work for dynamic paths & local tasks gets through.
Comment #22
nicolash commentedsub
Comment #23
AdrianB commentedSubscribing.
Comment #24
drifter commentedsub
Comment #25
andypost.
Comment #26
Nick Lewis commented#576290: Breadcrumbs don't work for dynamic paths & local tasks passed tests for the first time and needs someone less obsessed with this issue than me to RTBC it or at the very least give a "I understand what's going on, and I see the light that this patch sheds." This bug will resolve quickly if someone is qualified, and willing to do that.
Comment #27
kestes commentedSub
Comment #28
sunComment #29
yesct commentedIn preparation for the new "major" issue priority, I'm tagging this (not actually critical, but still important) issue as priority-major.
See: http://drupal.org/node/669048#comment-3019824 (#669048: We should have four priorities not three (introduce a new 'major' priority)) for more information about the coming major issue priority.
Comment #30
johnalbinOk, here's a first-draft, guaranteed-to-not-work patch.
The basic idea is that since menu_set_active_trail() is mostly useless, we should make it the go-to function when you want to alter the breadcrumbs and also the active trail used for trees generated by menu_tree_page_data(). The patch also adds a hook_menu_tree_alter() that allows alteration of the tree if you want to insert/modify items to the tree before node access and before it gets baked into the cache.
Comment #32
plachComment #33
greenc commented+1
Comment #34
johnalbinAnd here's a patch without the PHP parse error. I told you it wouldn't work! ;-)
Also updated the new API docs.
Comment #36
drifter commentedrerolling #34, I'm not getting as many failures when running the tests locally
Comment #38
drifter commentedJohnAlbin: there are lots of 'Undefined index: localized_options' failures in the tests. When menu_get_active_breadcrumb() calls menu_get_active_trail, $active_trail seems to get populated with a mix of both menu links and router items. Router items don't have a localized_options key.
I tried to go further but menu_tree_page_data() makes my head hurt. Can you describe your approach in more detail?
Comment #39
mdeltito commentedsubscribing
Comment #40
justin2pin commentedsubscribing
Comment #41
lotyrin commented#36: menu-set-active-trail-520106-34-reroll.patch queued for re-testing.
Comment #43
marcingy commentedChanging to major as per tag.
Comment #44
mustanggb commentedTag update
Comment #45
kebap commentedsubscribe
Comment #46
alfaguru commentedI hope very much this will get fixed for D7. I think the concept of a single active trail is not ideal, though, and it would be better to be able to set multiple contexts for the current item. I admit I have spent no time at all considering the practicalities, but I think a node/view/panel should be able to have multiple parents - different vocabularies, site sections and so on.
A useful if horrible trick for D6 I found that's a bit easier to use than the ones mentioned is to do this:
$original_path = $_GET['q'];
$_GET['q'] = --- new path to be the desired context --
... generate menu ...
$_GET['q'] = $original_path;
You can call this in a custom block, wrapping a call to the menu_block, or at the theming stage. One advantage is that it can be done multiple times as needed.
Comment #47
grendzy commentedhm, the menu_set_active hack in #5 appears to not work in D7 anyway. If this is true, it might merit raising the priority to critical again. [EDIT - nevermind, I think I was misled by a cache somewhere. Calling menu_set_item() in a hook_init() still works.]
I'm not sure I understand how this patch solves the issue - could someone post a summary? How does the proposed hook_menu_tree_alter() relate to the basic task of setting an active trail?
Comment #48
johnalbinHere's an updated patch.
BTW, this patch seems to require this issue to be fixed before all of its test failures shake out: #233807: No navigation links on 404 pages
Comment #49
sunLooking at this patch, it looks like it is possible that #907690: Breadcrumbs don't work for dynamic paths & local tasks #2 might resolve this issue to some extent, too.
This can too easily lead to infinite recursion, as menu_set_active_trail() calls back into menu_tree_page_data() for an automatically figured out $menu_name.
Furthermore, I suspect a major performance decrease with this change, since building the active trail is a very slow operation.
Powered by Dreditor.
Comment #50
johnalbinA performance decrease? The first time menu_get_active_trail() runs, it caches its result. And, even without this patch, menu_get_active_trail() is called on almost all page requests (since its the primary source for breadcrumbs), so this patch doesn't change that.
But perhaps you mean the way menu_set_active_trail() will work after #907690: Breadcrumbs don't work for dynamic paths & local tasks #2 lands. I'll have to reevaluate this patch after that lands since I agree that 907690 should land first.
Comment #51
drifter commented#907690: Breadcrumbs don't work for dynamic paths & local tasks #2 landed.
Comment #52
chx commentedIn the name of damage control and webchick's call to elevate issues to critical I am presenting this trivial patch which will allow you to set whatever active trail you want for the current menu tree. This is not the best solution we could invent as you need to figure out the plids yourself but it's too late for anything nicer.
Comment #53
andypostLooks reasonable, but how actually fixes? This hook would be called only once, so there's no context to detect actual trail to set
Comment #54
chx commentedThe context is the page. Enhanced docs and hook name 'cos webchick asked to do that.
Comment #55
pwolanin commentedIt's really unfortunate that we didn't give this a better architecture in D6
Comment #56
pwolanin commentedI'd wish to either rip out menu_set_active_trail() or hold out for a better architecture. chx is beating me down though.
Comment #57
tstoecklerTypo: "trial" -> "trail"
Doesn't qualify for a "needs work", but if someone happens to reroll...
Powered by Dreditor.
Comment #58
webchickI'd like to leave this a few hours for JohnAlbin to chime in. But this addresses my earlier (offline) feedback that hook_menu_tree_page_data_parameters_alter() is a ridiculous name, and the PHPDoc seems fairly clear now what this hook is for.
This patch is also technically edging into "feature" territory, as it were, since this architecture limitation also exists in D6. But if we're going to fix it in D7, now or never.
Comment #59
mark trappThat typo would plague me: I rerolled the patch to fix it. Marking as "needs review" just to trigger the testbot.
Comment #60
tstoecklerAs far as I know, the bot takes RTBC patches as well.
Comment #61
sun.core commentedCan we rename to hook_menu_tree_page_data_alter() or something? While we're indeed solving the issue at hand, this alter hook technically allows for much more, as you can tweak all tree parameters for any menu tree that is output.
Overall, two directions are possible: 1) Reduce the alter hook invocation to solve the issue at hand, or 2) Keep the current code but name it accordingly (and perhaps also provide means to identify whether we're actually dealing with the menu that contains the active trail... AFAIK, that's close to impossible in current HEAD without introducing a major performance decrease... which in turn makes me question the actual usefulness of this patch, sorry :-/ )
Powered by Dreditor.
Comment #62
webchickAgreed the hook name is not the best; we kicked around a few ideas and this was the closest.
We talked about naming it something more akin to hook_menu_tree_page_data_alter(). The only problem (which chx could elucidate on more) is that menu tree data is actually built in several places, and we're only allowing altering of one of them. Naturally, you might say, "Well let's move it up the stack to where the data gets built then" but we also talked about that, and the problem is that these other places don't get cached, where this one does. So the performance impact here is minimal, and it solves the bug (we think).
Comment #63
johnalbinI looked long and hard at this patch to see if it could remove the icky, icky cache-munging awefulness in the bowels of http://drupal.org/project/menu_position
And it looks like I can use the hook_menu_tree_active_trail() to add a $tree_parameters['active_trail'] array that forces the path to the one I want.
Over IRC, chx and I discussed how a much cleaner, unwritten, API-changing approach would be better, but it's way too late in the game for that.
So RTBC+1 from me.
Comment #64
webchickchx and I talked about this a bit more. This fix just really doesn't feel right; the fact that we're tossing an alter hook into this deep, dark corner of the menu system that only like 4 people on earth understand and exposing it to module developers gives me the heebie jeebies.
I'm not sure what a different approach would be, but I don't feel comfortable committing this patch unless it's truly our only option.
Comment #65
chx commentedSo we have jumped back to the original approach and JohnAlbin will write a patch that provides an item getter/setter that can be used in lieu of menu_get_item so we can do
if (($item = menu_tree_get_item()) || ($item = menu_get_item()))and then issue is simply solved by doing #5 without the side effects you do not want.Comment #66
johnalbinVery rough patch showing the direction I'm thinking this evening (not RTBC-worthy). Will come back to it in the morning. Comments requested.
Comment #67
chx commentedExactly what i suggested. Go!
Comment #68
chx commentedWe have survived three years with this and I am sure noone will be against adding such a trivial pair of functions to menu.inc especially given that it was here before RC just not 100% ready. Modules now have version dependencies. So, I am demoting from critical.
Comment #69
johnalbinHow about this? Now with simpletests.
Comment #70
johnalbinFixed the comment above MenuTreeTestCase.
Comment #71
sunPerhaps make menu_tree_get_item() default to menu_get_item() ?
This change needs a very good reason or should be reverted. menu_link_get_preferred() defaults to $_GET['q'] and is statically cached. $_GET['q'] can be different to $item['href']. Other callers do not pass a path.
Powered by Dreditor.
Comment #72
johnalbinAt first blush, this looks reasonable. However, menu_get_item() doesn't take a menu_name as an argument, so having menu_tree_get_item($menu_name) return a router item that may not have a link in the requested menu would be a WTF for the menu_tree_get_item() API.
When menu_tree_get_item() returns a menu item that isn't the path in $_GET['q'], then we need to find the $active_link for that path. That's why its important to make that change to
menu_link_get_preferred($item['href']).If menu_tree_get_item() returns NULL, then menu_get_item() will default to using $_GET['q'] as its path. Which means
menu_link_get_preferred($item['href'])is the equivalent tomenu_link_get_preferred(), no? Even if the router item's href is not exactly $_GET['q'], since the menu item returned by menu_get_item() shouldn't change per page request,menu_link_get_preferred($item['href'])'s static cache should work just fine.Comment #73
sun"2 functions call menu_link_get_preferred()"
Source: http://api.drupal.org/api/drupal/includes--menu.inc/function/menu_link_g...
Current patch updates one, so the other invocation will potentially lead to a duplicate operation for technically the same path under (un)certain circumstances. We could update the other invocation to follow.
However, menu_link_get_preferred() is a very useful API function and I expect that many menu-related and also custom/site-specific modules are going to use it. In turn, all of those would have to understand that they have to pass in the href of the current menu item instead of nothing. In turn, it might be worth to consider to make menu_link_get_preferred() to use the href of menu_tree_get_item() by default, falling back to menu_get_item()'s href, and ultimately falling back to $_GET['q'], in case it can be different to menu_get_item()'s href (not sure whether that is actually possible).
Additionally, we need to take menu_set_active_item() into account, resp., define the expected behavior in case it is used:
http://api.drupal.org/api/drupal/modules--user--user.pages.inc/function/...
Comment #74
johnalbinIf we alter menu_link_get_preferred() to check the menu_tree_get_item() that will have the side affect that it alters the breadcrumb returned by menu_set_active_trail(). And I don't think we want menu_TREE_set_item() to affect breadcrumbs. Maybe we do, but I just want to point out that particular non-obvious result.
I talked to chx in IRC and he said that, yes, $_GET['q'] can be different then the href value of the item returned by menu_get_item().
So, if we want to ensure that we don't muck with the cache of menu_link_get_preferred(), I think I need to change the patch so that it only passes a parameter to menu_link_get_preferred() if the $item was from menu_tree_get_item(). That way, menu_tree_page_data()'s default behavior of getting $item from menu_get_item() will be unaffected.
Comment #75
johnalbinFor the record, here's the patch I was thinking of in #74. But I don't like it anymore. Let me explain in a follow-up patch.
Comment #76
johnalbinmenu_tree_get/set_item() was kind of awkward because it required the user of that function to pass a $item that had many of the specialized array keys that menu_get_item() returns. In real word practice, that would have meant that users would probably have used menu_get_item() to get all those values for the menu item they wanted.
Instead, let's use
menu_tree_set_path()andmenu_tree_get_path(). Then user only need to specify the path of the menu link they want to be the active item in the generated menu tree. This is a much more natural API function.And it also simplifies the code greatly. The code in patch #75 was getting awkward. This patch is much cleaner.
It also avoids the cache issue that Sun was bringing up with
menu_link_get_preferred(). While the patch does callmenu_link_get_preferred($active_path), the $active_path will be NULL if no path has been set with menu_tree_set_path() and, thus, menu_link_get_preferred will end up doing its default behavior.Comment #77
sunSorry for being a pain, but what's the difference to menu_set_active_item() now?
Comment #78
johnalbin@sun Good questions are not a pain.
menu_set_active_item()internally actually changes the value of $_GET['q']. Which means if you set a path withmenu_set_active_item()it actually changes the router item for things like which menu item gets returned by menu_get_item() and (if called earlier enough) also affects what page content is delivered.The proposed
menu_tree_set_path()only changes which path is used to determine the active trail for menu trees. That's it. It has no other effects. If a module also wants to use that same path to affect breadcrumbs, that's relatively easy with our existing APIs plusmenu_tree_get_path($menu_name); but that isn't done automatically by design; I want this new API function to do what it says and no more.Comment #79
chx commentedThe gist of #78 needs to be in the doxygen of menu_tree_set_path(): the doxygen needs to clarify what's the difference between menu_set_active_trail() menu_set_active_item and menu_tree_set_path()
Comment #80
lotyrin commented> If a module also wants to use that same path to affect breadcrumbs, that's relatively easy... but that isn't done... I want this new API function to do what it says and no more.
It doesn't seem immediately obvious to me why "setting the menu tree path" wouldn't affect breadcrumbs, so I suspect that this part of the behavior especially needs documentation. I can imagine that an uninformed version of myself would likely try calling this function, then wonder why his breadcrumbs don't match his menus.
Comment #81
sunGiven recent clarifications and questions, I'd expect menu_tree_set_path() to have an impact on the default behavior of menu_set_active_trail(); i.e., the default breadcrumbs when not overridden manually.
Comment #82
johnalbinIndeed. :-)
Wow. Fantastic suggestion. Because when I stopped to think about how this should work, I realized the code already does this for the specific use-case that it should work for! :-D Here's the gist of what happens:
$preferred_link = menu_link_get_preferred();(without any parameters). It then callsmenu_tree_page_data($preferred_link['menu_name'],…);in order to find the active trail.menu_tree_page_data($preferred_link['menu_name'],…);will return the trail for the overridden path. For our example in step 1, if the preferred link is in the main_menu menu, then the breadcrumb will be overridden with the breadcrumb to our 'my/awesome/path' path.What happens if the preferred menu is different then one specified in calls to menu_tree_set_path()? IMO, the breadcrumb is correctly generated from the preferred menu and ignores the irrelevant calls to menu_tree_set_path().
I've updated the docsblocks to mention all this. I've also added two new test cases to check that the breadcrumbs are overridden when the preferred menu matches a menu set in menu_tree_set_path() and not otherwise.
The patch also moves around and clarifies related docs for menu_set_active_trail() and menu_get_active_trail(). And moves a paragraph of docs that was associated with the $path param of menu_set_active_item() to the function itself where it belongs: “Note that this may not have the desired effect unless invoked very early…” ← “invoked” clearly refers to the function, not the parameter.
Comment #83
sunTypo in "corresponsding".
I had to learn the hard way that "page content" may not be fully true. I was told that, as of now, menu_set_active_trail() contains a "hidden feature": by altering the active trail, modules are not only customizing the breadcrumb, but also the page title, and at least also the active trail in generated menu trees. Various other menu system and common request path and page API functions (e.g., stuff like drupal_get_title()) indirectly recurse and circle back into menu_set_active_trail(). Kinda messy and really hard to figure out what else actually relies on the active trail.
It doesn't look like this test actually tests the functionality of setting a custom menu tree path. I'd suggest to do the following:
1) Implement menu_test_init()
2) Add a system variable menu_test_menu_set_tree_path that can be an array containing $menu_name and $path, defaulting to FALSE (not affecting other tests).
3) In the test, invoke some regular paths, assert the expected default behavior.
4) Then, for each path, set the variable to override to something else, and assert the expected custom behavior.
We should make sure to leverage all of assertBreadcrumb()'s features; i.e., not only test the active trail, but also the page title, and possibly another visible menu tree. (see existing breadcrumb tests)
Powered by Dreditor.
Comment #84
johnalbinYes, it really does test the functionality. Take a look again; the code comments explain how it works. MenuTreeTestCase::testMenuTreeSetPath() is a minimal, but sufficient test.
I've been moving to a new apartment this week. If the tests need to be expanded, I can try to get my computer set up tomorrow night and work on it, but no guarantees I won't be exhausted. :-p
Comment #85
johnalbinAh, yes, you are correct that menu_set_active_trail() affects the page title. But it doesn't affect the active trail in menu trees. I wish it did! Then we wouldn't need this entire issue! :-D I've updated the docs to reflect that it alters the page title.
I've taken sun's suggestion in #83 and completely re-written the menu tree trail tests to be as robust as the breadcrumb tests.
Otherwise, the patch is the same. Just docblock updates and test updates.
Comment #86
sunhuh? Why did you duplicate the entire test methods instead of adding the new test method to the existing test case - or - extending MenuBreadcrumbsTestCase instead of DrupalWebTestCase?
Powered by Dreditor.
Comment #87
johnalbinSince this is OO, extending the class seemed most natural and is what I tried first. But I discovered that caused my MenuTrailTestCase to re-run all of MenuBreadcrumbTestCase's tests as if they were its own. Hmm… I guess I could override MenuBreadcrumbTestCase's tests with empty methods. Seems slightly hackish, but it will get the job done.
Ok. New patch extends test in an OO way.
Comment #88
sunmeh.
Comment #90
johnalbinAh, much better OO approach. :-) But you missed some things in the MenuTrailTestCase::setUp(). Fixored with this patch.
Comment #91
sunWe actually need a pass-through construct here; i.e., accept $modules, but
$modules = array_merge($modules, array('menu_test'));
parent::setUp($modules);
It doesn't look like this test actually needs all permissions (which is the case for the breadcrumbs test). Would be good to be more precise here.
Can we rename the first and the second both to $breadcrumb and $tree, and the second override ones to $breadcrumb_override and $tree_override?
This tweak could use a better code comment, because I do not understand why "Home" is removed.
Powered by Dreditor.
Comment #92
johnalbinOk. Pass-thru added to MenuWebTestCase::setUp(). Also, realized that MenuWebTestCase doesn't need the menu_test module, so I moved that to its children classes.
Ok. Changed the user to only required two admin-level permissions, needed for the paths it tests under admin/. Also, lowered the required permissions on the new paths in menu_test.module.
Ok. Renamed all those variables and simplified the generation of the $tree so it doesn't need an array_shift().
Comment #93
idflood commentedsubscribing
Comment #94
sunThanks! Looks ready to fly for me now.
Comment #95
johnalbinSummary for core commiters: (just noticed how long the thread is becoming)
Drupal 5 had the ability to set an active trail that would be used for breadcrumbs and menu trees. Unfortunately, D6 no longer had that ability. Double unfortunately, this was never fixed in D6. But it still remains a serious Usability-related regression.
This bug can most easily be fixed with a small API addition; specifically these two new functions:
Its not changing the way the existing API works in any way. No place in core calls
menu_tree_set_path(), which meansmenu_tree_get_path()will always return NULL. With this patch, the places in core that callmenu_tree_get_path()are designed to work _exactly_ the way they would before this patch ifmenu_tree_get_path()returns NULL.The patch is a bit bulky because it does a lot of cleanup of API documentation to help clarify the differences between these two new API functions and existing related API functions. It also refactors some of the breadcrumb tests so that the base functionality can be used for tests for these two functions and also for contrib.
Comment #96
johnalbinOne more follow-up. This is the only hunk from the patch that changes any existing core function. (Everything else in the 22kb patch is the 2 new functions, doc changes, and tests.)
menu_tree_page_data()is the only existing core function that is altered by this patch. Before callingmenu_get_item(), it first checks ifmenu_tree_get_path($menu_name)returns a path for requested menu. As I said in the previous comment,menu_tree_get_path()will always return NULL unless a contrib module callsmenu_tree_set_path()since core never uses that function. With$active_pathset to NULL,menu_get_item($active_path)and is the same as just callingmenu_get_item()without any parameters (see menu_get_item() docs).The same is also true later in
menu_tree_page_data(). Callingmenu_link_get_preferred($active_path)is the equivalent ofmenu_link_get_preferred()with no parameters (see menu_link_get_preferred() docs.)Thus, the changes to menu_tree_page_data() will have no affect given that
$active_path = menu_tree_get_path($menu_name);will always be NULL unless a contrib module usesmenu_tree_set_path().If you scan the rest of the patch, you'll see that the rest of it is only doc changes and test changes.
Also, at the request of webchick, I installed Views and its titles are unaffected by this patch. I created a view with a title, gave it a menu item with a different title, and both the menu item title and the page title were correct after applying the patch.
Comment #97
pwolanin commentedHave a parameter to the _get() that actually sets is a WTF. I think elsewhere in core, the _get() function wraps the _set() function and passes it NULL or whatever it needs to just return the value.
Comment #98
pwolanin commentedAlso, this function takes a menu link as a param, not a path. That seems like an inconsisentcy with the proposed API addition: http://api.drupal.org/api/drupal/includes--menu.inc/function/menu_tree_a...
Comment #99
johnalbinHere's an updated patch with the get/set work switched, per Peter's request.
Comment #100
johnalbinReply to: comment #98
It looks like
menu_tree_all_data()'s $link param is a menu link because book module stores its menu link in$node->book. And its convenient for the book module to pass in a menu link in that param tomenu_tree_all_data().It is, however, not convenient for anyone else. Its easy to find paths or store paths; why force me to query for its menu link? or to create a fake menu link? Especially when
menu_tree_page_data()would only use the path from the menu link anyway.If anything, in D8 we could refactor
menu_tree_all_data()so its params are easier to use. But let's not use a menu link in these two new functions.Comment #101
damien tournoud commentedThe way I see it, menu_tree_all_data() is basically deprecated. It serves no real purpose (except caching) now that the complex logic has been factored out in menu_build_tree().
This patch is actually very tiny, changes code in the minimal way possible, but adds a lot of documentation and tests. It blocks John's progress on refactoring menu blocks (and the new menu position dynamic menu item generation) so that those can be one day included in core.
I have nothing to add to this, let's get this in.
Comment #102
damien tournoud commentedRemoving the WTF tag. There is absolutely no WTF here, just conscious design decisions that we want to relax here.
Comment #103
kestra_chern commentedThis is a patch for D7, correct? Is there any chance of creating one for D6, or is the functionality too different?
Comment #104
johnalbinPlease note that #942782: Custom menus never receive an active trail depends on the breadcrumb simpletests refactoring in this patch.
@kestra
It would take some work to make a D6 patch, but it should be possible. I'm not willing to put the effort in, though. However, you can achieve some of the affect of this patch with the Menu Position module. http://drupal.org/project/menu_position
Comment #105
damien tournoud commentedRestoring tag.
Comment #106
dropcube commentedSubscribing.
Comment #107
scroogie commentedSubscribing. Looking forward to this.
Comment #108
dmitriy.trt commentedTaxonomy Menu Trails module is using workaround with menu_set_item(). It would be great to have a completely correct way to set current active trail in core.
Comment #109
andypostThis should be commited to HEAD first
Comment #110
catchTagging for backport.
Comment #111
sun#99: 520106-98-dynamic-menu-path.patch queued for re-testing.
Comment #112
sunComment #114
drifter commented#99: 520106-98-dynamic-menu-path.patch queued for re-testing.
Comment #115
pillarsdotnet commentedBack to RTBC as per #101
Comment #116
sunRe-rolled #98 as -p1 git patch against branch HEAD.
Comment #118
pillarsdotnet commentedCorrected and re-rolled.
Comment #119
sunThese are not compatible with DrupalWebTestCase::setUp(). No idea why it doesn't complain.
10 days to next Drupal core point release.
Comment #120
pillarsdotnet commentedInteresting...
Decided to see what would happen if
MenuWebTestCase::setUp()and friends were declared asprotected, for compatibility withDrupalWebTestCase::setUp(), and all tests failed.See http://qa.drupal.org/pifr/test/158134
Comment #121
ewills commentedSubscribing
Comment #122
OnkelTem commentedSubscribing.
Comment #123
ewills commentedJust as a quick note - I've found I can actually get Drupal to do what I need (dynamically assign nodes a menu position) by using a combination of Menu Position - http://drupal.org/project/menu_position - and Rules.
Comment #124
OnkelTem commented@ JohnAlbin
John, for any node which has no corresponding menu link, prefered link is node/% from navigation menu.
It is not from main_menu (against which I apply menu_tree_set_path()) and thus setting menu_tree_set_path() never affects breadcrumbs.
So my question is - what method do you recommend to get breadcrumbs to do follow the active trail in the circumstances when menu_link_get_prefered() returns menu which is not the menu of our interest?
Comment #125
franzThis issue could use a very good summary
Comment #126
felixSchl commentedNot sure if this got mentioning, but it seems as if something is going wrong theming wise, too:
The usual $main_menu array we get on our page.tpl.php is empty when I set the path using sth like menu_tree_set_path('main-menu', 'my/path');
Very frustrating. I followed the chain of execution, it starts with menu_main_menu(), which calls menu_navigation_links() which then calls menu_tree_page_data() and so on. What I find interesting is that I get a empty array even on kpr(menu_tree_page_data('main-menu', 0).
What's the deal with that?
Comment #127
OnkelTem commented@felixSchl
Well, I can't reproduce. I'm using Zen as the base theme, and the code outputting menu links from the page.tpl.php is:
$main_menu array is never empty on pages where I set active path in this way:
Comment #128
felixSchl commented@OnkelTem, sorry. I mistook the URL alias for the actual path.
menu_tree_set_path('main-menu', 'library/category');
did not work although the node's alias matched.
I worked around this via
menu_tree_set_path('main-menu', drupal_lookup_path('source','library/category'));
which works just fine.
Maybe this even helps someone not make the same stoopid mistake,
Cheers!
Comment #129
marcoka commented#17: menu-trail-alter.patch queued for re-testing.
Comment #130
smk-ka commentedNice, and the patch even works in D7 :)
Not sure why @sun thinks the setUp() method should be protected (how should it be called then?).
Comment #131
pillarsdotnet commentedThe attached version should resolve @sun's objections, which (as I now understand) weren't about protected vs. private, but about the parameter signature.
Comment #132
sunRight, thanks, the visibility is unimportant - it's the signature that's different, and which should normally throw an E_STRICT warning.
I know that @webchick is going to ask for it, so let's shortcut it: We should take over the identical logic/code lines for passing on $modules in the test helper class as in DrupalWebTestCase::setUp():
20 days to next Drupal core point release.
Comment #134
pillarsdotnet commentedSure.
Comment #135
sunThis cannot work - it's not possible to install or configure anything before setUp().
Just parent::setUp($modules)
20 days to next Drupal core point release.
Comment #136
pillarsdotnet commentedOkay.
Comment #137
bfroehle commentedGood to know. Opened #1273722: Fix FieldAttachTestCase::setUp() signature.
Comment #138
mnlund commentedVery nice patch for a very needed feature.
It works for D7. Is this planned to be committed to that branch?
Comment #139
franz@ramsalt, hence the keyword "needs backport to D7". The workflow is that bugs affection both D8 and D7 should be fixed on D8 first and then backported to D7.
Comment #140
mnlund commented@franz Dammit. This is a feature that solves a very important issue. And for all that are using context the menu reaction will now actually work. But the patch applied nicely on D7 and the feature works as it is now. Cross my fingers for backward compatablilty when it's committed to D8.
Comment #141
franz@ramsalt, no need to worry, if they both apply webchick will probably commit this to 7.x at the same time.
Comment #142
OnkelTem commentedWould anybody answer my question? The patch doesn't help to set breadcrumb correctly. See #124 for details.
Comment #143
johnalbinThe change to the SetUp() makes sense. Back to RTBC.
@OnkelTem To alter the breadcrumb for that use case, you need to use one of the existing breadcrumb altering API functions. http://api.drupal.org/api/search/7/breadcrumb
Comment #143.0
johnalbinstarting review
Comment #144
johnalbinI just updated the issue summary.
This is blocking the release of menu_position 7.x-1.0. And the simpletest breadcrumb refactoring is blocking another issue that is in turn blocking the release of menu_block 7.x-2.0. Anybody remember the issue tag for "blocking release of contrib module"?
Comment #145
pillarsdotnet commentedChange from "task" to "bug report" and bump to major. It's already "major" so changing category.
Comment #145.0
johnalbinUpdated issue summary.
Comment #146
catch#136: menu_trail-dynamic_local_paths-520106-136.patch queued for re-testing.
Comment #147
catchCommitted to 8.x, moving back to 7.x for webchick to consider.
Comment #148
webchickYeah, I think this is good to go since it's a pretty straight-forward API addition. Thanks a lot for the really helpful issue summary to make this look a lot less scary than it actually is! And thanks a lot for your patience as well.
Committed and pushed to 7.x. WOOHOO!
Comment #149
catchComment #150
webchickAnd since adding that summary is relatively easy, tagging as Novice.
Comment #151
ergonlogicI just sent an API change notification to the development list, but I assume it's in the moderation queue, as I only just signed up to the list. So, I'm marking as 'needs review'.
Am I right in assuming that the gist of these notifications gets posted to http://drupal.org/update/modules/6/7 with the next release?
Finally, from the summary: "After this patch is backported to Drupal 7, D8 should consider removing menu_set_active_trail()." Does this warrant a new issue?
Comment #152
pillarsdotnet commentedLooking through your history, I don't see the change notification you mention.
Could you please include a link to it here?
Comment #153
ergonlogicIt got refused by mailman, so no link. I'm following up with development-owner@drupal.org...
Comment #154
pillarsdotnet commentedJust go to http://drupal.org/node/add/changenotice
There is no support for adding change notifications via the development list.
Don't worry about getting it wrong; change notifications can be edited by any Drupal user, once created.
Yes, that would be a separate, follow-up issue. But there should be a link from this issue to that one and vice-versa.
Comment #155
ergonlogic@pillarsdotnet: oh, okay, thanks. I'd looked for documentation and only found some examples on the mailing list.
How about: http://drupal.org/node/1298642 ?
Comment #156
pillarsdotnet commentedIf it were me, I'd start out with something like,
Then I'd start an unordered list, and put each of the two added menu functions inside its own list item, and lhyperlink each one to its API page.
*AFTER* that succinct summary, I might go into details about the history of this feature, but not in the introduction.
Comment #157
ergonlogicupdated, as per suggestions.
Comment #158
pillarsdotnet commented(chuckle) -- You didn't have to follow *exactly* what I suggested -- Anyhow, I think we can remove the bogosity now.
Comment #159
ergonlogic@pillarsdotnet: well, I'd mostly just plagiarized a paragraph from the summary, so switching sources wasn't too much of a stretch ;)
Also, posted #1299056: Remove misnamed and useless menu_set_active_trail() function. for follow-up.
Comment #160.0
(not verified) commentedFix issue summary.
Comment #161
e2thex commentedComment #162
gejo commentedHi, newbie here.
How do I implement Damien's solution (#5)?
Could anyone explain the steps required?
I need to set as 'active' the same menu item for two different views (in three languages).
Is this the correct thread?
Thanks!
Comment #163
grendzy commentedgejo: in Drupal 7.9 and later you can use http://api.drupal.org/api/drupal/includes--menu.inc/function/menu_tree_s....
Since this issue is closed, a better place to request additional support would be http://drupal.org/support or http://drupal.stackexchange.com/.
Comment #164
SeriousMatters commentedFor those who arrive from Google in the future -
Menu Trail by Path module makes use of the new menu_tree_set_path() discussed in here.
Comment #165
xjmComment #165.0
xjmAdded target link.