Merge lp:~pressflow/pressflow/menu-tng into lp:pressflow

Proposed by David Strauss
Status: Rejected
Rejected by: David Strauss
Proposed branch: lp:~pressflow/pressflow/menu-tng
Merge into: lp:pressflow
Diff against target: 538 lines (+304/-52)
2 files modified
includes/menu.inc (+302/-50)
modules/book/book.module (+2/-2)
To merge this branch: bzr merge lp:~pressflow/pressflow/menu-tng
Reviewer Review Type Date Requested Status
Pressflow Administrators Pending
Review via email: mp+15439@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

65. By David Strauss

Initial (seemingly working) Menu-TNG system.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'includes/menu.inc'
2--- includes/menu.inc 2009-04-30 00:36:53 +0000
3+++ includes/menu.inc 2009-11-30 19:05:26 +0000
4@@ -285,7 +285,7 @@
5 }
6
7 /**
8- * Get a router item.
9+ * Get a router item. Pressflow-optimized.
10 *
11 * @param $path
12 * The path, for example node/5. The function will find the corresponding
13@@ -293,13 +293,14 @@
14 * @param $router_item
15 * Internal use only.
16 * @return
17- * The router item, an associate array corresponding to one row in the
18- * menu_router table. The value of key map holds the loaded objects. The
19- * value of key access is TRUE if the current user can access this page.
20- * The values for key title, page_arguments, access_arguments will be
21- * filled in based on the database values and the objects loaded.
22+ * The router item, an associative array corresponding to one menu item.
23+ * The value of key map holds the loaded objects. The value of key access
24+ * is TRUE if the current user can access this page.
25 */
26 function menu_get_item($path = NULL, $router_item = NULL) {
27+ static $tree = NULL;
28+
29+ $start = microtime(TRUE);
30 static $router_items;
31 if (!isset($path)) {
32 $path = $_GET['q'];
33@@ -310,28 +311,63 @@
34 if (!isset($router_items[$path])) {
35 $original_map = arg(NULL, $path);
36 $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
37- list($ancestors, $placeholders) = menu_get_ancestors($parts);
38-
39- if ($router_item = db_fetch_array(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
40- $map = _menu_translate($router_item, $original_map);
41- if ($map === FALSE) {
42+
43+ if (!$tree) {
44+ $tree = menu_get_tree();
45+ }
46+ $cursor =& $tree;
47+
48+ foreach ($parts as $element) {
49+ // Try the best fit first.
50+ if (isset($cursor[$element])) {
51+ $cursor =& $cursor[$element];
52+ }
53+ // Then try a placeholder fit.
54+ else if (isset($cursor['%'])) {
55+ $cursor =& $cursor['%'];
56+ }
57+ // Check if there's a leaf available.
58+ else if (isset($cursor['#leaf'])) {
59+ break;
60+ }
61+ // If there's no fit, there's no matching item.
62+ else {
63 $router_items[$path] = FALSE;
64 return FALSE;
65 }
66- if ($router_item['access']) {
67- $router_item['map'] = $map;
68- $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
69- }
70- }
71+ }
72+
73+ // At this point, there should be a leaf.
74+ if (!isset($cursor['#leaf'])) {
75+ $router_items[$path] = FALSE;
76+ return FALSE;
77+ }
78+
79+ $router_item = $cursor['#leaf'];
80+
81+ //echo "router_item:\n";
82+ //print_r($router_item);
83+
84+ $map = _menu_translate($router_item, $original_map);
85+ if ($map === FALSE) {
86+ $router_items[$path] = FALSE;
87+ return FALSE;
88+ }
89+ if ($router_item['access']) {
90+ $router_item['map'] = $map;
91+ $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
92+ }
93+
94 $router_items[$path] = $router_item;
95 }
96+ header('X-Profile-' . str_replace('/', '-', $path) . ': ' . number_format(microtime(TRUE) - $start, 10));
97 return $router_items[$path];
98 }
99
100 /**
101 * Execute the page callback associated with the current path
102 */
103-function menu_execute_active_handler($path = NULL) {
104+function menu_execute_active_handler($path = NULL, $old = FALSE) {
105 if (_menu_site_is_offline()) {
106 return MENU_SITE_OFFLINE;
107 }
108@@ -340,7 +376,8 @@
109 if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
110 menu_rebuild();
111 }
112- if ($router_item = menu_get_item($path)) {
113+
114+ if ((!$old && $router_item = menu_get_item($path)) || ($old && $router_item = menu_get_item_old($path))) {
115 if ($router_item['access']) {
116 if ($router_item['file']) {
117 require_once($router_item['file']);
118@@ -434,6 +471,7 @@
119 * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
120 */
121 function _menu_check_access(&$item, $map) {
122+ //print_r($item);
123 // Determine access callback, which will decide whether or not the current
124 // user has access to this path.
125 $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']);
126@@ -443,6 +481,9 @@
127 }
128 else {
129 $arguments = menu_unserialize($item['access_arguments'], $map);
130+
131+ //print_r($arguments);
132+
133 // As call_user_func_array is quite slow and user_access is a very common
134 // callback, it is worth making a special case for it.
135 if ($callback == 'user_access') {
136@@ -823,12 +864,11 @@
137 $parents = array();
138 }
139 array_unshift($args, $menu_name);
140- // Select the links from the table, and recursively build the tree. We
141- // LEFT JOIN since there is no match in {menu_router} for an external
142- // link.
143+ // Select the links from the table, and recursively build the tree. Loading data for
144+ // internal links (formerly from {menu_router}) has moved to _menu_tree_data() in Pressflow.
145 $data['tree'] = menu_tree_data(db_query("
146- SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
147- FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
148+ SELECT ml.*
149+ FROM {menu_links} ml
150 WHERE ml.menu_name = '%s'". $where ."
151 ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
152 $data['node_links'] = array();
153@@ -930,12 +970,10 @@
154 $placeholders = '%d';
155 $parents = array();
156 }
157- // Select the links from the table, and recursively build the tree. We
158- // LEFT JOIN since there is no match in {menu_router} for an external
159- // link.
160+ // Select the links from the table, and recursively build the tree.
161 $data['tree'] = menu_tree_data(db_query("
162- SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
163- FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
164+ SELECT ml.*
165+ FROM {menu_links} ml
166 WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .")
167 ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
168 $data['node_links'] = array();
169@@ -1067,6 +1105,26 @@
170 $remnant = NULL;
171 $tree = array();
172 while ($item = db_fetch_array($result)) {
173+
174+ // Load additional data for the menu item.
175+ $menu_item = menu_get_item($item['router_path']);
176+ if ($menu_item) {
177+ $menu_item_filter_keys = array(
178+ 'load_functions',
179+ 'to_arg_functions',
180+ 'access_callback',
181+ 'access_arguments',
182+ 'page_callback',
183+ 'page_arguments',
184+ 'title',
185+ 'title_callback',
186+ 'title_arguments',
187+ 'type',
188+ 'description',
189+ );
190+ $item += array_intersect_key($menu_item, array_fill_keys($menu_item_filter_keys, TRUE));
191+ }
192+
193 // We need to determine if we're on the path to root so we can later build
194 // the correct active trail and breadcrumb.
195 $item['in_active_trail'] = in_array($item['mlid'], $parents);
196@@ -1297,6 +1355,14 @@
197 return $links;
198 }
199
200+function _menu_sort_callback($item1, $item2) {
201+ if ($item1['weight'] != $item2['weight']) {
202+ return ($item1['weight'] < $item2['weight']) ? -1 : 1;
203+ }
204+
205+ return ($item1['title'] < $item2['title']) ? -1 : 1;
206+}
207+
208 /**
209 * Collects the local tasks (tabs) for a given level.
210 *
211@@ -1321,13 +1387,34 @@
212 return '';
213 }
214 // Get all tabs and the root page.
215- $result = db_query("SELECT * FROM {menu_router} WHERE tab_root = '%s' ORDER BY weight, title", $router_item['tab_root']);
216+ $menu_tree =& menu_get_tree();
217+ $items = array();
218+ $cursor =& $menu_tree;
219+ $parts = explode('/', $router_item['tab_root']);
220+ foreach ($parts as $part) {
221+ if (isset($cursor[$part])) {
222+ $cursor = $cursor[$part];
223+ }
224+ else if (isset($cursor['%'])) {
225+ $cursor = $cursor['%'];
226+ }
227+ else {
228+ $cursor = array();
229+ }
230+ }
231+ foreach ($cursor as $key => $item) {
232+ if ($key != '#leaf') {
233+ $items[] = $item;
234+ }
235+ }
236+ usort($items, '_menu_sort_callback');
237+
238 $map = arg();
239 $children = array();
240 $tasks = array();
241 $root_path = $router_item['path'];
242
243- while ($item = db_fetch_array($result)) {
244+ foreach ($items as $item) {
245 _menu_translate($item, $map, TRUE);
246 if ($item['tab_parent']) {
247 // All tabs, but not the root page.
248@@ -1627,7 +1714,11 @@
249 * rendering.
250 */
251 function menu_link_load($mlid) {
252- if (is_numeric($mlid) && $item = db_fetch_array(db_query("SELECT m.*, ml.* FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid))) {
253+ if (is_numeric($mlid) && $item = db_fetch_array(db_query("SELECT ml.* FROM {menu_links} ml WHERE ml.mlid = %d", $mlid))) {
254+ $menu_item = menu_get_item($item['router_path']);
255+ if ($menu_item) {
256+ $item += $menu_item;
257+ }
258 _menu_link_translate($item);
259 return $item;
260 }
261@@ -1648,6 +1739,7 @@
262 register_shutdown_function('cache_clear_all', 'links:'. $menu_name .':', 'cache_menu', TRUE);
263 $cache_cleared[$menu_name] = 2;
264 }
265+ menu_rebuild();
266 }
267
268 /**
269@@ -1656,12 +1748,13 @@
270 */
271 function menu_cache_clear_all() {
272 cache_clear_all('*', 'cache_menu', TRUE);
273+ menu_rebuild();
274 }
275
276 /**
277- * (Re)populate the database tables used by various menu functions.
278+ * (Re)populate the data used by various menu functions.
279 *
280- * This function will clear and populate the {menu_router} table, add entries
281+ * This function will clear and populate the menu tree, add entries
282 * to {menu_links} for new router items, then remove stale items from
283 * {menu_links}. If called from update.php or install.php, it will also
284 * schedule a call to itself on the first real page load from
285@@ -1669,15 +1762,9 @@
286 * is different and leaves stale data in the menu tables.
287 */
288 function menu_rebuild() {
289- variable_del('menu_rebuild_needed');
290- $menu = menu_router_build(TRUE);
291- _menu_navigation_links_rebuild($menu);
292- // Clear the menu, page and block caches.
293- menu_cache_clear_all();
294- _menu_clear_page_cache();
295- if (defined('MAINTENANCE_MODE')) {
296- variable_set('menu_rebuild_needed', TRUE);
297- }
298+ // Note the current time of the rebuild request. This timestamp is guaranteed consistent across the cluster.
299+ // The rebuild request is stored for up to a day.
300+ cache_set('menu_tree_rebuild', $_SERVER['REQUEST_TIME'], 'cache', $_SERVER['REQUEST_TIME'] + 86400);
301 }
302
303 /**
304@@ -1730,7 +1817,7 @@
305 $item['hidden'] = 1;
306 }
307 // Note, we set this as 'system', so that we can be sure to distinguish all
308- // the menu links generated automatically from entries in {menu_router}.
309+ // the menu links generated automatically from entries in the menu tree.
310 $item['module'] = 'system';
311 $item += array(
312 'menu_name' => 'navigation',
313@@ -2056,15 +2143,16 @@
314 // $menu will only have data during a menu rebuild.
315 $menu = _menu_router_cache();
316
317- $router_path = $link_path;
318- $parts = explode('/', $link_path, MENU_MAX_PARTS);
319- list($ancestors, $placeholders) = menu_get_ancestors($parts);
320-
321 if (empty($menu)) {
322 // Not during a menu rebuild, so look up in the database.
323- $router_path = (string)db_result(db_query_range('SELECT path FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1));
324+ $menu_item = menu_get_item($link_path);
325+ $router_path = $menu_item['path'];
326 }
327 elseif (!isset($menu[$router_path])) {
328+ $router_path = $link_path;
329+ $parts = explode('/', $link_path, MENU_MAX_PARTS);
330+ list($ancestors, $placeholders) = menu_get_ancestors($parts);
331+
332 // Add an empty path as a fallback.
333 $ancestors[] = '';
334 foreach ($ancestors as $key => $router_path) {
335@@ -2231,7 +2319,7 @@
336 }
337 }
338
339-/**
340+ /**
341 * Helper function to build the router table based on the data from hook_menu.
342 */
343 function _menu_router_build($callbacks) {
344@@ -2492,7 +2580,7 @@
345 }
346 elseif (preg_match('/\/\%/', $path)) {
347 // Path is dynamic (ie 'user/%'), so check directly against menu_router table.
348- if ($item = db_fetch_array(db_query("SELECT * FROM {menu_router} where path = '%s' ", $path))) {
349+ if ($item = menu_get_item($path)) {
350 $item['link_path'] = $form_item['link_path'];
351 $item['link_title'] = $form_item['link_title'];
352 $item['external'] = FALSE;
353@@ -2507,6 +2595,170 @@
354 return $item && $item['access'];
355 }
356
357+/*
358+ * Returns a structured array with all menu items. Pressflow-specific.
359+ *
360+ * @returns A structured array with all menu items.
361+ */
362+function menu_get_tree() {
363+ // Attempt to load the tree from cache.
364+ $cache_hit = FALSE;
365+ $rebuild = FALSE;
366+ $write_to_cache = TRUE;
367+ if (function_exists('apc_fetch')) {
368+ $cache_item = apc_fetch('menu_tree', $cache_hit);
369+
370+ if ($cache_hit) {
371+ // Check if there is a recent rebuild request.
372+ $rebuild_request = cache_get('menu_tree_rebuild');
373+
374+ // Check if the rebuild request postdates the locally cached menu tree.
375+ if ($rebuild_request && $rebuild_request->data > $cache_item->created) {
376+ // We need to rebuild the locally cached menu tree. Attempt to grab a semaphore.
377+ $rebuild = apc_add('menu_tree_semaphore', TRUE, 86400);
378+
379+ if (!$rebuild) {
380+ // If the semaphore grab was unsuccessful, return the current (slightly stale) menu tree.
381+ $tree =& $cache_item->data;
382+ }
383+ }
384+ else {
385+ $tree =& $cache_item->data;
386+ }
387+
388+ header('X-APC-Menu-Cache: HIT');
389+ }
390+ else {
391+ header('X-APC-Menu-Cache: MISS');
392+ }
393+ }
394+ else {
395+ $cache_item = cache_get('menu_tree', 'cache');
396+ if ($cache_item) {
397+ $cache_hit = TRUE;
398+ $tree = $cache_item->data;
399+ }
400+ }
401+
402+ // On miss, rebuild the tree.
403+ if (!$cache_hit || $rebuild) {
404+ // Build a fresh menu.
405+ $menu = menu_router_build(TRUE);
406+
407+ // Run the rest of the legacy menu_rebuild().
408+ // TODO: Obsolete this code and anything relying on {menu_*} tables.
409+ _menu_navigation_links_rebuild($menu);
410+ menu_cache_clear_all();
411+ _menu_clear_page_cache();
412+
413+ // Return to the Pressflow rebuild.
414+ $tree = array();
415+ foreach ($menu as $path => $item) {
416+
417+ $item = array(
418+ 'path' => $item['path'],
419+ 'load_functions' => $item['load_functions'],
420+ 'to_arg_functions' => $item['to_arg_functions'],
421+ 'access_callback' => $item['access callback'],
422+ 'access_arguments' => serialize($item['access arguments']),
423+ 'page_callback' => $item['page callback'],
424+ 'page_arguments' => serialize($item['page arguments']),
425+ 'fit' => $item['_fit'],
426+ 'number_parts' => count($item['_parts']),
427+ 'tab_parent' => $item['tab_parent'],
428+ 'tab_root' => $item['tab_root'],
429+ 'title' => $item['title'],
430+ 'title_callback' => $item['title callback'],
431+ 'title_arguments' => $item['title arguments'] ? serialize($item['title arguments']) : '',
432+ 'type' => $item['type'],
433+ 'block_callback' => $item['block callback'],
434+ 'description' => $item['description'],
435+ 'position' => $item['position'],
436+ 'weight' => $item['weight'],
437+ 'file' => $item['include file'],
438+ 'href' => $item['href'],
439+ 'options' => $item['options'],
440+ );
441+
442+ $path = explode('/', $path);
443+
444+ // Ensure the route to the item is initialized.
445+ $cursor =& $tree;
446+ foreach ($path as $element) {
447+ if (!isset($cursor[$element])) {
448+ $cursor[$element] = array();
449+ }
450+ $cursor =& $cursor[$element];
451+ }
452+
453+ $cursor['#leaf'] = $item;
454+ }
455+
456+ if ($write_to_cache) {
457+ // Use APC if possible.
458+ if (function_exists('apc_store')) {
459+ $cache_item = new stdClass();
460+ $cache_item->data = $tree;
461+ $cache_item->created = $_SERVER['REQUEST_TIME'];
462+
463+ // TODO: Make the maximum age a variable or constant.
464+ apc_store('menu_tree', $cache_item, 86400);
465+ header('X-APC-Menu-Cache-Write: DONE');
466+ apc_delete('menu_tree_semaphore');
467+ if (defined('MAINTENANCE_MODE')) {
468+ apc_add('menu_tree_rebuild', TRUE);
469+ }
470+ else {
471+ apc_delete('menu_tree_rebuild');
472+ }
473+ }
474+ // Otherwise use the internal cache.
475+ else {
476+ cache_set('menu_tree', $tree, 'cache', $_SERVER['REQUEST_TIME'] + 86400);
477+ if (defined('MAINTENANCE_MODE')) {
478+ cache_set('menu_tree_rebuild', TRUE, 'cache');
479+ }
480+ else {
481+ cache_clear_all('menu_tree_rebuild', 'cache');
482+ }
483+ }
484+ }
485+ }
486+
487+ return $tree;
488+}
489+
490+function menu_get_item_old($path = NULL, $router_item = NULL) {
491+ static $router_items;
492+ if (!isset($path)) {
493+ $path = $_GET['q'];
494+ }
495+ if (isset($router_item)) {
496+ $router_items[$path] = $router_item;
497+ }
498+ if (!isset($router_items[$path])) {
499+ $original_map = arg(NULL, $path);
500+ $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
501+ list($ancestors, $placeholders) = menu_get_ancestors($parts);
502+
503+ if ($router_item = db_fetch_array(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
504+ //echo "old router_item:\n";
505+ //print_r($router_item);
506+ $map = _menu_translate($router_item, $original_map);
507+ if ($map === FALSE) {
508+ $router_items[$path] = FALSE;
509+ return FALSE;
510+ }
511+ if ($router_item['access']) {
512+ $router_item['map'] = $map;
513+ $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
514+ }
515+ }
516+ $router_items[$path] = $router_item;
517+ }
518+ return $router_items[$path];
519+}
520+
521 /**
522 * @} End of "defgroup menu".
523 */
524
525=== modified file 'modules/book/book.module'
526--- modules/book/book.module 2009-02-25 22:33:09 +0000
527+++ modules/book/book.module 2009-11-30 19:05:26 +0000
528@@ -1068,8 +1068,8 @@
529 $i++;
530 }
531 $sql = "
532- SELECT b.*, m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, ml.*
533- FROM {menu_links} ml INNER JOIN {menu_router} m ON m.path = ml.router_path
534+ SELECT b.*, ml.*
535+ FROM {menu_links} ml
536 INNER JOIN {book} b ON ml.mlid = b.mlid
537 WHERE ". implode(' AND ', $match) ."
538 ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";

Subscribers

People subscribed via source and target branches

to status/vote changes: