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