mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
113 lines
3.6 KiB
113 lines
3.6 KiB
# frozen_string_literal: true
|
|
|
|
module Submissions
|
|
module GenerateCombinedAttachment
|
|
module_function
|
|
|
|
def call(submitter)
|
|
pdf = build_combined_pdf(submitter)
|
|
|
|
submission = submitter.submission
|
|
account = submission.account
|
|
|
|
pkcs = Accounts.load_signing_pkcs(account)
|
|
tsa_url = Accounts.load_timeserver_url(account)
|
|
|
|
io = StringIO.new
|
|
|
|
pdf.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"
|
|
|
|
if Docuseal.pdf_format == 'pdf/a-3b'
|
|
pdf.task(:pdfa, level: '3b')
|
|
pdf.config['font.map'] = GenerateResultAttachments::PDFA_FONT_MAP
|
|
end
|
|
|
|
# Always create a custom signature field to replace the default watermark, regardless of formal signing
|
|
sig_field = pdf.acro_form(create: true).create_signature_field("DocuSealSignature-#{SecureRandom.hex(4)}")
|
|
# The widget is placed on the page, but the appearance stream is what matters.
|
|
# We make it very small and out of the way.
|
|
widget = sig_field.create_widget(pdf.pages.first, Rect: [0, 0, 5, 5])
|
|
appearance = widget.create_appearance
|
|
canvas = appearance.canvas
|
|
logo = submitter.account.logo
|
|
|
|
if logo.attached?
|
|
logo.blob.open do |tempfile|
|
|
canvas.image(tempfile.path, at: [2, 1], height: 38)
|
|
end
|
|
end
|
|
|
|
pdf.pages.first[:Annots] = [] unless pdf.pages.first[:Annots].respond_to?(:<<)
|
|
|
|
if pkcs
|
|
sign_params = {
|
|
reason: Submissions::GenerateResultAttachments.single_sign_reason(submitter),
|
|
signature: sig_field,
|
|
**Submissions::GenerateResultAttachments.build_signing_params(submitter, pkcs, tsa_url)
|
|
}
|
|
|
|
sign_pdf(io, pdf, sign_params)
|
|
|
|
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
|
|
else
|
|
# Even without formal signing, use the custom signature field to prevent default watermark
|
|
begin
|
|
pdf.sign(io, signature: sig_field)
|
|
rescue StandardError => e
|
|
# Fallback to regular write if signing fails
|
|
Rollbar.error(e) if defined?(Rollbar)
|
|
pdf.write(io, incremental: true, validate: true)
|
|
end
|
|
end
|
|
|
|
ActiveStorage::Attachment.create!(
|
|
blob: ActiveStorage::Blob.create_and_upload!(
|
|
io: io.tap(&:rewind), filename: "#{submission.name || submission.template.name}.pdf"
|
|
),
|
|
name: 'combined_document',
|
|
record: submission
|
|
)
|
|
end
|
|
|
|
def sign_pdf(io, pdf, sign_params)
|
|
pdf.sign(io, **sign_params)
|
|
rescue HexaPDF::MalformedPDFError => e
|
|
Rollbar.error(e) if defined?(Rollbar)
|
|
|
|
pdf.sign(io, write_options: { incremental: false }, **sign_params)
|
|
rescue HexaPDF::Error => e
|
|
Rollbar.error(e) if defined?(Rollbar)
|
|
|
|
pdf.validate(auto_correct: true)
|
|
|
|
pdf.sign(io, write_options: { validate: false }, **sign_params)
|
|
end
|
|
|
|
def build_combined_pdf(submitter)
|
|
pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter)
|
|
|
|
audit_trail = I18n.with_locale(submitter.account.locale) do
|
|
Submissions::GenerateAuditTrail.build_audit_trail(submitter.submission)
|
|
end
|
|
|
|
audit_trail.dispatch_message(:complete_objects)
|
|
|
|
result = HexaPDF::Document.new
|
|
|
|
submitter.submission.template_schema.each do |item|
|
|
pdf = pdfs_index[item['attachment_uuid']]
|
|
|
|
next unless pdf
|
|
|
|
pdf.dispatch_message(:complete_objects)
|
|
|
|
pdf.pages.each { |page| result.pages << result.import(page) }
|
|
end
|
|
|
|
audit_trail.pages.each { |page| result.pages << result.import(page) }
|
|
|
|
result
|
|
end
|
|
end
|
|
end
|