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