Changeset 28144


Ignore:
Timestamp:
04/09/14 23:23:49 (6 years ago)
Author:
rvelices
Message:

bug 3056: quick search - now tag search is the same as image search (full text match or like operator)

File:
1 edited

Legend:

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

    r28128 r28144  
    256256} 
    257257 
    258 /** 
    259  * Finds if a char is a letter, a figure or any char of the extended ASCII table (>127). 
    260  * 
    261  * @param char $ch 
    262  * @return bool 
    263  */ 
    264 function is_word_char($ch) 
    265 { 
    266   return ($ch>='0' && $ch<='9') || ($ch>='a' && $ch<='z') || ($ch>='A' && $ch<='Z') || ord($ch)>127; 
    267 } 
    268  
    269 /** 
    270  * Finds if a char is a special token for word start: [{<=*+ 
    271  * 
    272  * @param char $ch 
    273  * @return bool 
    274  */ 
    275 function is_odd_wbreak_begin($ch) 
    276 { 
    277   return strpos('[{<=*+', $ch)===false ? false:true; 
    278 } 
    279  
    280 /** 
    281  * Finds if a char is a special token for word end: ]}>=*+ 
    282  * 
    283  * @param char $ch 
    284  * @return bool 
    285  */ 
    286 function is_odd_wbreak_end($ch) 
    287 { 
    288   return strpos(']}>=*+', $ch)===false ? false:true; 
    289 } 
    290258 
    291259 
     
    296264define('QST_WILDCARD_END',   0x10); 
    297265define('QST_WILDCARD', QST_WILDCARD_BEGIN|QST_WILDCARD_END); 
    298  
     266define('QST_BREAK',          0x20); 
    299267 
    300268class QSearchScope 
     
    553521    if (strlen($token) || (isset($scope) && $scope->nullable)) 
    554522    { 
     523      if (isset($scope)) 
     524        $modifier |= QST_BREAK; 
    555525      $this->tokens[] = new QSingleToken($token, $modifier, $scope); 
    556526    } 
     
    714684      { 
    715685        array_splice($this->tokens, $i, 1); 
     686        if ($i<count($this->tokens) && $this->tokens[$i]->is_single) 
     687        { 
     688          $this->tokens[$i]->modifier |= QST_BREAK; 
     689        } 
    716690        $i--; 
    717691      } 
     692    } 
     693 
     694    if ($level>0 && count($this->tokens) && $this->tokens[0]->is_single) 
     695    { 
     696      $this->tokens[0]->modifier |= QST_BREAK; 
    718697    } 
    719698  } 
     
    837816} 
    838817 
     818function qsearch_get_text_token_search_sql($token, $fields) 
     819{ 
     820  $clauses = array(); 
     821  $variants = array_merge(array($token->term), $token->variants); 
     822  $fts = array(); 
     823  foreach ($variants as $variant) 
     824  { 
     825    if (mb_strlen($variant)<=3 
     826      || strcspn($variant, '!"#$%&()*+,./:;<=>?@[\]^`{|}~') < 3) 
     827    {// odd term or too short for full text search; fallback to regex but unfortunately this is diacritic/accent sensitive 
     828      $pre = ($token->modifier & QST_WILDCARD_BEGIN) ? '' : '[[:<:]]'; 
     829      $post = ($token->modifier & QST_WILDCARD_END) ? '' : '[[:>:]]'; 
     830      foreach( $fields as $field) 
     831        $clauses[] = $field.' REGEXP \''.$pre.addslashes(preg_quote($variant)).$post.'\''; 
     832    } 
     833    else 
     834    { 
     835      $ft = $variant; 
     836      if ($token->modifier & QST_QUOTED) 
     837        $ft = '"'.$ft.'"'; 
     838      if ($token->modifier & QST_WILDCARD_END) 
     839        $ft .= '*'; 
     840      $fts[] = $ft; 
     841    } 
     842  } 
     843 
     844  if (count($fts)) 
     845  { 
     846    $clauses[] = 'MATCH('.implode(', ',$fields).') AGAINST( \''.addslashes(implode(' ',$fts)).'\' IN BOOLEAN MODE)'; 
     847  } 
     848  return $clauses; 
     849} 
     850 
    839851function qsearch_get_images(QExpression $expr, QResults $qsr) 
    840852{ 
     
    857869      case 'photo': 
    858870        $clauses[] = $file_like; 
    859  
    860         $variants = array_merge(array($token->term), $token->variants); 
    861         $fts = array(); 
    862         foreach ($variants as $variant) 
    863         { 
    864           if (mb_strlen($variant)<=3 
    865             || strcspn($variant, '!"#$%&()*+,./:;<=>?@[\]^`{|}~') < 3) 
    866           {// odd term or too short for full text search; fallback to regex but unfortunately this is diacritic/accent sensitive 
    867             $pre = ($token->modifier & QST_WILDCARD_BEGIN) ? '' : '[[:<:]]'; 
    868             $post = ($token->modifier & QST_WILDCARD_END) ? '' : '[[:>:]]'; 
    869             foreach( array('i.name', 'i.comment') as $field) 
    870               $clauses[] = $field.' REGEXP \''.$pre.addslashes(preg_quote($variant)).$post.'\''; 
    871           } 
    872           else 
    873           { 
    874             $ft = $variant; 
    875             if ($expr->stoken_modifiers[$i] & QST_QUOTED) 
    876               $ft = '"'.$ft.'"'; 
    877             if ($expr->stoken_modifiers[$i] & QST_WILDCARD_END) 
    878               $ft .= '*'; 
    879             $fts[] = $ft; 
    880           } 
    881         } 
    882  
    883         if (count($fts)) 
    884         { 
    885           $clauses[] = 'MATCH(i.name, i.comment) AGAINST( \''.addslashes(implode(' ',$fts)).'\' IN BOOLEAN MODE)'; 
    886         } 
     871        $clauses = array_merge($clauses, qsearch_get_text_token_search_sql($token, array('name','comment'))); 
    887872        break; 
    888873 
     
    930915function qsearch_get_tags(QExpression $expr, QResults $qsr) 
    931916{ 
    932   $tokens = $expr->stokens; 
    933   $token_modifiers = $expr->stoken_modifiers; 
    934  
    935   $token_tag_ids = array_fill(0, count($tokens), array() ); 
     917  $token_tag_ids = $qsr->tag_iids = array_fill(0, count($expr->stokens), array() ); 
    936918  $all_tags = array(); 
    937919 
    938   $token_tag_scores = $token_tag_ids; 
    939   $transliterated_tokens = array(); 
    940   foreach ($tokens as $token) 
    941   { 
    942     if (!isset($token->scope) || 'tag' == $token->scope->id) 
    943     { 
    944       $transliterated_tokens[] = transliterate($token->term); 
    945     } 
    946     else 
    947     { 
    948       $transliterated_tokens[] = ''; 
    949     } 
    950   } 
    951  
    952   $query = ' 
    953 SELECT t.*, COUNT(image_id) AS counter 
    954   FROM '.TAGS_TABLE.' t 
    955     INNER JOIN '.IMAGE_TAG_TABLE.' ON id=tag_id 
    956   GROUP BY id'; 
    957   $result = pwg_query($query); 
    958   while ($tag = pwg_db_fetch_assoc($result)) 
    959   { 
    960     $transliterated_tag = transliterate($tag['name']); 
    961  
    962     // find how this tag matches query tokens 
    963     for ($i=0; $i<count($tokens); $i++) 
    964     { 
    965       $transliterated_token = $transliterated_tokens[$i]; 
    966       if (strlen($transliterated_token)==0) 
    967         continue; 
    968  
    969       $match = false; 
    970       $pos = 0; 
    971       while ( ($pos = strpos($transliterated_tag, $transliterated_token, $pos)) !== false) 
    972       { 
    973         if ( ($token_modifiers[$i]&QST_WILDCARD)==QST_WILDCARD ) 
    974         {// wildcard in this token 
    975           $match = 1; 
    976           break; 
    977         } 
    978         $token_len = strlen($transliterated_token); 
    979  
    980         // search begin of word 
    981         $wbegin_len=0; $wbegin_char=' '; 
    982         while ($pos-$wbegin_len > 0) 
    983         { 
    984           if (! is_word_char($transliterated_tag[$pos-$wbegin_len-1]) ) 
    985           { 
    986             $wbegin_char = $transliterated_tag[$pos-$wbegin_len-1]; 
    987             break; 
    988           } 
    989           $wbegin_len++; 
    990         } 
    991  
    992         // search end of word 
    993         $wend_len=0; $wend_char=' '; 
    994         while ($pos+$token_len+$wend_len < strlen($transliterated_tag)) 
    995         { 
    996           if (! is_word_char($transliterated_tag[$pos+$token_len+$wend_len]) ) 
    997           { 
    998             $wend_char = $transliterated_tag[$pos+$token_len+$wend_len]; 
    999             break; 
    1000           } 
    1001           $wend_len++; 
    1002         } 
    1003  
    1004         $this_score = 0; 
    1005         if ( ($token_modifiers[$i]&QST_WILDCARD)==0 ) 
    1006         {// no wildcard begin or end 
    1007           if ($token_len <= 2) 
    1008           {// search for 1 or 2 characters must match exactly to avoid retrieving too much data 
    1009             if ($wbegin_len==0 && $wend_len==0 && !is_odd_wbreak_begin($wbegin_char) && !is_odd_wbreak_end($wend_char) ) 
    1010               $this_score = 1; 
    1011           } 
    1012           elseif ($token_len == 3) 
    1013           { 
    1014             if ($wbegin_len==0) 
    1015               $this_score = $token_len / ($token_len + $wend_len); 
    1016           } 
    1017           else 
    1018           { 
    1019             $this_score = $token_len / ($token_len + 1.1 * $wbegin_len + 0.9 * $wend_len); 
    1020           } 
    1021         } 
    1022  
    1023         if ($this_score>0) 
    1024           $match = max($match, $this_score ); 
    1025         $pos++; 
    1026       } 
    1027  
    1028       if ($match) 
    1029       { 
    1030         $tag_id = (int)$tag['id']; 
    1031         $all_tags[$tag_id] = $tag; 
    1032         $token_tag_ids[$i][] = $tag_id; 
    1033         $token_tag_scores[$i][] = $match; 
    1034       } 
    1035     } 
    1036   } 
    1037  
    1038   // process tags 
    1039   $not_tag_ids = array(); 
    1040   for ($i=0; $i<count($tokens); $i++) 
    1041   { 
    1042     array_multisort($token_tag_scores[$i], SORT_DESC|SORT_NUMERIC, $token_tag_ids[$i]); 
    1043     $is_not = $token_modifiers[$i]&QST_NOT; 
    1044     $counter = 0; 
    1045  
    1046     for ($j=0; $j<count($token_tag_scores[$i]); $j++) 
    1047     { 
    1048       if ($is_not) 
    1049       { 
    1050         if ($token_tag_scores[$i][$j] < 0.8 || 
    1051               ($j>0 && $token_tag_scores[$i][$j] < $token_tag_scores[$i][0]) ) 
    1052         { 
    1053           array_splice($token_tag_scores[$i], $j); 
    1054           array_splice($token_tag_ids[$i], $j); 
    1055         } 
    1056       } 
    1057       else 
    1058       { 
    1059         $tag_id = $token_tag_ids[$i][$j]; 
    1060         $counter += $all_tags[$tag_id]['counter']; 
    1061         if ( $j>0 && ( 
    1062           ($counter > 100 && $token_tag_scores[$i][0] > $token_tag_scores[$i][$j]) // "many" images in previous tags and starting from this tag is less relevant 
    1063           || ($token_tag_scores[$i][0]==1 && $token_tag_scores[$i][$j]<0.8) 
    1064           || ($token_tag_scores[$i][0]>0.8 && $token_tag_scores[$i][$j]<0.5) 
    1065           )) 
    1066         {// we remove this tag from the results, but we still leave it in all_tags list so that if we are wrong, the user chooses it 
    1067           array_splice($token_tag_ids[$i], $j); 
    1068           array_splice($token_tag_scores[$i], $j); 
    1069           break; 
    1070         } 
    1071       } 
    1072     } 
    1073  
    1074     if ($is_not) 
    1075     { 
    1076       $not_tag_ids = array_merge($not_tag_ids, $token_tag_ids[$i]); 
    1077     } 
    1078   } 
    1079  
    1080   $all_tags = array_diff_key($all_tags, array_flip($not_tag_ids)); 
    1081   usort($all_tags, 'tag_alpha_compare'); 
    1082   foreach ( $all_tags as &$tag ) 
    1083   { 
    1084     $tag['name'] = trigger_event('render_tag_name', $tag['name'], $tag); 
    1085   } 
    1086   $qsr->all_tags = $all_tags; 
    1087  
    1088   $qsr->tag_ids = $token_tag_ids; 
    1089   $qsr->tag_iids = array_fill(0, count($tokens), array() ); 
    1090  
    1091   for ($i=0; $i<count($tokens); $i++) 
     920  for ($i=0; $i<count($expr->stokens); $i++) 
     921  { 
     922    $token = $expr->stokens[$i]; 
     923    if (isset($token->scope) && 'tag' != $token->scope->id) 
     924      continue; 
     925    if (empty($token->term)) 
     926      continue; 
     927 
     928    $clauses = qsearch_get_text_token_search_sql( $token, array('name')); 
     929    $query = 'SELECT * FROM '.TAGS_TABLE.' 
     930WHERE ('. implode("\n OR ",$clauses) .')'; 
     931    $result = pwg_query($query); 
     932    while ($tag = pwg_db_fetch_assoc($result)) 
     933    { 
     934      $token_tag_ids[$i][] = $tag['id']; 
     935      $all_tags[$tag['id']] = $tag; 
     936    } 
     937  } 
     938 
     939  // check adjacent short words 
     940  for ($i=0; $i<count($expr->stokens)-1; $i++) 
     941  { 
     942    if ( (strlen($expr->stokens[$i])<=3 || strlen($expr->stokens[$i+1])<=3) 
     943      && (($expr->stoken_modifiers[$i] & (QST_QUOTED|QST_WILDCARD)) == 0) 
     944      && (($expr->stoken_modifiers[$i+1] & (QST_BREAK|QST_QUOTED|QST_WILDCARD)) == 0) ) 
     945    { 
     946      $common = array_intersect( $token_tag_ids[$i], $token_tag_ids[$i+1] ); 
     947      if (count($common)) 
     948      { 
     949        $token_tag_ids[$i] = $token_tag_ids[$i+1] = $common; 
     950      } 
     951    } 
     952  } 
     953 
     954  // get images 
     955  $positive_ids = $not_ids = array(); 
     956  for ($i=0; $i<count($expr->stokens); $i++) 
    1092957  { 
    1093958    $tag_ids = $token_tag_ids[$i]; 
     959    $token = $expr->stokens[$i]; 
    1094960 
    1095961    if (!empty($tag_ids)) 
     
    1100966  GROUP BY image_id'; 
    1101967      $qsr->tag_iids[$i] = query2array($query, null, 'image_id'); 
    1102     } 
    1103     elseif (isset($tokens[$i]->scope) && 'tag' == $tokens[$i]->scope->id && strlen($token->term)==0) 
     968      if ($expr->stoken_modifiers[$i]&QST_NOT) 
     969        $not_ids = array_merge($not_ids, $tag_ids); 
     970      else 
     971        $positive_ids = array_merge($positive_ids, $tag_ids); 
     972    } 
     973    elseif (isset($token->scope) && 'tag' == $token->scope->id && strlen($token->term)==0) 
    1104974    { 
    1105975      if ($tokens[$i]->modifier & QST_WILDCARD) 
     
    1113983    } 
    1114984  } 
    1115 } 
     985 
     986  $all_tags = array_intersect_key($all_tags, array_flip( array_diff($positive_ids, $not_ids) ) ); 
     987  usort($all_tags, 'tag_alpha_compare'); 
     988  foreach ( $all_tags as &$tag ) 
     989  { 
     990    $tag['name'] = trigger_event('render_tag_name', $tag['name'], $tag); 
     991  } 
     992  $qsr->all_tags = $all_tags; 
     993  $qsr->tag_ids = $token_tag_ids; 
     994} 
     995 
    1116996 
    1117997 
     
    12601140  { 
    12611141    $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' 
     1142      .' modifier:'.dechex($expression->stoken_modifiers[$i]) 
    12621143      .( !empty($expression->stokens[$i]->variants) ? ' variants: '.implode(', ',$expression->stokens[$i]->variants): ''); 
    12631144  } 
Note: See TracChangeset for help on using the changeset viewer.