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 ...@@ -29,6 +29,8 @@ class IssuesController < ApplicationController
include IssueRelationsHelper include IssueRelationsHelper
helper :watchers helper :watchers
include WatchersHelper include WatchersHelper
helper :attachments
include AttachmentsHelper
def show def show
@status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user @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 ...@@ -146,14 +148,6 @@ class IssuesController < ApplicationController
redirect_to :action => 'show', :id => @issue redirect_to :action => 'show', :id => @issue
end 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 private
def find_project def find_project
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
......
...@@ -22,6 +22,9 @@ class MessagesController < ApplicationController ...@@ -22,6 +22,9 @@ class MessagesController < ApplicationController
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show } verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
helper :attachments
include AttachmentsHelper
def show def show
@reply = Message.new(:subject => "RE: #{@message.subject}") @reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr? render :action => "show", :layout => false if request.xhr?
...@@ -48,13 +51,6 @@ class MessagesController < ApplicationController ...@@ -48,13 +51,6 @@ class MessagesController < ApplicationController
redirect_to :action => 'show', :id => @message redirect_to :action => 'show', :id => @message
end end
def download
@attachment = @message.attachments.find(params[:attachment_id])
send_file @attachment.diskfile, :filename => @attachment.filename
rescue
render_404
end
private private
def find_project def find_project
@board = Board.find(params[:board_id], :include => :project) @board = Board.find(params[:board_id], :include => :project)
......
...@@ -17,10 +17,13 @@ ...@@ -17,10 +17,13 @@
class WikiController < ApplicationController class WikiController < ApplicationController
layout 'base' layout 'base'
before_filter :find_wiki, :check_project_privacy, :except => [:preview] before_filter :find_wiki, :check_project_privacy
before_filter :authorize, :only => :destroy 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) # display a page (in editing mode if it doesn't exist)
def index def index
...@@ -107,10 +110,28 @@ class WikiController < ApplicationController ...@@ -107,10 +110,28 @@ class WikiController < ApplicationController
end end
def preview def preview
page = @wiki.find_page(params[:page])
@attachements = page.attachments if page
@text = params[:content][:text] @text = params[:content][:text]
render :partial => 'preview' render :partial => 'preview'
end 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 private
def find_wiki def find_wiki
......
...@@ -146,7 +146,24 @@ module ApplicationHelper ...@@ -146,7 +146,24 @@ module ApplicationHelper
# example: # example:
# r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6) # 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 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 # finally textilize text
@do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize") @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))) 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 ...@@ -77,7 +77,11 @@ class Attachment < ActiveRecord::Base
def self.most_downloaded def self.most_downloaded
find(:all, :limit => 5, :order => "downloads DESC") find(:all, :limit => 5, :order => "downloads DESC")
end end
def project
container.is_a?(Project) ? container : container.project
end
private private
def sanitize_filename(value) def sanitize_filename(value)
# get only the filename, not the whole path # get only the filename, not the whole path
......
...@@ -34,4 +34,8 @@ class Message < ActiveRecord::Base ...@@ -34,4 +34,8 @@ class Message < ActiveRecord::Base
board.increment! :topics_count board.increment! :topics_count
end end
end end
def project
board.project
end
end end
...@@ -31,7 +31,8 @@ class Wiki < ActiveRecord::Base ...@@ -31,7 +31,8 @@ class Wiki < ActiveRecord::Base
# find the page with the given title # find the page with the given title
def find_page(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 end
# turn a string into a valid page title # turn a string into a valid page title
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
class WikiPage < ActiveRecord::Base class WikiPage < ActiveRecord::Base
belongs_to :wiki belongs_to :wiki
has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
has_many :attachments, :as => :container, :dependent => :destroy
validates_presence_of :title validates_presence_of :title
validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/ validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
...@@ -41,4 +42,8 @@ class WikiPage < ActiveRecord::Base ...@@ -41,4 +42,8 @@ class WikiPage < ActiveRecord::Base
def self.pretty_title(str) def self.pretty_title(str)
(str && str.is_a?(String)) ? str.tr('_', ' ') : str (str && str.is_a?(String)) ? str.tr('_', ' ') : str
end end
def project
wiki.project
end
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 %> ...@@ -52,7 +52,7 @@ end %>
<% end %> <% end %>
<b><%=l(:field_description)%> :</b><br /><br /> <b><%=l(:field_description)%> :</b><br /><br />
<%= textilizable @issue.description %> <%= textilizable @issue.description, :attachments => @issue.attachments %>
<br /> <br />
<div class="contextual"> <div class="contextual">
...@@ -92,24 +92,14 @@ end %> ...@@ -92,24 +92,14 @@ end %>
<div class="box"> <div class="box">
<h3><%=l(:label_attachment_plural)%></h3> <h3><%=l(:label_attachment_plural)%></h3>
<table width="100%"> <%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
<% 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 />
<% if authorize_for('issues', 'add_attachment') %> <% if authorize_for('issues', 'add_attachment') %>
<% form_tag({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true, :class => "tabular") do %> <p><%= toggle_link l(:label_attachment_new), "add_attachment_form" %></p>
<p id="attachments_p"><label><%=l(:label_attachment_new)%> <% form_tag({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true, :class => "tabular", :id => "add_attachment_form", :style => "display:none;") do %>
<%= image_to_function "add.png", "addFileField();return false" %></label> <%= render :partial => 'attachments/form' %>
<%= 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) %>
<%= submit_tag l(:button_add) %> <% end %>
<% end %>
<% end %> <% end %>
</div> </div>
......
...@@ -10,8 +10,5 @@ ...@@ -10,8 +10,5 @@
<!--[eoform:message]--> <!--[eoform:message]-->
<span class="tabular"> <span class="tabular">
<p id="attachments_p"><label><%=l(:label_attachment)%> <%= render :partial => 'attachments/form' %>
<%= 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>
</div> </div>
...@@ -2,15 +2,10 @@ ...@@ -2,15 +2,10 @@
<p><em><%= @message.author.name %>, <%= format_time(@message.created_on) %></em></p> <p><em><%= @message.author.name %>, <%= format_time(@message.created_on) %></em></p>
<div class="wiki"> <div class="wiki">
<%= textilizable(@message.content) %> <%= textilizable(@message.content, :attachments => @message.attachments) %>
</div> </div>
<div class="attachments"> <%= link_to_attachments @message.attachments, :no_author => true %>
<% @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 />
<h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3> <h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3>
<% @message.children.each do |message| %> <% @message.children.each do |message| %>
<a name="<%= "message-#{message.id}" %>"></a> <a name="<%= "message-#{message.id}" %>"></a>
...@@ -28,4 +23,4 @@ ...@@ -28,4 +23,4 @@
<p><%= submit_tag l(:button_submit) %></p> <p><%= submit_tag l(:button_submit) %></p>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
\ No newline at end of file
<fieldset class="preview"><legend><%= l(:label_preview) %></legend> <fieldset class="preview"><legend><%= l(:label_preview) %></legend>
<%= textilizable @text %> <%= textilizable @text, :attachments => @attachements %>
</fieldset> </fieldset>
...@@ -21,12 +21,22 @@ ...@@ -21,12 +21,22 @@
<div class="wiki"> <div class="wiki">
<% cache "wiki/show/#{@page.id}/#{@content.version}" do %> <% cache "wiki/show/#{@page.id}/#{@content.version}" do %>
<%= textilizable @content.text %> <%= textilizable @content.text, :attachments => @page.attachments %>
<% end %> <% end %>
</div> </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"> <div class="contextual">
<%= l(:label_export_to) %> <%= l(:label_export_to) %>
<%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>, <%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>,
<%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %> <%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %>
</div> </div>
\ No newline at end of file
<% 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; ...@@ -475,7 +475,8 @@ position: relative;
margin: 0 5px 5px; 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{ .overlay{
position: absolute; 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