2007-03-12 20:59:02 +03:00
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require " digest/md5 "
class Attachment < ActiveRecord :: Base
belongs_to :container , :polymorphic = > true
belongs_to :author , :class_name = > " User " , :foreign_key = > " author_id "
2007-11-20 18:40:16 +03:00
validates_presence_of :container , :filename , :author
2007-07-16 21:16:49 +04:00
validates_length_of :filename , :maximum = > 255
validates_length_of :disk_filename , :maximum = > 255
2007-08-29 20:52:35 +04:00
acts_as_event :title = > :filename ,
2008-07-22 21:55:19 +04:00
:url = > Proc . new { | o | { :controller = > 'attachments' , :action = > 'download' , :id = > o . id , :filename = > o . filename } }
2007-08-29 20:52:35 +04:00
2008-07-27 21:54:09 +04:00
acts_as_activity_provider :type = > 'files' ,
:permission = > :view_files ,
2008-11-30 14:18:22 +03:00
:author_key = > :author_id ,
2008-07-27 21:54:09 +04:00
:find_options = > { :select = > " #{ Attachment . table_name } .* " ,
:joins = > " LEFT JOIN #{ Version . table_name } ON #{ Attachment . table_name } .container_type='Version' AND #{ Version . table_name } .id = #{ Attachment . table_name } .container_id " +
2009-03-08 17:31:15 +03:00
" 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 ) " }
2008-07-27 21:54:09 +04:00
acts_as_activity_provider :type = > 'documents' ,
:permission = > :view_documents ,
2008-11-30 14:18:22 +03:00
:author_key = > :author_id ,
2008-07-27 21:54:09 +04:00
:find_options = > { :select = > " #{ Attachment . table_name } .* " ,
:joins = > " LEFT JOIN #{ Document . table_name } ON #{ Attachment . table_name } .container_type='Document' AND #{ Document . table_name } .id = #{ Attachment . table_name } .container_id " +
" LEFT JOIN #{ Project . table_name } ON #{ Document . table_name } .project_id = #{ Project . table_name } .id " }
2007-03-12 20:59:02 +03:00
cattr_accessor :storage_path
2011-01-23 13:41:43 +03:00
@@storage_path = Redmine :: Configuration [ 'attachments_storage_path' ] || " #{ RAILS_ROOT } /files "
2007-03-12 20:59:02 +03:00
def validate
2009-04-08 20:56:01 +04:00
if self . filesize > Setting . attachment_max_size . to_i . kilobytes
errors . add ( :base , :too_long , :count = > Setting . attachment_max_size . to_i . kilobytes )
end
2007-03-12 20:59:02 +03:00
end
2008-04-03 01:30:32 +04:00
def file = ( incoming_file )
unless incoming_file . nil?
@temp_file = incoming_file
if @temp_file . size > 0
self . filename = sanitize_filename ( @temp_file . original_filename )
2008-05-17 15:03:43 +04:00
self . disk_filename = Attachment . disk_filename ( filename )
2008-04-03 01:30:32 +04:00
self . content_type = @temp_file . content_type . to_s . chomp
2009-12-29 16:28:30 +03:00
if content_type . blank?
self . content_type = Redmine :: MimeType . of ( filename )
end
2008-04-03 01:30:32 +04:00
self . filesize = @temp_file . size
end
end
end
2007-03-12 20:59:02 +03:00
2008-04-03 01:30:32 +04:00
def file
nil
end
2009-04-10 20:37:42 +04:00
# Copies the temporary file to its final location
# and computes its MD5 hash
2008-04-03 01:30:32 +04:00
def before_save
if @temp_file && ( @temp_file . size > 0 )
logger . debug ( " saving ' #{ self . diskfile } ' " )
2009-04-10 20:37:42 +04:00
md5 = Digest :: MD5 . new
2008-04-03 01:30:32 +04:00
File . open ( diskfile , " wb " ) do | f |
2009-04-10 20:37:42 +04:00
buffer = " "
while ( buffer = @temp_file . read ( 8192 ) )
f . write ( buffer )
md5 . update ( buffer )
end
2008-04-03 01:30:32 +04:00
end
2009-04-10 20:37:42 +04:00
self . digest = md5 . hexdigest
2008-04-03 01:30:32 +04:00
end
# Don't save the content type if it's longer than the authorized length
if self . content_type && self . content_type . length > 255
self . content_type = nil
end
end
# Deletes file on the disk
def after_destroy
2008-05-22 22:26:43 +04:00
File . delete ( diskfile ) if ! filename . blank? && File . exist? ( diskfile )
2008-04-03 01:30:32 +04:00
end
# Returns file's location on disk
def diskfile
" #{ @@storage_path } / #{ self . disk_filename } "
end
2007-03-12 20:59:02 +03:00
def increment_download
increment! ( :downloads )
end
2007-05-26 19:42:37 +04:00
def project
2007-11-27 20:20:57 +03:00
container . project
2007-05-26 19:42:37 +04:00
end
2008-12-09 19:54:46 +03:00
def visible? ( user = User . current )
container . attachments_visible? ( user )
end
def deletable? ( user = User . current )
container . attachments_deletable? ( user )
end
2007-08-15 19:36:15 +04:00
def image?
2008-04-03 01:30:32 +04:00
self . filename =~ / \ .(jpe?g|gif|png)$ /i
2007-08-15 19:36:15 +04:00
end
2008-06-09 22:40:59 +04:00
def is_text?
Redmine :: MimeType . is_type? ( 'text' , filename )
end
2008-06-08 22:26:39 +04:00
def is_diff?
self . filename =~ / \ .(patch|diff)$ /i
end
2009-04-25 13:31:36 +04:00
# Returns true if the file is readable
def readable?
File . readable? ( diskfile )
end
2010-03-02 22:26:03 +03:00
# Bulk attaches a set of files to an object
#
# Returns a Hash of the results:
# :files => array of the attached files
# :unsaved => array of the files that could not be attached
def self . attach_files ( obj , attachments )
attached = [ ]
if attachments && attachments . is_a? ( Hash )
attachments . each_value do | attachment |
file = attachment [ 'file' ]
next unless file && file . size > 0
a = Attachment . create ( :container = > obj ,
:file = > file ,
:description = > attachment [ 'description' ] . to_s . strip ,
:author = > User . current )
2010-06-19 07:54:17 +04:00
if a . new_record?
obj . unsaved_attachments || = [ ]
obj . unsaved_attachments << a
else
attached << a
end
2010-03-02 22:26:03 +03:00
end
end
2010-03-03 20:05:00 +03:00
{ :files = > attached , :unsaved = > obj . unsaved_attachments }
2010-03-02 22:26:03 +03:00
end
2009-04-25 13:31:36 +04:00
2007-03-12 20:59:02 +03:00
private
def sanitize_filename ( value )
2008-04-03 01:30:32 +04:00
# get only the filename, not the whole path
just_filename = value . gsub ( / ^.*( \\ | \/ ) / , '' )
# NOTE: File.basename doesn't work right with Windows paths on Unix
# INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
2007-03-12 20:59:02 +03:00
2008-04-03 01:30:32 +04:00
# Finally, replace all non alphanumeric, hyphens or periods with underscore
@filename = just_filename . gsub ( / [^ \ w \ . \ -] / , '_' )
2007-03-12 20:59:02 +03:00
end
2008-05-17 15:03:43 +04:00
# Returns an ASCII or hashed filename
def self . disk_filename ( filename )
2010-02-28 14:12:40 +03:00
timestamp = DateTime . now . strftime ( " %y%m%d%H%M%S " )
ascii = ''
2008-05-17 15:03:43 +04:00
if filename =~ %r{ ^[a-zA-Z0-9_ \ . \ -]*$ }
2010-02-28 14:12:40 +03:00
ascii = filename
2008-05-17 15:03:43 +04:00
else
2010-02-28 14:12:40 +03:00
ascii = Digest :: MD5 . hexdigest ( filename )
2008-05-17 15:03:43 +04:00
# keep the extension if any
2010-02-28 14:12:40 +03:00
ascii << $1 if filename =~ %r{ ( \ .[a-zA-Z0-9]+)$ }
2008-05-17 15:03:43 +04:00
end
2010-02-28 14:12:40 +03:00
while File . exist? ( File . join ( @@storage_path , " #{ timestamp } _ #{ ascii } " ) )
timestamp . succ!
end
" #{ timestamp } _ #{ ascii } "
2008-05-17 15:03:43 +04:00
end
2006-06-28 22:11:03 +04:00
end