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 15:24:12 -0000 @@ -1738,6 +1738,180 @@ function menu_router_build($reset = FALS drupal_alter('menu', $callbacks); $menu = _menu_router_build($callbacks); _menu_router_cache($menu); + + // We no longer need the callbacks. + unset($callbacks, $router_items, $module, $path); + + // Get the old menu from the cache if we can, otherwise assume an initial build + // and empty menu_router. + $menu_old = array(); + if ($data = cache_get('menu_router_build_old_menu')) { + $menu_old = $data->data; + + // Unset $data to save on memory usage. + unset($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 (array_keys($menu) as $path) { + $row_new = $menu[$path]; + + // If only a new path, then do an insert. + if (!isset($menu_old[$path])) { + $changes_insert[$path] = $row_new; + } + else { + // Get a sepcific subset of changes for this row. + $row_changes = array(); + foreach ($row_new as $key => $value) { + if ($value != $menu_old[$path][$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($menu_old[$path]); + } + } + + // Delete remaining rows. + foreach ($menu_old as $path => $row_old) { + $changes_delete[$path] = $path; + } + + // We no longer need $menu_old or other items. + unset($menu_old, $path, $row_new, $row_changes, $row_old); + + // Set up a database map. + $db_map = array( + 'path' => 'path', + 'load_functions' => 'load_functions', + 'to_arg_functions' => 'to_arg_functions', + 'access callback' => 'access_callback', + 'access arguments' => 'access_arguments', + 'page callback' => 'page_callback', + 'page arguments' => 'page_arguments', + '_fit' => 'fit', + '_number_parts' => 'number_parts', + 'tab_parent' => 'tab_parent', + 'tab_root' => 'tab_root', + 'title' => 'title', + 'title callback' => 'title_callback', + 'title arguments' => 'title_arguments', + 'type' => 'type', + 'block callback' => 'block_callback', + 'description' => 'description', + 'position' => 'position', + 'weight' => 'weight', + 'include file' => 'file', + ); + + // Perform the inserts. + if (!empty($changes_insert)) { + // The SQL is always the same, only the values change. + $menu_fields = array_keys(current($changes_insert)); + + // Map fields from the router build to the db equivelant. + $fields = array(); + $value_fields = array(); + foreach ($menu_fields as $mfield) { + if (isset($db_map[$mfield])) { + $fields[] = $db_map[$mfield]; + $value_fields[] = $mfield; + } + } + + $tokens = db_placeholders($fields, 'varchar'); + $fields = implode(', ', $fields); + $sql = "INSERT INTO {menu_router} ($fields) VALUES ($tokens)"; + + foreach (array_keys($changes_insert) as $key) { + // Map values from the router build the db equivelant. + $values = array(); + foreach ($value_fields as $mfield) { + $value = $changes_insert[$key][$mfield]; + if ($mfield == 'access arguments' || $mfield == 'page arguments') { + $values[] = serialize($value); + } + elseif ($mfield == 'title arguments') { + $values[] = $value ? serialize($value) : ''; + } + else { + $values[] = $value; + } + } + + db_query($sql, $values); + } + } + + // We no longer need the insert changes. + unset($changes_insert, $menu_fields, $fields, $value_fields, $tokens, $sql, $key, $mfield, $value, $values); + + // Perform the updates. + foreach (array_keys($changes_update) as $path) { + $row_changes = $changes_update[$path]; + + // The path should not change. (It's our key). + unset($row_changes['path']); + + $set = $args = array(); + foreach ($row_changes as $mfield => $value) { + if (isset($db_map[$mfield])) { + $set[] = $db_map[$mfield] ." = '%s'"; + if ($mfield == 'access arguments' || $mfield == 'page arguments') { + $args[] = serialize($value); + } + elseif ($mfield == 'title arguments') { + $args[] = $value ? serialize($value) : ''; + } + else { + $args[] = $value; + } + } + } + + if (!empty($set)) { + $set = implode(', ', $set); + $sql = "UPDATE {menu_router} SET $set WHERE path = '%s'"; + + $args[] = $path; + db_query($sql, $args); + } + } + + // We no longer need the update changes. + unset($changes_update, $row_changes, $path, $set, $args, $db_map, $mfield, $value, $sql); + + // Perform the delets. + $sql = "DELETE FROM {menu_router} WHERE path = '%s'"; + foreach ($changes_delete as $path) { + db_query($sql, $path); + } + + // We no longer need the delete changes. + unset($changes_delete, $path, $sql); + + // 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_menu', $menu); } return $menu; } @@ -2358,8 +2532,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 +2612,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);