##// END OF EJS Templates
Show all members in user custom field filter on the cross project issue list (#24769)....
Jean-Philippe Lang -
r15928:df4564bbd429
parent child
Show More
@@ -1,988 +1,986
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 require 'uri'
19 19
20 20 module Redmine
21 21 module FieldFormat
22 22 def self.add(name, klass)
23 23 all[name.to_s] = klass.instance
24 24 end
25 25
26 26 def self.delete(name)
27 27 all.delete(name.to_s)
28 28 end
29 29
30 30 def self.all
31 31 @formats ||= Hash.new(Base.instance)
32 32 end
33 33
34 34 def self.available_formats
35 35 all.keys
36 36 end
37 37
38 38 def self.find(name)
39 39 all[name.to_s]
40 40 end
41 41
42 42 # Return an array of custom field formats which can be used in select_tag
43 43 def self.as_select(class_name=nil)
44 44 formats = all.values.select do |format|
45 45 format.class.customized_class_names.nil? || format.class.customized_class_names.include?(class_name)
46 46 end
47 47 formats.map {|format| [::I18n.t(format.label), format.name] }.sort_by(&:first)
48 48 end
49 49
50 50 # Returns an array of formats that can be used for a custom field class
51 51 def self.formats_for_custom_field_class(klass=nil)
52 52 all.values.select do |format|
53 53 format.class.customized_class_names.nil? || format.class.customized_class_names.include?(klass.name)
54 54 end
55 55 end
56 56
57 57 class Base
58 58 include Singleton
59 59 include Redmine::I18n
60 60 include Redmine::Helpers::URL
61 61 include ERB::Util
62 62
63 63 class_attribute :format_name
64 64 self.format_name = nil
65 65
66 66 # Set this to true if the format supports multiple values
67 67 class_attribute :multiple_supported
68 68 self.multiple_supported = false
69 69
70 70 # Set this to true if the format supports filtering on custom values
71 71 class_attribute :is_filter_supported
72 72 self.is_filter_supported = true
73 73
74 74 # Set this to true if the format supports textual search on custom values
75 75 class_attribute :searchable_supported
76 76 self.searchable_supported = false
77 77
78 78 # Set this to true if field values can be summed up
79 79 class_attribute :totalable_supported
80 80 self.totalable_supported = false
81 81
82 82 # Set this to false if field cannot be bulk edited
83 83 class_attribute :bulk_edit_supported
84 84 self.bulk_edit_supported = true
85 85
86 86 # Restricts the classes that the custom field can be added to
87 87 # Set to nil for no restrictions
88 88 class_attribute :customized_class_names
89 89 self.customized_class_names = nil
90 90
91 91 # Name of the partial for editing the custom field
92 92 class_attribute :form_partial
93 93 self.form_partial = nil
94 94
95 95 class_attribute :change_as_diff
96 96 self.change_as_diff = false
97 97
98 98 class_attribute :change_no_details
99 99 self.change_no_details = false
100 100
101 101 def self.add(name)
102 102 self.format_name = name
103 103 Redmine::FieldFormat.add(name, self)
104 104 end
105 105 private_class_method :add
106 106
107 107 def self.field_attributes(*args)
108 108 CustomField.store_accessor :format_store, *args
109 109 end
110 110
111 111 field_attributes :url_pattern, :full_width_layout
112 112
113 113 def name
114 114 self.class.format_name
115 115 end
116 116
117 117 def label
118 118 "label_#{name}"
119 119 end
120 120
121 121 def set_custom_field_value(custom_field, custom_field_value, value)
122 122 if value.is_a?(Array)
123 123 value = value.map(&:to_s).reject{|v| v==''}.uniq
124 124 if value.empty?
125 125 value << ''
126 126 end
127 127 else
128 128 value = value.to_s
129 129 end
130 130
131 131 value
132 132 end
133 133
134 134 def cast_custom_value(custom_value)
135 135 cast_value(custom_value.custom_field, custom_value.value, custom_value.customized)
136 136 end
137 137
138 138 def cast_value(custom_field, value, customized=nil)
139 139 if value.blank?
140 140 nil
141 141 elsif value.is_a?(Array)
142 142 casted = value.map do |v|
143 143 cast_single_value(custom_field, v, customized)
144 144 end
145 145 casted.compact.sort
146 146 else
147 147 cast_single_value(custom_field, value, customized)
148 148 end
149 149 end
150 150
151 151 def cast_single_value(custom_field, value, customized=nil)
152 152 value.to_s
153 153 end
154 154
155 155 def target_class
156 156 nil
157 157 end
158 158
159 159 def possible_custom_value_options(custom_value)
160 160 possible_values_options(custom_value.custom_field, custom_value.customized)
161 161 end
162 162
163 163 def possible_values_options(custom_field, object=nil)
164 164 []
165 165 end
166 166
167 167 def value_from_keyword(custom_field, keyword, object)
168 168 possible_values_options = possible_values_options(custom_field, object)
169 169 if possible_values_options.present?
170 170 keyword = keyword.to_s
171 171 if v = possible_values_options.detect {|text, id| keyword.casecmp(text) == 0}
172 172 if v.is_a?(Array)
173 173 v.last
174 174 else
175 175 v
176 176 end
177 177 end
178 178 else
179 179 keyword
180 180 end
181 181 end
182 182
183 183 # Returns the validation errors for custom_field
184 184 # Should return an empty array if custom_field is valid
185 185 def validate_custom_field(custom_field)
186 186 errors = []
187 187 pattern = custom_field.url_pattern
188 188 if pattern.present? && !uri_with_safe_scheme?(url_pattern_without_tokens(pattern))
189 189 errors << [:url_pattern, :invalid]
190 190 end
191 191 errors
192 192 end
193 193
194 194 # Returns the validation error messages for custom_value
195 195 # Should return an empty array if custom_value is valid
196 196 # custom_value is a CustomFieldValue.
197 197 def validate_custom_value(custom_value)
198 198 values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''}
199 199 errors = values.map do |value|
200 200 validate_single_value(custom_value.custom_field, value, custom_value.customized)
201 201 end
202 202 errors.flatten.uniq
203 203 end
204 204
205 205 def validate_single_value(custom_field, value, customized=nil)
206 206 []
207 207 end
208 208
209 209 # CustomValue after_save callback
210 210 def after_save_custom_value(custom_field, custom_value)
211 211 end
212 212
213 213 def formatted_custom_value(view, custom_value, html=false)
214 214 formatted_value(view, custom_value.custom_field, custom_value.value, custom_value.customized, html)
215 215 end
216 216
217 217 def formatted_value(view, custom_field, value, customized=nil, html=false)
218 218 casted = cast_value(custom_field, value, customized)
219 219 if html && custom_field.url_pattern.present?
220 220 texts_and_urls = Array.wrap(casted).map do |single_value|
221 221 text = view.format_object(single_value, false).to_s
222 222 url = url_from_pattern(custom_field, single_value, customized)
223 223 [text, url]
224 224 end
225 225 links = texts_and_urls.sort_by(&:first).map {|text, url| view.link_to_if uri_with_safe_scheme?(url), text, url}
226 226 links.join(', ').html_safe
227 227 else
228 228 casted
229 229 end
230 230 end
231 231
232 232 # Returns an URL generated with the custom field URL pattern
233 233 # and variables substitution:
234 234 # %value% => the custom field value
235 235 # %id% => id of the customized object
236 236 # %project_id% => id of the project of the customized object if defined
237 237 # %project_identifier% => identifier of the project of the customized object if defined
238 238 # %m1%, %m2%... => capture groups matches of the custom field regexp if defined
239 239 def url_from_pattern(custom_field, value, customized)
240 240 url = custom_field.url_pattern.to_s.dup
241 241 url.gsub!('%value%') {URI.encode value.to_s}
242 242 url.gsub!('%id%') {URI.encode customized.id.to_s}
243 243 url.gsub!('%project_id%') {URI.encode (customized.respond_to?(:project) ? customized.project.try(:id) : nil).to_s}
244 244 url.gsub!('%project_identifier%') {URI.encode (customized.respond_to?(:project) ? customized.project.try(:identifier) : nil).to_s}
245 245 if custom_field.regexp.present?
246 246 url.gsub!(%r{%m(\d+)%}) do
247 247 m = $1.to_i
248 248 if matches ||= value.to_s.match(Regexp.new(custom_field.regexp))
249 249 URI.encode matches[m].to_s
250 250 end
251 251 end
252 252 end
253 253 url
254 254 end
255 255 protected :url_from_pattern
256 256
257 257 # Returns the URL pattern with substitution tokens removed,
258 258 # for validation purpose
259 259 def url_pattern_without_tokens(url_pattern)
260 260 url_pattern.to_s.gsub(/%(value|id|project_id|project_identifier|m\d+)%/, '')
261 261 end
262 262 protected :url_pattern_without_tokens
263 263
264 264 def edit_tag(view, tag_id, tag_name, custom_value, options={})
265 265 view.text_field_tag(tag_name, custom_value.value, options.merge(:id => tag_id))
266 266 end
267 267
268 268 def bulk_edit_tag(view, tag_id, tag_name, custom_field, objects, value, options={})
269 269 view.text_field_tag(tag_name, value, options.merge(:id => tag_id)) +
270 270 bulk_clear_tag(view, tag_id, tag_name, custom_field, value)
271 271 end
272 272
273 273 def bulk_clear_tag(view, tag_id, tag_name, custom_field, value)
274 274 if custom_field.is_required?
275 275 ''.html_safe
276 276 else
277 277 view.content_tag('label',
278 278 view.check_box_tag(tag_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{tag_id}"}) + l(:button_clear),
279 279 :class => 'inline'
280 280 )
281 281 end
282 282 end
283 283 protected :bulk_clear_tag
284 284
285 285 def query_filter_options(custom_field, query)
286 286 {:type => :string}
287 287 end
288 288
289 289 def before_custom_field_save(custom_field)
290 290 end
291 291
292 292 # Returns a ORDER BY clause that can used to sort customized
293 293 # objects by their value of the custom field.
294 294 # Returns nil if the custom field can not be used for sorting.
295 295 def order_statement(custom_field)
296 296 # COALESCE is here to make sure that blank and NULL values are sorted equally
297 297 "COALESCE(#{join_alias custom_field}.value, '')"
298 298 end
299 299
300 300 # Returns a GROUP BY clause that can used to group by custom value
301 301 # Returns nil if the custom field can not be used for grouping.
302 302 def group_statement(custom_field)
303 303 nil
304 304 end
305 305
306 306 # Returns a JOIN clause that is added to the query when sorting by custom values
307 307 def join_for_order_statement(custom_field)
308 308 alias_name = join_alias(custom_field)
309 309
310 310 "LEFT OUTER JOIN #{CustomValue.table_name} #{alias_name}" +
311 311 " ON #{alias_name}.customized_type = '#{custom_field.class.customized_class.base_class.name}'" +
312 312 " AND #{alias_name}.customized_id = #{custom_field.class.customized_class.table_name}.id" +
313 313 " AND #{alias_name}.custom_field_id = #{custom_field.id}" +
314 314 " AND (#{custom_field.visibility_by_project_condition})" +
315 315 " AND #{alias_name}.value <> ''" +
316 316 " AND #{alias_name}.id = (SELECT max(#{alias_name}_2.id) FROM #{CustomValue.table_name} #{alias_name}_2" +
317 317 " WHERE #{alias_name}_2.customized_type = #{alias_name}.customized_type" +
318 318 " AND #{alias_name}_2.customized_id = #{alias_name}.customized_id" +
319 319 " AND #{alias_name}_2.custom_field_id = #{alias_name}.custom_field_id)"
320 320 end
321 321
322 322 def join_alias(custom_field)
323 323 "cf_#{custom_field.id}"
324 324 end
325 325 protected :join_alias
326 326 end
327 327
328 328 class Unbounded < Base
329 329 def validate_single_value(custom_field, value, customized=nil)
330 330 errs = super
331 331 value = value.to_s
332 332 unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
333 333 errs << ::I18n.t('activerecord.errors.messages.invalid')
334 334 end
335 335 if custom_field.min_length && value.length < custom_field.min_length
336 336 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => custom_field.min_length)
337 337 end
338 338 if custom_field.max_length && custom_field.max_length > 0 && value.length > custom_field.max_length
339 339 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => custom_field.max_length)
340 340 end
341 341 errs
342 342 end
343 343 end
344 344
345 345 class StringFormat < Unbounded
346 346 add 'string'
347 347 self.searchable_supported = true
348 348 self.form_partial = 'custom_fields/formats/string'
349 349 field_attributes :text_formatting
350 350
351 351 def formatted_value(view, custom_field, value, customized=nil, html=false)
352 352 if html
353 353 if custom_field.url_pattern.present?
354 354 super
355 355 elsif custom_field.text_formatting == 'full'
356 356 view.textilizable(value, :object => customized)
357 357 else
358 358 value.to_s
359 359 end
360 360 else
361 361 value.to_s
362 362 end
363 363 end
364 364 end
365 365
366 366 class TextFormat < Unbounded
367 367 add 'text'
368 368 self.searchable_supported = true
369 369 self.form_partial = 'custom_fields/formats/text'
370 370 self.change_as_diff = true
371 371
372 372 def formatted_value(view, custom_field, value, customized=nil, html=false)
373 373 if html
374 374 if value.present?
375 375 if custom_field.text_formatting == 'full'
376 376 view.textilizable(value, :object => customized)
377 377 else
378 378 view.simple_format(html_escape(value))
379 379 end
380 380 else
381 381 ''
382 382 end
383 383 else
384 384 value.to_s
385 385 end
386 386 end
387 387
388 388 def edit_tag(view, tag_id, tag_name, custom_value, options={})
389 389 view.text_area_tag(tag_name, custom_value.value, options.merge(:id => tag_id, :rows => 8))
390 390 end
391 391
392 392 def bulk_edit_tag(view, tag_id, tag_name, custom_field, objects, value, options={})
393 393 view.text_area_tag(tag_name, value, options.merge(:id => tag_id, :rows => 8)) +
394 394 '<br />'.html_safe +
395 395 bulk_clear_tag(view, tag_id, tag_name, custom_field, value)
396 396 end
397 397
398 398 def query_filter_options(custom_field, query)
399 399 {:type => :text}
400 400 end
401 401 end
402 402
403 403 class LinkFormat < StringFormat
404 404 add 'link'
405 405 self.searchable_supported = false
406 406 self.form_partial = 'custom_fields/formats/link'
407 407
408 408 def formatted_value(view, custom_field, value, customized=nil, html=false)
409 409 if html && value.present?
410 410 if custom_field.url_pattern.present?
411 411 url = url_from_pattern(custom_field, value, customized)
412 412 else
413 413 url = value.to_s
414 414 unless url =~ %r{\A[a-z]+://}i
415 415 # no protocol found, use http by default
416 416 url = "http://" + url
417 417 end
418 418 end
419 419 view.link_to value.to_s.truncate(40), url
420 420 else
421 421 value.to_s
422 422 end
423 423 end
424 424 end
425 425
426 426 class Numeric < Unbounded
427 427 self.form_partial = 'custom_fields/formats/numeric'
428 428 self.totalable_supported = true
429 429
430 430 def order_statement(custom_field)
431 431 # Make the database cast values into numeric
432 432 # Postgresql will raise an error if a value can not be casted!
433 433 # CustomValue validations should ensure that it doesn't occur
434 434 "CAST(CASE #{join_alias custom_field}.value WHEN '' THEN '0' ELSE #{join_alias custom_field}.value END AS decimal(30,3))"
435 435 end
436 436
437 437 # Returns totals for the given scope
438 438 def total_for_scope(custom_field, scope)
439 439 scope.joins(:custom_values).
440 440 where(:custom_values => {:custom_field_id => custom_field.id}).
441 441 where.not(:custom_values => {:value => ''}).
442 442 sum("CAST(#{CustomValue.table_name}.value AS decimal(30,3))")
443 443 end
444 444
445 445 def cast_total_value(custom_field, value)
446 446 cast_single_value(custom_field, value)
447 447 end
448 448 end
449 449
450 450 class IntFormat < Numeric
451 451 add 'int'
452 452
453 453 def label
454 454 "label_integer"
455 455 end
456 456
457 457 def cast_single_value(custom_field, value, customized=nil)
458 458 value.to_i
459 459 end
460 460
461 461 def validate_single_value(custom_field, value, customized=nil)
462 462 errs = super
463 463 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value.to_s =~ /^[+-]?\d+$/
464 464 errs
465 465 end
466 466
467 467 def query_filter_options(custom_field, query)
468 468 {:type => :integer}
469 469 end
470 470
471 471 def group_statement(custom_field)
472 472 order_statement(custom_field)
473 473 end
474 474 end
475 475
476 476 class FloatFormat < Numeric
477 477 add 'float'
478 478
479 479 def cast_single_value(custom_field, value, customized=nil)
480 480 value.to_f
481 481 end
482 482
483 483 def cast_total_value(custom_field, value)
484 484 value.to_f.round(2)
485 485 end
486 486
487 487 def validate_single_value(custom_field, value, customized=nil)
488 488 errs = super
489 489 errs << ::I18n.t('activerecord.errors.messages.invalid') unless (Kernel.Float(value) rescue nil)
490 490 errs
491 491 end
492 492
493 493 def query_filter_options(custom_field, query)
494 494 {:type => :float}
495 495 end
496 496 end
497 497
498 498 class DateFormat < Unbounded
499 499 add 'date'
500 500 self.form_partial = 'custom_fields/formats/date'
501 501
502 502 def cast_single_value(custom_field, value, customized=nil)
503 503 value.to_date rescue nil
504 504 end
505 505
506 506 def validate_single_value(custom_field, value, customized=nil)
507 507 if value =~ /^\d{4}-\d{2}-\d{2}$/ && (value.to_date rescue false)
508 508 []
509 509 else
510 510 [::I18n.t('activerecord.errors.messages.not_a_date')]
511 511 end
512 512 end
513 513
514 514 def edit_tag(view, tag_id, tag_name, custom_value, options={})
515 515 view.date_field_tag(tag_name, custom_value.value, options.merge(:id => tag_id, :size => 10)) +
516 516 view.calendar_for(tag_id)
517 517 end
518 518
519 519 def bulk_edit_tag(view, tag_id, tag_name, custom_field, objects, value, options={})
520 520 view.date_field_tag(tag_name, value, options.merge(:id => tag_id, :size => 10)) +
521 521 view.calendar_for(tag_id) +
522 522 bulk_clear_tag(view, tag_id, tag_name, custom_field, value)
523 523 end
524 524
525 525 def query_filter_options(custom_field, query)
526 526 {:type => :date}
527 527 end
528 528
529 529 def group_statement(custom_field)
530 530 order_statement(custom_field)
531 531 end
532 532 end
533 533
534 534 class List < Base
535 535 self.multiple_supported = true
536 536 field_attributes :edit_tag_style
537 537
538 538 def edit_tag(view, tag_id, tag_name, custom_value, options={})
539 539 if custom_value.custom_field.edit_tag_style == 'check_box'
540 540 check_box_edit_tag(view, tag_id, tag_name, custom_value, options)
541 541 else
542 542 select_edit_tag(view, tag_id, tag_name, custom_value, options)
543 543 end
544 544 end
545 545
546 546 def bulk_edit_tag(view, tag_id, tag_name, custom_field, objects, value, options={})
547 547 opts = []
548 548 opts << [l(:label_no_change_option), ''] unless custom_field.multiple?
549 549 opts << [l(:label_none), '__none__'] unless custom_field.is_required?
550 550 opts += possible_values_options(custom_field, objects)
551 551 view.select_tag(tag_name, view.options_for_select(opts, value), options.merge(:multiple => custom_field.multiple?))
552 552 end
553 553
554 554 def query_filter_options(custom_field, query)
555 555 {:type => :list_optional, :values => lambda { query_filter_values(custom_field, query) }}
556 556 end
557 557
558 558 protected
559 559
560 560 # Returns the values that are available in the field filter
561 561 def query_filter_values(custom_field, query)
562 562 possible_values_options(custom_field, query.project)
563 563 end
564 564
565 565 # Renders the edit tag as a select tag
566 566 def select_edit_tag(view, tag_id, tag_name, custom_value, options={})
567 567 blank_option = ''.html_safe
568 568 unless custom_value.custom_field.multiple?
569 569 if custom_value.custom_field.is_required?
570 570 unless custom_value.custom_field.default_value.present?
571 571 blank_option = view.content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
572 572 end
573 573 else
574 574 blank_option = view.content_tag('option', '&nbsp;'.html_safe, :value => '')
575 575 end
576 576 end
577 577 options_tags = blank_option + view.options_for_select(possible_custom_value_options(custom_value), custom_value.value)
578 578 s = view.select_tag(tag_name, options_tags, options.merge(:id => tag_id, :multiple => custom_value.custom_field.multiple?))
579 579 if custom_value.custom_field.multiple?
580 580 s << view.hidden_field_tag(tag_name, '')
581 581 end
582 582 s
583 583 end
584 584
585 585 # Renders the edit tag as check box or radio tags
586 586 def check_box_edit_tag(view, tag_id, tag_name, custom_value, options={})
587 587 opts = []
588 588 unless custom_value.custom_field.multiple? || custom_value.custom_field.is_required?
589 589 opts << ["(#{l(:label_none)})", '']
590 590 end
591 591 opts += possible_custom_value_options(custom_value)
592 592 s = ''.html_safe
593 593 tag_method = custom_value.custom_field.multiple? ? :check_box_tag : :radio_button_tag
594 594 opts.each do |label, value|
595 595 value ||= label
596 596 checked = (custom_value.value.is_a?(Array) && custom_value.value.include?(value)) || custom_value.value.to_s == value
597 597 tag = view.send(tag_method, tag_name, value, checked, :id => tag_id)
598 598 # set the id on the first tag only
599 599 tag_id = nil
600 600 s << view.content_tag('label', tag + ' ' + label)
601 601 end
602 602 if custom_value.custom_field.multiple?
603 603 s << view.hidden_field_tag(tag_name, '')
604 604 end
605 605 css = "#{options[:class]} check_box_group"
606 606 view.content_tag('span', s, options.merge(:class => css))
607 607 end
608 608 end
609 609
610 610 class ListFormat < List
611 611 add 'list'
612 612 self.searchable_supported = true
613 613 self.form_partial = 'custom_fields/formats/list'
614 614
615 615 def possible_custom_value_options(custom_value)
616 616 options = possible_values_options(custom_value.custom_field)
617 617 missing = [custom_value.value].flatten.reject(&:blank?) - options
618 618 if missing.any?
619 619 options += missing
620 620 end
621 621 options
622 622 end
623 623
624 624 def possible_values_options(custom_field, object=nil)
625 625 custom_field.possible_values
626 626 end
627 627
628 628 def validate_custom_field(custom_field)
629 629 errors = []
630 630 errors << [:possible_values, :blank] if custom_field.possible_values.blank?
631 631 errors << [:possible_values, :invalid] unless custom_field.possible_values.is_a? Array
632 632 errors
633 633 end
634 634
635 635 def validate_custom_value(custom_value)
636 636 values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''}
637 637 invalid_values = values - Array.wrap(custom_value.value_was) - custom_value.custom_field.possible_values
638 638 if invalid_values.any?
639 639 [::I18n.t('activerecord.errors.messages.inclusion')]
640 640 else
641 641 []
642 642 end
643 643 end
644 644
645 645 def group_statement(custom_field)
646 646 order_statement(custom_field)
647 647 end
648 648 end
649 649
650 650 class BoolFormat < List
651 651 add 'bool'
652 652 self.multiple_supported = false
653 653 self.form_partial = 'custom_fields/formats/bool'
654 654
655 655 def label
656 656 "label_boolean"
657 657 end
658 658
659 659 def cast_single_value(custom_field, value, customized=nil)
660 660 value == '1' ? true : false
661 661 end
662 662
663 663 def possible_values_options(custom_field, object=nil)
664 664 [[::I18n.t(:general_text_Yes), '1'], [::I18n.t(:general_text_No), '0']]
665 665 end
666 666
667 667 def group_statement(custom_field)
668 668 order_statement(custom_field)
669 669 end
670 670
671 671 def edit_tag(view, tag_id, tag_name, custom_value, options={})
672 672 case custom_value.custom_field.edit_tag_style
673 673 when 'check_box'
674 674 single_check_box_edit_tag(view, tag_id, tag_name, custom_value, options)
675 675 when 'radio'
676 676 check_box_edit_tag(view, tag_id, tag_name, custom_value, options)
677 677 else
678 678 select_edit_tag(view, tag_id, tag_name, custom_value, options)
679 679 end
680 680 end
681 681
682 682 # Renders the edit tag as a simple check box
683 683 def single_check_box_edit_tag(view, tag_id, tag_name, custom_value, options={})
684 684 s = ''.html_safe
685 685 s << view.hidden_field_tag(tag_name, '0', :id => nil)
686 686 s << view.check_box_tag(tag_name, '1', custom_value.value.to_s == '1', :id => tag_id)
687 687 view.content_tag('span', s, options)
688 688 end
689 689 end
690 690
691 691 class RecordList < List
692 692 self.customized_class_names = %w(Issue TimeEntry Version Document Project)
693 693
694 694 def cast_single_value(custom_field, value, customized=nil)
695 695 target_class.find_by_id(value.to_i) if value.present?
696 696 end
697 697
698 698 def target_class
699 699 @target_class ||= self.class.name[/^(.*::)?(.+)Format$/, 2].constantize rescue nil
700 700 end
701 701
702 702 def reset_target_class
703 703 @target_class = nil
704 704 end
705 705
706 706 def possible_custom_value_options(custom_value)
707 707 options = possible_values_options(custom_value.custom_field, custom_value.customized)
708 708 missing = [custom_value.value_was].flatten.reject(&:blank?) - options.map(&:last)
709 709 if missing.any?
710 710 options += target_class.where(:id => missing.map(&:to_i)).map {|o| [o.to_s, o.id.to_s]}
711 711 end
712 712 options
713 713 end
714 714
715 715 def order_statement(custom_field)
716 716 if target_class.respond_to?(:fields_for_order_statement)
717 717 target_class.fields_for_order_statement(value_join_alias(custom_field))
718 718 end
719 719 end
720 720
721 721 def group_statement(custom_field)
722 722 "COALESCE(#{join_alias custom_field}.value, '')"
723 723 end
724 724
725 725 def join_for_order_statement(custom_field)
726 726 alias_name = join_alias(custom_field)
727 727
728 728 "LEFT OUTER JOIN #{CustomValue.table_name} #{alias_name}" +
729 729 " ON #{alias_name}.customized_type = '#{custom_field.class.customized_class.base_class.name}'" +
730 730 " AND #{alias_name}.customized_id = #{custom_field.class.customized_class.table_name}.id" +
731 731 " AND #{alias_name}.custom_field_id = #{custom_field.id}" +
732 732 " AND (#{custom_field.visibility_by_project_condition})" +
733 733 " AND #{alias_name}.value <> ''" +
734 734 " AND #{alias_name}.id = (SELECT max(#{alias_name}_2.id) FROM #{CustomValue.table_name} #{alias_name}_2" +
735 735 " WHERE #{alias_name}_2.customized_type = #{alias_name}.customized_type" +
736 736 " AND #{alias_name}_2.customized_id = #{alias_name}.customized_id" +
737 737 " AND #{alias_name}_2.custom_field_id = #{alias_name}.custom_field_id)" +
738 738 " LEFT OUTER JOIN #{target_class.table_name} #{value_join_alias custom_field}" +
739 739 " ON CAST(CASE #{alias_name}.value WHEN '' THEN '0' ELSE #{alias_name}.value END AS decimal(30,0)) = #{value_join_alias custom_field}.id"
740 740 end
741 741
742 742 def value_join_alias(custom_field)
743 743 join_alias(custom_field) + "_" + custom_field.field_format
744 744 end
745 745 protected :value_join_alias
746 746 end
747 747
748 748 class EnumerationFormat < RecordList
749 749 add 'enumeration'
750 750 self.form_partial = 'custom_fields/formats/enumeration'
751 751
752 752 def label
753 753 "label_field_format_enumeration"
754 754 end
755 755
756 756 def target_class
757 757 @target_class ||= CustomFieldEnumeration
758 758 end
759 759
760 760 def possible_values_options(custom_field, object=nil)
761 761 possible_values_records(custom_field, object).map {|u| [u.name, u.id.to_s]}
762 762 end
763 763
764 764 def possible_values_records(custom_field, object=nil)
765 765 custom_field.enumerations.active
766 766 end
767 767
768 768 def value_from_keyword(custom_field, keyword, object)
769 769 value = custom_field.enumerations.where("LOWER(name) LIKE LOWER(?)", keyword).first
770 770 value ? value.id : nil
771 771 end
772 772 end
773 773
774 774 class UserFormat < RecordList
775 775 add 'user'
776 776 self.form_partial = 'custom_fields/formats/user'
777 777 field_attributes :user_role
778 778
779 779 def possible_values_options(custom_field, object=nil)
780 780 possible_values_records(custom_field, object).map {|u| [u.name, u.id.to_s]}
781 781 end
782 782
783 783 def possible_values_records(custom_field, object=nil)
784 784 if object.is_a?(Array)
785 785 projects = object.map {|o| o.respond_to?(:project) ? o.project : nil}.compact.uniq
786 786 projects.map {|project| possible_values_records(custom_field, project)}.reduce(:&) || []
787 787 elsif object.respond_to?(:project) && object.project
788 788 scope = object.project.users
789 789 if custom_field.user_role.is_a?(Array)
790 790 role_ids = custom_field.user_role.map(&:to_s).reject(&:blank?).map(&:to_i)
791 791 if role_ids.any?
792 792 scope = scope.where("#{Member.table_name}.id IN (SELECT DISTINCT member_id FROM #{MemberRole.table_name} WHERE role_id IN (?))", role_ids)
793 793 end
794 794 end
795 795 scope.sorted
796 elsif object.nil?
797 Principal.member_of(Project.visible.to_a).sorted.select {|p| p.is_a?(User)}
796 798 else
797 799 []
798 800 end
799 801 end
800 802
801 803 def value_from_keyword(custom_field, keyword, object)
802 804 users = possible_values_records(custom_field, object).to_a
803 805 user = Principal.detect_by_keyword(users, keyword)
804 806 user ? user.id : nil
805 807 end
806 808
807 809 def before_custom_field_save(custom_field)
808 810 super
809 811 if custom_field.user_role.is_a?(Array)
810 812 custom_field.user_role.map!(&:to_s).reject!(&:blank?)
811 813 end
812 814 end
813 815
814 def query_filter_values(*args)
815 values = []
816 if User.current.logged?
817 values << ["<< #{l(:label_me)} >>", "me"]
818 end
819 values + super
816 def query_filter_values(custom_field, query)
817 query.author_values
820 818 end
821 819 end
822 820
823 821 class VersionFormat < RecordList
824 822 add 'version'
825 823 self.form_partial = 'custom_fields/formats/version'
826 824 field_attributes :version_status
827 825
828 826 def possible_values_options(custom_field, object=nil)
829 827 possible_values_records(custom_field, object).sort.collect{|v| [v.to_s, v.id.to_s] }
830 828 end
831 829
832 830 def before_custom_field_save(custom_field)
833 831 super
834 832 if custom_field.version_status.is_a?(Array)
835 833 custom_field.version_status.map!(&:to_s).reject!(&:blank?)
836 834 end
837 835 end
838 836
839 837 protected
840 838
841 839 def query_filter_values(custom_field, query)
842 840 versions = possible_values_records(custom_field, query.project, true)
843 841 Version.sort_by_status(versions).collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s, l("version_status_#{s.status}")] }
844 842 end
845 843
846 844 def possible_values_records(custom_field, object=nil, all_statuses=false)
847 845 if object.is_a?(Array)
848 846 projects = object.map {|o| o.respond_to?(:project) ? o.project : nil}.compact.uniq
849 847 projects.map {|project| possible_values_records(custom_field, project)}.reduce(:&) || []
850 848 elsif object.respond_to?(:project) && object.project
851 849 scope = object.project.shared_versions
852 850 filtered_versions_options(custom_field, scope, all_statuses)
853 851 elsif object.nil?
854 852 scope = ::Version.visible.where(:sharing => 'system')
855 853 filtered_versions_options(custom_field, scope, all_statuses)
856 854 else
857 855 []
858 856 end
859 857 end
860 858
861 859 def filtered_versions_options(custom_field, scope, all_statuses=false)
862 860 if !all_statuses && custom_field.version_status.is_a?(Array)
863 861 statuses = custom_field.version_status.map(&:to_s).reject(&:blank?)
864 862 if statuses.any?
865 863 scope = scope.where(:status => statuses.map(&:to_s))
866 864 end
867 865 end
868 866 scope
869 867 end
870 868 end
871 869
872 870 class AttachmentFormat < Base
873 871 add 'attachment'
874 872 self.form_partial = 'custom_fields/formats/attachment'
875 873 self.is_filter_supported = false
876 874 self.change_no_details = true
877 875 self.bulk_edit_supported = false
878 876 field_attributes :extensions_allowed
879 877
880 878 def set_custom_field_value(custom_field, custom_field_value, value)
881 879 attachment_present = false
882 880
883 881 if value.is_a?(Hash)
884 882 attachment_present = true
885 883 value = value.except(:blank)
886 884
887 885 if value.values.any? && value.values.all? {|v| v.is_a?(Hash)}
888 886 value = value.values.first
889 887 end
890 888
891 889 if value.key?(:id)
892 890 value = set_custom_field_value_by_id(custom_field, custom_field_value, value[:id])
893 891 elsif value[:token].present?
894 892 if attachment = Attachment.find_by_token(value[:token])
895 893 value = attachment.id.to_s
896 894 else
897 895 value = ''
898 896 end
899 897 elsif value.key?(:file)
900 898 attachment = Attachment.new(:file => value[:file], :author => User.current)
901 899 if attachment.save
902 900 value = attachment.id.to_s
903 901 else
904 902 value = ''
905 903 end
906 904 else
907 905 attachment_present = false
908 906 value = ''
909 907 end
910 908 elsif value.is_a?(String)
911 909 value = set_custom_field_value_by_id(custom_field, custom_field_value, value)
912 910 end
913 911 custom_field_value.instance_variable_set "@attachment_present", attachment_present
914 912
915 913 value
916 914 end
917 915
918 916 def set_custom_field_value_by_id(custom_field, custom_field_value, id)
919 917 attachment = Attachment.find_by_id(id)
920 918 if attachment && attachment.container.is_a?(CustomValue) && attachment.container.customized == custom_field_value.customized
921 919 id.to_s
922 920 else
923 921 ''
924 922 end
925 923 end
926 924 private :set_custom_field_value_by_id
927 925
928 926 def cast_single_value(custom_field, value, customized=nil)
929 927 Attachment.find_by_id(value.to_i) if value.present? && value.respond_to?(:to_i)
930 928 end
931 929
932 930 def validate_custom_value(custom_value)
933 931 errors = []
934 932
935 933 if custom_value.value.blank?
936 934 if custom_value.instance_variable_get("@attachment_present")
937 935 errors << ::I18n.t('activerecord.errors.messages.invalid')
938 936 end
939 937 else
940 938 if custom_value.value.present?
941 939 attachment = Attachment.where(:id => custom_value.value.to_s).first
942 940 extensions = custom_value.custom_field.extensions_allowed
943 941 if attachment && extensions.present? && !attachment.extension_in?(extensions)
944 942 errors << "#{::I18n.t('activerecord.errors.messages.invalid')} (#{l(:setting_attachment_extensions_allowed)}: #{extensions})"
945 943 end
946 944 end
947 945 end
948 946
949 947 errors.uniq
950 948 end
951 949
952 950 def after_save_custom_value(custom_field, custom_value)
953 951 if custom_value.value_changed?
954 952 if custom_value.value.present?
955 953 attachment = Attachment.where(:id => custom_value.value.to_s).first
956 954 if attachment
957 955 attachment.container = custom_value
958 956 attachment.save!
959 957 end
960 958 end
961 959 if custom_value.value_was.present?
962 960 attachment = Attachment.where(:id => custom_value.value_was.to_s).first
963 961 if attachment
964 962 attachment.destroy
965 963 end
966 964 end
967 965 end
968 966 end
969 967
970 968 def edit_tag(view, tag_id, tag_name, custom_value, options={})
971 969 attachment = nil
972 970 if custom_value.value.present?
973 971 attachment = Attachment.find_by_id(custom_value.value)
974 972 end
975 973
976 974 view.hidden_field_tag("#{tag_name}[blank]", "") +
977 975 view.render(:partial => 'attachments/form',
978 976 :locals => {
979 977 :attachment_param => tag_name,
980 978 :multiple => false,
981 979 :description => false,
982 980 :saved_attachments => [attachment].compact,
983 981 :filedrop => false
984 982 })
985 983 end
986 984 end
987 985 end
988 986 end
General Comments 0
You need to be logged in to leave comments. Login now