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