Index: includes/path.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/path.inc,v
retrieving revision 1.71
diff -u -p -r1.71 path.inc
--- includes/path.inc	9 Aug 2010 00:13:06 -0000	1.71
+++ includes/path.inc	22 Nov 2010 06:35:22 -0000
@@ -94,13 +94,18 @@ function drupal_lookup_path($action, $pa
         if ($cached = cache_get($cid, 'cache_path')) {
           $cache['system_paths'] = $cached->data;
           // Now fetch the aliases corresponding to these system paths.
-          // We order by ASC and overwrite array keys to ensure the correct
-          // alias is used when there are multiple aliases per path.
-          $cache['map'][$path_language] = db_query("SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC", array(
+          $aliases = db_query("SELECT pid, source, alias, language FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none)", array(
             ':system' => $cache['system_paths'],
             ':language' => $path_language,
             ':language_none' => LANGUAGE_NONE,
-          ))->fetchAllKeyed();
+          ))->fetchAll(PDO::FETCH_ASSOC);
+          usort($aliases, 'drupal_sort_url_aliases');
+          // Reverse the array and loop over it, so that the cache is populated
+          // by the preferred alias for each source.
+          $aliases = array_reverse($aliases);
+          foreach ($aliases as $alias) {
+            $cache['map'][$path_language][$alias['source']] = $alias['alias'];
+          }
           // Keep a record of paths with no alias to avoid querying twice.
           $cache['no_aliases'][$path_language] = array_flip(array_diff_key($cache['system_paths'], array_keys($cache['map'][$path_language])));
         }
@@ -118,13 +123,15 @@ function drupal_lookup_path($action, $pa
       // For system paths which were not cached, query aliases individually.
       elseif (!isset($cache['no_aliases'][$path_language][$path])) {
         // Get the most fitting result falling back with alias without language
-        $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", array(
+        $aliases = db_query("SELECT pid, source, alias, language FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none)", array(
           ':source' => $path,
           ':language' => $path_language,
           ':language_none' => LANGUAGE_NONE,
-        ))->fetchField();
-        $cache['map'][$path_language][$path] = $alias;
-        return $alias;
+        ))->fetchAll(PDO::FETCH_ASSOC);
+        usort($aliases, 'drupal_sort_url_aliases');
+        $alias = reset($aliases);
+        $cache['map'][$path_language][$path] = $alias['alias'];
+        return $alias['alias'];
       }
     }
     // Check $no_source for this $path in case we've already determined that there
@@ -134,11 +141,15 @@ function drupal_lookup_path($action, $pa
       $source = FALSE;
       if (!isset($cache['map'][$path_language]) || !($source = array_search($path, $cache['map'][$path_language]))) {
         // Get the most fitting result falling back with alias without language
-        if ($source = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", array(
-                     ':alias' => $path,
-                     ':language' => $path_language,
-                     ':language_none' => LANGUAGE_NONE))
-            ->fetchField()) {
+        $aliases = db_query("SELECT pid, source, alias, language FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none)", array(
+          ':alias' => $path,
+          ':language' => $path_language,
+          ':language_none' => LANGUAGE_NONE,
+        ))->fetchAll(PDO::FETCH_ASSOC);
+        if (!empty($aliases)) {
+          usort($aliases, 'drupal_sort_url_aliases');
+          $alias = reset($aliases);
+          $source = $alias['source'];
           $cache['map'][$path_language][$source] = $path;
         }
         else {
@@ -156,6 +167,21 @@ function drupal_lookup_path($action, $pa
 }
 
 /**
+ * Sort callback to order aliases by level of preference.
+ *
+ * Use this callback with usort(). Aliases with LANGUAGE_NONE will sink to the
+ * bottom of the array, and then the array is ordered by pid, descending.
+ */
+function drupal_sort_url_aliases($a, $b) {
+  if ($a['language'] != $b['language']) {
+    return $a['language'] == LANGUAGE_NONE ? 1 : -1;
+  }
+  else {
+    return $a['pid'] < $b['pid'] ? 1 : -1;
+  }
+}
+
+/**
  * Cache system paths for a page.
  *
  * Cache an array of the system paths available on each page. We assume
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.87
diff -u -p -r1.87 locale.test
--- modules/locale/locale.test	21 Nov 2010 09:53:05 -0000	1.87
+++ modules/locale/locale.test	22 Nov 2010 06:35:22 -0000
@@ -1577,7 +1577,58 @@ class LocalePathFunctionalTest extends D
     $this->drupalGet($prefix . '/' . $custom_language_path);
     $this->assertText($node->title, t('Custom language alias works.'));
 
-    $this->drupalLogout();
+    // Create a custom path.
+    $custom_path = $this->randomName(8);
+
+    // Check priority of language for alias by source path.
+    $edit = array(
+      'source'   => 'node/' . $node->nid,
+      'alias'    => $custom_path,
+      'language' => LANGUAGE_NONE,
+    );
+    path_save($edit);
+    $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, 'en');
+    $this->assertEqual($english_path, $lookup_path, t('English language alias has priority.'));
+    // Same check for language 'xx'.
+    $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, $prefix);
+    $this->assertEqual($custom_language_path, $lookup_path, t('Custom language alias has priority.'));
+    path_delete($edit);
+
+    // Create language nodes to check priority of aliases.
+    $first_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
+    $second_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
+
+    // Assign a custom path alias to the first node with the English language.
+    $edit = array(
+      'source'   => 'node/' . $first_node->nid,
+      'alias'    => $custom_path,
+      'language' => 'en',
+    );
+    path_save($edit);
+
+    // Assign a custom path alias to second node with LANGUAGE_NONE.
+    $edit = array(
+      'source'   => 'node/' . $second_node->nid,
+      'alias'    => $custom_path,
+      'language' => LANGUAGE_NONE,
+    );
+    path_save($edit);
+
+    // Test that both node titles link to our path alias.
+    $this->drupalGet('<front>');
+    $custom_path_url = base_path() . (variable_get('clean_url', 0) ? $custom_path : '?q=' . $custom_path);
+    $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title));
+    $this->assertTrue(!empty($elements), t('First node links to the path alias.'));
+    $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title));
+    $this->assertTrue(!empty($elements), t('Second node links to the path alias.'));
+
+    // Confirm that the custom path leads to the first node.
+    $this->drupalGet($custom_path);
+    $this->assertText($first_node->title, t('Custom alias returns first node.'));
+
+    // Confirm that the custom path with prefix leads to the second node.
+    $this->drupalGet($prefix . '/' . $custom_path);
+    $this->assertText($second_node->title, t('Custom alias with prefix returns second node.'));
   }
 }
 
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.525
diff -u -p -r1.525 system.install
--- modules/system/system.install	21 Nov 2010 20:35:10 -0000	1.525
+++ modules/system/system.install	22 Nov 2010 06:35:23 -0000
@@ -1617,7 +1617,7 @@ function system_schema() {
         'default' => '',
       ),
       'language' => array(
-        'description' => 'The language this alias is for; if blank, the alias will be used for unknown languages. Each Drupal path can have an alias for each supported language.',
+        'description' => "The language this alias is for; if 'und', the alias will be used for unknown languages. Each Drupal path can have an alias for each supported language.",
         'type' => 'varchar',
         'length' => 12,
         'not null' => TRUE,
@@ -2927,6 +2927,20 @@ function system_update_7066() {
 }
 
 /**
+ * Update {url_alias}.language description.
+ */
+function system_update_7067() {
+  $spec = array(
+    'description' => "The language this alias is for; if 'und', the alias will be used for unknown languages. Each Drupal path can have an alias for each supported language.",
+    'type' => 'varchar',
+    'length' => 12,
+    'not null' => TRUE,
+    'default' => '',
+  );
+  db_change_field('url_alias', 'language', 'language', $spec);
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
