Commit f8ef65e8 authored by Jean-Philippe Lang's avatar Jean-Philippe Lang

Attachments can now be added to wiki pages (original patch by Pavol Murin). Only…

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
parent 6446c312
# 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
......@@ -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])
......
......@@ -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)
......
......@@ -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
......
......@@ -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)))
......
# 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
......@@ -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
......
......@@ -34,4 +34,8 @@ class Message < ActiveRecord::Base
board.increment! :topics_count
end
end
def project
board.project
end
end
......@@ -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
......
......@@ -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
<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>
<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>
......@@ -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>
......
......@@ -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>
......@@ -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 %>
<fieldset class="preview"><legend><%= l(:label_preview) %></legend>
<%= textilizable @text %>
<%= textilizable @text, :attachments => @attachements %>
</fieldset>
......@@ -21,12 +21,22 @@
<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 %>
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
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment