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:)