OSDN Git Service

Attachments can now be added to wiki pages (original patch by Pavol Murin). Only...
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 26 May 2007 15:42:37 +0000 (15:42 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 26 May 2007 15:42:37 +0000 (15:42 +0000)
Attached images can be displayed inline, using textile image tag (for wiki pages, issue descriptions and forum messages).

git-svn-id: http://redmine.rubyforge.org/svn/trunk@541 e93f8b46-1217-0410-a6f0-8f06a7374b81

19 files changed:
app/controllers/attachments_controller.rb [new file with mode: 0644]
app/controllers/issues_controller.rb
app/controllers/messages_controller.rb
app/controllers/wiki_controller.rb
app/helpers/application_helper.rb
app/helpers/attachments_helper.rb [new file with mode: 0644]
app/models/attachment.rb
app/models/message.rb
app/models/wiki.rb
app/models/wiki_page.rb
app/views/attachments/_form.rhtml [new file with mode: 0644]
app/views/attachments/_links.rhtml [new file with mode: 0644]
app/views/issues/show.rhtml
app/views/messages/_form.rhtml
app/views/messages/show.rhtml
app/views/wiki/_preview.rhtml
app/views/wiki/show.rhtml
db/migrate/050_add_wiki_attachments_permissions.rb [new file with mode: 0644]
public/stylesheets/application.css

diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb
new file mode 100644 (file)
index 0000000..3528e72
--- /dev/null
@@ -0,0 +1,39 @@
+# 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.
+
+class AttachmentsController < ApplicationController
+  before_filter :find_project, :check_project_privacy
+
+  # sends an attachment
+  def download
+    send_file @attachment.diskfile, :filename => @attachment.filename
+  end
+    
+  # sends an image to be displayed inline
+  def show
+    render(:nothing => true, :status => 404) and return unless @attachment.diskfile =~ /\.(jpeg|jpg|gif|png)$/i
+    send_file @attachment.diskfile, :type => "image/#{$1}", :disposition => 'inline'
+  end
+private
+  def find_project
+    @attachment = Attachment.find(params[:id])
+    @project = @attachment.project
+  rescue
+    render_404
+  end
+end
index 0a8d19a..f57dfc8 100644 (file)
@@ -29,6 +29,8 @@ class IssuesController < ApplicationController
   include IssueRelationsHelper
   helper :watchers
   include WatchersHelper
+  helper :attachments
+  include AttachmentsHelper   
 
   def show
     @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
@@ -146,14 +148,6 @@ class IssuesController < ApplicationController
     redirect_to :action => 'show', :id => @issue
   end
 
-  # Send the file in stream mode
-  def download
-    @attachment = @issue.attachments.find(params[:attachment_id])
-    send_file @attachment.diskfile, :filename => @attachment.filename
-  rescue
-    render_404
-  end
-
 private
   def find_project
     @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
index 16a0409..1b0d2a6 100644 (file)
@@ -22,6 +22,9 @@ class MessagesController < ApplicationController
 
   verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
 
+  helper :attachments
+  include AttachmentsHelper   
+
   def show
     @reply = Message.new(:subject => "RE: #{@message.subject}")
     render :action => "show", :layout => false if request.xhr?
@@ -48,13 +51,6 @@ class MessagesController < ApplicationController
     redirect_to :action => 'show', :id => @message
   end
   
-  def download
-    @attachment = @message.attachments.find(params[:attachment_id])
-    send_file @attachment.diskfile, :filename => @attachment.filename
-  rescue
-    render_404
-  end
-  
 private
   def find_project
     @board = Board.find(params[:board_id], :include => :project)
index f68b71e..d8f23cf 100644 (file)
 
 class WikiController < ApplicationController
   layout 'base'
-  before_filter :find_wiki, :check_project_privacy, :except => [:preview]
-  before_filter :authorize, :only => :destroy
+  before_filter :find_wiki, :check_project_privacy
+  before_filter :authorize, :only => [:destroy, :add_attachment, :destroy_attachment]
   
-  verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
+  verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index }
+
+  helper :attachments
+  include AttachmentsHelper   
   
   # display a page (in editing mode if it doesn't exist)
   def index
@@ -107,10 +110,28 @@ class WikiController < ApplicationController
   end
   
   def preview
+    page = @wiki.find_page(params[:page])
+    @attachements = page.attachments if page
     @text = params[:content][:text]
     render :partial => 'preview'
   end
 
+  def add_attachment
+    @page = @wiki.find_page(params[:page])
+    # Save the attachments
+    params[:attachments].each { |file|
+      next unless file.size > 0
+      a = Attachment.create(:container => @page, :file => file, :author => logged_in_user)
+    } if params[:attachments] and params[:attachments].is_a? Array
+    redirect_to :action => 'index', :page => @page.title
+  end
+
+  def destroy_attachment
+    @page = @wiki.find_page(params[:page])
+    @page.attachments.find(params[:attachment_id]).destroy
+    redirect_to :action => 'index', :page => @page.title
+  end
+
 private
   
   def find_wiki
index cd1b2ea..12302a0 100644 (file)
@@ -146,7 +146,24 @@ module ApplicationHelper
     # example:
     #   r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6)
     text = text.gsub(/(?=\b)r(\d+)(?=\b)/) {|m| link_to "r#{$1}", :controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1} if @project
-   
+    
+    # when using an image link, try to use an attachment, if possible
+    attachments = options[:attachments]
+    if attachments
+      text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
+        align = $1
+        filename = $2
+        rf = Regexp.new(filename,  Regexp::IGNORECASE)
+        # search for the picture in attachments
+        if found = attachments.detect { |att| att.filename =~ rf }
+          image_url = url_for :controller => 'attachments', :action => 'show', :id => found.id
+          "!#{align}#{image_url}!"
+        else
+          "!#{align}#{filename}!"
+        end
+      end
+    end
+
     # finally textilize text
     @do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize")
     text = @do_textilize ? auto_link(RedCloth.new(text, [:hard_breaks]).to_html) : simple_format(auto_link(h(text)))
diff --git a/app/helpers/attachments_helper.rb b/app/helpers/attachments_helper.rb
new file mode 100644 (file)
index 0000000..989cd3e
--- /dev/null
@@ -0,0 +1,25 @@
+# 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.
+
+module AttachmentsHelper
+  # displays the links to a collection of attachments
+  def link_to_attachments(attachments, options = {})
+    if attachments.any?
+      render :partial => 'attachments/links', :locals => {:attachments => attachments, :options => options}
+    end
+  end
+end
index 82fcdad..2be4e16 100644 (file)
@@ -77,7 +77,11 @@ class Attachment < ActiveRecord::Base
        def self.most_downloaded
                find(:all, :limit => 5, :order => "downloads DESC")     
        end
-       
+
+  def project
+    container.is_a?(Project) ? container : container.project
+  end
+  
 private
   def sanitize_filename(value)
       # get only the filename, not the whole path
index 1f8dde5..935141b 100644 (file)
@@ -34,4 +34,8 @@ class Message < ActiveRecord::Base
       board.increment! :topics_count
     end
   end
+  
+  def project
+    board.project
+  end
 end
index 8233c3d..8d461a8 100644 (file)
@@ -31,7 +31,8 @@ class Wiki < ActiveRecord::Base
   
   # find the page with the given title
   def find_page(title)
-    pages.find_by_title(Wiki.titleize(title || start_page))
+    title = start_page if title.blank?
+    pages.find_by_title(Wiki.titleize(title))
   end
   
   # turn a string into a valid page title
index b7964a1..5624651 100644 (file)
@@ -18,6 +18,7 @@
 class WikiPage < ActiveRecord::Base
   belongs_to :wiki
   has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
+  has_many :attachments, :as => :container, :dependent => :destroy
   
   validates_presence_of :title
   validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
@@ -41,4 +42,8 @@ class WikiPage < ActiveRecord::Base
   def self.pretty_title(str)
     (str && str.is_a?(String)) ? str.tr('_', ' ') : str
   end
+  
+  def project
+    wiki.project
+  end
 end
diff --git a/app/views/attachments/_form.rhtml b/app/views/attachments/_form.rhtml
new file mode 100644 (file)
index 0000000..18f08c6
--- /dev/null
@@ -0,0 +1,4 @@
+<p id="attachments_p"><label for="attachment_file"><%=l(:label_attachment)%>
+<%= image_to_function "add.png", "addFileField();return false" %></label>
+
+<%= file_field_tag 'attachments[]', :size => 30  %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
diff --git a/app/views/attachments/_links.rhtml b/app/views/attachments/_links.rhtml
new file mode 100644 (file)
index 0000000..93d6b2a
--- /dev/null
@@ -0,0 +1,13 @@
+<div class="attachments">
+<% for attachment in attachments %>
+  <p><%= link_to attachment.filename, {:controller => 'attachments', :action => 'download', :id => attachment }, :class => 'icon icon-attachment' %>
+  (<%= number_to_human_size attachment.filesize %>)
+  <% unless options[:no_author] %>
+    <em><%= attachment.author.display_name %>, <%= format_date(attachment.created_on) %></em>
+  <% end %>
+  <% if options[:delete_url] %>
+    <%= link_to image_tag('delete.png'), options[:delete_url].update({:attachment_id => attachment}), :confirm => l(:text_are_you_sure), :method => :post %>
+  <% end %>
+  </p>
+<% end %>
+</div>
index 1895e7c..2c4905e 100644 (file)
@@ -52,7 +52,7 @@ end %>
 <% end %>
 
 <b><%=l(:field_description)%> :</b><br /><br />
-<%= textilizable @issue.description %>
+<%= textilizable @issue.description, :attachments => @issue.attachments %>
 <br />
 
 <div class="contextual">
@@ -92,24 +92,14 @@ end %>
 
 <div class="box">
 <h3><%=l(:label_attachment_plural)%></h3>
-<table width="100%">
-<% for attachment in @issue.attachments %>
-<tr>
-<td><%= link_to attachment.filename, { :action => 'download', :id => @issue, :attachment_id => attachment }, :class => 'icon icon-attachment' %> (<%= number_to_human_size(attachment.filesize) %>)</td>
-<td><%= format_date(attachment.created_on) %></td>
-<td><%= attachment.author.display_name %></td>
-<td><div class="contextual"><%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy_attachment', :id => @issue, :attachment_id => attachment }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %></div></td>
-</tr>
-<% end %>
-</table>
-<br />
+<%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
+
 <% if authorize_for('issues', 'add_attachment') %>
-  <% form_tag({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true, :class => "tabular") do %>
-  <p id="attachments_p"><label><%=l(:label_attachment_new)%>
-  <%= image_to_function "add.png", "addFileField();return false" %></label>
-  <%= file_field_tag 'attachments[]', :size => 30  %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
-  <%= submit_tag l(:button_add) %>
-  <% end %> 
+<p><%= toggle_link l(:label_attachment_new), "add_attachment_form" %></p>
+<% form_tag({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true, :class => "tabular", :id => "add_attachment_form", :style => "display:none;") do %>
+  <%= render :partial => 'attachments/form' %>
+<%= submit_tag l(:button_add) %>
+<% end %>
 <% end %>
 </div>
 
index 453bd8b..93e4311 100644 (file)
@@ -10,8 +10,5 @@
 <!--[eoform:message]-->
 
 <span class="tabular">
-<p id="attachments_p"><label><%=l(:label_attachment)%>
-<%= image_to_function "add.png", "addFileField();return false" %></label>
-<%= file_field_tag 'attachments[]', :size => 30  %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
-</span>
+<%= render :partial => 'attachments/form' %>
 </div>
index 8068da0..c18eb52 100644 (file)
@@ -2,15 +2,10 @@
 
 <p><em><%= @message.author.name %>, <%= format_time(@message.created_on) %></em></p>
 <div class="wiki">
-<%= textilizable(@message.content) %>
+<%= textilizable(@message.content, :attachments => @message.attachments) %>
 </div>
-<div class="attachments">
-<% @message.attachments.each do |attachment| %>
-<%= link_to attachment.filename, { :action => 'download', :id => @message, :attachment_id => attachment }, :class => 'icon icon-attachment' %>
-(<%= number_to_human_size(attachment.filesize) %>)<br />
-<% end %>
-</div>
-<br />
+<%= link_to_attachments @message.attachments, :no_author => true %>
+
 <h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3>
 <% @message.children.each do |message| %>
   <a name="<%= "message-#{message.id}" %>"></a>
@@ -28,4 +23,4 @@
   <p><%= submit_tag l(:button_submit) %></p>
 <% end %>
 </div>
-<% end %>
\ No newline at end of file
+<% end %>
index 7865d91..e3bfc3a 100644 (file)
@@ -1,3 +1,3 @@
 <fieldset class="preview"><legend><%= l(:label_preview) %></legend>
-<%= textilizable @text %>
+<%= textilizable @text, :attachments => @attachements %>
 </fieldset>
index c2deba0..8bb757a 100644 (file)
 
 <div class="wiki">
 <% cache "wiki/show/#{@page.id}/#{@content.version}" do %>
-<%= textilizable @content.text %>
+<%= textilizable @content.text, :attachments => @page.attachments %>
 <% end %>
 </div>
 
+<%= link_to_attachments @page.attachments, :delete_url => (authorize_for('wiki', 'destroy_attachment') ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %>
+
 <div class="contextual">
 <%= l(:label_export_to) %>
 <%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>,
 <%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %>
-</div>
\ No newline at end of file
+</div>
+
+<% if authorize_for('wiki', 'add_attachment') %>
+<p><%= toggle_link l(:label_attachment_new), "add_attachment_form" %></p>
+<% form_tag({ :controller => 'wiki', :action => 'add_attachment', :page => @page.title }, :multipart => true, :class => "tabular", :id => "add_attachment_form", :style => "display:none;") do %>
+  <%= render :partial => 'attachments/form' %>
+<%= submit_tag l(:button_add) %>
+<% end %>
+<% end %>
diff --git a/db/migrate/050_add_wiki_attachments_permissions.rb b/db/migrate/050_add_wiki_attachments_permissions.rb
new file mode 100644 (file)
index 0000000..6c81273
--- /dev/null
@@ -0,0 +1,11 @@
+class AddWikiAttachmentsPermissions < ActiveRecord::Migration
+  def self.up
+    Permission.create :controller => 'wiki', :action => 'add_attachment', :description => 'label_attachment_new', :sort => 1750, :is_public => false, :mail_option => 0, :mail_enabled => 0
+    Permission.create :controller => 'wiki', :action => 'destroy_attachment', :description => 'label_attachment_delete', :sort => 1755, :is_public => false, :mail_option => 0, :mail_enabled => 0
+  end
+
+  def self.down
+    Permission.find_by_controller_and_action('wiki', 'add_attachment').destroy
+    Permission.find_by_controller_and_action('wiki', 'destroy_attachment').destroy
+  end
+end
index 48079ad..a10062f 100644 (file)
@@ -475,7 +475,8 @@ position: relative;
 margin: 0 5px 5px;
 }
 
-div.attachments {padding-left: 6px; border-left: 2px solid #ccc;}
+div.attachments {padding-left: 6px; border-left: 2px solid #ccc; margin-bottom: 8px;}
+div.attachments p {margin-bottom:2px;}
 
 .overlay{ 
 position: absolute;