##// END OF EJS Templates
Image attachments are now sent inline to be viewed directly in the browser....
Jean-Philippe Lang -
r636:38e0c237a448
parent child
Show More
@@ -1,44 +1,39
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class AttachmentsController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :check_project_privacy
21 21
22 # sends an attachment
23 22 def download
24 send_file @attachment.diskfile, :filename => @attachment.filename
25 rescue
26 render_404
27 end
28
29 # sends an image to be displayed inline
30 def show
31 render(:nothing => true, :status => 404) and return unless @attachment.diskfile =~ /\.(jpeg|jpg|gif|png)$/i
32 send_file @attachment.diskfile, :filename => @attachment.filename, :type => "image/#{$1}", :disposition => 'inline'
23 # images are sent inline
24 send_file @attachment.diskfile, :filename => @attachment.filename,
25 :type => @attachment.content_type,
26 :disposition => (@attachment.image? ? 'inline' : 'attachment')
33 27 rescue
28 # in case the disk file was deleted
34 29 render_404
35 30 end
36 31
37 32 private
38 33 def find_project
39 34 @attachment = Attachment.find(params[:id])
40 35 @project = @attachment.project
41 36 rescue
42 37 render_404
43 38 end
44 39 end
@@ -1,290 +1,290
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class RedCloth
19 19 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
20 20 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
21 21 def hard_break( text )
22 22 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
23 23 end
24 24 end
25 25
26 26 module ApplicationHelper
27 27
28 28 # Return current logged in user or nil
29 29 def loggedin?
30 30 @logged_in_user
31 31 end
32 32
33 33 # Return true if user is logged in and is admin, otherwise false
34 34 def admin_loggedin?
35 35 @logged_in_user and @logged_in_user.admin?
36 36 end
37 37
38 38 # Return true if user is authorized for controller/action, otherwise false
39 39 def authorize_for(controller, action)
40 40 # check if action is allowed on public projects
41 41 if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
42 42 return true
43 43 end
44 44 # check if user is authorized
45 45 if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project) ) )
46 46 return true
47 47 end
48 48 return false
49 49 end
50 50
51 51 # Display a link if user is authorized
52 52 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
53 53 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
54 54 end
55 55
56 56 # Display a link to user's account page
57 57 def link_to_user(user)
58 58 link_to user.name, :controller => 'account', :action => 'show', :id => user
59 59 end
60 60
61 61 def link_to_issue(issue)
62 62 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
63 63 end
64 64
65 65 def toggle_link(name, id, options={})
66 66 onclick = "Element.toggle('#{id}'); "
67 67 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
68 68 onclick << "return false;"
69 69 link_to(name, "#", :onclick => onclick)
70 70 end
71 71
72 72 def image_to_function(name, function, html_options = {})
73 73 html_options.symbolize_keys!
74 74 tag(:input, html_options.merge({
75 75 :type => "image", :src => image_path(name),
76 76 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
77 77 }))
78 78 end
79 79
80 80 def format_date(date)
81 81 return nil unless date
82 82 @date_format_setting ||= Setting.date_format.to_i
83 83 @date_format_setting == 0 ? l_date(date) : date.strftime("%Y-%m-%d")
84 84 end
85 85
86 86 def format_time(time)
87 87 return nil unless time
88 88 @date_format_setting ||= Setting.date_format.to_i
89 89 time = time.to_time if time.is_a?(String)
90 90 @date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time))
91 91 end
92 92
93 93 def day_name(day)
94 94 l(:general_day_names).split(',')[day-1]
95 95 end
96 96
97 97 def month_name(month)
98 98 l(:actionview_datehelper_select_month_names).split(',')[month-1]
99 99 end
100 100
101 101 def pagination_links_full(paginator, options={}, html_options={})
102 102 page_param = options.delete(:page_param) || :page
103 103
104 104 html = ''
105 105 html << link_to_remote(('&#171; ' + l(:label_previous)),
106 106 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
107 107 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
108 108
109 109 html << (pagination_links_each(paginator, options) do |n|
110 110 link_to_remote(n.to_s,
111 111 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
112 112 {:href => url_for(:params => options.merge(page_param => n))})
113 113 end || '')
114 114
115 115 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
116 116 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
117 117 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
118 118 html
119 119 end
120 120
121 121 # textilize text according to system settings and RedCloth availability
122 122 def textilizable(text, options = {})
123 123 return "" if text.blank?
124 124
125 125 # different methods for formatting wiki links
126 126 case options[:wiki_links]
127 127 when :local
128 128 # used for local links to html files
129 129 format_wiki_link = Proc.new {|title| "#{title}.html" }
130 130 when :anchor
131 131 # used for single-file wiki export
132 132 format_wiki_link = Proc.new {|title| "##{title}" }
133 133 else
134 134 if @project
135 135 format_wiki_link = Proc.new {|title| url_for :controller => 'wiki', :action => 'index', :id => @project, :page => title }
136 136 else
137 137 format_wiki_link = Proc.new {|title| title }
138 138 end
139 139 end
140 140
141 141 # turn wiki links into textile links:
142 142 # example:
143 143 # [[link]] -> "link":link
144 144 # [[link|title]] -> "title":link
145 145 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) {|m| link_to(($3 || $1), format_wiki_link.call(Wiki.titleize($1)), :class => 'wiki-page') }
146 146
147 147 # turn issue ids into links
148 148 # example:
149 149 # #52 -> <a href="/issues/show/52">#52</a>
150 150 text = text.gsub(/#(\d+)(?=\b)/) {|m| link_to "##{$1}", {:controller => 'issues', :action => 'show', :id => $1}, :class => 'issue' }
151 151
152 152 # turn revision ids into links (@project needed)
153 153 # example:
154 154 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (@project.id is 6)
155 155 text = text.gsub(/(?=\b)r(\d+)(?=\b)/) {|m| link_to "r#{$1}", {:controller => 'repositories', :action => 'revision', :id => @project.id, :rev => $1}, :class => 'changeset' } if @project
156 156
157 157 # when using an image link, try to use an attachment, if possible
158 158 attachments = options[:attachments]
159 159 if attachments
160 160 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
161 161 align = $1
162 162 filename = $2
163 163 rf = Regexp.new(filename, Regexp::IGNORECASE)
164 164 # search for the picture in attachments
165 165 if found = attachments.detect { |att| att.filename =~ rf }
166 image_url = url_for :controller => 'attachments', :action => 'show', :id => found.id
166 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
167 167 "!#{align}#{image_url}!"
168 168 else
169 169 "!#{align}#{filename}!"
170 170 end
171 171 end
172 172 end
173 173
174 174 # finally textilize text
175 175 @do_textilize ||= (Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize")
176 176 text = @do_textilize ? auto_link(RedCloth.new(text, [:hard_breaks]).to_html) : simple_format(auto_link(h(text)))
177 177 end
178 178
179 179 # Same as Rails' simple_format helper without using paragraphs
180 180 def simple_format_without_paragraph(text)
181 181 text.to_s.
182 182 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
183 183 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
184 184 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
185 185 end
186 186
187 187 def error_messages_for(object_name, options = {})
188 188 options = options.symbolize_keys
189 189 object = instance_variable_get("@#{object_name}")
190 190 if object && !object.errors.empty?
191 191 # build full_messages here with controller current language
192 192 full_messages = []
193 193 object.errors.each do |attr, msg|
194 194 next if msg.nil?
195 195 msg = msg.first if msg.is_a? Array
196 196 if attr == "base"
197 197 full_messages << l(msg)
198 198 else
199 199 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
200 200 end
201 201 end
202 202 # retrieve custom values error messages
203 203 if object.errors[:custom_values]
204 204 object.custom_values.each do |v|
205 205 v.errors.each do |attr, msg|
206 206 next if msg.nil?
207 207 msg = msg.first if msg.is_a? Array
208 208 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
209 209 end
210 210 end
211 211 end
212 212 content_tag("div",
213 213 content_tag(
214 214 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
215 215 ) +
216 216 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
217 217 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
218 218 )
219 219 else
220 220 ""
221 221 end
222 222 end
223 223
224 224 def lang_options_for_select(blank=true)
225 225 (blank ? [["(auto)", ""]] : []) +
226 226 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
227 227 end
228 228
229 229 def label_tag_for(name, option_tags = nil, options = {})
230 230 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
231 231 content_tag("label", label_text)
232 232 end
233 233
234 234 def labelled_tabular_form_for(name, object, options, &proc)
235 235 options[:html] ||= {}
236 236 options[:html].store :class, "tabular"
237 237 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
238 238 end
239 239
240 240 def check_all_links(form_name)
241 241 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
242 242 " | " +
243 243 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
244 244 end
245 245
246 246 def calendar_for(field_id)
247 247 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
248 248 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
249 249 end
250 250
251 251 def wikitoolbar_for(field_id)
252 252 return '' unless Setting.text_formatting == 'textile'
253 253 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
254 254 end
255 255 end
256 256
257 257 class TabularFormBuilder < ActionView::Helpers::FormBuilder
258 258 include GLoc
259 259
260 260 def initialize(object_name, object, template, options, proc)
261 261 set_language_if_valid options.delete(:lang)
262 262 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
263 263 end
264 264
265 265 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
266 266 src = <<-END_SRC
267 267 def #{selector}(field, options = {})
268 268 return super if options.delete :no_label
269 269 label_text = l(options[:label]) if options[:label]
270 270 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
271 271 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
272 272 label = @template.content_tag("label", label_text,
273 273 :class => (@object && @object.errors[field] ? "error" : nil),
274 274 :for => (@object_name.to_s + "_" + field.to_s))
275 275 label + super
276 276 end
277 277 END_SRC
278 278 class_eval src, __FILE__, __LINE__
279 279 end
280 280
281 281 def select(field, choices, options = {}, html_options = {})
282 282 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
283 283 label = @template.content_tag("label", label_text,
284 284 :class => (@object && @object.errors[field] ? "error" : nil),
285 285 :for => (@object_name.to_s + "_" + field.to_s))
286 286 label + super
287 287 end
288 288
289 289 end
290 290
@@ -1,98 +1,102
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/md5"
19 19
20 20 class Attachment < ActiveRecord::Base
21 21 belongs_to :container, :polymorphic => true
22 22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23 23
24 24 validates_presence_of :container, :filename
25 25 validates_length_of :filename, :maximum => 255
26 26 validates_length_of :disk_filename, :maximum => 255
27 27
28 28 cattr_accessor :storage_path
29 29 @@storage_path = "#{RAILS_ROOT}/files"
30 30
31 31 def validate
32 32 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
33 33 end
34 34
35 35 def file=(incomming_file)
36 36 unless incomming_file.nil?
37 37 @temp_file = incomming_file
38 38 if @temp_file.size > 0
39 39 self.filename = sanitize_filename(@temp_file.original_filename)
40 40 self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
41 41 self.content_type = @temp_file.content_type.chomp
42 42 self.filesize = @temp_file.size
43 43 end
44 44 end
45 45 end
46 46
47 47 def file
48 48 nil
49 49 end
50 50
51 51 # Copy temp file to its final location
52 52 def before_save
53 53 if @temp_file && (@temp_file.size > 0)
54 54 logger.debug("saving '#{self.diskfile}'")
55 55 File.open(diskfile, "wb") do |f|
56 56 f.write(@temp_file.read)
57 57 end
58 58 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
59 59 end
60 60 end
61 61
62 62 # Deletes file on the disk
63 63 def after_destroy
64 64 if self.filename?
65 65 File.delete(diskfile) if File.exist?(diskfile)
66 66 end
67 67 end
68 68
69 69 # Returns file's location on disk
70 70 def diskfile
71 71 "#{@@storage_path}/#{self.disk_filename}"
72 72 end
73 73
74 74 def increment_download
75 75 increment!(:downloads)
76 76 end
77 77
78 78 # returns last created projects
79 79 def self.most_downloaded
80 80 find(:all, :limit => 5, :order => "downloads DESC")
81 81 end
82 82
83 83 def project
84 84 container.is_a?(Project) ? container : container.project
85 85 end
86 86
87 def image?
88 self.filename =~ /\.(jpeg|jpg|gif|png)$/i
89 end
90
87 91 private
88 92 def sanitize_filename(value)
89 93 # get only the filename, not the whole path
90 94 just_filename = value.gsub(/^.*(\\|\/)/, '')
91 95 # NOTE: File.basename doesn't work right with Windows paths on Unix
92 96 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
93 97
94 98 # Finally, replace all non alphanumeric, underscore or periods with underscore
95 99 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
96 100 end
97 101
98 102 end
General Comments 0
You need to be logged in to leave comments. Login now