##// END OF EJS Templates
Refactor: Move method to Query model...
Eric Davis -
r3570:bf33b57aa403
parent child
Show More
@@ -1,81 +1,77
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueriesController < ApplicationController
18 class QueriesController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20 before_filter :find_query, :except => :new
20 before_filter :find_query, :except => :new
21 before_filter :find_optional_project, :only => :new
21 before_filter :find_optional_project, :only => :new
22
22
23 def new
23 def new
24 @query = Query.new(params[:query])
24 @query = Query.new(params[:query])
25 @query.project = params[:query_is_for_all] ? nil : @project
25 @query.project = params[:query_is_for_all] ? nil : @project
26 @query.user = User.current
26 @query.user = User.current
27 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
27 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
28 @query.column_names = nil if params[:default_columns]
28 @query.column_names = nil if params[:default_columns]
29
29
30 params[:fields].each do |field|
30 @query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields]
31 @query.add_filter(field, params[:operators][field], params[:values][field])
32 end if params[:fields]
33 @query.group_by ||= params[:group_by]
31 @query.group_by ||= params[:group_by]
34
32
35 if request.post? && params[:confirm] && @query.save
33 if request.post? && params[:confirm] && @query.save
36 flash[:notice] = l(:notice_successful_create)
34 flash[:notice] = l(:notice_successful_create)
37 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
35 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
38 return
36 return
39 end
37 end
40 render :layout => false if request.xhr?
38 render :layout => false if request.xhr?
41 end
39 end
42
40
43 def edit
41 def edit
44 if request.post?
42 if request.post?
45 @query.filters = {}
43 @query.filters = {}
46 params[:fields].each do |field|
44 @query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields]
47 @query.add_filter(field, params[:operators][field], params[:values][field])
48 end if params[:fields]
49 @query.attributes = params[:query]
45 @query.attributes = params[:query]
50 @query.project = nil if params[:query_is_for_all]
46 @query.project = nil if params[:query_is_for_all]
51 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
47 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
52 @query.column_names = nil if params[:default_columns]
48 @query.column_names = nil if params[:default_columns]
53
49
54 if @query.save
50 if @query.save
55 flash[:notice] = l(:notice_successful_update)
51 flash[:notice] = l(:notice_successful_update)
56 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
52 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
57 end
53 end
58 end
54 end
59 end
55 end
60
56
61 def destroy
57 def destroy
62 @query.destroy if request.post?
58 @query.destroy if request.post?
63 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
59 redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
64 end
60 end
65
61
66 private
62 private
67 def find_query
63 def find_query
68 @query = Query.find(params[:id])
64 @query = Query.find(params[:id])
69 @project = @query.project
65 @project = @query.project
70 render_403 unless @query.editable_by?(User.current)
66 render_403 unless @query.editable_by?(User.current)
71 rescue ActiveRecord::RecordNotFound
67 rescue ActiveRecord::RecordNotFound
72 render_404
68 render_404
73 end
69 end
74
70
75 def find_optional_project
71 def find_optional_project
76 @project = Project.find(params[:project_id]) if params[:project_id]
72 @project = Project.find(params[:project_id]) if params[:project_id]
77 render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
73 render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
78 rescue ActiveRecord::RecordNotFound
74 rescue ActiveRecord::RecordNotFound
79 render_404
75 render_404
80 end
76 end
81 end
77 end
@@ -1,575 +1,582
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @caption_key = options[:caption] || "field_#{name}"
30 @caption_key = options[:caption] || "field_#{name}"
31 end
31 end
32
32
33 def caption
33 def caption
34 l(@caption_key)
34 l(@caption_key)
35 end
35 end
36
36
37 # Returns true if the column is sortable, otherwise false
37 # Returns true if the column is sortable, otherwise false
38 def sortable?
38 def sortable?
39 !sortable.nil?
39 !sortable.nil?
40 end
40 end
41
41
42 def value(issue)
42 def value(issue)
43 issue.send name
43 issue.send name
44 end
44 end
45 end
45 end
46
46
47 class QueryCustomFieldColumn < QueryColumn
47 class QueryCustomFieldColumn < QueryColumn
48
48
49 def initialize(custom_field)
49 def initialize(custom_field)
50 self.name = "cf_#{custom_field.id}".to_sym
50 self.name = "cf_#{custom_field.id}".to_sym
51 self.sortable = custom_field.order_statement || false
51 self.sortable = custom_field.order_statement || false
52 if %w(list date bool int).include?(custom_field.field_format)
52 if %w(list date bool int).include?(custom_field.field_format)
53 self.groupable = custom_field.order_statement
53 self.groupable = custom_field.order_statement
54 end
54 end
55 self.groupable ||= false
55 self.groupable ||= false
56 @cf = custom_field
56 @cf = custom_field
57 end
57 end
58
58
59 def caption
59 def caption
60 @cf.name
60 @cf.name
61 end
61 end
62
62
63 def custom_field
63 def custom_field
64 @cf
64 @cf
65 end
65 end
66
66
67 def value(issue)
67 def value(issue)
68 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
68 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
69 cv && @cf.cast_value(cv.value)
69 cv && @cf.cast_value(cv.value)
70 end
70 end
71 end
71 end
72
72
73 class Query < ActiveRecord::Base
73 class Query < ActiveRecord::Base
74 class StatementInvalid < ::ActiveRecord::StatementInvalid
74 class StatementInvalid < ::ActiveRecord::StatementInvalid
75 end
75 end
76
76
77 belongs_to :project
77 belongs_to :project
78 belongs_to :user
78 belongs_to :user
79 serialize :filters
79 serialize :filters
80 serialize :column_names
80 serialize :column_names
81 serialize :sort_criteria, Array
81 serialize :sort_criteria, Array
82
82
83 attr_protected :project_id, :user_id
83 attr_protected :project_id, :user_id
84
84
85 validates_presence_of :name, :on => :save
85 validates_presence_of :name, :on => :save
86 validates_length_of :name, :maximum => 255
86 validates_length_of :name, :maximum => 255
87
87
88 @@operators = { "=" => :label_equals,
88 @@operators = { "=" => :label_equals,
89 "!" => :label_not_equals,
89 "!" => :label_not_equals,
90 "o" => :label_open_issues,
90 "o" => :label_open_issues,
91 "c" => :label_closed_issues,
91 "c" => :label_closed_issues,
92 "!*" => :label_none,
92 "!*" => :label_none,
93 "*" => :label_all,
93 "*" => :label_all,
94 ">=" => :label_greater_or_equal,
94 ">=" => :label_greater_or_equal,
95 "<=" => :label_less_or_equal,
95 "<=" => :label_less_or_equal,
96 "<t+" => :label_in_less_than,
96 "<t+" => :label_in_less_than,
97 ">t+" => :label_in_more_than,
97 ">t+" => :label_in_more_than,
98 "t+" => :label_in,
98 "t+" => :label_in,
99 "t" => :label_today,
99 "t" => :label_today,
100 "w" => :label_this_week,
100 "w" => :label_this_week,
101 ">t-" => :label_less_than_ago,
101 ">t-" => :label_less_than_ago,
102 "<t-" => :label_more_than_ago,
102 "<t-" => :label_more_than_ago,
103 "t-" => :label_ago,
103 "t-" => :label_ago,
104 "~" => :label_contains,
104 "~" => :label_contains,
105 "!~" => :label_not_contains }
105 "!~" => :label_not_contains }
106
106
107 cattr_reader :operators
107 cattr_reader :operators
108
108
109 @@operators_by_filter_type = { :list => [ "=", "!" ],
109 @@operators_by_filter_type = { :list => [ "=", "!" ],
110 :list_status => [ "o", "=", "!", "c", "*" ],
110 :list_status => [ "o", "=", "!", "c", "*" ],
111 :list_optional => [ "=", "!", "!*", "*" ],
111 :list_optional => [ "=", "!", "!*", "*" ],
112 :list_subprojects => [ "*", "!*", "=" ],
112 :list_subprojects => [ "*", "!*", "=" ],
113 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
113 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
114 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
114 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
115 :string => [ "=", "~", "!", "!~" ],
115 :string => [ "=", "~", "!", "!~" ],
116 :text => [ "~", "!~" ],
116 :text => [ "~", "!~" ],
117 :integer => [ "=", ">=", "<=", "!*", "*" ] }
117 :integer => [ "=", ">=", "<=", "!*", "*" ] }
118
118
119 cattr_reader :operators_by_filter_type
119 cattr_reader :operators_by_filter_type
120
120
121 @@available_columns = [
121 @@available_columns = [
122 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
122 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
123 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
123 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
124 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
124 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
125 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
125 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
126 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
126 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
127 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
127 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
128 QueryColumn.new(:author),
128 QueryColumn.new(:author),
129 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
129 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
130 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
130 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
131 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
131 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
132 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
132 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
133 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
133 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
134 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
134 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
135 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
135 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
136 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
136 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
137 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
137 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
138 ]
138 ]
139 cattr_reader :available_columns
139 cattr_reader :available_columns
140
140
141 def initialize(attributes = nil)
141 def initialize(attributes = nil)
142 super attributes
142 super attributes
143 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
143 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
144 end
144 end
145
145
146 def after_initialize
146 def after_initialize
147 # Store the fact that project is nil (used in #editable_by?)
147 # Store the fact that project is nil (used in #editable_by?)
148 @is_for_all = project.nil?
148 @is_for_all = project.nil?
149 end
149 end
150
150
151 def validate
151 def validate
152 filters.each_key do |field|
152 filters.each_key do |field|
153 errors.add label_for(field), :blank unless
153 errors.add label_for(field), :blank unless
154 # filter requires one or more values
154 # filter requires one or more values
155 (values_for(field) and !values_for(field).first.blank?) or
155 (values_for(field) and !values_for(field).first.blank?) or
156 # filter doesn't require any value
156 # filter doesn't require any value
157 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
157 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
158 end if filters
158 end if filters
159 end
159 end
160
160
161 def editable_by?(user)
161 def editable_by?(user)
162 return false unless user
162 return false unless user
163 # Admin can edit them all and regular users can edit their private queries
163 # Admin can edit them all and regular users can edit their private queries
164 return true if user.admin? || (!is_public && self.user_id == user.id)
164 return true if user.admin? || (!is_public && self.user_id == user.id)
165 # Members can not edit public queries that are for all project (only admin is allowed to)
165 # Members can not edit public queries that are for all project (only admin is allowed to)
166 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
166 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
167 end
167 end
168
168
169 def available_filters
169 def available_filters
170 return @available_filters if @available_filters
170 return @available_filters if @available_filters
171
171
172 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
172 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
173
173
174 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
174 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
175 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
175 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
176 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
176 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
177 "subject" => { :type => :text, :order => 8 },
177 "subject" => { :type => :text, :order => 8 },
178 "created_on" => { :type => :date_past, :order => 9 },
178 "created_on" => { :type => :date_past, :order => 9 },
179 "updated_on" => { :type => :date_past, :order => 10 },
179 "updated_on" => { :type => :date_past, :order => 10 },
180 "start_date" => { :type => :date, :order => 11 },
180 "start_date" => { :type => :date, :order => 11 },
181 "due_date" => { :type => :date, :order => 12 },
181 "due_date" => { :type => :date, :order => 12 },
182 "estimated_hours" => { :type => :integer, :order => 13 },
182 "estimated_hours" => { :type => :integer, :order => 13 },
183 "done_ratio" => { :type => :integer, :order => 14 }}
183 "done_ratio" => { :type => :integer, :order => 14 }}
184
184
185 user_values = []
185 user_values = []
186 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
186 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
187 if project
187 if project
188 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
188 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
189 else
189 else
190 project_ids = User.current.projects.collect(&:id)
190 project_ids = User.current.projects.collect(&:id)
191 if project_ids.any?
191 if project_ids.any?
192 # members of the user's projects
192 # members of the user's projects
193 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
193 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
194 end
194 end
195 end
195 end
196 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
196 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
197 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
197 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
198
198
199 if User.current.logged?
199 if User.current.logged?
200 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
200 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
201 end
201 end
202
202
203 if project
203 if project
204 # project specific filters
204 # project specific filters
205 unless @project.issue_categories.empty?
205 unless @project.issue_categories.empty?
206 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
206 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
207 end
207 end
208 unless @project.shared_versions.empty?
208 unless @project.shared_versions.empty?
209 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
209 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
210 end
210 end
211 unless @project.descendants.active.empty?
211 unless @project.descendants.active.empty?
212 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
212 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
213 end
213 end
214 add_custom_fields_filters(@project.all_issue_custom_fields)
214 add_custom_fields_filters(@project.all_issue_custom_fields)
215 else
215 else
216 # global filters for cross project issue list
216 # global filters for cross project issue list
217 system_shared_versions = Version.visible.find_all_by_sharing('system')
217 system_shared_versions = Version.visible.find_all_by_sharing('system')
218 unless system_shared_versions.empty?
218 unless system_shared_versions.empty?
219 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
219 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
220 end
220 end
221 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
221 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
222 end
222 end
223 @available_filters
223 @available_filters
224 end
224 end
225
225
226 def add_filter(field, operator, values)
226 def add_filter(field, operator, values)
227 # values must be an array
227 # values must be an array
228 return unless values and values.is_a? Array # and !values.first.empty?
228 return unless values and values.is_a? Array # and !values.first.empty?
229 # check if field is defined as an available filter
229 # check if field is defined as an available filter
230 if available_filters.has_key? field
230 if available_filters.has_key? field
231 filter_options = available_filters[field]
231 filter_options = available_filters[field]
232 # check if operator is allowed for that filter
232 # check if operator is allowed for that filter
233 #if @@operators_by_filter_type[filter_options[:type]].include? operator
233 #if @@operators_by_filter_type[filter_options[:type]].include? operator
234 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
234 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
235 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
235 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
236 #end
236 #end
237 filters[field] = {:operator => operator, :values => values }
237 filters[field] = {:operator => operator, :values => values }
238 end
238 end
239 end
239 end
240
240
241 def add_short_filter(field, expression)
241 def add_short_filter(field, expression)
242 return unless expression
242 return unless expression
243 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
243 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
244 add_filter field, (parms[0] || "="), [parms[1] || ""]
244 add_filter field, (parms[0] || "="), [parms[1] || ""]
245 end
245 end
246
247 # Add multiple filters using +add_filter+
248 def add_filters(fields, operators, values)
249 fields.each do |field|
250 add_filter(field, operators[field], values[field])
251 end
252 end
246
253
247 def has_filter?(field)
254 def has_filter?(field)
248 filters and filters[field]
255 filters and filters[field]
249 end
256 end
250
257
251 def operator_for(field)
258 def operator_for(field)
252 has_filter?(field) ? filters[field][:operator] : nil
259 has_filter?(field) ? filters[field][:operator] : nil
253 end
260 end
254
261
255 def values_for(field)
262 def values_for(field)
256 has_filter?(field) ? filters[field][:values] : nil
263 has_filter?(field) ? filters[field][:values] : nil
257 end
264 end
258
265
259 def label_for(field)
266 def label_for(field)
260 label = available_filters[field][:name] if available_filters.has_key?(field)
267 label = available_filters[field][:name] if available_filters.has_key?(field)
261 label ||= field.gsub(/\_id$/, "")
268 label ||= field.gsub(/\_id$/, "")
262 end
269 end
263
270
264 def available_columns
271 def available_columns
265 return @available_columns if @available_columns
272 return @available_columns if @available_columns
266 @available_columns = Query.available_columns
273 @available_columns = Query.available_columns
267 @available_columns += (project ?
274 @available_columns += (project ?
268 project.all_issue_custom_fields :
275 project.all_issue_custom_fields :
269 IssueCustomField.find(:all)
276 IssueCustomField.find(:all)
270 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
277 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
271 end
278 end
272
279
273 # Returns an array of columns that can be used to group the results
280 # Returns an array of columns that can be used to group the results
274 def groupable_columns
281 def groupable_columns
275 available_columns.select {|c| c.groupable}
282 available_columns.select {|c| c.groupable}
276 end
283 end
277
284
278 # Returns a Hash of columns and the key for sorting
285 # Returns a Hash of columns and the key for sorting
279 def sortable_columns
286 def sortable_columns
280 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
287 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
281 h[column.name.to_s] = column.sortable
288 h[column.name.to_s] = column.sortable
282 h
289 h
283 })
290 })
284 end
291 end
285
292
286 def columns
293 def columns
287 if has_default_columns?
294 if has_default_columns?
288 available_columns.select do |c|
295 available_columns.select do |c|
289 # Adds the project column by default for cross-project lists
296 # Adds the project column by default for cross-project lists
290 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
297 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
291 end
298 end
292 else
299 else
293 # preserve the column_names order
300 # preserve the column_names order
294 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
301 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
295 end
302 end
296 end
303 end
297
304
298 def column_names=(names)
305 def column_names=(names)
299 if names
306 if names
300 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
307 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
301 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
308 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
302 # Set column_names to nil if default columns
309 # Set column_names to nil if default columns
303 if names.map(&:to_s) == Setting.issue_list_default_columns
310 if names.map(&:to_s) == Setting.issue_list_default_columns
304 names = nil
311 names = nil
305 end
312 end
306 end
313 end
307 write_attribute(:column_names, names)
314 write_attribute(:column_names, names)
308 end
315 end
309
316
310 def has_column?(column)
317 def has_column?(column)
311 column_names && column_names.include?(column.name)
318 column_names && column_names.include?(column.name)
312 end
319 end
313
320
314 def has_default_columns?
321 def has_default_columns?
315 column_names.nil? || column_names.empty?
322 column_names.nil? || column_names.empty?
316 end
323 end
317
324
318 def sort_criteria=(arg)
325 def sort_criteria=(arg)
319 c = []
326 c = []
320 if arg.is_a?(Hash)
327 if arg.is_a?(Hash)
321 arg = arg.keys.sort.collect {|k| arg[k]}
328 arg = arg.keys.sort.collect {|k| arg[k]}
322 end
329 end
323 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
330 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
324 write_attribute(:sort_criteria, c)
331 write_attribute(:sort_criteria, c)
325 end
332 end
326
333
327 def sort_criteria
334 def sort_criteria
328 read_attribute(:sort_criteria) || []
335 read_attribute(:sort_criteria) || []
329 end
336 end
330
337
331 def sort_criteria_key(arg)
338 def sort_criteria_key(arg)
332 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
339 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
333 end
340 end
334
341
335 def sort_criteria_order(arg)
342 def sort_criteria_order(arg)
336 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
343 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
337 end
344 end
338
345
339 # Returns the SQL sort order that should be prepended for grouping
346 # Returns the SQL sort order that should be prepended for grouping
340 def group_by_sort_order
347 def group_by_sort_order
341 if grouped? && (column = group_by_column)
348 if grouped? && (column = group_by_column)
342 column.sortable.is_a?(Array) ?
349 column.sortable.is_a?(Array) ?
343 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
350 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
344 "#{column.sortable} #{column.default_order}"
351 "#{column.sortable} #{column.default_order}"
345 end
352 end
346 end
353 end
347
354
348 # Returns true if the query is a grouped query
355 # Returns true if the query is a grouped query
349 def grouped?
356 def grouped?
350 !group_by.blank?
357 !group_by.blank?
351 end
358 end
352
359
353 def group_by_column
360 def group_by_column
354 groupable_columns.detect {|c| c.name.to_s == group_by}
361 groupable_columns.detect {|c| c.name.to_s == group_by}
355 end
362 end
356
363
357 def group_by_statement
364 def group_by_statement
358 group_by_column.groupable
365 group_by_column.groupable
359 end
366 end
360
367
361 def project_statement
368 def project_statement
362 project_clauses = []
369 project_clauses = []
363 if project && !@project.descendants.active.empty?
370 if project && !@project.descendants.active.empty?
364 ids = [project.id]
371 ids = [project.id]
365 if has_filter?("subproject_id")
372 if has_filter?("subproject_id")
366 case operator_for("subproject_id")
373 case operator_for("subproject_id")
367 when '='
374 when '='
368 # include the selected subprojects
375 # include the selected subprojects
369 ids += values_for("subproject_id").each(&:to_i)
376 ids += values_for("subproject_id").each(&:to_i)
370 when '!*'
377 when '!*'
371 # main project only
378 # main project only
372 else
379 else
373 # all subprojects
380 # all subprojects
374 ids += project.descendants.collect(&:id)
381 ids += project.descendants.collect(&:id)
375 end
382 end
376 elsif Setting.display_subprojects_issues?
383 elsif Setting.display_subprojects_issues?
377 ids += project.descendants.collect(&:id)
384 ids += project.descendants.collect(&:id)
378 end
385 end
379 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
386 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
380 elsif project
387 elsif project
381 project_clauses << "#{Project.table_name}.id = %d" % project.id
388 project_clauses << "#{Project.table_name}.id = %d" % project.id
382 end
389 end
383 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
390 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
384 project_clauses.join(' AND ')
391 project_clauses.join(' AND ')
385 end
392 end
386
393
387 def statement
394 def statement
388 # filters clauses
395 # filters clauses
389 filters_clauses = []
396 filters_clauses = []
390 filters.each_key do |field|
397 filters.each_key do |field|
391 next if field == "subproject_id"
398 next if field == "subproject_id"
392 v = values_for(field).clone
399 v = values_for(field).clone
393 next unless v and !v.empty?
400 next unless v and !v.empty?
394 operator = operator_for(field)
401 operator = operator_for(field)
395
402
396 # "me" value subsitution
403 # "me" value subsitution
397 if %w(assigned_to_id author_id watcher_id).include?(field)
404 if %w(assigned_to_id author_id watcher_id).include?(field)
398 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
405 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
399 end
406 end
400
407
401 sql = ''
408 sql = ''
402 if field =~ /^cf_(\d+)$/
409 if field =~ /^cf_(\d+)$/
403 # custom field
410 # custom field
404 db_table = CustomValue.table_name
411 db_table = CustomValue.table_name
405 db_field = 'value'
412 db_field = 'value'
406 is_custom_filter = true
413 is_custom_filter = true
407 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
414 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
408 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
415 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
409 elsif field == 'watcher_id'
416 elsif field == 'watcher_id'
410 db_table = Watcher.table_name
417 db_table = Watcher.table_name
411 db_field = 'user_id'
418 db_field = 'user_id'
412 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
419 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
413 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
420 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
414 else
421 else
415 # regular field
422 # regular field
416 db_table = Issue.table_name
423 db_table = Issue.table_name
417 db_field = field
424 db_field = field
418 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
425 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
419 end
426 end
420 filters_clauses << sql
427 filters_clauses << sql
421
428
422 end if filters and valid?
429 end if filters and valid?
423
430
424 (filters_clauses << project_statement).join(' AND ')
431 (filters_clauses << project_statement).join(' AND ')
425 end
432 end
426
433
427 # Returns the issue count
434 # Returns the issue count
428 def issue_count
435 def issue_count
429 Issue.count(:include => [:status, :project], :conditions => statement)
436 Issue.count(:include => [:status, :project], :conditions => statement)
430 rescue ::ActiveRecord::StatementInvalid => e
437 rescue ::ActiveRecord::StatementInvalid => e
431 raise StatementInvalid.new(e.message)
438 raise StatementInvalid.new(e.message)
432 end
439 end
433
440
434 # Returns the issue count by group or nil if query is not grouped
441 # Returns the issue count by group or nil if query is not grouped
435 def issue_count_by_group
442 def issue_count_by_group
436 r = nil
443 r = nil
437 if grouped?
444 if grouped?
438 begin
445 begin
439 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
446 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
440 r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
447 r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
441 rescue ActiveRecord::RecordNotFound
448 rescue ActiveRecord::RecordNotFound
442 r = {nil => issue_count}
449 r = {nil => issue_count}
443 end
450 end
444 c = group_by_column
451 c = group_by_column
445 if c.is_a?(QueryCustomFieldColumn)
452 if c.is_a?(QueryCustomFieldColumn)
446 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
453 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
447 end
454 end
448 end
455 end
449 r
456 r
450 rescue ::ActiveRecord::StatementInvalid => e
457 rescue ::ActiveRecord::StatementInvalid => e
451 raise StatementInvalid.new(e.message)
458 raise StatementInvalid.new(e.message)
452 end
459 end
453
460
454 # Returns the issues
461 # Returns the issues
455 # Valid options are :order, :offset, :limit, :include, :conditions
462 # Valid options are :order, :offset, :limit, :include, :conditions
456 def issues(options={})
463 def issues(options={})
457 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
464 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
458 order_option = nil if order_option.blank?
465 order_option = nil if order_option.blank?
459
466
460 Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
467 Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
461 :conditions => Query.merge_conditions(statement, options[:conditions]),
468 :conditions => Query.merge_conditions(statement, options[:conditions]),
462 :order => order_option,
469 :order => order_option,
463 :limit => options[:limit],
470 :limit => options[:limit],
464 :offset => options[:offset]
471 :offset => options[:offset]
465 rescue ::ActiveRecord::StatementInvalid => e
472 rescue ::ActiveRecord::StatementInvalid => e
466 raise StatementInvalid.new(e.message)
473 raise StatementInvalid.new(e.message)
467 end
474 end
468
475
469 # Returns the journals
476 # Returns the journals
470 # Valid options are :order, :offset, :limit
477 # Valid options are :order, :offset, :limit
471 def journals(options={})
478 def journals(options={})
472 Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
479 Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
473 :conditions => statement,
480 :conditions => statement,
474 :order => options[:order],
481 :order => options[:order],
475 :limit => options[:limit],
482 :limit => options[:limit],
476 :offset => options[:offset]
483 :offset => options[:offset]
477 rescue ::ActiveRecord::StatementInvalid => e
484 rescue ::ActiveRecord::StatementInvalid => e
478 raise StatementInvalid.new(e.message)
485 raise StatementInvalid.new(e.message)
479 end
486 end
480
487
481 # Returns the versions
488 # Returns the versions
482 # Valid options are :conditions
489 # Valid options are :conditions
483 def versions(options={})
490 def versions(options={})
484 Version.find :all, :include => :project,
491 Version.find :all, :include => :project,
485 :conditions => Query.merge_conditions(project_statement, options[:conditions])
492 :conditions => Query.merge_conditions(project_statement, options[:conditions])
486 rescue ::ActiveRecord::StatementInvalid => e
493 rescue ::ActiveRecord::StatementInvalid => e
487 raise StatementInvalid.new(e.message)
494 raise StatementInvalid.new(e.message)
488 end
495 end
489
496
490 private
497 private
491
498
492 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
499 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
493 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
500 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
494 sql = ''
501 sql = ''
495 case operator
502 case operator
496 when "="
503 when "="
497 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
504 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
498 when "!"
505 when "!"
499 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
506 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
500 when "!*"
507 when "!*"
501 sql = "#{db_table}.#{db_field} IS NULL"
508 sql = "#{db_table}.#{db_field} IS NULL"
502 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
509 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
503 when "*"
510 when "*"
504 sql = "#{db_table}.#{db_field} IS NOT NULL"
511 sql = "#{db_table}.#{db_field} IS NOT NULL"
505 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
512 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
506 when ">="
513 when ">="
507 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
514 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
508 when "<="
515 when "<="
509 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
516 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
510 when "o"
517 when "o"
511 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
518 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
512 when "c"
519 when "c"
513 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
520 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
514 when ">t-"
521 when ">t-"
515 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
522 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
516 when "<t-"
523 when "<t-"
517 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
524 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
518 when "t-"
525 when "t-"
519 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
526 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
520 when ">t+"
527 when ">t+"
521 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
528 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
522 when "<t+"
529 when "<t+"
523 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
530 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
524 when "t+"
531 when "t+"
525 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
532 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
526 when "t"
533 when "t"
527 sql = date_range_clause(db_table, db_field, 0, 0)
534 sql = date_range_clause(db_table, db_field, 0, 0)
528 when "w"
535 when "w"
529 from = l(:general_first_day_of_week) == '7' ?
536 from = l(:general_first_day_of_week) == '7' ?
530 # week starts on sunday
537 # week starts on sunday
531 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
538 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
532 # week starts on monday (Rails default)
539 # week starts on monday (Rails default)
533 Time.now.at_beginning_of_week
540 Time.now.at_beginning_of_week
534 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
541 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
535 when "~"
542 when "~"
536 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
543 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
537 when "!~"
544 when "!~"
538 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
545 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
539 end
546 end
540
547
541 return sql
548 return sql
542 end
549 end
543
550
544 def add_custom_fields_filters(custom_fields)
551 def add_custom_fields_filters(custom_fields)
545 @available_filters ||= {}
552 @available_filters ||= {}
546
553
547 custom_fields.select(&:is_filter?).each do |field|
554 custom_fields.select(&:is_filter?).each do |field|
548 case field.field_format
555 case field.field_format
549 when "text"
556 when "text"
550 options = { :type => :text, :order => 20 }
557 options = { :type => :text, :order => 20 }
551 when "list"
558 when "list"
552 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
559 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
553 when "date"
560 when "date"
554 options = { :type => :date, :order => 20 }
561 options = { :type => :date, :order => 20 }
555 when "bool"
562 when "bool"
556 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
563 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
557 else
564 else
558 options = { :type => :string, :order => 20 }
565 options = { :type => :string, :order => 20 }
559 end
566 end
560 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
567 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
561 end
568 end
562 end
569 end
563
570
564 # Returns a SQL clause for a date or datetime field.
571 # Returns a SQL clause for a date or datetime field.
565 def date_range_clause(table, field, from, to)
572 def date_range_clause(table, field, from, to)
566 s = []
573 s = []
567 if from
574 if from
568 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
575 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
569 end
576 end
570 if to
577 if to
571 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
578 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
572 end
579 end
573 s.join(' AND ')
580 s.join(' AND ')
574 end
581 end
575 end
582 end
General Comments 0
You need to be logged in to leave comments. Login now