Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1091
diff -u -9 -p -r1.1091 common.inc
--- includes/common.inc	18 Jan 2010 03:28:13 -0000	1.1091
+++ includes/common.inc	20 Jan 2010 17:39:59 -0000
@@ -2716,24 +2716,18 @@ function l($text, $path, array $options 
       'html' => FALSE,
     );
 
   // Append active class.
   if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
       (empty($options['language']) || $options['language']->language == $language_url->language)) {
     $options['attributes']['class'][] = 'active';
   }
 
-  // Remove all HTML and PHP tags from a tooltip. For best performance, we act only
-  // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
-  if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
-    $options['attributes']['title'] = strip_tags($options['attributes']['title']);
-  }
-
   // Determine if rendering of the link is to be done with a theme function
   // or the inline default. Inline is faster, but if the theme system has been
   // loaded and a module or theme implements a preprocess or process function
   // or overrides the theme_link() function, then invoke theme(). Preliminary
   // benchmarks indicate that invoking theme() can slow down the l() function
   // by 20% or more, and that some of the link-heavy Drupal pages spend more
   // than 10% of the total page request time in the l() function.
   if (!isset($use_theme) && function_exists('theme')) {
     // Allow edge cases to prevent theme initialization and force inline link
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.375
diff -u -9 -p -r1.375 menu.inc
--- includes/menu.inc	14 Jan 2010 13:45:33 -0000	1.375
+++ includes/menu.inc	20 Jan 2010 17:40:00 -0000
@@ -648,25 +648,41 @@ function _menu_item_localize(&$item, $ma
       // Avoid calling check_plain again on l() function.
       if ($callback == 'check_plain') {
         $item['localized_options']['html'] = TRUE;
       }
     }
   }
   elseif ($link_translate) {
     $item['title'] = $item['link_title'];
   }
+  if ($link_translate) {
+    $item['description'] = $item['link_description'];
+  }
 
   // Translate description, see the motivation above.
   if (!empty($item['description'])) {
     $original_description = $item['description'];
     $item['description'] = t($item['description']);
-    if ($link_translate && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) {
-      $item['localized_options']['attributes']['title'] = $item['description'];
+    if ($link_translate && isset($item['options']['attributes']['title'])) {
+      // For performance reasons, only run strip_tags() and decode_entities()
+      // when needed.
+      if ($item['options']['attributes']['title'] == $original_description) {
+        // The original description does not contain tags or entities, so we
+        // assume that the translated description does not either.
+        $item['localized_options']['attributes']['title'] = $item['description'];
+      }
+      elseif (strcspn($original_description, '<&') < strlen($original_description) && $item['options']['attributes']['title'] == decode_entities(strip_tags($original_description))) {
+        // The description contains entities and/or tags, but the link title
+        // attribute matches the original description after tags have been
+        // stripped and entities converted, so strip tags and convert entities
+        // in the translated description.
+        $item['localized_options']['attributes']['title'] = decode_entities(strip_tags($item['description']));
+      }
     }
   }
 }
 
 /**
  * Handles dynamic path translation and menu access control.
  *
  * When a user arrives on a page such as node/5, this function determines
  * what "5" corresponds to, by inspecting the page's menu path definition,
@@ -2335,22 +2351,23 @@ function _menu_link_build($item) {
   // Hide all items that are not visible in the tree.
   elseif (!($item['type'] & MENU_VISIBLE_IN_TREE)) {
     $item['hidden'] = -1;
   }
   // Note, we set this as 'system', so that we can be sure to distinguish all
   // the menu links generated automatically from entries in {menu_router}.
   $item['module'] = 'system';
   $item += array(
     'menu_name' => 'navigation',
+    'link_description' => $item['description'],
     'link_title' => $item['title'],
     'link_path' => $item['path'],
     'hidden' => 0,
-    'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
+    'options' => empty($item['description']) ? array() : array('attributes' => array('title' => decode_entities(strip_tags($item['description'])))),
   );
   return $item;
 }
 
 /**
  * Helper function to build menu links for the items in the menu router.
  */
 function _menu_navigation_links_rebuild($menu) {
   // Add normal and suggested items as links.
@@ -2591,18 +2608,19 @@ function menu_link_save(&$item) {
 
   // This is the easiest way to handle the unique internal path '<front>',
   // since a path marked as external does not need to match a router path.
   $item['external'] = (url_is_external($item['link_path'])  || $item['link_path'] == '<front>') ? 1 : 0;
   // Load defaults.
   $item += array(
     'menu_name' => 'navigation',
     'weight' => 0,
     'link_title' => '',
+    'link_description' => '',
     'hidden' => 0,
     'has_children' => 0,
     'expanded' => 0,
     'options' => array(),
     'module' => 'menu',
     'customized' => 0,
     'updated' => 0,
   );
   $existing_item = FALSE;
@@ -2666,18 +2684,19 @@ function menu_link_save(&$item) {
         'link_path' => $item['link_path'],
         'hidden' => $item['hidden'],
         'external' => $item['external'],
         'has_children' => $item['has_children'],
         'expanded' => $item['expanded'],
         'weight' => $item['weight'],
         'module' => $item['module'],
         'link_title' => $item['link_title'],
         'options' => serialize($item['options']),
+        'link_description' => $item['link_description'],
         'customized' => $item['customized'],
         'updated' => $item['updated'],
       ))
       ->execute();
   }
 
   // Directly fill parents for top-level links.
   if ($item['plid'] == 0) {
     $item['p1'] = $item['mlid'];
@@ -2740,18 +2759,19 @@ function menu_link_save(&$item) {
         'p4' => $item['p4'],
         'p5' => $item['p5'],
         'p6' => $item['p6'],
         'p7' => $item['p7'],
         'p8' => $item['p8'],
         'p9' => $item['p9'],
         'module' => $item['module'],
         'link_title' => $item['link_title'],
         'options' => serialize($item['options']),
+        'link_description' => $item['link_description'],
         'customized' => $item['customized'],
       ))
       ->condition('mlid', $item['mlid'])
       ->execute();
     // Check the has_children status of the parent.
     _menu_update_parental_status($item);
     menu_cache_clear($menu_name);
     if ($existing_item && $menu_name != $existing_item['menu_name']) {
       menu_cache_clear($existing_item['menu_name']);
Index: modules/menu/menu.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.admin.inc,v
retrieving revision 1.71
diff -u -9 -p -r1.71 menu.admin.inc
--- modules/menu/menu.admin.inc	3 Jan 2010 21:01:04 -0000	1.71
+++ modules/menu/menu.admin.inc	20 Jan 2010 17:40:00 -0000
@@ -235,19 +235,19 @@ function theme_menu_overview_form($varia
   return $output;
 }
 
 /**
  * Menu callback; Build the menu link editing form.
  */
 function menu_edit_item($form, &$form_state, $type, $item, $menu) {
   if ($type == 'add' || empty($item)) {
     // This is an add form, initialize the menu link.
-    $item = array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu['menu_name'], 'weight' => 0, 'link_path' => '', 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
+    $item = array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu['menu_name'], 'weight' => 0, 'link_path' => '', 'options' => array(), 'link_description' => '', 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
   }
   $form['link_title'] = array(
     '#type' => 'textfield',
     '#title' => t('Menu link title'),
     '#default_value' => $item['link_title'],
     '#description' => t('The text to be used for this link in the menu.'),
     '#required' => TRUE,
   );
   foreach (array('link_path', 'mlid', 'module', 'has_children', 'options') as $key) {
@@ -281,22 +281,22 @@ function menu_edit_item($form, &$form_st
     );
   }
   else {
     $form['_path'] = array(
       '#type' => 'item',
       '#title' => t('Path'),
       '#description' => l($item['link_title'], $item['href'], $item['options']),
     );
   }
-  $form['description'] = array(
+  $form['link_description'] = array(
     '#type' => 'textarea',
     '#title' => t('Description'),
-    '#default_value' => isset($item['options']['attributes']['title']) ? $item['options']['attributes']['title'] : '',
+    '#default_value' => $item['link_description'],
     '#rows' => 1,
     '#description' => t('The description displayed when hovering over a menu link.'),
   );
   $form['enabled'] = array(
     '#type' => 'checkbox',
     '#title' => t('Enabled'),
     '#default_value' => !$item['hidden'],
     '#description' => t('Menu links that are not enabled will not be listed in any menu.'),
   );
@@ -372,19 +372,19 @@ function menu_item_delete_submit($form, 
  */
 function menu_edit_item_submit($form, &$form_state) {
   $item = &$form_state['values'];
 
   // The value of "hidden" is the opposite of the value
   // supplied by the "enabled" checkbox.
   $item['hidden'] = (int) !$item['enabled'];
   unset($item['enabled']);
 
-  $item['options']['attributes']['title'] = $item['description'];
+  $item['options']['attributes']['title'] = decode_entities(strip_tags($item['link_description']));
   list($item['menu_name'], $item['plid']) = explode(':', $item['parent']);
   if (!menu_link_save($item)) {
     drupal_set_message(t('There was an error saving the menu link.'), 'error');
   }
   $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name'];
 }
 
 /**
  * Menu callback; Build the form that handles the adding/editing of a custom menu.
Index: modules/menu/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.test,v
retrieving revision 1.31
diff -u -9 -p -r1.31 menu.test
--- modules/menu/menu.test	9 Jan 2010 21:54:00 -0000	1.31
+++ modules/menu/menu.test	20 Jan 2010 17:40:00 -0000
@@ -238,19 +238,19 @@ class MenuTestCase extends DrupalWebTest
   function addMenuLink($plid = 0, $link = '<front>', $menu_name = 'navigation') {
     // View add menu link page.
     $this->drupalGet("admin/structure/menu/manage/$menu_name/add");
     $this->assertResponse(200);
 
     $title = '!link_' . $this->randomName(16);
     $edit = array(
       'link_path' => $link,
       'link_title' => $title,
-      'description' => '',
+      'link_description' => '',
       'enabled' => TRUE, // Use this to disable the menu and test.
       'expanded' => TRUE, // Setting this to true should test whether it works when we do the std_user tests.
       'parent' =>  $menu_name . ':' . $plid,
       'weight' => '0',
     );
 
     // Add menu link.
     $this->drupalPost(NULL, $edit, t('Save'));
     $this->assertResponse(200);
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.253
diff -u -9 -p -r1.253 system.admin.inc
--- modules/system/system.admin.inc	18 Jan 2010 17:08:20 -0000	1.253
+++ modules/system/system.admin.inc	20 Jan 2010 17:40:00 -0000
@@ -27,23 +27,18 @@ function system_main_admin_page($arg = N
       SELECT m.*, ml.*
       FROM {menu_links} ml
       INNER JOIN {menu_router} m ON ml.router_path = m.path
       WHERE ml.link_path != 'admin/help' AND menu_name = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
     foreach ($result as $item) {
       _menu_link_translate($item);
       if (!$item['access']) {
         continue;
       }
-      // The link 'description' either derived from the hook_menu 'description'
-      // or entered by the user via menu module is saved as the title attribute.
-      if (!empty($item['localized_options']['attributes']['title'])) {
-        $item['description'] = $item['localized_options']['attributes']['title'];
-      }
       $block = $item;
       $block['content'] = '';
       $block['show'] = FALSE;
       if ($item['block_callback'] && function_exists($item['block_callback'])) {
         $function = $item['block_callback'];
         $block['content'] .= $function();
       }
       $content = system_admin_menu_block($item);
       if ((isset($item['page_callback']) && !in_array($item['page_callback'], array('system_admin_menu_block_page', 'system_admin_config_page', 'system_settings_overview'))) || count($content)) {
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.442
diff -u -9 -p -r1.442 system.install
--- modules/system/system.install	13 Jan 2010 06:15:39 -0000	1.442
+++ modules/system/system.install	20 Jan 2010 17:40:01 -0000
@@ -1046,31 +1046,36 @@ function system_schema() {
       ),
       'router_path' => array(
         'description' => 'For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
         'default' => '',
       ),
       'link_title' => array(
-      'description' => 'The text displayed for the link, which may be modified by a title callback stored in {menu_router}.',
+        'description' => 'The text displayed for the link, which may be modified by a title callback stored in {menu_router}.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
         'default' => '',
         'translatable' => TRUE,
       ),
       'options' => array(
         'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
         'type' => 'text',
         'not null' => FALSE,
         'translatable' => TRUE,
       ),
+      'link_description' => array(
+        'description' => 'A description of this item.',
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
       'module' => array(
         'description' => 'The name of the module that generated this link.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
         'default' => 'system',
       ),
       'hidden' => array(
         'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)',
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.878
diff -u -9 -p -r1.878 system.module
--- modules/system/system.module	18 Jan 2010 17:08:20 -0000	1.878
+++ modules/system/system.module	20 Jan 2010 17:40:01 -0000
@@ -1931,20 +1931,19 @@ function system_admin_menu_block($item) 
     _menu_link_translate($link);
     if (!$link['access']) {
       continue;
     }
 
     // The link 'description' — either derived from the hook_menu 'description' or
     // entered by the user via menu module — is saved as the title attribute.
     // The title attribute is then unset to reduce redundancy in admin pages
     // for screen readers.
-    if (!empty($link['localized_options']['attributes']['title'])) {
-      $link['description'] = $link['localized_options']['attributes']['title'];
+    if (!empty($link['description'])) {
       unset($link['localized_options']['attributes']['title']);
     }
 
     // Prepare for sorting as in function _menu_tree_check_access().
     // The weight is offset so it is always positive, with a uniform 5-digits.
     $key = (50000 + $link['weight']) . ' ' . drupal_strtolower($link['title']) . ' ' . $link['mlid'];
     $content[$key] = $link;
     if ($link['type'] == MENU_DEFAULT_LOCAL_TASK) {
       $default_task = $key;
