1
2
3
4 """
5 API for the Drupal menu system.
6
7 @package includes
8 @see <a href='http://drupy.net'>Drupy Homepage</a>
9 @see <a href='http://drupal.org'>Drupal Homepage</a>
10 @note Drupy is a port of the Drupal project.
11 @note This file was ported from Drupal's includes/menu.inc
12 @author Brendon Crawford
13 @copyright 2008 Brendon Crawford
14 @contact message144 at users dot sourceforge dot net
15 @created 2008-05-22
16 @version 0.1
17 @note License:
18
19 This program is free software; you can redistribute it and/or
20 modify it under the terms of the GNU General Public License
21 as published by the Free Software Foundation; either version 2
22 of the License, or (at your option) any later version.
23
24 This program is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 GNU General Public License for more details.
28
29 You should have received a copy of the GNU General Public License
30 along with this program; if not, write to:
31
32 The Free Software Foundation, Inc.,
33 51 Franklin Street, Fifth Floor,
34 Boston, MA 02110-1301,
35 USA
36 """
37
38 __version__ = "$Revision: 1 $"
39
40 from lib.drupy import DrupyPHP as php
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 MENU_IS_ROOT = 0x0001
117
118
119
120
121 MENU_VISIBLE_IN_TREE = 0x0002
122
123
124
125
126 MENU_VISIBLE_IN_BREADCRUMB = 0x0004
127
128
129
130
131
132 MENU_LINKS_TO_PARENT = 0x0008
133
134
135
136
137 MENU_MODIFIED_BY_ADMIN = 0x0020
138
139
140
141
142 MENU_CREATED_BY_ADMIN = 0x0040
143
144
145
146
147 MENU_IS_LOCAL_TASK = 0x0080
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 MENU_NORMAL_ITEM = MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB
166
167
168
169
170
171
172 MENU_CALLBACK = MENU_VISIBLE_IN_BREADCRUMB
173
174
175
176
177
178
179
180
181 MENU_SUGGESTED_ITEM = MENU_VISIBLE_IN_BREADCRUMB | 0x0010
182
183
184
185
186
187
188 MENU_LOCAL_TASK = MENU_IS_LOCAL_TASK
189
190
191
192
193
194
195 MENU_DEFAULT_LOCAL_TASK = MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT
196
197
198
199
200
201
202
203
204
205
206
207
208 MENU_FOUND = 1
209
210
211
212
213 MENU_NOT_FOUND = 2
214
215
216
217
218 MENU_ACCESS_DENIED = 3
219
220
221
222
223 MENU_SITE_OFFLINE = 4
224
225
226
227
228
229
230
231
232
233
234
235
236 MENU_MAX_PARTS = 7
237
238
239
240 MENU_MAX_DEPTH = 9
241
242
244 """
245 @ End of "Menu tree parameters".
246 Returns the ancestors (and relevant placeholders) for any given path.
247
248 For example, the ancestors of node/12345/edit are:
249 - node/12345/edit
250 - node/12345/%
251 - node/%/edit
252 - node/%/%
253 - node/12345
254 - node/%
255 - node
256
257 To generate these, we will use binary numbers. Each bit represents a
258 part of the path. If the bit is 1, then it represents the original
259 value while 0 means wildcard. If the path is node/12/edit/foo
260 then the 1011 bitstring represents node/%/edit/foo where % means that
261 any argument matches that part. We limit ourselves to using binary
262 numbers that correspond the patterns of wildcards of router items that
263 actually exists. This list of 'masks' is built in menu_rebuild().
264
265 @param parts
266 An array of path parts, for the above example
267 array('node', '12345', 'edit').
268 @return
269 An array which contains the ancestors and placeholders. Placeholder
270 simply contain as many '%s' as the ancestors.
271 """
272 number_parts = php.count(parts)
273 placeholders = []
274 ancestors = []
275 length = number_parts - 1
276 end = (1 << number_parts) - 1
277 masks = variable_get('menu_masks', [])
278
279 for i in masks:
280 if (i > end):
281
282 continue
283 elif (i < (1 << length)):
284
285 length -= 1
286 current = ''
287 for j in range(length, -1, -1):
288 if (i & (1 << j)):
289 current += parts[length - j]
290 else:
291 current += '%'
292 if (j):
293 current += '/'
294 placeholders.append( "'%s'" )
295 ancestors.append( current )
296 return (ancestors, placeholders)
297
298
299
301 """
302 The menu system uses serialized arrays stored in the database for
303 arguments. However, often these need to change according to the
304 current path. This function unserializes such an array and does the
305 necessary change.
306
307 Integer values are mapped according to the map parameter. For
308 example, if php.unserialize(data) is array('view', 1) and map is
309 array('node', '12345') then 'view' will not be changed because
310 it is not an integer, but 1 will as it is an integer. As map[1]
311 is '12345', 1 will be replaced with '12345'. So the result will
312 be array('node_load', '12345').
313
314 @param @data
315 A serialized array.
316 @param @map
317 An array of potential replacements.
318 @return
319 The data array unserialized and mapped.
320 """
321 data = php.unserialize(data)
322 if (data):
323 for k,v in data.items():
324 if (is_int(v)):
325 data[k] = (map[v] if php.isset(map, v) else '')
326 return data
327 else:
328 return []
329
330
331
333 """
334 Replaces the statically cached item for a given path.
335 @param path
336 The path.
337 @param router_item
338 The router item. Usually you take a router entry from menu_get_item and
339 set it back either modified or to a different path. This lets you modify
340 the navigation block, the page title, the breadcrumb and the page help in
341 one call.
342 """
343 menu_get_item(path, router_item)
344
345
346
347 -def get_item(path = None, router_item = None):
348 """
349 Get a router item.
350
351 @param path
352 The path, for example node/5. The function will find the corresponding
353 node/% item and return that.
354 @param router_item
355 Internal use only.
356 @return
357 The router item, an associate array corresponding to one row in the
358 menu_router table. The value of key map holds the loaded objects. The
359 value of key access is True if the current user can access this page.
360 The values for key title, page_arguments, access_arguments will be
361 filled in based on the database values and the objects loaded.
362 """
363 php.static(menu_get_item, 'router_items')
364 if (not php.isset(path)):
365 path = php.GET['q']
366 if (router_item != None):
367 menu_get_item.router_items[path] = router_item
368 if (not php.isset(menu_get_item.router_items, path)):
369 original_map = arg(None, path)
370 parts = php.array_slice(original_map, 0, MENU_MAX_PARTS)
371 (ancestors, placeholders) = menu_get_ancestors(parts)
372 router_item = db_fetch_array(db_query_range(\
373 'SELECT * FROM {menu_router} ' + \
374 'WHERE path IN (' + implode (',', placeholders) + ') ' + \
375 'ORDER BY fit DESC', ancestors, 0, 1))
376 if (router_item):
377 map_ = _menu_translate(router_item, original_map)
378 if (map_ == False):
379 menu_get_item.router_items[path] = False
380 return False
381 if (router_item['access']):
382 router_item['map'] = map
383 router_item['page_arguments'] = \
384 php.array_merge(menu_unserialize(\
385 router_item['page_arguments'], map), \
386 php.array_slice(map, router_item['number_parts']))
387 menu_get_item.router_items[path] = router_item
388 return menu_get_item.router_items[path]
389
390
391
409
410
411
413 """
414 Loads objects into the map as defined in the item['load_functions'].
415
416 @param item
417 A menu router or menu link item
418 @param map
419 An array of path arguments (ex: array('node', '5'))
420 @return
421 Returns True for success, False if an object cannot be loaded.
422 Names of object loading functions are placed in item['load_functions'].
423 Loaded objects are placed in map[]; keys are the same as keys in the
424 item['load_functions'] array.
425 item['access'] is set to False if an object cannot be loaded.
426 """
427 php.Reference.check(item)
428 php.Reference.check(map_)
429 load_functions = item['load_functions']
430 if (load_functions):
431
432 load_functions_unserialized = php.unserialize(load_functions)
433 if (load_functions_unserialized):
434 load_functions = load_functions_unserialized
435 path_map = map_
436 for index,function in load_functions.items():
437 if (function):
438 value = (path_map[index] if php.isset(path_map, index) else '')
439 if (php.is_array(function)):
440
441
442
443
444 function, args = php.each(function)
445 load_functions[index] = function
446
447 for i,arg in args.items():
448 if (arg == '%index'):
449
450
451 args[i] = index
452 if (arg == '%map'):
453
454
455
456 args[i] = map_
457 if (is_int(arg)):
458 args[i] = (path_map[arg] if php.isset(path_map, arg) else '')
459 array_unshift(args, value)
460 return_ = function(*args)
461 else:
462 return_ = function(value)
463
464 if (return_ == False):
465 item['access'] = False
466 for i in range(len(map_)):
467 map_.pop()
468 return False
469 map[index] = return_
470 item['load_functions'] = load_functions
471 return True
472
473
474
476 """
477 Check access to a menu item using the access callback
478
479 @param item
480 A menu router or menu link item
481 @param map
482 An array of path arguments (ex: array('node', '5'))
483 @return
484 item['access'] becomes True if the item is accessible, False otherwise.
485 """
486 php.Reference.check(item)
487
488
489 callback = (0 if php.empty(item['access_callback']) else \
490 php.trim(item['access_callback']))
491
492 if (php.is_numeric(callback)):
493 item['access'] = p.bool_(callback)
494 else:
495 arguments = menu_unserialize(item['access_arguments'], map_)
496
497
498 if (callback == 'user_access'):
499 item['access'] = (user_access(arguments[0]) if \
500 (php.count(arguments) == 1) else \
501 user_access(arguments[0], arguments[1]))
502 else:
503 item['access'] = callback(*arguments)
504
505
506
508 """
509 Localize the router item title using t() or another callback.
510
511 Translate the title and description to allow storage of English title
512 strings in the database, yet display of them in the language required
513 by the current user.
514
515 @param item
516 A menu router item or a menu link item.
517 @param map
518 The path as an array with objects already replaced. E.g., for path
519 node/123 map would be array('node', node) where node is the node
520 object for node 123.
521 @param link_translate
522 True if we are translating a menu link item; False if we are
523 translating a menu router item.
524 @return
525 No return value.
526 item['title'] is localized according to item['title_callback'].
527 If an item's callback is check_plain(), item['options']['html'] becomes
528 True.
529 item['description'] is translated using t().
530 When doing link translation and the item['options']['attributes']['title']
531 (link title attribute) matches the description, it is translated as well.
532 """
533 php.Reference.check(item)
534 callback = item['title_callback']
535 item['localized_options'] = item['options']
536
537
538 if (not link_translate or (not php.empty(item['title']) and \
539 (item['title'] == item['link_title']))):
540
541
542 if (callback == 't'):
543 if (php.empty(item['title_arguments'])):
544 item['title'] = t(item['title'])
545 else:
546 item['title'] = t(item['title'], \
547 menu_unserialize(item['title_arguments'], map_))
548 elif (callback):
549 if (php.empty(item['title_arguments'])):
550 item['title'] = callback(item['title'])
551 else:
552 item['title'] = \
553 callback(*menu_unserialize(item['title_arguments'], map_))
554
555 if (callback == 'check_plain'):
556 item['localized_options']['html'] = True
557 elif (link_translate):
558 item['title'] = item['link_title']
559
560 if (not php.empty(item['description'])):
561 original_description = item['description']
562 item['description'] = t(item['description'])
563 if (link_translate and \
564 php.isset(item['options']['attributes'], 'title') and \
565 item['options']['attributes']['title'] == original_description):
566 item['localized_options']['attributes']['title'] = \
567 item['description']
568
569
570
571 -def _translate(router_item, map_, to_arg = False):
572 """
573 Handles dynamic path translation and menu access control.
574
575 When a user arrives on a page such as node/5, this function determines
576 what "5" corresponds to, by inspecting the page's menu path definition,
577 node/%node. This will call node_load(5) to load the corresponding node
578 object.
579
580 It also works in reverse, to allow the display of tabs and menu items which
581 contain these dynamic arguments, translating node/%node to node/5.
582
583 Translation of menu item titles and descriptions are done here to
584 allow for storage of English strings in the database, and translation
585 to the language required to generate the current page
586
587 @param router_item
588 A menu router item
589 @param map
590 An array of path arguments (ex: array('node', '5'))
591 @param to_arg
592 Execute item['to_arg_functions'] or not. Use only if you want to render a
593 path from the menu table, for example tabs.
594 @return
595 Returns the map with objects loaded as defined in the
596 item['load_functions. item['access'] becomes True if the item is
597 accessible, False otherwise. item['href'] is set according to the map.
598 If an error occurs during calling the load_functions (like trying to load
599 a non existing node) then this function return False.
600 """
601 php.Reference.check(router_item)
602 path_map = map_
603 if (not _menu_load_objects(router_item, map_)):
604
605 router_item['access'] = False
606 return False
607 if (to_arg):
608 _menu_link_map_translate(path_map, router_item['to_arg_functions'])
609
610 link_map = php.explode('/', router_item['path'])
611 for i in range(router_item['number_parts']):
612 if (link_map[i] == '%'):
613 link_map[i] = path_map[i]
614 router_item['href'] = php.implode('/', link_map)
615 router_item['options'] = {}
616 _menu_check_access(router_item, map_)
617
618 if (router_item['access']):
619 _menu_item_localize(router_item, map_)
620 return map_
621
622
623
625 """
626 This function translates the path elements in the map using any to_arg
627 helper function. These functions take an argument and return an object.
628 See http://drupal.org/node/109153 for more information.
629
630 @param map
631 An array of path arguments (ex: array('node', '5'))
632 @param to_arg_functions
633 An array of helper function (ex: array(2 : 'menu_tail_to_arg'))
634 """
635 php.Reference.check(map_)
636 if (to_arg_functions):
637 to_arg_functions = php.unserialize(to_arg_functions)
638 for index,function in to_arg_functions.items():
639
640 arg = function((map_[index] if \
641 (not php.empty(map_[index])) else ''), map_, index)
642 if (not php.empty(map[index]) or php.isset(arg)):
643 map_[index] = arg
644 else:
645 del(map_[index])
646
647
648
651
652
653
655 """
656 This function is similar to menu_translate_() but does link-specific
657 preparation such as always calling to_arg functions
658
659 @param item
660 A menu link
661 @return
662 Returns the map of path arguments with objects loaded as defined in the
663 item['load_functions'].
664 item['access'] becomes True if the item is accessible, False otherwise.
665 item['href'] is generated from link_path, possibly by to_arg functions.
666 item['title'] is generated from link_title, and may be localized.
667 item['options'] is unserialized; it is also changed within the call here
668 to item['localized_options'] by _menu_item_localize().
669 """
670 php.Reference.check(item)
671 item['options'] = php.unserialize(item['options'])
672 if (item['external']):
673 item['access'] = 1
674 map_ = {}
675 item['href'] = item['link_path']
676 item['title'] = item['link_title']
677 item['localized_options'] = item['options']
678 else:
679 map_ = php.explode('/', item['link_path'])
680 _menu_link_map_translate(map_, item['to_arg_functions'])
681 item['href'] = php.implode('/', map_)
682
683 if (php.strpos(item['href'], '%') != False):
684 item['access'] = False
685 return False
686
687 if (not php.isset(item['access'])):
688 if (not _menu_load_objects(item, map_)):
689
690 item['access'] = False
691 return False
692 _menu_check_access(item, map)
693 _menu_item_localize(item, map_, True)
694
695 if (item['access']):
696 _menu_item_localize(item, map_, True)
697
698
699
700
701 if (not php.empty(item['options']['alter'])):
702 drupal_alter('translated_menu_link', item, map_)
703 return map_
704
705
706
707 -def get_object(type_ = 'node', position = 1, path = None):
708 """
709 Get a loaded object from a router item.
710
711 menu_get_object() will provide you the current node on paths like node/5,
712 node/5/revisions/48 etc. menu_get_object('user') will give you the user
713 account on user/5 etc. Note - this function should never be called within a
714 _to_arg function (like user_current_to_arg()) since this may result in an
715 infinite recursion.
716
717 @param type
718 Type of the object. These appear in hook_menu definitons as %type. Core
719 provides aggregator_feed, aggregator_category, contact, filter_format,
720 forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the
721 relevant {type}_load function for more on each. Defaults to node.
722 @param position
723 The expected position for type object. For node/%node this is 1, for
724 comment/reply/%node this is 2. Defaults to 1.
725 @param path
726 See menu_get_item() for more on this. Defaults to the current path.
727 """
728 router_item = menu_get_item(path)
729 if (php.isset(router_item['load_functions'], position) and not \
730 php.empty(router_item['map'][position]) and \
731 router_item['load_functions'][position] == type_ + '_load'):
732 return router_item['map'][position]
733
734
735
736 -def tree(menu_name = 'navigation'):
737 """
738 Render a menu tree based on the current path.
739
740 The tree is expanded based on the current path and dynamic paths are also
741 changed according to the defined to_arg functions
742 (for example the 'My account'
743 link is changed from user/% to a link with the current user's uid).
744
745 @param menu_name
746 The name of the menu.
747 @return
748 The rendered HTML of that menu on the current page.
749 """
750 php.static(menu_tree, 'menu_output', {})
751 if (not php.isset(menu_tree.menu_output, menu_name)):
752 tree = menu_tree_page_data(menu_name)
753 menu_tree.menu_output[menu_name] = menu_tree_output(tree)
754 return menu_tree.menu_output[menu_name]
755
756
757
759 """
760 Returns a rendered menu tree.
761
762 @param tree
763 A data structure representing the tree as returned from menu_tree_data.
764 @return
765 The rendered HTML of that data structure.
766 """
767 output = ''
768 items = {}
769
770
771 for data in tree:
772 if (not data['link']['hidden']):
773 items.append( data )
774 num_items = php.count(items)
775 for i,data in items.items():
776 extra_class = None
777 if (i == 0):
778 extra_class = 'first'
779 if (i == num_items - 1):
780 extra_class = 'last'
781 link = theme('menu_item_link', data['link'])
782 if (data['below']):
783 output += theme('menu_item', link, data['link']['has_children'], \
784 menu_tree_output(data['below']), \
785 data['link']['in_active_trail'], extra_class)
786 else:
787 output += theme('menu_item', link, data['link']['has_children'], \
788 '', data['link']['in_active_trail'], extra_class)
789 return (theme('menu_tree', output) if output else '')
790
791
792
794 """
795 Get the data structure representing a named menu tree.
796
797 Since this can be the full tree including hidden items, the data returned
798 may be used for generating an an admin interface or a select.
799
800 @param menu_name
801 The named menu links to return
802 @param item
803 A fully loaded menu link, or None. If a link is supplied, only the
804 path to root will be included in the returned tree- as if this link
805 represented the current page in a visible menu.
806 @return
807 An tree of menu links in an array, in the order they should be rendered.
808 """
809 php.static(menu_tree_all_data, 'tree', {})
810 data = None
811
812 mlid = (item['mlid'] if php.isset(item, 'mlid') else 0)
813
814 cid = 'links:' + menu_name + ':all-cid:' + mlid
815 if (not php.isset(menu_tree_all_data.tree, cid)):
816
817 cache = cache_get(cid, 'cache_menu')
818 if (cache and php.isset(cache, 'data')):
819
820
821 cache = cache_get(cache.data, 'cache_menu')
822 if (cache and php.isset(cache, 'data')):
823 data = cache.data
824
825 if (not php.isset(data)):
826
827 if (mlid):
828
829
830 args = [0]
831 for i in range(1, MENU_MAX_DEPTH):
832 args.append( item["pi"] )
833 args = array_unique(args)
834 placeholders = php.implode(', ', php.array_fill(0, \
835 php.count(args), '%d'))
836 where = ' AND ml.plid IN (' + placeholders + ')'
837 parents = args
838 parents.append( item['mlid'] )
839 else:
840
841 where = ''
842 args = {}
843 parents = {}
844 array_unshift(args, menu_name)
845
846
847
848 data['tree'] = menu_tree_data(db_query( \
849 "SELECT m.load_functions, m.to_arg_functions, m.access_callback, " + \
850 "m.access_arguments, m.page_callback, m.page_arguments, m.title, " + \
851 "m.title_callback, m.title_arguments, m.type, m.description, ml.* " + \
852 "FROM {menu_links} ml " + \
853 "LEFT JOIN {menu_router} m ON m.path = ml.router_path " + \
854 "WHERE ml.menu_name = '%s'" + where + \
855 "ORDER BY p1 ASC, p2 ASC, p3 ASC, " + \
856 " p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, " + \
857 " p9 ASC", \
858 args), parents)
859 data['node_links'] = {}
860 menu_tree_collect_node_links(data['tree'], data['node_links'])
861
862 tree_cid = _menu_tree_cid(menu_name, data)
863 if (not cache_get(tree_cid, 'cache_menu')):
864 cache_set(tree_cid, data, 'cache_menu')
865
866
867 cache_set(cid, tree_cid, 'cache_menu')
868
869 menu_tree_check_access(data['tree'], data['node_links'])
870 menu_tree_all_data.tree[cid] = data['tree']
871 return menu_tree_all_data.tree[cid]
872
873
874
875 -def tree_page_data(menu_name = 'navigation'):
876 """
877 Get the data structure representing a named
878 menu tree, based on the current page.
879
880 The tree order is maintained by storing each parent in an individual
881 field, see http://drupal.org/node/141866 for more.
882
883 @param menu_name
884 The named menu links to return
885 @return
886 An array of menu links, in the order they should be rendered. The array
887 is a list of associative arrays -- these have two keys, link and below.
888 link is a menu item, ready for theming as a link. Below represents the
889 submenu below the link if there is one, and it is a subtree that has the
890 same structure described for the top-level array.
891 """
892 php.static(menu_tree_page_data, 'tree', {})
893
894 data = None
895 item = menu_get_item()
896 if (item):
897
898 cid = 'links:' + menu_name + ':page-cid:' + item['href'] + ':' + \
899 int(item['access'])
900 if (not php.isset(tree, cid)):
901
902 cache = cache_get(cid, 'cache_menu')
903 if (cache and php.isset(cache, 'data')):
904
905
906
907 cache = cache_get(cache.data, 'cache_menu')
908 if (cache and php.isset(cache, 'data')):
909 data = cache.data
910
911 if (not php.isset(data)):
912
913 if (item['access']):
914
915
916 args = array(menu_name, item['href'])
917 placeholders = "'%s'"
918 if (drupal_is_front_page()):
919 args.append( '<front>' )
920 placeholders += ", '%s'"
921 parents = db_fetch_array(db_query(\
922 "SELECT p1, p2, p3, p4, p5, p6, p7, p8 " + \
923 "FROM {menu_links} " + \
924 "WHERE menu_name = '%s' AND " + \
925 "link_path IN (" + placeholders + ")", args))
926 if (php.empty(parents)):
927
928
929
930
931 parents = db_fetch_array(db_query(\
932 "SELECT p1, p2, p3, p4, p5, p6, p7, p8 " + \
933 "FROM {menu_links} " + \
934 "WHERE menu_name = '%s' AND " + \
935 "link_path = '%s'", menu_name, item['tab_root']))
936
937 parents.append( '0' )
938
939
940 args = parents = array_unique(array_values(parents))
941 placeholders = php.implode(', ', php.array_fill(0, \
942 php.count(args), '%d'))
943 expanded = variable_get('menu_expanded', [])
944
945 if (php.in_array(menu_name, expanded)):
946
947
948 while True:
949 result = db_query( \
950 "SELECT mlid FROM {menu_links} WHERE menu_name = '%s' " + \
951 "AND expanded = 1 AND has_children = 1 AND plid IN (" + \
952 placeholders + \
953 ") AND mlid NOT IN (" + \
954 placeholders + \
955 ")", php.array_merge(array(menu_name), args, args))
956 num_rows = False
957 while True:
958 item = db_fetch_array(result)
959 if not item:
960 break
961 args.append( item['mlid'] )
962 num_rows = True
963 placeholders = php.implode(', ', php.array_fill(0, \
964 php.count(args), '%d'))
965 if not num_rows:
966 break
967 array_unshift(args, menu_name)
968 else:
969
970 args = array(menu_name, '0')
971 placeholders = '%d'
972 parents = {}
973
974
975
976 data['tree'] = menu_tree_data(db_query( \
977 "SELECT " + \
978 " m.load_functions, m.to_arg_functions, m.access_callback, " + \
979 " m.access_arguments, m.page_callback, " + \
980 " m.page_arguments, m.title, m.title_callback, " + \
981 " m.title_arguments, m.type, m.description, ml.* " + \
982 "FROM {menu_links} ml " + \
983 "LEFT JOIN {menu_router} m ON m.path = ml.router_path " + \
984 "WHERE ml.menu_name = '%s' AND ml.plid IN " + \
985 " (" + placeholders + ") " + \
986 "ORDER BY p1 ASC, p2 ASC, p3 ASC, " + \
987 " p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, " + \
988 " p9 ASC", \
989 args), parents)
990 data['node_links'] = {}
991 menu_tree_collect_node_links(data['tree'], data['node_links'])
992
993 tree_cid = _menu_tree_cid(menu_name, data)
994 if (not cache_get(tree_cid, 'cache_menu')):
995 cache_set(tree_cid, data, 'cache_menu')
996
997 cache_set(cid, tree_cid, 'cache_menu')
998
999 menu_tree_check_access(data['tree'], data['node_links'])
1000 menu_tree_page_data.tree[cid] = data['tree']
1001 return menu_tree_page_data.tree[cid]
1002 return {}
1003
1004
1005
1007 """
1008 Helper function - compute the real cache ID for menu tree data.
1009 """
1010 return 'links:' + menu_name + ':tree-data:' + php.md5(php.serialize(data))
1011
1012
1013
1015 """
1016 Recursive helper function - collect node links.
1017 """
1018 php.Reference.check(tree)
1019 php.Reference.check(node_links)
1020 for key,v in tree.items():
1021 if (tree[key]['link']['router_path'] == 'node/%'):
1022 nid = php.substr(tree[key]['link']['link_path'], 5)
1023 if (php.is_numeric(nid)):
1024 node_links[nid][tree[key]['link']['mlid']] = tree[key]['link']
1025 tree[key]['link']['access'] = False
1026 if (tree[key]['below']):
1027 menu_tree_collect_node_links(tree[key]['below'], node_links)
1028
1029
1030
1032 """
1033 Check access and perform other dynamic operations for each link in the tree.
1034 """
1035 php.Reference.check(tree)
1036 if (not php.empty(node_links)):
1037
1038
1039 nids = php.array_keys(node_links)
1040 placeholders = '%d' + str_repeat(', %d', php.count(nids) - 1)
1041 result = db_query(db_rewrite_sql(\
1042 "SELECT n.nid FROM {node} n " + \
1043 "WHERE n.status = 1 AND n.nid IN (" + placeholders + ")"), nids)
1044 while True:
1045 node = db_fetch_array(result)
1046 if not node:
1047 break
1048 nid = node['nid']
1049 for mlid,link in node_links[nid].items():
1050 node_links[nid][mlid]['access'] = True
1051 _menu_tree_check_access(tree)
1052 return
1053
1054
1055
1057 """
1058 Recursive helper function for menu_tree_check_access()
1059 """
1060 php.Reference.check(tree)
1061 new_tree = {}
1062 for key,v in tree.items():
1063 item = tree[key]['link']
1064 _menu_link_translate(item)
1065 if (item['access']):
1066 if (tree[key]['below']):
1067 _menu_tree_check_access(tree[key]['below'])
1068
1069
1070
1071
1072 new_tree[(50000 + item['weight']) + ' ' + \
1073 item['title'] + ' ' + item['mlid']] = tree[key]
1074
1075 ksort(new_tree)
1076 php.array_merge(tree, new_tree, True)
1077
1078
1079
1080 -def tree_data(result = None, parents = {}, depth = 1):
1081 """
1082 Build the data representing a menu tree.
1083
1084 @param result
1085 The database result.
1086 @param parents
1087 An array of the plid values that represent the path from the current page
1088 to the root of the menu tree.
1089 @param depth
1090 The depth of the current menu tree.
1091 @return
1092 See menu_tree_page_data for a description of the data structure.
1093 """
1094 tree = _menu_tree_data(result, parents, depth)[1]
1095 return tree
1096
1097
1098
1099 -def _tree_data(result, parents, depth, previous_element = ''):
1100 """
1101 Recursive helper function to build the data representing a menu tree.
1102
1103 The function is a bit complex because the rendering of an item depends on
1104 the next menu item. So we are always rendering the element previously
1105 processed not the current one.
1106 """
1107 remnant = None
1108 tree = {}
1109 while True:
1110 item = db_fetch_array(result)
1111 if not item:
1112 break
1113
1114
1115 item['in_active_trail'] = php.in_array(item['mlid'], parents)
1116
1117 if (item['depth'] > depth):
1118
1119 item, below = _menu_tree_data(result, parents, item['depth'], item)
1120 if (previous_element):
1121 tree[previous_element['mlid']] = {
1122 'link': previous_element,
1123 'below': below,
1124 }
1125 else:
1126 tree = below
1127
1128 if (item == None or item['depth'] < depth):
1129 return [item, tree]
1130
1131 previous_element = item
1132
1133 elif (item['depth'] == depth):
1134 if (previous_element):
1135
1136 tree[previous_element['mlid']] = {
1137 'link': previous_element,
1138 'below': False,
1139 }
1140
1141 previous_element = item
1142
1143 else:
1144 remnant = item
1145 break
1146 if (previous_element):
1147
1148 tree[previous_element['mlid']] = {
1149 'link': previous_element,
1150 'below': False,
1151 }
1152 return [remnant, tree]
1153
1154
1155
1157 """
1158 Generate the HTML output for a single menu link.
1159
1160 @ingroup themeable
1161 """
1162 if (php.empty(link['localized_options'])):
1163 link['localized_options'] = {}
1164 return l(link['title'], link['href'], link['localized_options'])
1165
1166
1167
1169 """
1170 Generate the HTML output for a menu tree
1171
1172 @ingroup themeable
1173 """
1174 return '<ul class="menu">' + tree + '</ul>'
1175
1176
1177
1180 """
1181 Generate the HTML output for a menu item and submenu.
1182
1183 @ingroup themeable
1184 """
1185 class_ = ('expanded' if not php.empty(menu) else \
1186 ('collapsed' if has_children else 'leaf'))
1187 if (not php.empty(extra_class)):
1188 class_ += ' ' + extra_class
1189 if (in_active_trail):
1190 class_ += ' active-trail'
1191 return '<li class="' + class_ + '">' + link + menu + "</li>\n"
1192
1193
1194
1195
1197 """
1198 Generate the HTML output for a single local task link.
1199
1200 @ingroup themeable
1201 """
1202 return '<li ' + ('class="active" ' if active else '') + \
1203 '>' + link + "</li>\n"
1204
1205
1206
1208 """
1209 Generates elements for the arg array in the help hook.
1210 """
1211
1212 return arg + ['', '', '', '', '', '', '', '', '', '', '', '']
1213
1214
1215
1217 """
1218 Returns the help associated with the active menu item.
1219 """
1220 output = ''
1221 router_path = menu_tab_root_path()
1222 arg = drupal_help_arg(arg(None))
1223 empty_arg = drupal_help_arg()
1224 for name in plugin_list():
1225 if (plugin_hook(name, 'help')):
1226
1227 help = plugin_invoke(name, 'help', router_path, arg)
1228 if (help):
1229 output += help + "\n"
1230
1231
1232 if (arg[0] == "admin" and plugin_exists('help') and plugin_invoke(name, \
1233 'help', 'admin/help#' + arg[2], empty_arg) and help):
1234 output += theme("more_help_link", url('admin/help/' + arg[2]))
1235 return output
1236
1237
1238
1239
1241 """
1242 Build a list of named menus.
1243 """
1244 php.static(menu_get_names, 'names', [])
1245 if (reset or php.empty(menu_get_names.names)):
1246 result = db_query(\
1247 "SELECT DISTINCT(menu_name) FROM {menu_links} ORDER BY menu_name")
1248 while True:
1249 name = db_fetch_array(result)
1250 if name == None:
1251 break
1252 menu_get_names.names.append(name['menu_name'])
1253 return menu_get_names.names
1254
1255
1256
1258 """
1259 Return an array containing the names of system-defined (default) menus.
1260 """
1261 return ('navigation', 'main-menu', 'secondary-menu')
1262
1263
1265 """
1266 Return an array of links to be rendered as the Main menu.
1267 """
1268 return menu_navigation_links(\
1269 variable_get('menu_main_menu_source', 'main-menu'))
1270
1271
1273 """
1274 Return an array of links to be rendered as the Secondary links.
1275 """
1276
1277
1278 if (variable_get('menu_secondary_menu_source', 'secondary-menu') == \
1279 variable_get('menu_main_menu_source', 'main-menu')):
1280 return menu_navigation_links(\
1281 variable_get('menu_main_menu_source', 'main-menu'), 1)
1282 else:
1283 return menu_navigation_links(\
1284 variable_get('menu_secondary_menu_source', 'secondary-menu'), 0)
1285
1286
1288 """
1289 Return an array of links for a navigation menu.
1290
1291 @param menu_name
1292 The name of the menu.
1293 @param level
1294 Optional, the depth of the menu to be returned.
1295 @return
1296 An array of links of the specified menu and level.
1297 """
1298
1299 if (php.empty(menu_name)):
1300 return []
1301
1302 tree = menu_tree_page_data(menu_name)
1303
1304 while (level > 0 and tree):
1305 level -= 1
1306
1307
1308 while True:
1309 item = php.array_shift(tree)
1310 if php.empty(item):
1311 break
1312 if (item['link']['in_active_trail']):
1313
1314 tree = ([] if php.empty(item['below']) else item['below'])
1315 break
1316
1317 links = []
1318 for item in tree:
1319 if (not item['link']['hidden']):
1320 l = item['link']['localized_options']
1321 l['href'] = item['link']['href']
1322 l['title'] = item['link']['title']
1323 if (item['link']['in_active_trail']):
1324 if (php.empty(lib_common.l['attributes']['class'])):
1325 lib_common.l['attributes']['class'] = 'active-trail';
1326 else:
1327 lib_common.l['attributes']['class'] += ' active-trail';
1328
1329 links['menu-' + item['link']['mlid']] = l
1330 return links
1331
1332
1333
1335 """
1336 Collects the local tasks (tabs) for a given level.
1337
1338 @param level
1339 The level of tasks you ask for. Primary tasks are 0, secondary are 1.
1340 @param return_root
1341 Whether to return the root path for the current page.
1342 @return
1343 Themed output corresponding to the tabs of the requested level, or
1344 router path if return_root == True. This router path corresponds to
1345 a parent tab, if the current page is a default local task.
1346 """
1347 php.static(menu_local_tasks, 'tabs', [])
1348 php.static(menu_local_tasks, 'root_path')
1349 if (php.empty(menu_local_tasks.tabs)):
1350 router_item = menu_get_item()
1351 if (not router_item or not router_item['access']):
1352 return ''
1353
1354 result = db_query(\
1355 "SELECT * FROM {menu_router} " + \
1356 "WHERE tab_root = '%s' ORDER BY weight, title", router_item['tab_root'])
1357 map_ = arg()
1358 children = []
1359 tasks = []
1360 menu_local_tasks.rootpath = router_item['path']
1361 while True:
1362 item = db_fetch_array(result)
1363 if item == None:
1364 break
1365 _menu_translate(item, map_, True)
1366 if (item['tab_parent']):
1367
1368 children[item['tab_parent']][item['path']] = item
1369
1370 tasks[item['path']] = item
1371
1372 path = router_item['path']
1373
1374
1375
1376 depth = 1001
1377 while (php.isset(children, path)):
1378 tabs_current = ''
1379 next_path = ''
1380 count = 0
1381 for item in children[path]:
1382 if (item['access']):
1383 count += 1
1384
1385 if (item['type'] == MENU_DEFAULT_LOCAL_TASK):
1386
1387 p = item['tab_parent']
1388 while True:
1389 if tasks[p]['type'] != MENU_DEFAULT_LOCAL_TASK:
1390 break
1391 p = tasks[p]['tab_parent']
1392 link = theme('menu_item_link', {'href' : tasks[p]['href']} + item)
1393 tabs_current += theme('menu_local_task', link, True)
1394 next_path = item['path']
1395 else:
1396 link = theme('menu_item_link', item)
1397 tabs_current += theme('menu_local_task', link)
1398 path = next_path
1399 menu_local_tasks.tabs[depth]['count'] = count
1400 menu_local_tasks.tabs[depth]['output'] = tabs_current
1401 depth += 1
1402
1403 parent = router_item['tab_parent']
1404 path = router_item['path']
1405 current = router_item
1406 depth = 1000
1407 while (php.isset(children, parent)):
1408 tabs_current = ''
1409 next_path = ''
1410 next_parent = ''
1411 count = 0
1412 for item in children[parent]:
1413 if (item['access']):
1414 count += 1
1415 if (item['type'] == MENU_DEFAULT_LOCAL_TASK):
1416
1417 p = item['tab_parent']
1418 while True:
1419 if tasks[p]['type'] != MENU_DEFAULT_LOCAL_TASK:
1420 break
1421 p = tasks[p]['tab_parent']
1422 link = theme('menu_item_link', {'href' : tasks[p]['href']} + item)
1423 if (item['path'] == router_item['path']):
1424 menu_local_tasks.root_path = tasks[p]['path']
1425 else:
1426 link = theme('menu_item_link', item)
1427
1428 if (item['path'] == path):
1429 tabs_current += theme('menu_local_task', link, True)
1430 next_path = item['tab_parent']
1431 if (php.isset(tasks, next_path)):
1432 next_parent = tasks[next_path]['tab_parent']
1433 else:
1434 tabs_current += theme('menu_local_task', link)
1435 path = next_path
1436 parent = next_parent
1437 menu_local_tasks.tabs[depth]['count'] = count
1438 menu_local_tasks.tabs[depth]['output'] = tabs_current
1439 depth -= 1
1440
1441 menu_local_tasks.tabs = ksort(menu_local_tasks.tabs)
1442
1443 menu_local_tasks.tabs = array_values(menu_local_tasks.tabs.tabs)
1444 if (return_root):
1445 return menu_local_tasks.rootpath
1446 else:
1447
1448 return (menu_local_tasks.tabs[level]['output'] if \
1449 (php.isset(menu_local_tasks.tabs, level) and \
1450 menu_local_tasks.tabs[level]['count'] > 1) else '')
1451
1452
1453
1455 """
1456 Returns the rendered local tasks at the top level.
1457 """
1458 return menu_local_tasks(0)
1459
1460
1461
1463 """
1464 Returns the rendered local tasks at the second level.
1465 """
1466 return menu_local_tasks(1)
1467
1468
1469
1471 """
1472 Returns the router path, or the path of the parent tab of a default
1473 local task.
1474 """
1475 return menu_local_tasks(0, True)
1476
1477
1478
1480 """
1481 Returns the rendered local tasks. The default implementation renders
1482 them as tabs.
1483
1484 @ingroup themeable
1485 """
1486 output = ''
1487 primary = menu_primary_local_tasks()
1488 if (primary):
1489 output += "<ul class=\"tabs primary\">\n" + primary + "</ul>\n"
1490 secondary = menu_secondary_local_tasks()
1491 if (secondary):
1492 output += "<ul class=\"tabs secondary\">\n" + secondary + "</ul>\n"
1493 return output
1494
1495
1496
1498 """
1499 Set (or get) the active menu for the current page -
1500 determines the active trail.
1501 """
1502 php.static(menu_set_active_menu_name, 'active')
1503 if (menu_name != None):
1504 menu_set_active_menu_name.active = menu_name
1505 elif (menu_set_active_menu_name.active == None):
1506 menu_set_active_menu_name.active = 'navigation'
1507 return menu_set_active_menu_name.active
1508
1509
1510
1511
1513 """
1514 Get the active menu for the current page - determines the active trail.
1515 """
1516 return menu_set_active_menu_name()
1517
1518
1519
1521 """
1522 Set the active path, which determines which page is loaded.
1523
1524 @param path
1525 A Drupal path - not a path alias.
1526
1527 Note that this may not have the desired effect unless invoked very early
1528 in the page load, such as during hook_boot, or unless you call
1529 menu_execute_active_handler() to generate your page output.
1530 """
1531 php.GET['q'] = path
1532
1533
1534
1536 """
1537 Set (or get) the active trail for the current page -
1538 the path to root in the menu tree.
1539 """
1540 php.static(menu_set_active_trail, 'trail')
1541 if (new_trail != None):
1542 static_menusetactivetrail_trail = new_trail
1543 elif (static_menusetactivetrail_trail == None):
1544 menu_set_active_trail.trail = []
1545 menu_set_active_trail.trail.append( {'title': \
1546 t('Home'), 'href': '<front>', 'localized_options': {}, 'type': 0})
1547 item = menu_get_item()
1548
1549 if (item['tab_parent']):
1550
1551
1552
1553
1554 parts = php.explode('/', item['tab_root'])
1555 args = arg()
1556
1557 for index,part in parts.items():
1558 if (part == '%'):
1559 parts[index] = args[index]
1560
1561 root_item = menu_get_item(php.implode('/', parts))
1562 if (root_item and root_item['access']):
1563 item = root_item
1564 tree = menu_tree_page_data(menu_get_active_menu_name())
1565 key,curr = each(tree)
1566 while (curr):
1567
1568 if (curr['link']['href'] == item['href']):
1569 menu_set_active_trail.trail.append( curr['link'] )
1570 curr = False
1571 else:
1572
1573
1574 if (curr['link']['in_active_trail']):
1575 menu_set_active_trail.trail.append( curr['link'] )
1576 tree = (curr['below'] if curr['below'] else []);
1577 key,curr = p.each(tree)
1578
1579
1580 last = php.count(menu_set_active_trail.trail) - 1
1581 if (menu_set_active_trail.trail[last]['href'] != item['href'] and not \
1582 drupy_bool(item['type'] & MENU_IS_LOCAL_TASK) and \
1583 not drupal_is_front_page()):
1584 menu_set_active_trail.trail.append( item )
1585 return menu_set_active_trail.trail
1586
1587
1588
1590 """
1591 Get the active trail for the current page - the path to
1592 root in the menu tree.
1593 """
1594 return menu_set_active_trail()
1595
1596
1597
1599 """
1600 Get the breadcrumb for the current page, as determined by the active trail.
1601 """
1602 breadcrumb = {}
1603
1604 if (drupal_is_front_page()):
1605 return breadcrumb
1606 item = menu_get_item()
1607 if (item and item['access']):
1608 active_trail = menu_get_active_trail()
1609 for parent in active_trail:
1610 breadcrumb.append( l(parent['title'], parent['href'], \
1611 parent['localized_options']) )
1612 end = end(active_trail)
1613
1614 if (item['href'] == end['href'] or \
1615 (item['type'] == MENU_DEFAULT_LOCAL_TASK and \
1616 end['href'] != '<front>')):
1617 php.array_pop(breadcrumb)
1618 return breadcrumb
1619
1620
1621
1623 """
1624 Get the title of the current page, as determined by the active trail.
1625 """
1626 active_trail = menu_get_active_trail()
1627 for item in php.array_reverse(active_trail):
1628 if (not drupy_bool(item['type'] & MENU_IS_LOCAL_TASK)):
1629 return item['title']
1630
1631
1632
1634 """
1635 Get a menu link by its mlid, access checked and link
1636 translated for rendering.
1637
1638 This function should never be called from within node_load() or any other
1639 function used as a menu object load function since an infinite recursion may
1640 occur.
1641
1642 @param mlid
1643 The mlid of the menu item.
1644 @return
1645 A menu link, with item['access'] filled and link translated for
1646 rendering.
1647 """
1648 item = db_fetch_array(db_query(\
1649 "SELECT m.*, ml.* FROM {menu_links} ml " + \
1650 "LEFT JOIN {menu_router} m ON m.path = ml.router_path " + \
1651 "WHERE ml.mlid = %d", mlid))
1652 if (php.is_numeric(mlid) and item):
1653 _menu_link_translate(item)
1654 return item
1655 return False
1656
1657
1658
1660 """
1661 Clears the cached cached data for a single named menu.
1662 """
1663 php.static(menu_cache_clear, 'cache_cleared', {})
1664 if (not php.isset(menu_cache_clear.cache_cleared, menu_name) or \
1665 php.empty(menu_cache_clear.cache_cleared[menu_name])):
1666 cache_clear_all('links:' + menu_name + ':', 'cache_menu', True)
1667 menu_cache_clear.cache_cleared[menu_name] = 1
1668 elif (menu_cache_clear.cache_cleared[menu_name] == 1):
1669 register_shutdown_function('cache_clear_all', 'links:' + \
1670 menu_name + ':', 'cache_menu', True)
1671 menu_cache_clear.cache_cleared[menu_name] = 2
1672
1673
1674
1676 """
1677 Clears all cached menu data. This should be called any time broad changes
1678 might have been made to the router items or menu links.
1679 """
1680 cache_clear_all('*', 'cache_menu', True)
1681
1682
1683
1685 """
1686 (Re)populate the database tables used by various menu functions.
1687
1688 This function will clear and populate the {menu_router} table, add entries
1689 to {menu_links} for new router items, then remove stale items from
1690 {menu_links}. If called from update.php or install.php, it will also
1691 schedule a call to itself on the first real page load from
1692 menu_execute_active_handler(), because the maintenance page environment
1693 is different and leaves stale data in the menu tables.
1694 """
1695 variable_del('menu_rebuild_needed')
1696 menu_cache_clear_all()
1697 menu = menu_router_build(True)
1698 _menu_navigation_links_rebuild(menu)
1699
1700 _menu_clear_page_cache()
1701 if (php.defined('MAINTENANCE_MODE')):
1702 variable_set('menu_rebuild_needed', True)
1703
1704
1705
1707 """
1708 Collect, alter and store the menu definitions.
1709 """
1710 php.static(menu_router_build, 'menu')
1711 if (menu_router_build.menu == None or reset):
1712 cache = cache_get('router:', 'cache_menu')
1713 if (not reset and cache and php.isset(cache, 'data')):
1714 menu_router_build.menu = cache.data
1715 else:
1716
1717
1718 callbacks = []
1719 for plugin in plugin_implements('menu', None, True):
1720 router_items = (plugin+'_menu')()
1721 if (router_items != None and php.is_array(router_items)):
1722 for path in php.array_keys(router_items):
1723 router_items[path]['plugin'] = plugin
1724 callbacks = php.array_merge(callbacks, router_items)
1725
1726 drupal_alter('menu', callbacks)
1727 menu_router_build.menu = _menu_router_build(callbacks)
1728 return menu_router_build.menu
1729
1730
1731
1733 """
1734 Builds a link from a router item.
1735 """
1736 if (item['type'] == MENU_CALLBACK):
1737 item['hidden'] = -1
1738 elif (item['type'] == MENU_SUGGESTED_ITEM):
1739 item['hidden'] = 1
1740
1741
1742 item['plugin'] = 'system'
1743 item += {
1744 'menu_name' : 'navigation',
1745 'link_title' : item['title'],
1746 'link_path' : item['path'],
1747 'hidden' : 0,
1748 'options' : ({} if php.empty(item['description']) else \
1749 {'attributes' : {'title' : item['description']}})
1750 }
1751 return item
1752
1753
1754
1756 """
1757 Helper function to build menu links for the items in the menu router.
1758 """
1759
1760 menu_links = {}
1761 for path,item in menu.items():
1762 if (item['_visible']):
1763 item = _menu_link_build(item)
1764 menu_links[path] = item
1765 sort[path] = item['_number_parts']
1766 if (menu_links):
1767
1768 array_multisort(sort, SORT_NUMERIC, menu_links)
1769 for item in menu_links:
1770 existing_item = db_fetch_array(db_query(\
1771 "SELECT mlid, menu_name, plid, customized, has_children, " + \
1772 "updated " + \
1773 "FROM {menu_links} " + \
1774 "WHERE link_path = '%s' AND plugin = '%s'", \
1775 item['link_path'], 'system'))
1776 if (existing_item):
1777 item['mlid'] = existing_item['mlid']
1778 item['menu_name'] = existing_item['menu_name']
1779 item['plid'] = existing_item['plid']
1780 item['has_children'] = existing_item['has_children']
1781 item['updated'] = existing_item['updated']
1782 if (not existing_item or not existing_item['customized']):
1783 menu_link_save(item)
1784 placeholders = db_placeholders(menu, 'varchar')
1785 paths = php.array_keys(menu)
1786
1787 result = db_query(\
1788 "SELECT ml.link_path, ml.mlid, " + \
1789 "ml.router_path, ml.updated " + \
1790 "FROM {menu_links} ml " + \
1791 "WHERE ml.updated = 1 OR " + \
1792 "(router_path NOT IN (placeholders) AND external = 0 AND " + \
1793 "customized = 1)", paths)
1794 while True:
1795 item = db_fetch_array(result)
1796 if not item:
1797 break
1798 router_path = _menu_find_router_path(menu, item['link_path'])
1799 if (not php.empty(router_path) and \
1800 (router_path != item['router_path'] or item['updated'])):
1801
1802
1803 updated = item['updated'] and router_path != item['link_path']
1804 db_query(\
1805 "UPDATE {menu_links} SET " + \
1806 "router_path = '%s', updated = %d WHERE mlid = %d", \
1807 router_path, updated, item['mlid'])
1808
1809 result = db_query(\
1810 "SELECT * FROM {menu_links} " + \
1811 "WHERE router_path NOT IN (placeholders) AND " + \
1812 "external = 0 AND updated = 0 AND customized = 0 " + \
1813 "ORDER BY depth DESC", paths)
1814
1815
1816 item = db_fetch_array(result)
1817 while True:
1818 if not item:
1819 break
1820 _menu_delete_item(item, True)
1821
1822
1823
1825 """
1826 Delete one or several menu links.
1827 @param mlid
1828 A valid menu link mlid or None. If None, path is used.
1829 @param path
1830 The path to the menu items to be deleted. mlid must be None.
1831 """
1832 if (not php.empty(mlid)):
1833 _menu_delete_item(db_fetch_array(db_query(\
1834 "SELECT * FROM {menu_links} WHERE mlid = %d", mlid)))
1835 else:
1836 result = db_query("SELECT * FROM {menu_links} WHERE link_path = '%s'", \
1837 path)
1838 while True:
1839 link = db_fetch_array(result)
1840 if not link:
1841 break
1842 _menu_delete_item(link)
1843
1844
1845
1847 """
1848 Helper function for menu_link_delete; deletes a single menu link.
1849
1850 @param item
1851 Item to be deleted.
1852 @param force
1853 Forces deletion. Internal use only, setting to True is discouraged.
1854 """
1855 if (item and (item['plugin'] != 'system' or item['updated'] or force)):
1856
1857 if (item['has_children']):
1858 result = db_query("SELECT mlid FROM {menu_links} WHERE plid = %d", \
1859 item['mlid'])
1860 while True:
1861 m = db_fetch_array(result)
1862 if not m:
1863 break
1864 child = menu_link_load(m['mlid'])
1865 child['plid'] = item['plid']
1866 menu_link_save(child)
1867 db_query('DELETE FROM {menu_links} WHERE mlid = %d', item['mlid'])
1868
1869 _menu_update_parental_status(item)
1870 menu_cache_clear(item['menu_name'])
1871 _menu_clear_page_cache()
1872
1873
1874
1876 """
1877 Save a menu link.
1878
1879 @param item
1880 An array representing a menu link item. The only mandatory keys are
1881 link_path and link_title. Possible keys are:
1882 - menu_name default is navigation
1883 - weight default is 0
1884 - expanded whether the item is expanded.
1885 - options An array of options, @see l for more.
1886 - mlid Set to an existing value, or 0 or None to insert a new link.
1887 - plid The mlid of the parent.
1888 - router_path The path of the relevant router item.
1889 """
1890 php.Reference.check(item)
1891 menu = menu_router_build()
1892 drupal_alter('menu_link', item, menu)
1893
1894
1895 item['_external'] = menu_path_is_external(item['link_path']) or \
1896 item['link_path'] == '<front>'
1897
1898 php.array_merge(item, {
1899 'menu_name': 'navigation',
1900 'weight': 0,
1901 'link_title': '',
1902 'hidden': 0,
1903 'has_children': 0,
1904 'expanded': 0,
1905 'options': array(),
1906 'plugin': 'menu',
1907 'customized': 0,
1908 'updated': 0,
1909 }, True)
1910 existing_item = False
1911 if (php.isset(item, 'mlid')):
1912 existing_item = db_fetch_array(db_query(\
1913 "SELECT * FROM {menu_links} WHERE mlid = %d", item['mlid']))
1914 if (php.isset(item, 'plid')):
1915 parent = db_fetch_array(db_query(\
1916 "SELECT * FROM {menu_links} WHERE mlid = %d", item['plid']))
1917 else:
1918
1919 parent_path = item['link_path']
1920 where = "WHERE link_path = '%s'"
1921
1922
1923 if (item['plugin'] == 'system'):
1924 where += " AND plugin = '%s'"
1925 arg2 = 'system'
1926 else:
1927
1928
1929 where += " AND menu_name = '%s'"
1930 arg2 = item['menu_name']
1931 while True:
1932 parent = False
1933 parent_path = php.substr(parent_path, 0, strrpos(parent_path, '/'))
1934 result = db_query("SELECT COUNT(*) FROM {menu_links} " + \
1935 where, parent_path, arg2)
1936
1937 if (db_result(result) == 1):
1938 parent = db_fetch_array(db_query("SELECT * FROM {menu_links} " + \
1939 where, parent_path, arg2))
1940 if not (parent == False and parent_path):
1941 break
1942 if (parent != False):
1943 item['menu_name'] = parent['menu_name']
1944 menu_name = item['menu_name']
1945
1946
1947 if (php.empty(parent['mlid']) or parent['hidden'] < 0):
1948 item['plid'] = 0
1949 else:
1950 item['plid'] = parent['mlid']
1951 if (not existing_item):
1952 db_query( \
1953 "INSERT INTO {menu_links} ( " + \
1954 "menu_name, plid, link_path, " + \
1955 "hidden, external, has_children, " + \
1956 "expanded, weight, " + \
1957 "plugin, link_title, options, " + \
1958 "customized, updated) VALUES ( " + \
1959 "'%s', %d, '%s', " + \
1960 "%d, %d, %d, " + \
1961 "%d, %d,'%s', '%s', '%s', %d, %d)",
1962 item['menu_name'], item['plid'], item['link_path'],
1963 item['hidden'], item['_external'], item['has_children'],
1964 item['expanded'], item['weight'],
1965 item['plugin'], item['link_title'], \
1966 php.serialize(item['options']),
1967 item['customized'], item['updated'])
1968 item['mlid'] = db_last_insert_id('menu_links', 'mlid')
1969 if (not item['plid']):
1970 item['p1'] = item['mlid']
1971 for i in range(2, MENU_MAX_DEPTH + 1):
1972 item["pi"] = 0
1973 item['depth'] = 1
1974 else:
1975
1976 if (item['has_children'] and existing_item):
1977 limit = MENU_MAX_DEPTH - menu_link_children_relative_depth(existing_item) - 1
1978 else:
1979 limit = MENU_MAX_DEPTH - 1
1980 if (parent['depth'] > limit):
1981 return False
1982 item['depth'] = parent['depth'] + 1
1983 _menu_link_parents_set(item, parent)
1984
1985 if (existing_item and (item['plid'] != existing_item['plid'] or \
1986 menu_name != existing_item['menu_name'])):
1987 _menu_link_move_children(item, existing_item)
1988
1989
1990 if (not php.isset(_SESSION, 'system_update_6021') and \
1991 (php.empty(item['router_path']) or \
1992 not existing_item or \
1993 (existing_item['link_path'] != item['link_path']))):
1994 if (item['_external']):
1995 item['router_path'] = ''
1996 else:
1997
1998 item['parts'] = php.explode('/', \
1999 item['link_path'], MENU_MAX_PARTS)
2000 item['router_path'] = \
2001 _menu_find_router_path(menu, item['link_path'])
2002 db_query( \
2003 "UPDATE {menu_links} SET " + \
2004 "menu_name = '%s', plid = %d, link_path = '%s', " + \
2005 "router_path = '%s', hidden = %d, external = %d, has_children = %d, " + \
2006 "expanded = %d, weight = %d, depth = %d, " + \
2007 "p1 = %d, p2 = %d, p3 = %d, p4 = %d, " + \
2008 "p5 = %d, p6 = %d, p7 = %d, p8 = %d, p9 = %d, " + \
2009 "plugin = '%s', link_title = '%s', options = '%s', " + \
2010 "customized = %d WHERE mlid = %d", \
2011 item['menu_name'], item['plid'], item['link_path'], \
2012 item['router_path'], item['hidden'], item['_external'], \
2013 item['has_children'], \
2014 item['expanded'], item['weight'], item['depth'], \
2015 item['p1'], item['p2'], item['p3'], item['p4'], \
2016 item['p5'], item['p6'], item['p7'], item['p8'], \
2017 item['p9'], \
2018 item['plugin'], item['link_title'], \
2019 php.serialize(item['options']), \
2020 item['customized'], item['mlid'])
2021
2022 _menu_update_parental_status(item)
2023 menu_cache_clear(menu_name)
2024 if (existing_item and menu_name != existing_item['menu_name']):
2025 menu_cache_clear(existing_item['menu_name'])
2026 _menu_clear_page_cache()
2027 return item['mlid']
2028
2029
2030
2032 """
2033 Helper function to clear the page and block caches
2034 at most twice per page load.
2035 """
2036 php.static(_menu_clear_page_cache, 'cache_cleared', 0)
2037
2038
2039 if (_menu_clear_page_cache.cache_cleared == 0):
2040 cache_clear_all()
2041
2042 _menu_set_expanded_menus()
2043 _menu_clear_page_cache.cache_cleared = 1
2044 elif (_menu_clear_page_cache.cache_cleared == 1):
2045 register_shutdown_function('cache_clear_all')
2046
2047 register_shutdown_function('_menu_set_expanded_menus')
2048 _menu_clear_page_cache.cache_cleared = 2
2049
2050
2051
2053 """
2054 Helper function to update a list of menus with expanded items
2055 """
2056 names = []
2057 result = db_query(\
2058 "SELECT menu_name FROM {menu_links} " + \
2059 "WHERE expanded != 0 GROUP BY menu_name")
2060 while True:
2061 n = db_fetch_array(result)
2062 if not n:
2063 break
2064 names.append( n['menu_name'] )
2065 variable_set('menu_expanded', names)
2066
2067
2068
2070 """
2071 Find the router path which will serve this path.
2072
2073 @param menu
2074 The full built menu.
2075 @param link_path
2076 The path for we are looking up its router path.
2077 @return
2078 A path from menu keys or empty if link_path points to a nonexisting
2079 place.
2080 """
2081 parts = php.explode('/', link_path, MENU_MAX_PARTS)
2082 router_path = link_path
2083 if (not php.isset(menu, router_path)):
2084 ancestors = menu_get_ancestors(parts)
2085 ancestors.append('')
2086 for key,router_path in ancestors.items():
2087 if (php.isset(menu, router_path)):
2088 break
2089 return router_path
2090
2091
2092
2093 -def link_maintain(plugin, op, link_path, link_title):
2094 """
2095 Insert, update or delete an uncustomized menu link related to a plugin.
2096
2097 @param plugin
2098 The name of the plugin.
2099 @param op
2100 Operation to perform: insert, update or delete.
2101 @param link_path
2102 The path this link points to.
2103 @param link_title
2104 Title of the link to insert or new title to update the link to.
2105 Unused for delete.
2106 @return
2107 The insert op returns the mlid of the new item. Others op return None.
2108 """
2109 if op == 'insert':
2110 menu_link = {
2111 'link_title': link_title,
2112 'link_path': link_path,
2113 'plugin': plugin,
2114 }
2115 return menu_link_save(menu_link)
2116 elif op == 'update':
2117 db_query(\
2118 "UPDATE {menu_links} SET " + \
2119 "link_title = '%s' WHERE link_path = '%s' AND " + \
2120 "customized = 0 AND plugin = '%s'", link_title, link_path, plugin)
2121 result = lib_database.query(\
2122 "SELECT menu_name " + \
2123 "FROM {menu_links} " + \
2124 "WHERE link_path = '%s' AND customized = 0 AND module = '%s'", \
2125 link_path, plugin);
2126 while True:
2127 item = lib_database.fetch_array(result)
2128 if not item:
2129 break
2130 elif op == 'delete':
2131 menu_link_delete(None, link_path)
2132
2133
2134
2136 """
2137 Find the depth of an item's children relative to its depth.
2138
2139 For example, if the item has a depth of 2, and the maximum of any child in
2140 the menu link tree is 5, the relative depth is 3.
2141
2142 @param item
2143 An array representing a menu link item.
2144 @return
2145 The relative depth, or zero.
2146 """
2147 i = 1
2148 match = ''
2149 args.append( item['menu_name'] )
2150 p = 'p1'
2151 while True:
2152 if not (i <= MENU_MAX_DEPTH and item[p]):
2153 break
2154 match += " AND p = %d"
2155 args.append( item[p] )
2156 p = 'p' + ++i
2157 max_depth = db_result(db_query_range(\
2158 "SELECT depth FROM {menu_links} " + \
2159 "WHERE menu_name = '%s'" + match + " ORDER BY depth DESC", args, 0, 1))
2160 return ((max_depth - item['depth']) if (max_depth > item['depth']) else 0)
2161
2162
2163
2165 """
2166 Update the children of a menu link that's being moved.
2167
2168 The menu name, parents (p1 - p6), and depth are updated for all children of
2169 the link, and the has_children status of the previous parent is updated.
2170 """
2171 args.append( item['menu_name'] )
2172 set.append( "menu_name = '%s'" )
2173 i = 1
2174 while True:
2175 if not (i <= item['depth']):
2176 break
2177 p = 'p' + i
2178 i += 1
2179 set.append("p = %d")
2180 args.append( item[p] )
2181 j = existing_item['depth'] + 1
2182 while True:
2183 if not (i <= MENU_MAX_DEPTH and j <= MENU_MAX_DEPTH):
2184 break
2185 set.append( 'p' + i + ' = p' + j )
2186 i += 1
2187 j += 1
2188 while True:
2189 if not (i <= MENU_MAX_DEPTH):
2190 break
2191 set.append( 'p' + i + ' = 0')
2192 i += 1
2193 shift = item['depth'] - existing_item['depth']
2194 if (shift < 0):
2195 args.append( -shift )
2196 set.append( 'depth = depth - %d' )
2197 elif (shift > 0):
2198
2199
2200
2201
2202 set = php.array_reverse(set)
2203 args = php.array_reverse(args)
2204 args.append( shift )
2205 set.append( 'depth = depth + %d' )
2206 where.append( "menu_name = '%s'" )
2207 args.append( existing_item['menu_name'] )
2208 p = 'p1'
2209 i = 1
2210 while True:
2211 if not (i <= MENU_MAX_DEPTH and existing_item[p]):
2212 break
2213 where.append( "p = %d" )
2214 args.append( existing_item[p] )
2215 i += 1
2216 p = 'p' + i
2217 db_query(\
2218 "UPDATE {menu_links} SET " + \
2219 php.implode(', ', set) + " WHERE " . php.implode(' AND ', where), args)
2220
2221 _menu_update_parental_status(existing_item, True)
2222
2223
2224
2226 """
2227 Check and update the has_children status for the parent of a link.
2228 """
2229
2230 if (item['plid']):
2231
2232 where = (" AND mlid != %d" if exclude else '')
2233
2234 parent_has_children = drupy_bool(db_result(db_query_range(\
2235 "SELECT mlid FROM {menu_links} WHERE " + \
2236 "menu_name = '%s' AND plid = %d AND hidden = 0" + where, \
2237 item['menu_name'], item['plid'], item['mlid'], 0, 1)))
2238 db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", \
2239 parent_has_children, item['plid'])
2240
2241
2242
2244 """
2245 Helper function that sets the p1..p9 values for a menu link being saved.
2246 """
2247 php.Reference.check(item)
2248 i = 1
2249 while (i < item['depth']):
2250 p = 'p' + i
2251 i += 1
2252 item[p] = parent[p]
2253 p = 'p' + i
2254 i += 1
2255
2256 item[p] = item['mlid']
2257 while (i <= MENU_MAX_DEPTH):
2258 p = 'p' + i
2259 i += 1
2260 item[p] = 0
2261
2262
2263
2265 """
2266 Helper function to build the router table based on the data from hook_menu.
2267 """
2268
2269
2270 menu = []
2271 for path,item in callbacks.items():
2272 load_functions = []
2273 to_arg_functions = []
2274 fit = 0
2275 move = False
2276 parts = php.explode('/', path, MENU_MAX_PARTS)
2277 number_parts = php.count(parts)
2278
2279
2280 slashes = number_parts - 1
2281
2282 for k,part in parts.items():
2283 match = False
2284 if (php.preg_match('/^%([a-z_]*)$/', part, matches)):
2285 if (php.empty(matches[1])):
2286 match = True
2287 load_functions[k] = None
2288 else:
2289 if (drupal_function_exists(matches[1] + '_to_arg')):
2290 to_arg_functions[k] = matches[1] + '_to_arg'
2291 load_functions[k] = None
2292 match = True
2293 if (drupal_function_exists(matches[1] + '_load')):
2294 function = matches[1] + '_load'
2295
2296
2297
2298 load_functions[k] = ({function : item['load arguments']} if \
2299 php.isset(item, 'load arguments') else function)
2300 match = True
2301 if (match):
2302 parts[k] = '%'
2303 else:
2304 fit |= 1 << (slashes - k)
2305 if (fit):
2306 move = True
2307 else:
2308
2309 fit = (1 << number_parts) - 1
2310 masks[fit] = 1
2311 item['load_functions'] = ('' if php.empty(load_functions) else \
2312 php.serialize(load_functions))
2313 item['to_arg_functions'] = ('' if php.empty(to_arg_functions) else \
2314 php.serialize(to_arg_functions))
2315 item += {
2316 'title': '',
2317 'weight': 0,
2318 'type': MENU_NORMAL_ITEM,
2319 '_number_parts': number_parts,
2320 '_parts': parts,
2321 '_fit': fit,
2322 }
2323 item += {
2324 '_visible' : drupy_bool((item['type'] & MENU_VISIBLE_IN_BREADCRUMB)),
2325 '_tab' : drupy_bool((item['type'] & MENU_IS_LOCAL_TASK)),
2326 }
2327 if (move):
2328 new_path = php.implode('/', item['_parts'])
2329 menu[new_path] = item
2330 sort[new_path] = number_parts
2331 else:
2332 menu[path] = item
2333 sort[path] = number_parts
2334 php.array_multisort(sort, SORT_NUMERIC, menu)
2335 if (menu):
2336
2337 lib_database.query('DELETE FROM {menu_router}');
2338
2339 for path,v in menu.items():
2340 item = menu[path]
2341 if (not item['_tab']):
2342
2343 item['tab_parent'] = ''
2344 item['tab_root'] = path
2345 for i in range(item['_number_parts'] - 1, 0, -1):
2346 parent_path = php.implode('/', php.array_slice(item['_parts'], 0, i))
2347 if (php.isset(menu, parent_path)):
2348 parent = menu[parent_path]
2349 if (not php.isset(item, 'tab_parent')):
2350
2351 item['tab_parent'] = parent_path
2352 if (not php.isset(item, 'tab_root') and not parent['_tab']):
2353 item['tab_root'] = parent_path
2354
2355
2356
2357 if ((item['type'] == MENU_DEFAULT_LOCAL_TASK) and \
2358 not php.isset(item, 'access callback') and \
2359 php.isset(parent, 'access callback')):
2360 item['access callback'] = parent['access callback']
2361 if (not php.isset(item, 'access arguments') and \
2362 php.isset(parent, 'access arguments')):
2363 item['access arguments'] = parent['access arguments']
2364
2365 if (not php.isset(item, 'page callback') and \
2366 php.isset(parent, 'page callback')):
2367 item['page callback'] = parent['page callback']
2368 if (not php.isset(item, 'page arguments') and \
2369 php.isset(parent, 'page arguments')):
2370 item['page arguments'] = parent['page arguments']
2371 if (not php.isset(item, 'access callback') and \
2372 php.isset(item, 'access arguments')):
2373
2374 item['access callback'] = 'user_access'
2375 if (not php.isset(item, 'access callback') or \
2376 php.empty(item['page callback'])):
2377 item['access callback'] = 0
2378 if (is_bool(item['access callback'])):
2379 item['access callback'] = intval(item['access callback'])
2380 item += {
2381 'access arguments': {},
2382 'access callback': '',
2383 'page arguments': {},
2384 'page callback': '',
2385 'block callback': '',
2386 'title arguments': {},
2387 'title callback': 't',
2388 'description': '',
2389 'position': '',
2390 'tab_parent': '',
2391 'tab_root': path,
2392 'path': path
2393 }
2394 title_arguments = (php.serialize(item['title arguments']) if \
2395 item['title arguments'] else '')
2396 db_query( \
2397 "INSERT INTO {menu_router} " + \
2398 "(path, load_functions, to_arg_functions, access_callback, " + \
2399 "access_arguments, page_callback, page_arguments, fit, " + \
2400 "number_parts, tab_parent, tab_root, " + \
2401 "title, title_callback, title_arguments, " + \
2402 "type, block_callback, description, position, weight) " + \
2403 "VALUES ('%s', '%s', '%s', '%s', " + \
2404 "'%s', '%s', '%s', %d, " + \
2405 "%d, '%s', '%s', " + \
2406 "'%s', '%s', '%s', " + \
2407 "%d, '%s', '%s', '%s', %d)",
2408 path, item['load_functions'], item['to_arg_functions'], \
2409 item['access callback'], \
2410 php.serialize(item['access arguments']), \
2411 item['page callback'], php.serialize(item['page arguments']), \
2412 item['_fit'], \
2413 item['_number_parts'], item['tab_parent'], item['tab_root'], \
2414 item['title'], item['title callback'], title_arguments, \
2415 item['type'], item['block callback'], item['description'], \
2416 item['position'], item['weight'])
2417
2418 masks = php.array_keys(masks)
2419 rsort(masks)
2420 variable_set('menu_masks', masks)
2421 lib_cache.set('router:', menu, 'cache_menu');
2422 return menu
2423
2424
2425
2426
2427
2428
2430 colonpos = php.strpos(path, ':')
2431 return (colonpos != False and not php.preg_match('not [/?#]not ', \
2432 php.substr(path, 0, colonpos)) and \
2433 filter_xss_bad_protocol(path, False) == check_plain(path))
2434
2435
2436
2438 """
2439 Checks whether the site is off-line for maintenance.
2440
2441 This function will log the current user out and redirect to front page
2442 if the current user has no 'administer site configuration' permission.
2443
2444 @return
2445 False if the site is not off-line or its the login page or the user has
2446 'administer site configuration' permission.
2447 True for anonymous users not on the login page if the site is off-line.
2448 """
2449
2450 if (variable_get('site_offline', 0)):
2451
2452 if (user_access('administer site configuration')):
2453
2454
2455
2456 if (drupal_get_normal_path(php.GET['q']) != \
2457 'admin/settings/site-maintenance'):
2458 drupal_set_message(t('Operating in off-line mode.'), 'status', False)
2459 else:
2460
2461 if (user_is_anonymous()):
2462 return php.GET['q'] != 'user' and php.GET['q'] != 'user/login'
2463
2464 php.require_once( drupal_get_path('plugin', 'user') + \
2465 '/user.pages.inc' )
2466 user_logout()
2467 return False
2468
2469
2470
2472 """
2473 Validates the path of a menu link being created or edited.
2474
2475 @return
2476 True if it is a valid path AND the current user has access permission,
2477 False otherwise.
2478 """
2479 item = {}
2480 path = form_item['link_path']
2481
2482 lib_appglobals.menu_admin = True
2483 if (path == '<front>' or menu_path_is_external(path)):
2484 item = {'access': True}
2485 elif (php.preg_match('/\/\%/', path)):
2486
2487
2488 item = db_fetch_array(\
2489 db_query("SELECT * FROM {menu_router} where path = '%s' ", path))
2490 if (item):
2491 item['link_path'] = form_item['link_path']
2492 item['link_title'] = form_item['link_title']
2493 item['external'] = False
2494 item['options'] = ''
2495 _menu_link_translate(item)
2496 else:
2497 item = menu_get_item(path)
2498 lib_appglobals.menu_admin = False
2499 return item and item['access']
2500