Serialization of attached documents is wrong #1601

This commit is contained in:
1redmine 2025-06-25 07:17:35 +02:00
parent 1b57325563
commit 138715dba2
10 changed files with 122 additions and 89 deletions

View File

@ -334,17 +334,13 @@ class DmsfFilesController < ApplicationController
end
def thumbnail
if @file.image?
tbnail = @file.thumbnail(size: params[:size])
if tbnail
if stale?(etag: tbnail)
send_file tbnail,
filename: filename_for_content_disposition(@file.last_revision.disk_file),
type: @file.last_revision.detect_content_type,
disposition: 'inline'
end
else
head :not_found
tbnail = @file.thumbnail(size: params[:size])
if tbnail
if stale?(etag: tbnail)
send_file tbnail,
filename: filename_for_content_disposition(@file.last_revision.disk_file),
type: @file.last_revision.detect_content_type,
disposition: 'inline'
end
else
head :not_found

View File

@ -499,7 +499,7 @@ class DmsfFile < ApplicationRecord
end
def thumbnailable?
image? && Redmine::Thumbnail.convert_available?
Redmine::Thumbnail.convert_available? && (image? || (pdf? && Redmine::Thumbnail.gs_available?))
end
def previewable?
@ -592,8 +592,6 @@ class DmsfFile < ApplicationRecord
end
def thumbnail(options = {})
return unless image?
size = options[:size].to_i
if size.positive?
# Limit the number of thumbnails per image
@ -606,7 +604,7 @@ class DmsfFile < ApplicationRecord
size = 100 unless size.positive?
target = File.join(Attachment.thumbnails_storage_path, "#{id}_#{last_revision.digest}_#{size}.thumb")
begin
Redmine::Thumbnail.generate last_revision.disk_file.to_s, target, size
Redmine::Thumbnail.generate last_revision.disk_file.to_s, target, size, pdf?
rescue StandardError => e
Rails.logger.error do
%(An error occured while generating thumbnail for #{last_revision.disk_file} to #{target}\n

View File

@ -67,7 +67,7 @@
<%= label_tag 'file_upload', l(:label_new_content) %>
<span class="dmsf-uploader">
<%= render partial: 'dmsf_upload/form',
locals: { multiple: false, container: nil, description: false, awf: false } %>
locals: { multiple: false, container: nil, awf: false } %>
</span>
</p>
<p>

View File

@ -27,7 +27,7 @@ var awf = false;
<% file = @dmsf_link.target_file %>
<% if file && !file.locked? && User.current.allowed_to?(:file_approval, file.project) %>
<% revision = file.last_revision %>
<% if revision && revision.workflow.nil? %>
<% if revision&.workflow.nil? %>
awf = true;
<% end %>
<% end %>

View File

@ -17,42 +17,53 @@
# <https://www.gnu.org/licenses/>.
%>
<span id="dmsf_attachments_fields">
<span class="dmsf-attachments-icons hidden">
<%= sprite_icon('del', icon_only: true, css_class: 'svg-del') %>
<%= sprite_icon('attachment', icon_only: true, size: 16, css_class: 'svg-attachment') %>
<%= sprite_icon('checked', icon_only: true, size: 16, css_class: 'svg-dmsf-assignment') %>
<%= sprite_icon('link', icon_only: true, size: 16, css_class: 'svg-dmsf-link') %>
</span>
<span id="dmsf_attachments_fields" class="attachments_fields">
<% if defined?(container) && container && container.saved_dmsf_attachments.present? %>
<% container.saved_dmsf_attachments.each_with_index do |attachment, i| %>
<% i += 1 %>
<span id="dmsf_attachments_p<%= i %>" class="attachment">
<%= hidden_field_tag "dmsf_attachments[p#{i}][token]", "#{attachment.token}" %>
<%= text_field_tag("dmsf_attachments[p#{i}][filename]", attachment.filename, class: 'filename') %>
<%= text_field_tag("dmsf_attachments[p#{i}][description]", attachment.description, maxlength: 255,
placeholder: l(:label_optional_description), class: 'description') if description %>
<%= link_to '', dmsf_attachment_path(attachment, attachment_id: "p#{i}", format: 'js'),
method: 'delete', remote: true, class: 'remove-upload icon-only icon-del' %>
<% wf = container.saved_dmsf_attachments_wfs[attachment.id] %>
<% if wf %>
<a href="javascript:void(0);" title="<%= l(:title_assigned) %>" class="icon-only icon-ok"></a>
<%= hidden_field_tag("dmsf_attachments_wfs[p#{i}]", wf.id) if wf %>
<% else %>
<%= link_to '', assign_dmsf_workflow_path(id: container.project.id, project_id: container.project.id,
attachment_id: i + 1), title: l(:label_dmsf_wokflow_action_assign),
remote: true, class: 'modify-upload icon-only icon-ok' %>
<% end %>
<%= hidden_field_tag "dmsf_attachments[p#{i}][token]", "#{attachment.token}" %>
<%= sprite_icon('attachment', icon_only: true, size: 16, css_class: 'svg-attachment') %>
<%= text_field_tag("dmsf_attachments[p#{i}][filename]", attachment.filename,
class: 'filename icon icon-attachment readonly') %>
<%= link_to sprite_icon('del', l(:button_delete), icon_only: true),
dmsf_attachment_path(attachment, attachment_id: "p#{i}", format: 'js'),
method: 'delete', remote: true, class: 'remove-upload icon-only icon-del' %>
<% wf = container.saved_dmsf_attachments_wfs[attachment.id] %>
<% if wf %>
<%= link_to sprite_icon('checked', l(:title_assigned), icon_only: true), '#', remote: true,
class: 'modify-upload icon-only icon-ok' %>
<%= hidden_field_tag("dmsf_attachments_wfs[p#{i}]", wf.id) %>
<% else %>
<%= link_to sprite_icon('checked', l(:title_assignment), icon_only: true),
assign_dmsf_workflow_path(id: container.project.id, project_id: container.project.id,
attachment_id: i + 1), title: l(:label_dmsf_wokflow_action_assign),
remote: true, class: 'modify-upload icon-only icon-ok' %>
<% end %>
</span>
<% end %>
<% end %>
</span>
<span id="dmsf_links_attachments_fields">
<% if defined?(container) && container && container.saved_dmsf_links.present? %>
<% container.saved_dmsf_links.each_with_index do |dmsf_link, index| %>
<span id="dmsf_links_attachments_<%= index %>" class="attachment">
<input name="dmsf_links[<%= index %>]" value="<%= dmsf_link.id %>" type="hidden">
<input type="text" class='filename readonly' value="<%= dmsf_link.name %>">
<%= link_to '', dmsf_link_attachment_path(dmsf_link, link_id: "#{index}", :format => 'js'),
method: 'delete', remote: true, class: 'remove-upload icon-only icon-del' %>
<span id="dmsf_links_attachments_fields" class="attachments_fields">
<% if defined?(container) && container && container.saved_dmsf_links.present? %>
<% container.saved_dmsf_links.each_with_index do |dmsf_link, index| %>
<span id="dmsf_links_attachments_<%= index %>" class="attachment">
<input name="dmsf_links[<%= index %>]" value="<%= dmsf_link.id %>" type="hidden">
<input type="text" class='filename readonly' value="<%= dmsf_link.name %>">
<%= link_to '', dmsf_link_attachment_path(dmsf_link, link_id: "#{index}", format: 'js'),
method: 'delete', remote: true, class: 'remove-upload icon-only icon-del' %>
<% wf = container.saved_dmsf_links_wfs[dmsf_link.id] %>
<% if wf %>
<a href="javascript:void(0);" title="<%= l(:title_assigned) %>" class="modify-upload icon-only icon-ok"></a>
<%= hidden_field_tag("dmsf_links_wfs[#{dmsf_link.id}]", wf.id) if wf %>
<%= link_to sprite_icon('checked', l(:title_assigned), icon_only: true), '#', remote: true,
class: 'modify-upload icon-only icon-ok' %>
<%= hidden_field_tag("dmsf_links_wfs[#{dmsf_link.id}]", wf.id) %>
<% else %>
<%= render partial: 'dmsf_workflows/approval_workflow_button',
locals: { file: dmsf_link.target_file,
@ -61,9 +72,9 @@
['project_id = ? OR project_id IS NULL', dmsf_link.target_file.project_id]).exists?,
project: dmsf_link.target_file.project, wf: wf, dmsf_link_id: dmsf_link.id } %>
<% end %>
</span>
<% end %>
<% end %>
</span>
<% end %>
<% end %>
</span>
<span class="dmsf_add_attachment add_attachment">
@ -97,7 +108,6 @@
max_size: number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)),
max_concurrent_uploads: Redmine::Configuration['max_concurrent_ajax_uploads'].to_i,
upload_path: dmsf_uploads_path(format: 'js'),
description_placeholder: l(:label_optional_description),
project: project.identifier,
awf: awf,
dmsf_file_details_form: controller.send(:render_to_string,

View File

@ -17,15 +17,13 @@
# <https://www.gnu.org/licenses/>.
%>
let input;
<% if @dmsf_link_id %>
input = $('input[value="<%= @dmsf_link_id %>"]');
var input = $('input[value="<%= @dmsf_link_id %>"]');
<% else %>
input = $('input[name="dmsf_attachments[<%= @attachment_id %>][token]"]');
var input = $('input[name="dmsf_attachments[<%= @attachment_id %>][token]"]');
<% end %>
let span = input.parent();
var span = input.parent();
<% if @dmsf_link_id %>
span.append(
@ -35,7 +33,7 @@ let span = input.parent();
"<input name='dmsf_attachments_wfs[<%= @attachment_id %>]' value='<%= @dmsf_workflow_id %>' type='hidden'>");
<% end %>
let a = span.children("a.icon-ok");
var a = span.children("a.icon-ok");
a.attr("href", "javascript:void(0);");
a.attr("href", "#");
a.attr("title", "<%= l(:title_assigned) %>");

View File

@ -18,17 +18,25 @@
*/
function dmsfAddLink(linksSpan, linkId, linkName, title, project, awf) {
let attachmentsForm = linksSpan.closest('.dmsf-uploader')
let attachmentsIcons = attachmentsForm.find('.dmsf-attachments-icons');
let delIcon = attachmentsIcons.find('svg.svg-del').clone();
let linkIcon = attachmentsIcons.find('svg.svg-dmsf-link').clone();
let assignmentIcon = attachmentsIcons.find('svg.svg-dmsf-assignment').clone();
let nextLinkId = dmsfAddLink.nextLinkId++;
let linkSpan = $('<span>', { id: 'dmsf_links_attachments_' + nextLinkId, 'class': 'attachment' });
let iconDel = $('<a>').attr({href: '#', 'class': 'remove-upload icon-only icon-del'});
let iconDel = $('<a>')
.attr({href: '#', class: 'remove-upload icon-only icon-del', title: 'Delete'})
.append(delIcon);
let inputId = $('<input>', {type: 'hidden', name: 'dmsf_links[' + nextLinkId + ']'}).val(linkId);
let inputName = $('<input>', {type: 'text', class: 'filename readonly'}).val(linkName);
let inputName = $('<input>', {type: 'text', class: 'filename icon icon-link readonly'}).val(linkName);
linkSpan.append(inputId);
linkSpan.append(linkIcon);
linkSpan.append(inputName);
linkSpan.append(iconDel.click(dmsfRemoveFileLbl));
if(awf) {
let iconWf = $('<a>').attr({href: "/dmsf-workflows/" + project + "/assign?dmsf_link_id=" + linkId,
'class': 'modify-upload icon-only icon-ok', 'data-remote': 'true', 'title': title});
let iconWf = $('<a>').attr({href: "/dmsf_workflows/" + project + "/assign?dmsf_link_id=" + linkId,
'class': 'modify-upload icon-only icon-ok', 'data-remote': 'true', 'title': title}).append(assignmentIcon);
linkSpan.append(iconWf);
}
linksSpan.append(linkSpan);
@ -137,41 +145,50 @@ function replaceVersion(detailsForm, attachmentId, name, version) {
return detailsForm;
}
function dmsfRevisionDetails(elem, attachmentId) {
let newRevisionForm = $('#dmsf_attachments_details_' + attachmentId);
newRevisionForm.toggle();
elem.text("[" + (newRevisionForm.is(':visible') ? "-" : "+") + "]");
}
function dmsfAddFile(inputEl, file, eagerUpload) {
let attachments = $('#dmsf_attachments_fields');
let max = ($(inputEl).attr('multiple') == 'multiple') ? 10 : 1
let attachmentsForm = $(inputEl).closest('.dmsf-uploader')
let attachmentsIcons = attachmentsForm.find('.dmsf-attachments-icons');
let delIcon = attachmentsIcons.find('svg.svg-del').clone();
let attachmentIcon = attachmentsIcons.find('svg.svg-attachment').clone();
let assignmentIcon = attachmentsIcons.find('svg.svg-dmsf-assignment').clone();
if (attachments.children('.attachment').length < max) {
let attachmentId = dmsfAddFile.nextAttachmentId++;
let fileSpan = $('<span>', { id: 'dmsf_attachments_' + attachmentId, 'class': 'attachment' });
let iconDel = $('<a>').attr({href: '#', 'class': 'remove-upload icon-only icon-del'}).toggle(!eagerUpload);
let fileName = $('<input>', {type: 'text', 'class': 'filename readonly',
let iconDel = $('<a>')
.attr({href: '#', class: 'remove-upload icon-only icon-del', title: 'Delete'})
.append(delIcon)
.toggle(!eagerUpload);
let fileName = $('<input>', {type: 'text', 'class': 'filename icon icon-attachment readonly',
name: 'dmsf_attachments[' + attachmentId + '][filename]', readonly: 'readonly'}).val(file.name);
fileSpan.append(attachmentIcon);
fileSpan.append(fileName);
if($(inputEl).attr('multiple') == 'multiple') {
fileSpan.append(iconDel.click(dmsfRemoveFileLbl));
if ($(inputEl).data('awf')) {
let iconWf = $('<a>').attr({
href: '/dmsf-workflows/' + $(inputEl).attr(
href: '/dmsf_workflows/' + $(inputEl).attr(
'data-project') + "/assign?attachment_id=" + attachmentId,
'class': 'modify-upload icon-only icon-ok',
'data-remote': 'true'
});
class: 'modify-upload icon-only icon-ok',
'data-remote': 'true',
title: 'Assign an approval workflow'
}).append(assignmentIcon);
fileSpan.append(iconWf);
}
// Details
let detailsDiv = $('<div>').attr({id: 'dmsf_attachments_details_' + attachmentId});
let detailsArrow = $('<a>');
detailsArrow.text('[+]');
detailsArrow.attr({href: "#", 'data-cy': 'toggle__new_revision_from_content--dmsf', title: 'Details'});
detailsArrow.attr(
{
onclick: "let newRevisionForm = $('#dmsf_attachments_details_" + attachmentId + "');" +
"let operator = newRevisionForm.is(':visible') ? '+' : '-';" +
"newRevisionForm.toggle();" +
"$(this).text('[' + operator + ']');" +
"$('#dmsf-upload-button').hide();" +
"return false;"
});
detailsArrow.attr({href: "#", 'data-cy': 'toggle__new_revision_from_content--dmsf', title: 'Details',
class: 'dmsf-plus-button'});
detailsArrow.attr('onclick', "dmsfRevisionDetails($(this), " + attachmentId + "); return false;");
let files = $(inputEl).data('files');
let locked = isFileLocked(file.name, files);
let detailsForm = $(inputEl).data(locked ? 'dmsf-file-details-form-locked' : 'dmsf-file-details-form');
@ -214,7 +231,6 @@ function dmsfAddFile(inputEl, file, eagerUpload) {
attachments.append(fileSpan);
$('#dmsf_file_revision_name').val(file.name);
}
attachments.append('<br>');
if(eagerUpload) {
dmsfAjaxUpload(file, attachmentId, fileSpan, inputEl);
}

View File

@ -189,6 +189,10 @@ div[id*="revision_access_"] {
color: gray;
}
.dmsf-gray svg {
stroke: grey;
}
svg.dmsf-gray {
stroke: grey;
}
@ -286,7 +290,10 @@ span.fileover {
width: 250px;
color: #555;
background-color: inherit;
padding-left: 18px;
}
.dmsf-plus-button {
vertical-align: middle;
}
#dmsf_attachments_fields div.ui-progressbar {
@ -307,7 +314,11 @@ span.fileover {
width: 250px;
color: #555;
background-color: inherit;
padding-left: 18px;
}
.attachments_fields .icon-link {
background-image: none;
padding-left: 0;
}
.dmfs-box-tabular {

View File

@ -157,7 +157,7 @@ module RedmineDmsf
# Attach DMS documents
uploaded_files = params[:dmsf_attachments]
details = params[:committed_files]
if uploaded_files && details
if uploaded_files
system_folder = issue.system_folder(create: true)
uploaded_files.each do |key, uploaded_file|
upload = DmsfUpload.create_from_uploaded_attachment(issue.project, system_folder, uploaded_file)
@ -166,11 +166,16 @@ module RedmineDmsf
uploaded_file[:disk_filename] = upload.disk_filename
uploaded_file[:name] = upload.name
uploaded_file[:title] = upload.title
uploaded_file[:description] = details[key][:description]
uploaded_file[:comment] = details[key][:comment]
uploaded_file[:version_major] = details[key][:version_major]
uploaded_file[:version_minor] = details[key][:version_minor]
uploaded_file[:version_patch] = details[key][:version_patch]
if details
uploaded_file[:description] = details[key][:description]
uploaded_file[:comment] = details[key][:comment]
uploaded_file[:version_major] = details[key][:version_major]
uploaded_file[:version_minor] = details[key][:version_minor]
uploaded_file[:version_patch] = details[key][:version_patch]
else
uploaded_file[:version_major] = 0
uploaded_file[:version_minor] = 1
end
uploaded_file[:size] = upload.size
uploaded_file[:mime_type] = upload.mime_type
uploaded_file[:tempfile_path] = upload.tempfile_path
@ -178,7 +183,7 @@ module RedmineDmsf
if params[:dmsf_attachments_wfs].present? && params[:dmsf_attachments_wfs][key].present?
uploaded_file[:workflow_id] = params[:dmsf_attachments_wfs][key].to_i
end
uploaded_file[:custom_field_values] = details[key][:custom_field_values]
uploaded_file[:custom_field_values] = details[key][:custom_field_values] if details
end
DmsfUploadHelper.commit_files_internal uploaded_files, issue.project, system_folder, context[:controller],
issue, new_object: @new_object

View File

@ -154,14 +154,14 @@ module RedmineDmsf
link_to: false } }
end
def attach_documents_form(context, label: true, description: true)
def attach_documents_form(context, label: true)
return unless context.is_a?(Hash) && context[:container]
# Add Dmsf upload form
container = context[:container]
return unless allowed_to_attach_documents(container)
html = description ? +'<p' : +'<div'
html = +'<p'
if User.current.pref.dmsf_attachments_upload_choice == 'Attachments' &&
allowed_to_attach_attachments(container)
html << ' style="display: none;"'
@ -177,10 +177,9 @@ module RedmineDmsf
html << context[:controller].send(:render_to_string, { partial: 'dmsf_upload/form',
locals: { container: container,
multiple: true,
description: description,
awf: false } })
awf: true } })
html << '</span>'
html << (description ? '</p>' : '</div>')
html << '</p>'
html
end