##// END OF EJS Templates
Validate length of custom field regexp (#24283)....
Jean-Philippe Lang -
r15752:1c44b16023f5
parent child
Show More
@@ -1,328 +1,328
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 class CustomField < ActiveRecord::Base
18 class CustomField < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 include Redmine::SubclassFactory
20 include Redmine::SubclassFactory
21
21
22 has_many :enumerations,
22 has_many :enumerations,
23 lambda { order(:position) },
23 lambda { order(:position) },
24 :class_name => 'CustomFieldEnumeration',
24 :class_name => 'CustomFieldEnumeration',
25 :dependent => :delete_all
25 :dependent => :delete_all
26 has_many :custom_values, :dependent => :delete_all
26 has_many :custom_values, :dependent => :delete_all
27 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
27 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
28 acts_as_positioned
28 acts_as_positioned
29 serialize :possible_values
29 serialize :possible_values
30 store :format_store
30 store :format_store
31
31
32 validates_presence_of :name, :field_format
32 validates_presence_of :name, :field_format
33 validates_uniqueness_of :name, :scope => :type
33 validates_uniqueness_of :name, :scope => :type
34 validates_length_of :name, :maximum => 30
34 validates_length_of :name, :maximum => 30
35 validates_length_of :regexp, maximum: 30
35 validates_length_of :regexp, maximum: 255
36 validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats }
36 validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats }
37 validate :validate_custom_field
37 validate :validate_custom_field
38 attr_protected :id
38 attr_protected :id
39
39
40 before_validation :set_searchable
40 before_validation :set_searchable
41 before_save do |field|
41 before_save do |field|
42 field.format.before_custom_field_save(field)
42 field.format.before_custom_field_save(field)
43 end
43 end
44 after_save :handle_multiplicity_change
44 after_save :handle_multiplicity_change
45 after_save do |field|
45 after_save do |field|
46 if field.visible_changed? && field.visible
46 if field.visible_changed? && field.visible
47 field.roles.clear
47 field.roles.clear
48 end
48 end
49 end
49 end
50
50
51 scope :sorted, lambda { order(:position) }
51 scope :sorted, lambda { order(:position) }
52 scope :visible, lambda {|*args|
52 scope :visible, lambda {|*args|
53 user = args.shift || User.current
53 user = args.shift || User.current
54 if user.admin?
54 if user.admin?
55 # nop
55 # nop
56 elsif user.memberships.any?
56 elsif user.memberships.any?
57 where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
57 where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
58 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
58 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
59 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
59 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
60 " WHERE m.user_id = ?)",
60 " WHERE m.user_id = ?)",
61 true, user.id)
61 true, user.id)
62 else
62 else
63 where(:visible => true)
63 where(:visible => true)
64 end
64 end
65 }
65 }
66 def visible_by?(project, user=User.current)
66 def visible_by?(project, user=User.current)
67 visible? || user.admin?
67 visible? || user.admin?
68 end
68 end
69
69
70 safe_attributes 'name',
70 safe_attributes 'name',
71 'field_format',
71 'field_format',
72 'possible_values',
72 'possible_values',
73 'regexp',
73 'regexp',
74 'min_lnegth',
74 'min_lnegth',
75 'max_length',
75 'max_length',
76 'is_required',
76 'is_required',
77 'is_for_all',
77 'is_for_all',
78 'is_filter',
78 'is_filter',
79 'position',
79 'position',
80 'searchable',
80 'searchable',
81 'default_value',
81 'default_value',
82 'editable',
82 'editable',
83 'visible',
83 'visible',
84 'multiple',
84 'multiple',
85 'description',
85 'description',
86 'role_ids',
86 'role_ids',
87 'url_pattern',
87 'url_pattern',
88 'text_formatting',
88 'text_formatting',
89 'edit_tag_style',
89 'edit_tag_style',
90 'user_role',
90 'user_role',
91 'version_status',
91 'version_status',
92 'extensions_allowed'
92 'extensions_allowed'
93
93
94 def format
94 def format
95 @format ||= Redmine::FieldFormat.find(field_format)
95 @format ||= Redmine::FieldFormat.find(field_format)
96 end
96 end
97
97
98 def field_format=(arg)
98 def field_format=(arg)
99 # cannot change format of a saved custom field
99 # cannot change format of a saved custom field
100 if new_record?
100 if new_record?
101 @format = nil
101 @format = nil
102 super
102 super
103 end
103 end
104 end
104 end
105
105
106 def set_searchable
106 def set_searchable
107 # make sure these fields are not searchable
107 # make sure these fields are not searchable
108 self.searchable = false unless format.class.searchable_supported
108 self.searchable = false unless format.class.searchable_supported
109 # make sure only these fields can have multiple values
109 # make sure only these fields can have multiple values
110 self.multiple = false unless format.class.multiple_supported
110 self.multiple = false unless format.class.multiple_supported
111 true
111 true
112 end
112 end
113
113
114 def validate_custom_field
114 def validate_custom_field
115 format.validate_custom_field(self).each do |attribute, message|
115 format.validate_custom_field(self).each do |attribute, message|
116 errors.add attribute, message
116 errors.add attribute, message
117 end
117 end
118
118
119 if regexp.present?
119 if regexp.present?
120 begin
120 begin
121 Regexp.new(regexp)
121 Regexp.new(regexp)
122 rescue
122 rescue
123 errors.add(:regexp, :invalid)
123 errors.add(:regexp, :invalid)
124 end
124 end
125 end
125 end
126
126
127 if default_value.present?
127 if default_value.present?
128 validate_field_value(default_value).each do |message|
128 validate_field_value(default_value).each do |message|
129 errors.add :default_value, message
129 errors.add :default_value, message
130 end
130 end
131 end
131 end
132 end
132 end
133
133
134 def possible_custom_value_options(custom_value)
134 def possible_custom_value_options(custom_value)
135 format.possible_custom_value_options(custom_value)
135 format.possible_custom_value_options(custom_value)
136 end
136 end
137
137
138 def possible_values_options(object=nil)
138 def possible_values_options(object=nil)
139 if object.is_a?(Array)
139 if object.is_a?(Array)
140 object.map {|o| format.possible_values_options(self, o)}.reduce(:&) || []
140 object.map {|o| format.possible_values_options(self, o)}.reduce(:&) || []
141 else
141 else
142 format.possible_values_options(self, object) || []
142 format.possible_values_options(self, object) || []
143 end
143 end
144 end
144 end
145
145
146 def possible_values
146 def possible_values
147 values = read_attribute(:possible_values)
147 values = read_attribute(:possible_values)
148 if values.is_a?(Array)
148 if values.is_a?(Array)
149 values.each do |value|
149 values.each do |value|
150 value.to_s.force_encoding('UTF-8')
150 value.to_s.force_encoding('UTF-8')
151 end
151 end
152 values
152 values
153 else
153 else
154 []
154 []
155 end
155 end
156 end
156 end
157
157
158 # Makes possible_values accept a multiline string
158 # Makes possible_values accept a multiline string
159 def possible_values=(arg)
159 def possible_values=(arg)
160 if arg.is_a?(Array)
160 if arg.is_a?(Array)
161 values = arg.compact.map {|a| a.to_s.strip}.reject(&:blank?)
161 values = arg.compact.map {|a| a.to_s.strip}.reject(&:blank?)
162 write_attribute(:possible_values, values)
162 write_attribute(:possible_values, values)
163 else
163 else
164 self.possible_values = arg.to_s.split(/[\n\r]+/)
164 self.possible_values = arg.to_s.split(/[\n\r]+/)
165 end
165 end
166 end
166 end
167
167
168 def set_custom_field_value(custom_field_value, value)
168 def set_custom_field_value(custom_field_value, value)
169 format.set_custom_field_value(self, custom_field_value, value)
169 format.set_custom_field_value(self, custom_field_value, value)
170 end
170 end
171
171
172 def cast_value(value)
172 def cast_value(value)
173 format.cast_value(self, value)
173 format.cast_value(self, value)
174 end
174 end
175
175
176 def value_from_keyword(keyword, customized)
176 def value_from_keyword(keyword, customized)
177 format.value_from_keyword(self, keyword, customized)
177 format.value_from_keyword(self, keyword, customized)
178 end
178 end
179
179
180 # Returns the options hash used to build a query filter for the field
180 # Returns the options hash used to build a query filter for the field
181 def query_filter_options(query)
181 def query_filter_options(query)
182 format.query_filter_options(self, query)
182 format.query_filter_options(self, query)
183 end
183 end
184
184
185 def totalable?
185 def totalable?
186 format.totalable_supported
186 format.totalable_supported
187 end
187 end
188
188
189 # Returns a ORDER BY clause that can used to sort customized
189 # Returns a ORDER BY clause that can used to sort customized
190 # objects by their value of the custom field.
190 # objects by their value of the custom field.
191 # Returns nil if the custom field can not be used for sorting.
191 # Returns nil if the custom field can not be used for sorting.
192 def order_statement
192 def order_statement
193 return nil if multiple?
193 return nil if multiple?
194 format.order_statement(self)
194 format.order_statement(self)
195 end
195 end
196
196
197 # Returns a GROUP BY clause that can used to group by custom value
197 # Returns a GROUP BY clause that can used to group by custom value
198 # Returns nil if the custom field can not be used for grouping.
198 # Returns nil if the custom field can not be used for grouping.
199 def group_statement
199 def group_statement
200 return nil if multiple?
200 return nil if multiple?
201 format.group_statement(self)
201 format.group_statement(self)
202 end
202 end
203
203
204 def join_for_order_statement
204 def join_for_order_statement
205 format.join_for_order_statement(self)
205 format.join_for_order_statement(self)
206 end
206 end
207
207
208 def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
208 def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
209 if visible? || user.admin?
209 if visible? || user.admin?
210 "1=1"
210 "1=1"
211 elsif user.anonymous?
211 elsif user.anonymous?
212 "1=0"
212 "1=0"
213 else
213 else
214 project_key ||= "#{self.class.customized_class.table_name}.project_id"
214 project_key ||= "#{self.class.customized_class.table_name}.project_id"
215 id_column ||= id
215 id_column ||= id
216 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
216 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
217 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
217 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
218 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
218 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
219 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id_column})"
219 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id_column})"
220 end
220 end
221 end
221 end
222
222
223 def self.visibility_condition
223 def self.visibility_condition
224 if user.admin?
224 if user.admin?
225 "1=1"
225 "1=1"
226 elsif user.anonymous?
226 elsif user.anonymous?
227 "#{table_name}.visible"
227 "#{table_name}.visible"
228 else
228 else
229 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
229 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
230 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
230 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
231 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
231 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
232 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
232 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
233 end
233 end
234 end
234 end
235
235
236 def <=>(field)
236 def <=>(field)
237 position <=> field.position
237 position <=> field.position
238 end
238 end
239
239
240 # Returns the class that values represent
240 # Returns the class that values represent
241 def value_class
241 def value_class
242 format.target_class if format.respond_to?(:target_class)
242 format.target_class if format.respond_to?(:target_class)
243 end
243 end
244
244
245 def self.customized_class
245 def self.customized_class
246 self.name =~ /^(.+)CustomField$/
246 self.name =~ /^(.+)CustomField$/
247 $1.constantize rescue nil
247 $1.constantize rescue nil
248 end
248 end
249
249
250 # to move in project_custom_field
250 # to move in project_custom_field
251 def self.for_all
251 def self.for_all
252 where(:is_for_all => true).order(:position).to_a
252 where(:is_for_all => true).order(:position).to_a
253 end
253 end
254
254
255 def type_name
255 def type_name
256 nil
256 nil
257 end
257 end
258
258
259 # Returns the error messages for the given value
259 # Returns the error messages for the given value
260 # or an empty array if value is a valid value for the custom field
260 # or an empty array if value is a valid value for the custom field
261 def validate_custom_value(custom_value)
261 def validate_custom_value(custom_value)
262 value = custom_value.value
262 value = custom_value.value
263 errs = format.validate_custom_value(custom_value)
263 errs = format.validate_custom_value(custom_value)
264
264
265 unless errs.any?
265 unless errs.any?
266 if value.is_a?(Array)
266 if value.is_a?(Array)
267 if !multiple?
267 if !multiple?
268 errs << ::I18n.t('activerecord.errors.messages.invalid')
268 errs << ::I18n.t('activerecord.errors.messages.invalid')
269 end
269 end
270 if is_required? && value.detect(&:present?).nil?
270 if is_required? && value.detect(&:present?).nil?
271 errs << ::I18n.t('activerecord.errors.messages.blank')
271 errs << ::I18n.t('activerecord.errors.messages.blank')
272 end
272 end
273 else
273 else
274 if is_required? && value.blank?
274 if is_required? && value.blank?
275 errs << ::I18n.t('activerecord.errors.messages.blank')
275 errs << ::I18n.t('activerecord.errors.messages.blank')
276 end
276 end
277 end
277 end
278 end
278 end
279
279
280 errs
280 errs
281 end
281 end
282
282
283 # Returns the error messages for the default custom field value
283 # Returns the error messages for the default custom field value
284 def validate_field_value(value)
284 def validate_field_value(value)
285 validate_custom_value(CustomFieldValue.new(:custom_field => self, :value => value))
285 validate_custom_value(CustomFieldValue.new(:custom_field => self, :value => value))
286 end
286 end
287
287
288 # Returns true if value is a valid value for the custom field
288 # Returns true if value is a valid value for the custom field
289 def valid_field_value?(value)
289 def valid_field_value?(value)
290 validate_field_value(value).empty?
290 validate_field_value(value).empty?
291 end
291 end
292
292
293 def after_save_custom_value(custom_value)
293 def after_save_custom_value(custom_value)
294 format.after_save_custom_value(self, custom_value)
294 format.after_save_custom_value(self, custom_value)
295 end
295 end
296
296
297 def format_in?(*args)
297 def format_in?(*args)
298 args.include?(field_format)
298 args.include?(field_format)
299 end
299 end
300
300
301 def self.human_attribute_name(attribute_key_name, *args)
301 def self.human_attribute_name(attribute_key_name, *args)
302 attr_name = attribute_key_name.to_s
302 attr_name = attribute_key_name.to_s
303 if attr_name == 'url_pattern'
303 if attr_name == 'url_pattern'
304 attr_name = "url"
304 attr_name = "url"
305 end
305 end
306 super(attr_name, *args)
306 super(attr_name, *args)
307 end
307 end
308
308
309 protected
309 protected
310
310
311 # Removes multiple values for the custom field after setting the multiple attribute to false
311 # Removes multiple values for the custom field after setting the multiple attribute to false
312 # We kepp the value with the highest id for each customized object
312 # We kepp the value with the highest id for each customized object
313 def handle_multiplicity_change
313 def handle_multiplicity_change
314 if !new_record? && multiple_was && !multiple
314 if !new_record? && multiple_was && !multiple
315 ids = custom_values.
315 ids = custom_values.
316 where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
316 where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
317 " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
317 " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
318 " AND cve.id > #{CustomValue.table_name}.id)").
318 " AND cve.id > #{CustomValue.table_name}.id)").
319 pluck(:id)
319 pluck(:id)
320
320
321 if ids.any?
321 if ids.any?
322 custom_values.where(:id => ids).delete_all
322 custom_values.where(:id => ids).delete_all
323 end
323 end
324 end
324 end
325 end
325 end
326 end
326 end
327
327
328 require_dependency 'redmine/field_format'
328 require_dependency 'redmine/field_format'
General Comments 0
You need to be logged in to leave comments. Login now