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