diff --git a/Dockerfile b/Dockerfile index ee883b70d..e7d53cf8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf WORKDIR /app -RUN apk add --no-cache libpq vips redis onnxruntime leptonica && \ +RUN apk add --no-cache libpq vips redis onnxruntime leptonica jemalloc font-liberation font-carlito && \ rm -f /usr/bin/onnx_test_runner /usr/bin/onnxruntime_test RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal @@ -91,7 +91,7 @@ COPY --chown=docuseal:docuseal --from=download /model.onnx /app/tmp/model.onnx COPY --chown=docuseal:docuseal --from=webpack /app/public/packs ./public/packs RUN mkdir -p /app/public/fonts && ln -s /fonts/DancingScript-Regular.otf /app/public/fonts/ && \ - mkdir -p /usr/share/fonts/noto && ln -s /fonts/GoNotoKurrent-Regular.ttf /usr/share/fonts/noto/ && ln -s /fonts/GoNotoKurrent-Bold.ttf /usr/share/fonts/noto/ && fc-cache -f && \ + mkdir -p /usr/share/fonts/noto/bold && ln -s /fonts/GoNotoKurrent-Regular.ttf /usr/share/fonts/noto/ && ln -s /fonts/GoNotoKurrent-Bold.ttf /usr/share/fonts/noto/bold/ && fc-cache -f && \ bundle exec bootsnap precompile -j 1 --gemfile app/ lib/ && \ chown -R docuseal:docuseal /app/tmp/cache @@ -100,6 +100,8 @@ ENV HOME=/home/docuseal ENV WORKDIR=/data/docuseal ENV VIPS_MAX_COORD=17000 ENV VIPS_BLOCK_UNTRUSTED=1 +# ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2 +ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true EXPOSE 3000 CMD ["/app/bin/bundle", "exec", "puma", "-C", "/app/config/puma.rb", "--dir", "/app"] diff --git a/Gemfile b/Gemfile index 99e771b98..fc482901b 100644 --- a/Gemfile +++ b/Gemfile @@ -45,6 +45,7 @@ gem 'trilogy', require: false gem 'turbo-rails' gem 'twitter_cldr', require: false gem 'tzinfo-data' +gem 'wordcraft', require: false, group: :ce gem 'xlsxtream', require: false group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index e62707472..ee3d8e886 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -572,6 +572,16 @@ GEM base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) + wordcraft (0.8.0-aarch64-linux-gnu) + ffi (>= 1.15) + wordcraft (0.8.0-aarch64-linux-musl) + ffi (>= 1.15) + wordcraft (0.8.0-arm64-darwin) + ffi (>= 1.15) + wordcraft (0.8.0-x86_64-linux-gnu) + ffi (>= 1.15) + wordcraft (0.8.0-x86_64-linux-musl) + ffi (>= 1.15) xlsxtream (3.1.0) zip_kit (>= 6.2, < 7) xpath (3.2.0) @@ -650,6 +660,7 @@ DEPENDENCIES tzinfo-data web-console webmock + wordcraft xlsxtream RUBY VERSION diff --git a/app/controllers/api/attachments_controller.rb b/app/controllers/api/attachments_controller.rb index 2bb0db922..95c3f6c18 100644 --- a/app/controllers/api/attachments_controller.rb +++ b/app/controllers/api/attachments_controller.rb @@ -38,7 +38,7 @@ module Api attachment = Submitters.create_attachment!(@submitter, file, metadata:) if params[:remember_signature] == 'true' && @submitter.email.present? - cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment) + cookies.permanent.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment) end render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type]) diff --git a/app/controllers/submit_form_upload_controller.rb b/app/controllers/submit_form_upload_controller.rb index e25f42207..99905703c 100644 --- a/app/controllers/submit_form_upload_controller.rb +++ b/app/controllers/submit_form_upload_controller.rb @@ -34,7 +34,7 @@ class SubmitFormUploadController < ActionController::API attachment = Submitters.create_attachment!(@submitter, file, metadata:) if params[:remember_signature] == 'true' && @submitter.email.present? - cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment) + cookies.permanent.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment) end render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type]) diff --git a/app/controllers/template_sharings_testing_controller.rb b/app/controllers/template_sharings_testing_controller.rb index c8f646962..7c642f7d9 100644 --- a/app/controllers/template_sharings_testing_controller.rb +++ b/app/controllers/template_sharings_testing_controller.rb @@ -8,7 +8,9 @@ class TemplateSharingsTestingController < ApplicationController end def create - testing_account = Accounts.find_or_create_testing_user(true_user.account).account + account = can?(:manage, :tenants_test) ? current_account : true_user.account + + testing_account = Accounts.find_or_create_testing_user(account).account if params[:value] == '1' TemplateSharing.create!(ability: :manage, account: testing_account, template: @template) diff --git a/app/javascript/elements/native_modal.js b/app/javascript/elements/native_modal.js index e412c5af4..9e11c52df 100644 --- a/app/javascript/elements/native_modal.js +++ b/app/javascript/elements/native_modal.js @@ -22,7 +22,7 @@ export default class extends HTMLElement { } async flashFrom (fetchResponse) { - const html = await fetchResponse?.responseHTML + const html = await fetchResponse?.responseHTML.catch(() => null) const flash = html && new DOMParser().parseFromString(html, 'text/html').getElementById('native_flash') if (flash?.dataset.alert) return { alert: flash.dataset.alert } diff --git a/app/javascript/submission_form/kba_step.vue b/app/javascript/submission_form/kba_step.vue index 27026d558..4f5c456b6 100644 --- a/app/javascript/submission_form/kba_step.vue +++ b/app/javascript/submission_form/kba_step.vue @@ -3,11 +3,19 @@ v-if="!error" class="label text-xl sm:text-2xl py-0 mb-2 sm:mb-3.5 field-name-label" > - - + + + + + + + import MarkdownContent from './markdown_content' -import { IconInnerShadowTop } from '@tabler/icons-vue' +import { IconInnerShadowTop, IconInfoCircle } from '@tabler/icons-vue' export default { name: 'KbaStep', components: { MarkdownContent, - IconInnerShadowTop + IconInnerShadowTop, + IconInfoCircle }, inject: ['baseUrl', 't'], props: { diff --git a/app/javascript/template_builder/dynamic_editor.js b/app/javascript/template_builder/dynamic_editor.js index 85c9acd94..cad495254 100644 --- a/app/javascript/template_builder/dynamic_editor.js +++ b/app/javascript/template_builder/dynamic_editor.js @@ -27,13 +27,17 @@ tiptapStylesheet.replaceSync( font-feature-settings: "liga" 0; display: flex; flex-flow: column nowrap; - min-height: inherit; + flex-grow: 1; } -.ProseMirror > article { +.ProseMirror > article:last-of-type { margin-bottom: auto; } +.ProseMirror p.doc-trailing-break::after { + content: none; +} + .ProseMirror [contenteditable="false"] { white-space: normal; } diff --git a/app/views/shared/_native_navbar.html.erb b/app/views/shared/_native_navbar.html.erb index 997f6492f..4eb16702b 100644 --- a/app/views/shared/_native_navbar.html.erb +++ b/app/views/shared/_native_navbar.html.erb @@ -22,13 +22,13 @@ <% end %> <% if can?(:create, ::Template) && controller_name.in?(%w[templates_dashboard submissions_dashboard template_folders templates_archived submissions_archived]) %> <% folder_name = @template_folder&.full_name %> - "> + <%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %> <% unless controller_name.in?(%w[templates_archived submissions_archived]) %> - "> + <%= link_to t('create'), new_template_path(folder_name: folder_name), data: { turbo_frame: :modal, native_modal: :medium } %> diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb index ae8e9d29d..0dc5a83ef 100644 --- a/app/views/shared/_settings_nav.html.erb +++ b/app/views/shared/_settings_nav.html.erb @@ -93,14 +93,14 @@ <% end %> <% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %> - <% if Docuseal.multitenant? || !turbo_native_ios? %> + <% unless turbo_native_app? %>
  • <%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %> <% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %> <% end %>
  • <% end %> - <% if Docuseal.multitenant? %> + <% if Docuseal.multitenant? && !turbo_native_app? %>
  • <%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %> <%= t('embedding') %> diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index f9728ce06..e749af450 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -227,39 +227,39 @@ <% if submitter&.name.present? %>
    - <%= svg_icon('user', class: 'w-5 h-5') %> - + <%= svg_icon('user', class: 'w-5 h-5 shrink-0') %> + <%= submitter&.name %>
    <% end %> <% if submitter&.email.present? %>
    - <%= svg_icon('mail', class: 'w-5 h-5') %> - + <%= svg_icon('mail', class: 'w-5 h-5 shrink-0') %> + <%= submitter.email || 'N/A' %>
    <% end %> <% if submitter&.phone.present? %>
    - <%= svg_icon('phone', class: 'w-5 h-5') %> - + <%= svg_icon('phone', class: 'w-5 h-5 shrink-0') %> + <%= submitter.phone %>
    <% end %>
    <% if submitter&.viewer? %> - <%= svg_icon(submitter.opened_at? ? 'file_check' : 'file_unknown', class: 'w-5 h-5') %> + <%= svg_icon(submitter.opened_at? ? 'file_check' : 'file_unknown', class: 'w-5 h-5 shrink-0') %> <% elsif submitter && !submitter.completed_at? && @submission.completed_at? %> - <%= svg_icon('writing_off', class: 'w-5 h-5') %> + <%= svg_icon('writing_off', class: 'w-5 h-5 shrink-0') %> <% elsif @submission.expire_at? && submitter && !submitter.completed_at? %> - <%= svg_icon('clock_exclamation', class: 'w-5 h-5') %> + <%= svg_icon('clock_exclamation', class: 'w-5 h-5 shrink-0') %> <% else %> - <%= svg_icon('writing', class: 'w-5 h-5') %> + <%= svg_icon('writing', class: 'w-5 h-5 shrink-0') %> <% end %> - + <% if submitter&.declined_at? %> <%= t('declined_on_time', time: l(submitter.declined_at.in_time_zone(@submission.account.timezone), format: :short, locale: @submission.account.locale)) %> <% elsif submitter&.viewer? %> diff --git a/app/views/templates/_dropzone.html.erb b/app/views/templates/_dropzone.html.erb index 855823781..cdc4bf4fc 100644 --- a/app/views/templates/_dropzone.html.erb +++ b/app/views/templates/_dropzone.html.erb @@ -31,7 +31,7 @@
    - " multiple> + diff --git a/app/views/templates/_upload_button.html.erb b/app/views/templates/_upload_button.html.erb index dbf104cb3..095464db3 100644 --- a/app/views/templates/_upload_button.html.erb +++ b/app/views/templates/_upload_button.html.erb @@ -16,7 +16,7 @@ - " multiple> + <% end %> diff --git a/app/views/templates/edit.html.erb b/app/views/templates/edit.html.erb index 6c527eb21..65e5739e0 100644 --- a/app/views/templates/edit.html.erb +++ b/app/views/templates/edit.html.erb @@ -10,4 +10,4 @@ <% account_configs = current_account.account_configs.where(key: [AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY, AccountConfig::TEMPLATE_DATE_FORMATS_KEY]).to_a %> <% custom_fields = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY }&.value || [] %> <% date_formats = account_configs.find { |e| e.key == AccountConfig::TEMPLATE_DATE_FORMATS_KEY }&.value || [] %> - + diff --git a/app/views/templates_code_modal/show.html.erb b/app/views/templates_code_modal/show.html.erb index c7bce02fd..0c72d6dd2 100644 --- a/app/views/templates_code_modal/show.html.erb +++ b/app/views/templates_code_modal/show.html.erb @@ -26,7 +26,7 @@ <%= t('share_template_with_test_mode') %> - <%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: true_user.account.testing_accounts) %> + <%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: (can?(:manage, :tenants_test) ? current_account : true_user.account).testing_accounts) %> <% end %> diff --git a/app/views/templates_dashboard/index.html.erb b/app/views/templates_dashboard/index.html.erb index d54056c5e..4a65f5cd6 100644 --- a/app/views/templates_dashboard/index.html.erb +++ b/app/views/templates_dashboard/index.html.erb @@ -127,4 +127,4 @@ <%= render 'shared/pagination', pagy: @related_submissions_pagy, items_name: 'submissions', next_page_path: submissions_path(q: params[:q]) %> <% end %> <% end %> -<%= render 'shared/review_form' %> +<%= render 'shared/review_form' unless turbo_native_app? %> diff --git a/app/views/templates_preferences/show.html.erb b/app/views/templates_preferences/show.html.erb index 91b0512b2..f6df9d583 100644 --- a/app/views/templates_preferences/show.html.erb +++ b/app/views/templates_preferences/show.html.erb @@ -194,7 +194,7 @@ <%= t('share_template_with_test_mode') %> - <%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: true_user.account.testing_accounts) %> + <%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: (can?(:manage, :tenants_test) ? current_account : true_user.account).testing_accounts) %> <% end %> diff --git a/config/initializers/autoload.rb b/config/initializers/autoload.rb index fd307b35d..cf66fc063 100644 --- a/config/initializers/autoload.rb +++ b/config/initializers/autoload.rb @@ -9,3 +9,4 @@ autoload :OnnxRuntime, 'onnxruntime' autoload :RQRCode, 'rqrcode' autoload :ArabicLetterConnector, 'arabic-letter-connector/logic' autoload :JWT, 'jwt' +autoload :WordCraft, 'wordcraft' diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index 17733511e..f4b6e272e 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -267,7 +267,10 @@ module Submissions is_id_verified = submission_events.any?(&:complete_verification?) - is_kba_passed = submission_events.any?(&:complete_kba?) + complete_kba_event = submission_events.find(&:complete_kba?) || SubmissionEvent.new + + kba_answers = complete_kba_event.data['answers'] + kba_score = " (#{kba_answers['correct']}/#{kba_answers['asked']})" if kba_answers info_rows = [ [ @@ -297,8 +300,8 @@ module Submissions is_id_verified && { text: "#{I18n.t('identity_verification')}: #{I18n.t('verified')}\n" }, - is_kba_passed && { - text: "#{I18n.t('knowledge_based_authentication')}: #{I18n.t('passed')}\n" + complete_kba_event.persisted? && { + text: "#{I18n.t('knowledge_based_authentication')}: #{I18n.t('passed')}#{kba_score}\n" }, completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" }, completed_event.data['sid'] && { text: "#{I18n.t('session_id')}: #{completed_event.data['sid']}\n" }, diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 44a047dee..189336156 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -602,6 +602,8 @@ module Submissions cell_valign = field.dig('preferences', 'valign').to_s.presence_in(TEXT_VALIGNS) || 'center' cell_layouter = cell_layouters[cell_valign] + value = value.to_s + if (mask = field.dig('preferences', 'mask').presence) value = TextUtils.mask_value(value, mask) end diff --git a/lib/templates/create_attachments.rb b/lib/templates/create_attachments.rb index 6f8f695d6..b3a2e375a 100644 --- a/lib/templates/create_attachments.rb +++ b/lib/templates/create_attachments.rb @@ -6,6 +6,7 @@ module Templates ZIP_CONTENT_TYPE = 'application/zip' X_ZIP_CONTENT_TYPE = 'application/x-zip-compressed' JSON_CONTENT_TYPE = 'application/json' + WORD_EXTENSIONS = %w[.docx .doc .rtf].freeze DOCUMENT_EXTENSIONS = %w[.docx .doc .xlsx .xls .odt .rtf].freeze DOCUMENT_CONTENT_TYPES = %w[ @@ -134,7 +135,27 @@ module Templates return [handle_pdf_or_image(template, file, file.read, params, extract_fields:), []] end + if File.extname(file.original_filename.to_s).downcase.in?(WORD_EXTENSIONS) + return [handle_word_document(template, file, params), []] + end + raise InvalidFileType, "#{file.content_type}/#{dynamic}" end + + def handle_word_document(template, file, params) + pdf = WordCraft.open_buffer(file, &:to_pdf) + + pdf_file = ActionDispatch::Http::UploadedFile.new( + tempfile: StringIO.new(pdf), + filename: "#{File.basename(file.original_filename, '.*')}.pdf", + type: PDF_CONTENT_TYPE + ) + + handle_pdf_or_image(template, pdf_file, pdf, params) + end + + def document_extensions + Docuseal.advanced_formats? ? DOCUMENT_EXTENSIONS : WORD_EXTENSIONS + end end end