- Timestamp:
- Mar 22, 2014, 2:03:45 PM (11 years ago)
- Location:
- trunk
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/include/functions_search.inc.php
r27868 r27882 306 306 */ 307 307 308 /** Represents a single word or quoted phrase to be searched.*/ 308 309 class QSingleToken 309 310 { 310 311 var $is_single = true; 311 var $token; 312 var $token; /* the actual word/phrase string*/ 312 313 var $idx; 313 314 … … 318 319 } 319 320 321 /** Represents an expression of several words or sub expressions to be searched.*/ 320 322 class QMultiToken 321 323 { 322 324 var $is_single = false; 323 var $tokens = array(); 324 var $token_modifiers = array(); 325 var $tokens = array(); // the actual array of QSingleToken or QMultiToken 326 var $token_modifiers = array(); // modifiers (OR,NOT,...) for every token 325 327 326 328 function __toString() … … 359 361 } 360 362 361 function push(&$token, &$modifier)363 private function push(&$token, &$modifier) 362 364 { 363 365 $this->tokens[] = new QSingleToken($token); … … 367 369 } 368 370 371 /** 372 * Parses the input query string by tokenizing the input, generating the modifiers (and/or/not/quotation/wildcards...). 373 * Recursivity occurs when parsing () 374 * @param string $q the actual query to be parsed 375 * @param int $qi the character index in $q where to start parsing 376 * @param int $level the depth from root in the tree (number of opened and unclosed opening brackets) 377 */ 369 378 protected function parse_expression($q, &$qi, $level) 370 379 { … … 487 496 } 488 497 } 498 499 private static function priority($modifier) 500 { 501 return $modifier & QST_OR ? 0 :1; 502 } 503 504 /* because evaluations occur left to right, we ensure that 'a OR b c d' is interpreted as 'a OR (b c d)'*/ 505 protected function check_operator_priority() 506 { 507 for ($i=0; $i<count($this->tokens); $i++) 508 { 509 if (!$this->tokens[$i]->is_single) 510 $this->tokens[$i]->check_operator_priority(); 511 if ($i==1) 512 $crt_prio = self::priority($this->token_modifiers[$i]); 513 if ($i<=1) 514 continue; 515 $prio = self::priority($this->token_modifiers[$i]); 516 if ($prio > $crt_prio) 517 {// e.g. 'a OR b c d' i=2, operator(c)=AND -> prio(AND) > prio(OR) = operator(b) 518 $term_count = 2; // at least b and c to be regrouped 519 for ($j=$i+1; $j<count($this->tokens); $j++) 520 { 521 if (self::priority($this->token_modifiers[$j]) >= $prio) 522 $term_count++; // also take d 523 else 524 break; 525 } 526 527 $i--; // move pointer to b 528 // crate sub expression (b c d) 529 $sub = new QMultiToken; 530 $sub->tokens = array_splice($this->tokens, $i, $term_count); 531 $sub->token_modifiers = array_splice($this->token_modifiers, $i, $term_count); 532 533 // rewrite ourseleves as a (b c d) 534 array_splice($this->tokens, $i, 0, array($sub)); 535 array_splice($this->token_modifiers, $i, 0, array($sub->token_modifiers[0]&QST_OR)); 536 $sub->token_modifiers[0] &= ~QST_OR; 537 538 $sub->check_operator_priority(); 539 } 540 else 541 $crt_prio = $prio; 542 } 543 } 489 544 } 490 545 … … 498 553 $i = 0; 499 554 $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 modifier555 //manipulate the tree so that 'a OR b c' is the same as 'b c OR a' 556 $this->check_operator_priority(); 557 $this->build_single_tokens($this, 0); 558 } 559 560 private function build_single_tokens(QMultiToken $expr, $this_is_not) 561 { 507 562 for ($i=0; $i<count($expr->tokens); $i++) 508 563 { 509 564 $token = $expr->tokens[$i]; 565 $crt_is_not = ($expr->token_modifiers[$i] ^ $this_is_not) & QST_NOT; // no negation OR double negation -> no negation; 566 510 567 if ($token->is_single) 511 568 { 512 569 $token->idx = count($this->stokens); 513 570 $this->stokens[] = $token->token; 514 $this->stoken_modifiers[] = $expr->token_modifiers[$i]; 571 572 $modifier = $expr->token_modifiers[$i]; 573 if ($crt_is_not) 574 $modifier |= QST_NOT; 575 else 576 $modifier &= ~QST_NOT; 577 $this->stoken_modifiers[] = $modifier; 515 578 } 516 579 else 517 $this->build_single_tokens($token); 518 } 519 } 520 } 521 580 $this->build_single_tokens($token, $crt_is_not); 581 } 582 } 583 } 584 585 /** 586 Structure of results being filled from different tables 587 */ 522 588 class QResults 523 589 { … … 730 796 731 797 732 function qsearch_eval(QExpression $expr, QResults $qsr, QMultiToken $crt_expr) 733 { 798 function qsearch_eval(QMultiToken $expr, QResults $qsr, &$qualifies, &$ignored_terms) 799 { 800 $qualifies = false; // until we find at least one positive term 801 $ignored_terms = array(); 802 734 803 $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]) ); 804 805 for ($i=0; $i<count($expr->tokens); $i++) 806 { 807 $crt = $expr->tokens[$i]; 808 if ($crt->is_single) 809 { 810 $crt_ids = $qsr->iids[$crt->idx] = array_unique( array_merge($qsr->images_iids[$crt->idx], $qsr->tag_iids[$crt->idx]) ); 811 $crt_qualifies = count($crt_ids)>0 || count($qsr->tag_ids[$crt->idx])>0; 812 $crt_ignored_terms = $crt_qualifies ? array() : array($crt->token); 742 813 } 743 814 else 744 $crt_ids = qsearch_eval($ expr, $qsr, $current);745 $modifier = $crt_expr->token_modifiers[$i]; 746 815 $crt_ids = qsearch_eval($crt, $qsr, $crt_qualifies, $crt_ignored_terms); 816 817 $modifier = $expr->token_modifiers[$i]; 747 818 if ($modifier & QST_NOT) 748 819 $not_ids = array_unique( array_merge($not_ids, $crt_ids)); 749 820 else 750 821 { 822 $ignored_terms = array_merge($ignored_terms, $crt_ignored_terms); 751 823 if ($modifier & QST_OR) 824 { 752 825 $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) 826 $qualifies |= $crt_qualifies; 827 } 828 elseif ($crt_qualifies) 829 { 830 if ($qualifies) 831 $ids = array_intersect($ids, $crt_ids); 832 else 761 833 $ids = $crt_ids; 762 else 763 $ids = array_intersect($ids, $crt_ids); 764 $first= false; 834 $qualifies = true; 765 835 } 766 836 } … … 779 849 * 'items' => array of matching images 780 850 * 'qs' => array( 851 * 'unmatched_terms' => array of terms from the input string that were not matched 781 852 * 'matching_tags' => array of matching tags 782 853 * 'matching_cats' => array of matching categories … … 792 863 function get_quick_search_results($q, $super_order_by, $images_where='') 793 864 { 794 global $ user, $conf;795 865 global $conf; 866 //@TODO: maybe cache for 10 minutes the result set to avoid many expensive sql calls when navigating the pictures 796 867 $search_results = 797 868 array( … … 808 879 //var_export($qsr->all_tags); 809 880 810 $ids = qsearch_eval($expression, $qsr, $ expression);881 $ids = qsearch_eval($expression, $qsr, $tmp, $search_results['qs']['unmatched_terms']); 811 882 812 883 $debug[] = "<!--\nparsed: ".$expression; -
trunk/index.php
r26461 r27882 241 241 $tag['URL'] = make_index_url(array('tags'=>array($tag))); 242 242 $template->append( 'tag_search_results', $tag); 243 } 244 245 if (empty($page['items'])) 246 { 247 $template->append( 'no_search_results', $page['qsearch_details']['q']); 248 } 249 elseif (!empty($page['qsearch_details']['unmatched_terms'])) 250 { 251 $template->assign( 'no_search_results', $page['qsearch_details']['unmatched_terms']); 243 252 } 244 253 } -
trunk/language/en_UK/common.lang.php
r26640 r27882 419 419 $lang['%d photos per page'] = '%d photos per page'; 420 420 $lang['Theme'] = 'Theme'; 421 $lang['No results for'] = 'No results for'; 421 422 ?> -
trunk/themes/default/template/index.tpl
r24802 r27882 122 122 {if !empty($PLUGIN_INDEX_CONTENT_BEGIN)}{$PLUGIN_INDEX_CONTENT_BEGIN}{/if} 123 123 124 {if !empty($no_search_results)} 125 <p class="search_results">{'No results for'|@translate} : 126 <em><strong> 127 {foreach $no_search_results as $res} 128 {if !$res@first} — {/if} 129 {$res} 130 {/foreach} 131 </strong></em> 132 </p> 133 {/if} 134 124 135 {if !empty($category_search_results)} 125 < div class="category_search_results">{'Album results for'|@translate} <strong>{$QUERY_SEARCH}</strong> :136 <p class="search_results">{'Album results for'|@translate} <strong>{$QUERY_SEARCH}</strong> : 126 137 <em><strong> 127 138 {foreach from=$category_search_results item=res name=res_loop} … … 130 141 {/foreach} 131 142 </strong></em> 132 </ div>143 </p> 133 144 {/if} 134 145 135 146 {if !empty($tag_search_results)} 136 < div class="tag_search_results">{'Tag results for'|@translate} <strong>{$QUERY_SEARCH}</strong> :147 <p class="search_results">{'Tag results for'|@translate} <strong>{$QUERY_SEARCH}</strong> : 137 148 <em><strong> 138 149 {foreach from=$tag_search_results item=tag name=res_loop} … … 140 151 {/foreach} 141 152 </strong></em> 142 </ div>153 </p> 143 154 {/if} 144 155 -
trunk/themes/default/theme.css
r25746 r27882 115 115 116 116 /* category and tag results paragraphs on a quick search */ 117 . category_search_results, .tag_search_results {117 .search_results { 118 118 font-size: 16px; 119 119 margin: 10px 16px;
Note: See TracChangeset
for help on using the changeset viewer.