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	11 Jan 2009 18:17:45 -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.16
diff -u -p -r1.16 path.admin.inc
--- modules/path/path.admin.inc	5 Dec 2008 12:50:27 -0000	1.16
+++ modules/path/path.admin.inc	11 Jan 2009 18:17:46 -0000
@@ -11,7 +11,10 @@
  * When filter key passed, perform a standard search on the given key,
  * and return the list of matching URL aliases.
  */
-function path_admin_overview($keys = NULL) {
+function path_admin_overview() {
+  // Respect for keywords including slashes.
+  $args = func_get_args();
+  $keys = implode('/', $args);
   // Add the filter form above the overview table.
   $output = drupal_get_form('path_admin_filter_form', $keys);
   // Enable language column if locale is enabled or if we have any alias with language
@@ -30,7 +33,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 +45,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 +53,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,85 +75,141 @@ 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);
+    // Get rid of breadcrumb generated for admin/path/edit.
+    $breadcrumb = drupal_get_breadcrumb();
+    array_pop($breadcrumb);
+    drupal_set_breadcrumb($breadcrumb);
   }
   else {
-    $output = drupal_get_form('path_admin_form');
+    $language = NULL;
   }
 
-  return $output;
-}
+  if (!empty($path['pid'])) {
+    $aliases = path_load(NULL, $path['src'], $language);
+  }
 
-/**
- * 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(
+  $form['alias']['#tree'] = TRUE;
+  $form['alias']['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 (!empty($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,
+      );
+      $form['operations'][$alias['pid']] = array(
+        '#markup' => l(t('delete'), 'admin/build/path/delete/'. $alias['pid'], array('query' => array('destination' => $_GET['q']))),
+      );
+      $options[$alias['pid']] = '';
+    }
+  }
+  else {
+    $form['alias']['src']['#description'] = t('Specify the existing path which you wish to alias. For example: node/28, taxonomy/term/12.');
+  }
+
+  $form['alias']['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'));
-  }
 
   return $form;
 }
 
+/**
+ * Theme the path creating and editing form.
+ */
+function theme_path_admin_form($form) {
+  $rows = array();
+  $header = array(t('Aliases'));
+  if (!empty($form['operations'])) {
+    $header[] = t('Operations');
+  }
+  foreach (element_children($form['alias']) as $key) {
+    $rows[] = array(
+      drupal_render($form['alias'][$key]),
+      drupal_render($form['operations'][$key]),
+    );
+  }
+  $output = theme('table', $header, $rows);
+  $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['alias']['src']) ? $form_values['alias']['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) {
-    form_set_error('src', t("The path '@link_path' is invalid.", 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'] : '';
+
+  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);
+      }
+    }
   }
 }
 
@@ -157,21 +217,47 @@ function path_admin_form_validate($form,
  * 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['alias']['src']) ? $form_values['alias']['src'] : NULL;
+  $new = !empty($form_values['alias']['new']) ? $form_values['alias']['new'] : NULL;
+
+  // Language is only set if locale module is enabled, otherwise save for all languages.
+  $language = isset($form_values['language']) ? $form_values['language'] : '';
+
+  unset($form_values['alias']['src'], $form_values['alias']['new']);
+  // Unset source and new alias, preparing a list of existing aliases.
+
+  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);
+  }
 
-  drupal_set_message(t('The alias has been saved.'));
-  $form_state['redirect'] = 'admin/build/path';
+  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');
@@ -184,13 +270,11 @@ function path_admin_delete_confirm($form
  */
 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 +323,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	11 Jan 2009 18:17:46 -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() {
@@ -41,14 +52,44 @@ function path_menu() {
   );
   $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(
-    'title' => 'Delete alias',
+  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 +100,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 +110,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 +224,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 +244,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 +299,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	11 Jan 2009 18:17:46 -0000
@@ -29,23 +29,24 @@ 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['alias[src]'] = 'node/' . $node1->nid;
+    $edit['alias[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['alias[new]']);
     $this->assertText($node1->title, 'Alias works.');
 
     // Change alias.
-    $pid = $this->getPID($edit['dst']);
+    $pid = $this->getPID($edit['alias[new]']);
 
-    $previous = $edit['dst'];
-    $edit['dst'] = $this->randomName(8);
-    $this->drupalPost('admin/build/path/edit/' . $pid, $edit, t('Update alias'));
+    $previous = $edit['alias[new]'];
+    $edit['alias[' . $pid . ']'] = $edit['alias[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['alias[new]']);
     $this->assertText($node1->title, 'Changed alias works.');
 
     // Confirm that previous alias no longer works.
@@ -57,18 +58,18 @@ class PathTestCase extends DrupalWebTest
     $node2 = $this->drupalCreateNode();
 
     // 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'));
+    $edit['alias[src]'] = 'node/' . $node2->nid;
+    // leave $edit['alias[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['alias[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['alias[new]']);
     $this->assertNoText($node1->title, 'Alias was successfully deleted.');
   }
 
