##// END OF EJS Templates
Attachments can now be added to wiki pages (original patch by Pavol Murin). Only authorized users can add/delete attachments....
Jean-Philippe Lang -
r538:f8ef65e8f641
parent child
Show More
@@ -0,0 +1,39
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class AttachmentsController < ApplicationController
19 before_filter :find_project, :check_project_privacy
20
21 # sends an attachment
22 def download
23 send_file @attachment.diskfile, :filename => @attachment.filename
24 end
25
26 # sends an image to be displayed inline
27 def show
28 render(:nothing => true, :status => 404) and return unless @attachment.diskfile =~ /\.(jpeg|jpg|gif|png)$/i
29 send_file @attachment.diskfile, :type => "image/#{$1}", :disposition => 'inline'
30 end
31
32 private
33 def find_project
34 @attachment = Attachment.find(params[:id])
35 @project = @attachment.project
36 rescue
37 render_404
38 end
39 end
@@ -0,0 +1,25
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module AttachmentsHelper
19 # displays the links to a collection of attachments
20 def link_to_attachments(attachments, options = {})
21 if attachments.any?
22 render :partial => 'attachments/links', :locals => {:attachments => attachments, :options => options}
23 end
24 end
25 end
@@ -0,0 +1,4
1 <p id="attachments_p"><label for="attachment_file"><%=l(:label_attachment)%>
2 <%= image_to_function "add.png", "addFileField();return false" %></label>
3
4 <%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
@@ -0,0 +1,13
1 <div class="attachments">
2 <% for attachment in attachments %>
3 <p><%= link_to attachment.filename, {:controller => 'attachments', :action => 'download', :id => attachment }, :class => 'icon icon-attachment' %>
4 (<%= number_to_human_size attachment.filesize %>)
5 <% unless options[:no_author] %>
6 <em><%= attachment.author.display_name %>, <%= format_date(attachment.created_on) %></em>
7 <% end %>
8 <% if options[:delete_url] %>
9 <%= link_to image_tag('delete.png'), options[:delete_url].update({:attachment_id => attachment}), :confirm => l(:text_are_you_sure), :method => :post %>
10 <% end %>
11 </p>
12 <% end %>
13 </div>
@@ -0,0 +1,11
1 class AddWikiAttachmentsPermissions < ActiveRecord::Migration
2 def self.up
3 Permission.create :controller => 'wiki', :action => 'add_attachment', :description => 'label_attachment_new', :sort => 1750, :is_public => false, :mail_option => 0, :mail_enabled => 0
4 Permission.create :controller => 'wiki', :action => 'destroy_attachment', :description => 'label_attachment_delete', :sort => 1755, :is_public => false, :mail_option => 0, :mail_enabled => 0
5 end
6
7 def self.down
8 Permission.find_by_controller_and_action('wiki', 'add_attachment').destroy
9 Permission.find_by_controller_and_action('wiki', 'destroy_attachment').destroy
10 end
11 end
@@ -29,6 +29,8 class IssuesController < ApplicationController
29 include IssueRelationsHelper
29 include IssueRelationsHelper
30 helper :watchers
30 helper :watchers
31 include WatchersHelper
31 include WatchersHelper
32 helper :attachments
33 include AttachmentsHelper
32
34
33 def show
35 def show
34 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
36 @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 redirect_to :action => 'show', :id => @issue
148 redirect_to :action => 'show', :id => @issue
147 end
149 end
148
150
149 # Send the file in stream mode
150 def download
151 @attachment = @issue.attachments.find(params[:attachment_id])
152 send_file @attachment.diskfile, :filename => @attachment.filename
153 rescue
154 render_404
155 end
156
157 private
151 private
158 def find_project
152 def find_project
159 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
153 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
@@ -22,6 +22,9 class MessagesController < ApplicationController
22
22
23 verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
23 verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
24
24
25 helper :attachments
26 include AttachmentsHelper
27
25 def show
28 def show
26 @reply = Message.new(:subject => "RE: #{@message.subject}")
29 @reply = Message.new(:subject => "RE: #{@message.subject}")
27 render :action => "show", :layout => false if request.xhr?
30 render :action => "show", :layout => false if request.xhr?
@@ -48,13 +51,6 class MessagesController < ApplicationController
48 redirect_to :action => 'show', :id => @message
51 redirect_to :action => 'show', :id => @message
49 end
52 end
50
53
51 def download
52 @attachment = @message.attachments.find(params[:attachment_id])
53 send_file @attachment.diskfile, :filename => @attachment.filename
54 rescue
55 render_404
56 end
57
58 private
54 private
59 def find_project
55 def find_project
60 @board = Board.find(params[:board_id], :include => :project)
56 @board = Board.find(params[:board_id], :include => :project)
@@ -17,10 +17,13
17
17
18 class WikiController < ApplicationController
18 class WikiController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :find_wiki, :check_project_privacy, :except => [:preview]
20 before_filter :find_wiki, :check_project_privacy
21 before_filter :authorize, :only => :destroy
21 before_filter :authorize, :only => [:destroy, :add_attachment, :destroy_attachment]
22
22
23 verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
23 verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index }
24
25 helper :attachments
26 include AttachmentsHelper
24
27
25 # display a page (in editing mode if it doesn't exist)
28 # display a page (in editing mode if it doesn't exist)
26 def index
29 def index
@@ -107,10 +110,28 class WikiController < ApplicationController
107 end
110 end
108
111
109 def preview
112 def preview
113 page = @wiki.find_page(params[:page])
114 @attachements = page.attachments if page
110 @text = params[:content][:text]
115 @text = params[:content][:text]
111 render :partial => 'preview'
116 render :partial => 'preview'
112 end
117 end
113
118
119 def add_attachment
120 @page = @wiki.find_page(params[:page])
121 # Save the attachments
122 params[:attachments].each { |file|
123 next unless file.size > 0
124 a = Attachment.create(:container => @page, :file => file, :author => logged_in_user)
125 } if params[:attachments] and params[:attachments].is_a? Array
126 redirect_to :action => 'index', :page => @page.title
127 end
128
129 def destroy_attachment
130 @page = @wiki.find_page(params[:page])
131 @page.attachments.find(params[:attachment_id]).destroy
132 redirect_to :action => 'index', :page => @page.title
133 end
134
114 private
135 private
115
136
116 def find_wiki
137 def find_wiki
@@ -146,7 +146,24 module ApplicationHelper
146 # example:
146 # example:
147 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6)
147 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6)
148 text = text.gsub(/(?=\b)r(\d+)(?=\b)/) {|m| link_to "r#{$1}", :controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1} if @project
148 text = text.gsub(/(?=\b)r(\d+)(?=\b)/) {|m| link_to "r#{$1}", :controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1} if @project
149
149
150 # when using an image link, try to use an attachment, if possible
151 attachments = options[:attachments]
152 if attachments
153 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
154 align = $1
155 filename = $2
156 rf = Regexp.new(filename, Regexp::IGNORECASE)
157 # search for the picture in attachments
158 if found = attachments.detect { |att| att.filename =~ rf }
159 image_url = url_for :controller => 'attachments', :action => 'show', :id => found.id
160 "!#{align}#{image_url}!"
161 else
162 "!#{align}#{filename}!"
163 end
164 end
165 end
166
150 # finally textilize text
167 # finally textilize text
151 @do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize")
168 @do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize")
152 text = @do_textilize ? auto_link(RedCloth.new(text, [:hard_breaks]).to_html) : simple_format(auto_link(h(text)))
169 text = @do_textilize ? auto_link(RedCloth.new(text, [:hard_breaks]).to_html) : simple_format(auto_link(h(text)))
@@ -77,7 +77,11 class Attachment < ActiveRecord::Base
77 def self.most_downloaded
77 def self.most_downloaded
78 find(:all, :limit => 5, :order => "downloads DESC")
78 find(:all, :limit => 5, :order => "downloads DESC")
79 end
79 end
80
80
81 def project
82 container.is_a?(Project) ? container : container.project
83 end
84
81 private
85 private
82 def sanitize_filename(value)
86 def sanitize_filename(value)
83 # get only the filename, not the whole path
87 # get only the filename, not the whole path
@@ -34,4 +34,8 class Message < ActiveRecord::Base
34 board.increment! :topics_count
34 board.increment! :topics_count
35 end
35 end
36 end
36 end
37
38 def project
39 board.project
40 end
37 end
41 end
@@ -31,7 +31,8 class Wiki < ActiveRecord::Base
31
31
32 # find the page with the given title
32 # find the page with the given title
33 def find_page(title)
33 def find_page(title)
34 pages.find_by_title(Wiki.titleize(title || start_page))
34 title = start_page if title.blank?
35 pages.find_by_title(Wiki.titleize(title))
35 end
36 end
36
37
37 # turn a string into a valid page title
38 # turn a string into a valid page title
@@ -18,6 +18,7
18 class WikiPage < ActiveRecord::Base
18 class WikiPage < ActiveRecord::Base
19 belongs_to :wiki
19 belongs_to :wiki
20 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
20 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
21 has_many :attachments, :as => :container, :dependent => :destroy
21
22
22 validates_presence_of :title
23 validates_presence_of :title
23 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
24 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
@@ -41,4 +42,8 class WikiPage < ActiveRecord::Base
41 def self.pretty_title(str)
42 def self.pretty_title(str)
42 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
43 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
43 end
44 end
45
46 def project
47 wiki.project
48 end
44 end
49 end
@@ -52,7 +52,7 end %>
52 <% end %>
52 <% end %>
53
53
54 <b><%=l(:field_description)%> :</b><br /><br />
54 <b><%=l(:field_description)%> :</b><br /><br />
55 <%= textilizable @issue.description %>
55 <%= textilizable @issue.description, :attachments => @issue.attachments %>
56 <br />
56 <br />
57
57
58 <div class="contextual">
58 <div class="contextual">
@@ -92,24 +92,14 end %>
92
92
93 <div class="box">
93 <div class="box">
94 <h3><%=l(:label_attachment_plural)%></h3>
94 <h3><%=l(:label_attachment_plural)%></h3>
95 <table width="100%">
95 <%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
96 <% for attachment in @issue.attachments %>
96
97 <tr>
98 <td><%= link_to attachment.filename, { :action => 'download', :id => @issue, :attachment_id => attachment }, :class => 'icon icon-attachment' %> (<%= number_to_human_size(attachment.filesize) %>)</td>
99 <td><%= format_date(attachment.created_on) %></td>
100 <td><%= attachment.author.display_name %></td>
101 <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>
102 </tr>
103 <% end %>
104 </table>
105 <br />
106 <% if authorize_for('issues', 'add_attachment') %>
97 <% if authorize_for('issues', 'add_attachment') %>
107 <% form_tag({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true, :class => "tabular") do %>
98 <p><%= toggle_link l(:label_attachment_new), "add_attachment_form" %></p>
108 <p id="attachments_p"><label><%=l(:label_attachment_new)%>
99 <% form_tag({ :controller => 'issues', :action => 'add_attachment', :id => @issue }, :multipart => true, :class => "tabular", :id => "add_attachment_form", :style => "display:none;") do %>
109 <%= image_to_function "add.png", "addFileField();return false" %></label>
100 <%= render :partial => 'attachments/form' %>
110 <%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
101 <%= submit_tag l(:button_add) %>
111 <%= submit_tag l(:button_add) %>
102 <% end %>
112 <% end %>
113 <% end %>
103 <% end %>
114 </div>
104 </div>
115
105
@@ -10,8 +10,5
10 <!--[eoform:message]-->
10 <!--[eoform:message]-->
11
11
12 <span class="tabular">
12 <span class="tabular">
13 <p id="attachments_p"><label><%=l(:label_attachment)%>
13 <%= render :partial => 'attachments/form' %>
14 <%= image_to_function "add.png", "addFileField();return false" %></label>
15 <%= file_field_tag 'attachments[]', :size => 30 %> <em>(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</em></p>
16 </span>
17 </div>
14 </div>
@@ -2,15 +2,10
2
2
3 <p><em><%= @message.author.name %>, <%= format_time(@message.created_on) %></em></p>
3 <p><em><%= @message.author.name %>, <%= format_time(@message.created_on) %></em></p>
4 <div class="wiki">
4 <div class="wiki">
5 <%= textilizable(@message.content) %>
5 <%= textilizable(@message.content, :attachments => @message.attachments) %>
6 </div>
6 </div>
7 <div class="attachments">
7 <%= link_to_attachments @message.attachments, :no_author => true %>
8 <% @message.attachments.each do |attachment| %>
8
9 <%= link_to attachment.filename, { :action => 'download', :id => @message, :attachment_id => attachment }, :class => 'icon icon-attachment' %>
10 (<%= number_to_human_size(attachment.filesize) %>)<br />
11 <% end %>
12 </div>
13 <br />
14 <h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3>
9 <h3 class="icon22 icon22-comment"><%= l(:label_reply_plural) %></h3>
15 <% @message.children.each do |message| %>
10 <% @message.children.each do |message| %>
16 <a name="<%= "message-#{message.id}" %>"></a>
11 <a name="<%= "message-#{message.id}" %>"></a>
@@ -28,4 +23,4
28 <p><%= submit_tag l(:button_submit) %></p>
23 <p><%= submit_tag l(:button_submit) %></p>
29 <% end %>
24 <% end %>
30 </div>
25 </div>
31 <% end %> No newline at end of file
26 <% end %>
@@ -1,3 +1,3
1 <fieldset class="preview"><legend><%= l(:label_preview) %></legend>
1 <fieldset class="preview"><legend><%= l(:label_preview) %></legend>
2 <%= textilizable @text %>
2 <%= textilizable @text, :attachments => @attachements %>
3 </fieldset>
3 </fieldset>
@@ -21,12 +21,22
21
21
22 <div class="wiki">
22 <div class="wiki">
23 <% cache "wiki/show/#{@page.id}/#{@content.version}" do %>
23 <% cache "wiki/show/#{@page.id}/#{@content.version}" do %>
24 <%= textilizable @content.text %>
24 <%= textilizable @content.text, :attachments => @page.attachments %>
25 <% end %>
25 <% end %>
26 </div>
26 </div>
27
27
28 <%= link_to_attachments @page.attachments, :delete_url => (authorize_for('wiki', 'destroy_attachment') ? {:controller => 'wiki', :action => 'destroy_attachment', :page => @page.title} : nil) %>
29
28 <div class="contextual">
30 <div class="contextual">
29 <%= l(:label_export_to) %>
31 <%= l(:label_export_to) %>
30 <%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>,
32 <%= link_to 'HTML', {:export => 'html', :version => @content.version}, :class => 'icon icon-html' %>,
31 <%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %>
33 <%= link_to 'TXT', {:export => 'txt', :version => @content.version}, :class => 'icon icon-txt' %>
32 </div> No newline at end of file
34 </div>
35
36 <% if authorize_for('wiki', 'add_attachment') %>
37 <p><%= toggle_link l(:label_attachment_new), "add_attachment_form" %></p>
38 <% form_tag({ :controller => 'wiki', :action => 'add_attachment', :page => @page.title }, :multipart => true, :class => "tabular", :id => "add_attachment_form", :style => "display:none;") do %>
39 <%= render :partial => 'attachments/form' %>
40 <%= submit_tag l(:button_add) %>
41 <% end %>
42 <% end %>
@@ -475,7 +475,8 position: relative;
475 margin: 0 5px 5px;
475 margin: 0 5px 5px;
476 }
476 }
477
477
478 div.attachments {padding-left: 6px; border-left: 2px solid #ccc;}
478 div.attachments {padding-left: 6px; border-left: 2px solid #ccc; margin-bottom: 8px;}
479 div.attachments p {margin-bottom:2px;}
479
480
480 .overlay{
481 .overlay{
481 position: absolute;
482 position: absolute;
General Comments 0
You need to be logged in to leave comments. Login now