From f8ef65e8f64111b5bfa45abd42e0e684afd61f07 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sat, 26 May 2007 15:42:37 +0000 Subject: [PATCH] Attachments can now be added to wiki pages (original patch by Pavol Murin). Only authorized users can add/delete attachments. 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 --- app/controllers/attachments_controller.rb | 39 +++++++++++++++++++ app/controllers/issues_controller.rb | 10 +---- app/controllers/messages_controller.rb | 10 ++--- app/controllers/wiki_controller.rb | 27 +++++++++++-- app/helpers/application_helper.rb | 19 ++++++++- app/helpers/attachments_helper.rb | 25 ++++++++++++ app/models/attachment.rb | 6 ++- app/models/message.rb | 4 ++ app/models/wiki.rb | 3 +- app/models/wiki_page.rb | 5 +++ app/views/attachments/_form.rhtml | 4 ++ app/views/attachments/_links.rhtml | 13 +++++++ app/views/issues/show.rhtml | 26 ++++--------- app/views/messages/_form.rhtml | 5 +-- app/views/messages/show.rhtml | 13 ++----- app/views/wiki/_preview.rhtml | 2 +- app/views/wiki/show.rhtml | 14 ++++++- .../050_add_wiki_attachments_permissions.rb | 11 ++++++ public/stylesheets/application.css | 3 +- 19 files changed, 183 insertions(+), 56 deletions(-) create mode 100644 app/controllers/attachments_controller.rb create mode 100644 app/helpers/attachments_helper.rb create mode 100644 app/views/attachments/_form.rhtml create mode 100644 app/views/attachments/_links.rhtml create mode 100644 db/migrate/050_add_wiki_attachments_permissions.rb diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb new file mode 100644 index 00000000..3528e722 --- /dev/null +++ b/app/controllers/attachments_controller.rb @@ -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 diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 0a8d19a3..f57dfc88 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -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]) diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 16a04097..1b0d2a68 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -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) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index f68b71ec..d8f23cfd 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -17,10 +17,13 @@ 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 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cd1b2ea0..12302a07 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -146,7 +146,24 @@ module ApplicationHelper # example: # r52 -> r52 (@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 index 00000000..989cd3e6 --- /dev/null +++ b/app/helpers/attachments_helper.rb @@ -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 diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 82fcdadf..2be4e163 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -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 diff --git a/app/models/message.rb b/app/models/message.rb index 1f8dde54..935141b7 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -34,4 +34,8 @@ class Message < ActiveRecord::Base board.increment! :topics_count end end + + def project + board.project + end end diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 8233c3d4..8d461a85 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -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 diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index b7964a11..56246519 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -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 index 00000000..18f08c6b --- /dev/null +++ b/app/views/attachments/_form.rhtml @@ -0,0 +1,4 @@ +

+ +<%= file_field_tag 'attachments[]', :size => 30 %> (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)

diff --git a/app/views/attachments/_links.rhtml b/app/views/attachments/_links.rhtml new file mode 100644 index 00000000..93d6b2a7 --- /dev/null +++ b/app/views/attachments/_links.rhtml @@ -0,0 +1,13 @@ +
+<% for attachment in attachments %> +

<%= link_to attachment.filename, {:controller => 'attachments', :action => 'download', :id => attachment }, :class => 'icon icon-attachment' %> + (<%= number_to_human_size attachment.filesize %>) + <% unless options[:no_author] %> + <%= attachment.author.display_name %>, <%= format_date(attachment.created_on) %> + <% 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 %> +

+<% end %> +
diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index 1895e7ce..2c4905e9 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -52,7 +52,7 @@ end %> <% end %> <%=l(:field_description)%> :

-<%= textilizable @issue.description %> +<%= textilizable @issue.description, :attachments => @issue.attachments %>
@@ -92,24 +92,14 @@ end %>

<%=l(:label_attachment_plural)%>

- -<% for attachment in @issue.attachments %> - - - - - - -<% end %> -
<%= link_to attachment.filename, { :action => 'download', :id => @issue, :attachment_id => attachment }, :class => 'icon icon-attachment' %> (<%= number_to_human_size(attachment.filesize) %>)<%= format_date(attachment.created_on) %><%= attachment.author.display_name %>
<%= 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' %>
-
+<%= 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 %> -

- <%= file_field_tag 'attachments[]', :size => 30 %> (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)

- <%= submit_tag l(:button_add) %> - <% end %> +

<%= toggle_link l(:label_attachment_new), "add_attachment_form" %>

+<% 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 %>
diff --git a/app/views/messages/_form.rhtml b/app/views/messages/_form.rhtml index 453bd8b2..93e4311b 100644 --- a/app/views/messages/_form.rhtml +++ b/app/views/messages/_form.rhtml @@ -10,8 +10,5 @@ -

-<%= file_field_tag 'attachments[]', :size => 30 %> (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)

-
+<%= render :partial => 'attachments/form' %>
diff --git a/app/views/messages/show.rhtml b/app/views/messages/show.rhtml index 8068da01..c18eb524 100644 --- a/app/views/messages/show.rhtml +++ b/app/views/messages/show.rhtml @@ -2,15 +2,10 @@

<%= @message.author.name %>, <%= format_time(@message.created_on) %>

-<%= textilizable(@message.content) %> +<%= textilizable(@message.content, :attachments => @message.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) %>)
-<% end %> -
-
+<%= link_to_attachments @message.attachments, :no_author => true %> +

<%= l(:label_reply_plural) %>

<% @message.children.each do |message| %> "> @@ -28,4 +23,4 @@

<%= submit_tag l(:button_submit) %>

<% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/wiki/_preview.rhtml b/app/views/wiki/_preview.rhtml index 7865d915..e3bfc3a2 100644 --- a/app/views/wiki/_preview.rhtml +++ b/app/views/wiki/_preview.rhtml @@ -1,3 +1,3 @@
<%= l(:label_preview) %> -<%= textilizable @text %> +<%= textilizable @text, :attachments => @attachements %>
diff --git a/app/views/wiki/show.rhtml b/app/views/wiki/show.rhtml index c2deba02..8bb757ac 100644 --- a/app/views/wiki/show.rhtml +++ b/app/views/wiki/show.rhtml @@ -21,12 +21,22 @@
<% cache "wiki/show/#{@page.id}/#{@content.version}" do %> -<%= textilizable @content.text %> +<%= textilizable @content.text, :attachments => @page.attachments %> <% end %>
+<%= link_to_attachments @page.attachments, :delete_url => (authorize_for('wiki', 'destroy_attachment') ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %> +
<%= 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' %> -
\ No newline at end of file + + +<% if authorize_for('wiki', 'add_attachment') %> +

<%= toggle_link l(:label_attachment_new), "add_attachment_form" %>

+<% 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 index 00000000..6c81273a --- /dev/null +++ b/db/migrate/050_add_wiki_attachments_permissions.rb @@ -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 diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 48079adf..a10062f5 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -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;