annotate app/models/attachment.rb @ 45:65d9e2cabaa3 luisf

Added tipoftheday to the config/settings in order to correct previous issues. Tip of the day is now working correctly. Added the heading strings to the locales files.
author luisf
date Tue, 23 Nov 2010 11:50:01 +0000
parents 513646585e45
children 0579821a129a
rev   line source
Chris@0 1 # redMine - project management software
Chris@0 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require "digest/md5"
Chris@0 19
Chris@0 20 class Attachment < ActiveRecord::Base
Chris@0 21 belongs_to :container, :polymorphic => true
Chris@0 22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
Chris@0 23
Chris@0 24 validates_presence_of :container, :filename, :author
Chris@0 25 validates_length_of :filename, :maximum => 255
Chris@0 26 validates_length_of :disk_filename, :maximum => 255
Chris@0 27
Chris@0 28 acts_as_event :title => :filename,
Chris@0 29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
Chris@0 30
Chris@0 31 acts_as_activity_provider :type => 'files',
Chris@0 32 :permission => :view_files,
Chris@0 33 :author_key => :author_id,
Chris@0 34 :find_options => {:select => "#{Attachment.table_name}.*",
Chris@0 35 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
Chris@0 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 )"}
Chris@0 37
Chris@0 38 acts_as_activity_provider :type => 'documents',
Chris@0 39 :permission => :view_documents,
Chris@0 40 :author_key => :author_id,
Chris@0 41 :find_options => {:select => "#{Attachment.table_name}.*",
Chris@0 42 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
Chris@0 43 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
Chris@0 44
Chris@0 45 cattr_accessor :storage_path
Chris@0 46 @@storage_path = "#{RAILS_ROOT}/files"
Chris@0 47
Chris@0 48 def validate
Chris@0 49 if self.filesize > Setting.attachment_max_size.to_i.kilobytes
Chris@0 50 errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
Chris@0 51 end
Chris@0 52 end
Chris@0 53
Chris@0 54 def file=(incoming_file)
Chris@0 55 unless incoming_file.nil?
Chris@0 56 @temp_file = incoming_file
Chris@0 57 if @temp_file.size > 0
Chris@0 58 self.filename = sanitize_filename(@temp_file.original_filename)
Chris@0 59 self.disk_filename = Attachment.disk_filename(filename)
Chris@0 60 self.content_type = @temp_file.content_type.to_s.chomp
Chris@0 61 if content_type.blank?
Chris@0 62 self.content_type = Redmine::MimeType.of(filename)
Chris@0 63 end
Chris@0 64 self.filesize = @temp_file.size
Chris@0 65 end
Chris@0 66 end
Chris@0 67 end
Chris@0 68
Chris@0 69 def file
Chris@0 70 nil
Chris@0 71 end
Chris@0 72
Chris@0 73 # Copies the temporary file to its final location
Chris@0 74 # and computes its MD5 hash
Chris@0 75 def before_save
Chris@0 76 if @temp_file && (@temp_file.size > 0)
Chris@0 77 logger.debug("saving '#{self.diskfile}'")
Chris@0 78 md5 = Digest::MD5.new
Chris@0 79 File.open(diskfile, "wb") do |f|
Chris@0 80 buffer = ""
Chris@0 81 while (buffer = @temp_file.read(8192))
Chris@0 82 f.write(buffer)
Chris@0 83 md5.update(buffer)
Chris@0 84 end
Chris@0 85 end
Chris@0 86 self.digest = md5.hexdigest
Chris@0 87 end
Chris@0 88 # Don't save the content type if it's longer than the authorized length
Chris@0 89 if self.content_type && self.content_type.length > 255
Chris@0 90 self.content_type = nil
Chris@0 91 end
Chris@0 92 end
Chris@0 93
Chris@0 94 # Deletes file on the disk
Chris@0 95 def after_destroy
Chris@0 96 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
Chris@0 97 end
Chris@0 98
Chris@0 99 # Returns file's location on disk
Chris@0 100 def diskfile
Chris@0 101 "#{@@storage_path}/#{self.disk_filename}"
Chris@0 102 end
Chris@0 103
Chris@0 104 def increment_download
Chris@0 105 increment!(:downloads)
Chris@0 106 end
Chris@0 107
Chris@0 108 def project
Chris@0 109 container.project
Chris@0 110 end
Chris@0 111
Chris@0 112 def visible?(user=User.current)
Chris@0 113 container.attachments_visible?(user)
Chris@0 114 end
Chris@0 115
Chris@0 116 def deletable?(user=User.current)
Chris@0 117 container.attachments_deletable?(user)
Chris@0 118 end
Chris@0 119
Chris@0 120 def image?
Chris@0 121 self.filename =~ /\.(jpe?g|gif|png)$/i
Chris@0 122 end
Chris@0 123
Chris@0 124 def is_text?
Chris@0 125 Redmine::MimeType.is_type?('text', filename)
Chris@0 126 end
Chris@0 127
Chris@0 128 def is_diff?
Chris@0 129 self.filename =~ /\.(patch|diff)$/i
Chris@0 130 end
Chris@0 131
Chris@0 132 # Returns true if the file is readable
Chris@0 133 def readable?
Chris@0 134 File.readable?(diskfile)
Chris@0 135 end
Chris@0 136
Chris@0 137 # Bulk attaches a set of files to an object
Chris@0 138 #
Chris@0 139 # Returns a Hash of the results:
Chris@0 140 # :files => array of the attached files
Chris@0 141 # :unsaved => array of the files that could not be attached
Chris@0 142 def self.attach_files(obj, attachments)
Chris@0 143 attached = []
Chris@0 144 if attachments && attachments.is_a?(Hash)
Chris@0 145 attachments.each_value do |attachment|
Chris@0 146 file = attachment['file']
Chris@0 147 next unless file && file.size > 0
Chris@0 148 a = Attachment.create(:container => obj,
Chris@0 149 :file => file,
Chris@0 150 :description => attachment['description'].to_s.strip,
Chris@0 151 :author => User.current)
Chris@0 152
Chris@0 153 if a.new_record?
Chris@0 154 obj.unsaved_attachments ||= []
Chris@0 155 obj.unsaved_attachments << a
Chris@0 156 else
Chris@0 157 attached << a
Chris@0 158 end
Chris@0 159 end
Chris@0 160 end
Chris@0 161 {:files => attached, :unsaved => obj.unsaved_attachments}
Chris@0 162 end
Chris@0 163
Chris@0 164 private
Chris@0 165 def sanitize_filename(value)
Chris@0 166 # get only the filename, not the whole path
Chris@0 167 just_filename = value.gsub(/^.*(\\|\/)/, '')
Chris@0 168 # NOTE: File.basename doesn't work right with Windows paths on Unix
Chris@0 169 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
Chris@0 170
Chris@0 171 # Finally, replace all non alphanumeric, hyphens or periods with underscore
Chris@0 172 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
Chris@0 173 end
Chris@0 174
Chris@0 175 # Returns an ASCII or hashed filename
Chris@0 176 def self.disk_filename(filename)
Chris@0 177 timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
Chris@0 178 ascii = ''
Chris@0 179 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
Chris@0 180 ascii = filename
Chris@0 181 else
Chris@0 182 ascii = Digest::MD5.hexdigest(filename)
Chris@0 183 # keep the extension if any
Chris@0 184 ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
Chris@0 185 end
Chris@0 186 while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
Chris@0 187 timestamp.succ!
Chris@0 188 end
Chris@0 189 "#{timestamp}_#{ascii}"
Chris@0 190 end
Chris@0 191 end