1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,156 | |||||
|
1 | # Redmine - project management software | |||
|
2 | # Copyright (C) 2006-2016 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 | require File.expand_path('../../../../../test_helper', __FILE__) | |||
|
19 | ||||
|
20 | class AttachmentFieldFormatTest < Redmine::IntegrationTest | |||
|
21 | fixtures :projects, | |||
|
22 | :users, :email_addresses, | |||
|
23 | :roles, | |||
|
24 | :members, | |||
|
25 | :member_roles, | |||
|
26 | :trackers, | |||
|
27 | :projects_trackers, | |||
|
28 | :enabled_modules, | |||
|
29 | :issue_statuses, | |||
|
30 | :issues, | |||
|
31 | :enumerations, | |||
|
32 | :custom_fields, | |||
|
33 | :custom_values, | |||
|
34 | :custom_fields_trackers, | |||
|
35 | :attachments | |||
|
36 | ||||
|
37 | def setup | |||
|
38 | set_tmp_attachments_directory | |||
|
39 | @field = IssueCustomField.generate!(:name => "File", :field_format => "attachment") | |||
|
40 | log_user "jsmith", "jsmith" | |||
|
41 | end | |||
|
42 | ||||
|
43 | def test_new_should_include_inputs | |||
|
44 | get '/projects/ecookbook/issues/new' | |||
|
45 | assert_response :success | |||
|
46 | ||||
|
47 | assert_select '[name^=?]', "issue[custom_field_values][#{@field.id}]", 2 | |||
|
48 | assert_select 'input[name=?][type=hidden][value=""]', "issue[custom_field_values][#{@field.id}][blank]" | |||
|
49 | end | |||
|
50 | ||||
|
51 | def test_create_with_attachment | |||
|
52 | issue = new_record(Issue) do | |||
|
53 | assert_difference 'Attachment.count' do | |||
|
54 | post '/projects/ecookbook/issues', { | |||
|
55 | :issue => { | |||
|
56 | :subject => "Subject", | |||
|
57 | :custom_field_values => { | |||
|
58 | @field.id => { | |||
|
59 | 'blank' => '', | |||
|
60 | '1' => {:file => uploaded_test_file("testfile.txt", "text/plain")} | |||
|
61 | } | |||
|
62 | } | |||
|
63 | } | |||
|
64 | } | |||
|
65 | assert_response 302 | |||
|
66 | end | |||
|
67 | end | |||
|
68 | ||||
|
69 | custom_value = issue.custom_value_for(@field) | |||
|
70 | assert custom_value | |||
|
71 | assert custom_value.value.present? | |||
|
72 | ||||
|
73 | attachment = Attachment.find_by_id(custom_value.value) | |||
|
74 | assert attachment | |||
|
75 | assert_equal custom_value, attachment.container | |||
|
76 | ||||
|
77 | follow_redirect! | |||
|
78 | assert_response :success | |||
|
79 | ||||
|
80 | # link to the attachment | |||
|
81 | link = css_select(".cf_#{@field.id} .value a") | |||
|
82 | assert_equal 1, link.size | |||
|
83 | assert_equal "testfile.txt", link.text | |||
|
84 | ||||
|
85 | # download the attachment | |||
|
86 | get link.attr('href') | |||
|
87 | assert_response :success | |||
|
88 | assert_equal "text/plain", response.content_type | |||
|
89 | end | |||
|
90 | ||||
|
91 | def test_create_without_attachment | |||
|
92 | issue = new_record(Issue) do | |||
|
93 | assert_no_difference 'Attachment.count' do | |||
|
94 | post '/projects/ecookbook/issues', { | |||
|
95 | :issue => { | |||
|
96 | :subject => "Subject", | |||
|
97 | :custom_field_values => { | |||
|
98 | @field.id => {:blank => ''} | |||
|
99 | } | |||
|
100 | } | |||
|
101 | } | |||
|
102 | assert_response 302 | |||
|
103 | end | |||
|
104 | end | |||
|
105 | ||||
|
106 | custom_value = issue.custom_value_for(@field) | |||
|
107 | assert custom_value | |||
|
108 | assert custom_value.value.blank? | |||
|
109 | ||||
|
110 | follow_redirect! | |||
|
111 | assert_response :success | |||
|
112 | ||||
|
113 | # no links to the attachment | |||
|
114 | assert_select ".cf_#{@field.id} .value a", 0 | |||
|
115 | end | |||
|
116 | ||||
|
117 | def test_failure_on_create_should_preserve_attachment | |||
|
118 | attachment = new_record(Attachment) do | |||
|
119 | assert_no_difference 'Issue.count' do | |||
|
120 | post '/projects/ecookbook/issues', { | |||
|
121 | :issue => { | |||
|
122 | :subject => "", | |||
|
123 | :custom_field_values => { | |||
|
124 | @field.id => {:file => uploaded_test_file("testfile.txt", "text/plain")} | |||
|
125 | } | |||
|
126 | } | |||
|
127 | } | |||
|
128 | assert_response :success | |||
|
129 | assert_select_error /Subject cannot be blank/ | |||
|
130 | end | |||
|
131 | end | |||
|
132 | ||||
|
133 | assert_nil attachment.container_id | |||
|
134 | assert_select 'input[name=?][value=?][type=hidden]', "issue[custom_field_values][#{@field.id}][p0][token]", attachment.token | |||
|
135 | assert_select 'input[name=?][value=?]', "issue[custom_field_values][#{@field.id}][p0][filename]", 'testfile.txt' | |||
|
136 | ||||
|
137 | issue = new_record(Issue) do | |||
|
138 | assert_no_difference 'Attachment.count' do | |||
|
139 | post '/projects/ecookbook/issues', { | |||
|
140 | :issue => { | |||
|
141 | :subject => "Subject", | |||
|
142 | :custom_field_values => { | |||
|
143 | @field.id => {:token => attachment.token} | |||
|
144 | } | |||
|
145 | } | |||
|
146 | } | |||
|
147 | assert_response 302 | |||
|
148 | end | |||
|
149 | end | |||
|
150 | ||||
|
151 | custom_value = issue.custom_value_for(@field) | |||
|
152 | assert custom_value | |||
|
153 | assert_equal attachment.id.to_s, custom_value.value | |||
|
154 | assert_equal custom_value, attachment.reload.container | |||
|
155 | end | |||
|
156 | end |
@@ -0,0 +1,163 | |||||
|
1 | # Redmine - project management software | |||
|
2 | # Copyright (C) 2006-2016 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 | require File.expand_path('../../../../../test_helper', __FILE__) | |||
|
19 | require 'redmine/field_format' | |||
|
20 | ||||
|
21 | class Redmine::AttachmentFieldFormatTest < ActionView::TestCase | |||
|
22 | include ApplicationHelper | |||
|
23 | include Redmine::I18n | |||
|
24 | ||||
|
25 | fixtures :users | |||
|
26 | ||||
|
27 | def setup | |||
|
28 | set_language_if_valid 'en' | |||
|
29 | set_tmp_attachments_directory | |||
|
30 | end | |||
|
31 | ||||
|
32 | def test_should_accept_a_hash_with_upload_on_create | |||
|
33 | field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment') | |||
|
34 | group = Group.new(:name => 'Group') | |||
|
35 | attachment = nil | |||
|
36 | ||||
|
37 | custom_value = new_record(CustomValue) do | |||
|
38 | attachment = new_record(Attachment) do | |||
|
39 | group.custom_field_values = {field.id => {:file => mock_file}} | |||
|
40 | assert group.save | |||
|
41 | end | |||
|
42 | end | |||
|
43 | ||||
|
44 | assert_equal 'a_file.png', attachment.filename | |||
|
45 | assert_equal custom_value, attachment.container | |||
|
46 | assert_equal field, attachment.container.custom_field | |||
|
47 | assert_equal group, attachment.container.customized | |||
|
48 | end | |||
|
49 | ||||
|
50 | def test_should_accept_a_hash_with_no_upload_on_create | |||
|
51 | field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment') | |||
|
52 | group = Group.new(:name => 'Group') | |||
|
53 | attachment = nil | |||
|
54 | ||||
|
55 | custom_value = new_record(CustomValue) do | |||
|
56 | assert_no_difference 'Attachment.count' do | |||
|
57 | group.custom_field_values = {field.id => {}} | |||
|
58 | assert group.save | |||
|
59 | end | |||
|
60 | end | |||
|
61 | ||||
|
62 | assert_equal '', custom_value.value | |||
|
63 | end | |||
|
64 | ||||
|
65 | def test_should_not_validate_with_invalid_upload_on_create | |||
|
66 | field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment') | |||
|
67 | group = Group.new(:name => 'Group') | |||
|
68 | ||||
|
69 | with_settings :attachment_max_size => 0 do | |||
|
70 | assert_no_difference 'CustomValue.count' do | |||
|
71 | assert_no_difference 'Attachment.count' do | |||
|
72 | group.custom_field_values = {field.id => {:file => mock_file}} | |||
|
73 | assert_equal false, group.save | |||
|
74 | end | |||
|
75 | end | |||
|
76 | end | |||
|
77 | end | |||
|
78 | ||||
|
79 | def test_should_accept_a_hash_with_token_on_create | |||
|
80 | field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment') | |||
|
81 | group = Group.new(:name => 'Group') | |||
|
82 | ||||
|
83 | attachment = Attachment.create!(:file => mock_file, :author => User.find(2)) | |||
|
84 | assert_nil attachment.container | |||
|
85 | ||||
|
86 | custom_value = new_record(CustomValue) do | |||
|
87 | assert_no_difference 'Attachment.count' do | |||
|
88 | group.custom_field_values = {field.id => {:token => attachment.token}} | |||
|
89 | assert group.save | |||
|
90 | end | |||
|
91 | end | |||
|
92 | ||||
|
93 | attachment.reload | |||
|
94 | assert_equal custom_value, attachment.container | |||
|
95 | assert_equal field, attachment.container.custom_field | |||
|
96 | assert_equal group, attachment.container.customized | |||
|
97 | end | |||
|
98 | ||||
|
99 | def test_should_not_validate_with_invalid_token_on_create | |||
|
100 | field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment') | |||
|
101 | group = Group.new(:name => 'Group') | |||
|
102 | ||||
|
103 | assert_no_difference 'CustomValue.count' do | |||
|
104 | assert_no_difference 'Attachment.count' do | |||
|
105 | group.custom_field_values = {field.id => {:token => "123.0123456789abcdef"}} | |||
|
106 | assert_equal false, group.save | |||
|
107 | end | |||
|
108 | end | |||
|
109 | end | |||
|
110 | ||||
|
111 | def test_should_replace_attachment_on_update | |||
|
112 | field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment') | |||
|
113 | group = Group.new(:name => 'Group') | |||
|
114 | attachment = nil | |||
|
115 | custom_value = new_record(CustomValue) do | |||
|
116 | attachment = new_record(Attachment) do | |||
|
117 | group.custom_field_values = {field.id => {:file => mock_file}} | |||
|
118 | assert group.save | |||
|
119 | end | |||
|
120 | end | |||
|
121 | group.reload | |||
|
122 | ||||
|
123 | assert_no_difference 'Attachment.count' do | |||
|
124 | assert_no_difference 'CustomValue.count' do | |||
|
125 | group.custom_field_values = {field.id => {:file => mock_file}} | |||
|
126 | assert group.save | |||
|
127 | end | |||
|
128 | end | |||
|
129 | ||||
|
130 | assert !Attachment.exists?(attachment.id) | |||
|
131 | assert CustomValue.exists?(custom_value.id) | |||
|
132 | ||||
|
133 | new_attachment = Attachment.order(:id => :desc).first | |||
|
134 | custom_value.reload | |||
|
135 | assert_equal custom_value, new_attachment.container | |||
|
136 | end | |||
|
137 | ||||
|
138 | def test_should_delete_attachment_on_update | |||
|
139 | field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment') | |||
|
140 | group = Group.new(:name => 'Group') | |||
|
141 | attachment = nil | |||
|
142 | custom_value = new_record(CustomValue) do | |||
|
143 | attachment = new_record(Attachment) do | |||
|
144 | group.custom_field_values = {field.id => {:file => mock_file}} | |||
|
145 | assert group.save | |||
|
146 | end | |||
|
147 | end | |||
|
148 | group.reload | |||
|
149 | ||||
|
150 | assert_difference 'Attachment.count', -1 do | |||
|
151 | assert_no_difference 'CustomValue.count' do | |||
|
152 | group.custom_field_values = {field.id => {}} | |||
|
153 | assert group.save | |||
|
154 | end | |||
|
155 | end | |||
|
156 | ||||
|
157 | assert !Attachment.exists?(attachment.id) | |||
|
158 | assert CustomValue.exists?(custom_value.id) | |||
|
159 | ||||
|
160 | custom_value.reload | |||
|
161 | assert_equal '', custom_value.value | |||
|
162 | end | |||
|
163 | end |
@@ -197,6 +197,8 module ApplicationHelper | |||||
197 | l(:general_text_No) |
|
197 | l(:general_text_No) | |
198 | when 'Issue' |
|
198 | when 'Issue' | |
199 | object.visible? && html ? link_to_issue(object) : "##{object.id}" |
|
199 | object.visible? && html ? link_to_issue(object) : "##{object.id}" | |
|
200 | when 'Attachment' | |||
|
201 | html ? link_to_attachment(object, :download => true) : object.filename | |||
200 | when 'CustomValue', 'CustomFieldValue' |
|
202 | when 'CustomValue', 'CustomFieldValue' | |
201 | if object.custom_field |
|
203 | if object.custom_field | |
202 | f = object.custom_field.format.formatted_custom_value(self, object, html) |
|
204 | f = object.custom_field.format.formatted_custom_value(self, object, html) |
@@ -329,6 +329,7 module IssuesHelper | |||||
329 | def show_detail(detail, no_html=false, options={}) |
|
329 | def show_detail(detail, no_html=false, options={}) | |
330 | multiple = false |
|
330 | multiple = false | |
331 | show_diff = false |
|
331 | show_diff = false | |
|
332 | no_details = false | |||
332 |
|
333 | |||
333 | case detail.property |
|
334 | case detail.property | |
334 | when 'attr' |
|
335 | when 'attr' | |
@@ -364,7 +365,9 module IssuesHelper | |||||
364 | custom_field = detail.custom_field |
|
365 | custom_field = detail.custom_field | |
365 | if custom_field |
|
366 | if custom_field | |
366 | label = custom_field.name |
|
367 | label = custom_field.name | |
367 |
if custom_field.format.class.change_ |
|
368 | if custom_field.format.class.change_no_details | |
|
369 | no_details = true | |||
|
370 | elsif custom_field.format.class.change_as_diff | |||
368 | show_diff = true |
|
371 | show_diff = true | |
369 | else |
|
372 | else | |
370 | multiple = custom_field.multiple? |
|
373 | multiple = custom_field.multiple? | |
@@ -417,7 +420,9 module IssuesHelper | |||||
417 | end |
|
420 | end | |
418 | end |
|
421 | end | |
419 |
|
422 | |||
420 |
if |
|
423 | if no_details | |
|
424 | s = l(:text_journal_changed_no_detail, :label => label).html_safe | |||
|
425 | elsif show_diff | |||
421 | s = l(:text_journal_changed_no_detail, :label => label) |
|
426 | s = l(:text_journal_changed_no_detail, :label => label) | |
422 | unless no_html |
|
427 | unless no_html | |
423 | diff_link = link_to 'diff', |
|
428 | diff_link = link_to 'diff', |
@@ -163,6 +163,10 class CustomField < ActiveRecord::Base | |||||
163 | end |
|
163 | end | |
164 | end |
|
164 | end | |
165 |
|
165 | |||
|
166 | def set_custom_field_value(custom_field_value, value) | |||
|
167 | format.set_custom_field_value(self, custom_field_value, value) | |||
|
168 | end | |||
|
169 | ||||
166 | def cast_value(value) |
|
170 | def cast_value(value) | |
167 | format.cast_value(self, value) |
|
171 | format.cast_value(self, value) | |
168 | end |
|
172 | end | |
@@ -254,20 +258,23 class CustomField < ActiveRecord::Base | |||||
254 | # or an empty array if value is a valid value for the custom field |
|
258 | # or an empty array if value is a valid value for the custom field | |
255 | def validate_custom_value(custom_value) |
|
259 | def validate_custom_value(custom_value) | |
256 | value = custom_value.value |
|
260 | value = custom_value.value | |
257 | errs = [] |
|
261 | errs = format.validate_custom_value(custom_value) | |
258 | if value.is_a?(Array) |
|
262 | ||
259 | if !multiple? |
|
263 | unless errs.any? | |
260 | errs << ::I18n.t('activerecord.errors.messages.invalid') |
|
264 | if value.is_a?(Array) | |
261 | end |
|
265 | if !multiple? | |
262 | if is_required? && value.detect(&:present?).nil? |
|
266 | errs << ::I18n.t('activerecord.errors.messages.invalid') | |
263 | errs << ::I18n.t('activerecord.errors.messages.blank') |
|
267 | end | |
264 | end |
|
268 | if is_required? && value.detect(&:present?).nil? | |
265 | else |
|
269 | errs << ::I18n.t('activerecord.errors.messages.blank') | |
266 | if is_required? && value.blank? |
|
270 | end | |
267 | errs << ::I18n.t('activerecord.errors.messages.blank') |
|
271 | else | |
|
272 | if is_required? && value.blank? | |||
|
273 | errs << ::I18n.t('activerecord.errors.messages.blank') | |||
|
274 | end | |||
268 | end |
|
275 | end | |
269 | end |
|
276 | end | |
270 | errs += format.validate_custom_value(custom_value) |
|
277 | ||
271 | errs |
|
278 | errs | |
272 | end |
|
279 | end | |
273 |
|
280 | |||
@@ -281,6 +288,10 class CustomField < ActiveRecord::Base | |||||
281 | validate_field_value(value).empty? |
|
288 | validate_field_value(value).empty? | |
282 | end |
|
289 | end | |
283 |
|
290 | |||
|
291 | def after_save_custom_value(custom_value) | |||
|
292 | format.after_save_custom_value(self, custom_value) | |||
|
293 | end | |||
|
294 | ||||
284 | def format_in?(*args) |
|
295 | def format_in?(*args) | |
285 | args.include?(field_format) |
|
296 | args.include?(field_format) | |
286 | end |
|
297 | end |
@@ -48,6 +48,10 class CustomFieldValue | |||||
48 | value.to_s |
|
48 | value.to_s | |
49 | end |
|
49 | end | |
50 |
|
50 | |||
|
51 | def value=(v) | |||
|
52 | @value = custom_field.set_custom_field_value(self, v) | |||
|
53 | end | |||
|
54 | ||||
51 | def validate_value |
|
55 | def validate_value | |
52 | custom_field.validate_custom_value(self).each do |message| |
|
56 | custom_field.validate_custom_value(self).each do |message| | |
53 | customized.errors.add(:base, custom_field.name + ' ' + message) |
|
57 | customized.errors.add(:base, custom_field.name + ' ' + message) |
@@ -20,6 +20,8 class CustomValue < ActiveRecord::Base | |||||
20 | belongs_to :customized, :polymorphic => true |
|
20 | belongs_to :customized, :polymorphic => true | |
21 | attr_protected :id |
|
21 | attr_protected :id | |
22 |
|
22 | |||
|
23 | after_save :custom_field_after_save_custom_value | |||
|
24 | ||||
23 | def initialize(attributes=nil, *args) |
|
25 | def initialize(attributes=nil, *args) | |
24 | super |
|
26 | super | |
25 | if new_record? && custom_field && !attributes.key?(:value) |
|
27 | if new_record? && custom_field && !attributes.key?(:value) | |
@@ -40,6 +42,10 class CustomValue < ActiveRecord::Base | |||||
40 | custom_field.visible? |
|
42 | custom_field.visible? | |
41 | end |
|
43 | end | |
42 |
|
44 | |||
|
45 | def attachments_visible?(user) | |||
|
46 | visible? && customized && customized.visible?(user) | |||
|
47 | end | |||
|
48 | ||||
43 | def required? |
|
49 | def required? | |
44 | custom_field.is_required? |
|
50 | custom_field.is_required? | |
45 | end |
|
51 | end | |
@@ -47,4 +53,10 class CustomValue < ActiveRecord::Base | |||||
47 | def to_s |
|
53 | def to_s | |
48 | value.to_s |
|
54 | value.to_s | |
49 | end |
|
55 | end | |
|
56 | ||||
|
57 | private | |||
|
58 | ||||
|
59 | def custom_field_after_save_custom_value | |||
|
60 | custom_field.after_save_custom_value(self) | |||
|
61 | end | |||
50 | end |
|
62 | end |
@@ -1,29 +1,45 | |||||
1 | <span id="attachments_fields"> |
|
1 | <% attachment_param ||= 'attachments' %> | |
2 | <% if defined?(container) && container && container.saved_attachments %> |
|
2 | <% saved_attachments ||= container.saved_attachments if defined?(container) && container %> | |
3 | <% container.saved_attachments.each_with_index do |attachment, i| %> |
|
3 | <% multiple = true unless defined?(multiple) && multiple == false %> | |
4 | <span id="attachments_p<%= i %>"> |
|
4 | <% show_add = multiple || saved_attachments.blank? %> | |
5 | <%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename') + |
|
5 | <% description = (defined?(description) && description == false ? false : true) %> | |
6 | text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') + |
|
6 | <% css_class = (defined?(filedrop) && filedrop == false ? '' : 'filedrop') %> | |
7 | link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %> |
|
7 | ||
8 | <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %> |
|
8 | <span class="attachments_form"> | |
9 | </span> |
|
9 | <span class="attachments_fields"> | |
|
10 | <% if saved_attachments.present? %> | |||
|
11 | <% saved_attachments.each_with_index do |attachment, i| %> | |||
|
12 | <span id="attachments_p<%= i %>"> | |||
|
13 | <%= text_field_tag("#{attachment_param}[p#{i}][filename]", attachment.filename, :class => 'filename') %> | |||
|
14 | <% if attachment.container_id.present? %> | |||
|
15 | <%= link_to l(:label_delete), "#", :onclick => "$(this).closest('.attachments_form').find('.add_attachment').show(); $(this).parent().remove(); return false;", :class => 'icon-only icon-del' %> | |||
|
16 | <%= hidden_field_tag "#{attachment_param}[p#{i}][id]", attachment.id %> | |||
|
17 | <% else %> | |||
|
18 | <%= text_field_tag("#{attachment_param}[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') if description %> | |||
|
19 | <%= link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %> | |||
|
20 | <%= hidden_field_tag "#{attachment_param}[p#{i}][token]", attachment.token %> | |||
|
21 | <% end %> | |||
|
22 | </span> | |||
|
23 | <% end %> | |||
10 | <% end %> |
|
24 | <% end %> | |
11 | <% end %> |
|
25 | </span> | |
12 | </span> |
|
26 | <span class="add_attachment" style="<%= show_add ? nil : 'display:none;' %>"> | |
13 | <span class="add_attachment"> |
|
27 | <%= file_field_tag "#{attachment_param}[dummy][file]", | |
14 | <%= file_field_tag 'attachments[dummy][file]', |
|
28 | :id => nil, | |
15 | :id => nil, |
|
29 | :class => "file_selector #{css_class}", | |
16 | :class => 'file_selector', |
|
30 | :multiple => multiple, | |
17 | :multiple => true, |
|
31 | :onchange => 'addInputFiles(this);', | |
18 | :onchange => 'addInputFiles(this);', |
|
32 | :data => { | |
19 | :data => { |
|
33 | :max_file_size => Setting.attachment_max_size.to_i.kilobytes, | |
20 | :max_file_size => Setting.attachment_max_size.to_i.kilobytes, |
|
34 | :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), | |
21 | :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), |
|
35 | :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, | |
22 | :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, |
|
36 | :upload_path => uploads_path(:format => 'js'), | |
23 | :upload_path => uploads_path(:format => 'js'), |
|
37 | :param => attachment_param, | |
24 |
:description |
|
38 | :description => description, | |
25 | } %> |
|
39 | :description_placeholder => l(:label_optional_description) | |
26 | (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) |
|
40 | } %> | |
|
41 | (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) | |||
|
42 | </span> | |||
27 | </span> |
|
43 | </span> | |
28 |
|
44 | |||
29 | <% content_for :header_tags do %> |
|
45 | <% content_for :header_tags do %> |
@@ -1,1 +1,2 | |||||
|
1 | $('#attachments_<%= j params[:attachment_id] %>').closest('.attachments_form').find('.add_attachment').show(); | |||
1 | $('#attachments_<%= j params[:attachment_id] %>').remove(); |
|
2 | $('#attachments_<%= j params[:attachment_id] %>').remove(); |
@@ -3,7 +3,7 var fileSpan = $('#attachments_<%= j params[:attachment_id] %>'); | |||||
3 | fileSpan.hide(); |
|
3 | fileSpan.hide(); | |
4 | alert("<%= escape_javascript @attachment.errors.full_messages.join(', ') %>"); |
|
4 | alert("<%= escape_javascript @attachment.errors.full_messages.join(', ') %>"); | |
5 | <% else %> |
|
5 | <% else %> | |
6 | $('<input>', { type: 'hidden', name: 'attachments[<%= j params[:attachment_id] %>][token]' } ).val('<%= j @attachment.token %>').appendTo(fileSpan); |
|
6 | fileSpan.find('input.token').val('<%= j @attachment.token %>'); | |
7 | fileSpan.find('a.remove-upload') |
|
7 | fileSpan.find('a.remove-upload') | |
8 | .attr({ |
|
8 | .attr({ | |
9 | "data-remote": true, |
|
9 | "data-remote": true, |
@@ -28,7 +28,9 | |||||
28 | when "IssueCustomField" %> |
|
28 | when "IssueCustomField" %> | |
29 | <p><%= f.check_box :is_required %></p> |
|
29 | <p><%= f.check_box :is_required %></p> | |
30 | <p><%= f.check_box :is_for_all, :data => {:disables => '#custom_field_project_ids input'} %></p> |
|
30 | <p><%= f.check_box :is_for_all, :data => {:disables => '#custom_field_project_ids input'} %></p> | |
|
31 | <% if @custom_field.format.is_filter_supported %> | |||
31 | <p><%= f.check_box :is_filter %></p> |
|
32 | <p><%= f.check_box :is_filter %></p> | |
|
33 | <% end %> | |||
32 | <% if @custom_field.format.searchable_supported %> |
|
34 | <% if @custom_field.format.searchable_supported %> | |
33 | <p><%= f.check_box :searchable %></p> |
|
35 | <p><%= f.check_box :searchable %></p> | |
34 | <% end %> |
|
36 | <% end %> | |
@@ -57,7 +59,9 when "IssueCustomField" %> | |||||
57 | <p><%= f.check_box :is_required %></p> |
|
59 | <p><%= f.check_box :is_required %></p> | |
58 | <p><%= f.check_box :visible %></p> |
|
60 | <p><%= f.check_box :visible %></p> | |
59 | <p><%= f.check_box :editable %></p> |
|
61 | <p><%= f.check_box :editable %></p> | |
|
62 | <% if @custom_field.format.is_filter_supported %> | |||
60 | <p><%= f.check_box :is_filter %></p> |
|
63 | <p><%= f.check_box :is_filter %></p> | |
|
64 | <% end %> | |||
61 |
|
65 | |||
62 | <% when "ProjectCustomField" %> |
|
66 | <% when "ProjectCustomField" %> | |
63 | <p><%= f.check_box :is_required %></p> |
|
67 | <p><%= f.check_box :is_required %></p> | |
@@ -65,19 +69,27 when "IssueCustomField" %> | |||||
65 | <% if @custom_field.format.searchable_supported %> |
|
69 | <% if @custom_field.format.searchable_supported %> | |
66 | <p><%= f.check_box :searchable %></p> |
|
70 | <p><%= f.check_box :searchable %></p> | |
67 | <% end %> |
|
71 | <% end %> | |
|
72 | <% if @custom_field.format.is_filter_supported %> | |||
68 | <p><%= f.check_box :is_filter %></p> |
|
73 | <p><%= f.check_box :is_filter %></p> | |
|
74 | <% end %> | |||
69 |
|
75 | |||
70 | <% when "VersionCustomField" %> |
|
76 | <% when "VersionCustomField" %> | |
71 | <p><%= f.check_box :is_required %></p> |
|
77 | <p><%= f.check_box :is_required %></p> | |
|
78 | <% if @custom_field.format.is_filter_supported %> | |||
72 | <p><%= f.check_box :is_filter %></p> |
|
79 | <p><%= f.check_box :is_filter %></p> | |
|
80 | <% end %> | |||
73 |
|
81 | |||
74 | <% when "GroupCustomField" %> |
|
82 | <% when "GroupCustomField" %> | |
75 | <p><%= f.check_box :is_required %></p> |
|
83 | <p><%= f.check_box :is_required %></p> | |
|
84 | <% if @custom_field.format.is_filter_supported %> | |||
76 | <p><%= f.check_box :is_filter %></p> |
|
85 | <p><%= f.check_box :is_filter %></p> | |
|
86 | <% end %> | |||
77 |
|
87 | |||
78 | <% when "TimeEntryCustomField" %> |
|
88 | <% when "TimeEntryCustomField" %> | |
79 | <p><%= f.check_box :is_required %></p> |
|
89 | <p><%= f.check_box :is_required %></p> | |
|
90 | <% if @custom_field.format.is_filter_supported %> | |||
80 | <p><%= f.check_box :is_filter %></p> |
|
91 | <p><%= f.check_box :is_filter %></p> | |
|
92 | <% end %> | |||
81 |
|
93 | |||
82 | <% else %> |
|
94 | <% else %> | |
83 | <p><%= f.check_box :is_required %></p> |
|
95 | <p><%= f.check_box :is_required %></p> |
@@ -34,6 +34,7 module Redmine | |||||
34 | options.merge(:as => :container, :dependent => :destroy, :inverse_of => :container) |
|
34 | options.merge(:as => :container, :dependent => :destroy, :inverse_of => :container) | |
35 | send :include, Redmine::Acts::Attachable::InstanceMethods |
|
35 | send :include, Redmine::Acts::Attachable::InstanceMethods | |
36 | before_save :attach_saved_attachments |
|
36 | before_save :attach_saved_attachments | |
|
37 | after_rollback :detach_saved_attachments | |||
37 | validate :warn_about_failed_attachments |
|
38 | validate :warn_about_failed_attachments | |
38 | end |
|
39 | end | |
39 | end |
|
40 | end | |
@@ -90,7 +91,7 module Redmine | |||||
90 | if file = attachment['file'] |
|
91 | if file = attachment['file'] | |
91 | next unless file.size > 0 |
|
92 | next unless file.size > 0 | |
92 | a = Attachment.create(:file => file, :author => author) |
|
93 | a = Attachment.create(:file => file, :author => author) | |
93 | elsif token = attachment['token'] |
|
94 | elsif token = attachment['token'].presence | |
94 | a = Attachment.find_by_token(token) |
|
95 | a = Attachment.find_by_token(token) | |
95 | unless a |
|
96 | unless a | |
96 | @failed_attachment_count += 1 |
|
97 | @failed_attachment_count += 1 | |
@@ -117,6 +118,14 module Redmine | |||||
117 | end |
|
118 | end | |
118 | end |
|
119 | end | |
119 |
|
120 | |||
|
121 | def detach_saved_attachments | |||
|
122 | saved_attachments.each do |attachment| | |||
|
123 | # TODO: use #reload instead, after upgrading to Rails 5 | |||
|
124 | # (after_rollback is called when running transactional tests in Rails 4) | |||
|
125 | attachment.container = nil | |||
|
126 | end | |||
|
127 | end | |||
|
128 | ||||
120 | def warn_about_failed_attachments |
|
129 | def warn_about_failed_attachments | |
121 | if @failed_attachment_count && @failed_attachment_count > 0 |
|
130 | if @failed_attachment_count && @failed_attachment_count > 0 | |
122 | errors.add :base, ::I18n.t('warning_attachments_not_saved', count: @failed_attachment_count) |
|
131 | errors.add :base, ::I18n.t('warning_attachments_not_saved', count: @failed_attachment_count) |
@@ -68,16 +68,7 module Redmine | |||||
68 | custom_field_values.each do |custom_field_value| |
|
68 | custom_field_values.each do |custom_field_value| | |
69 | key = custom_field_value.custom_field_id.to_s |
|
69 | key = custom_field_value.custom_field_id.to_s | |
70 | if values.has_key?(key) |
|
70 | if values.has_key?(key) | |
71 | value = values[key] |
|
71 | custom_field_value.value = values[key] | |
72 | if value.is_a?(Array) |
|
|||
73 | value = value.reject(&:blank?).map(&:to_s).uniq |
|
|||
74 | if value.empty? |
|
|||
75 | value << '' |
|
|||
76 | end |
|
|||
77 | else |
|
|||
78 | value = value.to_s |
|
|||
79 | end |
|
|||
80 | custom_field_value.value = value |
|
|||
81 | end |
|
72 | end | |
82 | end |
|
73 | end | |
83 | @custom_field_values_changed = true |
|
74 | @custom_field_values_changed = true | |
@@ -93,11 +84,11 module Redmine | |||||
93 | if values.empty? |
|
84 | if values.empty? | |
94 | values << custom_values.build(:customized => self, :custom_field => field) |
|
85 | values << custom_values.build(:customized => self, :custom_field => field) | |
95 | end |
|
86 | end | |
96 |
x. |
|
87 | x.instance_variable_set("@value", values.map(&:value)) | |
97 | else |
|
88 | else | |
98 | cv = custom_values.detect { |v| v.custom_field == field } |
|
89 | cv = custom_values.detect { |v| v.custom_field == field } | |
99 | cv ||= custom_values.build(:customized => self, :custom_field => field) |
|
90 | cv ||= custom_values.build(:customized => self, :custom_field => field) | |
100 |
x. |
|
91 | x.instance_variable_set("@value", cv.value) | |
101 | end |
|
92 | end | |
102 | x.value_was = x.value.dup if x.value |
|
93 | x.value_was = x.value.dup if x.value | |
103 | x |
|
94 | x |
@@ -67,6 +67,10 module Redmine | |||||
67 | class_attribute :multiple_supported |
|
67 | class_attribute :multiple_supported | |
68 | self.multiple_supported = false |
|
68 | self.multiple_supported = false | |
69 |
|
69 | |||
|
70 | # Set this to true if the format supports filtering on custom values | |||
|
71 | class_attribute :is_filter_supported | |||
|
72 | self.is_filter_supported = true | |||
|
73 | ||||
70 | # Set this to true if the format supports textual search on custom values |
|
74 | # Set this to true if the format supports textual search on custom values | |
71 | class_attribute :searchable_supported |
|
75 | class_attribute :searchable_supported | |
72 | self.searchable_supported = false |
|
76 | self.searchable_supported = false | |
@@ -87,6 +91,9 module Redmine | |||||
87 | class_attribute :change_as_diff |
|
91 | class_attribute :change_as_diff | |
88 | self.change_as_diff = false |
|
92 | self.change_as_diff = false | |
89 |
|
93 | |||
|
94 | class_attribute :change_no_details | |||
|
95 | self.change_no_details = false | |||
|
96 | ||||
90 | def self.add(name) |
|
97 | def self.add(name) | |
91 | self.format_name = name |
|
98 | self.format_name = name | |
92 | Redmine::FieldFormat.add(name, self) |
|
99 | Redmine::FieldFormat.add(name, self) | |
@@ -107,6 +114,19 module Redmine | |||||
107 | "label_#{name}" |
|
114 | "label_#{name}" | |
108 | end |
|
115 | end | |
109 |
|
116 | |||
|
117 | def set_custom_field_value(custom_field, custom_field_value, value) | |||
|
118 | if value.is_a?(Array) | |||
|
119 | value = value.map(&:to_s).reject{|v| v==''}.uniq | |||
|
120 | if value.empty? | |||
|
121 | value << '' | |||
|
122 | end | |||
|
123 | else | |||
|
124 | value = value.to_s | |||
|
125 | end | |||
|
126 | ||||
|
127 | value | |||
|
128 | end | |||
|
129 | ||||
110 | def cast_custom_value(custom_value) |
|
130 | def cast_custom_value(custom_value) | |
111 | cast_value(custom_value.custom_field, custom_value.value, custom_value.customized) |
|
131 | cast_value(custom_value.custom_field, custom_value.value, custom_value.customized) | |
112 | end |
|
132 | end | |
@@ -169,6 +189,7 module Redmine | |||||
169 |
|
189 | |||
170 | # Returns the validation error messages for custom_value |
|
190 | # Returns the validation error messages for custom_value | |
171 | # Should return an empty array if custom_value is valid |
|
191 | # Should return an empty array if custom_value is valid | |
|
192 | # custom_value is a CustomFieldValue. | |||
172 | def validate_custom_value(custom_value) |
|
193 | def validate_custom_value(custom_value) | |
173 | values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''} |
|
194 | values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''} | |
174 | errors = values.map do |value| |
|
195 | errors = values.map do |value| | |
@@ -181,6 +202,10 module Redmine | |||||
181 | [] |
|
202 | [] | |
182 | end |
|
203 | end | |
183 |
|
204 | |||
|
205 | # CustomValue after_save callback | |||
|
206 | def after_save_custom_value(custom_field, custom_value) | |||
|
207 | end | |||
|
208 | ||||
184 | def formatted_custom_value(view, custom_value, html=false) |
|
209 | def formatted_custom_value(view, custom_value, html=false) | |
185 | formatted_value(view, custom_value.custom_field, custom_value.value, custom_value.customized, html) |
|
210 | formatted_value(view, custom_value.custom_field, custom_value.value, custom_value.customized, html) | |
186 | end |
|
211 | end | |
@@ -830,5 +855,109 module Redmine | |||||
830 | scope.sort.collect{|u| [u.to_s, u.id.to_s] } |
|
855 | scope.sort.collect{|u| [u.to_s, u.id.to_s] } | |
831 | end |
|
856 | end | |
832 | end |
|
857 | end | |
|
858 | ||||
|
859 | class AttachementFormat < Base | |||
|
860 | add 'attachment' | |||
|
861 | self.form_partial = 'custom_fields/formats/attachment' | |||
|
862 | self.is_filter_supported = false | |||
|
863 | self.change_no_details = true | |||
|
864 | ||||
|
865 | def set_custom_field_value(custom_field, custom_field_value, value) | |||
|
866 | attachment_present = false | |||
|
867 | ||||
|
868 | if value.is_a?(Hash) | |||
|
869 | attachment_present = true | |||
|
870 | value = value.except(:blank) | |||
|
871 | ||||
|
872 | if value.values.any? && value.values.all? {|v| v.is_a?(Hash)} | |||
|
873 | value = value.values.first | |||
|
874 | end | |||
|
875 | ||||
|
876 | if value.key?(:id) | |||
|
877 | value = set_custom_field_value_by_id(custom_field, custom_field_value, value[:id]) | |||
|
878 | elsif value[:token].present? | |||
|
879 | if attachment = Attachment.find_by_token(value[:token]) | |||
|
880 | value = attachment.id.to_s | |||
|
881 | else | |||
|
882 | value = '' | |||
|
883 | end | |||
|
884 | elsif value.key?(:file) | |||
|
885 | attachment = Attachment.new(:file => value[:file], :author => User.current) | |||
|
886 | if attachment.save | |||
|
887 | value = attachment.id.to_s | |||
|
888 | else | |||
|
889 | value = '' | |||
|
890 | end | |||
|
891 | else | |||
|
892 | attachment_present = false | |||
|
893 | value = '' | |||
|
894 | end | |||
|
895 | elsif value.is_a?(String) | |||
|
896 | value = set_custom_field_value_by_id(custom_field, custom_field_value, value) | |||
|
897 | end | |||
|
898 | custom_field_value.instance_variable_set "@attachment_present", attachment_present | |||
|
899 | ||||
|
900 | value | |||
|
901 | end | |||
|
902 | ||||
|
903 | def set_custom_field_value_by_id(custom_field, custom_field_value, id) | |||
|
904 | attachment = Attachment.find_by_id(id) | |||
|
905 | if attachment && attachment.container.is_a?(CustomValue) && attachment.container.customized == custom_field_value.customized | |||
|
906 | id.to_s | |||
|
907 | else | |||
|
908 | '' | |||
|
909 | end | |||
|
910 | end | |||
|
911 | private :set_custom_field_value_by_id | |||
|
912 | ||||
|
913 | def cast_single_value(custom_field, value, customized=nil) | |||
|
914 | Attachment.find_by_id(value.to_i) if value.present? && value.respond_to?(:to_i) | |||
|
915 | end | |||
|
916 | ||||
|
917 | def validate_custom_value(custom_value) | |||
|
918 | errors = [] | |||
|
919 | ||||
|
920 | if custom_value.instance_variable_get("@attachment_present") && custom_value.value.blank? | |||
|
921 | errors << ::I18n.t('activerecord.errors.messages.invalid') | |||
|
922 | end | |||
|
923 | ||||
|
924 | errors.uniq | |||
|
925 | end | |||
|
926 | ||||
|
927 | def after_save_custom_value(custom_field, custom_value) | |||
|
928 | if custom_value.value_changed? | |||
|
929 | if custom_value.value.present? | |||
|
930 | attachment = Attachment.where(:id => custom_value.value.to_s).first | |||
|
931 | if attachment | |||
|
932 | attachment.container = custom_value | |||
|
933 | attachment.save! | |||
|
934 | end | |||
|
935 | end | |||
|
936 | if custom_value.value_was.present? | |||
|
937 | attachment = Attachment.where(:id => custom_value.value_was.to_s).first | |||
|
938 | if attachment | |||
|
939 | attachment.destroy | |||
|
940 | end | |||
|
941 | end | |||
|
942 | end | |||
|
943 | end | |||
|
944 | ||||
|
945 | def edit_tag(view, tag_id, tag_name, custom_value, options={}) | |||
|
946 | attachment = nil | |||
|
947 | if custom_value.value.present? #&& custom_value.value == custom_value.value_was | |||
|
948 | attachment = Attachment.find_by_id(custom_value.value) | |||
|
949 | end | |||
|
950 | ||||
|
951 | view.hidden_field_tag("#{tag_name}[blank]", "") + | |||
|
952 | view.render(:partial => 'attachments/form', | |||
|
953 | :locals => { | |||
|
954 | :attachment_param => tag_name, | |||
|
955 | :multiple => false, | |||
|
956 | :description => false, | |||
|
957 | :saved_attachments => [attachment].compact, | |||
|
958 | :filedrop => false | |||
|
959 | }) | |||
|
960 | end | |||
|
961 | end | |||
833 | end |
|
962 | end | |
834 | end |
|
963 | end |
@@ -2,23 +2,32 | |||||
2 | Copyright (C) 2006-2016 Jean-Philippe Lang */ |
|
2 | Copyright (C) 2006-2016 Jean-Philippe Lang */ | |
3 |
|
3 | |||
4 | function addFile(inputEl, file, eagerUpload) { |
|
4 | function addFile(inputEl, file, eagerUpload) { | |
|
5 | var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields'); | |||
|
6 | var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment'); | |||
|
7 | var maxFiles = ($(inputEl).prop('multiple') == true ? 10 : 1); | |||
5 |
|
8 | |||
6 |
if ( |
|
9 | if (attachmentsFields.children().length < maxFiles) { | |
7 |
|
||||
8 | var attachmentId = addFile.nextAttachmentId++; |
|
10 | var attachmentId = addFile.nextAttachmentId++; | |
9 |
|
||||
10 | var fileSpan = $('<span>', { id: 'attachments_' + attachmentId }); |
|
11 | var fileSpan = $('<span>', { id: 'attachments_' + attachmentId }); | |
|
12 | var param = $(inputEl).data('param'); | |||
|
13 | if (!param) {param = 'attachments'}; | |||
11 |
|
14 | |||
12 | fileSpan.append( |
|
15 | fileSpan.append( | |
13 |
$('<input>', { type: 'text', 'class': 'filename readonly', name: |
|
16 | $('<input>', { type: 'text', 'class': 'filename readonly', name: param +'[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name), | |
14 |
$('<input>', { type: 'text', 'class': 'description', name: |
|
17 | $('<input>', { type: 'text', 'class': 'description', name: param + '[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload), | |
|
18 | $('<input>', { type: 'hidden', 'class': 'token', name: param + '[' + attachmentId + '][token]'} ), | |||
15 | $('<a> </a>').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload) |
|
19 | $('<a> </a>').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload) | |
16 |
).appendTo( |
|
20 | ).appendTo(attachmentsFields); | |
|
21 | ||||
|
22 | if ($(inputEl).data('description') == 0) { | |||
|
23 | fileSpan.find('input.description').remove(); | |||
|
24 | } | |||
17 |
|
25 | |||
18 | if(eagerUpload) { |
|
26 | if(eagerUpload) { | |
19 | ajaxUpload(file, attachmentId, fileSpan, inputEl); |
|
27 | ajaxUpload(file, attachmentId, fileSpan, inputEl); | |
20 | } |
|
28 | } | |
21 |
|
29 | |||
|
30 | addAttachment.toggle(attachmentsFields.children().length < maxFiles); | |||
22 | return attachmentId; |
|
31 | return attachmentId; | |
23 | } |
|
32 | } | |
24 | return null; |
|
33 | return null; | |
@@ -118,11 +127,16 function uploadBlob(blob, uploadUrl, attachmentId, options) { | |||||
118 | } |
|
127 | } | |
119 |
|
128 | |||
120 | function addInputFiles(inputEl) { |
|
129 | function addInputFiles(inputEl) { | |
|
130 | var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields'); | |||
|
131 | var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment'); | |||
121 | var clearedFileInput = $(inputEl).clone().val(''); |
|
132 | var clearedFileInput = $(inputEl).clone().val(''); | |
|
133 | var sizeExceeded = false; | |||
|
134 | var param = $(inputEl).data('param'); | |||
|
135 | if (!param) {param = 'attachments'}; | |||
122 |
|
136 | |||
123 | if ($.ajaxSettings.xhr().upload && inputEl.files) { |
|
137 | if ($.ajaxSettings.xhr().upload && inputEl.files) { | |
124 | // upload files using ajax |
|
138 | // upload files using ajax | |
125 | uploadAndAttachFiles(inputEl.files, inputEl); |
|
139 | sizeExceeded = uploadAndAttachFiles(inputEl.files, inputEl); | |
126 | $(inputEl).remove(); |
|
140 | $(inputEl).remove(); | |
127 | } else { |
|
141 | } else { | |
128 | // browser not supporting the file API, upload on form submission |
|
142 | // browser not supporting the file API, upload on form submission | |
@@ -130,11 +144,11 function addInputFiles(inputEl) { | |||||
130 | var aFilename = inputEl.value.split(/\/|\\/); |
|
144 | var aFilename = inputEl.value.split(/\/|\\/); | |
131 | attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false); |
|
145 | attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false); | |
132 | if (attachmentId) { |
|
146 | if (attachmentId) { | |
133 |
$(inputEl).attr({ name: |
|
147 | $(inputEl).attr({ name: param + '[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId); | |
134 | } |
|
148 | } | |
135 | } |
|
149 | } | |
136 |
|
150 | |||
137 |
clearedFileInput. |
|
151 | clearedFileInput.prependTo(addAttachment); | |
138 | } |
|
152 | } | |
139 |
|
153 | |||
140 | function uploadAndAttachFiles(files, inputEl) { |
|
154 | function uploadAndAttachFiles(files, inputEl) { | |
@@ -151,6 +165,7 function uploadAndAttachFiles(files, inputEl) { | |||||
151 | } else { |
|
165 | } else { | |
152 | $.each(files, function() {addFile(inputEl, this, true);}); |
|
166 | $.each(files, function() {addFile(inputEl, this, true);}); | |
153 | } |
|
167 | } | |
|
168 | return sizeExceeded; | |||
154 | } |
|
169 | } | |
155 |
|
170 | |||
156 | function handleFileDropEvent(e) { |
|
171 | function handleFileDropEvent(e) { | |
@@ -159,7 +174,7 function handleFileDropEvent(e) { | |||||
159 | blockEventPropagation(e); |
|
174 | blockEventPropagation(e); | |
160 |
|
175 | |||
161 | if ($.inArray('Files', e.dataTransfer.types) > -1) { |
|
176 | if ($.inArray('Files', e.dataTransfer.types) > -1) { | |
162 |
uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file |
|
177 | uploadAndAttachFiles(e.dataTransfer.files, $('input:file.filedrop').first()); | |
163 | } |
|
178 | } | |
164 | } |
|
179 | } | |
165 |
|
180 | |||
@@ -178,12 +193,12 function setupFileDrop() { | |||||
178 |
|
193 | |||
179 | $.event.fixHooks.drop = { props: [ 'dataTransfer' ] }; |
|
194 | $.event.fixHooks.drop = { props: [ 'dataTransfer' ] }; | |
180 |
|
195 | |||
181 | $('form div.box').has('input:file').each(function() { |
|
196 | $('form div.box:not(.filedroplistner)').has('input:file.filedrop').each(function() { | |
182 | $(this).on({ |
|
197 | $(this).on({ | |
183 | dragover: dragOverHandler, |
|
198 | dragover: dragOverHandler, | |
184 | dragleave: dragOutHandler, |
|
199 | dragleave: dragOutHandler, | |
185 | drop: handleFileDropEvent |
|
200 | drop: handleFileDropEvent | |
186 | }); |
|
201 | }).addClass('filedroplistner'); | |
187 | }); |
|
202 | }); | |
188 | } |
|
203 | } | |
189 | } |
|
204 | } |
@@ -600,7 +600,7 span.pagination>span {white-space:nowrap;} | |||||
600 | margin: 0; |
|
600 | margin: 0; | |
601 | padding: 3px 0 3px 0; |
|
601 | padding: 3px 0 3px 0; | |
602 | padding-left: 180px; /* width of left column containing the label elements */ |
|
602 | padding-left: 180px; /* width of left column containing the label elements */ | |
603 |
|
|
603 | line-height: 2em; | |
604 | clear:left; |
|
604 | clear:left; | |
605 | } |
|
605 | } | |
606 |
|
606 | |||
@@ -626,13 +626,16 html>body .tabular p {overflow:hidden;} | |||||
626 | width: 270px; |
|
626 | width: 270px; | |
627 | } |
|
627 | } | |
628 |
|
628 | |||
|
629 | label.block { | |||
|
630 | display: block; | |||
|
631 | width: auto !important; | |||
|
632 | } | |||
|
633 | ||||
629 | .tabular label.block{ |
|
634 | .tabular label.block{ | |
630 | font-weight: normal; |
|
635 | font-weight: normal; | |
631 | margin-left: 0px !important; |
|
636 | margin-left: 0px !important; | |
632 | text-align: left; |
|
637 | text-align: left; | |
633 | float: none; |
|
638 | float: none; | |
634 | display: block; |
|
|||
635 | width: auto !important; |
|
|||
636 | } |
|
639 | } | |
637 |
|
640 | |||
638 | .tabular label.inline{ |
|
641 | .tabular label.inline{ | |
@@ -687,13 +690,14 span.required {color: #bb0000;} | |||||
687 | .check_box_group.bool_cf {border:0; background:inherit;} |
|
690 | .check_box_group.bool_cf {border:0; background:inherit;} | |
688 | .check_box_group.bool_cf label {display: inline;} |
|
691 | .check_box_group.bool_cf label {display: inline;} | |
689 |
|
692 | |||
690 |
|
|
693 | .attachments_fields input.description, #existing-attachments input.description {margin-left:4px; width:340px;} | |
691 |
|
|
694 | .attachments_fields>span, #existing-attachments>span {display:block; white-space:nowrap;} | |
692 |
|
|
695 | .attachments_fields input.filename, #existing-attachments .filename {border:0; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;} | |
693 | #attachments_fields input.filename {height:1.8em;} |
|
696 | .tabular input.filename {max-width:75% !important;} | |
694 | #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;} |
|
697 | .attachments_fields input.filename {height:1.8em;} | |
695 |
|
|
698 | .attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;} | |
696 | #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; } |
|
699 | .attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;} | |
|
700 | .attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; } | |||
697 | a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;} |
|
701 | a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;} | |
698 | a.remove-upload:hover {text-decoration:none !important;} |
|
702 | a.remove-upload:hover {text-decoration:none !important;} | |
699 | .existing-attachment.deleted .filename {text-decoration:line-through; color:#999 !important;} |
|
703 | .existing-attachment.deleted .filename {text-decoration:line-through; color:#999 !important;} | |
@@ -1160,7 +1164,7 a.close-icon:hover {background-image:url('../images/close_hl.png');} | |||||
1160 | padding-top: 0; |
|
1164 | padding-top: 0; | |
1161 | padding-bottom: 0; |
|
1165 | padding-bottom: 0; | |
1162 | font-size: 8px; |
|
1166 | font-size: 8px; | |
1163 |
vertical-align: |
|
1167 | vertical-align: middle; | |
1164 | } |
|
1168 | } | |
1165 | .icon-only::after { |
|
1169 | .icon-only::after { | |
1166 | content: " "; |
|
1170 | content: " "; |
General Comments 0
You need to be logged in to leave comments.
Login now