<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>vendor/plugins/acts_as_attachable/init.rb</filename>
    </added>
    <added>
      <filename>vendor/plugins/acts_as_attachable/lib/acts_as_attachable.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007  Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2008  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
@@ -17,7 +17,11 @@
 
 class AttachmentsController &lt; ApplicationController
   before_filter :find_project
-
+  before_filter :read_authorize, :except =&gt; :destroy
+  before_filter :delete_authorize, :only =&gt; :destroy
+  
+  verify :method =&gt; :post, :only =&gt; :destroy
+  
   def show
     if @attachment.is_diff?
       @diff = File.new(@attachment.diskfile, &quot;rb&quot;).read
@@ -37,19 +41,32 @@ class AttachmentsController &lt; ApplicationController
     send_file @attachment.diskfile, :filename =&gt; filename_for_content_disposition(@attachment.filename),
                                     :type =&gt; @attachment.content_type, 
                                     :disposition =&gt; (@attachment.image? ? 'inline' : 'attachment')
+   
   end
- 
+  
+  def destroy
+    # Make sure association callbacks are called
+    @attachment.container.attachments.delete(@attachment)
+    redirect_to :back
+  rescue ::ActionController::RedirectBackError
+    redirect_to :controller =&gt; 'projects', :action =&gt; 'show', :id =&gt; @project
+  end
+  
 private
   def find_project
     @attachment = Attachment.find(params[:id])
     # Show 404 if the filename in the url is wrong
     raise ActiveRecord::RecordNotFound if params[:filename] &amp;&amp; params[:filename] != @attachment.filename
-    
     @project = @attachment.project
-    permission = @attachment.container.is_a?(Version) ? :view_files : &quot;view_#{@attachment.container.class.name.underscore.pluralize}&quot;.to_sym
-    allowed = User.current.allowed_to?(permission, @project)
-    allowed ? true : (User.current.logged? ? render_403 : require_login)
   rescue ActiveRecord::RecordNotFound
     render_404
   end
+  
+  def read_authorize
+    @attachment.visible? ? true : deny_access
+  end
+  
+  def delete_authorize
+    @attachment.deletable? ? true : deny_access
+  end
 end</diff>
      <filename>app/controllers/attachments_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -71,11 +71,6 @@ class DocumentsController &lt; ApplicationController
     Mailer.deliver_attachments_added(attachments) if !attachments.empty? &amp;&amp; Setting.notified_events.include?('document_added')
     redirect_to :action =&gt; 'show', :id =&gt; @document
   end
-  
-  def destroy_attachment
-    @document.attachments.find(params[:attachment_id]).destroy
-    redirect_to :action =&gt; 'show', :id =&gt; @document
-  end
 
 private
   def find_project</diff>
      <filename>app/controllers/documents_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -18,7 +18,7 @@
 class IssuesController &lt; ApplicationController
   menu_item :new_issue, :only =&gt; :new
   
-  before_filter :find_issue, :only =&gt; [:show, :edit, :reply, :destroy_attachment]
+  before_filter :find_issue, :only =&gt; [:show, :edit, :reply]
   before_filter :find_issues, :only =&gt; [:bulk_edit, :move, :destroy]
   before_filter :find_project, :only =&gt; [:new, :update_form, :preview]
   before_filter :authorize, :except =&gt; [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
@@ -318,17 +318,6 @@ class IssuesController &lt; ApplicationController
     @issues.each(&amp;:destroy)
     redirect_to :action =&gt; 'index', :project_id =&gt; @project
   end
-
-  def destroy_attachment
-    a = @issue.attachments.find(params[:attachment_id])
-    a.destroy
-    journal = @issue.init_journal(User.current)
-    journal.details &lt;&lt; JournalDetail.new(:property =&gt; 'attachment',
-                                         :prop_key =&gt; a.id,
-                                         :old_value =&gt; a.filename)
-    journal.save
-    redirect_to :action =&gt; 'show', :id =&gt; @issue
-  end
   
   def gantt
     @gantt = Redmine::Helpers::Gantt.new(params)</diff>
      <filename>app/controllers/issues_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -188,10 +188,13 @@ class ProjectsController &lt; ApplicationController
 
   def add_file
     if request.post?
-      @version = @project.versions.find_by_id(params[:version_id])
-      attachments = attach_files(@version, params[:attachments])
-      Mailer.deliver_attachments_added(attachments) if !attachments.empty? &amp;&amp; Setting.notified_events.include?('file_added')
+      container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
+      attachments = attach_files(container, params[:attachments])
+      if !attachments.empty? &amp;&amp; Setting.notified_events.include?('file_added')
+        Mailer.deliver_attachments_added(attachments)
+      end
       redirect_to :controller =&gt; 'projects', :action =&gt; 'list_files', :id =&gt; @project
+      return
     end
     @versions = @project.versions.sort
   end
@@ -203,7 +206,8 @@ class ProjectsController &lt; ApplicationController
                 'size' =&gt; &quot;#{Attachment.table_name}.filesize&quot;,
                 'downloads' =&gt; &quot;#{Attachment.table_name}.downloads&quot;
                 
-    @versions = @project.versions.find(:all, :include =&gt; :attachments, :order =&gt; sort_clause).sort.reverse
+    @containers = [ Project.find(@project.id, :include =&gt; :attachments, :order =&gt; sort_clause)]
+    @containers += @project.versions.find(:all, :include =&gt; :attachments, :order =&gt; sort_clause).sort.reverse
     render :layout =&gt; !request.xhr?
   end
   </diff>
      <filename>app/controllers/projects_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -37,12 +37,6 @@ class VersionsController &lt; ApplicationController
     redirect_to :controller =&gt; 'projects', :action =&gt; 'settings', :tab =&gt; 'versions', :id =&gt; @project
   end
   
-  def destroy_file
-    @version.attachments.find(params[:attachment_id]).destroy
-    flash[:notice] = l(:notice_successful_delete)
-    redirect_to :controller =&gt; 'projects', :action =&gt; 'list_files', :id =&gt; @project
-  end
-  
   def status_by
     respond_to do |format|
       format.html { render :action =&gt; 'show' }</diff>
      <filename>app/controllers/versions_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -21,7 +21,7 @@ class WikiController &lt; ApplicationController
   before_filter :find_wiki, :authorize
   before_filter :find_existing_page, :only =&gt; [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
   
-  verify :method =&gt; :post, :only =&gt; [:destroy, :destroy_attachment, :protect], :redirect_to =&gt; { :action =&gt; :index }
+  verify :method =&gt; :post, :only =&gt; [:destroy, :protect], :redirect_to =&gt; { :action =&gt; :index }
 
   helper :attachments
   include AttachmentsHelper   
@@ -181,13 +181,6 @@ class WikiController &lt; ApplicationController
     redirect_to :action =&gt; 'index', :page =&gt; @page.title
   end
 
-  def destroy_attachment
-    @page = @wiki.find_page(params[:page])
-    return render_403 unless editable?
-    @page.attachments.find(params[:attachment_id]).destroy
-    redirect_to :action =&gt; 'index', :page =&gt; @page.title
-  end
-
 private
   
   def find_wiki</diff>
      <filename>app/controllers/wiki_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -16,10 +16,15 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 module AttachmentsHelper
-  # displays the links to a collection of attachments
-  def link_to_attachments(attachments, options = {})
-    if attachments.any?
-      render :partial =&gt; 'attachments/links', :locals =&gt; {:attachments =&gt; attachments, :options =&gt; options}
+  # Displays view/delete links to the attachments of the given object
+  # Options:
+  #   :author -- author names are not displayed if set to false
+  def link_to_attachments(container, options = {})
+    options.assert_valid_keys(:author)
+    
+    if container.attachments.any?
+      options = {:deletable =&gt; container.attachments_deletable?, :author =&gt; true}.merge(options)
+      render :partial =&gt; 'attachments/links', :locals =&gt; {:attachments =&gt; container.attachments, :options =&gt; options}
     end
   end
   </diff>
      <filename>app/helpers/attachments_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -98,6 +98,14 @@ class Attachment &lt; ActiveRecord::Base
     container.project
   end
   
+  def visible?(user=User.current)
+    container.attachments_visible?(user)
+  end
+  
+  def deletable?(user=User.current)
+    container.attachments_deletable?(user)
+  end
+  
   def image?
     self.filename =~ /\.(jpe?g|gif|png)$/i
   end</diff>
      <filename>app/models/attachment.rb</filename>
    </modified>
    <modified>
      <diff>@@ -18,7 +18,7 @@
 class Document &lt; ActiveRecord::Base
   belongs_to :project
   belongs_to :category, :class_name =&gt; &quot;Enumeration&quot;, :foreign_key =&gt; &quot;category_id&quot;
-  has_many :attachments, :as =&gt; :container, :dependent =&gt; :destroy
+  acts_as_attachable :delete_permission =&gt; :manage_documents
 
   acts_as_searchable :columns =&gt; ['title', &quot;#{table_name}.description&quot;], :include =&gt; :project
   acts_as_event :title =&gt; Proc.new {|o| &quot;#{l(:label_document)}: #{o.title}&quot;},</diff>
      <filename>app/models/document.rb</filename>
    </modified>
    <modified>
      <diff>@@ -26,13 +26,13 @@ class Issue &lt; ActiveRecord::Base
   belongs_to :category, :class_name =&gt; 'IssueCategory', :foreign_key =&gt; 'category_id'
 
   has_many :journals, :as =&gt; :journalized, :dependent =&gt; :destroy
-  has_many :attachments, :as =&gt; :container, :dependent =&gt; :destroy
   has_many :time_entries, :dependent =&gt; :delete_all
   has_and_belongs_to_many :changesets, :order =&gt; &quot;#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC&quot;
   
   has_many :relations_from, :class_name =&gt; 'IssueRelation', :foreign_key =&gt; 'issue_from_id', :dependent =&gt; :delete_all
   has_many :relations_to, :class_name =&gt; 'IssueRelation', :foreign_key =&gt; 'issue_to_id', :dependent =&gt; :delete_all
   
+  acts_as_attachable :after_remove =&gt; :attachment_removed
   acts_as_customizable
   acts_as_watchable
   acts_as_searchable :columns =&gt; ['subject', &quot;#{table_name}.description&quot;, &quot;#{Journal.table_name}.notes&quot;],
@@ -266,4 +266,15 @@ class Issue &lt; ActiveRecord::Base
   def to_s
     &quot;#{tracker} ##{id}: #{subject}&quot;
   end
+  
+  private
+  
+  # Callback on attachment deletion
+  def attachment_removed(obj)
+    journal = init_journal(User.current)
+    journal.details &lt;&lt; JournalDetail.new(:property =&gt; 'attachment',
+                                         :prop_key =&gt; obj.id,
+                                         :old_value =&gt; obj.filename)
+    journal.save
+  end
 end</diff>
      <filename>app/models/issue.rb</filename>
    </modified>
    <modified>
      <diff>@@ -74,6 +74,9 @@ class Mailer &lt; ActionMailer::Base
     added_to = ''
     added_to_url = ''
     case container.class.name
+    when 'Project'
+      added_to_url = url_for(:controller =&gt; 'projects', :action =&gt; 'list_files', :id =&gt; container)
+      added_to = &quot;#{l(:label_project)}: #{container}&quot;
     when 'Version'
       added_to_url = url_for(:controller =&gt; 'projects', :action =&gt; 'list_files', :id =&gt; container.project_id)
       added_to = &quot;#{l(:label_version)}: #{container.name}&quot;</diff>
      <filename>app/models/mailer.rb</filename>
    </modified>
    <modified>
      <diff>@@ -19,7 +19,7 @@ class Message &lt; ActiveRecord::Base
   belongs_to :board
   belongs_to :author, :class_name =&gt; 'User', :foreign_key =&gt; 'author_id'
   acts_as_tree :counter_cache =&gt; :replies_count, :order =&gt; &quot;#{Message.table_name}.created_on ASC&quot;
-  has_many :attachments, :as =&gt; :container, :dependent =&gt; :destroy
+  acts_as_attachable
   belongs_to :last_reply, :class_name =&gt; 'Message', :foreign_key =&gt; 'last_reply_id'
   
   acts_as_searchable :columns =&gt; ['subject', 'content'],</diff>
      <filename>app/models/message.rb</filename>
    </modified>
    <modified>
      <diff>@@ -44,6 +44,8 @@ class Project &lt; ActiveRecord::Base
                           :association_foreign_key =&gt; 'custom_field_id'
                           
   acts_as_tree :order =&gt; &quot;name&quot;, :counter_cache =&gt; true
+  acts_as_attachable :view_permission =&gt; :view_files,
+                     :delete_permission =&gt; :manage_files
 
   acts_as_customizable
   acts_as_searchable :columns =&gt; ['name', 'description'], :project_key =&gt; 'id', :permission =&gt; nil</diff>
      <filename>app/models/project.rb</filename>
    </modified>
    <modified>
      <diff>@@ -19,7 +19,8 @@ class Version &lt; ActiveRecord::Base
   before_destroy :check_integrity
   belongs_to :project
   has_many :fixed_issues, :class_name =&gt; 'Issue', :foreign_key =&gt; 'fixed_version_id'
-  has_many :attachments, :as =&gt; :container, :dependent =&gt; :destroy
+  acts_as_attachable :view_permission =&gt; :view_files,
+                     :delete_permission =&gt; :manage_files
 
   validates_presence_of :name
   validates_uniqueness_of :name, :scope =&gt; [:project_id]</diff>
      <filename>app/models/version.rb</filename>
    </modified>
    <modified>
      <diff>@@ -21,7 +21,7 @@ require 'enumerator'
 class WikiPage &lt; ActiveRecord::Base
   belongs_to :wiki
   has_one :content, :class_name =&gt; 'WikiContent', :foreign_key =&gt; 'page_id', :dependent =&gt; :destroy
-  has_many :attachments, :as =&gt; :container, :dependent =&gt; :destroy
+  acts_as_attachable :delete_permission =&gt; :delete_wiki_pages_attachments
   acts_as_tree :order =&gt; 'title'
   
   acts_as_event :title =&gt; Proc.new {|o| &quot;#{l(:label_wiki)}: #{o.title}&quot;},
@@ -111,6 +111,10 @@ class WikiPage &lt; ActiveRecord::Base
   def editable_by?(usr)
     !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
   end
+        
+  def attachments_deletable?(usr=User.current)
+    editable_by?(usr) &amp;&amp; super(usr)
+  end
   
   def parent_title
     @parent_title || (self.parent &amp;&amp; self.parent.pretty_title)</diff>
      <filename>app/models/wiki_page.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,14 +3,14 @@
 &lt;p&gt;&lt;%= link_to_attachment attachment, :class =&gt; 'icon icon-attachment' -%&gt;
 &lt;%= h(&quot; - #{attachment.description}&quot;) unless attachment.description.blank? %&gt;
   &lt;span class=&quot;size&quot;&gt;(&lt;%= number_to_human_size attachment.filesize %&gt;)&lt;/span&gt;
-  &lt;% if options[:delete_url] %&gt;
-    &lt;%= link_to image_tag('delete.png'), options[:delete_url].update({:attachment_id =&gt; attachment}),
+  &lt;% if options[:deletable] %&gt;
+    &lt;%= link_to image_tag('delete.png'), {:controller =&gt; 'attachments', :action =&gt; 'destroy', :id =&gt; attachment},
                                          :confirm =&gt; l(:text_are_you_sure),
                                          :method =&gt; :post,
                                          :class =&gt; 'delete',
                                          :title =&gt; l(:button_delete) %&gt;
   &lt;% end %&gt;
-  &lt;% unless options[:no_author] %&gt;
+  &lt;% if options[:author] %&gt;
     &lt;span class=&quot;author&quot;&gt;&lt;%= attachment.author %&gt;, &lt;%= format_time(attachment.created_on) %&gt;&lt;/span&gt;
   &lt;% end %&gt;
   &lt;/p&gt;</diff>
      <filename>app/views/attachments/_links.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,7 @@
 &lt;/div&gt;
 
 &lt;h3&gt;&lt;%= l(:label_attachment_plural) %&gt;&lt;/h3&gt;
-&lt;%= link_to_attachments @attachments, :delete_url =&gt; (authorize_for('documents', 'destroy_attachment') ? {:controller =&gt; 'documents', :action =&gt; 'destroy_attachment', :id =&gt; @document} : nil) %&gt;
+&lt;%= link_to_attachments @document %&gt;
 
 &lt;% if authorize_for('documents', 'add_attachment') %&gt;
 &lt;p&gt;&lt;%= link_to l(:label_attachment_new), {}, :onclick =&gt; &quot;Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;&quot;,</diff>
      <filename>app/views/documents/show.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -67,9 +67,7 @@ end %&gt;
 &lt;%= textilizable @issue, :description, :attachments =&gt; @issue.attachments %&gt;
 &lt;/div&gt;
 
-&lt;% if @issue.attachments.any? %&gt;
-&lt;%= link_to_attachments @issue.attachments, :delete_url =&gt; (authorize_for('issues', 'destroy_attachment') ? {:controller =&gt; 'issues', :action =&gt; 'destroy_attachment', :id =&gt; @issue} : nil) %&gt;
-&lt;% end %&gt;
+&lt;%= link_to_attachments @issue %&gt;
 
 &lt;% if authorize_for('issue_relations', 'new') || @issue.relations.any? %&gt;
 &lt;hr /&gt;</diff>
      <filename>app/views/issues/show.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -15,7 +15,7 @@
 &lt;div class=&quot;wiki&quot;&gt;
 &lt;%= textilizable(@topic.content, :attachments =&gt; @topic.attachments) %&gt;
 &lt;/div&gt;
-&lt;%= link_to_attachments @topic.attachments, :no_author =&gt; true %&gt;
+&lt;%= link_to_attachments @topic, :author =&gt; false %&gt;
 &lt;/div&gt;
 &lt;br /&gt;
 
@@ -31,7 +31,7 @@
   &lt;div class=&quot;message reply&quot;&gt;
   &lt;h4&gt;&lt;%=h message.subject %&gt; - &lt;%= authoring message.created_on, message.author %&gt;&lt;/h4&gt;
   &lt;div class=&quot;wiki&quot;&gt;&lt;%= textilizable message, :content, :attachments =&gt; message.attachments %&gt;&lt;/div&gt;
-  &lt;%= link_to_attachments message.attachments, :no_author =&gt; true %&gt;
+  &lt;%= link_to_attachments message, :author =&gt; false %&gt;
   &lt;/div&gt;
 &lt;% end %&gt;
 &lt;% end %&gt;</diff>
      <filename>app/views/messages/show.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -4,10 +4,13 @@
 &lt;div class=&quot;box&quot;&gt;
 &lt;% form_tag({ :action =&gt; 'add_file', :id =&gt; @project }, :multipart =&gt; true, :class =&gt; &quot;tabular&quot;) do %&gt;
 
-&lt;p&gt;&lt;label for=&quot;version_id&quot;&gt;&lt;%=l(:field_version)%&gt; &lt;span class=&quot;required&quot;&gt;*&lt;/span&gt;&lt;/label&gt;
-&lt;%= select_tag &quot;version_id&quot;, options_from_collection_for_select(@versions, &quot;id&quot;, &quot;name&quot;) %&gt;&lt;/p&gt;
+&lt;% if @versions.any? %&gt;
+&lt;p&gt;&lt;label for=&quot;version_id&quot;&gt;&lt;%=l(:field_version)%&gt;&lt;/label&gt;
+&lt;%= select_tag &quot;version_id&quot;, content_tag('option', '') +
+														 options_from_collection_for_select(@versions, &quot;id&quot;, &quot;name&quot;) %&gt;&lt;/p&gt;
+&lt;% end %&gt;
 
 &lt;p&gt;&lt;label&gt;&lt;%=l(:label_attachment_plural)%&gt;&lt;/label&gt;&lt;%= render :partial =&gt; 'attachments/form' %&gt;&lt;/p&gt;
 &lt;/div&gt;
 &lt;%= submit_tag l(:button_add) %&gt;
-&lt;% end %&gt; 
\ No newline at end of file
+&lt;% end %&gt; </diff>
      <filename>app/views/projects/add_file.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -4,39 +4,37 @@
 
 &lt;h2&gt;&lt;%=l(:label_attachment_plural)%&gt;&lt;/h2&gt;
 
-&lt;% delete_allowed = authorize_for('versions', 'destroy_file') %&gt;
+&lt;% delete_allowed = User.current.allowed_to?(:manage_files, @project) %&gt;
 
 &lt;table class=&quot;list&quot;&gt;
   &lt;thead&gt;&lt;tr&gt;
-    &lt;th&gt;&lt;%=l(:field_version)%&gt;&lt;/th&gt;
     &lt;%= sort_header_tag('filename', :caption =&gt; l(:field_filename)) %&gt;
     &lt;%= sort_header_tag('created_on', :caption =&gt; l(:label_date), :default_order =&gt; 'desc') %&gt;
     &lt;%= sort_header_tag('size', :caption =&gt; l(:field_filesize), :default_order =&gt; 'desc') %&gt;
     &lt;%= sort_header_tag('downloads', :caption =&gt; l(:label_downloads_abbr), :default_order =&gt; 'desc') %&gt;
     &lt;th&gt;MD5&lt;/th&gt;
-    &lt;% if delete_allowed %&gt;&lt;th&gt;&lt;/th&gt;&lt;% end %&gt;
+    &lt;th&gt;&lt;/th&gt;
   &lt;/tr&gt;&lt;/thead&gt;
   &lt;tbody&gt;
-&lt;% for version in @versions %&gt;	
-  &lt;% unless version.attachments.empty? %&gt;
-  &lt;tr&gt;&lt;th colspan=&quot;7&quot; align=&quot;left&quot;&gt;&lt;span class=&quot;icon icon-package&quot;&gt;&lt;b&gt;&lt;%= version.name %&gt;&lt;/b&gt;&lt;/span&gt;&lt;/th&gt;&lt;/tr&gt;
-  &lt;% for file in version.attachments %&gt;		
+&lt;% @containers.each do |container| %&gt;	
+  &lt;% next if container.attachments.empty? -%&gt;
+	&lt;% if container.is_a?(Version) -%&gt;
+  &lt;tr&gt;&lt;th colspan=&quot;6&quot; align=&quot;left&quot;&gt;&lt;span class=&quot;icon icon-package&quot;&gt;&lt;b&gt;&lt;%=h container %&gt;&lt;/b&gt;&lt;/span&gt;&lt;/th&gt;&lt;/tr&gt;
+	&lt;% end -%&gt;
+  &lt;% container.attachments.each do |file| %&gt;		
   &lt;tr class=&quot;&lt;%= cycle(&quot;odd&quot;, &quot;even&quot;) %&gt;&quot;&gt;
-    &lt;td&gt;&lt;/td&gt;
     &lt;td&gt;&lt;%= link_to_attachment file, :download =&gt; true, :title =&gt; file.description %&gt;&lt;/td&gt;
     &lt;td align=&quot;center&quot;&gt;&lt;%= format_time(file.created_on) %&gt;&lt;/td&gt;
     &lt;td align=&quot;center&quot;&gt;&lt;%= number_to_human_size(file.filesize) %&gt;&lt;/td&gt;
     &lt;td align=&quot;center&quot;&gt;&lt;%= file.downloads %&gt;&lt;/td&gt;
     &lt;td align=&quot;center&quot;&gt;&lt;small&gt;&lt;%= file.digest %&gt;&lt;/small&gt;&lt;/td&gt;
-    &lt;% if delete_allowed %&gt;
     &lt;td align=&quot;center&quot;&gt;
-        &lt;%= link_to_if_authorized image_tag('delete.png'), {:controller =&gt; 'versions', :action =&gt; 'destroy_file', :id =&gt; version, :attachment_id =&gt; file}, :confirm =&gt; l(:text_are_you_sure), :method =&gt; :post %&gt;
+    &lt;%= link_to(image_tag('delete.png'), {:controller =&gt; 'attachments', :action =&gt; 'destroy', :id =&gt; file},
+																				 :confirm =&gt; l(:text_are_you_sure), :method =&gt; :post) if delete_allowed %&gt;
     &lt;/td&gt;
-    &lt;% end %&gt;
   &lt;/tr&gt;		
   &lt;% end
   reset_cycle %&gt;
-  &lt;% end %&gt;
 &lt;% end %&gt;
   &lt;/tbody&gt;
 &lt;/table&gt;</diff>
      <filename>app/views/projects/list_files.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 &lt;%= render(:partial =&gt; &quot;wiki/content&quot;, :locals =&gt; {:content =&gt; @content}) %&gt;
 
-&lt;%= link_to_attachments @page.attachments, :delete_url =&gt; ((@editable &amp;&amp; authorize_for('wiki', 'destroy_attachment')) ? {:controller =&gt; 'wiki', :action =&gt; 'destroy_attachment', :page =&gt; @page.title} : nil) %&gt;
+&lt;%= link_to_attachments @page %&gt;
 
 &lt;% if @editable &amp;&amp; authorize_for('wiki', 'add_attachment') %&gt;
 &lt;p&gt;&lt;%= link_to l(:label_attachment_new), {}, :onclick =&gt; &quot;Element.show('add_attachment_form'); Element.hide(this); Element.scrollTo('add_attachment_form'); return false;&quot;,</diff>
      <filename>app/views/wiki/show.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -8,6 +8,7 @@ http://www.redmine.org/
 == v0.8.1
 
 * Select watchers on new issue form
+* Files module: ability to add files without version
 * Show view/annotate/download links on entry and annotate views
 * Fixed: Deleted files are shown when using Darcs
 </diff>
      <filename>doc/CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -35,7 +35,7 @@ Redmine::AccessControl.map do |map|
                                   :queries =&gt; :index,
                                   :reports =&gt; :issue_report}, :public =&gt; true                    
     map.permission :add_issues, {:issues =&gt; :new}
-    map.permission :edit_issues, {:issues =&gt; [:edit, :reply, :bulk_edit, :destroy_attachment]}
+    map.permission :edit_issues, {:issues =&gt; [:edit, :reply, :bulk_edit]}
     map.permission :manage_issue_relations, {:issue_relations =&gt; [:new, :destroy]}
     map.permission :add_issue_notes, {:issues =&gt; [:edit, :reply]}
     map.permission :edit_issue_notes, {:journals =&gt; :edit}, :require =&gt; :loggedin
@@ -67,12 +67,12 @@ Redmine::AccessControl.map do |map|
   end
 
   map.project_module :documents do |map|
-    map.permission :manage_documents, {:documents =&gt; [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require =&gt; :loggedin
+    map.permission :manage_documents, {:documents =&gt; [:new, :edit, :destroy, :add_attachment]}, :require =&gt; :loggedin
     map.permission :view_documents, :documents =&gt; [:index, :show, :download]
   end
   
   map.project_module :files do |map|
-    map.permission :manage_files, {:projects =&gt; :add_file, :versions =&gt; :destroy_file}, :require =&gt; :loggedin
+    map.permission :manage_files, {:projects =&gt; :add_file}, :require =&gt; :loggedin
     map.permission :view_files, :projects =&gt; :list_files, :versions =&gt; :download
   end
     
@@ -83,7 +83,7 @@ Redmine::AccessControl.map do |map|
     map.permission :view_wiki_pages, :wiki =&gt; [:index, :special]
     map.permission :view_wiki_edits, :wiki =&gt; [:history, :diff, :annotate]
     map.permission :edit_wiki_pages, :wiki =&gt; [:edit, :preview, :add_attachment]
-    map.permission :delete_wiki_pages_attachments, :wiki =&gt; :destroy_attachment
+    map.permission :delete_wiki_pages_attachments, {}
     map.permission :protect_wiki_pages, {:wiki =&gt; :protect}, :require =&gt; :member
   end
     </diff>
      <filename>lib/redmine.rb</filename>
    </modified>
    <modified>
      <diff>@@ -85,4 +85,28 @@ attachments_007:
   filename: archive.zip
   author_id: 1
   content_type: application/octet-stream
+attachments_008: 
+  created_on: 2006-07-19 21:07:27 +02:00
+  container_type: Project
+  container_id: 1
+  downloads: 0
+  disk_filename: 060719210727_project_file.zip
+  digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
+  id: 8
+  filesize: 320
+  filename: project_file.zip
+  author_id: 2
+  content_type: application/octet-stream
+attachments_009: 
+  created_on: 2006-07-19 21:07:27 +02:00
+  container_type: Version
+  container_id: 1
+  downloads: 0
+  disk_filename: 060719210727_version_file.zip
+  digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
+  id: 9
+  filesize: 452
+  filename: version_file.zip
+  author_id: 2
+  content_type: application/octet-stream
   
\ No newline at end of file</diff>
      <filename>test/fixtures/attachments.yml</filename>
    </modified>
    <modified>
      <diff>@@ -76,4 +76,50 @@ class AttachmentsControllerTest &lt; Test::Unit::TestCase
     get :download, :id =&gt; 7
     assert_redirected_to 'account/login'
   end
+  
+  def test_destroy_issue_attachment
+    issue = Issue.find(3)
+    @request.session[:user_id] = 2
+    
+    assert_difference 'issue.attachments.count', -1 do
+      post :destroy, :id =&gt; 1
+    end
+    # no referrer
+    assert_redirected_to 'projects/show/ecookbook'
+    assert_nil Attachment.find_by_id(1)
+    j = issue.journals.find(:first, :order =&gt; 'created_on DESC')
+    assert_equal 'attachment', j.details.first.property
+    assert_equal '1', j.details.first.prop_key
+    assert_equal 'error281.txt', j.details.first.old_value
+  end
+  
+  def test_destroy_wiki_page_attachment
+    @request.session[:user_id] = 2
+    assert_difference 'Attachment.count', -1 do
+      post :destroy, :id =&gt; 3
+      assert_response 302
+    end
+  end
+  
+  def test_destroy_project_attachment
+    @request.session[:user_id] = 2
+    assert_difference 'Attachment.count', -1 do
+      post :destroy, :id =&gt; 8
+      assert_response 302
+    end
+  end
+  
+  def test_destroy_version_attachment
+    @request.session[:user_id] = 2
+    assert_difference 'Attachment.count', -1 do
+      post :destroy, :id =&gt; 9
+      assert_response 302
+    end
+  end
+  
+  def test_destroy_without_permission
+    post :destroy, :id =&gt; 3
+    assert_redirected_to '/login'
+    assert Attachment.find_by_id(3)
+  end
 end</diff>
      <filename>test/functional/attachments_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -747,17 +747,4 @@ class IssuesControllerTest &lt; Test::Unit::TestCase
     assert_equal 2, TimeEntry.find(1).issue_id
     assert_equal 2, TimeEntry.find(2).issue_id
   end
-  
-  def test_destroy_attachment
-    issue = Issue.find(3)
-    a = issue.attachments.size
-    @request.session[:user_id] = 2
-    post :destroy_attachment, :id =&gt; 3, :attachment_id =&gt; 1
-    assert_redirected_to 'issues/show/3'
-    assert_nil Attachment.find_by_id(1)
-    issue.reload
-    assert_equal((a-1), issue.attachments.size)
-    j = issue.journals.find(:first, :order =&gt; 'created_on DESC')
-    assert_equal 'attachment', j.details.first.property
-  end
 end</diff>
      <filename>test/functional/issues_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -64,7 +64,7 @@ class MessagesControllerTest &lt; Test::Unit::TestCase
   def test_post_new
     @request.session[:user_id] = 2
     ActionMailer::Base.deliveries.clear
-    Setting.notified_events &lt;&lt; 'message_posted'
+    Setting.notified_events = ['message_posted']
     
     post :new, :board_id =&gt; 1,
                :message =&gt; { :subject =&gt; 'Test created message',</diff>
      <filename>test/functional/messages_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,7 +23,8 @@ class ProjectsController; def rescue_action(e) raise e end; end
 
 class ProjectsControllerTest &lt; Test::Unit::TestCase
   fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
-           :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
+           :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
+           :attachments
 
   def setup
     @controller = ProjectsController.new
@@ -112,12 +113,56 @@ class ProjectsControllerTest &lt; Test::Unit::TestCase
     assert_redirected_to 'admin/projects'
     assert_nil Project.find_by_id(1)
   end
+  
+  def test_add_file
+    set_tmp_attachments_directory
+    @request.session[:user_id] = 2
+    Setting.notified_events = ['file_added']
+    ActionMailer::Base.deliveries.clear
+    
+    assert_difference 'Attachment.count' do
+      post :add_file, :id =&gt; 1, :version_id =&gt; '',
+           :attachments =&gt; {'1' =&gt; {'file' =&gt; test_uploaded_file('testfile.txt', 'text/plain')}}
+    end
+    assert_redirected_to 'projects/list_files/ecookbook'
+    a = Attachment.find(:first, :order =&gt; 'created_on DESC')
+    assert_equal 'testfile.txt', a.filename
+    assert_equal Project.find(1), a.container
+
+    mail = ActionMailer::Base.deliveries.last
+    assert_kind_of TMail::Mail, mail
+    assert_equal &quot;[eCookbook] New file&quot;, mail.subject
+    assert mail.body.include?('testfile.txt')
+  end
+  
+  def test_add_version_file
+    set_tmp_attachments_directory
+    @request.session[:user_id] = 2
+    Setting.notified_events = ['file_added']
+    
+    assert_difference 'Attachment.count' do
+      post :add_file, :id =&gt; 1, :version_id =&gt; '2',
+           :attachments =&gt; {'1' =&gt; {'file' =&gt; test_uploaded_file('testfile.txt', 'text/plain')}}
+    end
+    assert_redirected_to 'projects/list_files/ecookbook'
+    a = Attachment.find(:first, :order =&gt; 'created_on DESC')
+    assert_equal 'testfile.txt', a.filename
+    assert_equal Version.find(2), a.container
+  end
   
   def test_list_files
     get :list_files, :id =&gt; 1
     assert_response :success
     assert_template 'list_files'
-    assert_not_nil assigns(:versions)
+    assert_not_nil assigns(:containers)
+    
+    # file attached to the project
+    assert_tag :a, :content =&gt; 'project_file.zip',
+                   :attributes =&gt; { :href =&gt; '/attachments/download/8/project_file.zip' }
+    
+    # file attached to a project's version
+    assert_tag :a, :content =&gt; 'version_file.zip',
+                   :attributes =&gt; { :href =&gt; '/attachments/download/9/version_file.zip' }
   end
 
   def test_changelog</diff>
      <filename>test/functional/projects_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -252,13 +252,6 @@ class WikiControllerTest &lt; Test::Unit::TestCase
     assert_template 'edit'    
   end
   
-  def test_destroy_attachment
-    @request.session[:user_id] = 2
-    assert_difference 'Attachment.count', -1 do
-      post :destroy_attachment, :id =&gt; 1, :page =&gt; 'Page_with_an_inline_image', :attachment_id =&gt; 3
-    end
-  end
-  
   def test_history_of_non_existing_page_should_return_404
     get :history, :id =&gt; 1, :page =&gt; 'Unknown_page'
     assert_response 404</diff>
      <filename>test/functional/wiki_controller_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>dea10c54f9f9fed80d60e0358d9f2675937daad9</id>
    </parent>
  </parents>
  <author>
    <name>Jean-Philippe Lang</name>
    <email>jp_lang@yahoo.fr</email>
  </author>
  <url>http://github.com/tricycle/redmine/commit/31178553f33063f2ee259d9b13fef63568f11318</url>
  <id>31178553f33063f2ee259d9b13fef63568f11318</id>
  <committed-date>2008-12-31T03:40:03-08:00</committed-date>
  <authored-date>2008-12-31T03:40:03-08:00</authored-date>
  <message>Merged r2116, r2117 and r2187 from trunk.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.8-stable@2217 e93f8b46-1217-0410-a6f0-8f06a7374b81</message>
  <tree>50f9a84624b91de38e4b25d2409f85db8a98eacc</tree>
  <committer>
    <name>Jean-Philippe Lang</name>
    <email>jp_lang@yahoo.fr</email>
  </committer>
</commit>
