Ignore:
Timestamp:
Mar 21, 2014, 6:16:35 AM (10 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.