Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.348
diff -u -p -r1.348 menu.inc
--- includes/menu.inc	1 Oct 2009 19:07:12 -0000	1.348
+++ includes/menu.inc	6 Oct 2009 20:49:04 -0000
@@ -1447,7 +1447,13 @@ function menu_get_names() {
  * Return an array containing the names of system-defined (default) menus.
  */
 function menu_list_system_menus() {
-  return array('navigation' => 'Navigation', 'management' => 'Management', 'user-menu' => 'User menu', 'main-menu' => 'Main menu', 'secondary-menu' => 'Secondary menu');
+  return array(
+    'navigation' => 'Navigation',
+    'management' => 'Management',
+    'user-menu' => 'User menu',
+    'main-menu' => 'Main menu',
+    'secondary-menu' => 'Secondary menu',
+  );
 }
 
 /**
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.383
diff -u -p -r1.383 block.module
--- modules/block/block.module	5 Oct 2009 01:18:25 -0000	1.383
+++ modules/block/block.module	6 Oct 2009 20:49:04 -0000
@@ -852,6 +852,20 @@ function block_filter_format_delete($for
 }
 
 /**
+ * Implement hook_menu_delete().
+ */
+function block_menu_delete($menu) {
+  db_delete('block')
+    ->condition('module', 'menu')
+    ->condition('delta', $menu['menu_name'])
+    ->execute();
+  db_delete('block_role')
+    ->condition('module', 'menu')
+    ->condition('delta', $menu['menu_name'])
+    ->execute();
+}
+
+/**
  * Implement hook_form_FORM_ID_alter().
  */
 function block_form_system_performance_settings_alter(&$form, &$form_state) {
Index: modules/menu/menu.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.admin.inc,v
retrieving revision 1.61
diff -u -p -r1.61 menu.admin.inc
--- modules/menu/menu.admin.inc	3 Oct 2009 01:16:02 -0000	1.61
+++ modules/menu/menu.admin.inc	6 Oct 2009 20:49:04 -0000
@@ -406,7 +406,11 @@ function menu_edit_item_submit($form, &$
  */
 function menu_edit_menu($form, &$form_state, $type, $menu = array()) {
   $system_menus = menu_list_system_menus();
-  $menu += array('menu_name' => '', 'title' => '', 'description' => '');
+  $menu += array('menu_name' => '', 'old_name' => '', 'title' => '', 'description' => '');
+  if (!empty($menu['menu_name'])) {
+    $menu['old_name'] = $menu['menu_name'];
+  }
+  $form['old_name'] = array('#type' => 'value', '#value' => $menu['old_name']);
 
   // The title of a system menu cannot be altered.
   if (isset($system_menus[$menu['menu_name']])) {
@@ -423,7 +427,7 @@ function menu_edit_menu($form, &$form_st
   }
 
   // The internal menu name can only be defined during initial menu creation.
-  if ($type == 'edit') {
+  if (!empty($menu['old_name'])) {
     $form['#insert'] = FALSE;
     $form['menu_name'] = array('#type' => 'value', '#value' => $menu['menu_name']);
   }
@@ -514,41 +518,28 @@ function menu_delete_menu_confirm($form,
 function menu_delete_menu_confirm_submit($form, &$form_state) {
   $menu = $form['#menu'];
   $form_state['redirect'] = 'admin/structure/menu';
+
   // System-defined menus may not be deleted - only menus defined by this module.
   $system_menus = menu_list_system_menus();
   if (isset($system_menus[$menu['menu_name']])  || !(db_query("SELECT 1 FROM {menu_custom} WHERE menu_name = :menu", array(':menu' => $menu['menu_name']))->fetchField())) {
     return;
   }
-  // Reset all the menu links defined by the system via hook_menu.
+
+  // Reset all the menu links defined by the system via hook_menu().
   $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.menu_name = :menu AND ml.module = 'system' ORDER BY m.number_parts ASC", array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
-  foreach ($result as $item) {
-    menu_reset_item($item);
+  foreach ($result as $link) {
+    menu_reset_item($link);
   }
+
   // Delete all links to the overview page for this menu.
   $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu/manage/' . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
-  foreach ($result as $m) {
-    menu_link_delete($m['mlid']);
+  foreach ($result as $link) {
+    menu_link_delete($link['mlid']);
   }
-  // Delete all the links in the menu and the menu from the list of custom menus.
-  db_delete('menu_links')
-    ->condition('menu_name', $menu['menu_name'])
-    ->execute();
-  db_delete('menu_custom')
-    ->condition('menu_name', $menu['menu_name'])
-    ->execute();
-  // Delete all the blocks for this menu.
-  if (module_exists('block')) {
-    db_delete('block')
-      ->condition('module', 'menu')
-      ->condition('delta', $menu['menu_name'])
-      ->execute();
-    db_delete('block_role')
-      ->condition('module', 'menu')
-      ->condition('delta', $menu['menu_name'])
-      ->execute();
-  }
-  menu_cache_clear_all();
-  cache_clear_all();
+
+  // Delete the custom menu and all its menu links.
+  menu_delete($menu);
+
   $t_args = array('%title' => $menu['title']);
   drupal_set_message(t('The custom menu %title has been deleted.', $t_args));
   watchdog('menu', 'Deleted custom menu %title and all its menu links.', $t_args, WATCHDOG_NOTICE);
@@ -569,8 +560,8 @@ function menu_edit_menu_validate($form, 
 
     // We will add 'menu-' to the menu name to help avoid name-space conflicts.
     $item['menu_name'] = 'menu-' . $item['menu_name'];
-    $custom_exists = db_query('SELECT menu_name FROM {menu_custom} WHERE menu_name = :menu', array(':menu' => $item['menu_name']))->fetchField();
-    $link_exists = db_query_range("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu", 0, 1, array(':menu' => $item['menu_name']))->fetchField();
+    $custom_exists = db_query_range('SELECT 1 FROM {menu_custom} WHERE menu_name = :menu', 0, 1, array(':menu' => $item['menu_name']))->fetchField();
+    $link_exists = db_query_range("SELECT 1 FROM {menu_links} WHERE menu_name = :menu", 0, 1, array(':menu' => $item['menu_name']))->fetchField();
     if ($custom_exists || $link_exists) {
       form_set_error('menu_name', t('The menu already exists.'));
     }
@@ -597,22 +588,10 @@ function menu_edit_menu_submit($form, &$
     ->fetchField();
 
     menu_link_save($link);
-    db_insert('menu_custom')
-      ->fields(array(
-        'menu_name' => $menu['menu_name'],
-        'title' => $menu['title'],
-        'description' => $menu['description'],
-      ))
-      ->execute();
+    menu_save($menu);
   }
   else {
-    db_update('menu_custom')
-      ->fields(array(
-        'title' => $menu['title'],
-        'description' => $menu['description'],
-      ))
-      ->condition('menu_name', $menu['menu_name'])
-      ->execute();
+    menu_save($menu);
     $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path", array(':path' => $path . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
     foreach ($result as $m) {
       $link = menu_link_load($m['mlid']);
Index: modules/menu/menu.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.api.php,v
retrieving revision 1.14
diff -u -p -r1.14 menu.api.php
--- modules/menu/menu.api.php	5 Oct 2009 04:36:39 -0000	1.14
+++ modules/menu/menu.api.php	6 Oct 2009 20:49:04 -0000
@@ -291,7 +291,7 @@ function hook_translated_menu_link_alter
   }
 }
 
- /**
+/**
  * Inform modules that a menu link has been created.
  *
  * This hook is used to notify modules that menu items have been
@@ -358,5 +358,77 @@ function hook_menu_link_delete($link) {
 }
 
 /**
+ * Informs modules that a custom menu was created.
+ *
+ * This hook is used to notify modules that a custom menu has been created.
+ * Contributed modules may use the information to perform actions based on the
+ * information entered into the menu system.
+ *
+ * @param $menu
+ *   An array representing a custom menu:
+ *   - menu_name: The unique name of the custom menu.
+ *   - title: The human readable menu title.
+ *   - description: The custom menu description.
+ *
+ * @see hook_menu_update()
+ * @see hook_menu_delete()
+ */
+function hook_menu_insert($menu) {
+  // For example, we track available menus in a variable.
+  $my_menus = variable_get('my_module_menus', array());
+  $my_menus[$menu['menu_name']] = $menu['menu_name'];
+  variable_set('my_module_menus', $my_menus);
+}
+
+/**
+ * Informs modules that a custom menu was updated.
+ *
+ * This hook is used to notify modules that a custom menu has been updated.
+ * Contributed modules may use the information to perform actions based on the
+ * information entered into the menu system.
+ *
+ * @param $menu
+ *   An array representing a custom menu:
+ *   - menu_name: The unique name of the custom menu.
+ *   - title: The human readable menu title.
+ *   - description: The custom menu description.
+ *   - old_name: The current 'menu_name'. Note that internal menu names cannot
+ *     be changed after initial creation.
+ *
+ * @see hook_menu_insert()
+ * @see hook_menu_delete()
+ */
+function hook_menu_update($menu) {
+  // For example, we track available menus in a variable.
+  $my_menus = variable_get('my_module_menus', array());
+  $my_menus[$menu['menu_name']] = $menu['menu_name'];
+  variable_set('my_module_menus', $my_menus);
+}
+
+/**
+ * Informs modules that a custom menu was deleted.
+ *
+ * This hook is used to notify modules that a custom menu along with all links
+ * contained in it (if any) has been deleted. Contributed modules may use the
+ * information to perform actions based on the information entered into the menu
+ * system.
+ *
+ * @param $link
+ *   An array representing a custom menu:
+ *   - menu_name: The unique name of the custom menu.
+ *   - title: The human readable menu title.
+ *   - description: The custom menu description.
+ *
+ * @see hook_menu_insert()
+ * @see hook_menu_update()
+ */
+function hook_menu_delete($menu) {
+  // Delete the record from our variable.
+  $my_menus = variable_get('my_module_menus', array());
+  unset($my_menus[$menu['menu_name']]);
+  variable_set('my_module_menus', $my_menus);
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/menu/menu.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.install,v
retrieving revision 1.20
diff -u -p -r1.20 menu.install
--- modules/menu/menu.install	10 Sep 2009 06:38:19 -0000	1.20
+++ modules/menu/menu.install	6 Oct 2009 20:49:04 -0000
@@ -11,17 +11,21 @@
  */
 function menu_install() {
   $system_menus = menu_list_system_menus();
+  $t = get_t();
   $descriptions = array(
-    'navigation' => 'The <em>Navigation</em> menu contains links such as Recent posts (if the Tracker module is enabled). Non-administrative links are added to this menu by default by modules.',
-    'user-menu' => "The <em>User menu</em> contains links related to the user's account, as well as the 'Log out' link.",
-    'management' => 'The <em>Management</em> menu contains links for content creation, structure, user management, and similar site activities.',
-    'main-menu' => 'The <em>Main menu</em> is the default source for the Main links which are often used by themes to show the major sections of a site.',
-    'secondary-menu' => 'The <em>Secondary menu</em> is the default source for the Secondary links which are often used for legal notices, contact details, and other navigation items that play a lesser role than the Main links.',
+    'navigation' => $t('The <em>Navigation</em> menu contains links such as Recent posts (if the Tracker module is enabled). Non-administrative links are added to this menu by default by modules.'),
+    'user-menu' => $t("The <em>User menu</em> contains links related to the user's account, as well as the 'Log out' link."),
+    'management' => $t('The <em>Management</em> menu contains links for content creation, structure, user management, and similar site activities.'),
+    'main-menu' => $t('The <em>Main menu</em> is the default source for the Main links which are often used by themes to show the major sections of a site.'),
+    'secondary-menu' => $t('The <em>Secondary menu</em> is the default source for the Secondary links which are often used for legal notices, contact details, and other navigation items that play a lesser role than the Main links.'),
   );
-  $t = get_t();
-  $query = db_insert('menu_custom')->fields(array('menu_name', 'title', 'description'));
   foreach ($system_menus as $menu_name => $title) {
-    $query->values(array('menu_name' => $menu_name, 'title' => $t($title), 'description' => $t($descriptions[$menu_name])))->execute();
+    $menu = array(
+      'menu_name' => $menu_name,
+      'title' => $t($title),
+      'description' => $descriptions[$menu_name],
+    );
+    menu_save($menu);
   }
 }
 
Index: modules/menu/menu.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v
retrieving revision 1.206
diff -u -p -r1.206 menu.module
--- modules/menu/menu.module	3 Oct 2009 01:16:02 -0000	1.206
+++ modules/menu/menu.module	6 Oct 2009 20:52:27 -0000
@@ -198,12 +198,98 @@ function menu_overview_title($menu) {
 
 /**
  * Load the data for a single custom menu.
+ *
+ * @param $menu_name
+ *   The unique name of a custom menu to load.
  */
 function menu_load($menu_name) {
   return db_query("SELECT * FROM {menu_custom} WHERE menu_name = :menu", array(':menu' => $menu_name))->fetchAssoc();
 }
 
 /**
+ * Save a custom menu.
+ *
+ * @param $menu
+ *   An array representing a custom menu:
+ *   - menu_name: The unique name of the custom menu.
+ *   - title: The human readable menu title.
+ *   - description: The custom menu description.
+ *   - old_name: For existing menus, the current 'menu_name', otherwise empty.
+ *     Decides whether hook_menu_insert() or hook_menu_update() will be invoked.
+ *
+ * Modules should always pass a fully populated $menu when saving a custom
+ * menu, so other modules are able to output proper status or watchdog messages.
+ *
+ * @see menu_load()
+ */
+function menu_save($menu) {
+  db_merge('menu_custom')
+    ->key(array('menu_name' => $menu['menu_name']))
+    ->fields(array(
+      'title' => $menu['title'],
+      'description' => $menu['description'],
+    ))
+    ->execute();
+
+  // Since custom menus are keyed by name and their machine-name cannot be
+  // changed, there is no real differentiation between inserting and updating a
+  // menu. To overcome this, we define the existing menu_name as 'old_name' in
+  // menu_edit_menu().
+  // @todo Replace this condition when db_merge() returns the proper query
+  //   result (insert/update).
+  if (!empty($menu['old_name'])) {
+    module_invoke_all('menu_update', $menu);
+  }
+  else {
+    module_invoke_all('menu_insert', $menu);
+  }
+}
+
+/**
+ * Delete a custom menu and all contained links.
+ *
+ * Note that this function deletes all menu links in a custom menu. While menu
+ * links derived from router paths may be restored by rebuilding the menu, all
+ * customized and custom links will be irreversibly gone. Therefore, this
+ * function should usually be called from a user interface (form submit) handler
+ * only, which allows the user to confirm the action.
+ *
+ * @param $menu
+ *   An array representing a custom menu:
+ *   - menu_name: The unique name of the custom menu.
+ *   - title: The human readable menu title.
+ *   - description: The custom menu description.
+ *
+ * Modules should always pass a fully populated $menu when deleting a custom
+ * menu, so other modules are able to output proper status or watchdog messages.
+ *
+ * @see menu_load()
+ *
+ * _menu_delete_item() will take care of clearing the page cache. Other modules
+ * should take care of their menu-related data by implementing
+ * hook_menu_delete().
+ */
+function menu_delete($menu) {
+  // Delete all links from the menu.
+  $links = db_query("SELECT * FROM {menu_links} WHERE menu_name = :menu_name", array(':menu_name' => $menu['menu_name']));
+  foreach ($links as $link) {
+    // To speed up the deletion process, we reset some link properties that
+    // would trigger re-parenting logic in _menu_delete_item() and
+    // _menu_update_parental_status().
+    $link['has_children'] = FALSE;
+    $link['plid'] = 0;
+    _menu_delete_item($link);
+  }
+
+  // Delete the custom menu.
+  db_delete('menu_custom')
+    ->condition('menu_name', $menu['menu_name'])
+    ->execute();
+
+  module_invoke_all('menu_delete', $menu);
+}
+
+/**
  * Return a list of menu items that are valid possible parents for the given menu item.
  *
  * @param $menus
@@ -496,3 +582,4 @@ function menu_get_menus($all = TRUE) {
 
   return $query->execute()->fetchAllKeyed();
 }
+
Index: modules/menu/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.test,v
retrieving revision 1.22
diff -u -p -r1.22 menu.test
--- modules/menu/menu.test	28 Sep 2009 22:14:30 -0000	1.22
+++ modules/menu/menu.test	6 Oct 2009 20:49:04 -0000
@@ -92,11 +92,38 @@ class MenuTestCase extends DrupalWebTest
     $this->menu = $this->addCustomMenu();
     $this->doMenuTests($this->menu['menu_name']);
     $this->addInvalidMenuLink($this->menu['menu_name']);
+    $this->addCustomMenuCRUD();
+  }
+
+  /**
+   * Add custom menu using CRUD functions.
+   */
+  function addCustomMenuCRUD() {
+    // Add a new custom menu.
+    $menu_name = substr(md5($this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
+    $title = $this->randomName(16);
+
+    $menu = array(
+      'menu_name' => $menu_name,
+      'title' => $title,
+      'description' => 'Description text',
+    );
+    menu_save($menu);
+
+    // Assert the new menu.
+    $this->drupalGet('admin/structure/menu/manage/' . $menu_name . '/edit');
+    $this->assertText($title, t('Custom menu was added.'));
+
+    // Edit the menu.
+    $new_title = $this->randomName(16);
+    $menu['title'] = $new_title;
+    menu_save($menu);
+    $this->drupalGet('admin/structure/menu/manage/' . $menu_name . '/edit');
+    $this->assertText($new_title, t('Custom menu was edited.'));
   }
 
   /**
    * Add custom menu.
-   *
    */
   function addCustomMenu() {
     // Add custom menu.
@@ -152,6 +179,9 @@ class MenuTestCase extends DrupalWebTest
     $this->assertResponse(200);
     $this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $title)), t('Custom menu was deleted'));
     $this->assertFalse(menu_load($menu_name), 'Custom menu was deleted');
+    // Test if all menu links associated to the menu were removed from database.
+    $result = db_query("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu_name", array(':menu_name' => $menu_name))->fetchField();
+    $this->assertFalse($result, t('All menu links associated to the custom menu were deleted.'));    
   }
 
   /**
Index: modules/toolbar/toolbar.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.install,v
retrieving revision 1.4
diff -u -p -r1.4 toolbar.install
--- modules/toolbar/toolbar.install	31 Aug 2009 17:09:01 -0000	1.4
+++ modules/toolbar/toolbar.install	6 Oct 2009 20:49:04 -0000
@@ -14,13 +14,12 @@
  */
 function toolbar_install() {
   $t = get_t();
-  $query = db_insert('menu_custom')
-    ->fields(array(
-      'menu_name' => 'admin_shortcuts',
-      'title' => $t('Administration shortcuts'),
-      'description' => $t('The <em>Admininstration shortcuts</em> menu contains commonly used links for administrative tasks.')
-    ))
-    ->execute();
+  $menu = array(
+    'menu_name' => 'admin_shortcuts',
+    'title' => $t('Administration shortcuts'),
+    'description' => $t('The <em>Administration shortcuts</em> menu contains commonly used links for administrative tasks.'),
+  );
+  menu_save($menu);
 
   // Add starter convenience shortcuts.
   menu_rebuild();
