Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.255.2.37 diff -u -p -r1.255.2.37 menu.inc --- includes/menu.inc 4 Nov 2010 09:58:34 -0000 1.255.2.37 +++ includes/menu.inc 20 Nov 2010 08:35:27 -0000 @@ -1737,6 +1737,127 @@ function menu_router_build($reset = FALS // Alter the menu as defined in modules, keys are like user/%user. drupal_alter('menu', $callbacks); $menu = _menu_router_build($callbacks); + + // Calculate the new content for the menu router table. + $rows_new = array(); + foreach ($menu as $path => $v) { + $item = $menu[$path]; + $rows_new[$path] = array( + 'path' => $path, + 'load_functions' => $item['load_functions'], + 'to_arg_functions' => $item['to_arg_functions'], + 'access_callback' => $item['access callback'], + 'access_arguments' => serialize($item['access arguments']), + 'page_callback' => $item['page callback'], + 'page_arguments' => serialize($item['page arguments']), + 'fit' => $item['_fit'], + 'number_parts' => $item['_number_parts'], + 'tab_parent' => $item['tab_parent'], + 'tab_root' => $item['tab_root'], + 'title' => $item['title'], + 'title_callback' => $item['title callback'], + 'title_arguments' => $item['title arguments'] ? serialize($item['title arguments']) : '', + 'type' => $item['type'], + 'block_callback' => $item['block callback'], + 'description' => $item['description'], + 'position' => $item['position'], + 'weight' => $item['weight'], + 'file' => $item['include file'], + ); + } + + // Get the old rows from the cache if we can, otherwise assume an initial build + // and empty menu_router. + $rows_old = array(); + if ($data = cache_get('menu_router_build_old_rows')) { + $rows_old = $data->data; + + // Empty the cache immediatly in case there is an error whilst processing changes + // so we don't get left in a indeterminant state. + cache_clear_all('menu_router_build_old_rows', 'cache'); + } + else { + db_query('TRUNCATE TABLE {menu_router}'); + } + + // Cacluate the changes. + $changes_insert = array(); + $changes_update = array(); + $changes_delete = array(); + foreach ($rows_new as $path => $row_new) { + // If only a new path, then do an insert. + if (!isset($rows_old[$path])) { + $changes_insert[$path] = $row_new; + } + else { + // Get a sepcific subset of changes for this row. + $row_old = $rows_old[$path]; + $row_changes = array(); + foreach ($row_new as $key => $value) { + if ($value != $row_old[$key]) { + $row_changes[$key] = $value; + } + } + + // If nothing changed, do nothing. + if (!empty($row_changes)) { + $changes_update[$path] = $row_changes; + } + + // Make sure this row is not triggered for deletion. + unset($rows_old[$path]); + } + } + + // Delete remaining rows. + foreach ($rows_old as $path => $row_old) { + $changes_delete[$path] = $path; + } + + // Perform the inserts. + if (!empty($changes_insert)) { + // The SQL is always the same, only the values change. + $fields = array_keys(current($changes_insert)); + $tokens = db_placeholders($fields, 'varchar'); + $fields = implode(', ', $fields); + // @todo: ON DUPLICATE KEY UPDATE ? + $sql = "INSERT INTO {menu_router} ($fields) VALUES ($tokens)"; + + foreach ($changes_insert as $row) { + db_query($sql, array_values($row)); + } + } + + // Perform the updates. + foreach ($changes_update as $path => $row_changes) { + // The path should not change. (It's our key). + unset($row_changes['path']); + + $set = $args = array(); + foreach ($row_changes as $k => $v) { + $set[] = "$k = '%s'"; + $args[] = $v; + } + + $set = implode(', ', $set); + $sql = "UPDATE {menu_router} SET $set WHERE path = '%s'"; + + $args[] = $path; + db_query($sql, $args); + } + + // Perform the delets. + $sql = "DELETE FROM {menu_router} WHERE path = '%s'"; + foreach ($changes_delete as $path) { + db_query($sql, $path); + } + + // Store the new rows in a cache for use as old rows on next rebuild. + // We can't store this in cache_menu, as this would get wiped almost instantlty + // due to a call to menu_cache_clear_all() right after the router build, + // meaning we could never actually use it, so store in cache instead. + cache_set('menu_router_build_old_rows', $rows_new); + _menu_router_cache($menu); } return $menu; @@ -2358,8 +2479,7 @@ function _menu_router_build($callbacks) watchdog('php', 'Menu router rebuild failed - some paths may not work correctly.', array(), WATCHDOG_ERROR); return array(); } - // Delete the existing router since we have some data to replace it. - db_query('DELETE FROM {menu_router}'); + // Apply inheritance rules. foreach ($menu as $path => $v) { $item = &$menu[$path]; @@ -2439,24 +2559,6 @@ function _menu_router_build($callbacks) $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']); $item['include file'] = $file_path .'/'. $item['file']; } - - $title_arguments = $item['title arguments'] ? serialize($item['title arguments']) : ''; - db_query("INSERT INTO {menu_router} - (path, load_functions, to_arg_functions, access_callback, - access_arguments, page_callback, page_arguments, fit, - number_parts, tab_parent, tab_root, - title, title_callback, title_arguments, - type, block_callback, description, position, weight, file) - VALUES ('%s', '%s', '%s', '%s', - '%s', '%s', '%s', %d, - %d, '%s', '%s', - '%s', '%s', '%s', - %d, '%s', '%s', '%s', %d, '%s')", - $path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'], - serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'], - $item['_number_parts'], $item['tab_parent'], $item['tab_root'], - $item['title'], $item['title callback'], $title_arguments, - $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight'], $item['include file']); } // Sort the masks so they are in order of descending fit, and store them. $masks = array_keys($masks);