@@ -1,190 +1,191 | |||
|
1 | 1 | # Redmine - project management software |
|
2 | 2 | # Copyright (C) 2006-2014 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 | before_filter :find_attachment, :only => [:show, :download, :thumbnail, :destroy] |
|
20 | 20 | before_filter :find_editable_attachments, :only => [:edit, :update] |
|
21 | 21 | before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail] |
|
22 | 22 | before_filter :delete_authorize, :only => :destroy |
|
23 | 23 | before_filter :authorize_global, :only => :upload |
|
24 | 24 | |
|
25 | 25 | accept_api_auth :show, :download, :upload |
|
26 | 26 | |
|
27 | 27 | def show |
|
28 | 28 | respond_to do |format| |
|
29 | 29 | format.html { |
|
30 | 30 | if @attachment.is_diff? |
|
31 | 31 | @diff = File.new(@attachment.diskfile, "rb").read |
|
32 | 32 | @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' |
|
33 | 33 | @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) |
|
34 | 34 | # Save diff type as user preference |
|
35 | 35 | if User.current.logged? && @diff_type != User.current.pref[:diff_type] |
|
36 | 36 | User.current.pref[:diff_type] = @diff_type |
|
37 | 37 | User.current.preference.save |
|
38 | 38 | end |
|
39 | 39 | render :action => 'diff' |
|
40 | 40 | elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte |
|
41 | 41 | @content = File.new(@attachment.diskfile, "rb").read |
|
42 | 42 | render :action => 'file' |
|
43 | 43 | else |
|
44 | 44 | download |
|
45 | 45 | end |
|
46 | 46 | } |
|
47 | 47 | format.api |
|
48 | 48 | end |
|
49 | 49 | end |
|
50 | 50 | |
|
51 | 51 | def download |
|
52 | 52 | if @attachment.container.is_a?(Version) || @attachment.container.is_a?(Project) |
|
53 | 53 | @attachment.increment_download |
|
54 | 54 | end |
|
55 | 55 | |
|
56 | 56 | if stale?(:etag => @attachment.digest) |
|
57 | 57 | # images are sent inline |
|
58 | 58 | send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), |
|
59 | 59 | :type => detect_content_type(@attachment), |
|
60 | 60 | :disposition => (@attachment.image? ? 'inline' : 'attachment') |
|
61 | 61 | end |
|
62 | 62 | end |
|
63 | 63 | |
|
64 | 64 | def thumbnail |
|
65 | 65 | if @attachment.thumbnailable? && tbnail = @attachment.thumbnail(:size => params[:size]) |
|
66 | 66 | if stale?(:etag => tbnail) |
|
67 | 67 | send_file tbnail, |
|
68 | 68 | :filename => filename_for_content_disposition(@attachment.filename), |
|
69 | 69 | :type => detect_content_type(@attachment), |
|
70 | 70 | :disposition => 'inline' |
|
71 | 71 | end |
|
72 | 72 | else |
|
73 | 73 | # No thumbnail for the attachment or thumbnail could not be created |
|
74 | 74 | render :nothing => true, :status => 404 |
|
75 | 75 | end |
|
76 | 76 | end |
|
77 | 77 | |
|
78 | 78 | def upload |
|
79 | 79 | # Make sure that API users get used to set this content type |
|
80 | 80 | # as it won't trigger Rails' automatic parsing of the request body for parameters |
|
81 | 81 | unless request.content_type == 'application/octet-stream' |
|
82 | 82 | render :nothing => true, :status => 406 |
|
83 | 83 | return |
|
84 | 84 | end |
|
85 | 85 | |
|
86 | 86 | @attachment = Attachment.new(:file => request.raw_post) |
|
87 | 87 | @attachment.author = User.current |
|
88 | 88 | @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16) |
|
89 | @attachment.content_type = params[:content_type].presence | |
|
89 | 90 | saved = @attachment.save |
|
90 | 91 | |
|
91 | 92 | respond_to do |format| |
|
92 | 93 | format.js |
|
93 | 94 | format.api { |
|
94 | 95 | if saved |
|
95 | 96 | render :action => 'upload', :status => :created |
|
96 | 97 | else |
|
97 | 98 | render_validation_errors(@attachment) |
|
98 | 99 | end |
|
99 | 100 | } |
|
100 | 101 | end |
|
101 | 102 | end |
|
102 | 103 | |
|
103 | 104 | def edit |
|
104 | 105 | end |
|
105 | 106 | |
|
106 | 107 | def update |
|
107 | 108 | if params[:attachments].is_a?(Hash) |
|
108 | 109 | if Attachment.update_attachments(@attachments, params[:attachments]) |
|
109 | 110 | redirect_back_or_default home_path |
|
110 | 111 | return |
|
111 | 112 | end |
|
112 | 113 | end |
|
113 | 114 | render :action => 'edit' |
|
114 | 115 | end |
|
115 | 116 | |
|
116 | 117 | def destroy |
|
117 | 118 | if @attachment.container.respond_to?(:init_journal) |
|
118 | 119 | @attachment.container.init_journal(User.current) |
|
119 | 120 | end |
|
120 | 121 | if @attachment.container |
|
121 | 122 | # Make sure association callbacks are called |
|
122 | 123 | @attachment.container.attachments.delete(@attachment) |
|
123 | 124 | else |
|
124 | 125 | @attachment.destroy |
|
125 | 126 | end |
|
126 | 127 | |
|
127 | 128 | respond_to do |format| |
|
128 | 129 | format.html { redirect_to_referer_or project_path(@project) } |
|
129 | 130 | format.js |
|
130 | 131 | end |
|
131 | 132 | end |
|
132 | 133 | |
|
133 | 134 | private |
|
134 | 135 | |
|
135 | 136 | def find_attachment |
|
136 | 137 | @attachment = Attachment.find(params[:id]) |
|
137 | 138 | # Show 404 if the filename in the url is wrong |
|
138 | 139 | raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename |
|
139 | 140 | @project = @attachment.project |
|
140 | 141 | rescue ActiveRecord::RecordNotFound |
|
141 | 142 | render_404 |
|
142 | 143 | end |
|
143 | 144 | |
|
144 | 145 | def find_editable_attachments |
|
145 | 146 | klass = params[:object_type].to_s.singularize.classify.constantize rescue nil |
|
146 | 147 | unless klass && klass.reflect_on_association(:attachments) |
|
147 | 148 | render_404 |
|
148 | 149 | return |
|
149 | 150 | end |
|
150 | 151 | |
|
151 | 152 | @container = klass.find(params[:object_id]) |
|
152 | 153 | if @container.respond_to?(:visible?) && !@container.visible? |
|
153 | 154 | render_403 |
|
154 | 155 | return |
|
155 | 156 | end |
|
156 | 157 | @attachments = @container.attachments.select(&:editable?) |
|
157 | 158 | if @container.respond_to?(:project) |
|
158 | 159 | @project = @container.project |
|
159 | 160 | end |
|
160 | 161 | render_404 if @attachments.empty? |
|
161 | 162 | rescue ActiveRecord::RecordNotFound |
|
162 | 163 | render_404 |
|
163 | 164 | end |
|
164 | 165 | |
|
165 | 166 | # Checks that the file exists and is readable |
|
166 | 167 | def file_readable |
|
167 | 168 | if @attachment.readable? |
|
168 | 169 | true |
|
169 | 170 | else |
|
170 | 171 | logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable." |
|
171 | 172 | render_404 |
|
172 | 173 | end |
|
173 | 174 | end |
|
174 | 175 | |
|
175 | 176 | def read_authorize |
|
176 | 177 | @attachment.visible? ? true : deny_access |
|
177 | 178 | end |
|
178 | 179 | |
|
179 | 180 | def delete_authorize |
|
180 | 181 | @attachment.deletable? ? true : deny_access |
|
181 | 182 | end |
|
182 | 183 | |
|
183 | 184 | def detect_content_type(attachment) |
|
184 | 185 | content_type = attachment.content_type |
|
185 | 186 | if content_type.blank? |
|
186 | 187 | content_type = Redmine::MimeType.of(attachment.filename) |
|
187 | 188 | end |
|
188 | 189 | content_type.to_s |
|
189 | 190 | end |
|
190 | 191 | end |
@@ -1,190 +1,191 | |||
|
1 | 1 | /* Redmine - project management software |
|
2 | 2 | Copyright (C) 2006-2014 Jean-Philippe Lang */ |
|
3 | 3 | |
|
4 | 4 | function addFile(inputEl, file, eagerUpload) { |
|
5 | 5 | |
|
6 | 6 | if ($('#attachments_fields').children().length < 10) { |
|
7 | 7 | |
|
8 | 8 | var attachmentId = addFile.nextAttachmentId++; |
|
9 | 9 | |
|
10 | 10 | var fileSpan = $('<span>', { id: 'attachments_' + attachmentId }); |
|
11 | 11 | |
|
12 | 12 | fileSpan.append( |
|
13 | 13 | $('<input>', { type: 'text', 'class': 'filename readonly', name: 'attachments[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name), |
|
14 | 14 | $('<input>', { type: 'text', 'class': 'description', name: 'attachments[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload), |
|
15 | 15 | $('<a> </a>').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload) |
|
16 | 16 | ).appendTo('#attachments_fields'); |
|
17 | 17 | |
|
18 | 18 | if(eagerUpload) { |
|
19 | 19 | ajaxUpload(file, attachmentId, fileSpan, inputEl); |
|
20 | 20 | } |
|
21 | 21 | |
|
22 | 22 | return attachmentId; |
|
23 | 23 | } |
|
24 | 24 | return null; |
|
25 | 25 | } |
|
26 | 26 | |
|
27 | 27 | addFile.nextAttachmentId = 1; |
|
28 | 28 | |
|
29 | 29 | function ajaxUpload(file, attachmentId, fileSpan, inputEl) { |
|
30 | 30 | |
|
31 | 31 | function onLoadstart(e) { |
|
32 | 32 | fileSpan.removeClass('ajax-waiting'); |
|
33 | 33 | fileSpan.addClass('ajax-loading'); |
|
34 | 34 | $('input:submit', $(this).parents('form')).attr('disabled', 'disabled'); |
|
35 | 35 | } |
|
36 | 36 | |
|
37 | 37 | function onProgress(e) { |
|
38 | 38 | if(e.lengthComputable) { |
|
39 | 39 | this.progressbar( 'value', e.loaded * 100 / e.total ); |
|
40 | 40 | } |
|
41 | 41 | } |
|
42 | 42 | |
|
43 | 43 | function actualUpload(file, attachmentId, fileSpan, inputEl) { |
|
44 | 44 | |
|
45 | 45 | ajaxUpload.uploading++; |
|
46 | 46 | |
|
47 | 47 | uploadBlob(file, $(inputEl).data('upload-path'), attachmentId, { |
|
48 | 48 | loadstartEventHandler: onLoadstart.bind(progressSpan), |
|
49 | 49 | progressEventHandler: onProgress.bind(progressSpan) |
|
50 | 50 | }) |
|
51 | 51 | .done(function(result) { |
|
52 | 52 | progressSpan.progressbar( 'value', 100 ).remove(); |
|
53 | 53 | fileSpan.find('input.description, a').css('display', 'inline-block'); |
|
54 | 54 | }) |
|
55 | 55 | .fail(function(result) { |
|
56 | 56 | progressSpan.text(result.statusText); |
|
57 | 57 | }).always(function() { |
|
58 | 58 | ajaxUpload.uploading--; |
|
59 | 59 | fileSpan.removeClass('ajax-loading'); |
|
60 | 60 | var form = fileSpan.parents('form'); |
|
61 | 61 | if (form.queue('upload').length == 0 && ajaxUpload.uploading == 0) { |
|
62 | 62 | $('input:submit', form).removeAttr('disabled'); |
|
63 | 63 | } |
|
64 | 64 | form.dequeue('upload'); |
|
65 | 65 | }); |
|
66 | 66 | } |
|
67 | 67 | |
|
68 | 68 | var progressSpan = $('<div>').insertAfter(fileSpan.find('input.filename')); |
|
69 | 69 | progressSpan.progressbar(); |
|
70 | 70 | fileSpan.addClass('ajax-waiting'); |
|
71 | 71 | |
|
72 | 72 | var maxSyncUpload = $(inputEl).data('max-concurrent-uploads'); |
|
73 | 73 | |
|
74 | 74 | if(maxSyncUpload == null || maxSyncUpload <= 0 || ajaxUpload.uploading < maxSyncUpload) |
|
75 | 75 | actualUpload(file, attachmentId, fileSpan, inputEl); |
|
76 | 76 | else |
|
77 | 77 | $(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl)); |
|
78 | 78 | } |
|
79 | 79 | |
|
80 | 80 | ajaxUpload.uploading = 0; |
|
81 | 81 | |
|
82 | 82 | function removeFile() { |
|
83 | 83 | $(this).parent('span').remove(); |
|
84 | 84 | return false; |
|
85 | 85 | } |
|
86 | 86 | |
|
87 | 87 | function uploadBlob(blob, uploadUrl, attachmentId, options) { |
|
88 | 88 | |
|
89 | 89 | var actualOptions = $.extend({ |
|
90 | 90 | loadstartEventHandler: $.noop, |
|
91 | 91 | progressEventHandler: $.noop |
|
92 | 92 | }, options); |
|
93 | 93 | |
|
94 | 94 | uploadUrl = uploadUrl + '?attachment_id=' + attachmentId; |
|
95 | 95 | if (blob instanceof window.File) { |
|
96 | 96 | uploadUrl += '&filename=' + encodeURIComponent(blob.name); |
|
97 | uploadUrl += '&content_type=' + encodeURIComponent(blob.type); | |
|
97 | 98 | } |
|
98 | 99 | |
|
99 | 100 | return $.ajax(uploadUrl, { |
|
100 | 101 | type: 'POST', |
|
101 | 102 | contentType: 'application/octet-stream', |
|
102 | 103 | beforeSend: function(jqXhr, settings) { |
|
103 | 104 | jqXhr.setRequestHeader('Accept', 'application/js'); |
|
104 | 105 | // attach proper File object |
|
105 | 106 | settings.data = blob; |
|
106 | 107 | }, |
|
107 | 108 | xhr: function() { |
|
108 | 109 | var xhr = $.ajaxSettings.xhr(); |
|
109 | 110 | xhr.upload.onloadstart = actualOptions.loadstartEventHandler; |
|
110 | 111 | xhr.upload.onprogress = actualOptions.progressEventHandler; |
|
111 | 112 | return xhr; |
|
112 | 113 | }, |
|
113 | 114 | data: blob, |
|
114 | 115 | cache: false, |
|
115 | 116 | processData: false |
|
116 | 117 | }); |
|
117 | 118 | } |
|
118 | 119 | |
|
119 | 120 | function addInputFiles(inputEl) { |
|
120 | 121 | var clearedFileInput = $(inputEl).clone().val(''); |
|
121 | 122 | |
|
122 | 123 | if ($.ajaxSettings.xhr().upload && inputEl.files) { |
|
123 | 124 | // upload files using ajax |
|
124 | 125 | uploadAndAttachFiles(inputEl.files, inputEl); |
|
125 | 126 | $(inputEl).remove(); |
|
126 | 127 | } else { |
|
127 | 128 | // browser not supporting the file API, upload on form submission |
|
128 | 129 | var attachmentId; |
|
129 | 130 | var aFilename = inputEl.value.split(/\/|\\/); |
|
130 | 131 | attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false); |
|
131 | 132 | if (attachmentId) { |
|
132 | 133 | $(inputEl).attr({ name: 'attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId); |
|
133 | 134 | } |
|
134 | 135 | } |
|
135 | 136 | |
|
136 | 137 | clearedFileInput.insertAfter('#attachments_fields'); |
|
137 | 138 | } |
|
138 | 139 | |
|
139 | 140 | function uploadAndAttachFiles(files, inputEl) { |
|
140 | 141 | |
|
141 | 142 | var maxFileSize = $(inputEl).data('max-file-size'); |
|
142 | 143 | var maxFileSizeExceeded = $(inputEl).data('max-file-size-message'); |
|
143 | 144 | |
|
144 | 145 | var sizeExceeded = false; |
|
145 | 146 | $.each(files, function() { |
|
146 | 147 | if (this.size && maxFileSize != null && this.size > parseInt(maxFileSize)) {sizeExceeded=true;} |
|
147 | 148 | }); |
|
148 | 149 | if (sizeExceeded) { |
|
149 | 150 | window.alert(maxFileSizeExceeded); |
|
150 | 151 | } else { |
|
151 | 152 | $.each(files, function() {addFile(inputEl, this, true);}); |
|
152 | 153 | } |
|
153 | 154 | } |
|
154 | 155 | |
|
155 | 156 | function handleFileDropEvent(e) { |
|
156 | 157 | |
|
157 | 158 | $(this).removeClass('fileover'); |
|
158 | 159 | blockEventPropagation(e); |
|
159 | 160 | |
|
160 | 161 | if ($.inArray('Files', e.dataTransfer.types) > -1) { |
|
161 | 162 | uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector')); |
|
162 | 163 | } |
|
163 | 164 | } |
|
164 | 165 | |
|
165 | 166 | function dragOverHandler(e) { |
|
166 | 167 | $(this).addClass('fileover'); |
|
167 | 168 | blockEventPropagation(e); |
|
168 | 169 | } |
|
169 | 170 | |
|
170 | 171 | function dragOutHandler(e) { |
|
171 | 172 | $(this).removeClass('fileover'); |
|
172 | 173 | blockEventPropagation(e); |
|
173 | 174 | } |
|
174 | 175 | |
|
175 | 176 | function setupFileDrop() { |
|
176 | 177 | if (window.File && window.FileList && window.ProgressEvent && window.FormData) { |
|
177 | 178 | |
|
178 | 179 | $.event.fixHooks.drop = { props: [ 'dataTransfer' ] }; |
|
179 | 180 | |
|
180 | 181 | $('form div.box').has('input:file').each(function() { |
|
181 | 182 | $(this).on({ |
|
182 | 183 | dragover: dragOverHandler, |
|
183 | 184 | dragleave: dragOutHandler, |
|
184 | 185 | drop: handleFileDropEvent |
|
185 | 186 | }); |
|
186 | 187 | }); |
|
187 | 188 | } |
|
188 | 189 | } |
|
189 | 190 | |
|
190 | 191 | $(document).ready(setupFileDrop); |
@@ -1,142 +1,152 | |||
|
1 | 1 | # Redmine - project management software |
|
2 | 2 | # Copyright (C) 2006-2014 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 File.expand_path('../../test_helper', __FILE__) |
|
19 | 19 | |
|
20 | 20 | class AttachmentsTest < Redmine::IntegrationTest |
|
21 | 21 | fixtures :projects, :enabled_modules, |
|
22 | 22 | :users, :roles, :members, :member_roles, |
|
23 | 23 | :trackers, :projects_trackers, |
|
24 | 24 | :issue_statuses, :enumerations |
|
25 | 25 | |
|
26 | 26 | def test_upload_should_set_default_content_type |
|
27 | 27 | log_user('jsmith', 'jsmith') |
|
28 | 28 | assert_difference 'Attachment.count' do |
|
29 | 29 | post "/uploads.js?attachment_id=1&filename=foo.txt", "File content", {"CONTENT_TYPE" => 'application/octet-stream'} |
|
30 | 30 | assert_response :success |
|
31 | 31 | end |
|
32 | 32 | attachment = Attachment.order(:id => :desc).first |
|
33 | 33 | assert_equal 'text/plain', attachment.content_type |
|
34 | 34 | end |
|
35 | 35 | |
|
36 | def test_upload_should_accept_content_type_param | |
|
37 | log_user('jsmith', 'jsmith') | |
|
38 | assert_difference 'Attachment.count' do | |
|
39 | post "/uploads.js?attachment_id=1&filename=foo&content_type=image/jpeg", "File content", {"CONTENT_TYPE" => 'application/octet-stream'} | |
|
40 | assert_response :success | |
|
41 | end | |
|
42 | attachment = Attachment.order(:id => :desc).first | |
|
43 | assert_equal 'image/jpeg', attachment.content_type | |
|
44 | end | |
|
45 | ||
|
36 | 46 | def test_upload_as_js_and_attach_to_an_issue |
|
37 | 47 | log_user('jsmith', 'jsmith') |
|
38 | 48 | |
|
39 | 49 | token = ajax_upload('myupload.txt', 'File content') |
|
40 | 50 | |
|
41 | 51 | assert_difference 'Issue.count' do |
|
42 | 52 | post '/projects/ecookbook/issues', { |
|
43 | 53 | :issue => {:tracker_id => 1, :subject => 'Issue with upload'}, |
|
44 | 54 | :attachments => {'1' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} |
|
45 | 55 | } |
|
46 | 56 | assert_response 302 |
|
47 | 57 | end |
|
48 | 58 | |
|
49 | 59 | issue = Issue.order('id DESC').first |
|
50 | 60 | assert_equal 'Issue with upload', issue.subject |
|
51 | 61 | assert_equal 1, issue.attachments.count |
|
52 | 62 | |
|
53 | 63 | attachment = issue.attachments.first |
|
54 | 64 | assert_equal 'myupload.txt', attachment.filename |
|
55 | 65 | assert_equal 'My uploaded file', attachment.description |
|
56 | 66 | assert_equal 'File content'.length, attachment.filesize |
|
57 | 67 | end |
|
58 | 68 | |
|
59 | 69 | def test_upload_as_js_and_preview_as_inline_attachment |
|
60 | 70 | log_user('jsmith', 'jsmith') |
|
61 | 71 | |
|
62 | 72 | token = ajax_upload('myupload.jpg', 'JPEG content') |
|
63 | 73 | |
|
64 | 74 | post '/issues/preview/new/ecookbook', { |
|
65 | 75 | :issue => {:tracker_id => 1, :description => 'Inline upload: !myupload.jpg!'}, |
|
66 | 76 | :attachments => {'1' => {:filename => 'myupload.jpg', :description => 'My uploaded file', :token => token}} |
|
67 | 77 | } |
|
68 | 78 | assert_response :success |
|
69 | 79 | |
|
70 | 80 | attachment_path = response.body.match(%r{<img src="(/attachments/download/\d+/myupload.jpg)"})[1] |
|
71 | 81 | assert_not_nil token, "No attachment path found in response:\n#{response.body}" |
|
72 | 82 | |
|
73 | 83 | get attachment_path |
|
74 | 84 | assert_response :success |
|
75 | 85 | assert_equal 'JPEG content', response.body |
|
76 | 86 | end |
|
77 | 87 | |
|
78 | 88 | def test_upload_and_resubmit_after_validation_failure |
|
79 | 89 | log_user('jsmith', 'jsmith') |
|
80 | 90 | |
|
81 | 91 | token = ajax_upload('myupload.txt', 'File content') |
|
82 | 92 | |
|
83 | 93 | assert_no_difference 'Issue.count' do |
|
84 | 94 | post '/projects/ecookbook/issues', { |
|
85 | 95 | :issue => {:tracker_id => 1, :subject => ''}, |
|
86 | 96 | :attachments => {'1' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} |
|
87 | 97 | } |
|
88 | 98 | assert_response :success |
|
89 | 99 | end |
|
90 | 100 | assert_select 'input[type=hidden][name=?][value=?]', 'attachments[p0][token]', token |
|
91 | 101 | assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'myupload.txt' |
|
92 | 102 | assert_select 'input[name=?][value=?]', 'attachments[p0][description]', 'My uploaded file' |
|
93 | 103 | |
|
94 | 104 | assert_difference 'Issue.count' do |
|
95 | 105 | post '/projects/ecookbook/issues', { |
|
96 | 106 | :issue => {:tracker_id => 1, :subject => 'Issue with upload'}, |
|
97 | 107 | :attachments => {'p0' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} |
|
98 | 108 | } |
|
99 | 109 | assert_response 302 |
|
100 | 110 | end |
|
101 | 111 | |
|
102 | 112 | issue = Issue.order('id DESC').first |
|
103 | 113 | assert_equal 'Issue with upload', issue.subject |
|
104 | 114 | assert_equal 1, issue.attachments.count |
|
105 | 115 | |
|
106 | 116 | attachment = issue.attachments.first |
|
107 | 117 | assert_equal 'myupload.txt', attachment.filename |
|
108 | 118 | assert_equal 'My uploaded file', attachment.description |
|
109 | 119 | assert_equal 'File content'.length, attachment.filesize |
|
110 | 120 | end |
|
111 | 121 | |
|
112 | 122 | def test_upload_as_js_and_destroy |
|
113 | 123 | log_user('jsmith', 'jsmith') |
|
114 | 124 | |
|
115 | 125 | token = ajax_upload('myupload.txt', 'File content') |
|
116 | 126 | |
|
117 | 127 | attachment = Attachment.order('id DESC').first |
|
118 | 128 | attachment_path = "/attachments/#{attachment.id}.js?attachment_id=1" |
|
119 | 129 | assert_include "href: '#{attachment_path}'", response.body, "Path to attachment: #{attachment_path} not found in response:\n#{response.body}" |
|
120 | 130 | |
|
121 | 131 | assert_difference 'Attachment.count', -1 do |
|
122 | 132 | delete attachment_path |
|
123 | 133 | assert_response :success |
|
124 | 134 | end |
|
125 | 135 | |
|
126 | 136 | assert_include "$('#attachments_1').remove();", response.body |
|
127 | 137 | end |
|
128 | 138 | |
|
129 | 139 | private |
|
130 | 140 | |
|
131 | 141 | def ajax_upload(filename, content, attachment_id=1) |
|
132 | 142 | assert_difference 'Attachment.count' do |
|
133 | 143 | post "/uploads.js?attachment_id=#{attachment_id}&filename=#{filename}", content, {"CONTENT_TYPE" => 'application/octet-stream'} |
|
134 | 144 | assert_response :success |
|
135 | 145 | assert_equal 'text/javascript', response.content_type |
|
136 | 146 | end |
|
137 | 147 | |
|
138 | 148 | token = response.body.match(/\.val\('(\d+\.[0-9a-f]+)'\)/)[1] |
|
139 | 149 | assert_not_nil token, "No upload token found in response:\n#{response.body}" |
|
140 | 150 | token |
|
141 | 151 | end |
|
142 | 152 | end |
General Comments 0
You need to be logged in to leave comments.
Login now