Changeset 27868


Ignore:
Timestamp:
03/21/14 06:16:35 (5 years ago)
Author:
rvelices
Message:

bug 3056: Improve/rewrite quick search engine: by default AND is used to match all entered terms, OR operator, grouping using brackets ()
still work in progress

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/include/functions_search.inc.php

    r26825 r27868  
    292292define('QST_QUOTED',         0x01); 
    293293define('QST_NOT',            0x02); 
    294 define('QST_WILDCARD_BEGIN', 0x04); 
    295 define('QST_WILDCARD_END',   0x08); 
     294define('QST_OR',             0x04); 
     295define('QST_WILDCARD_BEGIN', 0x08); 
     296define('QST_WILDCARD_END',   0x10); 
    296297define('QST_WILDCARD', QST_WILDCARD_BEGIN|QST_WILDCARD_END); 
    297298 
     
    303304 * 
    304305 * @param string $q 
    305  * @param array &$qtokens 
    306  * @param array &$qtoken_modifiers 
    307  */ 
    308 function analyse_qsearch($q, &$qtokens, &$qtoken_modifiers) 
    309 { 
    310   $q = stripslashes($q); 
    311   $tokens = array(); 
    312   $token_modifiers = array(); 
    313   $crt_token = ""; 
    314   $crt_token_modifier = 0; 
    315  
    316   for ($i=0; $i<strlen($q); $i++) 
    317   { 
    318     $ch = $q[$i]; 
    319     if ( ($crt_token_modifier&QST_QUOTED)==0) 
    320     { 
     306 */ 
     307 
     308class QSingleToken 
     309{ 
     310  var $is_single = true; 
     311  var $token; 
     312  var $idx; 
     313 
     314  function __construct($token) 
     315  { 
     316    $this->token = $token; 
     317  } 
     318} 
     319 
     320class QMultiToken 
     321{ 
     322  var $is_single = false; 
     323  var $tokens = array(); 
     324  var $token_modifiers = array(); 
     325 
     326  function __toString() 
     327  { 
     328    $s = ''; 
     329    for ($i=0; $i<count($this->tokens); $i++) 
     330    { 
     331      $modifier = $this->token_modifiers[$i]; 
     332      if ($i) 
     333        $s .= ' '; 
     334      if ($modifier & QST_OR) 
     335        $s .= 'OR '; 
     336      if ($modifier & QST_NOT) 
     337        $s .= 'NOT '; 
     338      if ($modifier & QST_WILDCARD_BEGIN) 
     339        $s .= '*'; 
     340      if ($modifier & QST_QUOTED) 
     341        $s .= '"'; 
     342      if (! ($this->tokens[$i]->is_single) ) 
     343      { 
     344        $s .= '('; 
     345        $s .= $this->tokens[$i]; 
     346        $s .= ')'; 
     347      } 
     348      else 
     349      { 
     350        $s .= $this->tokens[$i]->token; 
     351      } 
     352      if ($modifier & QST_QUOTED) 
     353        $s .= '"'; 
     354      if ($modifier & QST_WILDCARD_END) 
     355        $s .= '*'; 
     356 
     357    } 
     358    return $s; 
     359  } 
     360 
     361  function push(&$token, &$modifier) 
     362  { 
     363    $this->tokens[] = new QSingleToken($token); 
     364    $this->token_modifiers[] = $modifier; 
     365    $token = ""; 
     366    $modifier = 0; 
     367  } 
     368 
     369  protected function parse_expression($q, &$qi, $level) 
     370  { 
     371    $crt_token = ""; 
     372    $crt_modifier = 0; 
     373 
     374    for ($stop=false; !$stop && $qi<strlen($q); $qi++) 
     375    { 
     376      $ch = $q[$qi]; 
     377      if ( ($crt_modifier&QST_QUOTED)==0) 
     378      { 
     379        switch ($ch) 
     380        { 
     381          case '(': 
     382            if (strlen($crt_token)) 
     383              $this->push($crt_token, $crt_modifier); 
     384            $sub = new QMultiToken; 
     385            $qi++; 
     386            $sub->parse_expression($q, $qi, $level+1); 
     387            $this->tokens[] = $sub; 
     388            $this->token_modifiers[] = $crt_modifier; 
     389            $crt_modifier = 0; 
     390            break; 
     391          case ')': 
     392            if ($level>0) 
     393              $stop = true; 
     394            break; 
     395          case '"': 
     396            if (strlen($crt_token)) 
     397              $this->push($crt_token, $crt_modifier); 
     398            $crt_modifier |= QST_QUOTED; 
     399            break; 
     400          case '-': 
     401            if (strlen($crt_token)) 
     402              $crt_token .= $ch; 
     403            else 
     404              $crt_modifier |= QST_NOT; 
     405            break; 
     406          case '*': 
     407            if (strlen($crt_token)) 
     408              $crt_token .= $ch; // wildcard end later 
     409            else 
     410              $crt_modifier |= QST_WILDCARD_BEGIN; 
     411            break; 
     412          default: 
     413            if (preg_match('/[\s,.;!\?]+/', $ch)) 
     414            { // white space 
     415              if (strlen($crt_token)) 
     416                $this->push($crt_token, $crt_modifier); 
     417              $crt_modifier = 0; 
     418            } 
     419            else 
     420              $crt_token .= $ch; 
     421            break; 
     422        } 
     423      } 
     424      else 
     425      {// quoted 
    321426        if ($ch=='"') 
    322427        { 
    323           if (strlen($crt_token)) 
     428          if ($qi+1 < strlen($q) && $q[$qi+1]=='*') 
    324429          { 
    325             $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier; 
    326             $crt_token = ""; $crt_token_modifier = 0; 
    327           } 
    328           $crt_token_modifier |= QST_QUOTED; 
    329         } 
    330         elseif ( strcspn($ch, '*+-><~')==0 ) 
    331         { //special full text modifier 
    332           if (strlen($crt_token)) 
     430            $crt_modifier |= QST_WILDCARD_END; 
     431            $ai++; 
     432          } 
     433          $this->push($crt_token, $crt_modifier); 
     434        } 
     435        else 
     436          $crt_token .= $ch; 
     437      } 
     438    } 
     439 
     440    if (strlen($crt_token)) 
     441      $this->push($crt_token, $crt_modifier); 
     442 
     443    for ($i=0; $i<count($this->tokens); $i++) 
     444    { 
     445      $token = $this->tokens[$i]; 
     446      $remove = false; 
     447      if ($token->is_single) 
     448      { 
     449        if ( ($this->token_modifiers[$i]&QST_QUOTED)==0 ) 
     450        { 
     451          if ('not' == strtolower($token->token)) 
    333452          { 
    334             $crt_token .= $ch; 
    335           } 
    336           else 
     453            if ($i+1 < count($this->tokens)) 
     454              $this->token_modifiers[$i+1] |= QST_NOT; 
     455            $token->token = ""; 
     456          } 
     457          if ('or' == strtolower($token->token)) 
    337458          { 
    338             if ( $ch=='*' ) 
    339               $crt_token_modifier |= QST_WILDCARD_BEGIN; 
    340             if ( $ch=='-' ) 
    341               $crt_token_modifier |= QST_NOT; 
    342           } 
    343         } 
    344         elseif (preg_match('/[\s,.;!\?]+/', $ch)) 
    345         { // white space 
    346           if (strlen($crt_token)) 
     459            if ($i+1 < count($this->tokens)) 
     460              $this->token_modifiers[$i+1] |= QST_OR; 
     461            $token->token = ""; 
     462          } 
     463          if ('and' == strtolower($token->token)) 
    347464          { 
    348             $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier; 
    349             $crt_token = ""; 
    350           } 
    351           $crt_token_modifier = 0; 
    352         } 
    353         else 
    354         { 
    355           $crt_token .= $ch; 
    356         } 
    357     } 
    358     else // qualified with quotes 
    359     { 
    360       if ($ch=='"') 
    361       { 
    362         if ($i+1 < strlen($q) && $q[$i+1]=='*') 
    363         { 
    364           $crt_token_modifier |= QST_WILDCARD_END; 
    365           $i++; 
    366         } 
    367         $tokens[] = $crt_token; $token_modifiers[] = $crt_token_modifier; 
    368         $crt_token = ""; $crt_token_modifier = 0; 
    369         $state=0; 
     465            $token->token = ""; 
     466          } 
     467          if ( substr($token->token, -1)=='*' ) 
     468          { 
     469            $token->token = rtrim($token->token, '*'); 
     470            $this->token_modifiers[$i] |= QST_WILDCARD_END; 
     471          } 
     472        } 
     473        if (!strlen($token->token)) 
     474          $remove = true; 
    370475      } 
    371476      else 
    372         $crt_token .= $ch; 
    373     } 
    374   } 
    375  
    376   if (strlen($crt_token)) 
    377   { 
    378     $tokens[] = $crt_token; 
    379     $token_modifiers[] = $crt_token_modifier; 
    380   } 
    381  
    382   $qtokens = array(); 
    383   $qtoken_modifiers = array(); 
    384   for ($i=0; $i<count($tokens); $i++) 
    385   { 
    386     if ( !($token_modifiers[$i] & QST_QUOTED) ) 
    387     { 
    388       if ( substr($tokens[$i], -1)=='*' ) 
    389       { 
    390         $tokens[$i] = rtrim($tokens[$i], '*'); 
    391         $token_modifiers[$i] |= QST_WILDCARD_END; 
    392       } 
    393     } 
    394     if ( strlen($tokens[$i])==0) 
    395       continue; 
    396     $qtokens[] = $tokens[$i]; 
    397     $qtoken_modifiers[] = $token_modifiers[$i]; 
    398   } 
    399 } 
    400  
    401 /** 
    402  * Returns the LIKE SQL clause corresponding to the quick search query 
    403  * that has been split into tokens. 
    404  * for example file LIKE '%john%' OR file LIKE '%bill%'. 
    405  * 
    406  * @param array $tokens 
    407  * @param array $token_modifiers 
    408  * @param string $field 
    409  * @return string|null 
    410  */ 
    411 function get_qsearch_like_clause($tokens, $token_modifiers, $field) 
    412 { 
    413   $clauses = array(); 
    414   for ($i=0; $i<count($tokens); $i++) 
    415   { 
    416     $token = trim($tokens[$i], '%'); 
    417     if ($token_modifiers[$i]&QST_NOT) 
    418       continue; 
    419     if ( strlen($token)==0 ) 
    420       continue; 
    421     $token = addslashes($token); 
    422     $token = str_replace( array('%','_'), array('\\%','\\_'), $token); // escape LIKE specials %_ 
    423     $clauses[] = $field.' LIKE \'%'.$token.'%\''; 
    424   } 
    425  
    426   return count($clauses) ? '('.implode(' OR ', $clauses).')' : null; 
    427 } 
    428  
    429 /** 
    430  * Returns tags corresponding to the quick search query that has been split into tokens. 
    431  * 
    432  * @param array $tokens 
    433  * @param array $token_modifiers 
    434  * @param array &$token_tag_ids 
    435  * @param array &$not_tag_ids 
    436  * @param array &$all_tags 
    437  */ 
    438 function get_qsearch_tags($tokens, $token_modifiers, &$token_tag_ids, &$not_tag_ids, &$all_tags) 
    439 { 
     477      { 
     478        if (!count($token->tokens)) 
     479          $remove = true; 
     480      } 
     481      if ($remove) 
     482      { 
     483        array_splice($this->tokens, $i, 1); 
     484        array_splice($this->token_modifiers, $i, 1); 
     485        $i--; 
     486      } 
     487    } 
     488  } 
     489} 
     490 
     491class QExpression extends QMultiToken 
     492{ 
     493  var $stokens = array(); 
     494  var $stoken_modifiers = array(); 
     495 
     496  function __construct($q) 
     497  { 
     498    $i = 0; 
     499    $this->parse_expression($q, $i, 0); 
     500    //@TODO: manipulate the tree so that 'a OR b c' is the same as 'b c OR a' 
     501    $this->build_single_tokens($this); 
     502  } 
     503 
     504  private function build_single_tokens(QMultiToken $expr) 
     505  { 
     506    //@TODO: double negation results in no negation in token modifier 
     507    for ($i=0; $i<count($expr->tokens); $i++) 
     508    { 
     509      $token = $expr->tokens[$i]; 
     510      if ($token->is_single) 
     511      { 
     512        $token->idx = count($this->stokens); 
     513        $this->stokens[] = $token->token; 
     514        $this->stoken_modifiers[] = $expr->token_modifiers[$i]; 
     515      } 
     516      else 
     517        $this->build_single_tokens($token); 
     518    } 
     519  } 
     520} 
     521 
     522class QResults 
     523{ 
     524  var $all_tags; 
     525  var $tag_ids; 
     526  var $tag_iids; 
     527  var $images_iids; 
     528  var $iids; 
     529} 
     530 
     531function qsearch_get_images(QExpression $expr, QResults $qsr) 
     532{ 
     533  //@TODO: inflections for english / french 
     534  $qsr->images_iids = array_fill(0, count($expr->tokens), array()); 
     535  $query_base = 'SELECT id from '.IMAGES_TABLE.' i WHERE '; 
     536  for ($i=0; $i<count($expr->stokens); $i++) 
     537  { 
     538    $token = $expr->stokens[$i]; 
     539    $clauses = array(); 
     540 
     541    $like = addslashes($token); 
     542    $like = str_replace( array('%','_'), array('\\%','\\_'), $like); // escape LIKE specials %_ 
     543    $clauses[] = 'CONVERT(file, CHAR) LIKE \'%'.$like.'%\''; 
     544 
     545    if (strlen($token)>3) // default minimum full text index 
     546    { 
     547      $ft = $token; 
     548      if ($expr->stoken_modifiers[$i] & QST_QUOTED) 
     549        $ft = '"'.$ft.'"'; 
     550      if ($expr->stoken_modifiers[$i] & QST_WILDCARD_END) 
     551        $ft .= '*'; 
     552      $clauses[] = 'MATCH(i.name, i.comment) AGAINST( \''.addslashes($ft).'\' IN BOOLEAN MODE)'; 
     553    } 
     554    else 
     555    { 
     556      foreach( array('i.name', 'i.comment') as $field) 
     557      { 
     558        $clauses[] = $field.' LIKE \''.$like.' %\''; 
     559        $clauses[] = $field.' LIKE \'% '.$like.'\''; 
     560        $clauses[] = $field.' LIKE \'% '.$like.' %\''; 
     561      } 
     562    } 
     563    $query = $query_base.'('.implode(' OR ', $clauses).')'; 
     564    $qsr->images_iids[$i] = query2array($query,null,'id'); 
     565  } 
     566} 
     567 
     568function qsearch_get_tags(QExpression $expr, QResults $qsr) 
     569{ 
     570  $tokens = $expr->stokens; 
     571  $token_modifiers = $expr->stoken_modifiers; 
     572 
    440573  $token_tag_ids = array_fill(0, count($tokens), array() ); 
    441   $not_tag_ids = $all_tags = array(); 
     574  $all_tags = array(); 
    442575 
    443576  $token_tag_scores = $token_tag_ids; 
     
    532665  } 
    533666 
    534   // process not tags 
     667  // process tags 
     668  $not_tag_ids = array(); 
    535669  for ($i=0; $i<count($tokens); $i++) 
    536670  { 
    537     if ( ! ($token_modifiers[$i]&QST_NOT) ) 
    538       continue; 
    539  
    540671    array_multisort($token_tag_scores[$i], SORT_DESC|SORT_NUMERIC, $token_tag_ids[$i]); 
     672    $is_not = $token_modifiers[$i]&QST_NOT; 
     673    $counter = 0; 
    541674 
    542675    for ($j=0; $j<count($token_tag_scores[$i]); $j++) 
    543676    { 
    544       if ($token_tag_scores[$i][$j] < 0.8) 
    545         break; 
    546       if ($j>0 && $token_tag_scores[$i][$j] < $token_tag_scores[$i][0]) 
    547         break; 
    548       $tag_id = $token_tag_ids[$i][$j]; 
    549       if ( isset($all_tags[$tag_id]) ) 
    550       { 
    551         unset($all_tags[$tag_id]); 
    552         $not_tag_ids[] = $tag_id; 
    553       } 
    554     } 
    555     $token_tag_ids[$i] = array(); 
    556   } 
    557  
    558   // process regular tags 
    559   for ($i=0; $i<count($tokens); $i++) 
    560   { 
    561     if ( $token_modifiers[$i]&QST_NOT ) 
    562       continue; 
    563  
    564     array_multisort($token_tag_scores[$i], SORT_DESC|SORT_NUMERIC, $token_tag_ids[$i]); 
    565  
    566     $counter = 0; 
    567     for ($j=0; $j<count($token_tag_scores[$i]); $j++) 
    568     { 
    569       $tag_id = $token_tag_ids[$i][$j]; 
    570       if ( ! isset($all_tags[$tag_id]) ) 
    571       { 
    572         array_splice($token_tag_ids[$i], $j, 1); 
    573         array_splice($token_tag_scores[$i], $j, 1); 
    574         $j--; 
    575         continue; 
    576       } 
    577  
    578       $counter += $all_tags[$tag_id]['counter']; 
    579       if ($counter > 200 && $j>0 && $token_tag_scores[$i][0] > $token_tag_scores[$i][$j] ) 
    580       {// "many" images in previous tags and starting from this tag is less relevent 
    581         array_splice($token_tag_ids[$i], $j); 
    582         array_splice($token_tag_scores[$i], $j); 
    583         break; 
    584       } 
    585     } 
    586   } 
    587  
     677      if ($is_not) 
     678      { 
     679        if ($token_tag_scores[$i][$j] < 0.8 || 
     680              ($j>0 && $token_tag_scores[$i][$j] < $token_tag_scores[$i][0]) ) 
     681        { 
     682          array_splice($token_tag_scores[$i], $j); 
     683          array_splice($token_tag_ids[$i], $j); 
     684        } 
     685      } 
     686      else 
     687      { 
     688        $tag_id = $token_tag_ids[$i][$j]; 
     689        $counter += $all_tags[$tag_id]['counter']; 
     690        if ($counter > 200 && $j>0 && $token_tag_scores[$i][0] > $token_tag_scores[$i][$j] ) 
     691        {// "many" images in previous tags and starting from this tag is less relevent 
     692          array_splice($token_tag_ids[$i], $j); 
     693          array_splice($token_tag_scores[$i], $j); 
     694          break; 
     695        } 
     696      } 
     697    } 
     698 
     699    if ($is_not) 
     700    { 
     701      $not_tag_ids = array_merge($not_tag_ids, $token_tag_ids[$i]); 
     702    } 
     703  } 
     704 
     705  $all_tags = array_diff_key($all_tags, array_flip($not_tag_ids)); 
    588706  usort($all_tags, 'tag_alpha_compare'); 
    589707  foreach ( $all_tags as &$tag ) 
     
    591709    $tag['name'] = trigger_event('render_tag_name', $tag['name'], $tag); 
    592710  } 
     711  $qsr->all_tags = $all_tags; 
     712 
     713  $qsr->tag_ids = $token_tag_ids; 
     714  $qsr->tag_iids = array_fill(0, count($tokens), array() ); 
     715 
     716  for ($i=0; $i<count($tokens); $i++) 
     717  { 
     718    $tag_ids = $token_tag_ids[$i]; 
     719 
     720    if (!empty($tag_ids)) 
     721    { 
     722      $query = ' 
     723SELECT image_id FROM '.IMAGE_TAG_TABLE.' 
     724  WHERE tag_id IN ('.implode(',',$tag_ids).') 
     725  GROUP BY image_id'; 
     726      $qsr->tag_iids[$i] = query2array($query, null, 'image_id'); 
     727    } 
     728  } 
     729} 
     730 
     731 
     732function qsearch_eval(QExpression $expr, QResults $qsr, QMultiToken $crt_expr) 
     733{ 
     734  $ids = $not_ids = array(); 
     735  $first = true; 
     736  for ($i=0; $i<count($crt_expr->tokens); $i++) 
     737  { 
     738    $current = $crt_expr->tokens[$i]; 
     739    if ($current->is_single) 
     740    { 
     741      $crt_ids = $qsr->iids[$current->idx] = array_unique( array_merge($qsr->images_iids[$current->idx], $qsr->tag_iids[$current->idx]) ); 
     742    } 
     743    else 
     744      $crt_ids = qsearch_eval($expr, $qsr, $current); 
     745    $modifier = $crt_expr->token_modifiers[$i]; 
     746 
     747    if ($modifier & QST_NOT) 
     748      $not_ids = array_unique( array_merge($not_ids, $crt_ids)); 
     749    else 
     750    { 
     751      if ($modifier & QST_OR) 
     752        $ids = array_unique( array_merge($ids, $crt_ids) ); 
     753      else 
     754      { 
     755        if ($current->is_single && empty($crt_ids)) 
     756        { 
     757          //@TODO: mark this term as unmatched and tell users 
     758          //@TODO: if we don't find a term at all, maybe ignore it and produce some results 
     759        } 
     760        if ($first) 
     761          $ids = $crt_ids; 
     762        else 
     763          $ids = array_intersect($ids, $crt_ids); 
     764        $first= false; 
     765      } 
     766    } 
     767  } 
     768 
     769  if (count($not_ids)) 
     770    $ids = array_diff($ids, $not_ids); 
     771  return $ids; 
    593772} 
    594773 
     
    621800    ); 
    622801  $q = trim($q); 
    623   analyse_qsearch($q, $tokens, $token_modifiers); 
    624   if (count($tokens)==0) 
    625   { 
     802  $expression = new QExpression($q); 
     803//var_export($expression); 
     804 
     805  $qsr = new QResults; 
     806  qsearch_get_tags($expression, $qsr); 
     807  qsearch_get_images($expression, $qsr); 
     808//var_export($qsr->all_tags); 
     809 
     810  $ids = qsearch_eval($expression, $qsr, $expression); 
     811 
     812  $debug[] = "<!--\nparsed: ".$expression; 
     813  $debug[] = count($expression->stokens).' tokens'; 
     814  for ($i=0; $i<count($expression->stokens); $i++) 
     815  { 
     816    $debug[] = $expression->stokens[$i].': '.count($qsr->tag_ids[$i]).' tags, '.count($qsr->tag_iids[$i]).' tiids, '.count($qsr->images_iids[$i]).' iiids, '.count($qsr->iids[$i]).' iids'; 
     817  } 
     818  $debug[] = 'before perms '.count($ids); 
     819 
     820  $search_results['qs']['matching_tags'] = $qsr->all_tags; 
     821  global $template; 
     822 
     823  if (empty($ids)) 
     824  { 
     825    $debug[] = '-->'; 
     826    $template->append('footer_elements', implode("\n", $debug) ); 
    626827    return $search_results; 
    627828  } 
    628   $debug[] = '<!--'.count($tokens).' tokens'; 
    629  
    630   $q_like_field = '@@__db_field__@@'; //something never in a search 
    631   $q_like_clause = get_qsearch_like_clause($tokens, $token_modifiers, $q_like_field ); 
    632  
    633   // Step 1 - first we find matches in #images table =========================== 
    634   $where_clauses='MATCH(i.name, i.comment) AGAINST( \''.$q.'\' IN BOOLEAN MODE)'; 
    635   if (!empty($q_like_clause)) 
    636   { 
    637     $where_clauses .= ' 
    638     OR '. str_replace($q_like_field, 'CONVERT(file, CHAR)', $q_like_clause); 
    639     $where_clauses = '('.$where_clauses.')'; 
    640   } 
    641   $where_clauses = array($where_clauses); 
    642   if (!empty($images_where)) 
    643   { 
    644     $where_clauses[]='('.$images_where.')'; 
    645   } 
    646   $where_clauses[] .= get_sql_condition_FandF 
    647       ( 
    648         array( 'visible_images' => 'i.id' ), null, true 
    649       ); 
    650   $query = ' 
    651 SELECT i.id, 
    652     MATCH(i.name, i.comment) AGAINST( \''.$q.'\' IN BOOLEAN MODE) AS weight 
    653   FROM '.IMAGES_TABLE.' i 
    654   WHERE '.implode("\n AND ", $where_clauses); 
    655  
    656   $by_weights=array(); 
    657   $result = pwg_query($query); 
    658   while ($row = pwg_db_fetch_assoc($result)) 
    659   { // weight is important when sorting images by relevance 
    660     if ($row['weight']) 
    661     { 
    662       $by_weights[(int)$row['id']] =  2*$row['weight']; 
    663     } 
    664     else 
    665     {//full text does not match but file name match 
    666       $by_weights[(int)$row['id']] =  2; 
    667     } 
    668   } 
    669   $debug[] = count($by_weights).' fulltext'; 
    670   if (!empty($by_weights)) 
    671   { 
    672     $debug[] = 'ft score min:'.min($by_weights).' max:'.max($by_weights); 
    673   } 
    674  
    675  
    676   // Step 2 - get the tags and the images for tags 
    677   get_qsearch_tags($tokens, $token_modifiers, $token_tag_ids, $not_tag_ids, $search_results['qs']['matching_tags']); 
    678   $debug[] = count($search_results['qs']['matching_tags']).' tags'; 
    679  
    680   for ($i=0; $i<count($token_tag_ids); $i++) 
    681   { 
    682     $tag_ids = $token_tag_ids[$i]; 
    683     $debug[] = count($tag_ids).' unique tags'; 
    684  
    685     if (!empty($tag_ids)) 
    686     { 
    687       $tag_photo_count=0; 
    688       $query = ' 
    689 SELECT image_id FROM '.IMAGE_TAG_TABLE.' 
    690   WHERE tag_id IN ('.implode(',',$tag_ids).') 
    691   GROUP BY image_id'; 
    692       $result = pwg_query($query); 
    693       while ($row = pwg_db_fetch_assoc($result)) 
    694       { // weight is important when sorting images by relevance 
    695         $image_id=(int)$row['image_id']; 
    696         @$by_weights[$image_id] += 1; 
    697         $tag_photo_count++; 
    698       } 
    699       $debug[] = $tag_photo_count.' photos for tag'; 
    700       $debug[] = count($by_weights).' photos after'; 
    701     } 
    702   } 
    703  
    704   // Step 3 - search categories corresponding to the query $q ================== 
    705   $query = ' 
    706 SELECT id, name, permalink, nb_images 
    707   FROM '.CATEGORIES_TABLE.' 
    708     INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.' ON id=cat_id 
    709   WHERE user_id='.$user['id'].' 
    710     AND MATCH(name, comment) AGAINST( \''.$q.'\' IN BOOLEAN MODE)'. 
    711   get_sql_condition_FandF ( 
    712       array( 'visible_categories' => 'cat_id' ), "\n    AND" 
    713     ); 
    714   $result = pwg_query($query); 
    715   while ($row = pwg_db_fetch_assoc($result)) 
    716   { // weight is important when sorting images by relevance 
    717     if ($row['nb_images']==0) 
    718     { 
    719       $search_results['qs']['matching_cats_no_images'][] = $row; 
    720     } 
    721     else 
    722     { 
    723       $search_results['qs']['matching_cats'][$row['id']] = $row; 
    724     } 
    725   } 
    726   $debug[] = count(@$search_results['qs']['matching_cats']).' albums with images'; 
    727  
    728   if ( empty($by_weights) and empty($search_results['qs']['matching_cats']) ) 
    729   { 
    730     return $search_results; 
    731   } 
    732  
    733   if (!empty($not_tag_ids)) 
    734   { 
    735     $query = ' 
    736 SELECT image_id FROM '.IMAGE_TAG_TABLE.' 
    737   WHERE tag_id IN ('.implode(',',$not_tag_ids).') 
    738   GROUP BY image_id'; 
    739       $result = pwg_query($query); 
    740       while ($row = pwg_db_fetch_row($result)) 
    741       { 
    742         $id = $row[0]; 
    743         unset($by_weights[$id]); 
    744       } 
    745       $debug[] = count($by_weights).' after not tags'; 
    746   } 
    747   // Step 4 - now we have $by_weights ( array image id => weight ) that need 
    748   // permission checks and/or matching categories to get images from 
     829 
    749830  $where_clauses = array(); 
    750   if ( !empty($by_weights) ) 
    751   { 
    752     $where_clauses[]='i.id IN (' 
    753       . implode(',', array_keys($by_weights)) . ')'; 
    754   } 
    755   if ( !empty($search_results['qs']['matching_cats']) ) 
    756   { 
    757     $where_clauses[]='category_id IN ('. 
    758       implode(',',array_keys($search_results['qs']['matching_cats'])).')'; 
    759   } 
    760   $where_clauses = array( '('.implode("\n    OR ",$where_clauses).')' ); 
     831  $where_clauses[]='i.id IN ('. implode(',', $ids) . ')'; 
    761832  if (!empty($images_where)) 
    762833  { 
     
    780851  $conf['order_by']; 
    781852 
    782   $allowed_images = array_from_query( $query, 'id'); 
    783  
    784   $debug[] = count($allowed_images).' final photo count -->'; 
    785   global $template; 
    786   $template->append('footer_elements', implode(', ', $debug) ); 
    787  
    788   if ( $super_order_by or empty($by_weights) ) 
    789   { 
    790     $search_results['items'] = $allowed_images; 
    791     return $search_results; 
    792   } 
    793  
    794   $allowed_images = array_flip( $allowed_images ); 
    795   $divisor = 5.0 * count($allowed_images); 
    796   foreach ($allowed_images as $id=> &$rank ) 
    797   { 
    798     $weight = isset($by_weights[$id]) ? $by_weights[$id] : 1; 
    799     $weight -= $rank/$divisor; 
    800     $rank = $weight; 
    801   } 
    802   unset($rank); 
    803  
    804   arsort($allowed_images, SORT_NUMERIC); 
    805   $search_results['items'] = array_keys($allowed_images); 
     853  $ids = query2array($query, null, 'id'); 
     854 
     855  $debug[] = count($ids).' final photo count -->'; 
     856  $template->append('footer_elements', implode("\n", $debug) ); 
     857 
     858  $search_results['items'] = $ids; 
    806859  return $search_results; 
    807860} 
Note: See TracChangeset for help on using the changeset viewer.