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