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