@@ -61,7 +61,7 class AttachmentsController < ApplicationController | |||||
61 | end |
|
61 | end | |
62 |
|
62 | |||
63 | def thumbnail |
|
63 | def thumbnail | |
64 |
if @attachment.thumbnailable? |
|
64 | if @attachment.thumbnailable? && thumbnail = @attachment.thumbnail(:size => params[:size]) | |
65 | if stale?(:etag => thumbnail) |
|
65 | if stale?(:etag => thumbnail) | |
66 | send_file thumbnail, |
|
66 | send_file thumbnail, | |
67 | :filename => filename_for_content_disposition(@attachment.filename), |
|
67 | :filename => filename_for_content_disposition(@attachment.filename), |
@@ -171,9 +171,17 class Attachment < ActiveRecord::Base | |||||
171 |
|
171 | |||
172 | # Returns the full path the attachment thumbnail, or nil |
|
172 | # Returns the full path the attachment thumbnail, or nil | |
173 | # if the thumbnail cannot be generated. |
|
173 | # if the thumbnail cannot be generated. | |
174 | def thumbnail |
|
174 | def thumbnail(options={}) | |
175 | if thumbnailable? && readable? |
|
175 | if thumbnailable? && readable? | |
176 |
size = |
|
176 | size = options[:size].to_i | |
|
177 | if size > 0 | |||
|
178 | # Limit the number of thumbnails per image | |||
|
179 | size = (size / 50) * 50 | |||
|
180 | # Maximum thumbnail size | |||
|
181 | size = 800 if size > 800 | |||
|
182 | else | |||
|
183 | size = Setting.thumbnails_size.to_i | |||
|
184 | end | |||
177 | size = 100 unless size > 0 |
|
185 | size = 100 unless size > 0 | |
178 | target = File.join(self.class.thumbnails_storage_path, "#{id}_#{digest}_#{size}.thumb") |
|
186 | target = File.join(self.class.thumbnails_storage_path, "#{id}_#{digest}_#{size}.thumb") | |
179 |
|
187 |
@@ -264,7 +264,7 RedmineApp::Application.routes.draw do | |||||
264 | match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get |
|
264 | match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get | |
265 | match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get |
|
265 | match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get | |
266 | match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get |
|
266 | match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get | |
267 | match 'attachments/thumbnail/:id', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get |
|
267 | match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/ | |
268 | resources :attachments, :only => [:show, :destroy] |
|
268 | resources :attachments, :only => [:show, :destroy] | |
269 |
|
269 | |||
270 | resources :groups do |
|
270 | resources :groups do |
@@ -116,6 +116,24 module Redmine | |||||
116 | @included_wiki_pages.pop |
|
116 | @included_wiki_pages.pop | |
117 | out |
|
117 | out | |
118 | end |
|
118 | end | |
|
119 | ||||
|
120 | desc "Displays a clickable thumbnail of an attached image. Examples:\n\n<pre>{{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}</pre>" | |||
|
121 | macro :thumbnail do |obj, args| | |||
|
122 | args, options = extract_macro_options(args, :size, :title) | |||
|
123 | filename = args.first | |||
|
124 | raise 'Filename required' unless filename.present? | |||
|
125 | size = options[:size] | |||
|
126 | raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/) | |||
|
127 | size = size.to_i | |||
|
128 | size = nil unless size > 0 | |||
|
129 | if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename) | |||
|
130 | title = options[:title] || attachment.title | |||
|
131 | img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename) | |||
|
132 | link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title) | |||
|
133 | else | |||
|
134 | raise "Attachment #{filename} not found" | |||
|
135 | end | |||
|
136 | end | |||
119 | end |
|
137 | end | |
120 | end |
|
138 | end | |
121 | end |
|
139 | end |
@@ -262,43 +262,44 class AttachmentsControllerTest < ActionController::TestCase | |||||
262 | def test_thumbnail |
|
262 | def test_thumbnail | |
263 | Attachment.clear_thumbnails |
|
263 | Attachment.clear_thumbnails | |
264 | @request.session[:user_id] = 2 |
|
264 | @request.session[:user_id] = 2 | |
265 | with_settings :thumbnails_enabled => '1' do |
|
265 | ||
266 |
|
|
266 | get :thumbnail, :id => 16 | |
267 |
|
|
267 | assert_response :success | |
268 |
|
|
268 | assert_equal 'image/png', response.content_type | |
269 | end |
|
|||
270 | end |
|
269 | end | |
271 |
|
270 | |||
272 |
def test_thumbnail_should_ |
|
271 | def test_thumbnail_should_not_exceed_maximum_size | |
|
272 | Redmine::Thumbnail.expects(:generate).with {|source, target, size| size == 800} | |||
|
273 | ||||
273 | @request.session[:user_id] = 2 |
|
274 | @request.session[:user_id] = 2 | |
274 | with_settings :thumbnails_enabled => '1' do |
|
275 | get :thumbnail, :id => 16, :size => 2000 | |
275 | get :thumbnail, :id => 15 |
|
|||
276 | assert_response 404 |
|
|||
277 | end |
|
|||
278 | end |
|
276 | end | |
279 |
|
277 | |||
280 |
def test_thumbnail_should_r |
|
278 | def test_thumbnail_should_round_size | |
|
279 | Redmine::Thumbnail.expects(:generate).with {|source, target, size| size == 250} | |||
|
280 | ||||
281 | @request.session[:user_id] = 2 |
|
281 | @request.session[:user_id] = 2 | |
282 | with_settings :thumbnails_enabled => '0' do |
|
282 | get :thumbnail, :id => 16, :size => 260 | |
283 | get :thumbnail, :id => 16 |
|
283 | end | |
284 | assert_response 404 |
|
284 | ||
285 | end |
|
285 | def test_thumbnail_should_return_404_for_non_image_attachment | |
|
286 | @request.session[:user_id] = 2 | |||
|
287 | ||||
|
288 | get :thumbnail, :id => 15 | |||
|
289 | assert_response 404 | |||
286 | end |
|
290 | end | |
287 |
|
291 | |||
288 | def test_thumbnail_should_return_404_if_thumbnail_generation_failed |
|
292 | def test_thumbnail_should_return_404_if_thumbnail_generation_failed | |
289 | Attachment.any_instance.stubs(:thumbnail).returns(nil) |
|
293 | Attachment.any_instance.stubs(:thumbnail).returns(nil) | |
290 | @request.session[:user_id] = 2 |
|
294 | @request.session[:user_id] = 2 | |
291 | with_settings :thumbnails_enabled => '1' do |
|
295 | ||
292 |
|
|
296 | get :thumbnail, :id => 16 | |
293 |
|
|
297 | assert_response 404 | |
294 | end |
|
|||
295 | end |
|
298 | end | |
296 |
|
299 | |||
297 | def test_thumbnail_should_be_denied_without_permission |
|
300 | def test_thumbnail_should_be_denied_without_permission | |
298 |
|
|
301 | get :thumbnail, :id => 16 | |
299 | get :thumbnail, :id => 16 |
|
302 | assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fthumbnail%2F16' | |
300 | assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fthumbnail%2F16' |
|
|||
301 | end |
|
|||
302 | end |
|
303 | end | |
303 | else |
|
304 | else | |
304 | puts '(ImageMagick convert not available)' |
|
305 | puts '(ImageMagick convert not available)' |
@@ -50,6 +50,10 class RoutingAttachmentsTest < ActionController::IntegrationTest | |||||
50 | { :controller => 'attachments', :action => 'thumbnail', :id => '1' } |
|
50 | { :controller => 'attachments', :action => 'thumbnail', :id => '1' } | |
51 | ) |
|
51 | ) | |
52 | assert_routing( |
|
52 | assert_routing( | |
|
53 | { :method => 'get', :path => "/attachments/thumbnail/1/200" }, | |||
|
54 | { :controller => 'attachments', :action => 'thumbnail', :id => '1', :size => '200' } | |||
|
55 | ) | |||
|
56 | assert_routing( | |||
53 | { :method => 'delete', :path => "/attachments/1" }, |
|
57 | { :method => 'delete', :path => "/attachments/1" }, | |
54 | { :controller => 'attachments', :action => 'destroy', :id => '1' } |
|
58 | { :controller => 'attachments', :action => 'destroy', :id => '1' } | |
55 | ) |
|
59 | ) |
@@ -119,4 +119,24 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase | |||||
119 | def test_macro_child_pages_without_wiki_page_should_fail |
|
119 | def test_macro_child_pages_without_wiki_page_should_fail | |
120 | assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}") |
|
120 | assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}") | |
121 | end |
|
121 | end | |
|
122 | ||||
|
123 | def test_macro_thumbnail | |||
|
124 | assert_equal '<p><a href="/attachments/17" class="thumbnail" title="testfile.PNG"><img alt="testfile.PNG" src="/attachments/thumbnail/17" /></a></p>', | |||
|
125 | textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14)) | |||
|
126 | end | |||
|
127 | ||||
|
128 | def test_macro_thumbnail_with_size | |||
|
129 | assert_equal '<p><a href="/attachments/17" class="thumbnail" title="testfile.PNG"><img alt="testfile.PNG" src="/attachments/thumbnail/17/200" /></a></p>', | |||
|
130 | textilizable("{{thumbnail(testfile.png, size=200)}}", :object => Issue.find(14)) | |||
|
131 | end | |||
|
132 | ||||
|
133 | def test_macro_thumbnail_with_title | |||
|
134 | assert_equal '<p><a href="/attachments/17" class="thumbnail" title="Cool image"><img alt="testfile.PNG" src="/attachments/thumbnail/17" /></a></p>', | |||
|
135 | textilizable("{{thumbnail(testfile.png, title=Cool image)}}", :object => Issue.find(14)) | |||
|
136 | end | |||
|
137 | ||||
|
138 | def test_macro_thumbnail_with_invalid_filename_should_fail | |||
|
139 | assert_include 'test.png not found', | |||
|
140 | textilizable("{{thumbnail(test.png)}}", :object => Issue.find(14)) | |||
|
141 | end | |||
122 | end |
|
142 | end |
General Comments 0
You need to be logged in to leave comments.
Login now