? 306151-schema-based-install.patch
? 457450-menu-item-hooks.patch
? 457450-menu-link-hooks.patch
? 457450-menu-objects-ext_0.patch
? 557356-menu-object.patch
? sites/default/files
? sites/default/private
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.337
diff -u -p -r1.337 menu.inc
--- includes/menu.inc	22 Aug 2009 19:58:27 -0000	1.337
+++ includes/menu.inc	23 Aug 2009 21:40:40 -0000
@@ -2182,6 +2182,8 @@ function menu_link_save(&$item) {
     'module' => 'menu',
     'customized' => 0,
     'updated' => 0,
+    'object_type' => '',
+    'object_id' => '',
   );
   $existing_item = FALSE;
   if (isset($item['mlid'])) {
@@ -2253,6 +2255,8 @@ function menu_link_save(&$item) {
         'options' => serialize($item['options']),
         'customized' => $item['customized'],
         'updated' => $item['updated'],
+        'object_type' => $item['object_type'],
+        'object_id' => $item['object_id'],
       ))
       ->execute();
   }
@@ -2286,11 +2290,16 @@ function menu_link_save(&$item) {
   if (empty($item['router_path'])  || !$existing_item || ($existing_item['link_path'] != $item['link_path'])) {
     if ($item['external']) {
       $item['router_path'] = '';
+      // External links are marked as objects, but have no IDs.
+      $item['object_type'] = 'external_link';
+      $item['object_id'] = '';
     }
     else {
       // Find the router path which will serve this path.
       $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
       $item['router_path'] = _menu_find_router_path($item['link_path']);
+      // Find the object type of this item.
+      _menu_link_find_object_type($item);
     }
   }
   // If every value in $existing_item is the same in the $item, there is no
@@ -2324,6 +2333,8 @@ function menu_link_save(&$item) {
         'link_title' => $item['link_title'],
         'options' => serialize($item['options']),
         'customized' => $item['customized'],
+        'object_type' => $item['object_type'],
+        'object_id' => $item['object_id'],
       ))
       ->condition('mlid', $item['mlid'])
       ->execute();
@@ -2410,6 +2421,43 @@ function _menu_find_router_path($link_pa
   return $router_path;
 }
 
+ /**
+ * Determine the object type and ID for a menu item.
+ *
+ * This function takes the menu $link by reference and attempts
+ * to deduce the object type and ID based on the router and its
+ * associated loader functions.
+ *
+ * @param &$link
+ *   The menu item to be saved.
+ */
+function _menu_link_find_object_type(&$link) {
+  // If there is no router path, we cannot continue.
+  if (empty($link['router_path'])) {
+    return;
+  }
+  $loader = unserialize(db_query("SELECT load_functions FROM {menu_router} WHERE path = :path", array(':path' => $link['router_path']))->fetchField());
+  if (isset($loader) && is_array($loader)) {
+    $link['object_type'] = str_replace('_load', '', current($loader));
+    $parts = explode('/', $link['router_path']);
+    $data = explode('/', $link['link_path']);
+    // The router_path uses path/% and the link_path uses path/ID.
+    // Find the first % array element and match it to the path.
+    // This should be sufficient in most cases, since items with multiple
+    // loaders are not normally in the {menu_links} table.
+    foreach ($parts as $key => $value) {
+      // There are items in the {menu_links} table with no IDs.
+      if ($value == '%' && $data[$key] != '%') {
+        $link['object_id'] = $data[$key];
+      }
+    }
+  }
+  // Remove unknown types.
+  if (empty($link['object_id']) && $link['object_type'] != 'external_link') {
+    $link['object_type'] = '';
+  }
+}
+
 /**
  * Insert, update or delete an uncustomized menu link related to a module.
  *
Index: modules/simpletest/tests/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu.test,v
retrieving revision 1.13
diff -u -p -r1.13 menu.test
--- modules/simpletest/tests/menu.test	14 Jul 2009 20:53:16 -0000	1.13
+++ modules/simpletest/tests/menu.test	23 Aug 2009 21:41:39 -0000
@@ -125,6 +125,24 @@ class MenuIncTestCase extends DrupalWebT
     $this->assertEqual($compare_item, $item, t('Modified menu item is equal to newly retrieved menu item.'), 'menu');
   }
 
+  /**
+   * Test menu object storage.
+   */
+  function testMenuObject() {
+    $node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0));
+    $mlid = menu_link_maintain('menu_test', 'insert', "node/$node->nid", 'Menu link #1');
+    // This should create a menu item with object type of 'node' and an ID of $node->nid.
+    $data = db_query("SELECT object_type, object_id FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
+    $this->assertEqual($data['object_type'], 'node', t('Store a menu object type for a new menu item.'));
+    $this->assertEqual($data['object_id'], $node->nid, t('Store a menu object ID for a new menu item.'));
+    // Check to be sure that unknown items are not tracked.
+    $mlid = menu_link_maintain('menu_test', 'insert', 'menu_test_hierarchy_parent', 'Menu link custom');
+    // This should create a menu item with NULL object type and object ID..
+    $data = db_query("SELECT object_type, object_id FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
+    $this->assertEqual($data['object_type'], '', t('Ignore a menu object type for a new menu item with no loader function.'));
+    $this->assertEqual($data['object_id'], '', t('Ignore a menu object ID for a new menu item with no loader function.'));
+  }
+
 }
 
 /**
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.376
diff -u -p -r1.376 system.install
--- modules/system/system.install	22 Aug 2009 18:24:14 -0000	1.376
+++ modules/system/system.install	23 Aug 2009 21:41:46 -0000
@@ -1136,12 +1136,27 @@ function system_schema() {
         'default' => 0,
         'size' => 'small',
       ),
+      'object_type' => array(
+        'description' => 'An object type identifier for connecting menu items to external systems.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'object_id' => array(
+        'description' => 'A foreign key for objects stored in external systems.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
     ),
     'indexes' => array(
       'path_menu' => array(array('link_path', 128), 'menu_name'),
       'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'),
       'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
       'router_path' => array(array('router_path', 128)),
+      'object' => array('object_type', 'object_id'),
     ),
     'primary key' => array('mlid'),
   );
@@ -2455,6 +2470,25 @@ function system_update_7036() {
   return $ret;
 }
 
+ /**
+ * Add foreign object tracking data to {menu_links}.
+ */
+function system_update_7037() {
+  $ret = array();
+  db_add_field($ret, 'menu_links', 'object_type', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
+  db_add_field($ret, 'menu_links', 'object_id', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
+  db_add_index($ret, 'menu_links', 'object', array('object_type', 'object_id'));
+  // Update the {menu_links} table, ignoring items with wildcard link_path entries.
+  $links = db_query("SELECT mlid, link_path, router_path, object_type, object_id FROM {menu_links} WHERE router_path LIKE '%/\%' AND router_path != link_path")->fetchAll(PDO::FETCH_ASSOC);
+  foreach ($links as $item) {
+    _menu_link_find_object_type($item);
+    if (isset($item['object_type'])) {
+      $ret[] = update_sql(sprintf("UPDATE {menu_links} SET object_type = '%s', object_id = '%s' WHERE mlid = %d", $item['object_type'], $item['object_id'], $item['mlid']));
+    }
+  }
+  return $ret;
+}
+
 /**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
