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