Index: /trunk/include/functions_search.inc.php
===================================================================
--- /trunk/include/functions_search.inc.php	(revision 18206)
+++ /trunk/include/functions_search.inc.php	(revision 18207)
@@ -272,4 +272,21 @@
 }
 
+function is_odd_wbreak_begin($ch)
+{
+  return strpos('[{<=*+', $ch)===false ? false:true;
+}
+
+function is_odd_wbreak_end($ch)
+{
+  return strpos(']}>=*+', $ch)===false ? false:true;
+}
+
+define('QST_QUOTED',   0x01);
+define('QST_NOT',      0x02);
+define('QST_WILDCARD_BEGIN',0x04);
+define('QST_WILDCARD_END',  0x08);
+define('QST_WILDCARD', QST_WILDCARD_BEGIN|QST_WILDCARD_END);
+
+
 /**
  * analyzes and splits the quick/query search query $q into tokens
@@ -284,21 +301,22 @@
   $token_modifiers = array();
   $crt_token = "";
-  $crt_token_modifier = "";
-  $state = 0;
+  $crt_token_modifier = 0;
 
   for ($i=0; $i<strlen($q); $i++)
   {
     $ch = $q[$i];
-    switch ($state)
-    {
-      case 0:
+    if ($crt_token_modifier&QST_QUOTED==0)
+    {
         if ($ch=='"')
         {
-          $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier;
-          $crt_token = ""; $crt_token_modifier = "q";
-          $state=1;
-        }
-        elseif ( $ch=='*' )
-        { // wild card
+          if (strlen($crt_token))
+          {
+            $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier;
+            $crt_token = ""; $crt_token_modifier = 0;
+          }
+          $crt_token_modifier |= QST_QUOTED;
+        }
+        elseif ( strcspn($ch, '*+-><~')==0 )
+        { //special full text modifier
           if (strlen($crt_token))
           {
@@ -307,15 +325,9 @@
           else
           {
-            $crt_token_modifier .= '*';
-          }
-        }
-        elseif ( strcspn($ch, '+-><~')==0 )
-        { //special full text modifier
-          if (strlen($crt_token))
-          {
-            $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier;
-            $crt_token = ""; $crt_token_modifier = "";
-          }
-          $crt_token_modifier .= $ch;
+            if ( $ch=='*' )
+              $crt_token_modifier |= QST_WILDCARD_BEGIN;
+            if ( $ch=='-' )
+              $crt_token_modifier |= QST_NOT;
+          }
         }
         elseif (preg_match('/[\s,.;!\?]+/', $ch))
@@ -324,6 +336,7 @@
           {
             $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier;
-            $crt_token = ""; $crt_token_modifier = "";
-          }
+            $crt_token = "";
+          }
+          $crt_token_modifier = 0;
         }
         else
@@ -331,17 +344,21 @@
           $crt_token .= $ch;
         }
+    }
+    else // qualified with quotes
+    {
+      if ($ch=='"')
+      {
+        if ($i+1 < strlen($q) && $q[$i+1]=='*')
+        {
+          $crt_token_modifier |= QST_WILDCARD_END;
+          $i++;
+        }
+        $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier;
+        $crt_token = ""; $crt_token_modifier = 0;
+        $state=0;
         break;
-      case 1: // qualified with quotes
-        switch ($ch)
-        {
-          case '"':
-            $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier;
-            $crt_token = ""; $crt_token_modifier = "";
-            $state=0;
-            break;
-          default:
-            $crt_token .= $ch;
-        }
-        break;
+      }
+      else
+        $crt_token .= $ch;
     }
   }
@@ -383,5 +400,5 @@
   {
     $token = trim($tokens[$i], '%');
-    if (strstr($token_modifiers[$i], '-')!==false)
+    if ($token_modifiers[$i]&QST_NOT)
       continue;
     if ( strlen($token)==0 )
@@ -422,11 +439,11 @@
     );
   $q = trim($q);
-  if (empty($q))
+  analyse_qsearch($q, $tokens, $token_modifiers);
+  if (count($tokens)==0)
   {
     return $search_results;
   }
+  $debug[] = '<!--'.count($tokens).' tokens';
   
-  analyse_qsearch($q, $tokens, $token_modifiers);
-
   $q_like_field = '@@__db_field__@@'; //something never in a search
   $q_like_clause = get_qsearch_like_clause($tokens, $token_modifiers, $q_like_field );
@@ -468,4 +485,6 @@
     }
   }
+  $debug[] = count($by_weights).' fulltext';
+  $debug[] = 'ft score min:'.min($by_weights).' max:'.max($by_weights);
 
 
@@ -494,5 +513,5 @@
     for ($i=0; $i<count($tokens); $i++)
     {
-      if (strstr($token_modifiers[$i], '-')!==false)
+      if ($token_modifiers[$i]&QST_NOT)
         continue;// ignore this NOT token
       $transliterated_token = $transliterated_tokens[$i];
@@ -502,5 +521,5 @@
       while ( ($pos = strpos($transliterated_tag, $transliterated_token, $pos)) !== false)
       {
-        if (strstr($token_modifiers[$i], '*')!==false)
+        if ( ($token_modifiers[$i]&QST_WILDCARD)==QST_WILDCARD )
         {// wildcard in this token
           $match = 1;
@@ -509,26 +528,45 @@
         $token_len = strlen($transliterated_token);
 
-        $word_begin = $pos;
-        while ($word_begin>0)
+        // search begin of word
+        $wbegin_len=0; $wbegin_char=' ';
+        while ($pos-$wbegin_len > 0)
         {
-          if (! is_word_char($transliterated_tag[$word_begin-1]) )
+          if (! is_word_char($transliterated_tag[$pos-$wbegin_len-1]) )
+          {
+            $wbegin_char = $transliterated_tag[$pos-$wbegin_len-1];
             break;
-          $word_begin--;
-        }
-
-        $word_end = $pos + $token_len;
-        while ($word_end<strlen($transliterated_tag) && is_word_char($transliterated_tag[$word_end]) )
-          $word_end++;
-
-        $this_score = $token_len / ($word_end-$word_begin);
-        if ($token_len <= 2)
-        {// search for 1 or 2 characters must match exactly to avoid retrieving too much data
-          if ($token_len != $word_end-$word_begin)
-            $this_score = 0;
-        }
-        elseif ($token_len == 3)
+          }
+          $wbegin_len++;
+        }
+
+        // search end of word
+        $wend_len=0; $wend_char=' ';
+        while ($pos+$token_len+$wend_len < strlen($transliterated_tag))
         {
-          if ($word_end-$word_begin > 4)
-            $this_score = 0;
+          if (! is_word_char($transliterated_tag[$pos+$token_len+$wend_len]) )
+          {
+            $wend_char = $transliterated_tag[$pos+$token_len+$wend_len];
+            break;
+          }
+          $wend_len++;
+        }
+
+        $this_score = 0;
+        if ( ($token_modifiers[$i]&QST_WILDCARD)==0 )
+        {// no wildcard begin or end
+          if ($token_len <= 2)
+          {// search for 1 or 2 characters must match exactly to avoid retrieving too much data
+            if ($wbegin_len==0 && $wend_len==0 && !is_odd_wbreak_begin($wbegin_char) && !is_odd_wbreak_end($wend_char) )
+              $this_score = 1;
+          }
+          elseif ($token_len == 3)
+          {
+            if ($wbegin_len==0)
+              $this_score = $token_len / ($token_len + $wend_len);
+          }
+          else
+          {
+            $this_score = $token_len / ($token_len + 1.1 * $wbegin_len + 0.9 * $wend_len);
+          }
         }
 
@@ -547,4 +585,5 @@
   }
   $search_results['qs']['matching_tags']=$all_tags;
+  $debug[] = count($all_tags).' tags';
 
   // Step 2.2 - reduce matching tags for every token in the query search
@@ -573,7 +612,10 @@
     foreach($token_tags[$i] as $arr)
       $tag_ids[] = $arr['tag_id'];
+    $tag_ids = array_unique($tag_ids);
+    $debug[] = count($tag_ids).' unique tags';
 
     if (!empty($tag_ids))
     {
+      $tag_photo_count=0;
       $query = '
 SELECT image_id
@@ -586,5 +628,8 @@
         $image_id=(int)$row['image_id'];
         @$by_weights[$image_id] += 1;
-      }
+        $tag_photo_count++;
+      }
+      $debug[] = $tag_photo_count.' photos for tags';
+      $debug[] = count($by_weights).' photos after tags';
     }
   }
@@ -612,4 +657,5 @@
     }
   }
+  $debug[] = count(@$search_results['qs']['matching_cats']).' albums with images';
 
   if ( empty($by_weights) and empty($search_results['qs']['matching_cats']) )
@@ -655,4 +701,8 @@
   $allowed_images = array_from_query( $query, 'id');
 
+  $debug[] = count($allowed_images).' final photo count -->';
+  global $template;
+  $template->append('footer_elements', implode(', ', $debug) );
+
   if ( $super_order_by or empty($by_weights) )
   {
