Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.234
diff -u -p -r1.234 locale.module
--- modules/locale/locale.module	16 Dec 2008 23:57:32 -0000	1.234
+++ modules/locale/locale.module	14 Jan 2009 22:24:31 -0000
@@ -277,6 +277,7 @@ function locale_form_alter(&$form, $form
         '#title' => t('Language'),
         '#options' => array('' => t('All languages')) + locale_language_list('name'),
         '#default_value' => $form['language']['#value'],
+        '#access' => $form['language']['#access'],
         '#weight' => -10,
         '#description' => t('A path alias set for a specific language will always be used when displaying this page in that language, and takes precedence over path aliases set for <em>All languages</em>.'),
       );
Index: modules/path/path.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.admin.inc,v
retrieving revision 1.17
diff -u -p -r1.17 path.admin.inc
--- modules/path/path.admin.inc	13 Jan 2009 19:27:21 -0000	1.17
+++ modules/path/path.admin.inc	14 Jan 2009 22:24:31 -0000
@@ -30,7 +30,7 @@ function path_admin_overview($keys = NUL
   }
   $header = array(
     array('data' => t('Alias'), 'field' => 'dst', 'sort' => 'asc'),
-    array('data' => t('System'), 'field' => 'src'),
+    array('data' => t('System path'), 'field' => 'src'),
     array('data' => t('Operations'), 'colspan' => '2')
   );
   if ($multilanguage) {
@@ -42,6 +42,7 @@ function path_admin_overview($keys = NUL
   $rows = array();
   $destination = drupal_get_destination();
   while ($data = db_fetch_object($result)) {
+    $language = !empty($data->language) ? $data->language : 'all';
     $row = array(
       // If the system path maps to a different URL alias, highlight this table
       // row to let the user know of old aliases.
@@ -49,7 +50,7 @@ function path_admin_overview($keys = NUL
       'data' => array(
         l($data->dst, $data->src),
         l($data->src, $data->src, array('alias' => TRUE)),
-        l(t('edit'), "admin/build/path/edit/$data->pid", array('query' => $destination)),
+        l(t('edit'), "admin/build/path/edit/$language/$data->pid", array('query' => $destination)),
         l(t('delete'), "admin/build/path/delete/$data->pid", array('query' => $destination)),
       ),
     );
@@ -71,126 +72,201 @@ function path_admin_overview($keys = NUL
 }
 
 /**
- * Menu callback; handles pages for creating and editing URL aliases.
+ * Menu callback; Return a form for editing or creating an individual URL alias.
+ *
+ * @ingroup forms
+ * @see path_admin_form_validate()
+ * @see path_admin_form_submit()
  */
-function path_admin_edit($pid = 0) {
-  if ($pid) {
-    $alias = path_load($pid);
-    drupal_set_title($alias['dst']);
-    $output = drupal_get_form('path_admin_form', $alias);
+function path_admin_form(&$form_state, $language = '', $path = array('src' => '', 'dst' => '', 'language' => '', 'pid' => NULL)) {
+  $aliases = array();
+  $form = array();
+
+  if (isset($language) && module_exists('locale')) {
+    $language = ($language == 'all' ? '' : $language);
   }
   else {
-    $output = drupal_get_form('path_admin_form');
+    $language = NULL;
   }
 
-  return $output;
-}
+  if (!empty($path['pid']) && $path_data = path_load(NULL, $path['src'], $language)) {
+    $aliases = $path_data;
+  }
 
-/**
- * Return a form for editing or creating an individual URL alias.
- *
- * @ingroup forms
- * @see path_admin_form_validate()
- * @see path_admin_form_submit()
- */
-function path_admin_form(&$form_state, $edit = array('src' => '', 'dst' => '', 'language' => '', 'pid' => NULL)) {
+  $field_prefix = url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=');
 
-  $form['#alias'] = $edit;
+  $form['#alias'] = $path;
 
   $form['src'] = array(
     '#type' => 'textfield',
-    '#title' => t('Existing system path'),
-    '#default_value' => $edit['src'],
+    '#title' => t('System path'),
+    '#default_value' => !empty($path['src']) ? $path['src'] : '',
     '#maxlength' => 128,
     '#size' => 45,
     '#description' => t('Specify the existing path you wish to alias. For example: node/28, forum/1, taxonomy/term/1+2.'),
-    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
+    '#field_prefix' => $field_prefix,
     '#required' => TRUE,
   );
-  $form['dst'] = array(
+
+  if (count($aliases)) {
+    // Disable system path field when editing.
+    foreach ($aliases as $alias) {
+      $form['alias'][$alias['pid']] = array(
+        '#type' => 'textfield',
+        '#title' => '',
+        '#default_value' => $alias['dst'],
+        '#maxlength' => 128,
+        '#size' => 45,
+        '#field_prefix' => $field_prefix,
+      );
+    }
+  }
+  else {
+    $form['src']['#description'] = t('Specify the existing path which you wish to alias. For example: node/28, taxonomy/term/12.');
+  }
+
+  $form['new'] = array(
     '#type' => 'textfield',
-    '#title' => t('Path alias'),
-    '#default_value' => $edit['dst'],
+    '#title' => t('New path alias'),
     '#maxlength' => 128,
     '#size' => 45,
-    '#description' => t('Specify an alternative path by which this data can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'),
-    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
-    '#required' => TRUE,
+    '#required' => empty($path['pid']),
+    '#weight' => 1,
+    '#description' => t('Specify an alternative path by which this data can be accessed. For example: about, faq, etc.'),
+    '#field_prefix' => $field_prefix,
   );
-  // This will be a hidden value unless locale module is enabled
+
+  // This will be hidden unless locale module is enabled.
   $form['language'] = array(
     '#type' => 'value',
-    '#value' => $edit['language']
+    '#value' => !empty($language) ? $language : '',
+    '#access' => empty($path['pid']),
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
   );
-  if ($edit['pid']) {
-    $form['pid'] = array('#type' => 'hidden', '#value' => $edit['pid']);
-    $form['submit'] = array('#type' => 'submit', '#value' => t('Update alias'));
-  }
-  else {
-    $form['submit'] = array('#type' => 'submit', '#value' => t('Create new alias'));
-  }
 
+  $form['alias']['#tree'] = TRUE;
   return $form;
 }
 
+/**
+ * Theme the path creating and editing form.
+ */
+function theme_path_admin_form($form) {
+  $rows = array();
+  $header = array(t('Aliases'));
+
+  $output = theme('table', array(), array(array(drupal_render($form['src']))));
+
+  if (count(element_children($form['alias']))) {
+    foreach (element_children($form['alias']) as $key) {
+      $rows[] = array(
+        drupal_render($form['alias'][$key]),
+      );
+    }
+    $output .= theme('table', $header, $rows);
+  }
+  $output .= theme('table', array(), array(array(drupal_render($form['new']))));
+  $output .= drupal_render($form);
+
+  return $output;
+}
 
 /**
  * Verify that a new URL alias is valid
  */
 function path_admin_form_validate($form, &$form_state) {
-  $src = $form_state['values']['src'];
-  $dst = $form_state['values']['dst'];
-  $pid = isset($form_state['values']['pid']) ? $form_state['values']['pid'] : 0;
-  // Language is only set if locale module is enabled, otherwise save for all languages.
-  $language = isset($form_state['values']['language']) ? $form_state['values']['language'] : '';
+  $form_values = $form_state['values'];
+  $src = !empty($form_values['src']) ? $form_values['src'] : NULL;
 
-  if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE pid != %d AND dst = '%s' AND language = '%s'", $pid, $dst, $language))) {
-    form_set_error('dst', t('The alias %alias is already in use in this language.', array('%alias' => $dst)));
-  }
   $item = menu_get_item($src);
   if (!$item || !$item['access']) {
-    form_set_error('src', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $src)));
+    form_set_error('alias][0', t("The path '@link_path' is invalid.", array('@link_path' => $src)));
   }
+
+  // Language is only set if locale module is enabled, otherwise save for all languages.
+  $language = isset($form_values['language']) ? $form_values['language'] : '';
+
+  // Validate new alias value.
+  $form_values['alias']['new'] = $form_values['new'];
+  if (!empty($form_values['alias'])) {
+    $locale_exists = module_exists('locale');
+    foreach ($form_values['alias'] as $pid => $alias) {
+      $query = "SELECT COUNT(dst) FROM {url_alias} WHERE pid != %d AND dst = '%s'";
+      if ($locale_exists) {
+        $result = db_result(db_query($query . " AND language = '%s'", $pid, $alias, $language));
+      }
+      else {
+        $result = db_result(db_query($query, $pid, $alias));
+      }
+      if ($result) {
+        $error = $locale_exists ? t('The alias %alias is already in use in this language.', array('%alias' => $alias)) : t('The alias %alias is already in use.', array('%alias' => $alias));
+        form_set_error('alias]['. $pid, $error);
+      }
+    }
+  }
+  unset($form_values['alias']['new']);
 }
 
 /**
  * Save a new URL alias to the database.
  */
 function path_admin_form_submit($form, &$form_state) {
-  // Language is only set if locale module is enabled
-  path_set_alias($form_state['values']['src'], $form_state['values']['dst'], isset($form_state['values']['pid']) ? $form_state['values']['pid'] : 0, isset($form_state['values']['language']) ? $form_state['values']['language'] : '');
+  $form_values = $form_state['values'];
+  $src = !empty($form_values['src']) ? $form_values['src'] : NULL;
+  $new = !empty($form_values['new']) ? $form_values['new'] : NULL;
 
-  drupal_set_message(t('The alias has been saved.'));
-  $form_state['redirect'] = 'admin/build/path';
+  // Language is only set if locale module is enabled, otherwise save for all languages.
+  $language = isset($form_values['language']) ? $form_values['language'] : '';
+
+  if (!empty($form_values['alias'])) {
+    foreach ($form_values['alias'] as $pid => $alias) {
+      if (!empty($alias)) {
+        path_set_alias($src, $alias, $pid, $language);
+      }
+      else {
+        path_delete($pid);
+      }
+    }
+    drupal_clear_path_cache();
+  }
+
+  if (!empty($new)) {
+    path_set_alias($src, $new, NULL, $language);
+  }
+  menu_rebuild();
+  drupal_set_message(t('The aliases have been saved.'));
+
+  $pid = db_result(db_query("SELECT MAX(pid) FROM {url_alias} WHERE src = '%s'", $src));
+  $language = !empty($language) ? $language : 'all';
+  $form_state['redirect'] = 'admin/build/path' . (!empty($pid) ? "/edit/$language/$pid" : '');
   return;
 }
 
 /**
  * Menu callback; confirms deleting an URL alias
  */
-function path_admin_delete_confirm($form_state, $pid) {
-  $path = path_load($pid);
+function path_admin_delete_confirm(&$form_state, $path) {
   if (user_access('administer url aliases')) {
-    $form['pid'] = array('#type' => 'value', '#value' => $pid);
+    $form['pid'] = array('#type' => 'value', '#markup' => $path['pid']);
     $output = confirm_form($form,
       t('Are you sure you want to delete path alias %title?', array('%title' => $path['dst'])),
       isset($_GET['destination']) ? $_GET['destination'] : 'admin/build/path');
   }
   return $output;
 }
-
 /**
  * Execute URL alias deletion
  */
 function path_admin_delete_confirm_submit($form, &$form_state) {
   if ($form_state['values']['confirm']) {
-    path_admin_delete($form_state['values']['pid']);
-    $form_state['redirect'] = 'admin/build/path';
-    return;
+    path_delete($form['pid']['#markup']);
+    drupal_set_message(t('The alias has been deleted.'));
   }
 }
 
-
 /**
  * Return a form to filter URL aliases.
  *
@@ -239,7 +315,6 @@ function path_admin_filter_form_submit_r
   $form_state['redirect'] = 'admin/build/path/list';
 }
 
-
 /**
  * Helper function for grabbing filter keys.
  */
Index: modules/path/path.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.module,v
retrieving revision 1.152
diff -u -p -r1.152 path.module
--- modules/path/path.module	9 Dec 2008 11:30:24 -0000	1.152
+++ modules/path/path.module	14 Jan 2009 22:24:32 -0000
@@ -30,6 +30,17 @@ function path_help($path, $arg) {
 }
 
 /**
+ * Implementation of hook_theme().
+ */
+function path_theme() {
+  return array(
+    'path_admin_form' => array(
+      'arguments' => array('form' => NULL),
+    ),
+  );
+}
+
+/**
  * Implementation of hook_menu().
  */
 function path_menu() {
@@ -39,16 +50,51 @@ function path_menu() {
     'page callback' => 'path_admin_overview',
     'access arguments' => array('administer url aliases'),
   );
+  $items['admin/build/path/%menu_tail'] = array(
+    'page callback' => 'path_admin_overview',
+    'access arguments' => array('administer url aliases'),
+  );
   $items['admin/build/path/edit'] = array(
     'title' => 'Edit alias',
-    'page callback' => 'path_admin_edit',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('path_admin_form'),
     'access arguments' => array('administer url aliases'),
     'type' => MENU_CALLBACK,
   );
-  $items['admin/build/path/delete'] = array(
+  if (module_exists('locale')) {
+    $items['admin/build/path/edit/all/%path'] = array(
+      'title' => 'All languages',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('path_admin_form', 4, 5),
+      'access arguments' => array('administer url aliases'),
+      'type' => MENU_LOCAL_TASK,
+      'weight' => -1,
+    );
+    foreach (locale_language_list('name') as $key => $language) {
+      $items['admin/build/path/edit/' . $key . '/%path'] = array(
+        'title' => '!language',
+        'title arguments' => array('!language' => $language),
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('path_admin_form', 4, 5),
+        'access arguments' => array('administer url aliases'),
+        'type' => MENU_LOCAL_TASK,
+      );
+    }
+  }
+  else {
+    $items['admin/build/path/edit/%/%path'] = array(
+      'title' => 'All languages',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('path_admin_form', 4, 5),
+      'access arguments' => array('administer url aliases'),
+      'type' => MENU_DEFAULT_LOCAL_TASK,
+      'weight' => -1,
+    );
+  }
+  $items['admin/build/path/delete/%path'] = array(
     'title' => 'Delete alias',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('path_admin_delete_confirm'),
+    'page arguments' => array('path_admin_delete_confirm', 4),
     'access arguments' => array('administer url aliases'),
     'type' => MENU_CALLBACK,
   );
@@ -59,7 +105,8 @@ function path_menu() {
   );
   $items['admin/build/path/add'] = array(
     'title' => 'Add alias',
-    'page callback' => 'path_admin_edit',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('path_admin_form'),
     'access arguments' => array('administer url aliases'),
     'type' => MENU_LOCAL_TASK,
   );
@@ -68,11 +115,27 @@ function path_menu() {
 }
 
 /**
- * Post-confirmation; delete an URL alias.
+ * Post-confirmation; delete a URL alias.
  */
-function path_admin_delete($pid = 0) {
-  db_query('DELETE FROM {url_alias} WHERE pid = %d', $pid);
-  drupal_set_message(t('The alias has been deleted.'));
+function path_delete($pid = NULL, $src = '', $language = NULL) {
+  // Delete based on path ID.
+  if (isset($pid) && is_numeric($pid)) {
+    db_query('DELETE FROM {url_alias} WHERE pid = %d', $pid);
+  }
+  // Delete based on system path.
+  else if (!empty($src)) {
+    // Let the user delete aliases based on an empty (not every), or
+    // a particular language.
+    if ($language !== NULL) {
+      // Delete for a specified language. e.g. 'en', 'de', or even ''.
+      db_query("DELETE FROM {url_alias} WHERE src = '%s' AND language = '%s'", $src, $language);
+    }
+    else {
+      // Delete for every language. e.g. 'en', 'de', AND even ''.
+      db_query("DELETE FROM {url_alias} WHERE src = '%s'", $src);
+    }
+  }
+  drupal_clear_path_cache();
 }
 
 /**
@@ -166,7 +229,15 @@ function path_nodeapi_insert($node) {
 function path_nodeapi_update($node) {
   if (user_access('create url aliases') || user_access('administer url aliases')) {
     $language = isset($node->language) ? $node->language : '';
-    path_set_alias('node/' . $node->nid, isset($node->path) ? $node->path : NULL, isset($node->pid) ? $node->pid : NULL, $language);
+    $pid = isset($node->pid) ? $node->pid : NULL;
+    if (!empty($node->path)) {
+      // Update alias data.
+      path_set_alias("node/$node->nid", $node->path, $pid, $language);
+    }
+    else if (!empty($pid)) {
+      // Delete the alias if path was emptied.
+      path_delete($pid);
+    }
   }
 }
 
@@ -178,7 +249,7 @@ function path_nodeapi_delete($node) {
     $language = isset($node->language) ? $node->language : '';
     $path = 'node/' . $node->nid;
     if (drupal_get_path_alias($path) != $path) {
-      path_set_alias($path);
+      path_delete(NULL, $path);
     }
   }
 }
@@ -233,6 +304,25 @@ function path_perm() {
 /**
  * Fetch a specific URL alias from the database.
  */
-function path_load($pid) {
-  return db_fetch_array(db_query('SELECT * FROM {url_alias} WHERE pid = %d', $pid));
+function path_load($pid = NULL, $src = '', $language = NULL) {
+  $output = '';
+  if (isset($pid) && is_numeric($pid)) {
+    $output = db_fetch_array(db_query('SELECT * FROM {url_alias} WHERE pid = %d', $pid));
+  }
+  else if (!empty($src)) {
+    // Let the user load aliases based on an empty (not every), or
+    // a particular language.
+    if ($language !== NULL) {
+      // Load for a specified language. e.g. 'en', 'de', or even ''.
+      $result = db_query("SELECT * FROM {url_alias} WHERE src = '%s' AND language = '%s' ORDER BY pid", $src, $language);
+    }
+    else {
+      // Load for every language. e.g. 'en', 'de', AND even ''.
+      $result = db_query("SELECT * FROM {url_alias} WHERE src = '%s' ORDER BY pid", $src);
+    }
+    while ($row = db_fetch_array($result)) {
+      $output[] = $row;
+    }
+  }
+  return $output;
 }
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.6
diff -u -p -r1.6 path.test
--- modules/path/path.test	9 Jan 2009 07:44:00 -0000	1.6
+++ modules/path/path.test	14 Jan 2009 22:24:32 -0000
@@ -30,22 +30,23 @@ class PathTestCase extends DrupalWebTest
     // Create alias.
     $edit = array();
     $edit['src'] = 'node/' . $node1->nid;
-    $edit['dst'] = $this->randomName(8);
-    $this->drupalPost('admin/build/path/add', $edit, t('Create new alias'));
+    $edit['new'] = $this->randomName(8);
+    $this->drupalPost('admin/build/path/add', $edit, t('Save'));
 
     // Confirm that the alias works.
-    $this->drupalGet($edit['dst']);
+    $this->drupalGet($edit['new']);
     $this->assertText($node1->title, 'Alias works.');
 
     // Change alias.
-    $pid = $this->getPID($edit['dst']);
+    $pid = $this->getPID($edit['new']);
 
-    $previous = $edit['dst'];
-    $edit['dst'] = $this->randomName(8);
-    $this->drupalPost('admin/build/path/edit/' . $pid, $edit, t('Update alias'));
+    $previous = $edit['new'];
+    $edit['alias[' . $pid . ']'] = $edit['new'] = $this->randomName(8);
+    $this->drupalPost('admin/build/path/edit/all/' . $pid, $edit, t('Save'));
+    unset($edit['alias[' . $pid . ']']);
 
     // Confirm that the alias works.
-    $this->drupalGet($edit['dst']);
+    $this->drupalGet($edit['new']);
     $this->assertText($node1->title, 'Changed alias works.');
 
     // Confirm that previous alias no longer works.
@@ -58,17 +59,17 @@ class PathTestCase extends DrupalWebTest
 
     // Set alias to second test node.
     $edit['src'] = 'node/' . $node2->nid;
-    // leave $edit['dst'] the same
-    $this->drupalPost('admin/build/path/add', $edit, t('Create new alias'));
+    // leave $edit['new'] the same
+    $this->drupalPost('admin/build/path/add', $edit, t('Save'));
 
     // Confirm no duplicate was created.
-    $this->assertRaw(t('The alias %alias is already in use in this language.', array('%alias' => $edit['dst'])), 'Attempt to move alias was rejected.');
+    $this->assertRaw(t('The alias %alias is already in use', array('%alias' => $edit['new'])), 'Attempt to move alias was rejected.');
 
     // Delete alias.
     $this->drupalPost('admin/build/path/delete/' . $pid, array(), t('Confirm'));
 
     // Confirm that the alias no longer works.
-    $this->drupalGet($edit['dst']);
+    $this->drupalGet($edit['new']);
     $this->assertNoText($node1->title, 'Alias was successfully deleted.');
   }
 
