##// END OF EJS Templates
Fixes that custom values length and attachment size validation error raises an error....
Jean-Philippe Lang -
r2575:1c03b98e5dac
parent child
Show More
@@ -1,151 +1,153
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 require "digest/md5"
18 require "digest/md5"
19
19
20 class Attachment < ActiveRecord::Base
20 class Attachment < ActiveRecord::Base
21 belongs_to :container, :polymorphic => true
21 belongs_to :container, :polymorphic => true
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23
23
24 validates_presence_of :container, :filename, :author
24 validates_presence_of :container, :filename, :author
25 validates_length_of :filename, :maximum => 255
25 validates_length_of :filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
27
27
28 acts_as_event :title => :filename,
28 acts_as_event :title => :filename,
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
30
30
31 acts_as_activity_provider :type => 'files',
31 acts_as_activity_provider :type => 'files',
32 :permission => :view_files,
32 :permission => :view_files,
33 :author_key => :author_id,
33 :author_key => :author_id,
34 :find_options => {:select => "#{Attachment.table_name}.*",
34 :find_options => {:select => "#{Attachment.table_name}.*",
35 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
35 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
36 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"}
36 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"}
37
37
38 acts_as_activity_provider :type => 'documents',
38 acts_as_activity_provider :type => 'documents',
39 :permission => :view_documents,
39 :permission => :view_documents,
40 :author_key => :author_id,
40 :author_key => :author_id,
41 :find_options => {:select => "#{Attachment.table_name}.*",
41 :find_options => {:select => "#{Attachment.table_name}.*",
42 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
42 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
43 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
43 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
44
44
45 cattr_accessor :storage_path
45 cattr_accessor :storage_path
46 @@storage_path = "#{RAILS_ROOT}/files"
46 @@storage_path = "#{RAILS_ROOT}/files"
47
47
48 def validate
48 def validate
49 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
49 if self.filesize > Setting.attachment_max_size.to_i.kilobytes
50 errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
51 end
50 end
52 end
51
53
52 def file=(incoming_file)
54 def file=(incoming_file)
53 unless incoming_file.nil?
55 unless incoming_file.nil?
54 @temp_file = incoming_file
56 @temp_file = incoming_file
55 if @temp_file.size > 0
57 if @temp_file.size > 0
56 self.filename = sanitize_filename(@temp_file.original_filename)
58 self.filename = sanitize_filename(@temp_file.original_filename)
57 self.disk_filename = Attachment.disk_filename(filename)
59 self.disk_filename = Attachment.disk_filename(filename)
58 self.content_type = @temp_file.content_type.to_s.chomp
60 self.content_type = @temp_file.content_type.to_s.chomp
59 self.filesize = @temp_file.size
61 self.filesize = @temp_file.size
60 end
62 end
61 end
63 end
62 end
64 end
63
65
64 def file
66 def file
65 nil
67 nil
66 end
68 end
67
69
68 # Copy temp file to its final location
70 # Copy temp file to its final location
69 def before_save
71 def before_save
70 if @temp_file && (@temp_file.size > 0)
72 if @temp_file && (@temp_file.size > 0)
71 logger.debug("saving '#{self.diskfile}'")
73 logger.debug("saving '#{self.diskfile}'")
72 File.open(diskfile, "wb") do |f|
74 File.open(diskfile, "wb") do |f|
73 f.write(@temp_file.read)
75 f.write(@temp_file.read)
74 end
76 end
75 self.digest = self.class.digest(diskfile)
77 self.digest = self.class.digest(diskfile)
76 end
78 end
77 # Don't save the content type if it's longer than the authorized length
79 # Don't save the content type if it's longer than the authorized length
78 if self.content_type && self.content_type.length > 255
80 if self.content_type && self.content_type.length > 255
79 self.content_type = nil
81 self.content_type = nil
80 end
82 end
81 end
83 end
82
84
83 # Deletes file on the disk
85 # Deletes file on the disk
84 def after_destroy
86 def after_destroy
85 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
87 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
86 end
88 end
87
89
88 # Returns file's location on disk
90 # Returns file's location on disk
89 def diskfile
91 def diskfile
90 "#{@@storage_path}/#{self.disk_filename}"
92 "#{@@storage_path}/#{self.disk_filename}"
91 end
93 end
92
94
93 def increment_download
95 def increment_download
94 increment!(:downloads)
96 increment!(:downloads)
95 end
97 end
96
98
97 def project
99 def project
98 container.project
100 container.project
99 end
101 end
100
102
101 def visible?(user=User.current)
103 def visible?(user=User.current)
102 container.attachments_visible?(user)
104 container.attachments_visible?(user)
103 end
105 end
104
106
105 def deletable?(user=User.current)
107 def deletable?(user=User.current)
106 container.attachments_deletable?(user)
108 container.attachments_deletable?(user)
107 end
109 end
108
110
109 def image?
111 def image?
110 self.filename =~ /\.(jpe?g|gif|png)$/i
112 self.filename =~ /\.(jpe?g|gif|png)$/i
111 end
113 end
112
114
113 def is_text?
115 def is_text?
114 Redmine::MimeType.is_type?('text', filename)
116 Redmine::MimeType.is_type?('text', filename)
115 end
117 end
116
118
117 def is_diff?
119 def is_diff?
118 self.filename =~ /\.(patch|diff)$/i
120 self.filename =~ /\.(patch|diff)$/i
119 end
121 end
120
122
121 private
123 private
122 def sanitize_filename(value)
124 def sanitize_filename(value)
123 # get only the filename, not the whole path
125 # get only the filename, not the whole path
124 just_filename = value.gsub(/^.*(\\|\/)/, '')
126 just_filename = value.gsub(/^.*(\\|\/)/, '')
125 # NOTE: File.basename doesn't work right with Windows paths on Unix
127 # NOTE: File.basename doesn't work right with Windows paths on Unix
126 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
128 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
127
129
128 # Finally, replace all non alphanumeric, hyphens or periods with underscore
130 # Finally, replace all non alphanumeric, hyphens or periods with underscore
129 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
131 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
130 end
132 end
131
133
132 # Returns an ASCII or hashed filename
134 # Returns an ASCII or hashed filename
133 def self.disk_filename(filename)
135 def self.disk_filename(filename)
134 df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
136 df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
135 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
137 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
136 df << filename
138 df << filename
137 else
139 else
138 df << Digest::MD5.hexdigest(filename)
140 df << Digest::MD5.hexdigest(filename)
139 # keep the extension if any
141 # keep the extension if any
140 df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
142 df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
141 end
143 end
142 df
144 df
143 end
145 end
144
146
145 # Returns the MD5 digest of the file at given path
147 # Returns the MD5 digest of the file at given path
146 def self.digest(filename)
148 def self.digest(filename)
147 File.open(filename, 'rb') do |f|
149 File.open(filename, 'rb') do |f|
148 Digest::MD5.hexdigest(f.read)
150 Digest::MD5.hexdigest(f.read)
149 end
151 end
150 end
152 end
151 end
153 end
@@ -1,67 +1,67
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 CustomValue < ActiveRecord::Base
18 class CustomValue < ActiveRecord::Base
19 belongs_to :custom_field
19 belongs_to :custom_field
20 belongs_to :customized, :polymorphic => true
20 belongs_to :customized, :polymorphic => true
21
21
22 def after_initialize
22 def after_initialize
23 if custom_field && new_record? && (customized_type.blank? || (customized && customized.new_record?))
23 if custom_field && new_record? && (customized_type.blank? || (customized && customized.new_record?))
24 self.value ||= custom_field.default_value
24 self.value ||= custom_field.default_value
25 end
25 end
26 end
26 end
27
27
28 # Returns true if the boolean custom value is true
28 # Returns true if the boolean custom value is true
29 def true?
29 def true?
30 self.value == '1'
30 self.value == '1'
31 end
31 end
32
32
33 def editable?
33 def editable?
34 custom_field.editable?
34 custom_field.editable?
35 end
35 end
36
36
37 def required?
37 def required?
38 custom_field.is_required?
38 custom_field.is_required?
39 end
39 end
40
40
41 def to_s
41 def to_s
42 value.to_s
42 value.to_s
43 end
43 end
44
44
45 protected
45 protected
46 def validate
46 def validate
47 if value.blank?
47 if value.blank?
48 errors.add(:value, :blank) if custom_field.is_required? and value.blank?
48 errors.add(:value, :blank) if custom_field.is_required? and value.blank?
49 else
49 else
50 errors.add(:value, :invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
50 errors.add(:value, :invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
51 errors.add(:value, :too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length
51 errors.add(:value, :too_short, :count => custom_field.min_length) if custom_field.min_length > 0 and value.length < custom_field.min_length
52 errors.add(:value, :too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
52 errors.add(:value, :too_long, :count => custom_field.max_length) if custom_field.max_length > 0 and value.length > custom_field.max_length
53
53
54 # Format specific validations
54 # Format specific validations
55 case custom_field.field_format
55 case custom_field.field_format
56 when 'int'
56 when 'int'
57 errors.add(:value, :not_a_number) unless value =~ /^[+-]?\d+$/
57 errors.add(:value, :not_a_number) unless value =~ /^[+-]?\d+$/
58 when 'float'
58 when 'float'
59 begin; Kernel.Float(value); rescue; errors.add(:value, :invalid) end
59 begin; Kernel.Float(value); rescue; errors.add(:value, :invalid) end
60 when 'date'
60 when 'date'
61 errors.add(:value, :not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/
61 errors.add(:value, :not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/
62 when 'list'
62 when 'list'
63 errors.add(:value, :inclusion) unless custom_field.possible_values.include?(value)
63 errors.add(:value, :inclusion) unless custom_field.possible_values.include?(value)
64 end
64 end
65 end
65 end
66 end
66 end
67 end
67 end
General Comments 0
You need to be logged in to leave comments. Login now