Mercurial > hg > soundsoftware-site
comparison lib/redmine/field_format.rb @ 1517:dffacf8a6908 redmine-2.5
Update to Redmine SVN revision 13367 on 2.5-stable branch
author | Chris Cannam |
---|---|
date | Tue, 09 Sep 2014 09:29:00 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1516:b450a9d58aed | 1517:dffacf8a6908 |
---|---|
1 # Redmine - project management software | |
2 # Copyright (C) 2006-2014 Jean-Philippe Lang | |
3 # | |
4 # This program is free software; you can redistribute it and/or | |
5 # modify it under the terms of the GNU General Public License | |
6 # as published by the Free Software Foundation; either version 2 | |
7 # of the License, or (at your option) any later version. | |
8 # | |
9 # This program is distributed in the hope that it will be useful, | |
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 # GNU General Public License for more details. | |
13 # | |
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 | |
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 | |
18 module Redmine | |
19 module FieldFormat | |
20 def self.add(name, klass) | |
21 all[name.to_s] = klass.instance | |
22 end | |
23 | |
24 def self.delete(name) | |
25 all.delete(name.to_s) | |
26 end | |
27 | |
28 def self.all | |
29 @formats ||= Hash.new(Base.instance) | |
30 end | |
31 | |
32 def self.available_formats | |
33 all.keys | |
34 end | |
35 | |
36 def self.find(name) | |
37 all[name.to_s] | |
38 end | |
39 | |
40 # Return an array of custom field formats which can be used in select_tag | |
41 def self.as_select(class_name=nil) | |
42 formats = all.values.select do |format| | |
43 format.class.customized_class_names.nil? || format.class.customized_class_names.include?(class_name) | |
44 end | |
45 formats.map {|format| [::I18n.t(format.label), format.name] }.sort_by(&:first) | |
46 end | |
47 | |
48 class Base | |
49 include Singleton | |
50 include Redmine::I18n | |
51 include ERB::Util | |
52 | |
53 class_attribute :format_name | |
54 self.format_name = nil | |
55 | |
56 # Set this to true if the format supports multiple values | |
57 class_attribute :multiple_supported | |
58 self.multiple_supported = false | |
59 | |
60 # Set this to true if the format supports textual search on custom values | |
61 class_attribute :searchable_supported | |
62 self.searchable_supported = false | |
63 | |
64 # Restricts the classes that the custom field can be added to | |
65 # Set to nil for no restrictions | |
66 class_attribute :customized_class_names | |
67 self.customized_class_names = nil | |
68 | |
69 # Name of the partial for editing the custom field | |
70 class_attribute :form_partial | |
71 self.form_partial = nil | |
72 | |
73 def self.add(name) | |
74 self.format_name = name | |
75 Redmine::FieldFormat.add(name, self) | |
76 end | |
77 private_class_method :add | |
78 | |
79 def self.field_attributes(*args) | |
80 CustomField.store_accessor :format_store, *args | |
81 end | |
82 | |
83 field_attributes :url_pattern | |
84 | |
85 def name | |
86 self.class.format_name | |
87 end | |
88 | |
89 def label | |
90 "label_#{name}" | |
91 end | |
92 | |
93 def cast_custom_value(custom_value) | |
94 cast_value(custom_value.custom_field, custom_value.value, custom_value.customized) | |
95 end | |
96 | |
97 def cast_value(custom_field, value, customized=nil) | |
98 if value.blank? | |
99 nil | |
100 elsif value.is_a?(Array) | |
101 casted = value.map do |v| | |
102 cast_single_value(custom_field, v, customized) | |
103 end | |
104 casted.compact.sort | |
105 else | |
106 cast_single_value(custom_field, value, customized) | |
107 end | |
108 end | |
109 | |
110 def cast_single_value(custom_field, value, customized=nil) | |
111 value.to_s | |
112 end | |
113 | |
114 def target_class | |
115 nil | |
116 end | |
117 | |
118 def possible_custom_value_options(custom_value) | |
119 possible_values_options(custom_value.custom_field, custom_value.customized) | |
120 end | |
121 | |
122 def possible_values_options(custom_field, object=nil) | |
123 [] | |
124 end | |
125 | |
126 # Returns the validation errors for custom_field | |
127 # Should return an empty array if custom_field is valid | |
128 def validate_custom_field(custom_field) | |
129 [] | |
130 end | |
131 | |
132 # Returns the validation error messages for custom_value | |
133 # Should return an empty array if custom_value is valid | |
134 def validate_custom_value(custom_value) | |
135 values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''} | |
136 errors = values.map do |value| | |
137 validate_single_value(custom_value.custom_field, value, custom_value.customized) | |
138 end | |
139 errors.flatten.uniq | |
140 end | |
141 | |
142 def validate_single_value(custom_field, value, customized=nil) | |
143 [] | |
144 end | |
145 | |
146 def formatted_custom_value(view, custom_value, html=false) | |
147 formatted_value(view, custom_value.custom_field, custom_value.value, custom_value.customized, html) | |
148 end | |
149 | |
150 def formatted_value(view, custom_field, value, customized=nil, html=false) | |
151 casted = cast_value(custom_field, value, customized) | |
152 if html && custom_field.url_pattern.present? | |
153 texts_and_urls = Array.wrap(casted).map do |single_value| | |
154 text = view.format_object(single_value, false).to_s | |
155 url = url_from_pattern(custom_field, single_value, customized) | |
156 [text, url] | |
157 end | |
158 links = texts_and_urls.sort_by(&:first).map {|text, url| view.link_to text, url} | |
159 links.join(', ').html_safe | |
160 else | |
161 casted | |
162 end | |
163 end | |
164 | |
165 # Returns an URL generated with the custom field URL pattern | |
166 # and variables substitution: | |
167 # %value% => the custom field value | |
168 # %id% => id of the customized object | |
169 # %project_id% => id of the project of the customized object if defined | |
170 # %project_identifier% => identifier of the project of the customized object if defined | |
171 # %m1%, %m2%... => capture groups matches of the custom field regexp if defined | |
172 def url_from_pattern(custom_field, value, customized) | |
173 url = custom_field.url_pattern.to_s.dup | |
174 url.gsub!('%value%') {value.to_s} | |
175 url.gsub!('%id%') {customized.id.to_s} | |
176 url.gsub!('%project_id%') {(customized.respond_to?(:project) ? customized.project.try(:id) : nil).to_s} | |
177 url.gsub!('%project_identifier%') {(customized.respond_to?(:project) ? customized.project.try(:identifier) : nil).to_s} | |
178 if custom_field.regexp.present? | |
179 url.gsub!(%r{%m(\d+)%}) do | |
180 m = $1.to_i | |
181 if matches ||= value.to_s.match(Regexp.new(custom_field.regexp)) | |
182 matches[m].to_s | |
183 end | |
184 end | |
185 end | |
186 url | |
187 end | |
188 protected :url_from_pattern | |
189 | |
190 def edit_tag(view, tag_id, tag_name, custom_value, options={}) | |
191 view.text_field_tag(tag_name, custom_value.value, options.merge(:id => tag_id)) | |
192 end | |
193 | |
194 def bulk_edit_tag(view, tag_id, tag_name, custom_field, objects, value, options={}) | |
195 view.text_field_tag(tag_name, value, options.merge(:id => tag_id)) + | |
196 bulk_clear_tag(view, tag_id, tag_name, custom_field, value) | |
197 end | |
198 | |
199 def bulk_clear_tag(view, tag_id, tag_name, custom_field, value) | |
200 if custom_field.is_required? | |
201 ''.html_safe | |
202 else | |
203 view.content_tag('label', | |
204 view.check_box_tag(tag_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{tag_id}"}) + l(:button_clear), | |
205 :class => 'inline' | |
206 ) | |
207 end | |
208 end | |
209 protected :bulk_clear_tag | |
210 | |
211 def query_filter_options(custom_field, query) | |
212 {:type => :string} | |
213 end | |
214 | |
215 def before_custom_field_save(custom_field) | |
216 end | |
217 | |
218 # Returns a ORDER BY clause that can used to sort customized | |
219 # objects by their value of the custom field. | |
220 # Returns nil if the custom field can not be used for sorting. | |
221 def order_statement(custom_field) | |
222 # COALESCE is here to make sure that blank and NULL values are sorted equally | |
223 "COALESCE(#{join_alias custom_field}.value, '')" | |
224 end | |
225 | |
226 # Returns a GROUP BY clause that can used to group by custom value | |
227 # Returns nil if the custom field can not be used for grouping. | |
228 def group_statement(custom_field) | |
229 nil | |
230 end | |
231 | |
232 # Returns a JOIN clause that is added to the query when sorting by custom values | |
233 def join_for_order_statement(custom_field) | |
234 alias_name = join_alias(custom_field) | |
235 | |
236 "LEFT OUTER JOIN #{CustomValue.table_name} #{alias_name}" + | |
237 " ON #{alias_name}.customized_type = '#{custom_field.class.customized_class.base_class.name}'" + | |
238 " AND #{alias_name}.customized_id = #{custom_field.class.customized_class.table_name}.id" + | |
239 " AND #{alias_name}.custom_field_id = #{custom_field.id}" + | |
240 " AND (#{custom_field.visibility_by_project_condition})" + | |
241 " AND #{alias_name}.value <> ''" + | |
242 " AND #{alias_name}.id = (SELECT max(#{alias_name}_2.id) FROM #{CustomValue.table_name} #{alias_name}_2" + | |
243 " WHERE #{alias_name}_2.customized_type = #{alias_name}.customized_type" + | |
244 " AND #{alias_name}_2.customized_id = #{alias_name}.customized_id" + | |
245 " AND #{alias_name}_2.custom_field_id = #{alias_name}.custom_field_id)" | |
246 end | |
247 | |
248 def join_alias(custom_field) | |
249 "cf_#{custom_field.id}" | |
250 end | |
251 protected :join_alias | |
252 end | |
253 | |
254 class Unbounded < Base | |
255 def validate_single_value(custom_field, value, customized=nil) | |
256 errs = super | |
257 value = value.to_s | |
258 unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp) | |
259 errs << ::I18n.t('activerecord.errors.messages.invalid') | |
260 end | |
261 if custom_field.min_length && value.length < custom_field.min_length | |
262 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => custom_field.min_length) | |
263 end | |
264 if custom_field.max_length && custom_field.max_length > 0 && value.length > custom_field.max_length | |
265 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => custom_field.max_length) | |
266 end | |
267 errs | |
268 end | |
269 end | |
270 | |
271 class StringFormat < Unbounded | |
272 add 'string' | |
273 self.searchable_supported = true | |
274 self.form_partial = 'custom_fields/formats/string' | |
275 field_attributes :text_formatting | |
276 | |
277 def formatted_value(view, custom_field, value, customized=nil, html=false) | |
278 if html | |
279 if custom_field.url_pattern.present? | |
280 super | |
281 elsif custom_field.text_formatting == 'full' | |
282 view.textilizable(value, :object => customized) | |
283 else | |
284 value.to_s | |
285 end | |
286 else | |
287 value.to_s | |
288 end | |
289 end | |
290 end | |
291 | |
292 class TextFormat < Unbounded | |
293 add 'text' | |
294 self.searchable_supported = true | |
295 self.form_partial = 'custom_fields/formats/text' | |
296 | |
297 def formatted_value(view, custom_field, value, customized=nil, html=false) | |
298 if html | |
299 if custom_field.text_formatting == 'full' | |
300 view.textilizable(value, :object => customized) | |
301 else | |
302 view.simple_format(html_escape(value)) | |
303 end | |
304 else | |
305 value.to_s | |
306 end | |
307 end | |
308 | |
309 def edit_tag(view, tag_id, tag_name, custom_value, options={}) | |
310 view.text_area_tag(tag_name, custom_value.value, options.merge(:id => tag_id, :rows => 3)) | |
311 end | |
312 | |
313 def bulk_edit_tag(view, tag_id, tag_name, custom_field, objects, value, options={}) | |
314 view.text_area_tag(tag_name, value, options.merge(:id => tag_id, :rows => 3)) + | |
315 '<br />'.html_safe + | |
316 bulk_clear_tag(view, tag_id, tag_name, custom_field, value) | |
317 end | |
318 | |
319 def query_filter_options(custom_field, query) | |
320 {:type => :text} | |
321 end | |
322 end | |
323 | |
324 class LinkFormat < StringFormat | |
325 add 'link' | |
326 self.searchable_supported = false | |
327 self.form_partial = 'custom_fields/formats/link' | |
328 | |
329 def formatted_value(view, custom_field, value, customized=nil, html=false) | |
330 if html | |
331 if custom_field.url_pattern.present? | |
332 url = url_from_pattern(custom_field, value, customized) | |
333 else | |
334 url = value.to_s | |
335 unless url =~ %r{\A[a-z]+://}i | |
336 # no protocol found, use http by default | |
337 url = "http://" + url | |
338 end | |
339 end | |
340 view.link_to value.to_s, url | |
341 else | |
342 value.to_s | |
343 end | |
344 end | |
345 end | |
346 | |
347 class Numeric < Unbounded | |
348 self.form_partial = 'custom_fields/formats/numeric' | |
349 | |
350 def order_statement(custom_field) | |
351 # Make the database cast values into numeric | |
352 # Postgresql will raise an error if a value can not be casted! | |
353 # CustomValue validations should ensure that it doesn't occur | |
354 "CAST(CASE #{join_alias custom_field}.value WHEN '' THEN '0' ELSE #{join_alias custom_field}.value END AS decimal(30,3))" | |
355 end | |
356 end | |
357 | |
358 class IntFormat < Numeric | |
359 add 'int' | |
360 | |
361 def label | |
362 "label_integer" | |
363 end | |
364 | |
365 def cast_single_value(custom_field, value, customized=nil) | |
366 value.to_i | |
367 end | |
368 | |
369 def validate_single_value(custom_field, value, customized=nil) | |
370 errs = super | |
371 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/ | |
372 errs | |
373 end | |
374 | |
375 def query_filter_options(custom_field, query) | |
376 {:type => :integer} | |
377 end | |
378 | |
379 def group_statement(custom_field) | |
380 order_statement(custom_field) | |
381 end | |
382 end | |
383 | |
384 class FloatFormat < Numeric | |
385 add 'float' | |
386 | |
387 def cast_single_value(custom_field, value, customized=nil) | |
388 value.to_f | |
389 end | |
390 | |
391 def validate_single_value(custom_field, value, customized=nil) | |
392 errs = super | |
393 errs << ::I18n.t('activerecord.errors.messages.invalid') unless (Kernel.Float(value) rescue nil) | |
394 errs | |
395 end | |
396 | |
397 def query_filter_options(custom_field, query) | |
398 {:type => :float} | |
399 end | |
400 end | |
401 | |
402 class DateFormat < Unbounded | |
403 add 'date' | |
404 self.form_partial = 'custom_fields/formats/date' | |
405 | |
406 def cast_single_value(custom_field, value, customized=nil) | |
407 value.to_date rescue nil | |
408 end | |
409 | |
410 def validate_single_value(custom_field, value, customized=nil) | |
411 if value =~ /^\d{4}-\d{2}-\d{2}$/ && (value.to_date rescue false) | |
412 [] | |
413 else | |
414 [::I18n.t('activerecord.errors.messages.not_a_date')] | |
415 end | |
416 end | |
417 | |
418 def edit_tag(view, tag_id, tag_name, custom_value, options={}) | |
419 view.text_field_tag(tag_name, custom_value.value, options.merge(:id => tag_id, :size => 10)) + | |
420 view.calendar_for(tag_id) | |
421 end | |
422 | |
423 def bulk_edit_tag(view, tag_id, tag_name, custom_field, objects, value, options={}) | |
424 view.text_field_tag(tag_name, value, options.merge(:id => tag_id, :size => 10)) + | |
425 view.calendar_for(tag_id) + | |
426 bulk_clear_tag(view, tag_id, tag_name, custom_field, value) | |
427 end | |
428 | |
429 def query_filter_options(custom_field, query) | |
430 {:type => :date} | |
431 end | |
432 | |
433 def group_statement(custom_field) | |
434 order_statement(custom_field) | |
435 end | |
436 end | |
437 | |
438 class List < Base | |
439 self.multiple_supported = true | |
440 field_attributes :edit_tag_style | |
441 | |
442 def edit_tag(view, tag_id, tag_name, custom_value, options={}) | |
443 if custom_value.custom_field.edit_tag_style == 'check_box' | |
444 check_box_edit_tag(view, tag_id, tag_name, custom_value, options) | |
445 else | |
446 select_edit_tag(view, tag_id, tag_name, custom_value, options) | |
447 end | |
448 end | |
449 | |
450 def bulk_edit_tag(view, tag_id, tag_name, custom_field, objects, value, options={}) | |
451 opts = [] | |
452 opts << [l(:label_no_change_option), ''] unless custom_field.multiple? | |
453 opts << [l(:label_none), '__none__'] unless custom_field.is_required? | |
454 opts += possible_values_options(custom_field, objects) | |
455 view.select_tag(tag_name, view.options_for_select(opts, value), options.merge(:multiple => custom_field.multiple?)) | |
456 end | |
457 | |
458 def query_filter_options(custom_field, query) | |
459 {:type => :list_optional, :values => possible_values_options(custom_field, query.project)} | |
460 end | |
461 | |
462 protected | |
463 | |
464 # Renders the edit tag as a select tag | |
465 def select_edit_tag(view, tag_id, tag_name, custom_value, options={}) | |
466 blank_option = ''.html_safe | |
467 unless custom_value.custom_field.multiple? | |
468 if custom_value.custom_field.is_required? | |
469 unless custom_value.custom_field.default_value.present? | |
470 blank_option = view.content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '') | |
471 end | |
472 else | |
473 blank_option = view.content_tag('option', ' '.html_safe, :value => '') | |
474 end | |
475 end | |
476 options_tags = blank_option + view.options_for_select(possible_custom_value_options(custom_value), custom_value.value) | |
477 s = view.select_tag(tag_name, options_tags, options.merge(:id => tag_id, :multiple => custom_value.custom_field.multiple?)) | |
478 if custom_value.custom_field.multiple? | |
479 s << view.hidden_field_tag(tag_name, '') | |
480 end | |
481 s | |
482 end | |
483 | |
484 # Renders the edit tag as check box or radio tags | |
485 def check_box_edit_tag(view, tag_id, tag_name, custom_value, options={}) | |
486 opts = [] | |
487 unless custom_value.custom_field.multiple? || custom_value.custom_field.is_required? | |
488 opts << ["(#{l(:label_none)})", ''] | |
489 end | |
490 opts += possible_custom_value_options(custom_value) | |
491 s = ''.html_safe | |
492 tag_method = custom_value.custom_field.multiple? ? :check_box_tag : :radio_button_tag | |
493 opts.each do |label, value| | |
494 value ||= label | |
495 checked = (custom_value.value.is_a?(Array) && custom_value.value.include?(value)) || custom_value.value.to_s == value | |
496 tag = view.send(tag_method, tag_name, value, checked, :id => tag_id) | |
497 # set the id on the first tag only | |
498 tag_id = nil | |
499 s << view.content_tag('label', tag + ' ' + label) | |
500 end | |
501 if custom_value.custom_field.multiple? | |
502 s << view.hidden_field_tag(tag_name, '') | |
503 end | |
504 css = "#{options[:class]} check_box_group" | |
505 view.content_tag('span', s, options.merge(:class => css)) | |
506 end | |
507 end | |
508 | |
509 class ListFormat < List | |
510 add 'list' | |
511 self.searchable_supported = true | |
512 self.form_partial = 'custom_fields/formats/list' | |
513 | |
514 def possible_custom_value_options(custom_value) | |
515 options = possible_values_options(custom_value.custom_field) | |
516 missing = [custom_value.value].flatten.reject(&:blank?) - options | |
517 if missing.any? | |
518 options += missing | |
519 end | |
520 options | |
521 end | |
522 | |
523 def possible_values_options(custom_field, object=nil) | |
524 custom_field.possible_values | |
525 end | |
526 | |
527 def validate_custom_field(custom_field) | |
528 errors = [] | |
529 errors << [:possible_values, :blank] if custom_field.possible_values.blank? | |
530 errors << [:possible_values, :invalid] unless custom_field.possible_values.is_a? Array | |
531 errors | |
532 end | |
533 | |
534 def validate_custom_value(custom_value) | |
535 values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''} | |
536 invalid_values = values - Array.wrap(custom_value.value_was) - custom_value.custom_field.possible_values | |
537 if invalid_values.any? | |
538 [::I18n.t('activerecord.errors.messages.inclusion')] | |
539 else | |
540 [] | |
541 end | |
542 end | |
543 | |
544 def group_statement(custom_field) | |
545 order_statement(custom_field) | |
546 end | |
547 end | |
548 | |
549 class BoolFormat < List | |
550 add 'bool' | |
551 self.multiple_supported = false | |
552 self.form_partial = 'custom_fields/formats/bool' | |
553 | |
554 def label | |
555 "label_boolean" | |
556 end | |
557 | |
558 def cast_single_value(custom_field, value, customized=nil) | |
559 value == '1' ? true : false | |
560 end | |
561 | |
562 def possible_values_options(custom_field, object=nil) | |
563 [[::I18n.t(:general_text_Yes), '1'], [::I18n.t(:general_text_No), '0']] | |
564 end | |
565 | |
566 def group_statement(custom_field) | |
567 order_statement(custom_field) | |
568 end | |
569 | |
570 def edit_tag(view, tag_id, tag_name, custom_value, options={}) | |
571 case custom_value.custom_field.edit_tag_style | |
572 when 'check_box' | |
573 single_check_box_edit_tag(view, tag_id, tag_name, custom_value, options) | |
574 when 'radio' | |
575 check_box_edit_tag(view, tag_id, tag_name, custom_value, options) | |
576 else | |
577 select_edit_tag(view, tag_id, tag_name, custom_value, options) | |
578 end | |
579 end | |
580 | |
581 # Renders the edit tag as a simple check box | |
582 def single_check_box_edit_tag(view, tag_id, tag_name, custom_value, options={}) | |
583 s = ''.html_safe | |
584 s << view.hidden_field_tag(tag_name, '0', :id => nil) | |
585 s << view.check_box_tag(tag_name, '1', custom_value.value.to_s == '1', :id => tag_id) | |
586 view.content_tag('span', s, options) | |
587 end | |
588 end | |
589 | |
590 class RecordList < List | |
591 self.customized_class_names = %w(Issue TimeEntry Version Project) | |
592 | |
593 def cast_single_value(custom_field, value, customized=nil) | |
594 target_class.find_by_id(value.to_i) if value.present? | |
595 end | |
596 | |
597 def target_class | |
598 @target_class ||= self.class.name[/^(.*::)?(.+)Format$/, 2].constantize rescue nil | |
599 end | |
600 | |
601 def possible_custom_value_options(custom_value) | |
602 options = possible_values_options(custom_value.custom_field, custom_value.customized) | |
603 missing = [custom_value.value_was].flatten.reject(&:blank?) - options.map(&:last) | |
604 if missing.any? | |
605 options += target_class.where(:id => missing.map(&:to_i)).map {|o| [o.to_s, o.id.to_s]} | |
606 #TODO: use #sort_by! when ruby1.8 support is dropped | |
607 options = options.sort_by(&:first) | |
608 end | |
609 options | |
610 end | |
611 | |
612 def order_statement(custom_field) | |
613 if target_class.respond_to?(:fields_for_order_statement) | |
614 target_class.fields_for_order_statement(value_join_alias(custom_field)) | |
615 end | |
616 end | |
617 | |
618 def group_statement(custom_field) | |
619 "COALESCE(#{join_alias custom_field}.value, '')" | |
620 end | |
621 | |
622 def join_for_order_statement(custom_field) | |
623 alias_name = join_alias(custom_field) | |
624 | |
625 "LEFT OUTER JOIN #{CustomValue.table_name} #{alias_name}" + | |
626 " ON #{alias_name}.customized_type = '#{custom_field.class.customized_class.base_class.name}'" + | |
627 " AND #{alias_name}.customized_id = #{custom_field.class.customized_class.table_name}.id" + | |
628 " AND #{alias_name}.custom_field_id = #{custom_field.id}" + | |
629 " AND (#{custom_field.visibility_by_project_condition})" + | |
630 " AND #{alias_name}.value <> ''" + | |
631 " AND #{alias_name}.id = (SELECT max(#{alias_name}_2.id) FROM #{CustomValue.table_name} #{alias_name}_2" + | |
632 " WHERE #{alias_name}_2.customized_type = #{alias_name}.customized_type" + | |
633 " AND #{alias_name}_2.customized_id = #{alias_name}.customized_id" + | |
634 " AND #{alias_name}_2.custom_field_id = #{alias_name}.custom_field_id)" + | |
635 " LEFT OUTER JOIN #{target_class.table_name} #{value_join_alias custom_field}" + | |
636 " ON CAST(CASE #{alias_name}.value WHEN '' THEN '0' ELSE #{alias_name}.value END AS decimal(30,0)) = #{value_join_alias custom_field}.id" | |
637 end | |
638 | |
639 def value_join_alias(custom_field) | |
640 join_alias(custom_field) + "_" + custom_field.field_format | |
641 end | |
642 protected :value_join_alias | |
643 end | |
644 | |
645 class UserFormat < RecordList | |
646 add 'user' | |
647 self.form_partial = 'custom_fields/formats/user' | |
648 field_attributes :user_role | |
649 | |
650 def possible_values_options(custom_field, object=nil) | |
651 if object.is_a?(Array) | |
652 projects = object.map {|o| o.respond_to?(:project) ? o.project : nil}.compact.uniq | |
653 projects.map {|project| possible_values_options(custom_field, project)}.reduce(:&) || [] | |
654 elsif object.respond_to?(:project) && object.project | |
655 scope = object.project.users | |
656 if custom_field.user_role.is_a?(Array) | |
657 role_ids = custom_field.user_role.map(&:to_s).reject(&:blank?).map(&:to_i) | |
658 if role_ids.any? | |
659 scope = scope.where("#{Member.table_name}.id IN (SELECT DISTINCT member_id FROM #{MemberRole.table_name} WHERE role_id IN (?))", role_ids) | |
660 end | |
661 end | |
662 scope.sorted.collect {|u| [u.to_s, u.id.to_s]} | |
663 else | |
664 [] | |
665 end | |
666 end | |
667 | |
668 def before_custom_field_save(custom_field) | |
669 super | |
670 if custom_field.user_role.is_a?(Array) | |
671 custom_field.user_role.map!(&:to_s).reject!(&:blank?) | |
672 end | |
673 end | |
674 end | |
675 | |
676 class VersionFormat < RecordList | |
677 add 'version' | |
678 self.form_partial = 'custom_fields/formats/version' | |
679 field_attributes :version_status | |
680 | |
681 def possible_values_options(custom_field, object=nil) | |
682 if object.is_a?(Array) | |
683 projects = object.map {|o| o.respond_to?(:project) ? o.project : nil}.compact.uniq | |
684 projects.map {|project| possible_values_options(custom_field, project)}.reduce(:&) || [] | |
685 elsif object.respond_to?(:project) && object.project | |
686 scope = object.project.shared_versions | |
687 if custom_field.version_status.is_a?(Array) | |
688 statuses = custom_field.version_status.map(&:to_s).reject(&:blank?) | |
689 if statuses.any? | |
690 scope = scope.where(:status => statuses.map(&:to_s)) | |
691 end | |
692 end | |
693 scope.sort.collect {|u| [u.to_s, u.id.to_s]} | |
694 else | |
695 [] | |
696 end | |
697 end | |
698 | |
699 def before_custom_field_save(custom_field) | |
700 super | |
701 if custom_field.version_status.is_a?(Array) | |
702 custom_field.version_status.map!(&:to_s).reject!(&:blank?) | |
703 end | |
704 end | |
705 end | |
706 end | |
707 end |