diff --git a/modules/search.module b/modules/search.module
index 1327e8bb79e257a39964f01bac06314de52864a8..3f983a155e8a07dec173309f5a23043311fa06c3 100644
--- a/modules/search.module
+++ b/modules/search.module
@@ -282,6 +282,21 @@ function _search_keywords_truncate(&$text) {
   $text = truncate_utf8($text, 50);
 }
 
+/**
+ * Loosens up a set of search keywords by adding wildcards, if possible.
+ * 
+ * @param $text
+ *   The keywords as entered by the user.
+ * @return
+ *   If more wildcards can be added, the adjusted keywords are returned.
+ *   If the query is already as loose as possible, NULL is returned.
+ */
+function search_keywords_variation($text) {
+  $text = trim($text);
+  $new = preg_replace('/\*+/', '*', '*'. implode('* *', explode(' ', trim($text))) .'*');
+  return ($new != $text) ? $new : NULL;
+}
+
 /**
  * Invokes hook_search_preprocess() in modules.
  */
@@ -445,7 +460,7 @@ function search_index($sid, $type, $text) {
  * GROUP BY i.type, i.sid
  * ORDER BY score DESC";
  *
- * @param $keys
+ * @param $keywords
  *   A search string as entered by the user.
  *
  * @param $type
@@ -459,15 +474,18 @@ function search_index($sid, $type, $text) {
  *   (optional) A string to be inserted into the WHERE part of the SQL query.
  *   For example "(n.status > 0)".
  *
+ * @param $variation
+ *   Used internally. Must not be specified.
+ *
  * @return
  *   An array of SIDs for the search results.
  *
  * @ingroup search
  */
-function do_search($keys, $type, $join = '', $where = '1') {
+function do_search($keywords, $type, $join = '', $where = '1', $variation = true) {
   // Note, we replace the wildcards with U+FFFD (Replacement character) to pass
   // through the keyword extractor. Multiple wildcards are collapsed into one.
-  $keys = preg_replace('!\*+!', '�', $keys);
+  $keys = preg_replace('!\*+!', '�', $keywords);
 
   // Split into words
   $keys = search_keywords_split($keys);
@@ -494,24 +512,33 @@ function do_search($keys, $type, $join = '', $where = '1') {
     }
   }
   // Tell the user which words were excluded
-  if (count($refused)) {
-    drupal_set_message(t('The following word(s) were not included because they were too short: %words', array('%words' => '<em>'. implode(', ', $refused) .'</em>')));
+  if (count($refused) && $variation) {
+    $message = format_plural(count($refused),
+                             'The word %words was not included because it is too short.',
+                             'The words %words were not included because they were too short.');
+    drupal_set_message(strtr($message, array('%words' => '<em>'. drupal_specialchars(implode(', ', $refused)) .'</em>')));
   }
 
   if (count($words) == 0) {
     return array();
   }
-  $where .= ' AND ('. implode(' OR ', $words) .')';
+  $conditions = $where .' AND ('. implode(' OR ', $words) .')';
 
   // Get result count (for pager)
-  $count = db_result(db_query("SELECT COUNT(DISTINCT i.sid, i.type) FROM {search_index} i $join WHERE $where", $arguments));
+  $count = db_result(db_query("SELECT COUNT(DISTINCT i.sid, i.type) FROM {search_index} i $join WHERE $conditions", $arguments));
   if ($count == 0) {
-    return array();
+    // Try out a looser search query if nothing was found.
+    if ($variation && $loose = search_keywords_variation($keywords)) {
+      return do_search($loose, $type, $join, $where, false);
+    }
+    else {
+      return array();
+    }
   }
   $count_query = "SELECT $count";
 
   // Do pager query
-  $query = "SELECT i.type, i.sid, SUM(i.score/t.count) AS score FROM {search_index} i $join INNER JOIN {search_total} t ON i.word = t.word WHERE $where GROUP BY i.type, i.sid ORDER BY score DESC";
+  $query = "SELECT i.type, i.sid, SUM(i.score/t.count) AS score FROM {search_index} i $join INNER JOIN {search_total} t ON i.word = t.word WHERE $conditions GROUP BY i.type, i.sid ORDER BY score DESC";
   $result = pager_query($query, 15, 0, $count_query, $arguments);
 
   $results = array();
diff --git a/modules/search/search.module b/modules/search/search.module
index 1327e8bb79e257a39964f01bac06314de52864a8..3f983a155e8a07dec173309f5a23043311fa06c3 100644
--- a/modules/search/search.module
+++ b/modules/search/search.module
@@ -282,6 +282,21 @@ function _search_keywords_truncate(&$text) {
   $text = truncate_utf8($text, 50);
 }
 
+/**
+ * Loosens up a set of search keywords by adding wildcards, if possible.
+ * 
+ * @param $text
+ *   The keywords as entered by the user.
+ * @return
+ *   If more wildcards can be added, the adjusted keywords are returned.
+ *   If the query is already as loose as possible, NULL is returned.
+ */
+function search_keywords_variation($text) {
+  $text = trim($text);
+  $new = preg_replace('/\*+/', '*', '*'. implode('* *', explode(' ', trim($text))) .'*');
+  return ($new != $text) ? $new : NULL;
+}
+
 /**
  * Invokes hook_search_preprocess() in modules.
  */
@@ -445,7 +460,7 @@ function search_index($sid, $type, $text) {
  * GROUP BY i.type, i.sid
  * ORDER BY score DESC";
  *
- * @param $keys
+ * @param $keywords
  *   A search string as entered by the user.
  *
  * @param $type
@@ -459,15 +474,18 @@ function search_index($sid, $type, $text) {
  *   (optional) A string to be inserted into the WHERE part of the SQL query.
  *   For example "(n.status > 0)".
  *
+ * @param $variation
+ *   Used internally. Must not be specified.
+ *
  * @return
  *   An array of SIDs for the search results.
  *
  * @ingroup search
  */
-function do_search($keys, $type, $join = '', $where = '1') {
+function do_search($keywords, $type, $join = '', $where = '1', $variation = true) {
   // Note, we replace the wildcards with U+FFFD (Replacement character) to pass
   // through the keyword extractor. Multiple wildcards are collapsed into one.
-  $keys = preg_replace('!\*+!', '�', $keys);
+  $keys = preg_replace('!\*+!', '�', $keywords);
 
   // Split into words
   $keys = search_keywords_split($keys);
@@ -494,24 +512,33 @@ function do_search($keys, $type, $join = '', $where = '1') {
     }
   }
   // Tell the user which words were excluded
-  if (count($refused)) {
-    drupal_set_message(t('The following word(s) were not included because they were too short: %words', array('%words' => '<em>'. implode(', ', $refused) .'</em>')));
+  if (count($refused) && $variation) {
+    $message = format_plural(count($refused),
+                             'The word %words was not included because it is too short.',
+                             'The words %words were not included because they were too short.');
+    drupal_set_message(strtr($message, array('%words' => '<em>'. drupal_specialchars(implode(', ', $refused)) .'</em>')));
   }
 
   if (count($words) == 0) {
     return array();
   }
-  $where .= ' AND ('. implode(' OR ', $words) .')';
+  $conditions = $where .' AND ('. implode(' OR ', $words) .')';
 
   // Get result count (for pager)
-  $count = db_result(db_query("SELECT COUNT(DISTINCT i.sid, i.type) FROM {search_index} i $join WHERE $where", $arguments));
+  $count = db_result(db_query("SELECT COUNT(DISTINCT i.sid, i.type) FROM {search_index} i $join WHERE $conditions", $arguments));
   if ($count == 0) {
-    return array();
+    // Try out a looser search query if nothing was found.
+    if ($variation && $loose = search_keywords_variation($keywords)) {
+      return do_search($loose, $type, $join, $where, false);
+    }
+    else {
+      return array();
+    }
   }
   $count_query = "SELECT $count";
 
   // Do pager query
-  $query = "SELECT i.type, i.sid, SUM(i.score/t.count) AS score FROM {search_index} i $join INNER JOIN {search_total} t ON i.word = t.word WHERE $where GROUP BY i.type, i.sid ORDER BY score DESC";
+  $query = "SELECT i.type, i.sid, SUM(i.score/t.count) AS score FROM {search_index} i $join INNER JOIN {search_total} t ON i.word = t.word WHERE $conditions GROUP BY i.type, i.sid ORDER BY score DESC";
   $result = pager_query($query, 15, 0, $count_query, $arguments);
 
   $results = array();