Closed (fixed)
Project:
Drupal core
Version:
6.x-dev
Component:
menu system
Priority:
Critical
Category:
Bug report
Assigned:
Reporter:
Created:
20 Apr 2007 at 00:16 UTC
Updated:
2 Jun 2007 at 14:02 UTC
Jump to comment: Most recent file
Menu.in and menu.module need to support multiple menus. Here's Karoly's call to battle on the devel list:
The way I imagine this.
a) hook_menus to define multiple menus.
b) menu_menus table to store 'em. (of course, hook_menu has an FK to this)
c) menu_rebuild gets renamed to _menu_rebuild and gets the menu name as
argument
d) hook_menu also gets the name of menu as argument, returns the menu
items in that menu.
| Comment | File | Size | Author |
|---|---|---|---|
| #125 | system-menu-router-links.patch | 1.92 KB | bjaspan |
| #122 | p6_menus_17.patch | 78.13 KB | chx |
| #120 | p6_menus_16.patch | 79.75 KB | Crell |
| #111 | p6_menus_15.patch | 78.1 KB | andremolnar |
| #110 | p6_menus_14.patch | 78.17 KB | chx |
Comments
Comment #1
chx commentedThanks for taking this issue. We discussed here a bit. Two challenges: menu module needs to edit menus defined by other modules. And menu titles are highly dynamic, the navigation block is titled check_plain($user->name)
Comment #2
pwolanin commentedOk, I'm getting a bit stuck moving forward- here's why: Right now, the path is the primary key of the {menu} table, and thus a single path can only exist once. With multiple menus, each path can appear once (or more than once?) per menu. The path is also the key of the array that is build from calling hook_menu, and thus mus be unique in this context as well.
This is what I 'm thinking now- we need an additional table- something like {menu_display}. This would be keyed by menu name as well as path, and would contain all custom menu items and external links. This would map directly to the contents of blocks. In this way, I think the 2 calls to drupal_alter() will be for 2 different data structures:
1st call will allow modules to work on the {menu} to alter the callbacks or access parameters of module-defined paths.
2nd call will be for the {menu_display} data to allow addition of external links, links to individual nodes, links to individual users, etc.
As I imagine it, this split will also minimize the siz of the {menu} table, which should have some performance benefits even if there are links to 10,000 individual nodes being displayed via menus in blocks.
Since I'm still getting my head around the full 6.x menu system as Karoly coded it, I'd appreciate feedback on this direction.
Comment #3
pwolanin commentedok, so things are very broken with this patch, but it's a first step to separating into two tables.
Comment #4
chx commentedTwo tables, yes, sure. In the beginning I was even contemplating (and mailing devel) about splitting the hooks into hook_router and hook_menu but later realized that's not necessary or good so I contemplated two tables which indeed became necessary. Rock on.
Comment #5
pwolanin commenteddon't try to actually use this patch- it's very much a snapshot of work-in-progress. I'm trying to separate all the code that builds the {menu} table from the later code to build the {menu_links}
Comment #6
pwolanin commentedHey, this patch code is getting somewhere towards functional in terms of separating the callbacks from the links.
I think it actually simplifies the logic quite a bit in places. I'm also trying to break up the monster menu_rebuild() into several functions.
For saving menu items, is it kosher to save (for example) 100 at a time in a big query string? Similar to what you get by running mysqldump?
Comment #7
Crell commentedYou mean like this?
INSERT INTO foo (a, b) VALUES (1, 2), VALUES (3, 4), VALUES (5, 6);That's MySQL-specific, so no, that's no good. Some other database engines allow multiple full INSERT statements in one query command, but MySQL does not for injection-prevention purposes. Just stick with one query call per insert.
menu_rebuild() is, hopefully, a rarely called function so its performance is not super-critical.
Comment #8
pwolanin commentedOk, thanks- I won't try it then. In any case, the functionality needs to preceed optimization to some degree.
Comment #9
pwolanin commentedwell, I may have bolluxed as many things as are fixed (especially _menu_translate() and perhaps my misunderstanding of the to_arg functions), but I think this is moving closer to a logical separation of menu callbacks and menu links.
Comment #10
pwolanin commentedOk, the last set of changes broke some of the menu tree code somehow, but anyhow I think it represents progress:
1) fixed code in user_current_to_arg()
2) made it so system module declares a set of menus for all items like admin/X
3) Put all the menu items like admin/X/Y into the admin/X menu (i.e. all the by-task items)
4) change the system block code to use menu_tree() rather than a direct query, so The admin page kind of works again.
So this now demonstrates (more or less) how to build multiple menus with the new code and tables. Note the menus described by #2 have 'customizable' == FALSE, with the idea being when the menu module itself it brought into alignment with these changes, it will not list these admin menus as ones where you can add/disable items.
Comment #11
pwolanin commentedquick list of obvious bugs:
1) Menus are not expanding
2) When you are on a page corresponding to a local task, the tabs disappear.
3) No support yet for a by-module admin overview. This is a little trickier. Maybe menu module needs to do a customized module_invoke_all so that the name of the declaring module can be stored with each menu callback?
quick list of things to change:
1) menu_tree should return a non-rendered data structure and a separate rendering function needs to be built- this way the book module, for example, could use the tree information to build the book navigation.
2) The per-user menu_tree data structures, and other menu info should be cached (eventually).
Comment #12
chx commenteduser_current_to_arg is designed to always return the current user id. That's how my account link works even on user/564854 pages. _menu_check_access does not make any sense to, i surely miss something. I do not readily get what are you doing with admin/*, even if we decide it's a good idea, it's a separate patch, but while yes, it is similar to menu tree, the descriptions make it distinctive.
Otherwise, it's a fantastic piece of effort. Thank you really much.
Changing menu_tree to return a structure -- I do not like the idea, book/outline module can easily reuse the menu table itself, it's trivial to write a query which gets the items in menu order.
Comment #13
pwolanin commented@chx - here's one reason why I think have the structured data is important- it's not just one query to build this. This menu can include links to lots of nodes, so for every node in the menu you also have to do a node_access() check before deciding whether it will show up in the menu or in the navigation. For the sake of efficiency, I think it's really important to have each menu tree cached per user (or, at the very least, saved in a static variable), which means calculating only once all the access checks per user.
As far as the user_current_to_arg()- I don't really think we want what you are suggesting. If so, zillions of links on d.o will get broken, right? Also, this is quite confusing, since if it works the way you suggest, if you go to URL user/999999 you'll still see this as the address, but see your own account page.
Comment #14
pwolanin commented@chx - I think I'm a little confused by the dynamic wildcard replacement you originally envisioned: http://drupal.org/node/109153
Is the dynamic (to_arg) replacement only doe when rendering links, or should it also happen when handling a request?
I you original menu.inc code, it's the former, but reading the handbook page linked above it seemed to imply (to me) that to_arg worked in both cases.
Comment #15
chx commentedLet's make the menu_tree structure a separate issue, OK? I was also thinking of somewhat doing array-ish things in there. Let's dissect _menu_translate on a MENU_HANDLE_REQUEST . First of all, $path_map becomes map which is the exploded stuff of the current path. Second, to_arg_function is not called at all, there is a if ($operation == MENU_RENDER_LINK) which stops that. It's the object loading calls that happen all the time because the access checks needs those. I am sorry if the menu handbook is confusing, I will look at it. I do not readily see what gets broken, I tested that if I am user 1 and go to user/2/edit then my account still points to user/1 and all the tabs point to user/2. This was some time ago, but I believe it still stands.
This all can be cleaned up, lots after or during this patch because we longer need to mesh the user/%user router item with the user/_actual_user_id_ menu link. Yay.
Comment #16
pwolanin commented@chx - a quick thought here, more later:
I was thinking about this a lot last night, and realized that even in the case of a menu with a few nodes showing (e.g. 10), the current rendering system is going to be deadly for performance, and doubly so if there is an access control module. The way it's set now, a full node_load() followed by node_Access() will need done to check the access status of each node link! So, I think we need to generate the data structure/array containing the links, then special-case nodes and use a query with db_rewrite_sql to find out which of the nodes the current user is allowed to view.
Comment #17
add1sun commentedsubscribing
Comment #18
chx commentedI can see your problem but first can we get the split storage and the multiple menus working and more on later issues? We need to support multiple menus in menu.module , too... optimization can come later -- not just can, it needs to, but first let's get this one out of the door.
Comment #19
pwolanin commentedYes, I agree that we need function before optimization, but I want to get all these issues on the table so it informs our thinking about which functions to split and possible
Comment #20
pwolanin commentedgetting much better, but not quite perfect. Tabs seem to work (mostly) after some servious revamping of function menu_local_tasks().
Note- no PostgreSQL schema yet, and the changes to system.module should be a separate patch eventually.
Comment #21
gábor hojtsypwolanin asked me to come and help out get this patch in line with the latest menu localization changes, but as it seems from his latest patch, the title and description translation code is already there, so I don't see what is the supposed conflict or problem with menu localization?!
Comment #22
gábor hojtsyOh, I got an email from pwolanin, which I will repeat here, so it is saved for history.
Hi,
pwolanin wrote:
> regarding this patch: http://drupal.org/node/128082
>
> I'm trying to figure out how to merge it in with multiple menu patch I've
> been working on with advice from Karoly: http://drupal.org/node/137767
>
> In particular, which the new locale system should t() be called with
> user-entered text. For example, when the user enters a link and the text
> for the link, should t() be called on that as well as on the title? I
> guess a roughly equal question is whether t() now gets called on node
> titles?
>
>> From this: http://api.drupal.org/api/HEAD/function/node_page_view
> It doesn't seem to be the case.
The new menu localization system makes no difference between built-in menu items and user defined menu items, when it comes to localization. This is because we have no solution yet to translate user defined interface elements, but are in the works with one. If Drupal 6 will not include a user defined interface translation solution, we can still pull the plug on t()-ing user defined text, until that this is an accepted wrong way.
I am equally fine with checking for custom vs. built-in menu items, and only translate built in ones for now. Then put a TODO note into the comment in the "else" branch of the code, that there comes the user defined translation :)
We are still working out performance issues with user defined text translation (otherwise our model is very simple and works nicely). I hope we will have it done in time for Drupal 6.
Gabor
Comment #23
pwolanin commentedTehre is no conflict, I was just trying to understand what that recent patch did so I could incorporate it.
As you say, we should probably add translation at least for the cases where the menu_link's link tet is identical to the module-defined menu title.
Comment #24
pwolanin commentedHere's the essential core of the patch, excluding proposed changes to system.module's building of the admin page. With just this, the admin page is broken again.
@chx - thinking about our conversation. Is it really not feasable to build the links in memory? I think it could be if we go back to your idea of doing hook_menu_links_alter one named menu at a time. Then even for a big site like d.o, we're not imagining building all the book pages at once, but only 10-20% of them. Maybe ~2-3 MB of memory? I'm just imagining that trying to build the re-build that part of the table by doing 2000 inserts is going to be worse, and the numbering, finding parents, re-parenting, etc seems a lot easier.
Comment #25
pwolanin commented@chx- ignore most of the previous comment- obviously I didn't get enough sleep since in either case we're doing the same number of DB inserts.
Comment #26
pwolanin commentedattached patch includes work Karoly has done in the last couple days. My contribution to this today is to get the rendering of the navigation menu working. If you look at the patch, there is now a function that returns the *data* corresponding to the menu links tree that is currently being rendered This is with the idea that other modules (e.g. the follow-on to book.module) can use this data to render navigation elements, or that we can use it to render the breadcrumb trail, etc. without doing any additional queries (a static variable is used to cache the result- in the future we can actually use the cache system).
Comment #27
chx commentedOoooooh! You figured how to do the data collection-HTML separately? My brain exploded when I tried. How very nice. We will be able to reuse that in so many ways. Thanks!
Comment #28
pwolanin commented@chx - the data collection was 90% there in your patch, so I just carried it over the finish line.
Comment #29
pwolanin commentedI'm trying to fix the problems with local tasks in the previous patches(they really are not working now below level == 0). I think the real fix involves another change to the {menu} table.
Right now, the number stored in tab_depth is always the same as num_parts, so we really don't need it. What we need instead is the
tab_root, as well as the tab_parent. The tab_root will be the path of the non-tab below which a group of tabs belongs, while tab_parent will be the path of the router item that's the parent of a given tab. Thus, tab_parent will be empty for a non-tab page.
Attached patch implements this (after much angst). This allows us to get and present all the tabs for unlimited levels using a single query.
Comment #30
pwolanin commentedminor additional changes to function menu_tree_data() to move towards proper handling of links vs. router items.
Comment #31
chx commentedAwesome! This version removes an empty Navigation block, and fixes system_admin_menu_block .
Comment #32
pwolanin commentedJust an update of chx's last patch to accomodate this commit to HEAD: http://drupal.org/node/137211
Comment #33
pwolanin commentedminor additional work to start to get breadcrumbs working again. Not quite there yet for local tasks (it's some problem building up the menu tree)
Comment #34
pwolanin commentedTo simplify the rebuilding process, I'm wondering if we should only allow router item links to be added to the 'navigation' menu?
That would also probably mean we could do away with menu_get_names() and hook_menu_info(), or at the least simplify menu_get_names() by making it a query to find the unique menu_name entries in the {menu_links} table. Then, a module could make a new "menu" just by inserting one or more links with a new menu_name into {menu_links}. Menu module can then just keep a list of its own menu_name entries, and only offer to customize those (plus 'navigation').
Note that's I've started trying to let modules set a "context" for their breadcrumbs, etc with functions menu_get_active_menu_name() and menu_set_active_menu_name().
I'm imagining that this could be used by a module like book module to make the visible breadcrumb easily match the menu hierarchy.
Comment #35
chx commentedAnother round of patch. This one introduces the oh-so-important menu_link_save API function. The feature set is at such a point that now I would like to see this one fixed, ironed out (like writing an UPDATE statement), etc. and later, in separate patches menu.module and book.module can use that menu_link_save . I have a text that explains the maths involved.
Comment #36
pwolanin commentedcan you post the math page as a new page in the 6.x menu handbook and link from here?
Comment #37
pwolanin commented@chx - pasted in your description of the new system here: http://drupal.org/node/141866
Comment #38
pwolanin commented@chx - this is a bit of cleanup as we discussed.
To make life simpler, router items will only be added to the 'navigation' menu. Thus, it seems pretty safe to just delete all items from {menu_links} with this menu_name and start fresh. Adds a new hook hook_navigation_links() which is called when the navigation menu is rebuilt. Removes hook_menu_info(), since this is something menu module itself should handle.
Added a call to drupal_alter('menu_link', $item, $menu) in function menu_link_save().
Also, got menu_get_active_trail() working better for local tasks, so breadcrumbs and title work better.
Comment #39
chx commentedI am not so sure our work is so much easier if we restrict router items to the navigation menu. You can't delete everything from there the same as you can't delete any other menu. So I have removed this. However, with tasks, breadcrumb, title and navigation menu all working I am deeming this ready to review. Menu module supports come later. I added a module column to menu_links so that for example menu module does not start to customize book module links...
I am very happy how our duo was working on this big, big rehaul. It's good to have another man on board -- it makes our code even better. Instead of cleaning up in Drupal 7, Peter cleaned up (and/or forced me to clean up) the code. Fantastic!
There is still work to do in other patches:
Comment #40
chx commentedAnother action point: we need to check all menu_rebuild calls in core and remove them in favor of menu_link_save calls.
Comment #41
pwolanin commented@chx- haven't looked at this patch in detail, but per my e-mail I think the table architecture needs to be refined further to enable efficient re-parenting for menus with a large number of items. Per my e-mail, I think we can do this by breaking out the "parents" column into 5 or 6 separate int columns if we are not constrained by having to get the items at a given depth in the right order as they come from the DB.
Comment #42
chx commentedThere is time for refinement later.
Comment #43
pwolanin commentedOk- what about the navigation menu? I still think we're going to have trouble doing a menu rebuild if router items can be declared as going into menus other than navigation. Assume the module that did this is disabled- there is no way now to find the "dead" menu links unless you've stored somewhere all the router items from the last time you did a menu rebuild, right? Actually - this might be the best approach, since then you could disable or delete all menu_links that use a router item that has been removed from {menu}.
The way I coded it in my last patch I'm imagining that menu module (for example) could use the hook_menu_link_alter to change the menu a router item is saved to, but also keep track of this so it can do a cleanup if the router item gets disabled.
Comment #44
chx commentedWhat's your problem?
Comment #45
pwolanin commentedNo problem- that code will work. But, how will performance be with ~300 keys and 10,000 links? Would it be more to get all the existing paths from {menu} before truncating it? In that way, you can check if any of the existing paths are missing after the rebuild and write the query as
IN()rather thanNOT IN().Comment #46
pwolanin commentedmissing a DB column "system" in {menu_links}
Comment #47
pwolanin commentedOk- the columns 'system' was there, but the insert/update code wasn't quite right in function menu_link_save().
Also made a minor change to the query (use COUNT(*) instead of db_num_rows()).
Additional small change to menu_set_active_trail()- link to 'Home' is added there instead of in get_breadcrumb. That way, BC can be full changed to cut off 'Home' link.
Updated patch attached.
Comment #48
chx commentedIt was deliberate to leave out the module column from update -- you never want to update that.
Comment #49
pwolanin commentedOk, per my e-mail to Karoly, here's a totally different way of constructing the menu order- the parents (each element of the materialized path) each have their own int column- up to 6 deep. I think this makes the code much simpler. The re-parenting isn't implemented yet, but all other functionality seems ok. If there is some reason to have menus deeper than 6 levels, this can easily done by adding additional columns- please comment!
I've added a description of the method form my e-mail here: http://drupal.org/node/141866#materialized
I think that especially for re-parenting this method is a big winner since all the work can be done in 2 queries regardless of the number of elements in the menu tree.
Comment #50
chx commentedI like Peter's method. It's indeed a clever trick using the fact that we know the max depth of our tree.
Comment #51
webchickWhen enabling menu.module, you get:
Fatal error: Call to undefined function _menu_primary_links() in /Applications/MAMP/htdocs/head/includes/menu.inc on line 734
I was told by chx not to enable menu module, but since this is added by this patch, it probably should have a stub function or something as a safety.
Also, there is no update path. chx tells me that that will be added after this patch is committed.
When I enabled 'create book' permissions for authenticated users, neither "Create content" nor "Create book page" appears.
I clicked around to a bunch of stuff and it otherwise appeared to be in order, but I wasn't able to give it my total full attention so probably needs another set of eyes.
Comment #52
pwolanin commentedClearly this is not yet RTBC- the basic question at this point is whether menus, tabs, breadcrumbs, etc. work and whether you have opinions on the algorithms being employed in the code.
This current work lays the infrastructure for menu.module, book.module, etc. (but they should all be optional for a site that wants just a simple navigation menu). Obviously the final patch will include an updated menu module, upgrade path, etc. I'll re-roll tonight with a stub for menu module to avoid reviewer confusion ;-)
Comment #53
chx commentedI believe that if you can get
this one fixed, then it is good to go -- we have the backend in place, let's get it moving.
Comment #54
pwolanin commented@chx - any ideas on this problem? node_type_form_submit() calls menu_rebuild() already. Appropriately, since additional callbacks may be created or removed.
Some other outstanding concerns that need to be resolved:
Comment #55
chx commentedShould the code that deletes links if their callbacks are gone also try to re-parent any orphaned children? Or maybe these links should be disabled rather than deleted?
This falls into not babysitting broken stuff. But then again, a user might just move his / her stuff in there. Disabling won't work, we do not bubble, reparenting can be much, much work... let's give this more thought. Maybe anoher issue.
re-parenting needs to be implemented and tested.
Definitely a whole another issue. I believe the next two issues, in parallel are upgrade path and reparenting.
some of the admin/log/X items end up in the {menu} table with access_callback == 0
I believe some admin log paths are botched, there was already an issue for them. Please look at this.
The active trail includes the current page, so the last item in the breadcrumb is the current, rather than parent, page.
That's easy to fix...
should the allowed depth be > 6?
No.
Are the table indices working? We need to generate a reasonably large {menu_links} table to test it.
I will do that -- I will import (a part of) the Open Directory RDF structure dump.
When inserting a new menu item, do we need to check that the parent has depth < 6?
Yes, return FALSE if we can't save it.
When re-parenting, do we need to check that neither that item or any of its children will have depth > 6? What should the code do in this case?
Reparenting is another issue :)
Comment #56
pwolanin commentedOk, tracking down the access bug- at least part of the problem seems to be that some node/%/X items in {menu} are getting assigned user_access instead of node_access as the access callback.
Comment #57
pwolanin commentedok, a better patch. breadcrumbs work better (stupidly spent lot of time on this), and node access check should be happening right so the problem with the book module should be fixed. Menu module doesn't work yet- no reason to activate it.
Also, changed 'link_path' to 'href' and 'link_text' to 'link_title' to be more similar to the usage elsewhere in core by theme_links()/hook_links.
Comment #58
chx commentedDmitrig01 reported http://dmitrizone.com/dump/Picture%202_.png .
I forgot one case in the menu tree hoarding, when you need to fail back more than one level, in the default install admin/user/user and logout just happens to stand like that. I removed ksort calls for testing ran
insert into menu_links (menu_name, mlid, plid, href, router_path, disabled, external, has_children, expanded, weight, depth, p1, p2, p3, p4, p5, p6, module, link_title, options) values ('navigation', 500, 41, 'admin/user/user/1', 'admin/user/user', 0, 0, 0, 0, 0, 4, 1, 9, 41, 500, 0, 0, 'system', 'Users1', 'a:0:{}'); update menu_links set has_children = 1 where mlid = 41;and got the attached picture -- which is what we expect. Dmitri's bug is now fixed.He also reported another, I will promptly fix that and then submit the patch.
Comment #59
chx commentedSo this patch contains two bug fixes, one detailed above and the callback tree needed sorting, I have added that, this fixed some problems when access callbacks became 0 like on admin/user/profile.
Comment #60
pwolanin commentedI'm not sure how best to handle this, but note that when you go to a page with just a callback like: admin/user/profile/edit/1
Then the navigation menu tree is no longer expanded down to the level it was on the page admin/user/profile, and the breadcrumb just has "Home". Obviously, profile module itself could fix up the BC, but this doesn't seem totally satisfactory. Also, it's not clear the current code is handling MENU_SUGGESTED items correctly.
Perhaps we need to be putting callbacks into the {menu_links} user 'navigation' too? Then menu module will just have to know not to show them if they are type == CALLBACK vs. TYPE == SUGGESTED?
In this case, should the column in menu links be changed form 'disabled' to a more accurate 'hidden' or 'not_shown', or reverse the sen of it and make it 'shown' or 'visible' or 'visible_in_menu'? Attached patch changes it to 'hidden'.
Attached patch tries to fix all these issues. Note, we have to do a little double-check now in _menu_link_translate. Maybe there is a smarter way to avoid this problem when building the navigation menu? For example, exclude any router items with '%' except if they have a to_arg() function?
Comment #61
pwolanin commentedFollow on to the previous patch - as best I can tell, that's working (except My Account not showing again - hopefully this patch fixes that).
This patch adds to this the handing of menu link items set to be "expanded". No Drupal UI for this yet, just set expanded = 1 for some menu items via your DB interface to test. Creates a new pair of functions that parallel menu_tree_data and _menu_tree_data: menu_subtree_data and _menu_subtree_data.
This should be generally useful- for example:
$output = menu_tree_output(menu_subtree_data('navigation'));Should give you the rendered navigation menu where only items with expanded = 1 are expanded, independent of the current path/page.
Note also- disabling a module doesn't trigger menu_rebuild() - maybe a bug in system.module?
Comment #62
chx commentedPeter, sorry, but this is not how expanded needs to be handled. See my patch. Expanded does not require new processing code, just iterating a new query if there are expanded items. I never wanted to write this myself, but ah well, it's not too complicated...
Comment #63
chx commentedScreenshot of expanded in action.
Comment #64
webchickGoing through, found two more issues:
- My recent posts is gone from tracker, and after visiting any subsequent page, you get:
... chx is fixing now.
- Attempting to add a path breaks horribly:
chx claims it should not be trying to update menu at all, but menu_links. menu_links has no link_path field. it has a router_path field, but then it also has no path field. I gave up trying to fix it.
Comment #65
webchickTwo others I forgot about:
- Apostrophes are double-escaped when used as a menu title (made a profile category "Let's dance" that came out as HTML entities for the apostrophe)
- There are a couple notices as search/user:
search/user/ is fine.
Comment #66
webchickAhem. search/user/[somename] is fine.
Comment #67
chx commentedThis patch does not serve aliased links at this point, so I removed the path code. I needed to add dynamic translation back to menu_translate but only when tabs are rendered. There is no a function which is used by menu_translate and menu_link_translate. This fixed the search tabs as well. The double escape I am not fixing it's a side effect of the localization patch: the title callback is check_plain and then we run it through
l(). I will talk with Goba what's the best avenue to fix it.I am RTBC'ing at this point because I fixed everything webchick was able to find with one sweeping fix -- actually since the moment I saw the changed menu_translate it nagged me.
Let me sum up what we have gained with this patch: an API which lets you add links to any menu and the ability to render said menu. As a bonus, we got a lot of things, most important is one query for tabs instead of many and expanded support.
Comment #68
dries commentedClearly, a lot of hard work went into this patch. That's great! :)
But, boy, this is a _lot_ of code, and much of this is hard to understand. It will take me several iterations to get this review right. I spent 45 minutes reviewing it and I doesn't feel like this review is particularly useful. I'll need to spend more time with it, but I have to run now.
My overall, initial reaction is: poorly documented. With better documentation and code comments, I'd have been able to get a lot more done in 45 minutes.
In fact, I can't comment on the engineering/architecture yet, as I've yet to figure out how exactly everything works. The lack of good code comments are a bit problematic. Without better code comments, I'll have to sit down and spend some hours reviewing this. I'll want to find a free evening for that.
For outsiders, the old menu system was hard to understand. But for outsiders, the new menu system also risks being hard to understand. At least, that's how I feel now, but I'm positive that I'll see how it all works fairly soon. ;-)
1. For example, _menu_translate() is documented to return a $map object but it can also return FALSE (which is not documented).
2. In the documentation '$to_arg_functions' should probably be '$to_arg functions' (space) as the variable is called '$to_arg' and not '$to_arg_functions'.
3. Documentation is sparse at points. What does _menu_link_flatten do, and how?
4. "Returns a rendered menu tree based on the current page." -- while I understand what you mean, it could do with being a bit more verbose. "based on the current page" might not be explanatory for everyone.
5. Shouldn't "menu_tree_output" be an internal function? I.e. "_menu_tree_output"? The function's PHPdoc does not help me understand it either. I had to read to code to understand that it is a helper function of menu_tree().
6. "Get the data representing a named menu tree". What is 'the data'? The stuff? :)
7. If would help if you could document -- in the code -- how the algorithm works. As this is what you are soliciting feedback for, it would help reviewers do their thing.
8. What is the performance impact of this patch? On a small or medium sized Drupal site with a dozen menu items? How much extra queries does it add? It seems to be database heavy. Will it slow down or speed up Drupal?
9. There are about a dozen TODOs in the code. Sure this is RTBC?
10. _menu_router_build seems key and it has no code comments whatsoever.
To make a long story short. I only had a first look at it, and I think I understand the bigger picture. I'll need to dive in and inspect the different bits and pieces into more detail. If you want to help me do that, focus on writing better code comments (this is going to be a requirement anyway).
Either way, good job on taking on this legendary task. Most of what I saw, makes sense -- I think. It's just that I have to convince myself a bit more/better by spending more time with this patch.
Comment #69
pwolanin commented@chx- I disagree about the expanded link code.
You need to be able to distinguish the subtrees placed there because they are marked "expanded" to those placed there because they are part of the path to root in order to find the correct breadcrumb trail. Are the breadcrumbs working now for all cases?
Also, I think the subtree functions are very important for the API, even if we are not using them now.
Finally, I'll look further, but I don't understand why this code every needs to get the path alias if the links for tabs, etc are being generated via calls to l() which should look up the path
@Dries - for menu_tree_output, I removed the leading "_" in my most recent patch with the idea being that this should be an API function for rendering any menu tree/subtree rather than just a helper function. The thrust of moving towards something like menu_tree_data() is to be able to reuse the same information to generate the rendered menu, the breadcrumbs, book navigation, etc without doing any more queries.
Comment #70
chx commentedPeter, I believe there is no difference between an expanded tree and a tree which is just there. Think of it, when you stand on administer, you see tree elements below admin , so what? Breadcrumbs will work, why not? The menu_set_active_trail will iterate down until the current path/tab root and that's it. Expanded does not differ from what we already have.
I do not understand a single word. The code does not get the path alias. What I said is that previous versions (like HEAD) had the ability to store aliased links. This is lost. To reintroduce, we need to look at path code. Later.
You found the only function which is not documented to death. Congratulations! It's an internal function.
TODOs are further patches.
_menu_router_build is also an internal function which you should never ever call and it has comments inside.
If someone else -- let's hope Peter -- wants to add even more comments, be my guest, but my time is limited at this point.
Comment #71
pwolanin commented@chx - I need some time to test the code in terms of the menu tree, breadcrumbs etc.
I'll also try to add in as code documentation a version of this writeup of the method: http://drupal.org/node/141866#materialized
If aliased paths are stored, I'd only see this as part of a system of caching a rendered tree, but never in the {menu_links} table.
Comment #72
pwolanin commented@chx- also, unless you fixed this, the help text isn't showing up when you're on the full path of the default tab.
For example, help text may show on 'admin/settings/filters/%' but not on 'admin/settings/filters/%/list'
Comment #73
chx commentedLet me make myself clear: my core coding time for the next two or four days is zero. And no, I have not checked help.
Comment #74
pwolanin commented@chx - I understand- I'm just trying to think of all the relevant questions/concerns/bugs before I look at the code again.
Comment #75
chx commentedPeter, I believe only more comments per Dries' call and help texts by your call need attention. Everything else can come after. Code freeze is approaching and this patch is already huge.
Comment #76
riccardoR commentedThere are still some issues with expanded and breadcrumb IMO:
- Administer > Categories > Edit a vocabulary
- Administer > Input formats > configure an input format
- Administer > Users > edit an user
(Note: patch #67 applies to #HEAD with 3 lines offset in user.module)
Comment #77
chx commentedIf needed just rip the ~!@#$%^&*() expanded out of the patch , I totally did not want to do it (ever) especially not part of this patch but as Peter added it I had little choice but recode it.
Comment #78
pwolanin commented@chx- it seems you didn't like the algorithm/method I used for expanded items? However, I think it works? Are there flaws in that code? Maybe this should have been saved for a subsequent patch, but I'm a perfectionist.
Comment #79
chx commentedI fixed riccardoR's finding which obviously had nothing to do with the expanded code. I added a billion more comments. Re. DB heavy, not at all, actually, tabs are now cheaper. Why have you thought this is DB heavy? Expanded can cost you a few queries (max five) but -- there is a price to be paid for exotic features. Normally, not a single expanded query is executed. _menu_router_build is internal, there are comments inside where needed.
And no, we do not ask anyone to comment on the algorithm -- if we would have kept the matrix implementation you would expect them to review that? We agreed on this is not necessary. We are asking whether it works and it does. And unless you ask for even more comment, it is ready.
Comment #80
pwolanin commented@chx - I didn't have any chance to code yesterday- I'll go through this last patch tonight for some final review/testing.
Comment #81
owen barton commentedOne thing that I did notice was that this patch, although it looks huge (especially in terms of Kb) actually removes a significant number of lines.
Here is the ~count:
-628
+969
i.e. a total of 341 new lines of code, which I don't think is too bad, considering the amount of functionality this (re)enables.
Comment #82
pwolanin commentedI see some minor problems- here's one comparing menu.inc after my last patch to Karoly's last patch:
I think we have to do a LEFT JOIN, since there will be no m.path in {menu} for an external link in {menu_links}.
Comment #83
riccardoR commented@chx, @pwolanin: I am sorry for having created a misunderstanding with the word "expanded".
I have just realized that you were talking about the expanded link flag, while my feedback was about the normal tree expansion when navigating down the menu.
As I am here, I can confirm that the last patch fixes my finding.
Thanks
Comment #84
pwolanin commentedOk, I'm getting my head around Karoly's way of getting the expanded items- does indeed seem better than what I coded. My only suggestion- rather than storing just a TRUE/FALSE in the 'menu_expanded' variable, why not an array of menu names?
However, I think function menu_set_active_trail() needs a slight adjustment, or all the active trail may show the expanded items, rather than the path to root for the current page in the active menu.
I still think something like a menu_subtree_data() function would be good for the API (though not necessary for this patch).
Comment #85
pwolanin commentedI think this patch fixes all the issues I identified - I checked, and previously if the menu items were set to be expanded, the breadcrumb was totally wrong. Also I manually added external items to {menu_links} and they work with this patch.
- fixed query in menu_tree_data()
- fixed active trail/breadcrumb by setting a flag in _menu_tree_data() for each item
- use drupal_clone() in menu_get_item()
- rearrange a couple indices
- make the same help show on a page and on its default local task.
- implemented 'menu_expanded' as an array instead of boolean.
Comment #86
pwolanin commented@chx - here a diff showing what changes from your last patch, _6, to mine, _7.
Comment #87
chx commentedI agree wholeheartedly with your changes -- but this
part is not needed -- you always want to check for expanded menu names because what if I just switched off the last expanded menu item? (BTW. if someone is concerned why my patches are smaller, that's because bzr puts smaller header per files, but the patch itself is the same)
Comment #88
pwolanin commentedYes, it's probably simplest and most robust as you suggest.
Extremely minor changes in the attached patch- I fixed a spelling error in a comment and deleted one stray line of useless code.
Comment #89
dries commentedIs it normal that the menu module is still broken? I applied the patch, did a fresh installation and went to the menu administration page: Unknown column 'visible' in 'where clause' query: SELECT m.*, me.disabled FROM menu m LEFT JOIN menu_custom me ON m.path = me.path WHERE visible = 1 OR (disabled = 1 AND admin = 0) ORDER BY mleft in includes/database.mysql.inc on line 161.
Other than the broken menu administration page, this patch seems to work. ;-)
Comment #90
chx commentedYes menu is module broken.
Comment #91
pwolanin commentedI think in an earlier stage of the menu rewrite i remember that the checkbox for menu module was disabled on the modules page? Perhaps we can just do this again to avoid angst and confusion.
Comment #92
pwolanin commentedAttached patch adds a couple lines of temporary code to system.module to disable the menu module checkbox.
Comment #93
gábor hojtsyTried to look through what is happening in this patch. But it does seem to have evolved a lot farther then what chx set out in his five initial tasks. Maybe we can get an overview of what is happening here. I see a lot of changes are around, but maybe we can get a short architectural overview. Sure pwolanin and chx knows what concepts and approaches are changed, but I cannot make it from the patch.
I also noted 13 TODO items in the latest patch, some in groups of three, one in an empty stub function. Are these supposed to be filled in with later improvements in the 6.x timeframe?
Comment #94
pwolanin commented@Gábor - the TODO items fall into 2 categories:
1) performance related (i.e. not necessary to have them done before the code freeze)
2) important functionality
This patch is a stepping stone towards a a follow-on patch that should (at the least) address all the TODO items in category #2 and re-enable the expected functionality in the menu module. Note, the goal is that menu module will be optional, and (for example) I think it would make sense to be able to enable it, add links to the primary links, and then be able to disable it to improve site performance while still having those primary links appear.
Overall this represents a necessary continuation of Karoly's initial rewrite of the menu system. Here's what it does:
It splits the "menu" between two tables: {menu} and {menu_links}. The {menu} table contains "router" items- in other words, the items that determine the correct page callback for a given path. The {menu_links} table contains the links that are displayed in menu blocks like the navigation menu, primary links, etc. By making this separation, {menu_links} can become quite large (for example, our goal is to rewrite the book module to save an actual menu link for each item in the hierarchy) without having a negative impact on page serving. In addition, only the items needed to render menu blocks/links are loaded from {menu_links}, thus minimizing memory usage.
Comment #95
pwolanin commented@Gábor - also (quite frankly) this split was more difficult than Karoly described it as being initially. However, while some of the names used don't match what's in his 5 points, it all follows directly from his proposal.
Also, if it would be helpful for your review, I'd be willing to annotate (and maybe add more) TODO comments to make clear whether they are performance or function related. This change to the menu system will clearly impact other people's patches, so I think it's important to get it in now, and then build on it.
Comment #96
chx commentedThe patch has nothing to do with the original post... There was a TODO in HEAD (and a mail to devel, long ago) asking for splitting router and link items and Peter implemented this. This lets us scale the new menu_links table to quite new sizes -- book with use this. As a collateral, multiple menus are supported.
Comment #97
dries commentedI would be useful if we could add that high-level discription (router vs menus) at the top of menu.inc.
Also, if {menu} is use for router-stuff, why not call the table {router}? And if {menu_links} is used for menu-stuff, why not call that {menu}? :-)
Comment #98
chx commentedAdded - changed comments to the top, changed table names. I truly hope this gets in now...
Comment #99
Stefan Nagtegaal commentedPerhaps my opinion doesn't matter that much, because I totally have no clue how the new menu-stuff works atm but wouldn't it be nicer if we prefix the new created tables with the modulename?
So that would make {menu_router} and {menu_links}.
I would love to see this for every core/contrib module, so examinating the database would be pretty easy and much easier to have a clue where to start looking at in the first place.
Just a thought, you can safely ignore this if you want to...
Comment #100
pwolanin commentedThese tables are both defined by the system install and used by menu.inc (really part of system.module) and are independent of the menu module.
So, I'm not sure that "router" is any less clear than "system_router" or "menu_router", but I don't have strong feeling about how the table should be named.
Comment #101
chx commentedWe never had anything similar, splitting a table by module. We can do that later for now all I care about is getting it in fast.
Comment #102
pwolanin commented@chx - if I understand Stephen correctly, his suggestion is not to split the table, but rather that all Drupal tables should have a name that indicates which module "owns" it.
Comment #103
chx commentedPlease, please get off table renaming especially sweeping ones and get this in finally!
Comment #104
chx commentedIt applies still but not cleanly so I reroll.
Comment #105
dries commentedI looked at the code once more, and I'm OK with it although I'm still not super-excited about the code comments. The function explanation seems decent, but the code comments in the function bodies seems somewhat lacking.
My review:
1. In the code comments, 'fail' should probably be 'fall'.
2. Elsewhere in Drupal core's database schema we use 'link' and not 'href' to denote a hyperlink.
3. 'user..' should be 'user.'.
4. "This function translates the objects in the map to current values with helper functions." -- Can we make this slightly more concrete? What are 'current values with helper functions'?
5. "When rendering a menu link, this function is similar to _menu_translate() but does other link-specific preparation." -- What is this supposed to mean? Please specify how it is different. In other words, what are these 'other link-specific preparations'?
6. "and dynamic paths are also changed accordingly." -- What do you mean with this? How are dynamic paths changed, and why?
7. "Build the data representing a menu tree" -- Sentence lacks trailing point.
8. In the comment module we use 'cid' and 'pid'. Here we use 'mlid' and 'plid'. I'd prefer to use 'mid' and 'pid' here.
9. 'has_children' should be 'children' -- we don't use 'is_hidden' either. We simply use 'hidden'.
10. 'link_title' should be 'title'.
Also, I'd like one other core developer to review the next iteration of this patch before I commit it. This will help guarantee that the code (and the code comments) make the new menu system accessible to new people. I want this person to look at the code changes and the code comments, not just click around a bit. I think this is important because we want to avoid a scenario where only 1 or 2 people can debug a menu system problem. This is an important patch so I'd appreciate it this could bubble to the top of our todo lists. Thanks.
Needless to say, this patch is pretty an amazing step forward. We're just trying to perfect it through review. :)
Comment #106
chx commentedI would keep title and link_title separate. For the other requests, I am too tired to even try to explain this in code comments properly. We have a handbook page and that should suffice . But of course not. However, I am not going to write these comments today. Or ever. I do not know.
Comment #107
pwolanin commented@Dries:
re: #2
I made this 'href' to be consistent with the usage by hook_link. This column can contain either a valid site path or an external URL. In other words, the $path parameter for l(). Aggregator uses uses 'link' as a column name, but I think this is always an external URL. Drupal module (which is being removed/changes?) uses it, but again it's for a URL which is always external to the site. I think {url_alias} used it at one point, but now seems to be 'src' and 'dst'.
re: #8.
I disagree for two reasons. 1) As a new developer, I found the re-use of column names for unrelated IDs (e.g. vid =node revision ID or vid= vocabulary ID) a very real and unnecessary impediment to understanding the schema. 2) Karoly suggested the change to make the different role clear to developers who are familiar with the previous menu system schema.
re: #10.
The column cannot easily be renamed 'title' since in the code we join to {menu} which has a 'title' column.
Comment #108
chx commentedYes. I stand for mlid/plid. At one point we were discussing this patch over the phone and my mind just blowed from the name of the identifiers. Imagine someone who is not neck deep into this.
#9. 'has_children' should be 'children' -- we don't use 'is_hidden' either. We simply use 'hidden'.
No. Look at #children in drupal_render -- from 'children' I would expect to hold the actual children, this is just a flag.
Comment #109
pwolanin commentedOk, I worked on some of the comments, and fixed another minor bug or two. In particular, the default install profile didn't do a menu_rebuild. A menu rebuild needs to happen every time a new node type is added since a new router item is defined.
Also made the naming of the tables consistent: {menu_links} and {menu_router}. Thus, mlid and plid make sense as "menu link ID" and "parent link ID".
Put a temporary piece of code in system.module to prevent menu module from ever being activated.
Comment #110
chx commentedWorked on the comments w/ Andre Molnar.
Comment #111
andremolnar commentedfixed a couple of tiny typos
Comment #112
dries commentedI don't think my previous review is properly addressed. Some of them still stand.
Comment #113
pwolanin commented@Dries - can you be specific and/or address the arguments Karoly and I made?
Comment #114
dries commentedI'd like to see has_children renamed to children, and I'd like to see mlid and plid renamed to mid and pid, respectively.
Why do both routers and menu items need titles?
It's not clear whether andre actually understood the menu system and/or approves the changes.
Comment #115
chx commentedChildren is expected to be a data structure that holds stuff with a common parent. is_children is a flag whether such entities exist. title in menu_router is defined in hook_menu, can be run through t(), title menu_links is entered by the user, not suited for t(). Andre seemed to understand the changes...
Comment #116
chx commentedAlso, given that menu_router and menu_item get joined occassionally, renaming mlid and plid to mid and pid is a very bad idea, I believe.
Comment #117
pwolanin commented@Dries -
'has_children' to 'children': I think Karoly's point is worth considering, but I don't feel strongly.
title in {menu_router} is used to generate the page or tab title - this is defined by hook_menu
link_title in {menu_links} is used to generate the link title in the visible menu block and breadcrumb and will (eventually) be changeable by the user using menu module, etc. Using the example of node links- every one would typically have the node title as the link_title, but every one will join to {menu_router} with path 'node/%/view' which is what makes the title 'View' show up on the default tab.
See my reasoning above for my mlid and plid are better.
Comment #118
riccardoR commentedPatch no longer applies because of http://drupal.org/node/137767 committed to HEAD.
Comment #119
riccardoR commentedI've just noticed that a warning is displayed every time menu_rebuild() gets called :
The reason is menu_cache_clear_all() still referencing the old menu table.
The following modification seems to work correctly:
Comment #120
Crell commentedI just went through and read menu.inc pretty much top to bottom. I think it has to be the most documented component we have now. :-) By and large I do understand what is happening (although I was at chx's talk at DrupalCon), and like what I see.
Regarding mlid & plid: I say keep them as is. Requiring 3 character primary keys leads to far too much confusion, especially if one has to start joining things. vid and vid is annoying enough. We don't need mid and mid and pid and pid, too.
Regarding has_children: $has_children is used throughout the PHP code. The database column should match it for clarity. Using $children would be horribly confusing when dealing with this sort of data structure. I don't have a problem with a has_children boolean column.
Attached patch is a reroll against HEAD that fixes the items riccardo noted.
Comment #121
ChrisKennedy commentedThere are several typos and a modified settings.php included in #120. A lot of the comments would look better if they included a period to end the sentence too.
includes/menu.inc
If the link title matches that of a router item, locaize it. -> localize
An error occured loading an object -> occurred
see http://drupal.org/node/141866 for more. -> more information?
Don't show a link to the current page in the bc trail -> breadcrumb
We are on a tab -> should be on a new line.
TODO how do we hadle the case -> handle
theme_menu_local_tasks() -> doxygen indentation is off by one space.
Remove those items which router path does not exist any more -> Remove items if their router path does not exist any more
Mandatory key is only href and link_title. -> The only mandatory keys are href and link_title.
Find the router path which will serve this paths. -> "these paths" or "this path"
separate callbacks from pathes, making pathes ready for matching -> paths
Comment #122
chx commentedAgain I feel like in http://drupal.org/node/77919#comment-125579 -- why it is always my patches that get scrutinized to the last comma when incredibly broken stuff can get in just to be removed later (I will not name specifics, but it did happen lately). Nevermind. I am just grumpy as usual.
Comment #123
chx commentedReally nevermind. Drupal will get better and at one point I will get the patience needed for it... just there is little time until the freeze (my linked comment was also before a bit more than ten days before the freeze) and there is so much to be done.
Comment #124
dries commentedAlright, committed to CVS HEAD. :)
Marking this as 'code needs work' until the documentation has been written.
Comment #125
bjaspan commentedThis prevents HEAD from installing on postgres. Patch attached. The patch makes it install cleanly but I can't promise there are not other pgsql-related issues.
FYI, now that I'm paying careful attention to pgsql, I've noticed several core patches recently that create id columns as type int, not type serial, for pgsql. Remember, db_next_id() for pgsql *requires* that any column it is called to generate an id for be type 'serial'. This requirement has nothing to do with the schema patch.
Comment #126
moshe weitzman commentedComment #127
dries commentedCommitted. Thanks! :)
Comment #128
pwolanin commentedI've started editing Karoly's documentation here: http://drupal.org/node/102338
Also, just opened a new follow-on issue for menu module, etc: http://drupal.org/node/144753
Comment #129
pwolanin commentedSplit the follow-on patch into two parts: the one referenced above for menu.module, and this one for the necessary changes to menu.inc to support menu module: http://drupal.org/node/145058
Comment #130
(not verified) commented