diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index cd2339f4..ad295068 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -660,7 +660,7 @@ export default { acceptFileTypes: { type: String, required: false, - default: 'image/*, application/pdf' + default: 'image/*, application/pdf, application/zip' }, baseUrl: { type: String, diff --git a/app/javascript/template_builder/controls.vue b/app/javascript/template_builder/controls.vue index 270aa623..2196fe56 100644 --- a/app/javascript/template_builder/controls.vue +++ b/app/javascript/template_builder/controls.vue @@ -66,7 +66,7 @@ export default { acceptFileTypes: { type: String, required: false, - default: 'image/*, application/pdf' + default: 'image/*, application/pdf, application/zip' }, withReplaceButton: { type: Boolean, diff --git a/app/javascript/template_builder/dropzone.vue b/app/javascript/template_builder/dropzone.vue index 9031f3d1..ff30e756 100644 --- a/app/javascript/template_builder/dropzone.vue +++ b/app/javascript/template_builder/dropzone.vue @@ -107,7 +107,7 @@ export default { acceptFileTypes: { type: String, required: false, - default: 'image/*, application/pdf' + default: 'image/*, application/pdf, application/zip' } }, emits: ['success', 'error', 'loading'], @@ -131,7 +131,7 @@ export default { message () { if (this.isLoading) { return this.t('uploading') - } else if (this.acceptFileTypes === 'image/*, application/pdf') { + } else if (this.acceptFileTypes === 'image/*, application/pdf, application/zip') { return this.title || this.t('add_pdf_documents_or_images') } else { return this.title || this.t('add_documents_or_images') @@ -146,7 +146,7 @@ export default { methods: { upload: Upload.methods.upload, onDropFiles (e) { - if (this.acceptFileTypes !== 'image/*, application/pdf' || [...e.dataTransfer.files].every((f) => f.type.match(/(?:image\/)|(?:application\/pdf)/))) { + if (this.acceptFileTypes !== 'image/*, application/pdf, application/zip' || [...e.dataTransfer.files].every((f) => f.type.match(/(?:image\/)|(?:application\/pdf)|(?:application\/zip)/))) { this.$refs.input.files = e.dataTransfer.files this.upload() diff --git a/app/javascript/template_builder/hover_dropzone.vue b/app/javascript/template_builder/hover_dropzone.vue index 575c832c..28a07281 100644 --- a/app/javascript/template_builder/hover_dropzone.vue +++ b/app/javascript/template_builder/hover_dropzone.vue @@ -78,7 +78,7 @@ export default { acceptFileTypes: { type: String, required: false, - default: 'image/*, application/pdf' + default: 'image/*, application/pdf, application/zip' } }, emits: ['add', 'replace', 'replace-and-clone', 'error'], diff --git a/app/javascript/template_builder/preview.vue b/app/javascript/template_builder/preview.vue index 7a60d71e..c83f58f7 100644 --- a/app/javascript/template_builder/preview.vue +++ b/app/javascript/template_builder/preview.vue @@ -157,7 +157,7 @@ export default { acceptFileTypes: { type: String, required: false, - default: 'image/*, application/pdf' + default: 'image/*, application/pdf, application/zip' }, withReplaceButton: { type: Boolean, diff --git a/app/javascript/template_builder/replace.vue b/app/javascript/template_builder/replace.vue index 6a467206..bc72b4f0 100644 --- a/app/javascript/template_builder/replace.vue +++ b/app/javascript/template_builder/replace.vue @@ -35,7 +35,7 @@ export default { acceptFileTypes: { type: String, required: false, - default: 'image/*, application/pdf' + default: 'image/*, application/pdf, application/zip' } }, emits: ['success'], diff --git a/app/javascript/template_builder/upload.vue b/app/javascript/template_builder/upload.vue index a502ff3e..bdb633ce 100644 --- a/app/javascript/template_builder/upload.vue +++ b/app/javascript/template_builder/upload.vue @@ -57,7 +57,7 @@ export default { acceptFileTypes: { type: String, required: false, - default: 'image/*, application/pdf' + default: 'image/*, application/pdf, application/zip' } }, emits: ['success', 'error'], diff --git a/app/views/templates/_dropzone.html.erb b/app/views/templates/_dropzone.html.erb index 124efa83..2569aa7d 100644 --- a/app/views/templates/_dropzone.html.erb +++ b/app/views/templates/_dropzone.html.erb @@ -23,7 +23,7 @@ - + " multiple> diff --git a/app/views/templates/_upload_button.html.erb b/app/views/templates/_upload_button.html.erb index 12f36df0..40460070 100644 --- a/app/views/templates/_upload_button.html.erb +++ b/app/views/templates/_upload_button.html.erb @@ -14,6 +14,6 @@ - + " multiple> <% end %> diff --git a/lib/templates/create_attachments.rb b/lib/templates/create_attachments.rb index 22d84e2f..aeb65e9c 100644 --- a/lib/templates/create_attachments.rb +++ b/lib/templates/create_attachments.rb @@ -3,6 +3,19 @@ module Templates module CreateAttachments PDF_CONTENT_TYPE = 'application/pdf' + ZIP_CONTENT_TYPE = 'application/zip' + JSON_CONTENT_TYPE = 'application/json' + DOCUMENT_EXTENSIONS = %w[.docx .doc .xlsx .xls .odt .rtf].freeze + + DOCUMENT_CONTENT_TYPES = %w[ + application/vnd.openxmlformats-officedocument.wordprocessingml.document + application/msword + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + application/vnd.ms-excel + application/vnd.oasis.opendocument.text + application/rtf + ].freeze + ANNOTATIONS_SIZE_LIMIT = 6.megabytes InvalidFileType = Class.new(StandardError) PdfEncrypted = Class.new(StandardError) @@ -10,7 +23,7 @@ module Templates module_function def call(template, params, extract_fields: false) - Array.wrap(params[:files].presence || params[:file]).map do |file| + extract_zip_files(params[:files].presence || params[:file]).flat_map do |file| handle_file_types(template, file, params, extract_fields:) end end @@ -53,6 +66,40 @@ module Templates raise PdfEncrypted end + def extract_zip_files(files) + extracted_files = [] + + Array.wrap(files).each do |file| + if file.content_type == ZIP_CONTENT_TYPE + Zip::File.open(file.tempfile).each do |entry| + next if entry.directory? + + tempfile = Tempfile.new(entry.name) + tempfile.binmode + entry.get_input_stream { |in_stream| IO.copy_stream(in_stream, tempfile) } + tempfile.rewind + + type = Marcel::MimeType.for(tempfile, name: entry.name) + + next if type.exclude?('image') && + type != PDF_CONTENT_TYPE && + type != JSON_CONTENT_TYPE && + DOCUMENT_CONTENT_TYPES.exclude?(type) + + extracted_files << ActionDispatch::Http::UploadedFile.new( + filename: File.basename(entry.name), + type:, + tempfile: + ) + end + else + extracted_files << file + end + end + + extracted_files + end + def handle_file_types(template, file, params, extract_fields:) if file.content_type.include?('image') || file.content_type == PDF_CONTENT_TYPE return handle_pdf_or_image(template, file, file.read, params, extract_fields:)