From 8b98ceb92c8fba72315d28c3b7664f481547bf24 Mon Sep 17 00:00:00 2001
From: Jean-Philippe Lang <jp_lang@yahoo.fr>
Date: Sat, 10 Mar 2007 13:32:04 +0000
Subject: [PATCH] improved search engine * it's now possible to search for
 multiple words ("all words" or "one of the words" options) * added a fixed
 limit for result count

git-svn-id: http://redmine.rubyforge.org/svn/trunk@321 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 app/controllers/projects_controller.rb | 24 ++++++++++++++++--------
 app/helpers/projects_helper.rb         | 12 +++++++++---
 app/views/projects/search.rhtml        | 17 +++++++++--------
 lang/de.yml                            |  1 +
 lang/en.yml                            |  1 +
 lang/es.yml                            |  1 +
 lang/fr.yml                            |  1 +
 lang/ja.yml                            |  1 +
 public/stylesheets/application.css     |  2 +-
 9 files changed, 40 insertions(+), 20 deletions(-)

diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index e2ddd3df5..4a957a9cb 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -540,16 +540,24 @@ class ProjectsController < ApplicationController
   end
   
   def search
-    @token = params[:token]
+    @question = params[:q] || ""
+    @question.strip!
+    @all_words = params[:all_words] || (params[:submit] ? false : true)
     @scope = params[:scope] || (params[:submit] ? [] : %w(issues news documents) )
-
-    if @token and @token.length > 2
-      @token.strip!
-      like_token = "%#{@token}%"
+    if !@question.empty?
+      # tokens must be at least 3 character long
+      @tokens = @question.split.uniq.select {|w| w.length > 2 }
+      # no more than 5 tokens to search for
+      @tokens.slice! 5..-1 if @tokens.size > 5
+      # strings used in sql like statement
+      like_tokens = @tokens.collect {|w| "%#{w}%"}
+      operator = @all_words ? " AND " : " OR "
+      limit = 10
       @results = []
-      @results += @project.issues.find(:all, :include => :author, :conditions => ["issues.subject like ? or issues.description like ?", like_token, like_token] ) if @scope.include? 'issues'
-      @results += @project.news.find(:all, :conditions => ["news.title like ? or news.description like ?", like_token, like_token], :include => :author ) if @scope.include? 'news'
-      @results += @project.documents.find(:all, :conditions => ["title like ? or description like ?", like_token, like_token] ) if @scope.include? 'documents'
+      @results += @project.issues.find(:all, :limit => limit, :include => :author, :conditions => [ (["(LOWER(issues.subject) like ? OR LOWER(issues.description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'issues'
+      @results += @project.news.find(:all, :limit => limit, :conditions => [ (["(LOWER(news.title) like ? OR LOWER(news.description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort], :include => :author ) if @scope.include? 'news'
+      @results += @project.documents.find(:all, :limit => limit, :conditions => [ (["(LOWER(title) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'documents'
+      @question = @tokens.join(" ")
     end
   end
   
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a6c033d0f..694dd9124 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -16,8 +16,14 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 module ProjectsHelper
-  def result_overview(text, token)
-    match = excerpt(text, token)
-    match ? highlight(match, token) : truncate(text, 150)
+
+  def highlight_tokens(text, tokens)
+    return text unless tokens && !tokens.empty?
+    regexp = Regexp.new "(#{tokens.join('|')})", Regexp::IGNORECASE    
+    result = ''
+    text.split(regexp).each_with_index do |words, i|
+      result << (i.even? ? (words.length > 100 ? "#{words[0..44]} ... #{words[-45..-1]}" : words) : content_tag('span', words, :class => 'highlight'))
+    end
+    result
   end
 end
diff --git a/app/views/projects/search.rhtml b/app/views/projects/search.rhtml
index df95ee3ae..e699d8731 100644
--- a/app/views/projects/search.rhtml
+++ b/app/views/projects/search.rhtml
@@ -2,10 +2,11 @@
 
 <div class="box">
 <% form_tag({:action => 'search', :id => @project}, :method => :get) do %>
-<p><%= text_field_tag 'token', @token, :size => 30 %>
+<p><%= text_field_tag 'q', @question, :size => 30 %>
 <%= check_box_tag 'scope[]', 'issues', (@scope.include? 'issues') %> <label><%= l(:label_issue_plural) %></label>
 <%= check_box_tag 'scope[]', 'news', (@scope.include? 'news') %> <label><%= l(:label_news_plural) %></label>
-<%= check_box_tag 'scope[]', 'documents', (@scope.include? 'documents') %> <label><%= l(:label_document_plural) %></label></p>
+<%= check_box_tag 'scope[]', 'documents', (@scope.include? 'documents') %> <label><%= l(:label_document_plural) %></label><br />
+<%= check_box_tag 'all_words', 1, @all_words %> <%= l(:label_all_words) %></p>
 <%= submit_tag l(:button_submit), :name => 'submit' %>
 <% end %>
 </div>
@@ -16,16 +17,16 @@
       <% @results.each do |e| %>
         <li><p>
         <% if e.is_a? Issue %>
-            <%= link_to "#{e.tracker.name} ##{e.id}", :controller => 'issues', :action => 'show', :id => e %>: <%= highlight(h(e.subject), @token) %><br />
-            <%= result_overview(e.description, @token) %><br />
+            <%= link_to "#{e.tracker.name} ##{e.id}", :controller => 'issues', :action => 'show', :id => e %>: <%= highlight_tokens(h(e.subject), @tokens) %><br />
+            <%= highlight_tokens(e.description, @tokens) %><br />
             <i><%= e.author.name %>, <%= format_time(e.created_on) %></i>
         <% elsif e.is_a? News %>
-            <%=l(:label_news)%>: <%= link_to highlight(h(e.title), @token), :controller => 'news', :action => 'show', :id => e %><br />
-            <%= result_overview(e.description, @token) %><br />
+            <%=l(:label_news)%>: <%= link_to highlight_tokens(h(e.title), @tokens), :controller => 'news', :action => 'show', :id => e %><br />
+            <%= highlight_tokens(e.description, @tokens) %><br />
             <i><%= e.author.name %>, <%= format_time(e.created_on) %></i>
         <% elsif e.is_a? Document %>
-            <%=l(:label_document)%>: <%= link_to highlight(h(e.title), @token), :controller => 'documents', :action => 'show', :id => e %><br />
-            <%= result_overview(e.description, @token) %><br />
+            <%=l(:label_document)%>: <%= link_to highlight_tokens(h(e.title), @tokens), :controller => 'documents', :action => 'show', :id => e %><br />
+            <%= highlight_tokens(e.description, @tokens) %><br />
             <i><%= format_time(e.created_on) %></i>
         <% end %>
         </p></li>  
diff --git a/lang/de.yml b/lang/de.yml
index 69cc23df7..cb253b383 100644
--- a/lang/de.yml
+++ b/lang/de.yml
@@ -321,6 +321,7 @@ label_roadmap: Roadmap
 label_search: Suche
 label_result: %d Resultat
 label_result_plural: %d Resultate
+label_all_words: Alle Wörter
 
 button_login: Einloggen
 button_submit: Einreichen
diff --git a/lang/en.yml b/lang/en.yml
index c8026e629..20a43e564 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -321,6 +321,7 @@ label_roadmap: Roadmap
 label_search: Search
 label_result: %d result
 label_result_plural: %d results
+label_all_words: All words
 
 button_login: Login
 button_submit: Submit
diff --git a/lang/es.yml b/lang/es.yml
index 79c96adf4..3004470e6 100644
--- a/lang/es.yml
+++ b/lang/es.yml
@@ -321,6 +321,7 @@ label_roadmap: Roadmap
 label_search: Búsqueda
 label_result: %d resultado
 label_result_plural: %d resultados
+label_all_words: Todas las palabras
 
 button_login: Conexión
 button_submit: Someter
diff --git a/lang/fr.yml b/lang/fr.yml
index 90e2c6628..fba9cb9fb 100644
--- a/lang/fr.yml
+++ b/lang/fr.yml
@@ -321,6 +321,7 @@ label_roadmap: Roadmap
 label_search: Recherche
 label_result: %d résultat
 label_result_plural: %d résultats
+label_all_words: Tous les mots
 
 button_login: Connexion
 button_submit: Soumettre
diff --git a/lang/ja.yml b/lang/ja.yml
index b473f4c0a..86dcd7f3f 100644
--- a/lang/ja.yml
+++ b/lang/ja.yml
@@ -322,6 +322,7 @@ label_roadmap: ロードマップ
 label_search: 検索
 label_result: %d 件の結果
 label_result_plural: %d 件の結果
+label_all_words: すべての単語
 
 button_login: ログイン
 button_submit: 変更
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 732349adf..40997af97 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -243,7 +243,7 @@ legend {color: #505050;}
 hr { border:0; border-top: dotted 1px #fff; border-bottom: dotted 1px #c0c0c0; }
 table p {margin:0; padding:0;}
 
-strong.highlight { background-color: #FCFD8D;}
+.highlight { background-color: #FCFD8D;}
 
 div.square {
  border: 1px solid #999;
-- 
GitLab