##// END OF EJS Templates
Fixed syntax for ruby1.8....
Jean-Philippe Lang -
r10742:093ecdfc730f
parent child
Show More
@@ -1,769 +1,769
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 @inline = options.key?(:inline) ? options[:inline] : true
30 @inline = options.key?(:inline) ? options[:inline] : true
31 @caption_key = options[:caption] || "field_#{name}"
31 @caption_key = options[:caption] || "field_#{name}"
32 end
32 end
33
33
34 def caption
34 def caption
35 l(@caption_key)
35 l(@caption_key)
36 end
36 end
37
37
38 # Returns true if the column is sortable, otherwise false
38 # Returns true if the column is sortable, otherwise false
39 def sortable?
39 def sortable?
40 !@sortable.nil?
40 !@sortable.nil?
41 end
41 end
42
42
43 def sortable
43 def sortable
44 @sortable.is_a?(Proc) ? @sortable.call : @sortable
44 @sortable.is_a?(Proc) ? @sortable.call : @sortable
45 end
45 end
46
46
47 def inline?
47 def inline?
48 @inline
48 @inline
49 end
49 end
50
50
51 def value(object)
51 def value(object)
52 object.send name
52 object.send name
53 end
53 end
54
54
55 def css_classes
55 def css_classes
56 name
56 name
57 end
57 end
58 end
58 end
59
59
60 class QueryCustomFieldColumn < QueryColumn
60 class QueryCustomFieldColumn < QueryColumn
61
61
62 def initialize(custom_field)
62 def initialize(custom_field)
63 self.name = "cf_#{custom_field.id}".to_sym
63 self.name = "cf_#{custom_field.id}".to_sym
64 self.sortable = custom_field.order_statement || false
64 self.sortable = custom_field.order_statement || false
65 self.groupable = custom_field.group_statement || false
65 self.groupable = custom_field.group_statement || false
66 @inline = true
66 @inline = true
67 @cf = custom_field
67 @cf = custom_field
68 end
68 end
69
69
70 def caption
70 def caption
71 @cf.name
71 @cf.name
72 end
72 end
73
73
74 def custom_field
74 def custom_field
75 @cf
75 @cf
76 end
76 end
77
77
78 def value(object)
78 def value(object)
79 cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
79 cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
80 cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first
80 cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first
81 end
81 end
82
82
83 def css_classes
83 def css_classes
84 @css_classes ||= "#{name} #{@cf.field_format}"
84 @css_classes ||= "#{name} #{@cf.field_format}"
85 end
85 end
86 end
86 end
87
87
88 class Query < ActiveRecord::Base
88 class Query < ActiveRecord::Base
89 class StatementInvalid < ::ActiveRecord::StatementInvalid
89 class StatementInvalid < ::ActiveRecord::StatementInvalid
90 end
90 end
91
91
92 belongs_to :project
92 belongs_to :project
93 belongs_to :user
93 belongs_to :user
94 serialize :filters
94 serialize :filters
95 serialize :column_names
95 serialize :column_names
96 serialize :sort_criteria, Array
96 serialize :sort_criteria, Array
97
97
98 attr_protected :project_id, :user_id
98 attr_protected :project_id, :user_id
99
99
100 validates_presence_of :name
100 validates_presence_of :name
101 validates_length_of :name, :maximum => 255
101 validates_length_of :name, :maximum => 255
102 validate :validate_query_filters
102 validate :validate_query_filters
103
103
104 class_attribute :operators
104 class_attribute :operators
105 self.operators = {
105 self.operators = {
106 "=" => :label_equals,
106 "=" => :label_equals,
107 "!" => :label_not_equals,
107 "!" => :label_not_equals,
108 "o" => :label_open_issues,
108 "o" => :label_open_issues,
109 "c" => :label_closed_issues,
109 "c" => :label_closed_issues,
110 "!*" => :label_none,
110 "!*" => :label_none,
111 "*" => :label_any,
111 "*" => :label_any,
112 ">=" => :label_greater_or_equal,
112 ">=" => :label_greater_or_equal,
113 "<=" => :label_less_or_equal,
113 "<=" => :label_less_or_equal,
114 "><" => :label_between,
114 "><" => :label_between,
115 "<t+" => :label_in_less_than,
115 "<t+" => :label_in_less_than,
116 ">t+" => :label_in_more_than,
116 ">t+" => :label_in_more_than,
117 "><t+"=> :label_in_the_next_days,
117 "><t+"=> :label_in_the_next_days,
118 "t+" => :label_in,
118 "t+" => :label_in,
119 "t" => :label_today,
119 "t" => :label_today,
120 "ld" => :label_yesterday,
120 "ld" => :label_yesterday,
121 "w" => :label_this_week,
121 "w" => :label_this_week,
122 "lw" => :label_last_week,
122 "lw" => :label_last_week,
123 "l2w" => [:label_last_n_weeks, :count => 2],
123 "l2w" => [:label_last_n_weeks, {:count => 2}],
124 "m" => :label_this_month,
124 "m" => :label_this_month,
125 "lm" => :label_last_month,
125 "lm" => :label_last_month,
126 "y" => :label_this_year,
126 "y" => :label_this_year,
127 ">t-" => :label_less_than_ago,
127 ">t-" => :label_less_than_ago,
128 "<t-" => :label_more_than_ago,
128 "<t-" => :label_more_than_ago,
129 "><t-"=> :label_in_the_past_days,
129 "><t-"=> :label_in_the_past_days,
130 "t-" => :label_ago,
130 "t-" => :label_ago,
131 "~" => :label_contains,
131 "~" => :label_contains,
132 "!~" => :label_not_contains,
132 "!~" => :label_not_contains,
133 "=p" => :label_any_issues_in_project,
133 "=p" => :label_any_issues_in_project,
134 "=!p" => :label_any_issues_not_in_project,
134 "=!p" => :label_any_issues_not_in_project,
135 "!p" => :label_no_issues_in_project
135 "!p" => :label_no_issues_in_project
136 }
136 }
137
137
138 class_attribute :operators_by_filter_type
138 class_attribute :operators_by_filter_type
139 self.operators_by_filter_type = {
139 self.operators_by_filter_type = {
140 :list => [ "=", "!" ],
140 :list => [ "=", "!" ],
141 :list_status => [ "o", "=", "!", "c", "*" ],
141 :list_status => [ "o", "=", "!", "c", "*" ],
142 :list_optional => [ "=", "!", "!*", "*" ],
142 :list_optional => [ "=", "!", "!*", "*" ],
143 :list_subprojects => [ "*", "!*", "=" ],
143 :list_subprojects => [ "*", "!*", "=" ],
144 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
144 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
145 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
145 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
146 :string => [ "=", "~", "!", "!~", "!*", "*" ],
146 :string => [ "=", "~", "!", "!~", "!*", "*" ],
147 :text => [ "~", "!~", "!*", "*" ],
147 :text => [ "~", "!~", "!*", "*" ],
148 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
148 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
149 :float => [ "=", ">=", "<=", "><", "!*", "*" ],
149 :float => [ "=", ">=", "<=", "><", "!*", "*" ],
150 :relation => ["=", "=p", "=!p", "!p", "!*", "*"]
150 :relation => ["=", "=p", "=!p", "!p", "!*", "*"]
151 }
151 }
152
152
153 class_attribute :available_columns
153 class_attribute :available_columns
154 self.available_columns = []
154 self.available_columns = []
155
155
156 class_attribute :queried_class
156 class_attribute :queried_class
157
157
158 def queried_table_name
158 def queried_table_name
159 @queried_table_name ||= self.class.queried_class.table_name
159 @queried_table_name ||= self.class.queried_class.table_name
160 end
160 end
161
161
162 def initialize(attributes=nil, *args)
162 def initialize(attributes=nil, *args)
163 super attributes
163 super attributes
164 @is_for_all = project.nil?
164 @is_for_all = project.nil?
165 end
165 end
166
166
167 # Builds the query from the given params
167 # Builds the query from the given params
168 def build_from_params(params)
168 def build_from_params(params)
169 if params[:fields] || params[:f]
169 if params[:fields] || params[:f]
170 self.filters = {}
170 self.filters = {}
171 add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
171 add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
172 else
172 else
173 available_filters.keys.each do |field|
173 available_filters.keys.each do |field|
174 add_short_filter(field, params[field]) if params[field]
174 add_short_filter(field, params[field]) if params[field]
175 end
175 end
176 end
176 end
177 self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
177 self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
178 self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
178 self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
179 self
179 self
180 end
180 end
181
181
182 # Builds a new query from the given params and attributes
182 # Builds a new query from the given params and attributes
183 def self.build_from_params(params, attributes={})
183 def self.build_from_params(params, attributes={})
184 new(attributes).build_from_params(params)
184 new(attributes).build_from_params(params)
185 end
185 end
186
186
187 def validate_query_filters
187 def validate_query_filters
188 filters.each_key do |field|
188 filters.each_key do |field|
189 if values_for(field)
189 if values_for(field)
190 case type_for(field)
190 case type_for(field)
191 when :integer
191 when :integer
192 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
192 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
193 when :float
193 when :float
194 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
194 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
195 when :date, :date_past
195 when :date, :date_past
196 case operator_for(field)
196 case operator_for(field)
197 when "=", ">=", "<=", "><"
197 when "=", ">=", "<=", "><"
198 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
198 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
199 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
199 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
200 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
200 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
201 end
201 end
202 end
202 end
203 end
203 end
204
204
205 add_filter_error(field, :blank) unless
205 add_filter_error(field, :blank) unless
206 # filter requires one or more values
206 # filter requires one or more values
207 (values_for(field) and !values_for(field).first.blank?) or
207 (values_for(field) and !values_for(field).first.blank?) or
208 # filter doesn't require any value
208 # filter doesn't require any value
209 ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field)
209 ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field)
210 end if filters
210 end if filters
211 end
211 end
212
212
213 def add_filter_error(field, message)
213 def add_filter_error(field, message)
214 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
214 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
215 errors.add(:base, m)
215 errors.add(:base, m)
216 end
216 end
217
217
218 def editable_by?(user)
218 def editable_by?(user)
219 return false unless user
219 return false unless user
220 # Admin can edit them all and regular users can edit their private queries
220 # Admin can edit them all and regular users can edit their private queries
221 return true if user.admin? || (!is_public && self.user_id == user.id)
221 return true if user.admin? || (!is_public && self.user_id == user.id)
222 # Members can not edit public queries that are for all project (only admin is allowed to)
222 # Members can not edit public queries that are for all project (only admin is allowed to)
223 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
223 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
224 end
224 end
225
225
226 def trackers
226 def trackers
227 @trackers ||= project.nil? ? Tracker.sorted.all : project.rolled_up_trackers
227 @trackers ||= project.nil? ? Tracker.sorted.all : project.rolled_up_trackers
228 end
228 end
229
229
230 # Returns a hash of localized labels for all filter operators
230 # Returns a hash of localized labels for all filter operators
231 def self.operators_labels
231 def self.operators_labels
232 operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h}
232 operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h}
233 end
233 end
234
234
235 # Returns a representation of the available filters for JSON serialization
235 # Returns a representation of the available filters for JSON serialization
236 def available_filters_as_json
236 def available_filters_as_json
237 json = {}
237 json = {}
238 available_filters.each do |field, options|
238 available_filters.each do |field, options|
239 json[field] = options.slice(:type, :name, :values).stringify_keys
239 json[field] = options.slice(:type, :name, :values).stringify_keys
240 end
240 end
241 json
241 json
242 end
242 end
243
243
244 def all_projects
244 def all_projects
245 @all_projects ||= Project.visible.all
245 @all_projects ||= Project.visible.all
246 end
246 end
247
247
248 def all_projects_values
248 def all_projects_values
249 return @all_projects_values if @all_projects_values
249 return @all_projects_values if @all_projects_values
250
250
251 values = []
251 values = []
252 Project.project_tree(all_projects) do |p, level|
252 Project.project_tree(all_projects) do |p, level|
253 prefix = (level > 0 ? ('--' * level + ' ') : '')
253 prefix = (level > 0 ? ('--' * level + ' ') : '')
254 values << ["#{prefix}#{p.name}", p.id.to_s]
254 values << ["#{prefix}#{p.name}", p.id.to_s]
255 end
255 end
256 @all_projects_values = values
256 @all_projects_values = values
257 end
257 end
258
258
259 def add_filter(field, operator, values=nil)
259 def add_filter(field, operator, values=nil)
260 # values must be an array
260 # values must be an array
261 return unless values.nil? || values.is_a?(Array)
261 return unless values.nil? || values.is_a?(Array)
262 # check if field is defined as an available filter
262 # check if field is defined as an available filter
263 if available_filters.has_key? field
263 if available_filters.has_key? field
264 filter_options = available_filters[field]
264 filter_options = available_filters[field]
265 filters[field] = {:operator => operator, :values => (values || [''])}
265 filters[field] = {:operator => operator, :values => (values || [''])}
266 end
266 end
267 end
267 end
268
268
269 def add_short_filter(field, expression)
269 def add_short_filter(field, expression)
270 return unless expression && available_filters.has_key?(field)
270 return unless expression && available_filters.has_key?(field)
271 field_type = available_filters[field][:type]
271 field_type = available_filters[field][:type]
272 operators_by_filter_type[field_type].sort.reverse.detect do |operator|
272 operators_by_filter_type[field_type].sort.reverse.detect do |operator|
273 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
273 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
274 add_filter field, operator, $1.present? ? $1.split('|') : ['']
274 add_filter field, operator, $1.present? ? $1.split('|') : ['']
275 end || add_filter(field, '=', expression.split('|'))
275 end || add_filter(field, '=', expression.split('|'))
276 end
276 end
277
277
278 # Add multiple filters using +add_filter+
278 # Add multiple filters using +add_filter+
279 def add_filters(fields, operators, values)
279 def add_filters(fields, operators, values)
280 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
280 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
281 fields.each do |field|
281 fields.each do |field|
282 add_filter(field, operators[field], values && values[field])
282 add_filter(field, operators[field], values && values[field])
283 end
283 end
284 end
284 end
285 end
285 end
286
286
287 def has_filter?(field)
287 def has_filter?(field)
288 filters and filters[field]
288 filters and filters[field]
289 end
289 end
290
290
291 def type_for(field)
291 def type_for(field)
292 available_filters[field][:type] if available_filters.has_key?(field)
292 available_filters[field][:type] if available_filters.has_key?(field)
293 end
293 end
294
294
295 def operator_for(field)
295 def operator_for(field)
296 has_filter?(field) ? filters[field][:operator] : nil
296 has_filter?(field) ? filters[field][:operator] : nil
297 end
297 end
298
298
299 def values_for(field)
299 def values_for(field)
300 has_filter?(field) ? filters[field][:values] : nil
300 has_filter?(field) ? filters[field][:values] : nil
301 end
301 end
302
302
303 def value_for(field, index=0)
303 def value_for(field, index=0)
304 (values_for(field) || [])[index]
304 (values_for(field) || [])[index]
305 end
305 end
306
306
307 def label_for(field)
307 def label_for(field)
308 label = available_filters[field][:name] if available_filters.has_key?(field)
308 label = available_filters[field][:name] if available_filters.has_key?(field)
309 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
309 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
310 end
310 end
311
311
312 def self.add_available_column(column)
312 def self.add_available_column(column)
313 self.available_columns << (column) if column.is_a?(QueryColumn)
313 self.available_columns << (column) if column.is_a?(QueryColumn)
314 end
314 end
315
315
316 # Returns an array of columns that can be used to group the results
316 # Returns an array of columns that can be used to group the results
317 def groupable_columns
317 def groupable_columns
318 available_columns.select {|c| c.groupable}
318 available_columns.select {|c| c.groupable}
319 end
319 end
320
320
321 # Returns a Hash of columns and the key for sorting
321 # Returns a Hash of columns and the key for sorting
322 def sortable_columns
322 def sortable_columns
323 available_columns.inject({}) {|h, column|
323 available_columns.inject({}) {|h, column|
324 h[column.name.to_s] = column.sortable
324 h[column.name.to_s] = column.sortable
325 h
325 h
326 }
326 }
327 end
327 end
328
328
329 def columns
329 def columns
330 # preserve the column_names order
330 # preserve the column_names order
331 (has_default_columns? ? default_columns_names : column_names).collect do |name|
331 (has_default_columns? ? default_columns_names : column_names).collect do |name|
332 available_columns.find { |col| col.name == name }
332 available_columns.find { |col| col.name == name }
333 end.compact
333 end.compact
334 end
334 end
335
335
336 def inline_columns
336 def inline_columns
337 columns.select(&:inline?)
337 columns.select(&:inline?)
338 end
338 end
339
339
340 def block_columns
340 def block_columns
341 columns.reject(&:inline?)
341 columns.reject(&:inline?)
342 end
342 end
343
343
344 def available_inline_columns
344 def available_inline_columns
345 available_columns.select(&:inline?)
345 available_columns.select(&:inline?)
346 end
346 end
347
347
348 def available_block_columns
348 def available_block_columns
349 available_columns.reject(&:inline?)
349 available_columns.reject(&:inline?)
350 end
350 end
351
351
352 def default_columns_names
352 def default_columns_names
353 []
353 []
354 end
354 end
355
355
356 def column_names=(names)
356 def column_names=(names)
357 if names
357 if names
358 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
358 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
359 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
359 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
360 # Set column_names to nil if default columns
360 # Set column_names to nil if default columns
361 if names == default_columns_names
361 if names == default_columns_names
362 names = nil
362 names = nil
363 end
363 end
364 end
364 end
365 write_attribute(:column_names, names)
365 write_attribute(:column_names, names)
366 end
366 end
367
367
368 def has_column?(column)
368 def has_column?(column)
369 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
369 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
370 end
370 end
371
371
372 def has_default_columns?
372 def has_default_columns?
373 column_names.nil? || column_names.empty?
373 column_names.nil? || column_names.empty?
374 end
374 end
375
375
376 def sort_criteria=(arg)
376 def sort_criteria=(arg)
377 c = []
377 c = []
378 if arg.is_a?(Hash)
378 if arg.is_a?(Hash)
379 arg = arg.keys.sort.collect {|k| arg[k]}
379 arg = arg.keys.sort.collect {|k| arg[k]}
380 end
380 end
381 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
381 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
382 write_attribute(:sort_criteria, c)
382 write_attribute(:sort_criteria, c)
383 end
383 end
384
384
385 def sort_criteria
385 def sort_criteria
386 read_attribute(:sort_criteria) || []
386 read_attribute(:sort_criteria) || []
387 end
387 end
388
388
389 def sort_criteria_key(arg)
389 def sort_criteria_key(arg)
390 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
390 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
391 end
391 end
392
392
393 def sort_criteria_order(arg)
393 def sort_criteria_order(arg)
394 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
394 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
395 end
395 end
396
396
397 def sort_criteria_order_for(key)
397 def sort_criteria_order_for(key)
398 sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
398 sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
399 end
399 end
400
400
401 # Returns the SQL sort order that should be prepended for grouping
401 # Returns the SQL sort order that should be prepended for grouping
402 def group_by_sort_order
402 def group_by_sort_order
403 if grouped? && (column = group_by_column)
403 if grouped? && (column = group_by_column)
404 order = sort_criteria_order_for(column.name) || column.default_order
404 order = sort_criteria_order_for(column.name) || column.default_order
405 column.sortable.is_a?(Array) ?
405 column.sortable.is_a?(Array) ?
406 column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
406 column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
407 "#{column.sortable} #{order}"
407 "#{column.sortable} #{order}"
408 end
408 end
409 end
409 end
410
410
411 # Returns true if the query is a grouped query
411 # Returns true if the query is a grouped query
412 def grouped?
412 def grouped?
413 !group_by_column.nil?
413 !group_by_column.nil?
414 end
414 end
415
415
416 def group_by_column
416 def group_by_column
417 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
417 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
418 end
418 end
419
419
420 def group_by_statement
420 def group_by_statement
421 group_by_column.try(:groupable)
421 group_by_column.try(:groupable)
422 end
422 end
423
423
424 def project_statement
424 def project_statement
425 project_clauses = []
425 project_clauses = []
426 if project && !project.descendants.active.empty?
426 if project && !project.descendants.active.empty?
427 ids = [project.id]
427 ids = [project.id]
428 if has_filter?("subproject_id")
428 if has_filter?("subproject_id")
429 case operator_for("subproject_id")
429 case operator_for("subproject_id")
430 when '='
430 when '='
431 # include the selected subprojects
431 # include the selected subprojects
432 ids += values_for("subproject_id").each(&:to_i)
432 ids += values_for("subproject_id").each(&:to_i)
433 when '!*'
433 when '!*'
434 # main project only
434 # main project only
435 else
435 else
436 # all subprojects
436 # all subprojects
437 ids += project.descendants.collect(&:id)
437 ids += project.descendants.collect(&:id)
438 end
438 end
439 elsif Setting.display_subprojects_issues?
439 elsif Setting.display_subprojects_issues?
440 ids += project.descendants.collect(&:id)
440 ids += project.descendants.collect(&:id)
441 end
441 end
442 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
442 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
443 elsif project
443 elsif project
444 project_clauses << "#{Project.table_name}.id = %d" % project.id
444 project_clauses << "#{Project.table_name}.id = %d" % project.id
445 end
445 end
446 project_clauses.any? ? project_clauses.join(' AND ') : nil
446 project_clauses.any? ? project_clauses.join(' AND ') : nil
447 end
447 end
448
448
449 def statement
449 def statement
450 # filters clauses
450 # filters clauses
451 filters_clauses = []
451 filters_clauses = []
452 filters.each_key do |field|
452 filters.each_key do |field|
453 next if field == "subproject_id"
453 next if field == "subproject_id"
454 v = values_for(field).clone
454 v = values_for(field).clone
455 next unless v and !v.empty?
455 next unless v and !v.empty?
456 operator = operator_for(field)
456 operator = operator_for(field)
457
457
458 # "me" value subsitution
458 # "me" value subsitution
459 if %w(assigned_to_id author_id watcher_id).include?(field)
459 if %w(assigned_to_id author_id watcher_id).include?(field)
460 if v.delete("me")
460 if v.delete("me")
461 if User.current.logged?
461 if User.current.logged?
462 v.push(User.current.id.to_s)
462 v.push(User.current.id.to_s)
463 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
463 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
464 else
464 else
465 v.push("0")
465 v.push("0")
466 end
466 end
467 end
467 end
468 end
468 end
469
469
470 if field == 'project_id'
470 if field == 'project_id'
471 if v.delete('mine')
471 if v.delete('mine')
472 v += User.current.memberships.map(&:project_id).map(&:to_s)
472 v += User.current.memberships.map(&:project_id).map(&:to_s)
473 end
473 end
474 end
474 end
475
475
476 if field =~ /cf_(\d+)$/
476 if field =~ /cf_(\d+)$/
477 # custom field
477 # custom field
478 filters_clauses << sql_for_custom_field(field, operator, v, $1)
478 filters_clauses << sql_for_custom_field(field, operator, v, $1)
479 elsif respond_to?("sql_for_#{field}_field")
479 elsif respond_to?("sql_for_#{field}_field")
480 # specific statement
480 # specific statement
481 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
481 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
482 else
482 else
483 # regular field
483 # regular field
484 filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
484 filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
485 end
485 end
486 end if filters and valid?
486 end if filters and valid?
487
487
488 filters_clauses << project_statement
488 filters_clauses << project_statement
489 filters_clauses.reject!(&:blank?)
489 filters_clauses.reject!(&:blank?)
490
490
491 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
491 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
492 end
492 end
493
493
494 private
494 private
495
495
496 def sql_for_custom_field(field, operator, value, custom_field_id)
496 def sql_for_custom_field(field, operator, value, custom_field_id)
497 db_table = CustomValue.table_name
497 db_table = CustomValue.table_name
498 db_field = 'value'
498 db_field = 'value'
499 filter = @available_filters[field]
499 filter = @available_filters[field]
500 return nil unless filter
500 return nil unless filter
501 if filter[:format] == 'user'
501 if filter[:format] == 'user'
502 if value.delete('me')
502 if value.delete('me')
503 value.push User.current.id.to_s
503 value.push User.current.id.to_s
504 end
504 end
505 end
505 end
506 not_in = nil
506 not_in = nil
507 if operator == '!'
507 if operator == '!'
508 # Makes ! operator work for custom fields with multiple values
508 # Makes ! operator work for custom fields with multiple values
509 operator = '='
509 operator = '='
510 not_in = 'NOT'
510 not_in = 'NOT'
511 end
511 end
512 customized_key = "id"
512 customized_key = "id"
513 customized_class = queried_class
513 customized_class = queried_class
514 if field =~ /^(.+)\.cf_/
514 if field =~ /^(.+)\.cf_/
515 assoc = $1
515 assoc = $1
516 customized_key = "#{assoc}_id"
516 customized_key = "#{assoc}_id"
517 customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
517 customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
518 raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class
518 raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class
519 end
519 end
520 "#{queried_table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
520 "#{queried_table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
521 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
521 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
522 end
522 end
523
523
524 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
524 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
525 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
525 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
526 sql = ''
526 sql = ''
527 case operator
527 case operator
528 when "="
528 when "="
529 if value.any?
529 if value.any?
530 case type_for(field)
530 case type_for(field)
531 when :date, :date_past
531 when :date, :date_past
532 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
532 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
533 when :integer
533 when :integer
534 if is_custom_filter
534 if is_custom_filter
535 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})"
535 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})"
536 else
536 else
537 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
537 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
538 end
538 end
539 when :float
539 when :float
540 if is_custom_filter
540 if is_custom_filter
541 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
541 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
542 else
542 else
543 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
543 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
544 end
544 end
545 else
545 else
546 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
546 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
547 end
547 end
548 else
548 else
549 # IN an empty set
549 # IN an empty set
550 sql = "1=0"
550 sql = "1=0"
551 end
551 end
552 when "!"
552 when "!"
553 if value.any?
553 if value.any?
554 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
554 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
555 else
555 else
556 # NOT IN an empty set
556 # NOT IN an empty set
557 sql = "1=1"
557 sql = "1=1"
558 end
558 end
559 when "!*"
559 when "!*"
560 sql = "#{db_table}.#{db_field} IS NULL"
560 sql = "#{db_table}.#{db_field} IS NULL"
561 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
561 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
562 when "*"
562 when "*"
563 sql = "#{db_table}.#{db_field} IS NOT NULL"
563 sql = "#{db_table}.#{db_field} IS NOT NULL"
564 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
564 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
565 when ">="
565 when ">="
566 if [:date, :date_past].include?(type_for(field))
566 if [:date, :date_past].include?(type_for(field))
567 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
567 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
568 else
568 else
569 if is_custom_filter
569 if is_custom_filter
570 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})"
570 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})"
571 else
571 else
572 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
572 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
573 end
573 end
574 end
574 end
575 when "<="
575 when "<="
576 if [:date, :date_past].include?(type_for(field))
576 if [:date, :date_past].include?(type_for(field))
577 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
577 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
578 else
578 else
579 if is_custom_filter
579 if is_custom_filter
580 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})"
580 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})"
581 else
581 else
582 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
582 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
583 end
583 end
584 end
584 end
585 when "><"
585 when "><"
586 if [:date, :date_past].include?(type_for(field))
586 if [:date, :date_past].include?(type_for(field))
587 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
587 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
588 else
588 else
589 if is_custom_filter
589 if is_custom_filter
590 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
590 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
591 else
591 else
592 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
592 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
593 end
593 end
594 end
594 end
595 when "o"
595 when "o"
596 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
596 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
597 when "c"
597 when "c"
598 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
598 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
599 when "><t-"
599 when "><t-"
600 # between today - n days and today
600 # between today - n days and today
601 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
601 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
602 when ">t-"
602 when ">t-"
603 # >= today - n days
603 # >= today - n days
604 sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
604 sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
605 when "<t-"
605 when "<t-"
606 # <= today - n days
606 # <= today - n days
607 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
607 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
608 when "t-"
608 when "t-"
609 # = n days in past
609 # = n days in past
610 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
610 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
611 when "><t+"
611 when "><t+"
612 # between today and today + n days
612 # between today and today + n days
613 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
613 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
614 when ">t+"
614 when ">t+"
615 # >= today + n days
615 # >= today + n days
616 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
616 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
617 when "<t+"
617 when "<t+"
618 # <= today + n days
618 # <= today + n days
619 sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
619 sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
620 when "t+"
620 when "t+"
621 # = today + n days
621 # = today + n days
622 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
622 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
623 when "t"
623 when "t"
624 # = today
624 # = today
625 sql = relative_date_clause(db_table, db_field, 0, 0)
625 sql = relative_date_clause(db_table, db_field, 0, 0)
626 when "ld"
626 when "ld"
627 # = yesterday
627 # = yesterday
628 sql = relative_date_clause(db_table, db_field, -1, -1)
628 sql = relative_date_clause(db_table, db_field, -1, -1)
629 when "w"
629 when "w"
630 # = this week
630 # = this week
631 first_day_of_week = l(:general_first_day_of_week).to_i
631 first_day_of_week = l(:general_first_day_of_week).to_i
632 day_of_week = Date.today.cwday
632 day_of_week = Date.today.cwday
633 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
633 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
634 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
634 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
635 when "lw"
635 when "lw"
636 # = last week
636 # = last week
637 first_day_of_week = l(:general_first_day_of_week).to_i
637 first_day_of_week = l(:general_first_day_of_week).to_i
638 day_of_week = Date.today.cwday
638 day_of_week = Date.today.cwday
639 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
639 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
640 sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1)
640 sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1)
641 when "l2w"
641 when "l2w"
642 # = last 2 weeks
642 # = last 2 weeks
643 first_day_of_week = l(:general_first_day_of_week).to_i
643 first_day_of_week = l(:general_first_day_of_week).to_i
644 day_of_week = Date.today.cwday
644 day_of_week = Date.today.cwday
645 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
645 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
646 sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1)
646 sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1)
647 when "m"
647 when "m"
648 # = this month
648 # = this month
649 date = Date.today
649 date = Date.today
650 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
650 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
651 when "lm"
651 when "lm"
652 # = last month
652 # = last month
653 date = Date.today.prev_month
653 date = Date.today.prev_month
654 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
654 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
655 when "y"
655 when "y"
656 # = this year
656 # = this year
657 date = Date.today
657 date = Date.today
658 sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year)
658 sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year)
659 when "~"
659 when "~"
660 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
660 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
661 when "!~"
661 when "!~"
662 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
662 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
663 else
663 else
664 raise "Unknown query operator #{operator}"
664 raise "Unknown query operator #{operator}"
665 end
665 end
666
666
667 return sql
667 return sql
668 end
668 end
669
669
670 def add_custom_fields_filters(custom_fields, assoc=nil)
670 def add_custom_fields_filters(custom_fields, assoc=nil)
671 return unless custom_fields.present?
671 return unless custom_fields.present?
672 @available_filters ||= {}
672 @available_filters ||= {}
673
673
674 custom_fields.select(&:is_filter?).each do |field|
674 custom_fields.select(&:is_filter?).each do |field|
675 case field.field_format
675 case field.field_format
676 when "text"
676 when "text"
677 options = { :type => :text, :order => 20 }
677 options = { :type => :text, :order => 20 }
678 when "list"
678 when "list"
679 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
679 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
680 when "date"
680 when "date"
681 options = { :type => :date, :order => 20 }
681 options = { :type => :date, :order => 20 }
682 when "bool"
682 when "bool"
683 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
683 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
684 when "int"
684 when "int"
685 options = { :type => :integer, :order => 20 }
685 options = { :type => :integer, :order => 20 }
686 when "float"
686 when "float"
687 options = { :type => :float, :order => 20 }
687 options = { :type => :float, :order => 20 }
688 when "user", "version"
688 when "user", "version"
689 next unless project
689 next unless project
690 values = field.possible_values_options(project)
690 values = field.possible_values_options(project)
691 if User.current.logged? && field.field_format == 'user'
691 if User.current.logged? && field.field_format == 'user'
692 values.unshift ["<< #{l(:label_me)} >>", "me"]
692 values.unshift ["<< #{l(:label_me)} >>", "me"]
693 end
693 end
694 options = { :type => :list_optional, :values => values, :order => 20}
694 options = { :type => :list_optional, :values => values, :order => 20}
695 else
695 else
696 options = { :type => :string, :order => 20 }
696 options = { :type => :string, :order => 20 }
697 end
697 end
698 filter_id = "cf_#{field.id}"
698 filter_id = "cf_#{field.id}"
699 filter_name = field.name
699 filter_name = field.name
700 if assoc.present?
700 if assoc.present?
701 filter_id = "#{assoc}.#{filter_id}"
701 filter_id = "#{assoc}.#{filter_id}"
702 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
702 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
703 end
703 end
704 @available_filters[filter_id] = options.merge({
704 @available_filters[filter_id] = options.merge({
705 :name => filter_name,
705 :name => filter_name,
706 :format => field.field_format,
706 :format => field.field_format,
707 :field => field
707 :field => field
708 })
708 })
709 end
709 end
710 end
710 end
711
711
712 def add_associations_custom_fields_filters(*associations)
712 def add_associations_custom_fields_filters(*associations)
713 fields_by_class = CustomField.where(:is_filter => true).group_by(&:class)
713 fields_by_class = CustomField.where(:is_filter => true).group_by(&:class)
714 associations.each do |assoc|
714 associations.each do |assoc|
715 association_klass = queried_class.reflect_on_association(assoc).klass
715 association_klass = queried_class.reflect_on_association(assoc).klass
716 fields_by_class.each do |field_class, fields|
716 fields_by_class.each do |field_class, fields|
717 if field_class.customized_class <= association_klass
717 if field_class.customized_class <= association_klass
718 add_custom_fields_filters(fields, assoc)
718 add_custom_fields_filters(fields, assoc)
719 end
719 end
720 end
720 end
721 end
721 end
722 end
722 end
723
723
724 # Returns a SQL clause for a date or datetime field.
724 # Returns a SQL clause for a date or datetime field.
725 def date_clause(table, field, from, to)
725 def date_clause(table, field, from, to)
726 s = []
726 s = []
727 if from
727 if from
728 from_yesterday = from - 1
728 from_yesterday = from - 1
729 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
729 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
730 if self.class.default_timezone == :utc
730 if self.class.default_timezone == :utc
731 from_yesterday_time = from_yesterday_time.utc
731 from_yesterday_time = from_yesterday_time.utc
732 end
732 end
733 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
733 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
734 end
734 end
735 if to
735 if to
736 to_time = Time.local(to.year, to.month, to.day)
736 to_time = Time.local(to.year, to.month, to.day)
737 if self.class.default_timezone == :utc
737 if self.class.default_timezone == :utc
738 to_time = to_time.utc
738 to_time = to_time.utc
739 end
739 end
740 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
740 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
741 end
741 end
742 s.join(' AND ')
742 s.join(' AND ')
743 end
743 end
744
744
745 # Returns a SQL clause for a date or datetime field using relative dates.
745 # Returns a SQL clause for a date or datetime field using relative dates.
746 def relative_date_clause(table, field, days_from, days_to)
746 def relative_date_clause(table, field, days_from, days_to)
747 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
747 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
748 end
748 end
749
749
750 # Additional joins required for the given sort options
750 # Additional joins required for the given sort options
751 def joins_for_order_statement(order_options)
751 def joins_for_order_statement(order_options)
752 joins = []
752 joins = []
753
753
754 if order_options
754 if order_options
755 if order_options.include?('authors')
755 if order_options.include?('authors')
756 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id"
756 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id"
757 end
757 end
758 order_options.scan(/cf_\d+/).uniq.each do |name|
758 order_options.scan(/cf_\d+/).uniq.each do |name|
759 column = available_columns.detect {|c| c.name.to_s == name}
759 column = available_columns.detect {|c| c.name.to_s == name}
760 join = column && column.custom_field.join_for_order_statement
760 join = column && column.custom_field.join_for_order_statement
761 if join
761 if join
762 joins << join
762 joins << join
763 end
763 end
764 end
764 end
765 end
765 end
766
766
767 joins.any? ? joins.join(' ') : nil
767 joins.any? ? joins.join(' ') : nil
768 end
768 end
769 end
769 end
General Comments 0
You need to be logged in to leave comments. Login now