--- path_redirect/path_redirect.admin.inc.ORIGINAL	2010-12-05 22:14:15.000000000 +0100
+++ path_redirect/path_redirect.admin.inc		2010-12-07 15:39:06.000000000 +0100
@@ -1,5 +1,5 @@
 <?php
-// $Id: path_redirect.admin.inc,v 1.1.2.87 2010/12/05 21:14:15 davereid Exp $
+// $Id: path_redirect.admin.inc,v 1.1.2.87.ml 2010/12/05 21:14:15 davereid Exp $
 
 /**
  * @file
@@ -41,7 +41,7 @@ function path_redirect_list_redirects($q
   // Build the SQL query.
   $sql = 'SELECT rid FROM {path_redirect}';
   if ($query['conditions']) {
-    $sql .= ' WHERE (' . implode(') AND (', $query['conditions']). ')';
+    $sql .= ' WHERE (' . implode(') AND (', $query['conditions']) . ')';
   }
   $sql .= tablesort_sql($header);
   if ($query['limit']) {
@@ -447,7 +447,8 @@ function path_redirect_validate_redirect
 function path_redirect_edit_form_validate($form, &$form_state) {
   $redirect = &$form_state['values'];
 
-  if ($existing = path_redirect_load_by_source($redirect['source'], $redirect['language'])) {
+  $redirects = path_redirect_load_by_source($redirect['source'], $redirect['language']);
+  if ($existing = $redirects[0]) {
     if ($redirect['rid'] != $existing['rid'] && $redirect['language'] == $existing['language']) {
       // The "from" path should not conflict with another redirect
       form_set_error('source', t('The source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => $redirect['source'], '@edit-page' => url('admin/build/path-redirect/edit/'. $existing['rid']))));
@@ -511,6 +512,13 @@ function path_redirect_settings_form() {
     '#default_value' => variable_get('path_redirect_auto_redirect', 1),
     '#access' => module_exists('path'),
   );
+  $form['path_redirect_use_wildcards'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable use of wildcards (*) in redirects ("From:" and "To:" fields).'),
+    '#description' => t('<b>Check redirects carefully if you disable this option</b> once it has been used already, as <b>all redirects with wildcards will stop working</b>. This option is <b>disabled</b> for <b>PostgreSQL</b>.'),
+    '#default_value' => variable_get('path_redirect_use_wildcards', 0),
+    '#disabled' => $GLOBALS['db_type'] == 'pgsql',
+  );
   $form['path_redirect_purge_inactive'] = array(
     '#type' => 'select',
     '#title' => t('Discard redirects that have not been accessed for'),
@@ -553,7 +561,8 @@ function path_redirect_js_autocomplete_4
   $paths = db_query("SELECT message, COUNT(message) AS count FROM {watchdog} WHERE type = 'page not found' AND LOWER(message) LIKE '%%%s%%' GROUP BY message ORDER BY count DESC", drupal_strtolower($string));
   while ($path = db_result($paths)) {
     // If the 404 is now a valid path or already has a redirect, discard it.
-    if (!menu_get_item($path) && !path_redirect_load_by_source($path)) {
+    $redirects = path_redirect_load_by_source($path);
+    if (!menu_get_item($path) && !$redirects[0]) {
       $matches[$path] = check_plain($path);
     }
   }
--- path_redirect/path_redirect.module.ORIGINAL		2010-12-01 01:50:53.000000000 +0100
+++ path_redirect/path_redirect.module			2010-12-07 18:14:30.000000000 +0100
@@ -1,5 +1,5 @@
 <?php
-// $Id: path_redirect.module,v 1.3.2.7.2.108 2010/12/01 00:50:53 davereid Exp $
+// $Id: path_redirect.module,v 1.3.2.7.2.108.ml 2010/12/01 00:50:53 davereid Exp $
 
 /**
  * Implements hook_help().
@@ -108,13 +108,118 @@ function path_redirect_goto($redirect = 
     $path = path_redirect_get_path();
     $language = $GLOBALS['language']->language;
     $query = path_redirect_get_query();
-    $redirect = path_redirect_load_by_source($path, $language, $query);
+    $redirects = path_redirect_load_by_source($path, $language, TRUE, $query);
+
+    if (($GLOBALS['db_type'] != 'pgsql') AND variable_get('path_redirect_use_wildcards', NULL) == 1) {
+    // We check all paths for matches with wildcards:
+
+      // Trailing slash is removed, what can cause improper wildcard matches:
+      // Example: Redirect From = '<something>/*'
+      //     URL: '<something>/' should match, but as '<something>' it wouldn't
+      $request_uri = request_uri();
+      $query_from = parse_url($request_uri, PHP_URL_QUERY); 
+      if (substr($request_uri, -1) == '/') {
+        $xpath = $path . '/';
+        if ($xpath == substr($request_uri, 1)) {
+          $path = $xpath;
+        }
+      }
+
+      // We don't have a match at the beginning:
+      $error = 10;
+
+      foreach ($redirects as $redirect) {
+
+        if ($error > 0) {
+          // Let's save a current redirect first:
+          $red_rid = $redirect['rid'];
+          $red_source = $redirect['source'];
+          $red_redirect = $redirect['redirect'];
+          $red_query = $redirect['query'];
+          $red_fragment = $redirect['fragment'];
+          $red_type = $redirect['type'];
+          $red_last_used = $redirect['last_used'];
+          $red_language = $redirect['language'];
+          $red_source_query = $redirect['source_query'];
+
+          // If there is a query in path, we have to include it in check:
+          if ($query_from) {
+            $red_from = $path . '?' . $query_from;
+          }
+          else {
+            $red_from = $path;
+          }
+
+          // As well as if there is one in 'Redirect From':
+          if ($red_source_query) {
+            $red_source = path_redirect_build_url($red_source, $red_source_query, $red_fragment);
+            unset($red_source_query);
+          }
+
+          $red_to = '';
+
+          if (substr_count($red_source, '*') > 0) {
+            // We have wildcards in 'Redirect From'; check for a match...
+            $error = path_redirect_match_wildcards($red_from, $red_source, $red_redirect, $red_to);
+          }
+          elseif ($red_from == $red_source) {
+            // No wildcards in 'Redirect From' - Path shall be equal to 'Redirect From'...
+            $red_to = $red_redirect;
+            $error = 0;
+          }
+
+          if ($error == 0) {
+            // So far, so good...
+            if (strpos($red_to, '?')) {
+              // We have a query within a matched 'Redirect', extract it:
+              if ($red_query) {
+                // We can not have 2 queries (one in a matched 'Redirect'),
+                // another in 'Redirect To'; set an error:
+                $error = 20;
+              }
+              else {
+                // No 'Redirect To' query - we use a matched 'Redirect' query:
+                $query_to = parse_url($red_to, PHP_URL_QUERY);
+                $red_to = substr($red_to, 0, strpos($red_to, '?'));
+                $red_query = path_redirect_get_query_array($query_to);
+              }        
+            }
+            // If we don't have a matched 'Redirect', we simply use stored
+            // 'Redirect To' query, if it exist (or not, if empty...)
+          }
+        }
+      }
+
+      $redirect['rid'] = $red_rid;
+      $redirect['source'] = $red_from;
+      $redirect['redirect'] = $red_to;
+      $redirect['query'] = $red_query;
+      $redirect['fragment'] = $red_fragment;
+      $redirect['type'] = $red_type;
+      $redirect['last_used'] = $red_last_used;
+      $redirect['language'] = $red_language;
+      $redirect['source_query'] = $red_source_query;
+
+    }
+    else {
+    // As in original version (just adapted to returned array and "error control"):
+      $redirect = $redirects[0];
+      $error = 0;
+    }
+
   }
   elseif (is_numeric($redirect)) {
     $redirect = path_redirect_load($redirect);
+    $error = 0;
+
+    if (substr_count($redirect['redirect'], '*') > 0) {
+    // Don't know how we shall find a way here at all; so, for the moment
+    // we simply discard a redirect if there are wildcards in it...
+      $error = 1;
+    }
   }
 
-  if ($redirect) {
+  if ($redirect AND $error == 0) {
     // Create the absolute redirection URL.
     $redirect['redirect_url'] = url($redirect['redirect'], array('query' => $redirect['query'], 'fragment' => $redirect['fragment'], 'absolute' => TRUE));
 
@@ -377,8 +482,15 @@ function path_redirect_load($rid) {
  * @param $query
  *   An optional query string to match.
  */
-function path_redirect_load_by_source($source, $language = '', $query = array()) {
-  $where = $query ? "(source = '%s' OR source LIKE '%s%%')" : "source = '%s'";
+function path_redirect_load_by_source($source, $language = '', $with_wildcards = FALSE, $query = array()) {
+
+  if (($GLOBALS['db_type'] != 'pgsql') AND (variable_get('path_redirect_use_wildcards', NULL) == 1) AND $with_wildcards) {
+    $where = $query ? "(source = '%s' OR source LIKE '%s' OR source LIKE '%%*%')" : "(source = '%s' OR source LIKE '%%*%')";
+  }
+  else {
+    $where = $query ? "(source = '%s' OR source LIKE '%s%%')" : "source = '%s'";
+  }
+
   $args = $query ? array($source, $source . '?') : array($source);
   $args[] = $language;
   $rid_query = db_query("SELECT rid FROM {path_redirect} WHERE $where AND language IN ('%s', '') ORDER BY language DESC, source DESC, rid DESC", $args);
@@ -388,34 +500,44 @@ function path_redirect_load_by_source($s
   }
 
   if ($rids && $redirects = path_redirect_load_multiple($rids)) {
-    // Narrow down the list of candidates.
-    foreach ($redirects as $rid => $redirect) {
-      if (!empty($redirect['source_query'])) {
-        if (empty($query) || !path_redirect_compare_array($redirect['source_query'], $query)) {
-          unset($redirects[$rid]);
-          continue;
+    if (!$with_wildcards) {
+      // Narrow down the list of candidates.
+      foreach ($redirects as $rid => $redirect) {
+        if (!empty($redirect['source_query'])) {
+          if (empty($query) || !path_redirect_compare_array($redirect['source_query'], $query)) {
+            unset($redirects[$rid]);
+            continue;
+          }
         }
-      }
 
-      // Add a case sensitive matches condition to be used in sorting.
-      if ($source !== $redirect['source']) {
-        $redirects[$rid]['weight'] = 1;
+        // Add a case sensitive matches condition to be used in sorting.
+        if ($source !== $redirect['source']) {
+          $redirects[$rid]['weight'] = 1;
+        }
       }
-    }
 
-    if (!empty($redirects)) {
-      // Sort the redirects in the proper order.
-      uasort($redirects, '_path_redirect_uasort');
+      if (!empty($redirects)) {
+        // Sort the redirects in the proper order.
+        uasort($redirects, '_path_redirect_uasort');
+
+        // Allow other modules to alter the redirect candidates before selecting the top one.
+        $context = array('language' => $language, 'query' => $query);
+        drupal_alter('path_redirect_load_by_source', $redirects, $source, $context);
 
-      // Allow other modules to alter the redirect candidates before selecting the top one.
-      $context = array('language' => $language, 'query' => $query);
-      drupal_alter('path_redirect_load_by_source', $redirects, $source, $context);
+        return !empty($redirects) ? array(reset($redirects)) : array(FALSE);
+      }
+    }
+    else {
+      $returns = array();
+      foreach ($redirects as $rid => $redirect) {
+        array_push($returns, $redirect);
+      }
 
-      return !empty($redirects) ? reset($redirects) : FALSE;
+      return !empty($returns) ? $returns : array(FALSE);
     }
   }
 
-  return FALSE;
+  return array(FALSE);
 }
 
 function path_redirect_load_multiple($rids = NULL, $conditions = array()) {
@@ -654,3 +776,178 @@ function path_redirect_variables() {
     'path_redirect_nodeapi_enabled' => NULL,
   );
 }
+
+function path_redirect_match_wildcards($in, $in_fmt, $out_fmt, &$out) {
+// -------------------------------------------------------------------------------------
+// Function parses input string ($in) according to input format string ($in_fmt), and
+// replaces corresponding wildcard characters (*) in output format string ($out_fmt)
+// with the first possible matches from input string ($in); if there are more wildcard
+// characters in input format string ($in_fmt) as in output format string ($out_fmt),
+// only replacements for the first matching ones are used to calculate the output string
+// -------------------------------------------------------------------------------------
+// Function returns calculated string setting the $out argument
+// -------------------------------------------------------------------------------------
+// Function returns the error code:
+// 0 => OK,
+// 1 => wildcard error,
+// > 1 => input string ($in) doesn't match input format string ($in_fmt)
+// -------------------------------------------------------------------------------------
+
+  $out = '';
+  $error = 0;
+
+  // Wildcards should not be repeated in $in_fmt (ambiguity)
+  while (substr_count($in_fmt, '**') > 0) {
+    $in_fmt = str_replace('**', '*', $in_fmt);
+  }
+
+  $wcif = substr_count($in_fmt, '*');
+  $wcof = substr_count($out_fmt, '*');
+
+  if ($wcif < $wcof) {
+    // ERROR #1: The number of wildcards in $in_fmt shall not
+    // be smaller than the number of wildcards in $out_fmt (as
+    // some of them could not be resolved in such a case):
+    $out = '';
+    $error = 1;
+  }
+
+  if ($error == 0) {
+    if ($wcif > 0) {
+    // There is at least one wildcard in $in_fmt:
+      $wpif = strpos($in_fmt, '*');
+
+      if ($wpif > 0) {
+        // If there is anything left to the first wildcard character in $in_fmt, the
+        // same substring ($match) shall be at the very beginning of $in as well;
+        // If so, we strip the matching part ($match) from the left of both strings,
+        // and proceed with the rest; otherwise, we set "INPUT DOESN'T MATCH" error
+        $match = substr($in_fmt, 0, $wpif);
+
+        if (substr($in, 0, $wpif) == $match) {
+          $in = substr($in, $wpif);
+          $in_fmt = substr($in_fmt, $wpif);
+        }
+        else {
+          // ERROR #2: INPUT doesn't match INPUT FORMAT!!!
+          $out = '';
+          $error = 2;
+        }
+      }
+
+      if ($error == 0) {
+        // We have a wildcard character at the beginning of $in_fmt here for sure!
+        $in_fmt = substr($in_fmt, 1);
+        $wcif--;
+
+        if ($wcif > 0) {
+          // There is another wildcard somewhere in $in_fmt...
+          // We strip the substring found between both wildcards ($match) from $in_fmt
+
+          $wpif = strpos($in_fmt, '*');
+          $match = substr($in_fmt, 0, $wpif);
+          $in_fmt = substr($in_fmt, $wpif);
+
+          if (substr_count($in, $match) > 0) {
+            // There is at least one $match in $in; everything left to the first $match
+            // within $in is considered to be a replacement ($repl) for the first wildcard;
+            // we strip ($repl . $match) from $in, and proceed with the rest...
+            $wpi = strpos($in, $match);
+            $repl = substr($in, 0, $wpi);
+            $in = substr($in, $wpi + strlen($match));
+
+            if ($wcof > 0) {
+              // If there are any wildcards in $out_fmt left, we replace the first one
+              $wpof = strpos($out_fmt, '*');
+              $out_fmt = substr_replace($out_fmt, $repl, $wpof, 1);
+              $wcof--;
+            }
+          }
+          else {
+            // ERROR #3: INPUT doesn't match INPUT FORMAT!!!
+            $out = '';
+            $error = 3;
+          }
+
+          if ($error == 0) {
+            if (strlen($in_fmt) > 0) {
+              // If there are more wildcards in $in_fmt,
+              // just recursively process the remaining...
+              $error = path_redirect_match_wildcards($in, $in_fmt, $out_fmt, $out);
+            }
+            else {
+              // We've done it...
+              $out = $out_fmt;
+            }
+          }
+        }
+        else {
+          // There is no wildcard in $in_fmt anymore...
+          // If there is anything in $in_fmt, it must match the right substring of $in
+          // (nothing should be right to the last $match within $in), and all the
+          // rest (before that $match) is considered as this last replacement ($repl)
+          $match = $in_fmt;
+
+          if ($match == '') {
+            // We've had only the wildcard character in $in_fmt here
+            // (the whole $in is a replacement $repl)
+            $repl = $in;
+
+            if ($wcof > 0) {
+            // If there are any wildcards in $out_fmt left, we replace the first one
+              $wpof = strpos($out_fmt, '*');
+              $out_fmt = substr_replace($out_fmt, $repl, $wpof, 1);
+              $wcof--;
+            }
+
+            // We've done it...
+            $out = $out_fmt;
+          }
+          elseif (substr_count($in, $match) > 0) {
+          // As stated above, there should be a match, and nothing right to it within $in
+          // (everything left to $match is considered as a replacement $repl)
+            $wpi = strrpos($in, $match);
+            $repl = substr($in, 0, $wpi);
+
+            if (strlen($in) > $wpi + strlen($match)) {
+            // ERROR #4: INPUT doesn't match INPUT FORMAT!!!
+              $out = '';
+              $error = 4;
+            }
+            else {
+              if ($wcof > 0) {
+                // If there are any wildcards in $out_fmt left, we replace the first one
+                $wpof = strpos($out_fmt, '*');
+                $out_fmt = substr_replace($out_fmt, $repl, $wpof, 1);
+                $wcof--;
+              }
+
+              // We've done it...
+              $out = $out_fmt;
+            }
+          }
+          else {
+            // ERROR #5: INPUT doesn't match INPUT FORMAT!!!
+            $out = '';
+            $error = 5;
+          }
+        }
+      }
+    }
+    else {
+    // There is no wildcard character in $in_fmt;
+    // if $in and $in_fmt do match, we've done it...
+      if (strcmp($in, $in_fmt) == 0) {
+        $out = $out_fmt;
+        $error = 0;
+      }
+      else {
+        // ERROR #6: INPUT doesn't match INPUT FORMAT!!!
+        $out = '';
+        $error = 6;
+      }
+    }
+  }
+
+  return $error;
+}
