mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47c090e1f1 | ||
|
|
678997fb36 | ||
|
|
24f195c7e5 | ||
|
|
5741bb4db2 | ||
|
|
1d169668f0 | ||
|
|
ef3d5bec7e | ||
|
|
d578ca3cc9 | ||
|
|
5432978c0d | ||
|
|
de1af49348 | ||
|
|
2f0bcb4a80 | ||
|
|
11f27c8cf0 | ||
|
|
6e2430e5a2 | ||
|
|
85fbae1813 | ||
|
|
9941122d90 | ||
|
|
71be6db358 | ||
|
|
994c1b3155 | ||
|
|
92b297a620 | ||
|
|
bd03498b0f | ||
|
|
660b24710e | ||
|
|
1416cf72e8 | ||
|
|
ae7b0ce608 | ||
|
|
a3828ec390 | ||
|
|
b8c1691bd7 | ||
|
|
4b00c896e4 | ||
|
|
781da32a07 | ||
|
|
307036eae6 | ||
|
|
9944a9b1a5 | ||
|
|
11856a17ac | ||
|
|
7b4c74d711 | ||
|
|
e0b0d4e803 | ||
|
|
ec04a95e36 | ||
|
|
887d80f6d7 | ||
|
|
9ad281d457 | ||
|
|
e316d2006f | ||
|
|
f9669b0e16 | ||
|
|
785eb789ad | ||
|
|
436d947f6a | ||
|
|
033ce3a169 | ||
|
|
c216e43d24 | ||
|
|
d2373c1578 | ||
|
|
474603e1c9 | ||
|
|
1fc1444b8f | ||
|
|
47361da26f | ||
|
|
11dea042ad | ||
|
|
8756931638 | ||
|
|
180b201dcf | ||
|
|
a1c30c2093 | ||
|
|
66bf6af7ba | ||
|
|
1a65e7bcff | ||
|
|
59b5f03948 | ||
|
|
4626518c83 | ||
|
|
c18e263f34 | ||
|
|
c3dae1bfe4 | ||
|
|
b44152fd52 | ||
|
|
2889a7c937 | ||
|
|
91e65e4a19 | ||
|
|
2a1a2cbd82 | ||
|
|
99b3247cf7 | ||
|
|
fa91c6d6be | ||
|
|
27a0d10525 | ||
|
|
d25f0812ca | ||
|
|
9e2aa6f8c8 | ||
|
|
11c76bf554 | ||
|
|
e5733d88c0 | ||
|
|
6d38c9f375 | ||
|
|
91ec29ad2c | ||
|
|
d374c7fa10 | ||
|
|
ca0daf6d0b | ||
|
|
076c3d805a | ||
|
|
1f149ac67a | ||
|
|
616f7b4fd8 | ||
|
|
6f4527038b | ||
|
|
8ed2883504 | ||
|
|
651b84a460 | ||
|
|
73c4f158ff | ||
|
|
6f1cc34433 | ||
|
|
a77029ffcb | ||
|
|
0480572ef5 | ||
|
|
807463c465 | ||
|
|
0f61053976 | ||
|
|
b8cf525034 | ||
|
|
fef203daed | ||
|
|
41b38cc594 | ||
|
|
4e4354af99 | ||
|
|
113b61588b | ||
|
|
c1aac6b763 | ||
|
|
f85fd3cff7 | ||
|
|
004a22c1c8 | ||
|
|
69343b1bdd | ||
|
|
81a306928c | ||
|
|
2bf3d42ed6 | ||
|
|
eec08266c9 | ||
|
|
cbbc75b485 | ||
|
|
3d93bbe5aa | ||
|
|
5308e3e15b | ||
|
|
22c146d564 | ||
|
|
c8d3af9841 | ||
|
|
ee1d45d461 | ||
|
|
27d075ed63 | ||
|
|
df63acf0c8 | ||
|
|
185827c951 | ||
|
|
e361cbabf4 | ||
|
|
e867bf5c48 | ||
|
|
726d0ea4f3 | ||
|
|
90be34b243 | ||
|
|
e87c06d811 |
No files matched your search
+15
-25
@@ -111,48 +111,39 @@ jobs:
|
|||||||
rspec:
|
rspec:
|
||||||
name: RSpec
|
name: RSpec
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 15
|
||||||
|
container:
|
||||||
|
image: ruby:4.0.5-alpine
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:14
|
image: postgres:18
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: docuseal_test
|
POSTGRES_DB: docuseal_test
|
||||||
ports: ["5432:5432"]
|
|
||||||
options: >-
|
options: >-
|
||||||
--health-cmd pg_isready
|
--health-cmd "pg_isready -U postgres -d docuseal_test"
|
||||||
--health-interval 10s
|
--health-interval 10s
|
||||||
--health-timeout 5s
|
--health-timeout 5s
|
||||||
--health-retries 5
|
--health-retries 5
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install packages
|
||||||
|
run: apk add --no-cache build-base git libpq-dev yaml-dev nodejs yarn tar zstd gzip wget unzip vips leptonica chromium
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install Ruby
|
|
||||||
uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: 4.0.5
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 20.19.0
|
|
||||||
- name: Install Chrome
|
|
||||||
uses: browser-actions/setup-chrome@latest
|
|
||||||
with:
|
|
||||||
chrome-version: 125
|
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-musl-node-${{ hashFiles('**/yarn.lock') }}
|
||||||
- name: Cache gems
|
- name: Cache gems
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: vendor/bundle
|
path: vendor/bundle
|
||||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
key: ${{ runner.os }}-musl-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gem-
|
${{ runner.os }}-musl-gem-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
env:
|
env:
|
||||||
RAILS_ENV: test
|
RAILS_ENV: test
|
||||||
@@ -161,17 +152,16 @@ jobs:
|
|||||||
bundle config path vendor/bundle
|
bundle config path vendor/bundle
|
||||||
bundle install --jobs 4 --retry 4
|
bundle install --jobs 4 --retry 4
|
||||||
yarn install
|
yarn install
|
||||||
sudo apt-get update
|
wget -O pdfium-linux.zip "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-musl-$(uname -m).zip"
|
||||||
sudo apt-get install -y libvips liblept5
|
unzip -q pdfium-linux.zip -d /tmp/pdfium-linux
|
||||||
wget -O pdfium-linux.tgz "https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz"
|
cp /tmp/pdfium-linux/lib/libpdfium.so /usr/lib/libpdfium.so
|
||||||
sudo tar -xzf pdfium-linux.tgz --strip-components=1 -C /usr/lib lib/libpdfium.so
|
rm -rf pdfium-linux.zip /tmp/pdfium-linux
|
||||||
rm -f pdfium-linux.tgz
|
|
||||||
- name: Run
|
- name: Run
|
||||||
env:
|
env:
|
||||||
RAILS_ENV: test
|
RAILS_ENV: test
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
COVERAGE: true
|
COVERAGE: true
|
||||||
DATABASE_URL: postgres://postgres:postgres@localhost:5432/docuseal_test
|
DATABASE_URL: postgres://postgres:postgres@postgres:5432/docuseal_test
|
||||||
run: |
|
run: |
|
||||||
bundle exec rake db:create
|
bundle exec rake db:create
|
||||||
bundle exec rake db:migrate
|
bundle exec rake db:migrate
|
||||||
|
|||||||
+3
-3
@@ -9,10 +9,10 @@ RUN apk --no-cache add wget unzip && \
|
|||||||
wget https://raw.githubusercontent.com/impallari/DancingScript/master/OFL.txt && \
|
wget https://raw.githubusercontent.com/impallari/DancingScript/master/OFL.txt && \
|
||||||
wget https://raw.githubusercontent.com/notofonts/noto-fonts/refs/heads/main/LICENSE && \
|
wget https://raw.githubusercontent.com/notofonts/noto-fonts/refs/heads/main/LICENSE && \
|
||||||
wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \
|
wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \
|
||||||
wget -O pdfium-linux.zip "https://github.com/docusealco/pdfium-binaries/releases/download/20260613/pdfium-musl-$(uname -m).zip" && \
|
wget -O pdfium-linux.zip "https://github.com/docusealco/pdfium-binaries/releases/download/20260920/pdfium-musl-$(uname -m).zip" && \
|
||||||
case "$(uname -m)" in \
|
case "$(uname -m)" in \
|
||||||
x86_64) echo "2c953ff72ee2dda07e7fc577e25841cc3d6464468a7c5adfaea574efcbc3b90b pdfium-linux.zip" ;; \
|
x86_64) echo "bbca8a648dbd1ba81f9c6c223f21dc394cf1ad381c6c17d60c6dcc4a7d3a2ae0 pdfium-linux.zip" ;; \
|
||||||
aarch64) echo "23bbe287d2753fdb05741c7660647eb0ef0d2e4da2ce0722bfa9d9d455bd64e2 pdfium-linux.zip" ;; \
|
aarch64) echo "73b72ed79b9bfdd494dee7e863482c24971a8d4b11f6cd1289aa170d31304a1f pdfium-linux.zip" ;; \
|
||||||
esac | sha256sum -c - && \
|
esac | sha256sum -c - && \
|
||||||
mkdir -p /pdfium-linux && \
|
mkdir -p /pdfium-linux && \
|
||||||
unzip -q pdfium-linux.zip -d /pdfium-linux
|
unzip -q pdfium-linux.zip -d /pdfium-linux
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ gem 'rotp'
|
|||||||
gem 'rouge', require: false
|
gem 'rouge', require: false
|
||||||
gem 'rqrcode', require: false
|
gem 'rqrcode', require: false
|
||||||
gem 'ruby-vips'
|
gem 'ruby-vips'
|
||||||
gem 'rubyXL', require: false
|
gem 'rubyzip', require: false
|
||||||
gem 'shakapacker'
|
gem 'shakapacker'
|
||||||
gem 'sidekiq'
|
gem 'sidekiq'
|
||||||
gem 'sqlite3', require: false
|
gem 'sqlite3', require: false
|
||||||
@@ -45,6 +45,7 @@ gem 'trilogy', require: false
|
|||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
gem 'twitter_cldr', require: false
|
gem 'twitter_cldr', require: false
|
||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
|
gem 'xlsxtream', require: false
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'better_html'
|
gem 'better_html'
|
||||||
|
|||||||
+6
-5
@@ -271,7 +271,7 @@ GEM
|
|||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.21.1)
|
json (2.21.2)
|
||||||
jwt (3.2.0)
|
jwt (3.2.0)
|
||||||
base64
|
base64
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.5)
|
||||||
@@ -497,9 +497,6 @@ GEM
|
|||||||
ruby-vips (2.3.0)
|
ruby-vips (2.3.0)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
logger
|
logger
|
||||||
rubyXL (3.4.35)
|
|
||||||
nokogiri (>= 1.10.8)
|
|
||||||
rubyzip (>= 3.2.2)
|
|
||||||
rubyzip (3.2.2)
|
rubyzip (3.2.2)
|
||||||
securerandom (0.4.1)
|
securerandom (0.4.1)
|
||||||
semantic_range (3.1.1)
|
semantic_range (3.1.1)
|
||||||
@@ -575,10 +572,13 @@ GEM
|
|||||||
base64
|
base64
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
|
xlsxtream (3.1.0)
|
||||||
|
zip_kit (>= 6.2, < 7)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
yaml (0.4.0)
|
yaml (0.4.0)
|
||||||
zeitwerk (2.8.2)
|
zeitwerk (2.8.2)
|
||||||
|
zip_kit (6.3.4)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
@@ -638,7 +638,7 @@ DEPENDENCIES
|
|||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
ruby-vips
|
ruby-vips
|
||||||
rubyXL
|
rubyzip
|
||||||
shakapacker
|
shakapacker
|
||||||
sidekiq
|
sidekiq
|
||||||
simplecov
|
simplecov
|
||||||
@@ -650,6 +650,7 @@ DEPENDENCIES
|
|||||||
tzinfo-data
|
tzinfo-data
|
||||||
web-console
|
web-console
|
||||||
webmock
|
webmock
|
||||||
|
xlsxtream
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 4.0.5
|
ruby 4.0.5
|
||||||
|
|||||||
@@ -23,17 +23,20 @@ class AccountsController < ApplicationController
|
|||||||
unless Docuseal.multitenant?
|
unless Docuseal.multitenant?
|
||||||
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
|
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
|
||||||
key: EncryptedConfig::APP_URL_KEY)
|
key: EncryptedConfig::APP_URL_KEY)
|
||||||
@encrypted_config.assign_attributes(app_url_params)
|
|
||||||
|
|
||||||
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
|
if can?(:manage, @encrypted_config)
|
||||||
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
|
@encrypted_config.assign_attributes(app_url_params)
|
||||||
|
|
||||||
return render :show, status: :unprocessable_content
|
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
|
||||||
|
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
|
||||||
|
|
||||||
|
return render :show, status: :unprocessable_content
|
||||||
|
end
|
||||||
|
|
||||||
|
@encrypted_config.save!
|
||||||
|
|
||||||
|
Docuseal.refresh_default_url_options!
|
||||||
end
|
end
|
||||||
|
|
||||||
@encrypted_config.save!
|
|
||||||
|
|
||||||
Docuseal.refresh_default_url_options!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
with_locale do
|
with_locale do
|
||||||
@@ -47,7 +50,8 @@ class AccountsController < ApplicationController
|
|||||||
authorize!(:manage, current_account)
|
authorize!(:manage, current_account)
|
||||||
|
|
||||||
true_user.skip_reconfirmation!
|
true_user.skip_reconfirmation!
|
||||||
true_user.update!(locked_at: Time.current, email: true_user.email.sub('@', '+removed@'))
|
true_user.update!(locked_at: Time.current, archived_at: Time.current,
|
||||||
|
email: true_user.email.sub('@', '+removed@'))
|
||||||
true_user.account.update!(archived_at: Time.current)
|
true_user.account.update!(archived_at: Time.current)
|
||||||
|
|
||||||
# rubocop:disable Layout/LineLength
|
# rubocop:disable Layout/LineLength
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ module Api
|
|||||||
|
|
||||||
blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid)
|
blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid)
|
||||||
|
|
||||||
if Submitters::DANGEROUS_EXTENSIONS.include?(blob.filename.extension.to_s.downcase)
|
if FilenameUtils.dangerous_extension(blob.filename)
|
||||||
Rollbar.error('Dangerous extension') if defined?(Rollbar)
|
Rollbar.error('Dangerous extension') if defined?(Rollbar)
|
||||||
|
|
||||||
return head :unprocessable_content
|
return head :unprocessable_content
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ module Api
|
|||||||
|
|
||||||
return head :not_found unless blob
|
return head :not_found unless blob
|
||||||
|
|
||||||
if Submitters::DANGEROUS_EXTENSIONS.include?(blob.filename.extension.to_s.downcase)
|
if FilenameUtils.dangerous_extension(blob.filename)
|
||||||
Rollbar.error('Dangerous extension') if defined?(Rollbar)
|
Rollbar.error('Dangerous extension') if defined?(Rollbar)
|
||||||
|
|
||||||
return head :unprocessable_content
|
return head :unprocessable_content
|
||||||
|
|||||||
@@ -17,24 +17,23 @@ module Api
|
|||||||
|
|
||||||
def verify
|
def verify
|
||||||
file = Base64.decode64(params[:file])
|
file = Base64.decode64(params[:file])
|
||||||
pdf = HexaPDF::Document.new(io: StringIO.new(file))
|
|
||||||
|
|
||||||
trusted_certs = Accounts.load_trusted_certs(current_account)
|
trusted_certs = Accounts.load_trusted_certs(current_account)
|
||||||
is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file)))
|
is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file)))
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
checksum_status: is_checksum_found ? 'verified' : 'not_found',
|
checksum_status: is_checksum_found ? 'verified' : 'not_found',
|
||||||
signatures: pdf.signatures.map do |sig|
|
signatures: VerifyPdfSignature.call(StringIO.new(file), trusted_certs).map do |sig|
|
||||||
{
|
{
|
||||||
verification_result: sig.verify(trusted_certs:).messages,
|
verification_result: sig.messages.map { |m| { type: m.status || :info, content: m.text } },
|
||||||
signer_name: sig.signer_name,
|
signer_name: sig.common_name,
|
||||||
signing_reason: sig.signing_reason,
|
signing_reason: sig.reason,
|
||||||
signing_time: sig.signing_time,
|
signing_time: sig.signing_time,
|
||||||
signature_type: sig.signature_type
|
signature_type: sig.type
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
rescue HexaPDF::MalformedPDFError
|
rescue Pdfium::PdfiumError
|
||||||
render json: { error: 'Malformed PDF' }, status: :unprocessable_content
|
render json: { error: 'Malformed PDF' }, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class ApplicationController < ActionController::Base
|
|||||||
rescue_from RateLimit::LimitApproached do |e|
|
rescue_from RateLimit::LimitApproached do |e|
|
||||||
Rollbar.error(e) if defined?(Rollbar)
|
Rollbar.error(e) if defined?(Rollbar)
|
||||||
|
|
||||||
redirect_to request.referer, alert: 'Too many requests', status: :too_many_requests
|
redirect_back_or_to root_path, alert: 'Too many requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
if Rails.env.production? || Rails.env.test?
|
if Rails.env.production? || Rails.env.test?
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ module Mcp
|
|||||||
|
|
||||||
authorize!(:create, @template)
|
authorize!(:create, @template)
|
||||||
|
|
||||||
|
Templates.maybe_assign_access(@template)
|
||||||
|
|
||||||
if mcp_params['url'].present?
|
if mcp_params['url'].present?
|
||||||
tempfile = Tempfile.new
|
tempfile = Tempfile.new
|
||||||
tempfile.binmode
|
tempfile.binmode
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ class MfaSetupController < ApplicationController
|
|||||||
def edit; end
|
def edit; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true)
|
||||||
|
|
||||||
if current_user.validate_and_consume_otp!(params[:otp_attempt])
|
if current_user.validate_and_consume_otp!(params[:otp_attempt])
|
||||||
current_user.otp_required_for_login = true
|
current_user.otp_required_for_login = true
|
||||||
current_user.save!
|
current_user.save!
|
||||||
|
|
||||||
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured')
|
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured')
|
||||||
else
|
else
|
||||||
RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true)
|
|
||||||
|
|
||||||
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
|
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
|
||||||
|
|
||||||
@error_message = I18n.t('code_is_invalid')
|
@error_message = I18n.t('code_is_invalid')
|
||||||
@@ -31,13 +31,13 @@ class MfaSetupController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true)
|
||||||
|
|
||||||
if current_user.validate_and_consume_otp!(params[:otp_attempt])
|
if current_user.validate_and_consume_otp!(params[:otp_attempt])
|
||||||
current_user.update!(otp_required_for_login: false, otp_secret: nil)
|
current_user.update!(otp_required_for_login: false, otp_secret: nil)
|
||||||
|
|
||||||
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed')
|
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed')
|
||||||
else
|
else
|
||||||
RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true)
|
|
||||||
|
|
||||||
@error_message = I18n.t('code_is_invalid')
|
@error_message = I18n.t('code_is_invalid')
|
||||||
|
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_content
|
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_content
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ class PreviewDocumentPageController < ActionController::API
|
|||||||
|
|
||||||
@template = attachment.record
|
@template = attachment.record
|
||||||
|
|
||||||
preview_image = attachment.preview_images.joins(:blob)
|
preview_image =
|
||||||
.find_by(blob: { filename: ["#{params[:id]}.png", "#{params[:id]}.jpg"] })
|
attachment.preview_images.joins(:blob)
|
||||||
|
.find_by(blob: { filename: ["#{params[:id].to_i}.png", "#{params[:id].to_i}.jpg"] })
|
||||||
|
|
||||||
if preview_image
|
if preview_image
|
||||||
return redirect_to preview_image.url(time: ActiveStorage::Attachment.service_url_time),
|
return redirect_to preview_image.url(time: ActiveStorage::Attachment.service_url_time),
|
||||||
|
|||||||
@@ -8,13 +8,9 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
around_action :with_browser_locale, only: %i[show update completed]
|
around_action :with_browser_locale, only: %i[show update completed]
|
||||||
before_action :maybe_redirect_com, only: %i[show completed]
|
before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
before_action :load_resubmit_submitter, only: :update
|
|
||||||
before_action :load_template
|
before_action :load_template
|
||||||
before_action :authorize_start!, only: :update
|
before_action :authorize_start!, only: :update
|
||||||
|
|
||||||
COOKIES_TTL = 12.hours
|
|
||||||
COOKIES_DEFAULTS = { httponly: true, secure: Rails.env.production? }.freeze
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
if @template.preferences['require_phone_2fa'] || @template.preferences['require_email_2fa']
|
if @template.preferences['require_phone_2fa'] || @template.preferences['require_email_2fa']
|
||||||
raise ActionController::RoutingError, I18n.t('not_found')
|
raise ActionController::RoutingError, I18n.t('not_found')
|
||||||
@@ -23,8 +19,7 @@ class StartFormController < ApplicationController
|
|||||||
if @template.shared_link?
|
if @template.shared_link?
|
||||||
@submitter = @template.submissions.new(account_id: @template.account_id)
|
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||||
.submitters.new(account_id: @template.account_id,
|
.submitters.new(account_id: @template.account_id,
|
||||||
uuid: (filter_undefined_submitters(@template).first ||
|
uuid: Submitters::StartForm.first_submitter_uuid(@template))
|
||||||
@template.submitters.first)['uuid'])
|
|
||||||
render :email_verification if params[:email_verification]
|
render :email_verification if params[:email_verification]
|
||||||
else
|
else
|
||||||
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
|
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
|
||||||
@@ -41,24 +36,28 @@ class StartFormController < ApplicationController
|
|||||||
if @submitter.completed_at?
|
if @submitter.completed_at?
|
||||||
redirect_to start_form_completed_path(@template.slug, submitter_params.compact_blank)
|
redirect_to start_form_completed_path(@template.slug, submitter_params.compact_blank)
|
||||||
else
|
else
|
||||||
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
|
if Templates.filter_undefined_submitters(@template.submitters).size > 1 && @submitter.new_record?
|
||||||
@error_message = multiple_submitters_error_message
|
@error_message = multiple_submitters_error_message
|
||||||
|
|
||||||
return render :show, status: :unprocessable_content
|
return render :show, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
|
|
||||||
if (is_new_record = @submitter.new_record?)
|
if (is_new_record = @submitter.new_record?)
|
||||||
assign_submission_attributes(@submitter, @template)
|
Submitters::StartForm.assign_submission_attributes(
|
||||||
|
@submitter, @template, ip: request.remote_ip, user_agent: request.user_agent
|
||||||
|
)
|
||||||
|
|
||||||
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
|
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
|
||||||
else
|
else
|
||||||
@submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
|
@submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
|
||||||
end
|
end
|
||||||
|
|
||||||
if @template.preferences['shared_link_2fa'] == true
|
if require_link_2fa?(@template, @submitter)
|
||||||
handle_require_2fa(@submitter, is_new_record:)
|
handle_require_2fa(@template, @submitter)
|
||||||
elsif @submitter.errors.blank? && @submitter.save
|
elsif @submitter.errors.blank? && @submitter.save
|
||||||
enqueue_new_submitter_jobs(@submitter) if is_new_record
|
Submitters::StartForm.enqueue_new_submitter_jobs(@submitter) if is_new_record
|
||||||
|
|
||||||
|
Submitters::StartForm.assign_start_form_cookie(@submitter, request)
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug)
|
redirect_to submit_form_path(@submitter.slug)
|
||||||
else
|
else
|
||||||
@@ -88,41 +87,40 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def enqueue_new_submitter_jobs(submitter)
|
def find_or_initialize_submitter(template, submitter_params)
|
||||||
WebhookUrls.enqueue_events(submitter.submission, 'submission.created')
|
required_fields = template.preferences.fetch('link_form_fields', ['email'])
|
||||||
|
|
||||||
SearchEntries.enqueue_reindex(submitter)
|
blank_fields = required_fields.select { |key| submitter_params[key].blank? }
|
||||||
|
|
||||||
expire_at = submitter.submission.expire_at
|
submitter =
|
||||||
|
if blank_fields.present?
|
||||||
return unless expire_at
|
Submitter.new(submitter_params)
|
||||||
|
else
|
||||||
ProcessSubmissionExpiredJob.perform_at(expire_at, 'submission_id' => submitter.submission_id,
|
Submitters::StartForm.find_or_initialize_submitter(
|
||||||
'expire_at' => expire_at.to_i)
|
template, submitter_params, exclude_completed: params[:resubmit].present?,
|
||||||
end
|
request:, current_user:
|
||||||
|
)
|
||||||
def load_resubmit_submitter
|
|
||||||
@resubmit_submitter =
|
|
||||||
if params[:resubmit].present? && !params[:resubmit].in?([true, 'true'])
|
|
||||||
submitter = Submitter.find_by(slug: params[:resubmit])
|
|
||||||
|
|
||||||
submitter if submitter && can_resubmit?(submitter)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
blank_fields.each { |key| submitter.errors.add(key.to_sym, :blank) }
|
||||||
|
|
||||||
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_resubmit?(submitter)
|
def require_link_2fa?(template, submitter)
|
||||||
submitter.completed_at? && submitter.completed_at > 14.days.ago &&
|
return true if template.preferences['shared_link_2fa'] == true
|
||||||
%w[api embed mcp].exclude?(submitter.submission.source) &&
|
return false if cookies.encrypted[:start_form_slug] == submitter.slug
|
||||||
submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false
|
return false if current_user && submitter.email == current_user.email &&
|
||||||
|
current_user.account_id == submitter.account_id
|
||||||
|
|
||||||
|
!submitter.new_record?
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize_start!
|
def authorize_start!
|
||||||
is_archived = @template.archived_at? || @template.account.archived_at?
|
is_archived = @template.archived_at? || @template.account.archived_at?
|
||||||
|
|
||||||
return redirect_to submit_form_path(@resubmit_submitter.slug) if @resubmit_submitter && is_archived
|
|
||||||
return redirect_to start_form_path(@template.slug) if is_archived
|
return redirect_to start_form_path(@template.slug) if is_archived
|
||||||
|
|
||||||
return if @resubmit_submitter
|
|
||||||
return if @template.shared_link? || (current_user && current_ability.can?(:read, @template))
|
return if @template.shared_link? || (current_user && current_ability.can?(:read, @template))
|
||||||
|
|
||||||
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
|
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
|
||||||
@@ -130,90 +128,14 @@ class StartFormController < ApplicationController
|
|||||||
redirect_to start_form_path(@template.slug)
|
redirect_to start_form_path(@template.slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_or_initialize_submitter(template, submitter_params)
|
|
||||||
required_fields = template.preferences.fetch('link_form_fields', ['email'])
|
|
||||||
|
|
||||||
required_params = required_fields.index_with { |key| submitter_params[key] }
|
|
||||||
|
|
||||||
find_params = required_params.except('name')
|
|
||||||
|
|
||||||
submitter = Submitter.new if find_params.compact_blank.blank?
|
|
||||||
|
|
||||||
submitter ||=
|
|
||||||
Submitter
|
|
||||||
.where(submission: template.submissions.non_expired.active)
|
|
||||||
.order(id: :desc)
|
|
||||||
.where(declined_at: nil)
|
|
||||||
.where(external_id: nil)
|
|
||||||
.where(template.preferences['shared_link_2fa'] == true ? {} : { ip: [nil, request.remote_ip] })
|
|
||||||
.then { |rel| params[:resubmit].present? || params[:selfsign].present? ? rel.where(completed_at: nil) : rel }
|
|
||||||
.find_or_initialize_by(find_params)
|
|
||||||
|
|
||||||
submitter = Submitter.new(find_params) if submitter.submission&.completed_at? && submitter.viewer?
|
|
||||||
|
|
||||||
submitter.name = required_params['name'] if submitter.new_record?
|
|
||||||
|
|
||||||
unless @resubmit_submitter
|
|
||||||
required_params.each do |key, value|
|
|
||||||
submitter.errors.add(key.to_sym, :blank) if value.blank?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
submitter
|
|
||||||
end
|
|
||||||
|
|
||||||
def assign_submission_attributes(submitter, template)
|
|
||||||
submitter.assign_attributes(
|
|
||||||
uuid: (filter_undefined_submitters(template).first || @template.submitters.first)['uuid'],
|
|
||||||
ip: request.remote_ip,
|
|
||||||
ua: request.user_agent,
|
|
||||||
values: @resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
|
|
||||||
preferences: @resubmit_submitter&.preferences.presence || { 'send_email' => true },
|
|
||||||
metadata: @resubmit_submitter&.metadata.presence || {}
|
|
||||||
)
|
|
||||||
|
|
||||||
submitter.assign_attributes(@resubmit_submitter.slice(:name, :email, :phone)) if @resubmit_submitter
|
|
||||||
|
|
||||||
if submitter.values.present?
|
|
||||||
@resubmit_submitter.attachments.each do |attachment|
|
|
||||||
submitter.attachments << attachment.dup if submitter.values.value?(attachment.uuid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
submitter.submission ||= Submission.new(template:,
|
|
||||||
account_id: template.account_id,
|
|
||||||
template_submitters: template.submitters,
|
|
||||||
expire_at: Templates.build_default_expire_at(template),
|
|
||||||
submitters: [submitter],
|
|
||||||
source: :link)
|
|
||||||
|
|
||||||
Submissions::CreateFromSubmitters.maybe_set_dynamic_documents(submitter.submission)
|
|
||||||
|
|
||||||
submitter.account_id = submitter.submission.account_id
|
|
||||||
|
|
||||||
submitter
|
|
||||||
end
|
|
||||||
|
|
||||||
def filter_undefined_submitters(template)
|
|
||||||
Templates.filter_undefined_submitters(template.submitters)
|
|
||||||
end
|
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
return { 'email' => current_user.email, 'name' => current_user.full_name } if params[:selfsign]
|
|
||||||
return @resubmit_submitter.slice(:name, :phone, :email) if @resubmit_submitter.present?
|
|
||||||
|
|
||||||
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
|
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
|
||||||
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_template
|
def load_template
|
||||||
@template =
|
@template = Template.find_by!(slug: params[:slug] || params[:start_form_slug])
|
||||||
if @resubmit_submitter
|
|
||||||
@resubmit_submitter.template
|
|
||||||
else
|
|
||||||
Template.find_by!(slug: params[:slug] || params[:start_form_slug])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def multiple_submitters_error_message
|
def multiple_submitters_error_message
|
||||||
@@ -224,38 +146,31 @@ class StartFormController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_require_2fa(submitter, is_new_record:)
|
def handle_require_2fa(template, submitter)
|
||||||
return render :show, status: :unprocessable_content if submitter.errors.present?
|
return render :show, status: :unprocessable_content if submitter.errors.present?
|
||||||
|
|
||||||
is_otp_verified = Submitters.verify_link_otp!(params[:one_time_code], submitter)
|
if Submitters::StartForm.verify_2fa_and_save_submitter(submitter, request, is_new_record: submitter.new_record?)
|
||||||
|
redirect_to submit_form_path(submitter.slug)
|
||||||
if cookies.encrypted[:email_2fa_slug] == submitter.slug || is_otp_verified
|
|
||||||
if submitter.save
|
|
||||||
enqueue_new_submitter_jobs(submitter) if is_new_record
|
|
||||||
|
|
||||||
if is_otp_verified
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'email_verified', request)
|
|
||||||
|
|
||||||
cookies.encrypted[:email_2fa_slug] =
|
|
||||||
{ value: submitter.slug, expires: COOKIES_TTL.from_now, **COOKIES_DEFAULTS }
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect_to submit_form_path(submitter.slug)
|
|
||||||
else
|
|
||||||
render :show, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
|
if defined?(Rollbar) && template.preferences['shared_link_2fa'] != true
|
||||||
|
Rollbar.info("2FA link requested: #{submitter.id}")
|
||||||
|
end
|
||||||
|
|
||||||
Submitters.send_shared_link_email_verification_code(submitter, request:)
|
Submitters.send_shared_link_email_verification_code(submitter, request:)
|
||||||
|
|
||||||
render :email_verification
|
render :email_verification
|
||||||
end
|
end
|
||||||
|
rescue Submitters::StartForm::NotSaved
|
||||||
|
render :show, status: :unprocessable_content
|
||||||
|
rescue Submitters::BouncedEmail => e
|
||||||
|
@error_message = e.message
|
||||||
|
|
||||||
|
render :error, status: :unprocessable_content
|
||||||
rescue Submitters::UnableToSendCode, Submitters::InvalidOtp => e
|
rescue Submitters::UnableToSendCode, Submitters::InvalidOtp => e
|
||||||
redirect_to start_form_path(submitter.submission.template.slug,
|
redirect_to start_form_path(template.slug, params: submitter_params.merge(email_verification: true)),
|
||||||
params: submitter_params.merge(email_verification: true)),
|
|
||||||
alert: e.message
|
alert: e.message
|
||||||
rescue RateLimit::LimitApproached
|
rescue RateLimit::LimitApproached
|
||||||
redirect_to start_form_path(submitter.submission.template.slug,
|
redirect_to start_form_path(template.slug, params: submitter_params.merge(email_verification: true)),
|
||||||
params: submitter_params.merge(email_verification: true)),
|
|
||||||
alert: I18n.t(:too_many_attempts)
|
alert: I18n.t(:too_many_attempts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class StartFormEmail2faSendController < ApplicationController
|
class StartFormEmail2faSendController < ApplicationController
|
||||||
|
layout 'form'
|
||||||
|
|
||||||
around_action :with_browser_locale
|
around_action :with_browser_locale
|
||||||
|
|
||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
@@ -13,12 +15,20 @@ class StartFormEmail2faSendController < ApplicationController
|
|||||||
@submitter = @template.submissions.new(account_id: @template.account_id)
|
@submitter = @template.submissions.new(account_id: @template.account_id)
|
||||||
.submitters.new(**submitter_params, account_id: @template.account_id)
|
.submitters.new(**submitter_params, account_id: @template.account_id)
|
||||||
|
|
||||||
|
if @submitter.email.blank?
|
||||||
|
return redirect_to start_form_path(@template.slug), alert: I18n.t(:provide_your_email_to_start)
|
||||||
|
end
|
||||||
|
|
||||||
Submitters.send_shared_link_email_verification_code(@submitter, request:)
|
Submitters.send_shared_link_email_verification_code(@submitter, request:)
|
||||||
|
|
||||||
redir_params = { notice: I18n.t(:code_has_been_resent) } if params[:resend]
|
redir_params = { notice: I18n.t(:code_has_been_resent) } if params[:resend]
|
||||||
|
|
||||||
redirect_to start_form_path(@template.slug, params: submitter_params.merge(email_verification: true)),
|
redirect_to start_form_path(@template.slug, params: submitter_params.merge(email_verification: true)),
|
||||||
**redir_params
|
**redir_params
|
||||||
|
rescue Submitters::BouncedEmail => e
|
||||||
|
@error_message = e.message
|
||||||
|
|
||||||
|
render 'start_form/error', status: :unprocessable_content
|
||||||
rescue Submitters::UnableToSendCode => e
|
rescue Submitters::UnableToSendCode => e
|
||||||
redirect_to start_form_path(@template.slug, params: submitter_params.merge(email_verification: true)),
|
redirect_to start_form_path(@template.slug, params: submitter_params.merge(email_verification: true)),
|
||||||
alert: e.message
|
alert: e.message
|
||||||
@@ -36,14 +46,24 @@ class StartFormEmail2faSendController < ApplicationController
|
|||||||
return redirect_to start_form_path(@template.slug) if is_archived
|
return redirect_to start_form_path(@template.slug) if is_archived
|
||||||
|
|
||||||
return if (@template.shared_link? || (current_user && current_ability.can?(:read, @template))) &&
|
return if (@template.shared_link? || (current_user && current_ability.can?(:read, @template))) &&
|
||||||
@template.preferences['shared_link_2fa'] == true
|
(@template.preferences['shared_link_2fa'] == true || submitter_exists?(@template, params))
|
||||||
|
|
||||||
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
|
Rollbar.warning("Not 2FA shared template: #{@template.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
redirect_to start_form_path(@template.slug)
|
redirect_to start_form_path(@template.slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def submitter_exists?(template, params)
|
||||||
|
email = Submissions.normalize_email(params.dig(:submitter, :email))
|
||||||
|
|
||||||
|
return false if email.blank?
|
||||||
|
|
||||||
|
Submitters::StartForm.build_submitters_scope(template, exclude_completed: true).exists?(email:)
|
||||||
|
end
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
params.require(:submitter).permit(:name, :email, :phone)
|
params.require(:submitter).permit(:name, :email, :phone).tap do |attrs|
|
||||||
|
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class StartFormResubmitController < ApplicationController
|
||||||
|
layout 'form'
|
||||||
|
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
around_action :with_browser_locale
|
||||||
|
before_action :load_resubmit_submitter
|
||||||
|
before_action :load_template
|
||||||
|
before_action :authorize_start!
|
||||||
|
|
||||||
|
def update
|
||||||
|
@submitter = find_or_initialize_submitter(@template, @resubmit_submitter)
|
||||||
|
|
||||||
|
if Templates.filter_undefined_submitters(@template.submitters).size > 1 && @submitter.new_record?
|
||||||
|
@error_message = multiple_submitters_error_message
|
||||||
|
|
||||||
|
return render 'start_form/show', status: :unprocessable_content
|
||||||
|
end
|
||||||
|
|
||||||
|
if (is_new_record = @submitter.new_record?)
|
||||||
|
Submitters::StartForm.assign_submission_attributes(
|
||||||
|
@submitter, @template,
|
||||||
|
ip: request.remote_ip, user_agent: request.user_agent, resubmit_submitter: @resubmit_submitter,
|
||||||
|
source: @resubmit_submitter.submission.source_self? ? :self : :link
|
||||||
|
)
|
||||||
|
|
||||||
|
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
|
||||||
|
else
|
||||||
|
@submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
|
||||||
|
end
|
||||||
|
|
||||||
|
if @template.preferences['shared_link_2fa'] == true
|
||||||
|
handle_require_2fa(@submitter)
|
||||||
|
elsif @submitter.save
|
||||||
|
Submitters::StartForm.enqueue_new_submitter_jobs(@submitter) if is_new_record
|
||||||
|
|
||||||
|
Submitters::StartForm.assign_start_form_cookie(@submitter, request)
|
||||||
|
|
||||||
|
redirect_to submit_form_path(@submitter.slug)
|
||||||
|
else
|
||||||
|
render 'start_form/show', status: :unprocessable_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_or_initialize_submitter(template, resubmit_submitter)
|
||||||
|
Submitters::StartForm.build_submitters_scope(template, exclude_completed: true)
|
||||||
|
.find_by(slug: cookies.encrypted[:start_form_slug],
|
||||||
|
email: resubmit_submitter.email) || Submitter.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_resubmit_submitter
|
||||||
|
@resubmit_submitter = Submitter.find_by(slug: params[:resubmit])
|
||||||
|
|
||||||
|
raise ActiveRecord::RecordNotFound if @resubmit_submitter.blank? ||
|
||||||
|
!Submitters::StartForm.can_resubmit?(@resubmit_submitter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_template
|
||||||
|
@template = @resubmit_submitter.template
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_start!
|
||||||
|
redirect_to submit_form_path(@resubmit_submitter.slug) if @template.archived_at? || @template.account.archived_at?
|
||||||
|
end
|
||||||
|
|
||||||
|
def multiple_submitters_error_message
|
||||||
|
if current_user&.account_id == @template.account_id
|
||||||
|
helpers.t('this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html')
|
||||||
|
else
|
||||||
|
I18n.t('not_found')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_require_2fa(submitter)
|
||||||
|
if Submitters::StartForm.verify_2fa_and_save_submitter(submitter, request,
|
||||||
|
is_new_record: submitter.new_record?)
|
||||||
|
redirect_to submit_form_path(submitter.slug)
|
||||||
|
else
|
||||||
|
Submitters.send_shared_link_email_verification_code(submitter, request:)
|
||||||
|
|
||||||
|
render 'start_form/email_verification'
|
||||||
|
end
|
||||||
|
rescue Submitters::StartForm::NotSaved
|
||||||
|
render 'start_form/show', status: :unprocessable_content
|
||||||
|
rescue Submitters::BouncedEmail => e
|
||||||
|
@error_message = e.message
|
||||||
|
|
||||||
|
render 'start_form/error', status: :unprocessable_content
|
||||||
|
rescue Submitters::UnableToSendCode, Submitters::InvalidOtp => e
|
||||||
|
flash.now[:alert] = e.message
|
||||||
|
|
||||||
|
render 'start_form/email_verification'
|
||||||
|
rescue RateLimit::LimitApproached
|
||||||
|
flash.now[:alert] = I18n.t(:too_many_attempts)
|
||||||
|
|
||||||
|
render 'start_form/email_verification'
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class StartFormSelfController < ApplicationController
|
||||||
|
load_resource :template, parent: false
|
||||||
|
|
||||||
|
around_action :with_browser_locale
|
||||||
|
before_action :authorize_start!
|
||||||
|
|
||||||
|
def update
|
||||||
|
@submitter = Submitters::StartForm.build_submitters_scope(@template, exclude_completed: true, source: :self)
|
||||||
|
.find_or_initialize_by(email: current_user.email)
|
||||||
|
|
||||||
|
@submitter = Submitter.new(email: current_user.email) if @submitter.persisted? &&
|
||||||
|
outdated_template_fields?(@submitter, @template)
|
||||||
|
|
||||||
|
if (is_new_record = @submitter.new_record?)
|
||||||
|
@submitter.name = current_user.full_name
|
||||||
|
|
||||||
|
Submitters::StartForm.assign_submission_attributes(
|
||||||
|
@submitter, @template, ip: request.remote_ip, user_agent: request.user_agent, source: :self
|
||||||
|
)
|
||||||
|
|
||||||
|
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
|
||||||
|
else
|
||||||
|
@submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
|
||||||
|
end
|
||||||
|
|
||||||
|
@submitter.save!
|
||||||
|
|
||||||
|
Submitters::StartForm.enqueue_new_submitter_jobs(@submitter) if is_new_record
|
||||||
|
|
||||||
|
Submitters::StartForm.assign_start_form_cookie(@submitter, request)
|
||||||
|
|
||||||
|
redirect_to submit_form_path(@submitter.slug)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def outdated_template_fields?(submitter, template)
|
||||||
|
template_fields = submitter.submission.template_fields
|
||||||
|
|
||||||
|
template_fields.present? && template_fields != template.fields
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_start!
|
||||||
|
authorize!(:read, @template)
|
||||||
|
|
||||||
|
redirect_to template_path(@template) if @template.archived_at?
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -48,7 +48,7 @@ class SubmitFormCompletedDownloadController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def submitter_slug
|
def submitter_slug
|
||||||
params[:submit_form_slug] || params[:submitter_slug] || params[:submitter_id]
|
params[:submit_form_slug] || params[:submitter_slug]
|
||||||
end
|
end
|
||||||
|
|
||||||
def respond_with_combined(submitter)
|
def respond_with_combined(submitter)
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ class SubmitFormController < ApplicationController
|
|||||||
submitter_version = SubmitterVersion.find_by!(slug: params[:slug] || params[:submit_form_slug])
|
submitter_version = SubmitterVersion.find_by!(slug: params[:slug] || params[:submit_form_slug])
|
||||||
|
|
||||||
@submitter = submitter_version.submitter
|
@submitter = submitter_version.submitter
|
||||||
|
|
||||||
|
maybe_render_locked_page
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ class SubmitFormDeclineController < ApplicationController
|
|||||||
request)
|
request)
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
@submitter.update!(declined_at: Time.current)
|
@submitter.update!(declined_at: Time.current, values: @submitter.preferences['default_values'] || {})
|
||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(@submitter, 'decline_form', request, { reason: params[:reason] })
|
SubmissionEvents.create_with_tracking_data(@submitter, 'decline_form', request, { reason: params[:reason] })
|
||||||
end
|
end
|
||||||
|
|
||||||
user = @submitter.submission.created_by_user || @submitter.template.author
|
user = @submitter.submission.created_by_user || @submitter.template.author
|
||||||
|
|
||||||
if user.user_configs.find_by(key: UserConfig::RECEIVE_DECLINED_EMAIL)&.value != false
|
if Users.send_emails?(user) &&
|
||||||
|
user.user_configs.find_by(key: UserConfig::RECEIVE_DECLINED_EMAIL)&.value != false
|
||||||
SubmitterMailer.declined_email(@submitter, user).deliver_later!
|
SubmitterMailer.declined_email(@submitter, user).deliver_later!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ class SubmitFormDelegateController < ApplicationController
|
|||||||
SubmissionEvents.create_with_tracking_data(@submitter, 'delegate_form', request,
|
SubmissionEvents.create_with_tracking_data(@submitter, 'delegate_form', request,
|
||||||
{ old_email: @submitter.email, email: })
|
{ old_email: @submitter.email, email: })
|
||||||
|
|
||||||
@submitter.update!(email:, phone: nil, name: nil, slug: SecureRandom.base58(14))
|
@submitter.update!(email:, phone: nil, name: nil, slug: SecureRandom.base58(14),
|
||||||
|
values: Submitters.fetch_values_for_delegate(@submitter))
|
||||||
end
|
end
|
||||||
|
|
||||||
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id)
|
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id)
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ class SubmitFormEmail2fasController < ApplicationController
|
|||||||
|
|
||||||
before_action :load_submitter
|
before_action :load_submitter
|
||||||
|
|
||||||
COOKIES_TTL = 12.hours
|
|
||||||
COOKIES_DEFAULTS = { httponly: true, secure: Rails.env.production? }.freeze
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
RateLimit.call("verify-2fa-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true)
|
RateLimit.call("verify-2fa-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true)
|
||||||
|
|
||||||
@@ -20,7 +17,8 @@ class SubmitFormEmail2fasController < ApplicationController
|
|||||||
SubmissionEvents.create_with_tracking_data(@submitter, 'email_verified', request, { email: @submitter.email })
|
SubmissionEvents.create_with_tracking_data(@submitter, 'email_verified', request, { email: @submitter.email })
|
||||||
|
|
||||||
cookies.encrypted[:email_2fa_slug] =
|
cookies.encrypted[:email_2fa_slug] =
|
||||||
{ value: @submitter.slug, expires: COOKIES_TTL.from_now, **COOKIES_DEFAULTS }
|
{ value: @submitter.slug, expires: Submitters::StartForm::COOKIES_TTL.from_now,
|
||||||
|
**Submitters::StartForm::COOKIES_DEFAULTS }
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug)
|
redirect_to submit_form_path(@submitter.slug)
|
||||||
else
|
else
|
||||||
@@ -37,6 +35,12 @@ class SubmitFormEmail2fasController < ApplicationController
|
|||||||
|
|
||||||
RateLimit.call("send-email-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true)
|
RateLimit.call("send-email-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true)
|
||||||
|
|
||||||
|
if Docuseal.multitenant? && Submitters.email_bounced_recently?(@submitter.email)
|
||||||
|
Rollbar.warning("Bounced OTP email for submitter: #{@submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
|
return redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:verification_email_bounced)
|
||||||
|
end
|
||||||
|
|
||||||
SendSubmitterVerificationEmailJob.perform_async('submitter_id' => @submitter.id, 'locale' => I18n.locale.to_s)
|
SendSubmitterVerificationEmailJob.perform_async('submitter_id' => @submitter.id, 'locale' => I18n.locale.to_s)
|
||||||
|
|
||||||
redir_params = params[:resend] ? { alert: I18n.t(:code_has_been_resent) } : {}
|
redir_params = params[:resend] ? { alert: I18n.t(:code_has_been_resent) } : {}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class SubmitFormInviteController < ApplicationController
|
|||||||
|
|
||||||
@submitter.submission.submitters.create!(uuid: attrs[:uuid], email:, account_id: @submitter.account_id)
|
@submitter.submission.submitters.create!(uuid: attrs[:uuid], email:, account_id: @submitter.account_id)
|
||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(@submitter, 'invite_party', request, { uuid: @submitter.uuid })
|
SubmissionEvents.create_with_tracking_data(@submitter, 'invite_party', request, { uuid: attrs[:uuid] })
|
||||||
end
|
end
|
||||||
|
|
||||||
@submitter.submission.update!(submitters_order: :preserved)
|
@submitter.submission.update!(submitters_order: :preserved)
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ class TemplatesDebugController < ApplicationController
|
|||||||
data = attachment.download
|
data = attachment.download
|
||||||
|
|
||||||
unless attachment.image?
|
unless attachment.image?
|
||||||
pdf = HexaPDF::Document.new(io: StringIO.new(data))
|
fields =
|
||||||
|
Pdfium::Document.open_io(StringIO.new(data)) do |doc|
|
||||||
fields = Templates::FindAcroFields.call(pdf, attachment, data)
|
Templates::FindPdfiumAcroFields.call(attachment, doc, data)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# fields, = Templates::DetectFields.call(StringIO.new(data), attachment:) if fields.blank?
|
# fields, = Templates::DetectFields.call(StringIO.new(data), attachment:) if fields.blank?
|
||||||
|
|||||||
@@ -10,8 +10,14 @@ class TemplatesDetectFieldsController < ApplicationController
|
|||||||
|
|
||||||
sse = SSE.new(response.stream)
|
sse = SSE.new(response.stream)
|
||||||
|
|
||||||
documents = @template.schema_documents.preload(:blob)
|
documents =
|
||||||
documents = documents.where(uuid: params[:attachment_uuid]) if params[:attachment_uuid].present?
|
if params[:attachment_uuid].present?
|
||||||
|
@template.documents.where(uuid: params[:attachment_uuid])
|
||||||
|
else
|
||||||
|
@template.schema_documents
|
||||||
|
end
|
||||||
|
|
||||||
|
documents = documents.preload(:blob)
|
||||||
|
|
||||||
page_number = params[:page].presence&.to_i
|
page_number = params[:page].presence&.to_i
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
layout 'plain'
|
layout 'plain'
|
||||||
|
|
||||||
def show
|
def show
|
||||||
redirect_to root_path if params[:url].blank?
|
url_uri = Addressable::URI.parse(params[:url].to_s)
|
||||||
|
|
||||||
|
redirect_to root_path if url_uri.normalized_scheme != 'https' || url_uri.host.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@@ -61,7 +63,7 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
tempfile.write(DownloadUtils.call(params[:url], validate: true).body)
|
tempfile.write(DownloadUtils.call(params[:url], validate: true).body)
|
||||||
tempfile.rewind
|
tempfile.rewind
|
||||||
|
|
||||||
filename = URI.decode_www_form_component(params[:filename]) if params[:filename].present?
|
filename = URI.decode_www_form_component(params[:filename]).tr('/', '-') if params[:filename].present?
|
||||||
filename ||= File.basename(URI.decode_www_form_component(params[:url]))
|
filename ||= File.basename(URI.decode_www_form_component(params[:url]))
|
||||||
|
|
||||||
file = ActionDispatch::Http::UploadedFile.new(
|
file = ActionDispatch::Http::UploadedFile.new(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class TestingAccountsController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
authorize!(:manage, current_account)
|
authorize!(:manage, current_account)
|
||||||
authorize!(:manage, current_user)
|
authorize!(:manage, current_user)
|
||||||
|
authorize!(:manage, EncryptedConfig)
|
||||||
|
|
||||||
impersonate_user(Accounts.find_or_create_testing_user(true_user.account))
|
impersonate_user(Accounts.find_or_create_testing_user(true_user.account))
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ class UserInitialsController < ApplicationController
|
|||||||
|
|
||||||
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank?
|
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank?
|
||||||
|
|
||||||
extension = File.extname(file.original_filename).delete_prefix('.').downcase
|
extension = FilenameUtils.dangerous_extension(file.original_filename)
|
||||||
|
|
||||||
if Submitters::DANGEROUS_EXTENSIONS.include?(extension)
|
raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed." if extension
|
||||||
raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed."
|
|
||||||
end
|
|
||||||
|
|
||||||
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
||||||
filename: file.original_filename,
|
filename: file.original_filename,
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ class UserSignaturesController < ApplicationController
|
|||||||
|
|
||||||
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature') if file.blank?
|
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature') if file.blank?
|
||||||
|
|
||||||
extension = File.extname(file.original_filename).delete_prefix('.').downcase
|
extension = FilenameUtils.dangerous_extension(file.original_filename)
|
||||||
|
|
||||||
if Submitters::DANGEROUS_EXTENSIONS.include?(extension)
|
raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed." if extension
|
||||||
raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed."
|
|
||||||
end
|
|
||||||
|
|
||||||
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
|
||||||
filename: file.original_filename,
|
filename: file.original_filename,
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ class VerifyPdfSignatureController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
def create
|
def create
|
||||||
pdfs =
|
|
||||||
params[:files].map do |file|
|
|
||||||
HexaPDF::Document.new(io: file.open)
|
|
||||||
end
|
|
||||||
|
|
||||||
trusted_certs = Accounts.load_trusted_certs(current_account)
|
trusted_certs = Accounts.load_trusted_certs(current_account)
|
||||||
|
|
||||||
|
signatures =
|
||||||
|
params[:files].map do |file|
|
||||||
|
VerifyPdfSignature.call(file.open, trusted_certs)
|
||||||
|
end
|
||||||
|
|
||||||
render turbo_stream: turbo_stream.replace('result', partial: 'result',
|
render turbo_stream: turbo_stream.replace('result', partial: 'result',
|
||||||
locals: { pdfs:, files: params[:files], trusted_certs: })
|
locals: { signatures:, files: params[:files] })
|
||||||
rescue HexaPDF::MalformedPDFError
|
rescue Pdfium::PdfiumError
|
||||||
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div(I18n.t('invalid_pdf'), id: 'result'))
|
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div(I18n.t('invalid_pdf'), id: 'result'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -3,11 +3,19 @@
|
|||||||
class WebhookSecretController < ApplicationController
|
class WebhookSecretController < ApplicationController
|
||||||
load_and_authorize_resource :webhook_url, parent: false
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
|
|
||||||
|
HEADER_NAME_REGEXP = /\A[\w-]+\z/
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
key = webhook_secret_params[:key]
|
||||||
|
|
||||||
|
if key.present? && !HEADER_NAME_REGEXP.match?(key)
|
||||||
|
return redirect_back(fallback_location: settings_webhook_path(@webhook_url), alert: I18n.t('unable_to_save'))
|
||||||
|
end
|
||||||
|
|
||||||
@webhook_url.update!(secret: {
|
@webhook_url.update!(secret: {
|
||||||
webhook_secret_params[:key] => webhook_secret_params[:value]
|
key => webhook_secret_params[:value]
|
||||||
}.compact_blank)
|
}.compact_blank)
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
|
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ function buildExtensions ({ Node, Mark, Extension, Plugin, Decoration, Decoratio
|
|||||||
|
|
||||||
const buildDecorations = (doc) => {
|
const buildDecorations = (doc) => {
|
||||||
const decorations = []
|
const decorations = []
|
||||||
const regex = /\{\{?[a-zA-Z0-9_.-]+\}\}?/g
|
const regex = /\{\{?[^{}\n]+\}\}?/g
|
||||||
|
|
||||||
doc.descendants((node, pos) => {
|
doc.descendants((node, pos) => {
|
||||||
if (!node.isText) return
|
if (!node.isText) return
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export default actionable(targetable(class extends HTMLElement {
|
|||||||
|
|
||||||
const buildDecorations = (doc) => {
|
const buildDecorations = (doc) => {
|
||||||
const decorations = []
|
const decorations = []
|
||||||
const regex = /\{\{?[a-zA-Z0-9_.-]+\}\}?/g
|
const regex = /\{\{?[^{}\n]+\}\}?/g
|
||||||
|
|
||||||
doc.descendants((node, pos) => {
|
doc.descendants((node, pos) => {
|
||||||
if (!node.isText) return
|
if (!node.isText) return
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ export default {
|
|||||||
download () {
|
download () {
|
||||||
this.isDownloading = true
|
this.isDownloading = true
|
||||||
|
|
||||||
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`, {
|
fetch(this.baseUrl + `/s/${this.submitterSlug}/documents`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
...this.fetchOptions
|
...this.fetchOptions
|
||||||
}).then(async (response) => {
|
}).then(async (response) => {
|
||||||
|
|||||||
@@ -560,6 +560,7 @@
|
|||||||
:fields="formulaFields"
|
:fields="formulaFields"
|
||||||
:values="values"
|
:values="values"
|
||||||
:readonly-values="readonlyFieldValues"
|
:readonly-values="readonlyFieldValues"
|
||||||
|
:fetch-options="fetchOptions"
|
||||||
:provider="paymentProvider"
|
:provider="paymentProvider"
|
||||||
@attached="attachments.push($event)"
|
@attached="attachments.push($event)"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@@ -573,6 +574,7 @@
|
|||||||
:empty-value-required-step="emptyValueRequiredStep"
|
:empty-value-required-step="emptyValueRequiredStep"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
|
:fetch-options="fetchOptions"
|
||||||
:values="values"
|
:values="values"
|
||||||
@submit="!isSubmitting && submitStep()"
|
@submit="!isSubmitting && submitStep()"
|
||||||
/>
|
/>
|
||||||
@@ -585,6 +587,7 @@
|
|||||||
:empty-value-required-step="emptyValueRequiredStep"
|
:empty-value-required-step="emptyValueRequiredStep"
|
||||||
:field="currentField"
|
:field="currentField"
|
||||||
:submitter-slug="submitterSlug"
|
:submitter-slug="submitterSlug"
|
||||||
|
:fetch-options="fetchOptions"
|
||||||
:values="values"
|
:values="values"
|
||||||
@focus="scrollIntoField(currentField)"
|
@focus="scrollIntoField(currentField)"
|
||||||
@submit="!isSubmitting && submitStep()"
|
@submit="!isSubmitting && submitStep()"
|
||||||
@@ -1776,7 +1779,7 @@ export default {
|
|||||||
window.location.href = sanitizeUrl(this.completedRedirectUrl)
|
window.location.href = sanitizeUrl(this.completedRedirectUrl)
|
||||||
} else {
|
} else {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const root = this.$root.$el.parentNode.getRootNode()
|
const root = this.$root.$el.parentNode?.getRootNode() || document
|
||||||
const completedEl = root.getElementById('form_completed')
|
const completedEl = root.getElementById('form_completed')
|
||||||
|
|
||||||
if (completedEl) {
|
if (completedEl) {
|
||||||
|
|||||||
@@ -312,6 +312,11 @@ export default {
|
|||||||
values: {
|
values: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
fetchOptions: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['submit'],
|
emits: ['submit'],
|
||||||
@@ -494,8 +499,9 @@ export default {
|
|||||||
|
|
||||||
const resp = await fetch(this.baseUrl + '/api/kba', {
|
const resp = await fetch(this.baseUrl + '/api/kba', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: JSON.stringify(payload),
|
||||||
body: JSON.stringify(payload)
|
...this.fetchOptions,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...this.fetchOptions.headers }
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
@@ -550,7 +556,8 @@ export default {
|
|||||||
reference: this.reference,
|
reference: this.reference,
|
||||||
submitter_slug: this.submitterSlug
|
submitter_slug: this.submitterSlug
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
...this.fetchOptions,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...this.fetchOptions.headers }
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|||||||
@@ -121,6 +121,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
fetchOptions: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
||||||
@@ -234,7 +239,8 @@ export default {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
submitter_slug: this.submitterSlug
|
submitter_slug: this.submitterSlug
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
...this.fetchOptions,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...this.fetchOptions.headers }
|
||||||
}).then(async (resp) => {
|
}).then(async (resp) => {
|
||||||
if (resp.status === 422 || resp.status === 500) {
|
if (resp.status === 422 || resp.status === 500) {
|
||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
@@ -274,7 +280,8 @@ export default {
|
|||||||
check_status: checkStatus,
|
check_status: checkStatus,
|
||||||
referer: this.normalizedPaymentUrl()
|
referer: this.normalizedPaymentUrl()
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
...this.fetchOptions,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...this.fetchOptions.headers }
|
||||||
}).then(async (resp) => {
|
}).then(async (resp) => {
|
||||||
if (resp.status === 422 || resp.status === 500) {
|
if (resp.status === 422 || resp.status === 500) {
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resendCode () {
|
resendCode () {
|
||||||
if (this.codeSentAt && Date.now() - this.codeSentAt < 15000) {
|
if (this.codeSentAt && Date.now() - this.codeSentAt < 30000) {
|
||||||
this.startResendCodeCountdown()
|
this.startResendCodeCountdown()
|
||||||
} else {
|
} else {
|
||||||
this.isResendLoading = true
|
this.isResendLoading = true
|
||||||
@@ -274,7 +274,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
startResendCodeCountdown () {
|
startResendCodeCountdown () {
|
||||||
this.resendCodeCountdown = 15 - parseInt((Date.now() - this.codeSentAt) / 1000)
|
this.resendCodeCountdown = 30 - parseInt((Date.now() - this.codeSentAt) / 1000)
|
||||||
|
|
||||||
this.interval = setInterval(() => {
|
this.interval = setInterval(() => {
|
||||||
this.resendCodeCountdown--
|
this.resendCodeCountdown--
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ export default {
|
|||||||
submitterSlug: {
|
submitterSlug: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
fetchOptions: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
emits: ['focus', 'submit', 'update:model-value', 'attached'],
|
||||||
@@ -163,7 +168,8 @@ export default {
|
|||||||
submitter_slug: this.submitterSlug,
|
submitter_slug: this.submitterSlug,
|
||||||
redirect_url: document.location.href
|
redirect_url: document.location.href
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
...this.fetchOptions,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...this.fetchOptions.headers }
|
||||||
}).then(async (resp) => {
|
}).then(async (resp) => {
|
||||||
this.eidEasyData = await resp.json()
|
this.eidEasyData = await resp.json()
|
||||||
|
|
||||||
@@ -205,7 +211,8 @@ export default {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
submitter_slug: this.submitterSlug
|
submitter_slug: this.submitterSlug
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
...this.fetchOptions,
|
||||||
|
headers: { 'Content-Type': 'application/json', ...this.fetchOptions.headers }
|
||||||
})
|
})
|
||||||
|
|
||||||
if (resp.status === 404) {
|
if (resp.status === 404) {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
data-turbo="false"
|
data-turbo="false"
|
||||||
class="inline"
|
class="inline"
|
||||||
method="post"
|
method="post"
|
||||||
:action="`/d/${template.slug}`"
|
:action="`/start_form_self/${template.id}`"
|
||||||
@submit="maybeShowErrorTemplateAlert"
|
@submit="maybeShowErrorTemplateAlert"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -125,12 +125,6 @@
|
|||||||
:value="authenticityToken"
|
:value="authenticityToken"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
>
|
>
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="selfsign"
|
|
||||||
value="true"
|
|
||||||
autocomplete="off"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
class="btn btn-primary btn-ghost text-base hidden md:flex"
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -2377,7 +2371,6 @@ export default {
|
|||||||
const areaCopy = JSON.parse(JSON.stringify(area))
|
const areaCopy = JSON.parse(JSON.stringify(area))
|
||||||
|
|
||||||
delete fieldCopy.areas
|
delete fieldCopy.areas
|
||||||
delete fieldCopy.submitter_uuid
|
|
||||||
|
|
||||||
areaCopy.relativeX = area.x - minX
|
areaCopy.relativeX = area.x - minX
|
||||||
areaCopy.relativeY = area.y - minY
|
areaCopy.relativeY = area.y - minY
|
||||||
@@ -2387,6 +2380,7 @@ export default {
|
|||||||
|
|
||||||
const clipboardData = {
|
const clipboardData = {
|
||||||
items,
|
items,
|
||||||
|
submitters: this.template.submitters.map((submitter) => ({ uuid: submitter.uuid, name: submitter.name })),
|
||||||
templateId: this.template.id,
|
templateId: this.template.id,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
isGroup: true
|
isGroup: true
|
||||||
@@ -2497,6 +2491,20 @@ export default {
|
|||||||
|
|
||||||
const fieldUuidIndex = {}
|
const fieldUuidIndex = {}
|
||||||
const fieldOptionsMap = {}
|
const fieldOptionsMap = {}
|
||||||
|
const submitterUuidsMap = {}
|
||||||
|
|
||||||
|
const copiedSubmitterUuids = [...new Set(data.items.map((item) => item.field.submitter_uuid).filter(Boolean))]
|
||||||
|
|
||||||
|
if (copiedSubmitterUuids.length > 1) {
|
||||||
|
copiedSubmitterUuids.forEach((uuid) => {
|
||||||
|
const name = data.submitters?.find((submitter) => submitter.uuid === uuid)?.name
|
||||||
|
|
||||||
|
const submitter = this.template.submitters.find((s) => s.uuid === uuid) ||
|
||||||
|
(name && this.template.submitters.find((s) => s.name.toLowerCase() === name.toLowerCase()))
|
||||||
|
|
||||||
|
submitterUuidsMap[uuid] = (submitter || this.selectedSubmitter).uuid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
data.items.forEach((item) => {
|
data.items.forEach((item) => {
|
||||||
const field = JSON.parse(JSON.stringify(item.field))
|
const field = JSON.parse(JSON.stringify(item.field))
|
||||||
@@ -2521,7 +2529,7 @@ export default {
|
|||||||
const newField = fieldUuidIndex[field.uuid] || {
|
const newField = fieldUuidIndex[field.uuid] || {
|
||||||
...field,
|
...field,
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
submitter_uuid: submitterUuidsMap[field.submitter_uuid] || this.selectedSubmitter.uuid,
|
||||||
areas: []
|
areas: []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3531,6 +3539,8 @@ export default {
|
|||||||
this.onChange(this.template)
|
this.onChange(this.template)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.pushUndo()
|
||||||
|
|
||||||
if (!this.autosave && !force) {
|
if (!this.autosave && !force) {
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
@@ -3541,8 +3551,6 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.pushUndo()
|
|
||||||
|
|
||||||
return this.baseFetch(`/templates/${this.template.id}`, {
|
return this.baseFetch(`/templates/${this.template.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
@@ -555,8 +555,10 @@ export default {
|
|||||||
return this.template.schema.some((item) => item.dynamic)
|
return this.template.schema.some((item) => item.dynamic)
|
||||||
},
|
},
|
||||||
numberOfPages () {
|
numberOfPages () {
|
||||||
return this.template.documents.reduce((acc, doc) => {
|
return this.template.schema.reduce((acc, item) => {
|
||||||
return acc + doc.metadata?.pdf?.number_of_pages || doc.preview_images.length
|
const doc = this.template.documents.find((d) => d.uuid === item.attachment_uuid)
|
||||||
|
|
||||||
|
return acc + (doc?.metadata?.pdf?.number_of_pages || doc?.preview_images?.length || 0)
|
||||||
}, 0)
|
}, 0)
|
||||||
},
|
},
|
||||||
isShowFieldSearch () {
|
isShowFieldSearch () {
|
||||||
@@ -774,9 +776,10 @@ export default {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
...(this.withDetectExistingFields
|
body: JSON.stringify({
|
||||||
? { body: JSON.stringify({ fields: this.buildExistingFields() }) }
|
attachment_uuid: this.template.schema.map((item) => item.attachment_uuid),
|
||||||
: {})
|
...(this.withDetectExistingFields ? { fields: this.buildExistingFields() } : {})
|
||||||
|
})
|
||||||
}).then(async (response) => {
|
}).then(async (response) => {
|
||||||
const reader = response.body.getReader()
|
const reader = response.body.getReader()
|
||||||
const decoder = new TextDecoder('utf-8')
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
|||||||
@@ -366,6 +366,9 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
document.getElementById('list_form_buttons')?.classList?.add('hidden')
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
t (key) {
|
t (key) {
|
||||||
return this.i18n[key] || key
|
return this.i18n[key] || key
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class GeneratePreviewImagesJob
|
|
||||||
include Sidekiq::Job
|
|
||||||
|
|
||||||
sidekiq_options queue: :images
|
|
||||||
|
|
||||||
def perform(params = {})
|
|
||||||
attachment = ActiveStorage::Attachment.find(params['attachment_id'])
|
|
||||||
|
|
||||||
max_page = [attachment.metadata['pdf']['number_of_pages'].to_i - 1,
|
|
||||||
Templates::ProcessDocument::MAX_NUMBER_OF_PAGES_PROCESSED].min
|
|
||||||
|
|
||||||
Templates::ProcessDocument.generate_document_preview_images(attachment, attachment.download, 1..max_page,
|
|
||||||
concurrency: 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -123,13 +123,18 @@ class ProcessSubmitterCompletionJob
|
|||||||
|
|
||||||
user = submission.created_by_user || template.author
|
user = submission.created_by_user || template.author
|
||||||
|
|
||||||
|
copy_email_configs = AccountConfigs.find_or_initialize_for_key(submitter.account,
|
||||||
|
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
||||||
|
|
||||||
|
is_copy_email_enabled = documents_copy_email_enabled?(submitter, copy_email_configs)
|
||||||
|
|
||||||
if submitter.account.users.exists?(id: user.id) && submission.preferences['send_email'] != false &&
|
if submitter.account.users.exists?(id: user.id) && submission.preferences['send_email'] != false &&
|
||||||
(!template || template.preferences['completed_notification_email_enabled'] != false)
|
(!template || template.preferences['completed_notification_email_enabled'] != false)
|
||||||
user_submitter = submission.submitters.find { |s| s.email == user.email }
|
user_submitter = submission.submitters.find { |s| s.email == user.email }
|
||||||
|
|
||||||
is_sent_to_user =
|
is_sent_to_user =
|
||||||
if user.role != 'integration' &&
|
if user.role != 'integration' && Users.send_emails?(user) &&
|
||||||
(!user_submitter || user_submitter.preferences['send_email'] == false) &&
|
(!user_submitter || user_submitter.preferences['send_email'] == false || !is_copy_email_enabled) &&
|
||||||
user.user_configs.find_by(key: UserConfig::RECEIVE_COMPLETED_EMAIL)&.value != false
|
user.user_configs.find_by(key: UserConfig::RECEIVE_COMPLETED_EMAIL)&.value != false
|
||||||
SubmitterMailer.completed_email(submitter, user).deliver_later!
|
SubmitterMailer.completed_email(submitter, user).deliver_later!
|
||||||
|
|
||||||
@@ -139,11 +144,11 @@ class ProcessSubmitterCompletionJob
|
|||||||
enqueue_bcc_completed_emails(submitter, user, is_sent_to_user)
|
enqueue_bcc_completed_emails(submitter, user, is_sent_to_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
maybe_enqueue_copy_emails(submitter)
|
enqueue_copy_emails(submitter, copy_email_configs) if is_copy_email_enabled
|
||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_bcc_completed_emails(submitter, user, is_sent_to_user)
|
def enqueue_bcc_completed_emails(submitter, user, is_sent_to_user)
|
||||||
bcc_addresses = build_bcc_addresses(submitter.submission)
|
bcc_addresses = build_bcc_addresses(submitter.submission).uniq
|
||||||
|
|
||||||
raise TooManyBcc, submitter.account_id if Docuseal.multitenant? && bcc_addresses.size > BCC_LIMIT
|
raise TooManyBcc, submitter.account_id if Docuseal.multitenant? && bcc_addresses.size > BCC_LIMIT
|
||||||
|
|
||||||
@@ -154,14 +159,13 @@ class ProcessSubmitterCompletionJob
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_enqueue_copy_emails(submitter)
|
def documents_copy_email_enabled?(submitter, configs)
|
||||||
return if submitter.template&.preferences&.dig('documents_copy_email_enabled') == false
|
return false if submitter.template&.preferences&.dig('documents_copy_email_enabled') == false
|
||||||
|
|
||||||
configs = AccountConfigs.find_or_initialize_for_key(submitter.account,
|
configs.value['enabled'] != false
|
||||||
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
end
|
||||||
|
|
||||||
return if configs.value['enabled'] == false
|
|
||||||
|
|
||||||
|
def enqueue_copy_emails(submitter, configs)
|
||||||
to = submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
|
to = submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
|
||||||
.sort_by { |e| e.completed_at || Time.current }.select(&:email?).map(&:friendly_name)
|
.sort_by { |e| e.completed_at || Time.current }.select(&:email?).map(&:friendly_name)
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class EmailMessage < ApplicationRecord
|
|||||||
|
|
||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||||
|
|
||||||
|
normalizes :body, with: ->(value) { value.to_s }, apply_to_nil: true
|
||||||
|
|
||||||
before_validation :set_sha1, on: :create
|
before_validation :set_sha1, on: :create
|
||||||
|
|
||||||
def normalized_body
|
def normalized_body
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ class Submission < ApplicationRecord
|
|||||||
api: 'api',
|
api: 'api',
|
||||||
embed: 'embed',
|
embed: 'embed',
|
||||||
mcp: 'mcp',
|
mcp: 'mcp',
|
||||||
link: 'link'
|
link: 'link',
|
||||||
|
self: 'self'
|
||||||
}, scope: false, prefix: true
|
}, scope: false, prefix: true
|
||||||
|
|
||||||
enum :submitters_order, {
|
enum :submitters_order, {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class Submitter < ApplicationRecord
|
|||||||
|
|
||||||
def friendly_name
|
def friendly_name
|
||||||
if name.present? && email.present? && email.exclude?(',')
|
if name.present? && email.present? && email.exclude?(',')
|
||||||
%("#{name.delete('"')}" <#{email}>)
|
%("#{name.delete('"').squish}" <#{email}>)
|
||||||
else
|
else
|
||||||
email
|
email
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<%= svg_icon('discount_check_filled', class: 'w-6 h-6 shrink-0 text-green-500') %>
|
<%= svg_icon('discount_check_filled', class: 'w-6 h-6 shrink-0 text-green-500') %>
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<span class="whitespace-nowrap"><%= t('docuseal_trusted_signature') %></span>
|
<span class="whitespace-nowrap"><%= t('docuseal_trusted_signature') %></span>
|
||||||
<div class="tooltip tooltip-top ml-1 shrink-0" data-tip="<%= t('sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature') %>">
|
<div class="tooltip tooltip-top ml-1 shrink-0 hidden md:flex" data-tip="<%= t('sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature') %>">
|
||||||
<%= svg_icon('circle_question', class: 'w-4 h-4 stroke-1 shrink-0') %>
|
<%= svg_icon('circle_question', class: 'w-4 h-4 stroke-1 shrink-0') %>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="tooltip" data-tip="<%= t('unlock_with_docuseal_pro') %>">
|
<div class="md:tooltip" data-tip="<%= t('unlock_with_docuseal_pro') %>">
|
||||||
<%= button_to settings_esign_path, method: :put, params: { name: Docuseal::AATL_CERT_NAME }, class: 'btn btn-outline btn-neutral btn-xs whitespace-nowrap', title: t('make_default'), disabled: true do %>
|
<%= button_to settings_esign_path, method: :put, params: { name: Docuseal::AATL_CERT_NAME }, class: 'btn btn-outline btn-neutral btn-xs whitespace-nowrap', title: t('make_default'), disabled: true do %>
|
||||||
<%= t('make_default') %>
|
<%= t('make_default') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('timeserver_url') %>
|
<%= t('timeserver_url') %>
|
||||||
</span>
|
</span>
|
||||||
<span class="tooltip" data-tip="<%= t('url_of_the_trusted_rfc_3161_timeserver_to_be_used_to_generate_timestamp_signatures') %>">
|
<span class="tooltip hidden md:flex" data-tip="<%= t('url_of_the_trusted_rfc_3161_timeserver_to_be_used_to_generate_timestamp_signatures') %>">
|
||||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
<%= t('mcp_server') %>
|
<%= t('mcp_server') %>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex flex-col md:flex-row gap-y-2 gap-x-4 md:items-center">
|
<div class="flex flex-col md:flex-row gap-y-2 gap-x-4 md:items-center">
|
||||||
<div class="tooltip">
|
<%= link_to new_settings_mcp_path, class: 'btn btn-primary btn-md gap-2 w-full md:w-fit', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= link_to new_settings_mcp_path, class: 'btn btn-primary btn-md gap-2 w-full md:w-fit', data: { turbo_frame: 'modal' } do %>
|
<%= svg_icon('plus', class: 'w-6 h-6') %>
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6') %>
|
<span><%= t('new_token') %></span>
|
||||||
<span><%= t('new_token') %></span>
|
<% end %>
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if @mcp_token.present? %>
|
<% if @mcp_token.present? %>
|
||||||
|
|||||||
@@ -21,13 +21,15 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'bcc_form', config: @bcc_config %>
|
<%= render 'bcc_form', config: @bcc_config %>
|
||||||
<div class="flex justify-between items-end mb-4 mt-8">
|
<% unless can?(:manage, :personalization_restricted) %>
|
||||||
<h2 class="text-2xl md:text-3xl font-bold">
|
<div class="flex justify-between items-end mb-4 mt-8">
|
||||||
<%= t('sign_request_email_reminders') %>
|
<h2 class="text-2xl md:text-3xl font-bold">
|
||||||
</h2>
|
<%= t('sign_request_email_reminders') %>
|
||||||
</div>
|
</h2>
|
||||||
<%= render 'reminder_banner' %>
|
</div>
|
||||||
<%= render 'reminder_form', config: @reminder_config %>
|
<%= render 'reminder_banner' %>
|
||||||
|
<%= render 'reminder_form', config: @reminder_config %>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-0 md:w-52"></div>
|
<div class="w-0 md:w-52"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -6,11 +6,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'flex flex-col gap-4' } do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents, :bcc_recipients, :enabled).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents', 'bcc_recipients', 'enabled')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents, :bcc_recipients, :enabled).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents', 'bcc_recipients', 'enabled')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||||
|
<span class="tooltip tooltip-right hidden md:flex" data-tip="<%= t('completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'personalization_settings/email_body_field', ff:, config: f.object %>
|
<%= render 'personalization_settings/email_body_field', ff:, config: f.object %>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::FORM_COMPLETED_BUTTON_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::FORM_COMPLETED_BUTTON_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'flex flex-col gap-4' } do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<%= f.fields_for :value, Struct.new(:title, :url).new(*(f.object.value || {}).values_at('title', 'url')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:title, :url).new(*(f.object.value || {}).values_at('title', 'url')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::FORM_COMPLETED_MESSAGE_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::FORM_COMPLETED_MESSAGE_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'flex flex-col gap-4' } do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<%= f.fields_for :value, Struct.new(:title, :body).new(*(f.object.value || {}).values_at('title', 'body')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:title, :body).new(*(f.object.value || {}).values_at('title', 'body')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::POLICY_LINKS_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::POLICY_LINKS_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'flex flex-col gap-4' } do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<autoresize-textarea>
|
<autoresize-textarea>
|
||||||
|
|||||||
@@ -6,11 +6,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'flex flex-col gap-4' } do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to).new(*f.object.value.values_at('subject', 'body', 'reply_to')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to).new(*f.object.value.values_at('subject', 'body', 'reply_to')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||||
|
<span class="tooltip tooltip-right hidden md:flex" data-tip="<%= t('signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'personalization_settings/email_body_field', ff:, config: f.object %>
|
<%= render 'personalization_settings/email_body_field', ff:, config: f.object %>
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'flex flex-col gap-4' } do |f| %>
|
||||||
<%= f.hidden_field :key %>
|
<%= f.hidden_field :key %>
|
||||||
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||||
<span class="tooltip" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::EMAIL_VARIABLES[AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY].map { |v| "{#{v}}" }.join(', ') %>">
|
<span class="tooltip tooltip-right hidden md:flex" data-tip="<%= t('notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties') %> <%= t('use_following_placeholders_text_') %> <%= AccountConfig::EMAIL_VARIABLES[AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY].map { |v| "{#{v}}" }.join(', ') %>">
|
||||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<%= render 'banner' %>
|
<%= render 'start_form/banner' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { template_slug: @template.slug, email: params[:email] }, class: 'base-button w-full' %>
|
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { template_slug: @template.slug, email: params[:email] }, class: 'base-button w-full' %>
|
||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && %w[api embed mcp].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @template.shared_link? %>
|
<% if @template.shared_link? && Templates.filter_undefined_submitters(@template.submitters).size == 1 && %w[api embed mcp].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %>
|
||||||
<toggle-submit class="block">
|
<toggle-submit class="block">
|
||||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: params.permit(:name, :email, :phone).compact_blank, resubmit: true }, method: :put, class: 'white-button w-full' %>
|
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: params.permit(:name, :email, :phone).compact_blank, resubmit: true }, method: :put, class: 'white-button w-full' %>
|
||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
|
|||||||
@@ -23,12 +23,12 @@
|
|||||||
<div>
|
<div>
|
||||||
<%= t('we_sent_a_one_time_verification_code_to_your_email_address_please_enter_the_code_below_to_continue') %>
|
<%= t('we_sent_a_one_time_verification_code_to_your_email_address_please_enter_the_code_below_to_continue') %>
|
||||||
</div>
|
</div>
|
||||||
<%= form_for '', url: start_form_path(@template.slug), method: :put, html: { class: 'space-y-4', id: 'code_form' } do |f| %>
|
<%= form_for '', url: @resubmit_submitter ? resubmit_form_path : start_form_path(@template.slug), method: :put, html: { class: 'space-y-4', id: 'code_form' } do |f| %>
|
||||||
<div dir="auto" class="form-control !mt-0">
|
<div dir="auto" class="form-control !mt-0">
|
||||||
<%= f.hidden_field 'resubmit', value: params[:resubmit] %>
|
<%= f.hidden_field 'resubmit', value: params[:resubmit] %>
|
||||||
<%= f.hidden_field 'submitter[name]', value: params[:name] || @submitter&.name %>
|
<%= f.hidden_field 'submitter[name]', value: params[:name] || params.dig(:submitter, :name) %>
|
||||||
<%= f.hidden_field 'submitter[email]', value: params[:email] || @submitter&.email %>
|
<%= f.hidden_field 'submitter[email]', value: params[:email] || params.dig(:submitter, :email) %>
|
||||||
<%= f.hidden_field 'submitter[phone]', value: params[:phone] || @submitter&.phone %>
|
<%= f.hidden_field 'submitter[phone]', value: params[:phone] || params.dig(:submitter, :phone) %>
|
||||||
<%= f.text_field :one_time_code, required: true, inputmode: 'numeric', class: 'base-input text-center', placeholder: 'XXXXXX' %>
|
<%= f.text_field :one_time_code, required: true, inputmode: 'numeric', class: 'base-input text-center', placeholder: 'XXXXXX' %>
|
||||||
<div class="flex justify-between items-center mt-1">
|
<div class="flex justify-between items-center mt-1">
|
||||||
<span>
|
<span>
|
||||||
@@ -49,6 +49,10 @@
|
|||||||
<%= f.button button_title(title: t('submit')), class: 'base-button' %>
|
<%= f.button button_title(title: t('submit')), class: 'base-button' %>
|
||||||
</toggle-submit>
|
</toggle-submit>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= button_to t(:re_send_email), start_form_email_2fa_send_index_path, params: { slug: @template.slug, resend: true, submitter: { name: params[:name] || @submitter&.name, email: params[:email] || @submitter&.email, phone: params[:phone] || @submitter&.phone } }.compact, method: :post, id: 'resend_code', class: 'hidden' %>
|
<% if @resubmit_submitter %>
|
||||||
|
<%= button_to t(:re_send_email), resubmit_form_path, params: { resubmit: @resubmit_submitter.slug }, method: :put, id: 'resend_code', class: 'hidden' %>
|
||||||
|
<% else %>
|
||||||
|
<%= button_to t(:re_send_email), start_form_email_2fa_send_index_path, params: { slug: @template.slug, resend: true, submitter: { name: params[:name] || params.dig(:submitter, :name), email: params[:email] || params.dig(:submitter, :email), phone: params[:phone] || params.dig(:submitter, :phone) } }.compact, method: :post, id: 'resend_code', class: 'hidden' %>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<% content_for(:html_title, "#{@template.name} | DocuSeal") %>
|
||||||
|
<% I18n.with_locale(@template.account.locale) do %>
|
||||||
|
<% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %>
|
||||||
|
<% end %>
|
||||||
|
<main class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||||
|
<div class="space-y-6 mx-auto">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<%= render 'start_form/banner' %>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="mr-3">
|
||||||
|
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-lg font-bold mb-1"><%= @template.name %></p>
|
||||||
|
<p dir="auto" class="text-sm"><%= t('invited_by_html', name: @template.account.name) %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="alert items-start bg-base-100 border border-base-300">
|
||||||
|
<%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6 mt-1') %>
|
||||||
|
<div class="text-sm"><%= @error_message %></div>
|
||||||
|
</div>
|
||||||
|
<div dir="auto" class="form-control">
|
||||||
|
<%= link_to t('back'), @resubmit_submitter ? submit_form_path(@resubmit_submitter.slug) : start_form_path(@template.slug, @submitter.slice(:name, :email, :phone).compact_blank), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<main class="max-w-md space-y-6 mx-auto px-2 mt-12 mb-4">
|
<main class="max-w-md space-y-6 mx-auto px-2 mt-12 mb-4">
|
||||||
<div class="text-center w-full space-y-6">
|
<div class="text-center w-full space-y-6">
|
||||||
<%= render 'banner' %>
|
<%= render 'start_form/banner' %>
|
||||||
<p class="text-xl font-semibold text-center">
|
<p class="text-xl font-semibold text-center">
|
||||||
<%= t('share_link_is_currently_disabled') %>
|
<%= t('share_link_is_currently_disabled') %>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="space-y-6 mx-auto">
|
<div class="space-y-6 mx-auto">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="text-center w-full space-y-6">
|
<div class="text-center w-full space-y-6">
|
||||||
<%= render 'banner' %>
|
<%= render 'start_form/banner' %>
|
||||||
<% unless @template.archived_at? %>
|
<% unless @template.archived_at? %>
|
||||||
<p class="text-xl font-semibold text-center"><%= t('you_have_been_invited_to_submit_a_form') %></p>
|
<p class="text-xl font-semibold text-center"><%= t('you_have_been_invited_to_submit_a_form') %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -28,7 +28,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if !@template.archived_at? && !@template.account.archived_at? %>
|
<% if !@template.archived_at? && !@template.account.archived_at? %>
|
||||||
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
|
<%= form_for @submitter, url: @resubmit_submitter ? resubmit_form_path : start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
|
||||||
|
<%= hidden_field_tag :resubmit, @resubmit_submitter.slug if @resubmit_submitter %>
|
||||||
<% if @error_message %>
|
<% if @error_message %>
|
||||||
<div class="alert items-start bg-base-100 border border-red-400 text-red-400">
|
<div class="alert items-start bg-base-100 border border-red-400 text-red-400">
|
||||||
<%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6 mt-1') %>
|
<%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6 mt-1') %>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<%= l(event.event_timestamp.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
|
<%= l(event.event_timestamp.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
|
||||||
</span>
|
</span>
|
||||||
<% if (device = DetectBrowserDevice.call(event.data['ua'])) %>
|
<% if (device = DetectBrowserDevice.call(event.data['ua'])) %>
|
||||||
<span class="tooltip tooltip-top" data-tip="<%= t(device) %>">
|
<span class="md:tooltip tooltip-top" data-tip="<%= t(device) %>">
|
||||||
<%= svg_icon("device_#{device}", class: 'w-4 h-4') %>
|
<%= svg_icon("device_#{device}", class: 'w-4 h-4') %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<%= f.label :preserve_order, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %>
|
<%= f.label :preserve_order, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %>
|
||||||
<%= f.check_box :preserve_order, id: uuid, class: 'base-checkbox', checked: last_submission&.submitters_order.in?(['preserved', nil]) %>
|
<%= f.check_box :preserve_order, id: uuid, class: 'base-checkbox', checked: last_submission&.submitters_order.in?(['preserved', nil]) %>
|
||||||
<span class="label"><%= t('preserve_order') %></span>
|
<span class="label"><%= t('preserve_order') %></span>
|
||||||
<span class="tooltip" data-tip="<%= t('when_checked_notifications_will_be_sent_to_the_second_party_once_the_form_is_completed_by_the_previous_party_uncheck_this_option_to_send_notifications_to_all_parties_simultaneously_right_away') %>">
|
<span class="tooltip hidden md:flex" data-tip="<%= t('when_checked_notifications_will_be_sent_to_the_second_party_once_the_form_is_completed_by_the_previous_party_uncheck_this_option_to_send_notifications_to_all_parties_simultaneously_right_away') %>">
|
||||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center md:items-end gap-2">
|
<div class="flex items-center md:items-end gap-2">
|
||||||
<%= render 'submissions_filters/applied_filters', filter_params:, with_status: true %>
|
<%= render 'submissions_filters/applied_filters', filter_params:, with_status: true, with_default_status: true %>
|
||||||
<%= render 'submissions_filters/filter_button', filter_params: %>
|
<%= render 'submissions_filters/filter_button', filter_params: %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% view_archived_html = capture do %>
|
<% view_archived_html = capture do %>
|
||||||
<% if can?(:manage, :countless) || current_account.submissions.where.not(archived_at: nil).exists? %>
|
<% if can?(:manage, :countless) || current_account.submissions.archived.exists? || current_account.templates.where(Submission.where(Submission.arel_table[:template_id].eq(::Template.arel_table[:id])).arel.exists).archived.exists? %>
|
||||||
<div>
|
<div>
|
||||||
<a href="<%= submissions_archived_index_path %>" class="link text-sm"><%= t('view_archived') %></a>
|
<a href="<%= submissions_archived_index_path %>" class="link text-sm"><%= t('view_archived') %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,15 +3,16 @@
|
|||||||
<% ordered_filters = request.query_parameters.keys.filter_map { |key| filter_names.find { |name| key.start_with?(name) } }.uniq %>
|
<% ordered_filters = request.query_parameters.keys.filter_map { |key| filter_names.find { |name| key.start_with?(name) } }.uniq %>
|
||||||
<% chip_order = ordered_filters.reverse.each_with_index.to_h { |name, index| [name, index + 1] } %>
|
<% chip_order = ordered_filters.reverse.each_with_index.to_h { |name, index| [name, index + 1] } %>
|
||||||
<% chip_order.default = ordered_filters.size + 1 %>
|
<% chip_order.default = ordered_filters.size + 1 %>
|
||||||
<% status_icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit', 'sent' => 'send', 'opened' => 'mail_opened' }[params[:status]] %>
|
|
||||||
<% status_icons = { 'all' => 'list', 'pending' => 'clock', 'completed' => 'circle_check' } %>
|
<% status_icons = { 'all' => 'list', 'pending' => 'clock', 'completed' => 'circle_check' } %>
|
||||||
<% current_status = status_icons.key?(params[:status].to_s) ? params[:status].to_s : 'all' %>
|
<% current_status = status_icons.key?(params[:status].to_s) ? params[:status].to_s : 'all' %>
|
||||||
|
<% status_icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit', 'sent' => 'send', 'opened' => 'mail_opened' }[params[:status]] %>
|
||||||
|
<% default_status_icon = status_icons[params[:status]] if status_icon.blank? && local_assigns[:with_default_status] && current_status != 'all' %>
|
||||||
<% with_status_button = local_assigns[:with_status] && status_icon.blank? %>
|
<% with_status_button = local_assigns[:with_status] && status_icon.blank? %>
|
||||||
<% chips_html = capture do %>
|
<% status_chip_html = capture do %>
|
||||||
<% if status_icon %>
|
<% if status_icon || default_status_icon %>
|
||||||
<div class="order-none flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700">
|
<div class="order-none flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700 <%= 'max-md:hidden' if default_status_icon %>">
|
||||||
<%= link_to submissions_filter_path('status', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
<%= link_to submissions_filter_path('status', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
||||||
<%= svg_icon(status_icon, class: 'w-5 h-5 shrink-0') %>
|
<%= svg_icon(status_icon || default_status_icon, class: 'w-5 h-5 shrink-0') %>
|
||||||
<span class="font-normal truncate"><%= t(params[:status]) %></span>
|
<span class="font-normal truncate"><%= t(params[:status]) %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to url_for(params: request.query_parameters.except('status')), class: 'rounded-lg ml-1 shrink-0 hover:bg-base-content hover:text-white' do %>
|
<%= link_to url_for(params: request.query_parameters.except('status')), class: 'rounded-lg ml-1 shrink-0 hover:bg-base-content hover:text-white' do %>
|
||||||
@@ -19,6 +20,8 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% chips_html = capture do %>
|
||||||
<% if params[:folder].present? %>
|
<% if params[:folder].present? %>
|
||||||
<div class="order-<%= chip_order['folder'] %> md:order-none md:tooltip tooltip-no-touch tooltip-bottom flex md:flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700" data-tip="<%= t('folder') %>">
|
<div class="order-<%= chip_order['folder'] %> md:order-none md:tooltip tooltip-no-touch tooltip-bottom flex md:flex h-10 px-2 py-1 text-base md:text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl shrink-0 max-w-[60vw] md:w-36 border-neutral-700" data-tip="<%= t('folder') %>">
|
||||||
<%= link_to submissions_filter_path('folder', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
<%= link_to submissions_filter_path('folder', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %>
|
||||||
@@ -78,7 +81,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if with_status_button && chips_html.blank? %>
|
<% with_status_dropdown = with_status_button && chips_html.blank? %>
|
||||||
|
<% if with_status_dropdown %>
|
||||||
<div class="dropdown md:hidden shrink-0">
|
<div class="dropdown md:hidden shrink-0">
|
||||||
<label tabindex="0" class="cursor-pointer flex h-10 px-2 py-1 space-x-1 text-base items-center border text-neutral rounded-xl <%= current_status == 'all' ? 'border-neutral-300' : 'border-neutral-700' %>">
|
<label tabindex="0" class="cursor-pointer flex h-10 px-2 py-1 space-x-1 text-base items-center border text-neutral rounded-xl <%= current_status == 'all' ? 'border-neutral-300' : 'border-neutral-700' %>">
|
||||||
<%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %>
|
<%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %>
|
||||||
@@ -122,15 +126,16 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if chips_html.present? %>
|
<% if chips_html.present? || status_chip_html.present? %>
|
||||||
<scroll-fade class="flex items-center gap-2 overflow-x-auto flex-1 min-w-0 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [&::-webkit-scrollbar]:h-0 [&::-webkit-scrollbar]:w-0 md:contents">
|
<scroll-fade class="flex items-center gap-2 overflow-x-auto flex-1 min-w-0 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [&::-webkit-scrollbar]:h-0 [&::-webkit-scrollbar]:w-0 md:contents <%= 'max-md:hidden' if with_status_dropdown %>">
|
||||||
<% if with_status_button %>
|
<% if with_status_button && !with_status_dropdown %>
|
||||||
<a href="<%= submissions_filter_path('status', query_params.merge(path: url_for)) %>" data-turbo-frame="modal" class="order-none md:hidden flex h-10 px-2 py-1 space-x-1 text-base items-center border text-neutral rounded-xl shrink-0 <%= current_status == 'all' ? 'border-neutral-300' : 'border-neutral-700' %>">
|
<a href="<%= submissions_filter_path('status', query_params.merge(path: url_for)) %>" data-turbo-frame="modal" class="order-none md:hidden flex h-10 px-2 py-1 space-x-1 text-base items-center border text-neutral rounded-xl shrink-0 <%= current_status == 'all' ? 'border-neutral-300' : 'border-neutral-700' %>">
|
||||||
<%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %>
|
<%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %>
|
||||||
<span><%= t(current_status) %></span>
|
<span><%= t(current_status) %></span>
|
||||||
<%= svg_icon('chevron_down', class: 'w-4 h-4 shrink-0') %>
|
<%= svg_icon('chevron_down', class: 'w-4 h-4 shrink-0') %>
|
||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= status_chip_html %>
|
||||||
<%= chips_html %>
|
<%= chips_html %>
|
||||||
</scroll-fade>
|
</scroll-fade>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
<% path_uri = Addressable::URI.parse(params[:path].to_s) %>
|
||||||
|
<% path = url_for(**Rails.application.routes.recognize_path(path_uri.path, method: :get)) %>
|
||||||
<%= render 'shared/turbo_modal', title: local_assigns[:title] do %>
|
<%= render 'shared/turbo_modal', title: local_assigns[:title] do %>
|
||||||
<%= form_for '', url: params[:path], method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
<%= form_for '', url: path, method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
||||||
<%= hidden_field_tag :q, params[:q] if params[:q].present? %>
|
<%= hidden_field_tag :q, params[:q] if params[:q].present? %>
|
||||||
<% local_assigns[:default_params].each do |key, value| %>
|
<% local_assigns[:default_params].each do |key, value| %>
|
||||||
<%= hidden_field_tag(key, value) if value.present? %>
|
<%= hidden_field_tag(key, value) if value.present? %>
|
||||||
@@ -10,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% if params[:with_remove] %>
|
<% if params[:with_remove] %>
|
||||||
<div class="text-center w-full mt-4">
|
<div class="text-center w-full mt-4">
|
||||||
<%= link_to t('remove_filter'), "#{params[:path]}?#{request.query_parameters.slice('q').merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
|
<%= link_to t('remove_filter'), "#{path}?#{request.query_parameters.slice('q').merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template_submitters) %>
|
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template_submitters) %>
|
||||||
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && @submitter.completed_at? && @submitter.completed_at > 14.days.ago && %w[api embed mcp].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @submitter.template && !@submitter.template.archived_at? %>
|
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && Submitters::StartForm.can_resubmit?(@submitter) && @submitter.template && !@submitter.template.archived_at? %>
|
||||||
<div class="divider uppercase"><%= t('or') %></div>
|
<div class="divider uppercase"><%= t('or') %></div>
|
||||||
<toggle-submit class="block">
|
<toggle-submit class="block">
|
||||||
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), resubmit_form_path, params: { resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), resubmit_form_path, params: { resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
<div class="mockup-code overflow-hidden pb-0 mt-4">
|
||||||
<span class="top-0 right-0 absolute flex">
|
<span class="top-0 right-0 absolute flex">
|
||||||
<%= link_to t('learn_more'), console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
<%= link_to t('learn_more'), console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), target: '_blank', data: { turbo: false }, class: 'btn btn-ghost text-gray-100 flex', rel: 'noopener' %>
|
||||||
<clipboard-copy data-text="<script src="<%= Docuseal::CDN_URL %>/js/form.js"></script>
|
<clipboard-copy data-text="<script src="<%= Docuseal.multitenant? ? "#{Docuseal::CDN_URL}/js/form.js" : embed_script_url(filename: 'form.js') %>"></script>
|
||||||
|
|
||||||
<docuseal-form data-src="<%= start_form_url(slug: template.slug) %>"></docuseal-form>
|
<docuseal-form data-src="<%= start_form_url(slug: template.slug) %>"></docuseal-form>
|
||||||
">
|
">
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
</clipboard-copy>
|
</clipboard-copy>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #f4bf75"><script </span><span style="color: #6a9fb5">src=</span><span style="color: #90a959">"<%= Docuseal::CDN_URL %>/js/form.js"</span><span style="color: #f4bf75">></script></span>
|
<pre class="before:!m-0 pl-6 pb-4 overflow-auto"><code class="overflow-hidden w-full"><span style="color: #f4bf75"><script </span><span style="color: #6a9fb5">src=</span><span style="color: #90a959">"<%= Docuseal.multitenant? ? "#{Docuseal::CDN_URL}/js/form.js" : embed_script_url(filename: 'form.js') %>"</span><span style="color: #f4bf75">></script></span>
|
||||||
|
|
||||||
<span style="color: #f4bf75"><docuseal-form</span> <span style="color: #6a9fb5">data-src=</span><span style="color: #90a959">"<%= start_form_url(slug: template.slug) %>"</span><span style="color: #f4bf75">></span><span style="color: #f4bf75"></docuseal-form></span>
|
<span style="color: #f4bf75"><docuseal-form</span> <span style="color: #6a9fb5">data-src=</span><span style="color: #90a959">"<%= start_form_url(slug: template.slug) %>"</span><span style="color: #f4bf75">></span><span style="color: #f4bf75"></docuseal-form></span>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|||||||
@@ -35,14 +35,14 @@
|
|||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<span class="flex flex-col md:flex-row md:items-center gap-3">
|
<span class="flex flex-col md:flex-row md:items-center gap-3">
|
||||||
<% if submission.expired? && !submitter.completed_at? && !submitter.declined_at? %>
|
<% if submission.expired? && !submitter.completed_at? && !submitter.declined_at? %>
|
||||||
<div class="tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
<div class="md:tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
||||||
<%= t('expired') %>
|
<%= t('expired') %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<a href="<%= submission_path(submission) %>" class="flex z-[1]">
|
<a href="<%= submission_path(submission) %>" class="flex z-[1]">
|
||||||
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold tooltip" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold md:tooltip" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
<%= t(submitter.status) %>
|
<%= t(submitter.status) %>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<% elsif submission.expired? %>
|
<% elsif submission.expired? %>
|
||||||
<div class="tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
<div class="md:tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
|
||||||
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
|
||||||
<%= t('expired') %>
|
<%= t('expired') %>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -64,8 +64,12 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 w-full md:w-fit md:justify-between md:flex-none md:pt-1">
|
<div class="flex flex-wrap gap-2 w-full md:w-fit md:justify-between md:flex-none md:pt-1">
|
||||||
<% if !template.archived_at? && can?(:destroy, template) %>
|
<% if can?(:destroy, template) %>
|
||||||
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving')[..-4], title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete %>
|
<% if template.archived_at? %>
|
||||||
|
<%= button_to button_title(title: t('restore'), disabled_with: t('restoring')[..-4], icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1' %>
|
||||||
|
<% else %>
|
||||||
|
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving')[..-4], title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, current_account.templates.new(author: current_user)) %>
|
<% if can?(:create, current_account.templates.new(author: current_user)) %>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
@@ -99,9 +103,6 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if template.archived_at? %>
|
<% if template.archived_at? %>
|
||||||
<% if can?(:destroy, template) %>
|
|
||||||
<%= button_to button_title(title: t('restore'), disabled_with: t('restoring')[..-4], icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1' %>
|
|
||||||
<% end %>
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm w-full' do %>
|
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm w-full' do %>
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-2">
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
</open-modal-mobile>
|
</open-modal-mobile>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && @template.variables_schema.blank? %>
|
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && @template.variables_schema.blank? %>
|
||||||
<%= button_to start_form_path(@template.slug), params: { selfsign: true }, method: :put, class: 'white-button w-full', form: { style: 'display: inline', target: '_blank', data: { turbo: false } } do %>
|
<%= button_to start_form_self_path(@template), method: :put, class: 'white-button w-full', form: { style: 'display: inline', target: '_blank', data: { turbo: false } } do %>
|
||||||
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
<%= svg_icon('writing', class: 'w-6 h-6') %>
|
||||||
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
<span class="mr-1"><%= t('sign_it_yourself') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-nowrap md:flex-wrap md:justify-end items-center gap-2">
|
<div class="flex flex-nowrap md:flex-wrap md:justify-end items-center gap-2">
|
||||||
<%= render 'submissions_filters/applied_filters', filter_params:, with_status: true %>
|
<%= render 'submissions_filters/applied_filters', filter_params:, with_status: true, with_default_status: true %>
|
||||||
<div class="hidden md:block shrink-0 md:order-first">
|
<div class="hidden md:block shrink-0 md:order-first">
|
||||||
<%= link_to new_template_submissions_export_path(@template, archived: true), class: 'btn btn-ghost text-base h-10 min-h-10', data: { turbo_frame: 'modal' } do %>
|
<%= link_to new_template_submissions_export_path(@template, archived: true), class: 'btn btn-ghost text-base h-10 min-h-10', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
<%= svg_icon('download', class: 'w-6 h-6 stroke-2') %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<% has_archived = can?(:manage, :countless) || current_account.templates.where.not(archived_at: nil).exists? %>
|
<% has_archived = can?(:manage, :countless) || current_account.templates.archived.exists? %>
|
||||||
<% show_dropzone = params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
|
<% show_dropzone = params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %>
|
||||||
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
|
||||||
<dashboard-dropzone>
|
<dashboard-dropzone>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<%= f.hidden_field :parent_name, value: @template.folder.parent_folder&.name || @template.folder.name %>
|
<%= f.hidden_field :parent_name, value: @template.folder.parent_folder&.name || @template.folder.name %>
|
||||||
<toggle-visible data-element-ids="<%= %w[folder_form subfolder_form].to_json %>" class="block relative" data-focus-id="name">
|
<toggle-visible data-element-ids="<%= %w[folder_form subfolder_form].to_json %>" class="block relative" data-focus-id="name">
|
||||||
<div class="flex items-center justify-between mb-2.5">
|
<div class="flex items-center justify-between mb-2.5">
|
||||||
<label for="is_root_folder" class="flex items-center tooltip tooltip-right pr-2 group" data-tip="<%= t('change_parent_folder') %>">
|
<label for="is_root_folder" class="flex items-center md:tooltip tooltip-right pr-2 group" data-tip="<%= t('change_parent_folder') %>">
|
||||||
<%= check_box_tag :is_root_folder, 'folder_form', data: { action: 'change:toggle-visible#trigger' }, class: 'hidden' %>
|
<%= check_box_tag :is_root_folder, 'folder_form', data: { action: 'change:toggle-visible#trigger' }, class: 'hidden' %>
|
||||||
<span class="flex items-center mt-1">
|
<span class="flex items-center mt-1">
|
||||||
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0 group-hover:hidden mr-1') %>
|
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0 group-hover:hidden mr-1') %>
|
||||||
|
|||||||
@@ -10,7 +10,12 @@
|
|||||||
<%= f.fields_for :preferences, Struct.new(:completed_notification_email_subject, :completed_notification_email_body).new(@template.preferences['completed_notification_email_subject'].presence || configs['subject'], @template.preferences['completed_notification_email_body'].presence || configs['body']) do |ff| %>
|
<%= f.fields_for :preferences, Struct.new(:completed_notification_email_subject, :completed_notification_email_body).new(@template.preferences['completed_notification_email_subject'].presence || configs['subject'], @template.preferences['completed_notification_email_body'].presence || configs['body']) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<%= ff.label :completed_notification_email_subject, t('email_subject'), class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :completed_notification_email_subject, t('email_subject'), class: 'label' %>
|
||||||
|
<span class="tooltip tooltip-right hidden md:flex" data-tip="<%= t('notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<% if is_custom_template_email %>
|
<% if is_custom_template_email %>
|
||||||
<label for="submitter_completed_email_reset_link" class="label underline">
|
<label for="submitter_completed_email_reset_link" class="label underline">
|
||||||
<%= t('reset_default') %>
|
<%= t('reset_default') %>
|
||||||
|
|||||||
@@ -10,7 +10,12 @@
|
|||||||
<%= f.fields_for :preferences, Struct.new(:documents_copy_email_reply_to, :documents_copy_email_subject, :documents_copy_email_body).new(@template.preferences['documents_copy_email_reply_to'].presence || configs['reply_to'], @template.preferences['documents_copy_email_subject'].presence || configs['subject'], @template.preferences['documents_copy_email_body'].presence || configs['body']) do |ff| %>
|
<%= f.fields_for :preferences, Struct.new(:documents_copy_email_reply_to, :documents_copy_email_subject, :documents_copy_email_body).new(@template.preferences['documents_copy_email_reply_to'].presence || configs['reply_to'], @template.preferences['documents_copy_email_subject'].presence || configs['subject'], @template.preferences['documents_copy_email_body'].presence || configs['body']) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<%= ff.label :documents_copy_email_subject, t('email_subject'), class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :documents_copy_email_subject, t('email_subject'), class: 'label' %>
|
||||||
|
<span class="tooltip tooltip-right hidden md:flex" data-tip="<%= t('completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<% if is_custom_template_email %>
|
<% if is_custom_template_email %>
|
||||||
<label for="submitter_documents_copy_email_reset_link" class="label underline">
|
<label for="submitter_documents_copy_email_reset_link" class="label underline">
|
||||||
<%= t('reset_default') %>
|
<%= t('reset_default') %>
|
||||||
|
|||||||
@@ -16,7 +16,12 @@
|
|||||||
<%= f.fields_for :preferences, Struct.new(:request_email_subject, :request_email_body).new(*(template_email_preferences_values.presence || default_template_email_preferences_values)) do |ff| %>
|
<%= f.fields_for :preferences, Struct.new(:request_email_subject, :request_email_body).new(*(template_email_preferences_values.presence || default_template_email_preferences_values)) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<%= ff.label :request_email_subject, t('email_subject'), class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :request_email_subject, t('email_subject'), class: 'label' %>
|
||||||
|
<span class="tooltip tooltip-right hidden md:flex" data-tip="<%= t('signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<% if is_custom_template_email %>
|
<% if is_custom_template_email %>
|
||||||
<label for="submitter_invitation_email_reset_link" class="label underline">
|
<label for="submitter_invitation_email_reset_link" class="label underline">
|
||||||
<%= t('reset_default') %>
|
<%= t('reset_default') %>
|
||||||
|
|||||||
@@ -10,7 +10,12 @@
|
|||||||
<%= f.fields_for :preferences, Struct.new(:invitation_view_email_subject, :invitation_view_email_body).new(@template.preferences['invitation_view_email_subject'].presence || configs['subject'], @template.preferences['invitation_view_email_body'].presence || configs['body']) do |ff| %>
|
<%= f.fields_for :preferences, Struct.new(:invitation_view_email_subject, :invitation_view_email_body).new(@template.preferences['invitation_view_email_subject'].presence || configs['subject'], @template.preferences['invitation_view_email_body'].presence || configs['body']) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<%= ff.label :invitation_view_email_subject, t('email_subject'), class: 'label' %>
|
<div class="flex items-center">
|
||||||
|
<%= ff.label :invitation_view_email_subject, t('email_subject'), class: 'label' %>
|
||||||
|
<span class="tooltip tooltip-right hidden md:flex" data-tip="<%= t('view_only_email_sent_to_recipients_that_need_to_only_review_the_documents') %>">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<% if is_custom_template_email %>
|
<% if is_custom_template_email %>
|
||||||
<label for="submitter_view_invitation_email_reset_link" class="label underline">
|
<label for="submitter_view_invitation_email_reset_link" class="label underline">
|
||||||
<%= t('reset_default') %>
|
<%= t('reset_default') %>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %>
|
<%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %>
|
||||||
<% if @template.preferences&.dig('require_email_2fa') || @template.preferences&.dig('require_phone_2fa') %>
|
<% if @template.preferences&.dig('require_email_2fa') || @template.preferences&.dig('require_phone_2fa') %>
|
||||||
<label for="template_shared_link" class="tooltip tooltip-bottom flex items-center my-4 justify-between gap-1 alert bg-base-100 border-base-300" data-tip="<%= t(:templates_that_require_email_or_phone_2fa_cannot_be_used_via_a_shared_link) %>">
|
<label for="template_shared_link" class="md:tooltip tooltip-bottom flex items-center my-4 justify-between gap-1 alert bg-base-100 border-base-300" data-tip="<%= t(:templates_that_require_email_or_phone_2fa_cannot_be_used_via_a_shared_link) %>">
|
||||||
<span><%= t('enable_shared_link') %></span>
|
<span><%= t('enable_shared_link') %></span>
|
||||||
<%= check_box_tag 'shared_link', 'true', false, disabled: true, class: 'toggle' %>
|
<%= check_box_tag 'shared_link', 'true', false, disabled: true, class: 'toggle' %>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="tooltip" data-tip="<%= t('contact_your_administrator_to_add_new_users') %>">
|
<div class="md:tooltip" data-tip="<%= t('contact_your_administrator_to_add_new_users') %>">
|
||||||
<%= link_to '#', class: 'btn btn-primary btn-md gap-2 w-full md:w-fit btn-disabled', data: { turbo_frame: 'modal' } do %>
|
<%= link_to '#', class: 'btn btn-primary btn-md gap-2 w-full md:w-fit btn-disabled', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= svg_icon('plus', class: 'w-6 h-6') %>
|
<%= svg_icon('plus', class: 'w-6 h-6') %>
|
||||||
<span><%= t('new_user') %></span>
|
<span><%= t('new_user') %></span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div id="result">
|
<div id="result">
|
||||||
<% files.zip(pdfs).each do |file, pdf| %>
|
<% files.zip(signatures).each do |file, file_signatures| %>
|
||||||
<div class="mb-4 border border-base-300 rounded-md py-2 px-3">
|
<div class="mb-4 border border-base-300 rounded-md py-2 px-3">
|
||||||
<% if pdf.signatures.to_a.size == 0 %>
|
<% if file_signatures.size == 0 %>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<%= file.original_filename %>
|
<%= file.original_filename %>
|
||||||
</div>
|
</div>
|
||||||
@@ -11,42 +11,29 @@
|
|||||||
<% else %>
|
<% else %>
|
||||||
<div class="flex items-center space-x-1 border-b border-dashed border-base-300 pb-2">
|
<div class="flex items-center space-x-1 border-b border-dashed border-base-300 pb-2">
|
||||||
<%= svg_icon('file_text', class: 'w-5 h-5 inline') %>
|
<%= svg_icon('file_text', class: 'w-5 h-5 inline') %>
|
||||||
<span><%= file.original_filename %> - <%= pluralize(pdf.signatures.to_a.size, 'Signature') %></span>
|
<span><%= file.original_filename %> - <%= pluralize(file_signatures.size, 'Signature') %></span>
|
||||||
</div>
|
</div>
|
||||||
<% pdf.signatures.to_a.each do |signature| %>
|
<% file_signatures.each do |signature| %>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div class="space-y-1 font-medium pb-2 text-xl">
|
<div class="space-y-1 font-medium pb-2 text-xl">
|
||||||
<% signature.verify(trusted_certs:).messages.map(&:content).each do |message| %>
|
<% signature.messages.each do |message| %>
|
||||||
<p class="flex space-x-1 items-center">
|
<p class="flex space-x-1 items-center">
|
||||||
<% if message == 'Signature verification failed' %>
|
<% if message.status == :error %>
|
||||||
<%= svg_icon('x_circle', class: 'w-6 h-6 text-red-500') %>
|
<%= svg_icon('x_circle', class: 'w-6 h-6 text-red-500') %>
|
||||||
<% elsif message == 'Signature valid' %>
|
<% elsif message.status == :success %>
|
||||||
<%= svg_icon('circle_check', class: 'w-6 h-6 text-green-500') %>
|
<%= svg_icon('circle_check', class: 'w-6 h-6 text-green-500') %>
|
||||||
|
<% elsif message.status == :warning %>
|
||||||
|
<%= svg_icon('alert_circle', class: 'w-6 h-6 text-yellow-500') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span>
|
<span>
|
||||||
<%= message %>
|
<%= message.text %>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<% if message == 'Signature valid' %>
|
|
||||||
<p class="flex space-x-1 items-center">
|
|
||||||
<% if trusted_certs.any? { |e| e.public_key.to_der == signature.signature_handler.signer_certificate.public_key.to_der } %>
|
|
||||||
<%= svg_icon('circle_check', class: 'w-6 h-6 text-green-500') %>
|
|
||||||
<span>
|
|
||||||
<%= t('signed_with_trusted_certificate') %>
|
|
||||||
</span>
|
|
||||||
<% else %>
|
|
||||||
<%= svg_icon('x_circle', class: 'w-6 h-6 text-red-500') %>
|
|
||||||
<span>
|
|
||||||
<%= t('signed_with_external_certificate') %>
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('user', class: 'w-5 h-5 inline') %>
|
<%= svg_icon('user', class: 'w-5 h-5 inline') %>
|
||||||
<span><%= signature.signing_reason %></span>
|
<span><%= signature.reason %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('calendar', class: 'w-5 h-5 inline') %>
|
<%= svg_icon('calendar', class: 'w-5 h-5 inline') %>
|
||||||
@@ -54,11 +41,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('certificate', class: 'w-5 h-5 inline') %>
|
<%= svg_icon('certificate', class: 'w-5 h-5 inline') %>
|
||||||
<span><%= signature.signer_name.force_encoding('UTF-8') %></span>
|
<span><%= signature.common_name %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<%= svg_icon('lock_access', class: 'w-5 h-5 inline') %>
|
<%= svg_icon('lock_access', class: 'w-5 h-5 inline') %>
|
||||||
<span><%= signature.signature_type %></span>
|
<span><%= signature.type %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% if @webhook_events.present? || params[:status].present? %>
|
<% if @webhook_events.present? || params[:status].present? %>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<h2 id="log" class="text-2xl md:text-3xl font-bold"><%= t('events_log') %></h2>
|
<h2 id="log" class="text-2xl md:text-3xl font-bold"><%= t('event_log') %></h2>
|
||||||
<div class="tabs border-b mt-4">
|
<div class="tabs border-b mt-4">
|
||||||
<%= link_to t('all'), url_for(params: request.query_parameters.except('status', 'page')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status].blank? ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
<%= link_to t('all'), url_for(params: request.query_parameters.except('status', 'page')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status].blank? ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
||||||
<%= link_to t('succeeded'), url_for(params: request.query_parameters.except('page').merge('status' => 'success')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'success' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
<%= link_to t('succeeded'), url_for(params: request.query_parameters.except('page').merge('status' => 'success')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'success' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ module DocuSeal
|
|||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
config.load_defaults 8.1
|
config.load_defaults 8.1
|
||||||
|
|
||||||
|
config.active_support.message_serializer = :json
|
||||||
|
|
||||||
config.autoload_lib(ignore: %w[assets tasks puma])
|
config.autoload_lib(ignore: %w[assets tasks puma])
|
||||||
|
|
||||||
config.active_storage.routes_prefix = ''
|
config.active_storage.routes_prefix = ''
|
||||||
|
|||||||
+1
-1
@@ -105,6 +105,6 @@ if ENV['REDIS_URL'].to_s.empty?
|
|||||||
|
|
||||||
redis_password = Digest::SHA1.hexdigest("redis#{ENV.fetch('SECRET_KEY_BASE', '')}")
|
redis_password = Digest::SHA1.hexdigest("redis#{ENV.fetch('SECRET_KEY_BASE', '')}")
|
||||||
|
|
||||||
ENV['REDIS_URL'] = "redis://default:#{redis_password}@0.0.0.0:6379/0"
|
ENV['REDIS_URL'] = "redis://default:#{redis_password}@127.0.0.1:16379/0"
|
||||||
ENV['LOCAL_REDIS_URL'] = ENV.fetch('REDIS_URL', nil)
|
ENV['LOCAL_REDIS_URL'] = ENV.fetch('REDIS_URL', nil)
|
||||||
end
|
end
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
autoload :CSV, 'csv'
|
autoload :CSV, 'csv'
|
||||||
autoload :CSVSafe, 'csv-safe'
|
autoload :CSVSafe, 'csv-safe'
|
||||||
autoload :RubyXL, 'rubyXL'
|
autoload :Xlsxtream, 'xlsxtream'
|
||||||
autoload :Zip, 'zip'
|
autoload :Zip, 'zip'
|
||||||
autoload :Numo, 'numo/narray'
|
autoload :Numo, 'numo/narray'
|
||||||
autoload :OnnxRuntime, 'onnxruntime'
|
autoload :OnnxRuntime, 'onnxruntime'
|
||||||
|
|||||||
@@ -46,6 +46,20 @@ module HexaPDF
|
|||||||
def color_space(name)
|
def color_space(name)
|
||||||
GlobalConfiguration.constantize('color_space.map', name).new
|
GlobalConfiguration.constantize('color_space.map', name).new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def [](name)
|
||||||
|
return super unless value[name].nil? && INHERITABLE_FIELDS.include?(name)
|
||||||
|
|
||||||
|
seen = Set.new.compare_by_identity
|
||||||
|
seen << value
|
||||||
|
node = self
|
||||||
|
|
||||||
|
while node.value[name].nil? && (parent = node[:Parent]) && seen.add?(parent.value)
|
||||||
|
node = parent
|
||||||
|
end
|
||||||
|
|
||||||
|
node == self || node.value[name].nil? ? super : node[name]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# fix NoMethodError: undefined method `field_value' for #<HexaPDF::Type::AcroForm::Field
|
# fix NoMethodError: undefined method `field_value' for #<HexaPDF::Type::AcroForm::Field
|
||||||
@@ -112,6 +126,8 @@ module HexaPDF
|
|||||||
end
|
end
|
||||||
|
|
||||||
module CycleSafeInheritedValue
|
module CycleSafeInheritedValue
|
||||||
|
using HexaPDF::Type::AcroForm::Field::HashRefinement
|
||||||
|
|
||||||
def inherited_value(field, name)
|
def inherited_value(field, name)
|
||||||
seen = Set.new.compare_by_identity
|
seen = Set.new.compare_by_identity
|
||||||
seen << field.value
|
seen << field.value
|
||||||
@@ -167,8 +183,53 @@ module HexaPDF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module CycleSafePageAncestors
|
||||||
|
def index
|
||||||
|
acyclic_ancestors? ? super : 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def ancestor_nodes
|
||||||
|
acyclic_ancestors? ? super : []
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def perform_validation
|
||||||
|
super if acyclic_ancestors?
|
||||||
|
end
|
||||||
|
|
||||||
|
def acyclic_ancestors?
|
||||||
|
seen = Set.new.compare_by_identity
|
||||||
|
node = self
|
||||||
|
node = node[:Parent] while node && seen.add?(node.value)
|
||||||
|
node.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module CycleSafeOutlineEndpoints
|
||||||
|
private
|
||||||
|
|
||||||
|
def perform_validation
|
||||||
|
first = value[:First]
|
||||||
|
last = value[:Last]
|
||||||
|
|
||||||
|
if (first && !last) || (!first && last)
|
||||||
|
node, dir = first ? [self[:First], :Next] : [self[:Last], :Prev]
|
||||||
|
seen = Set.new.compare_by_identity
|
||||||
|
node = node[dir] while node && seen.add?(node.value)
|
||||||
|
|
||||||
|
return if node
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
HexaPDF::Type::AcroForm::Field.singleton_class.prepend(HexaPDF::CycleSafeInheritedValue)
|
HexaPDF::Type::AcroForm::Field.singleton_class.prepend(HexaPDF::CycleSafeInheritedValue)
|
||||||
HexaPDF::Type::AcroForm::Field.prepend(HexaPDF::CycleSafeFullFieldName)
|
HexaPDF::Type::AcroForm::Field.prepend(HexaPDF::CycleSafeFullFieldName)
|
||||||
HexaPDF::Type::AcroForm::Form.prepend(HexaPDF::CycleSafeEachField)
|
HexaPDF::Type::AcroForm::Form.prepend(HexaPDF::CycleSafeEachField)
|
||||||
|
HexaPDF::Type::Page.prepend(HexaPDF::CycleSafePageAncestors)
|
||||||
|
HexaPDF::Type::Outline.prepend(HexaPDF::CycleSafeOutlineEndpoints)
|
||||||
|
HexaPDF::Type::OutlineItem.prepend(HexaPDF::CycleSafeOutlineEndpoints)
|
||||||
+113
-1
@@ -228,6 +228,7 @@ en: &en
|
|||||||
profile_details: Profile Details
|
profile_details: Profile Details
|
||||||
sign_up_with_google: Sign up with Google
|
sign_up_with_google: Sign up with Google
|
||||||
sign_up_with_microsoft: Sign up with Microsoft
|
sign_up_with_microsoft: Sign up with Microsoft
|
||||||
|
redirecting_to_provider: Redirecting to %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'By creating an account, you agree to our <a target="_blank" href="https://www.docuseal.com/privacy">Privacy Policy</a> and <a target="_blank" href="https://www.docuseal.com/terms">Terms of Service</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'By creating an account, you agree to our <a target="_blank" href="https://www.docuseal.com/privacy">Privacy Policy</a> and <a target="_blank" href="https://www.docuseal.com/terms">Terms of Service</a>.'
|
||||||
enter_email_to_continue: Enter email to continue
|
enter_email_to_continue: Enter email to continue
|
||||||
account: Account
|
account: Account
|
||||||
@@ -596,6 +597,9 @@ en: &en
|
|||||||
there_are_no_signatures: There are no signatures
|
there_are_no_signatures: There are no signatures
|
||||||
signed_with_trusted_certificate: Signed with trusted certificate
|
signed_with_trusted_certificate: Signed with trusted certificate
|
||||||
signed_with_external_certificate: Signed with external certificate
|
signed_with_external_certificate: Signed with external certificate
|
||||||
|
signature_valid: Signature valid
|
||||||
|
signature_verification_failed: Signature verification failed
|
||||||
|
contains_unsigned_changes_after_the_last_signature: Contains unsigned changes after the last signature
|
||||||
setup_2fa_to_continue: Setup 2FA to continue.
|
setup_2fa_to_continue: Setup 2FA to continue.
|
||||||
create_a_new_account: Create a new account.
|
create_a_new_account: Create a new account.
|
||||||
you_are_already_signed_in: You are already signed in.
|
you_are_already_signed_in: You are already signed in.
|
||||||
@@ -786,6 +790,7 @@ en: &en
|
|||||||
the_code_has_been_sent_to_your_email: The code has been sent to your email.
|
the_code_has_been_sent_to_your_email: The code has been sent to your email.
|
||||||
enter_the_verification_code_from_your_email: Enter the verification code from your email.
|
enter_the_verification_code_from_your_email: Enter the verification code from your email.
|
||||||
too_many_attempts: Too many attempts.
|
too_many_attempts: Too many attempts.
|
||||||
|
verification_email_bounced: "Unable to send the verification code: email to this address bounced."
|
||||||
verification_code: Verification Code
|
verification_code: Verification Code
|
||||||
resend_code: Resend Code
|
resend_code: Resend Code
|
||||||
verify_new_sign_in: Verify new sign in
|
verify_new_sign_in: Verify new sign in
|
||||||
@@ -988,6 +993,13 @@ en: &en
|
|||||||
open_file_from: 'Open file from'
|
open_file_from: 'Open file from'
|
||||||
cancel: Cancel
|
cancel: Cancel
|
||||||
open: Open
|
open: Open
|
||||||
|
signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign: Signature request email sent to recipients with a link to review and sign.
|
||||||
|
view_only_email_sent_to_recipients_that_need_to_only_review_the_documents: View only email sent to recipients that need to only review the documents.
|
||||||
|
reminds_recipients_who_havent_completed_their_part_yet_to_sign_following_the_reminders_schedule: Reminds recipients who haven't completed their part yet to sign, following the reminders schedule.
|
||||||
|
signature_request_sms_sent_to_recipients_with_a_link_to_review_and_sign: Signature request SMS sent to recipients with a link to review and sign.
|
||||||
|
phone_2fa_verification_text_message: Phone 2FA verification text message.
|
||||||
|
notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties: Notification email sent to the sender user and BCC addresses once the submission is completed by all parties.
|
||||||
|
completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents: Completed documents copy email sent to recipients upon completion containing their documents.
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Your email address has been successfully confirmed.
|
confirmed: Your email address has been successfully confirmed.
|
||||||
@@ -1003,6 +1015,7 @@ en: &en
|
|||||||
invite: Invite
|
invite: Invite
|
||||||
link: Link
|
link: Link
|
||||||
mcp: MCP
|
mcp: MCP
|
||||||
|
self: Sign Yourself
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
||||||
bounce_email_html: '<b>Email bounced</b> %{submitter_name}'
|
bounce_email_html: '<b>Email bounced</b> %{submitter_name}'
|
||||||
@@ -1338,6 +1351,7 @@ es: &es
|
|||||||
profile_details: Detalles del perfil
|
profile_details: Detalles del perfil
|
||||||
sign_up_with_google: Registrarse con Google
|
sign_up_with_google: Registrarse con Google
|
||||||
sign_up_with_microsoft: Registrarse con Microsoft
|
sign_up_with_microsoft: Registrarse con Microsoft
|
||||||
|
redirecting_to_provider: Redirigiendo a %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Al crear una cuenta, aceptas nuestra <a target="_blank" href="https://www.docuseal.com/privacy">Política de Privacidad</a> y <a target="_blank" href="https://www.docuseal.com/terms">Términos de Servicio</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Al crear una cuenta, aceptas nuestra <a target="_blank" href="https://www.docuseal.com/privacy">Política de Privacidad</a> y <a target="_blank" href="https://www.docuseal.com/terms">Términos de Servicio</a>.'
|
||||||
enter_email_to_continue: Ingresa tu correo electrónico para continuar
|
enter_email_to_continue: Ingresa tu correo electrónico para continuar
|
||||||
account: Cuenta
|
account: Cuenta
|
||||||
@@ -1465,6 +1479,13 @@ es: &es
|
|||||||
completed_form_message: Mensaje de formulario completado
|
completed_form_message: Mensaje de formulario completado
|
||||||
completed_form_redirect_button: Botón de redirección de formulario completado
|
completed_form_redirect_button: Botón de redirección de formulario completado
|
||||||
documents_copy_email: Correo de copia de documentos
|
documents_copy_email: Correo de copia de documentos
|
||||||
|
signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign: Correo de solicitud de firma enviado a los destinatarios con un enlace para revisar y firmar.
|
||||||
|
view_only_email_sent_to_recipients_that_need_to_only_review_the_documents: Correo de solo lectura enviado a los destinatarios que solo necesitan revisar los documentos.
|
||||||
|
reminds_recipients_who_havent_completed_their_part_yet_to_sign_following_the_reminders_schedule: Recuerda firmar a los destinatarios que aún no han completado su parte, según la programación de recordatorios.
|
||||||
|
signature_request_sms_sent_to_recipients_with_a_link_to_review_and_sign: SMS de solicitud de firma enviado a los destinatarios con un enlace para revisar y firmar.
|
||||||
|
phone_2fa_verification_text_message: Mensaje de texto de verificación 2FA por teléfono.
|
||||||
|
notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties: Correo de notificación enviado al usuario remitente y a las direcciones BCC una vez que todas las partes han completado el envío.
|
||||||
|
completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents: Correo con la copia de los documentos completados enviado a los destinatarios al finalizar, con sus documentos.
|
||||||
form_custom_css: CSS personalizado del formulario
|
form_custom_css: CSS personalizado del formulario
|
||||||
subject: Asunto
|
subject: Asunto
|
||||||
body: Cuerpo
|
body: Cuerpo
|
||||||
@@ -1706,6 +1727,9 @@ es: &es
|
|||||||
there_are_no_signatures: No hay firmas
|
there_are_no_signatures: No hay firmas
|
||||||
signed_with_trusted_certificate: Firmado con certificado de confianza
|
signed_with_trusted_certificate: Firmado con certificado de confianza
|
||||||
signed_with_external_certificate: Firmado con certificado externo
|
signed_with_external_certificate: Firmado con certificado externo
|
||||||
|
signature_valid: Firma válida
|
||||||
|
signature_verification_failed: Verificación de firma fallida
|
||||||
|
contains_unsigned_changes_after_the_last_signature: Contiene cambios no firmados después de la última firma
|
||||||
setup_2fa_to_continue: Configura 2FA para continuar.
|
setup_2fa_to_continue: Configura 2FA para continuar.
|
||||||
create_a_new_account: Crear una nueva cuenta.
|
create_a_new_account: Crear una nueva cuenta.
|
||||||
you_are_already_signed_in: Ya has iniciado sesión.
|
you_are_already_signed_in: Ya has iniciado sesión.
|
||||||
@@ -1893,6 +1917,7 @@ es: &es
|
|||||||
the_code_has_been_sent_to_your_email: El código ha sido enviado a tu correo electrónico.
|
the_code_has_been_sent_to_your_email: El código ha sido enviado a tu correo electrónico.
|
||||||
enter_the_verification_code_from_your_email: Ingresa el código de verificación de tu correo electrónico.
|
enter_the_verification_code_from_your_email: Ingresa el código de verificación de tu correo electrónico.
|
||||||
too_many_attempts: Demasiados intentos.
|
too_many_attempts: Demasiados intentos.
|
||||||
|
verification_email_bounced: "No se pudo enviar el código de verificación: el correo a esta dirección rebotó."
|
||||||
verification_code: Código de Verificación
|
verification_code: Código de Verificación
|
||||||
resend_code: Reenviar Código
|
resend_code: Reenviar Código
|
||||||
verify_new_sign_in: Verificar nuevo inicio de sesión
|
verify_new_sign_in: Verificar nuevo inicio de sesión
|
||||||
@@ -2110,6 +2135,7 @@ es: &es
|
|||||||
invite: Invitación
|
invite: Invitación
|
||||||
link: Enlace
|
link: Enlace
|
||||||
mcp: MCP
|
mcp: MCP
|
||||||
|
self: Firma tú mismo
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
||||||
bounce_email_html: '<b>Correo electrónico rebotado</b> %{submitter_name}'
|
bounce_email_html: '<b>Correo electrónico rebotado</b> %{submitter_name}'
|
||||||
@@ -2459,6 +2485,7 @@ it: &it
|
|||||||
profile_details: Dettagli del profilo
|
profile_details: Dettagli del profilo
|
||||||
sign_up_with_google: Registrati con Google
|
sign_up_with_google: Registrati con Google
|
||||||
sign_up_with_microsoft: Registrati con Microsoft
|
sign_up_with_microsoft: Registrati con Microsoft
|
||||||
|
redirecting_to_provider: Reindirizzamento a %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Creando un account, accetti la nostra <a target="_blank" href="https://www.docuseal.com/privacy">Privacy Policy</a> e i nostri <a target="_blank" href="https://www.docuseal.com/terms">Termini di servizio</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Creando un account, accetti la nostra <a target="_blank" href="https://www.docuseal.com/privacy">Privacy Policy</a> e i nostri <a target="_blank" href="https://www.docuseal.com/terms">Termini di servizio</a>.'
|
||||||
enter_email_to_continue: "Inserisci l'email per continuare"
|
enter_email_to_continue: "Inserisci l'email per continuare"
|
||||||
account: Account
|
account: Account
|
||||||
@@ -2586,6 +2613,13 @@ it: &it
|
|||||||
completed_form_message: Messaggio di completamento del modulo
|
completed_form_message: Messaggio di completamento del modulo
|
||||||
completed_form_redirect_button: Pulsante di reindirizzamento dopo il completamento del modulo
|
completed_form_redirect_button: Pulsante di reindirizzamento dopo il completamento del modulo
|
||||||
documents_copy_email: Email con copia dei documenti
|
documents_copy_email: Email con copia dei documenti
|
||||||
|
signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign: Email di richiesta di firma inviata ai destinatari con un link per revisionare e firmare.
|
||||||
|
view_only_email_sent_to_recipients_that_need_to_only_review_the_documents: Email di sola lettura inviata ai destinatari che devono solo revisionare i documenti.
|
||||||
|
reminds_recipients_who_havent_completed_their_part_yet_to_sign_following_the_reminders_schedule: Ricorda di firmare ai destinatari che non hanno ancora completato la loro parte, in base alla pianificazione dei promemoria.
|
||||||
|
signature_request_sms_sent_to_recipients_with_a_link_to_review_and_sign: SMS di richiesta di firma inviato ai destinatari con un link per revisionare e firmare.
|
||||||
|
phone_2fa_verification_text_message: Messaggio di testo per la verifica 2FA tramite telefono.
|
||||||
|
notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties: Email di notifica inviata all'utente mittente e agli indirizzi BCC una volta che l'invio è stato completato da tutte le parti.
|
||||||
|
completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents: Email con la copia dei documenti completati inviata ai destinatari al completamento, con i loro documenti.
|
||||||
form_custom_css: CSS personalizzato del modulo
|
form_custom_css: CSS personalizzato del modulo
|
||||||
subject: Oggetto
|
subject: Oggetto
|
||||||
body: Corpo
|
body: Corpo
|
||||||
@@ -2827,6 +2861,9 @@ it: &it
|
|||||||
there_are_no_signatures: Non ci sono firme
|
there_are_no_signatures: Non ci sono firme
|
||||||
signed_with_trusted_certificate: Firmato con certificato affidabile
|
signed_with_trusted_certificate: Firmato con certificato affidabile
|
||||||
signed_with_external_certificate: Firmato con certificato esterno
|
signed_with_external_certificate: Firmato con certificato esterno
|
||||||
|
signature_valid: Firma valida
|
||||||
|
signature_verification_failed: Verifica della firma non riuscita
|
||||||
|
contains_unsigned_changes_after_the_last_signature: Contiene modifiche non firmate dopo l’ultima firma
|
||||||
setup_2fa_to_continue: Configura 2FA per continuare.
|
setup_2fa_to_continue: Configura 2FA per continuare.
|
||||||
create_a_new_account: Crea un nuovo account.
|
create_a_new_account: Crea un nuovo account.
|
||||||
you_are_already_signed_in: "Hai già effettuato l'accesso."
|
you_are_already_signed_in: "Hai già effettuato l'accesso."
|
||||||
@@ -3014,6 +3051,7 @@ it: &it
|
|||||||
the_code_has_been_sent_to_your_email: Il codice è stato inviato alla tua e-mail.
|
the_code_has_been_sent_to_your_email: Il codice è stato inviato alla tua e-mail.
|
||||||
enter_the_verification_code_from_your_email: Inserisci il codice di verifica dalla tua e-mail.
|
enter_the_verification_code_from_your_email: Inserisci il codice di verifica dalla tua e-mail.
|
||||||
too_many_attempts: Troppi tentativi.
|
too_many_attempts: Troppi tentativi.
|
||||||
|
verification_email_bounced: "Impossibile inviare il codice di verifica: l'email a questo indirizzo è stata respinta."
|
||||||
verification_code: Codice di Verifica
|
verification_code: Codice di Verifica
|
||||||
resend_code: Reinvia Codice
|
resend_code: Reinvia Codice
|
||||||
verify_new_sign_in: Verifica nuovo accesso
|
verify_new_sign_in: Verifica nuovo accesso
|
||||||
@@ -3231,6 +3269,7 @@ it: &it
|
|||||||
invite: Invito
|
invite: Invito
|
||||||
link: Link
|
link: Link
|
||||||
mcp: MCP
|
mcp: MCP
|
||||||
|
self: Firma te stesso
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Email inviata</b> a %{submitter_name}'
|
send_email_to_html: '<b>Email inviata</b> a %{submitter_name}'
|
||||||
bounce_email_html: '<b>Email respinta</b> %{submitter_name}'
|
bounce_email_html: '<b>Email respinta</b> %{submitter_name}'
|
||||||
@@ -3566,6 +3605,7 @@ fr: &fr
|
|||||||
profile_details: Détails du profil
|
profile_details: Détails du profil
|
||||||
sign_up_with_google: S’inscrire avec Google
|
sign_up_with_google: S’inscrire avec Google
|
||||||
sign_up_with_microsoft: S’inscrire avec Microsoft
|
sign_up_with_microsoft: S’inscrire avec Microsoft
|
||||||
|
redirecting_to_provider: Redirection vers %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: En créant un compte, vous acceptez notre <a target="_blank" href="https://www.docuseal.com/privacy">Politique de confidentialité</a> et nos <a target="_blank" href="https://www.docuseal.com/terms">Conditions d’utilisation</a>.
|
by_creating_an_account_you_agree_to_our_html: En créant un compte, vous acceptez notre <a target="_blank" href="https://www.docuseal.com/privacy">Politique de confidentialité</a> et nos <a target="_blank" href="https://www.docuseal.com/terms">Conditions d’utilisation</a>.
|
||||||
enter_email_to_continue: Entrez votre e‑mail pour continuer
|
enter_email_to_continue: Entrez votre e‑mail pour continuer
|
||||||
account: Compte
|
account: Compte
|
||||||
@@ -3693,6 +3733,13 @@ fr: &fr
|
|||||||
completed_form_message: Message de formulaire complété
|
completed_form_message: Message de formulaire complété
|
||||||
completed_form_redirect_button: Bouton de redirection après finalisation
|
completed_form_redirect_button: Bouton de redirection après finalisation
|
||||||
documents_copy_email: E‑mail avec copie des documents
|
documents_copy_email: E‑mail avec copie des documents
|
||||||
|
signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign: E-mail de demande de signature envoyé aux destinataires avec un lien pour consulter et signer.
|
||||||
|
view_only_email_sent_to_recipients_that_need_to_only_review_the_documents: E-mail en lecture seule envoyé aux destinataires qui doivent uniquement consulter les documents.
|
||||||
|
reminds_recipients_who_havent_completed_their_part_yet_to_sign_following_the_reminders_schedule: Rappelle de signer aux destinataires qui n'ont pas encore terminé leur partie, selon la planification des rappels.
|
||||||
|
signature_request_sms_sent_to_recipients_with_a_link_to_review_and_sign: SMS de demande de signature envoyé aux destinataires avec un lien pour consulter et signer.
|
||||||
|
phone_2fa_verification_text_message: Message texte de vérification 2FA par téléphone.
|
||||||
|
notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties: E-mail de notification envoyé à l'utilisateur expéditeur et aux adresses Cci une fois la soumission complétée par toutes les parties.
|
||||||
|
completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents: E-mail avec la copie des documents finalisés, envoyé aux destinataires à la finalisation, contenant leurs documents.
|
||||||
form_custom_css: CSS personnalisé du formulaire
|
form_custom_css: CSS personnalisé du formulaire
|
||||||
subject: Objet
|
subject: Objet
|
||||||
body: Corps
|
body: Corps
|
||||||
@@ -3934,6 +3981,9 @@ fr: &fr
|
|||||||
there_are_no_signatures: Il n’y a pas de signatures
|
there_are_no_signatures: Il n’y a pas de signatures
|
||||||
signed_with_trusted_certificate: Signé avec un certificat de confiance
|
signed_with_trusted_certificate: Signé avec un certificat de confiance
|
||||||
signed_with_external_certificate: Signé avec un certificat externe
|
signed_with_external_certificate: Signé avec un certificat externe
|
||||||
|
signature_valid: Signature valide
|
||||||
|
signature_verification_failed: Échec de la vérification de la signature
|
||||||
|
contains_unsigned_changes_after_the_last_signature: Contient des modifications non signées après la dernière signature
|
||||||
setup_2fa_to_continue: Configurez la 2FA pour continuer.
|
setup_2fa_to_continue: Configurez la 2FA pour continuer.
|
||||||
create_a_new_account: Créer un nouveau compte.
|
create_a_new_account: Créer un nouveau compte.
|
||||||
you_are_already_signed_in: Vous êtes déjà connecté.
|
you_are_already_signed_in: Vous êtes déjà connecté.
|
||||||
@@ -4121,6 +4171,7 @@ fr: &fr
|
|||||||
the_code_has_been_sent_to_your_email: Le code a été envoyé à votre e‑mail.
|
the_code_has_been_sent_to_your_email: Le code a été envoyé à votre e‑mail.
|
||||||
enter_the_verification_code_from_your_email: Saisissez le code de vérification reçu par e‑mail.
|
enter_the_verification_code_from_your_email: Saisissez le code de vérification reçu par e‑mail.
|
||||||
too_many_attempts: Trop de tentatives.
|
too_many_attempts: Trop de tentatives.
|
||||||
|
verification_email_bounced: "Impossible d'envoyer le code de vérification : l'e-mail à cette adresse a été rejeté."
|
||||||
verification_code: Code de vérification
|
verification_code: Code de vérification
|
||||||
resend_code: Renvoyer le code
|
resend_code: Renvoyer le code
|
||||||
verify_new_sign_in: Vérifier une nouvelle connexion
|
verify_new_sign_in: Vérifier une nouvelle connexion
|
||||||
@@ -4335,6 +4386,7 @@ fr: &fr
|
|||||||
invite: Invitation
|
invite: Invitation
|
||||||
link: Lien
|
link: Lien
|
||||||
mcp: MCP
|
mcp: MCP
|
||||||
|
self: Signer vous-même
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: "<b>E‑mail envoyé</b> à %{submitter_name}"
|
send_email_to_html: "<b>E‑mail envoyé</b> à %{submitter_name}"
|
||||||
bounce_email_html: "<b>E‑mail rejeté</b> %{submitter_name}"
|
bounce_email_html: "<b>E‑mail rejeté</b> %{submitter_name}"
|
||||||
@@ -4683,6 +4735,7 @@ pt: &pt
|
|||||||
profile_details: Detalhes do perfil
|
profile_details: Detalhes do perfil
|
||||||
sign_up_with_google: Inscrever-se com Google
|
sign_up_with_google: Inscrever-se com Google
|
||||||
sign_up_with_microsoft: Inscrever-se com Microsoft
|
sign_up_with_microsoft: Inscrever-se com Microsoft
|
||||||
|
redirecting_to_provider: Redirecionando para %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Ao criar uma conta, você concorda com nossa <a target="_blank" href="https://www.docuseal.com/privacy">Política de Privacidade</a> e <a target="_blank" href="https://www.docuseal.com/terms">Termos de Serviço</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Ao criar uma conta, você concorda com nossa <a target="_blank" href="https://www.docuseal.com/privacy">Política de Privacidade</a> e <a target="_blank" href="https://www.docuseal.com/terms">Termos de Serviço</a>.'
|
||||||
enter_email_to_continue: Insira o e-mail para continuar
|
enter_email_to_continue: Insira o e-mail para continuar
|
||||||
account: Conta
|
account: Conta
|
||||||
@@ -4810,6 +4863,13 @@ pt: &pt
|
|||||||
completed_form_message: Mensagem de formulário concluído
|
completed_form_message: Mensagem de formulário concluído
|
||||||
completed_form_redirect_button: Botão de redirecionamento do formulário concluído
|
completed_form_redirect_button: Botão de redirecionamento do formulário concluído
|
||||||
documents_copy_email: E-mail com cópia dos documentos
|
documents_copy_email: E-mail com cópia dos documentos
|
||||||
|
signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign: E-mail de solicitação de assinatura enviado aos destinatários com um link para revisar e assinar.
|
||||||
|
view_only_email_sent_to_recipients_that_need_to_only_review_the_documents: E-mail somente de leitura enviado aos destinatários que precisam apenas revisar os documentos.
|
||||||
|
reminds_recipients_who_havent_completed_their_part_yet_to_sign_following_the_reminders_schedule: Lembra os destinatários que ainda não concluíram a sua parte de assinar, de acordo com o agendamento de lembretes.
|
||||||
|
signature_request_sms_sent_to_recipients_with_a_link_to_review_and_sign: SMS de solicitação de assinatura enviado aos destinatários com um link para revisar e assinar.
|
||||||
|
phone_2fa_verification_text_message: Mensagem de texto de verificação 2FA por telefone.
|
||||||
|
notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties: E-mail de notificação enviado ao usuário remetente e aos endereços BCC assim que a submissão for concluída por todas as partes.
|
||||||
|
completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents: E-mail com a cópia dos documentos concluídos enviado aos destinatários na conclusão, com os seus documentos.
|
||||||
form_custom_css: CSS personalizado do formulário
|
form_custom_css: CSS personalizado do formulário
|
||||||
subject: Assunto
|
subject: Assunto
|
||||||
body: Corpo
|
body: Corpo
|
||||||
@@ -5051,6 +5111,9 @@ pt: &pt
|
|||||||
there_are_no_signatures: Não há assinaturas
|
there_are_no_signatures: Não há assinaturas
|
||||||
signed_with_trusted_certificate: Assinado com certificado confiável
|
signed_with_trusted_certificate: Assinado com certificado confiável
|
||||||
signed_with_external_certificate: Assinado com certificado externo
|
signed_with_external_certificate: Assinado com certificado externo
|
||||||
|
signature_valid: Assinatura válida
|
||||||
|
signature_verification_failed: Falha na verificação da assinatura
|
||||||
|
contains_unsigned_changes_after_the_last_signature: Contém alterações não assinadas após a última assinatura
|
||||||
setup_2fa_to_continue: Configure 2FA para continuar.
|
setup_2fa_to_continue: Configure 2FA para continuar.
|
||||||
create_a_new_account: Criar uma nova conta.
|
create_a_new_account: Criar uma nova conta.
|
||||||
you_are_already_signed_in: Você já está conectado.
|
you_are_already_signed_in: Você já está conectado.
|
||||||
@@ -5238,6 +5301,7 @@ pt: &pt
|
|||||||
the_code_has_been_sent_to_your_email: O código foi enviado para seu e-mail.
|
the_code_has_been_sent_to_your_email: O código foi enviado para seu e-mail.
|
||||||
enter_the_verification_code_from_your_email: Insira o código de verificação do seu e-mail.
|
enter_the_verification_code_from_your_email: Insira o código de verificação do seu e-mail.
|
||||||
too_many_attempts: Muitas tentativas.
|
too_many_attempts: Muitas tentativas.
|
||||||
|
verification_email_bounced: "Não foi possível enviar o código de verificação: o e-mail para este endereço não foi entregue."
|
||||||
verification_code: Código de Verificação
|
verification_code: Código de Verificação
|
||||||
resend_code: Reenviar Código
|
resend_code: Reenviar Código
|
||||||
verify_new_sign_in: Verificar novo login
|
verify_new_sign_in: Verificar novo login
|
||||||
@@ -5455,6 +5519,7 @@ pt: &pt
|
|||||||
invite: Convite
|
invite: Convite
|
||||||
link: Link
|
link: Link
|
||||||
mcp: MCP
|
mcp: MCP
|
||||||
|
self: Assine você mesmo
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Email enviado</b> para %{submitter_name}'
|
send_email_to_html: '<b>Email enviado</b> para %{submitter_name}'
|
||||||
bounce_email_html: '<b>Email não entregue</b> %{submitter_name}'
|
bounce_email_html: '<b>Email não entregue</b> %{submitter_name}'
|
||||||
@@ -5803,6 +5868,7 @@ de: &de
|
|||||||
profile_details: Profildetails
|
profile_details: Profildetails
|
||||||
sign_up_with_google: Mit Google registrieren
|
sign_up_with_google: Mit Google registrieren
|
||||||
sign_up_with_microsoft: Mit Microsoft registrieren
|
sign_up_with_microsoft: Mit Microsoft registrieren
|
||||||
|
redirecting_to_provider: Weiterleitung zu %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Durch die Erstellung eines Kontos stimmen Sie unseren <a target="_blank" href="https://www.docuseal.com/privacy">Datenschutzrichtlinien</a> und <a target="_blank" href="https://www.docuseal.com/terms">Nutzungsbedingungen</a> zu.'
|
by_creating_an_account_you_agree_to_our_html: 'Durch die Erstellung eines Kontos stimmen Sie unseren <a target="_blank" href="https://www.docuseal.com/privacy">Datenschutzrichtlinien</a> und <a target="_blank" href="https://www.docuseal.com/terms">Nutzungsbedingungen</a> zu.'
|
||||||
enter_email_to_continue: E-Mail eingeben, um fortzufahren
|
enter_email_to_continue: E-Mail eingeben, um fortzufahren
|
||||||
account: Konto
|
account: Konto
|
||||||
@@ -5930,6 +5996,13 @@ de: &de
|
|||||||
completed_form_message: Nachricht zum abgeschlossenen Formular
|
completed_form_message: Nachricht zum abgeschlossenen Formular
|
||||||
completed_form_redirect_button: Weiterleitungsschaltfläche nach Formularabschluss
|
completed_form_redirect_button: Weiterleitungsschaltfläche nach Formularabschluss
|
||||||
documents_copy_email: E-Mail mit Dokumentenkopie
|
documents_copy_email: E-Mail mit Dokumentenkopie
|
||||||
|
signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign: E-Mail mit der Signaturanfrage, die mit einem Link zum Prüfen und Signieren an die Empfänger gesendet wird.
|
||||||
|
view_only_email_sent_to_recipients_that_need_to_only_review_the_documents: E-Mail nur zur Ansicht, die an Empfänger gesendet wird, die die Dokumente nur prüfen müssen.
|
||||||
|
reminds_recipients_who_havent_completed_their_part_yet_to_sign_following_the_reminders_schedule: Erinnert Empfänger, die ihren Teil noch nicht abgeschlossen haben, an das Signieren, gemäß dem Erinnerungszeitplan.
|
||||||
|
signature_request_sms_sent_to_recipients_with_a_link_to_review_and_sign: SMS mit der Signaturanfrage, die mit einem Link zum Prüfen und Signieren an die Empfänger gesendet wird.
|
||||||
|
phone_2fa_verification_text_message: Textnachricht zur 2FA-Verifizierung per Telefon.
|
||||||
|
notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties: Benachrichtigungs-E-Mail, die an den absendenden Benutzer und die BCC-Adressen gesendet wird, sobald die Einreichung von allen Parteien abgeschlossen wurde.
|
||||||
|
completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents: E-Mail mit der Kopie der abgeschlossenen Dokumente, die nach Abschluss an die Empfänger gesendet wird.
|
||||||
form_custom_css: Benutzerdefiniertes CSS für Formular
|
form_custom_css: Benutzerdefiniertes CSS für Formular
|
||||||
subject: Betreff
|
subject: Betreff
|
||||||
body: Inhalt
|
body: Inhalt
|
||||||
@@ -6171,6 +6244,9 @@ de: &de
|
|||||||
there_are_no_signatures: Es gibt keine Unterschriften
|
there_are_no_signatures: Es gibt keine Unterschriften
|
||||||
signed_with_trusted_certificate: Signiert mit vertrauenswürdigem Zertifikat
|
signed_with_trusted_certificate: Signiert mit vertrauenswürdigem Zertifikat
|
||||||
signed_with_external_certificate: Signiert mit externem Zertifikat
|
signed_with_external_certificate: Signiert mit externem Zertifikat
|
||||||
|
signature_valid: Signatur gültig
|
||||||
|
signature_verification_failed: Signaturprüfung fehlgeschlagen
|
||||||
|
contains_unsigned_changes_after_the_last_signature: Enthält nicht signierte Änderungen nach der letzten Signatur
|
||||||
setup_2fa_to_continue: Richten Sie 2FA ein, um fortzufahren.
|
setup_2fa_to_continue: Richten Sie 2FA ein, um fortzufahren.
|
||||||
create_a_new_account: Neues Konto erstellen.
|
create_a_new_account: Neues Konto erstellen.
|
||||||
you_are_already_signed_in: Sie sind bereits angemeldet.
|
you_are_already_signed_in: Sie sind bereits angemeldet.
|
||||||
@@ -6358,6 +6434,7 @@ de: &de
|
|||||||
the_code_has_been_sent_to_your_email: Der Code wurde an Ihre E-Mail gesendet.
|
the_code_has_been_sent_to_your_email: Der Code wurde an Ihre E-Mail gesendet.
|
||||||
enter_the_verification_code_from_your_email: Geben Sie den Verifizierungscode aus Ihrer E-Mail ein.
|
enter_the_verification_code_from_your_email: Geben Sie den Verifizierungscode aus Ihrer E-Mail ein.
|
||||||
too_many_attempts: Zu viele Versuche.
|
too_many_attempts: Zu viele Versuche.
|
||||||
|
verification_email_bounced: "Der Verifizierungscode konnte nicht gesendet werden: E-Mail an diese Adresse ist unzustellbar."
|
||||||
verification_code: Verifizierungscode
|
verification_code: Verifizierungscode
|
||||||
resend_code: Code erneut senden
|
resend_code: Code erneut senden
|
||||||
verify_new_sign_in: Neue Anmeldung verifizieren
|
verify_new_sign_in: Neue Anmeldung verifizieren
|
||||||
@@ -6575,6 +6652,7 @@ de: &de
|
|||||||
invite: Einladung
|
invite: Einladung
|
||||||
link: Link
|
link: Link
|
||||||
mcp: MCP
|
mcp: MCP
|
||||||
|
self: Selbst unterschreiben
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
||||||
bounce_email_html: '<b>E-Mail unzustellbar</b> %{submitter_name}'
|
bounce_email_html: '<b>E-Mail unzustellbar</b> %{submitter_name}'
|
||||||
@@ -6790,11 +6868,14 @@ pl:
|
|||||||
profile_details: Szczegóły profilu
|
profile_details: Szczegóły profilu
|
||||||
sign_up_with_google: Zarejestruj się przez Google
|
sign_up_with_google: Zarejestruj się przez Google
|
||||||
sign_up_with_microsoft: Zarejestruj się przez Microsoft
|
sign_up_with_microsoft: Zarejestruj się przez Microsoft
|
||||||
|
redirecting_to_provider: Przekierowywanie do %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Tworząc konto, akceptujesz naszą <a target="_blank" href="https://www.docuseal.com/privacy">Politykę Prywatności</a> i <a target="_blank" href="https://www.docuseal.com/terms">Regulamin</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Tworząc konto, akceptujesz naszą <a target="_blank" href="https://www.docuseal.com/privacy">Politykę Prywatności</a> i <a target="_blank" href="https://www.docuseal.com/terms">Regulamin</a>.'
|
||||||
enter_email_to_continue: Wprowadź e-mail, aby kontynuować
|
enter_email_to_continue: Wprowadź e-mail, aby kontynuować
|
||||||
the_code_has_been_sent_to_your_email: Kod został wysłany na Twój e-mail.
|
the_code_has_been_sent_to_your_email: Kod został wysłany na Twój e-mail.
|
||||||
enter_the_verification_code_from_your_email: Wprowadź kod weryfikacyjny z Twojego e-maila.
|
enter_the_verification_code_from_your_email: Wprowadź kod weryfikacyjny z Twojego e-maila.
|
||||||
too_many_attempts: Zbyt wiele prób.
|
too_many_attempts: Zbyt wiele prób.
|
||||||
|
verification_email_bounced: "Nie można wysłać kodu weryfikacyjnego: e-mail na ten adres został odrzucony."
|
||||||
|
back: Wstecz
|
||||||
verification_code: Kod Weryfikacyjny
|
verification_code: Kod Weryfikacyjny
|
||||||
resend_code: Wyślij Kod Ponownie
|
resend_code: Wyślij Kod Ponownie
|
||||||
powered_by: 'Napędzany przez'
|
powered_by: 'Napędzany przez'
|
||||||
@@ -6929,11 +7010,14 @@ uk:
|
|||||||
profile_details: Дані профілю
|
profile_details: Дані профілю
|
||||||
sign_up_with_google: Зареєструватися через Google
|
sign_up_with_google: Зареєструватися через Google
|
||||||
sign_up_with_microsoft: Зареєструватися через Microsoft
|
sign_up_with_microsoft: Зареєструватися через Microsoft
|
||||||
|
redirecting_to_provider: Перенаправлення до %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Створюючи акаунт, ви погоджуєтесь з нашою <a target="_blank" href="https://www.docuseal.com/privacy">Політикою конфіденційності</a> і <a target="_blank" href="https://www.docuseal.com/terms">Умовами надання послуг</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Створюючи акаунт, ви погоджуєтесь з нашою <a target="_blank" href="https://www.docuseal.com/privacy">Політикою конфіденційності</a> і <a target="_blank" href="https://www.docuseal.com/terms">Умовами надання послуг</a>.'
|
||||||
enter_email_to_continue: Введіть електронну пошту, щоб продовжити
|
enter_email_to_continue: Введіть електронну пошту, щоб продовжити
|
||||||
the_code_has_been_sent_to_your_email: Код було надіслано на вашу електронну пошту.
|
the_code_has_been_sent_to_your_email: Код було надіслано на вашу електронну пошту.
|
||||||
enter_the_verification_code_from_your_email: Введіть код перевірки з вашої електронної пошти.
|
enter_the_verification_code_from_your_email: Введіть код перевірки з вашої електронної пошти.
|
||||||
too_many_attempts: Забагато спроб.
|
too_many_attempts: Забагато спроб.
|
||||||
|
verification_email_bounced: "Не вдалося надіслати код підтвердження: лист на цю адресу було відхилено."
|
||||||
|
back: Назад
|
||||||
verification_code: Код перевірки
|
verification_code: Код перевірки
|
||||||
resend_code: Надіслати код знову
|
resend_code: Надіслати код знову
|
||||||
powered_by: 'Працює на базі'
|
powered_by: 'Працює на базі'
|
||||||
@@ -7068,11 +7152,14 @@ cs:
|
|||||||
profile_details: Detaily profilu
|
profile_details: Detaily profilu
|
||||||
sign_up_with_google: Zaregistrovat se pomocí Googlu
|
sign_up_with_google: Zaregistrovat se pomocí Googlu
|
||||||
sign_up_with_microsoft: Zaregistrovat se pomocí Microsoftu
|
sign_up_with_microsoft: Zaregistrovat se pomocí Microsoftu
|
||||||
|
redirecting_to_provider: Přesměrování na %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Vytvořením účtu souhlasíte s našimi <a target="_blank" href="https://www.docuseal.com/privacy">Zásadami ochrany osobních údajů</a> a <a target="_blank" href="https://www.docuseal.com/terms">Podmínkami služby</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Vytvořením účtu souhlasíte s našimi <a target="_blank" href="https://www.docuseal.com/privacy">Zásadami ochrany osobních údajů</a> a <a target="_blank" href="https://www.docuseal.com/terms">Podmínkami služby</a>.'
|
||||||
enter_email_to_continue: Zadejte e-mail pro pokračování
|
enter_email_to_continue: Zadejte e-mail pro pokračování
|
||||||
the_code_has_been_sent_to_your_email: Kód byl odeslán na váš e-mail.
|
the_code_has_been_sent_to_your_email: Kód byl odeslán na váš e-mail.
|
||||||
enter_the_verification_code_from_your_email: Zadejte ověřovací kód z vašeho e-mailu.
|
enter_the_verification_code_from_your_email: Zadejte ověřovací kód z vašeho e-mailu.
|
||||||
too_many_attempts: Příliš mnoho pokusů.
|
too_many_attempts: Příliš mnoho pokusů.
|
||||||
|
verification_email_bounced: "Ověřovací kód nelze odeslat: e-mail na tuto adresu byl odmítnut."
|
||||||
|
back: Zpět
|
||||||
verification_code: Ověřovací kód
|
verification_code: Ověřovací kód
|
||||||
resend_code: Znovu odeslat kód
|
resend_code: Znovu odeslat kód
|
||||||
powered_by: 'Poháněno'
|
powered_by: 'Poháněno'
|
||||||
@@ -7193,11 +7280,14 @@ he:
|
|||||||
profile_details: פרטי הפרופיל
|
profile_details: פרטי הפרופיל
|
||||||
sign_up_with_google: הירשם עם גוגל
|
sign_up_with_google: הירשם עם גוגל
|
||||||
sign_up_with_microsoft: הירשם עם מיקרוסופט
|
sign_up_with_microsoft: הירשם עם מיקרוסופט
|
||||||
|
redirecting_to_provider: מפנה אל %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'על ידי יצירת חשבון, אתה מסכים ל<a target="_blank" href="https://www.docuseal.com/privacy">מדיניות הפרטיות</a> ול<a target="_blank" href="https://www.docuseal.com/terms">תנאי השירות</a> שלנו.'
|
by_creating_an_account_you_agree_to_our_html: 'על ידי יצירת חשבון, אתה מסכים ל<a target="_blank" href="https://www.docuseal.com/privacy">מדיניות הפרטיות</a> ול<a target="_blank" href="https://www.docuseal.com/terms">תנאי השירות</a> שלנו.'
|
||||||
enter_email_to_continue: הכנס דוא"ל כדי להמשיך
|
enter_email_to_continue: הכנס דוא"ל כדי להמשיך
|
||||||
the_code_has_been_sent_to_your_email: הקוד נשלח לדוא"ל שלך.
|
the_code_has_been_sent_to_your_email: הקוד נשלח לדוא"ל שלך.
|
||||||
enter_the_verification_code_from_your_email: הזן את קוד האימות מדוא"ל שלך.
|
enter_the_verification_code_from_your_email: הזן את קוד האימות מדוא"ל שלך.
|
||||||
too_many_attempts: יותר מדי ניסיונות.
|
too_many_attempts: יותר מדי ניסיונות.
|
||||||
|
verification_email_bounced: "לא ניתן לשלוח את קוד האימות: האימייל לכתובת זו נדחה."
|
||||||
|
back: חזרה
|
||||||
verification_code: קוד אימות
|
verification_code: קוד אימות
|
||||||
resend_code: שלח קוד מחדש
|
resend_code: שלח קוד מחדש
|
||||||
powered_by: 'מופעל על ידי'
|
powered_by: 'מופעל על ידי'
|
||||||
@@ -7439,6 +7529,7 @@ nl: &nl
|
|||||||
profile_details: Profielgegevens
|
profile_details: Profielgegevens
|
||||||
sign_up_with_google: Registreren met Google
|
sign_up_with_google: Registreren met Google
|
||||||
sign_up_with_microsoft: Registreren met Microsoft
|
sign_up_with_microsoft: Registreren met Microsoft
|
||||||
|
redirecting_to_provider: Doorverwijzen naar %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: Door een account aan te maken gaat u akkoord met ons <a target="_blank" href="https://www.docuseal.com/privacy">Privacybeleid</a> en onze <a target="_blank" href="https://www.docuseal.com/terms">Servicevoorwaarden</a>.
|
by_creating_an_account_you_agree_to_our_html: Door een account aan te maken gaat u akkoord met ons <a target="_blank" href="https://www.docuseal.com/privacy">Privacybeleid</a> en onze <a target="_blank" href="https://www.docuseal.com/terms">Servicevoorwaarden</a>.
|
||||||
enter_email_to_continue: Voer e-mailadres in om door te gaan
|
enter_email_to_continue: Voer e-mailadres in om door te gaan
|
||||||
account: Account
|
account: Account
|
||||||
@@ -7566,6 +7657,13 @@ nl: &nl
|
|||||||
completed_form_message: Bericht voltooid formulier
|
completed_form_message: Bericht voltooid formulier
|
||||||
completed_form_redirect_button: Knop voor doorverwijzing na voltooid formulier
|
completed_form_redirect_button: Knop voor doorverwijzing na voltooid formulier
|
||||||
documents_copy_email: E-mail met kopie van documenten
|
documents_copy_email: E-mail met kopie van documenten
|
||||||
|
signature_request_email_sent_to_recipients_with_a_link_to_review_and_sign: E-mail met het handtekeningverzoek die naar de ontvangers wordt verzonden met een link om te bekijken en te ondertekenen.
|
||||||
|
view_only_email_sent_to_recipients_that_need_to_only_review_the_documents: E-mail met alleen weergave die wordt verzonden naar ontvangers die de documenten alleen hoeven te bekijken.
|
||||||
|
reminds_recipients_who_havent_completed_their_part_yet_to_sign_following_the_reminders_schedule: Herinnert ontvangers die hun deel nog niet hebben voltooid eraan te ondertekenen, volgens het herinneringsschema.
|
||||||
|
signature_request_sms_sent_to_recipients_with_a_link_to_review_and_sign: Sms met het handtekeningverzoek die naar de ontvangers wordt verzonden met een link om te bekijken en te ondertekenen.
|
||||||
|
phone_2fa_verification_text_message: Sms-bericht voor 2FA-verificatie via telefoon.
|
||||||
|
notification_email_sent_to_the_sender_user_and_bcc_addresses_once_the_submission_is_completed_by_all_parties: Meldingse-mail die naar de verzendende gebruiker en de BCC-adressen wordt gestuurd zodra de indiening door alle partijen is voltooid.
|
||||||
|
completed_documents_copy_email_sent_to_recipients_upon_completion_containing_their_documents: E-mail met een kopie van de voltooide documenten die na voltooiing naar de ontvangers wordt verzonden.
|
||||||
form_custom_css: Aangepaste CSS voor formulier
|
form_custom_css: Aangepaste CSS voor formulier
|
||||||
subject: Onderwerp
|
subject: Onderwerp
|
||||||
body: Inhoud
|
body: Inhoud
|
||||||
@@ -7807,6 +7905,9 @@ nl: &nl
|
|||||||
there_are_no_signatures: Er zijn geen handtekeningen
|
there_are_no_signatures: Er zijn geen handtekeningen
|
||||||
signed_with_trusted_certificate: Ondertekend met vertrouwd certificaat
|
signed_with_trusted_certificate: Ondertekend met vertrouwd certificaat
|
||||||
signed_with_external_certificate: Ondertekend met extern certificaat
|
signed_with_external_certificate: Ondertekend met extern certificaat
|
||||||
|
signature_valid: Handtekening geldig
|
||||||
|
signature_verification_failed: Verificatie van handtekening mislukt
|
||||||
|
contains_unsigned_changes_after_the_last_signature: Bevat niet-ondertekende wijzigingen na de laatste handtekening
|
||||||
setup_2fa_to_continue: Stel 2FA in om door te gaan.
|
setup_2fa_to_continue: Stel 2FA in om door te gaan.
|
||||||
create_a_new_account: Maak een nieuw account aan.
|
create_a_new_account: Maak een nieuw account aan.
|
||||||
you_are_already_signed_in: U bent al aangemeld.
|
you_are_already_signed_in: U bent al aangemeld.
|
||||||
@@ -7933,7 +8034,7 @@ nl: &nl
|
|||||||
awaiting: Wachten
|
awaiting: Wachten
|
||||||
document_id: Document-ID
|
document_id: Document-ID
|
||||||
envelope_id: Envelop-ID
|
envelope_id: Envelop-ID
|
||||||
event_log: Gebeurtenissenlogboek
|
event_log: Gebeurtenislogboek
|
||||||
verify: Verifiëren
|
verify: Verifiëren
|
||||||
testing_log_not_for_production_use: Testlogboek - Niet voor productiegebruik
|
testing_log_not_for_production_use: Testlogboek - Niet voor productiegebruik
|
||||||
original_sha256: Oorspronkelijke SHA256
|
original_sha256: Oorspronkelijke SHA256
|
||||||
@@ -7994,6 +8095,7 @@ nl: &nl
|
|||||||
the_code_has_been_sent_to_your_email: De code is naar uw e-mail verzonden.
|
the_code_has_been_sent_to_your_email: De code is naar uw e-mail verzonden.
|
||||||
enter_the_verification_code_from_your_email: Voer de verificatiecode uit uw e-mail in.
|
enter_the_verification_code_from_your_email: Voer de verificatiecode uit uw e-mail in.
|
||||||
too_many_attempts: Te veel pogingen.
|
too_many_attempts: Te veel pogingen.
|
||||||
|
verification_email_bounced: "Verificatiecode kan niet worden verzonden: e-mail naar dit adres is geretourneerd."
|
||||||
verification_code: Verificatiecode
|
verification_code: Verificatiecode
|
||||||
resend_code: Code opnieuw verzenden
|
resend_code: Code opnieuw verzenden
|
||||||
verify_new_sign_in: Nieuwe aanmelding verifiëren
|
verify_new_sign_in: Nieuwe aanmelding verifiëren
|
||||||
@@ -8207,6 +8309,7 @@ nl: &nl
|
|||||||
invite: Uitnodiging
|
invite: Uitnodiging
|
||||||
link: Link
|
link: Link
|
||||||
mcp: MCP
|
mcp: MCP
|
||||||
|
self: Zelf ondertekenen
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: "<b>E-mail verzonden</b> naar %{submitter_name}"
|
send_email_to_html: "<b>E-mail verzonden</b> naar %{submitter_name}"
|
||||||
bounce_email_html: "<b>E-mail geretourneerd</b> %{submitter_name}"
|
bounce_email_html: "<b>E-mail geretourneerd</b> %{submitter_name}"
|
||||||
@@ -8409,11 +8512,14 @@ ar:
|
|||||||
profile_details: تفاصيل الملف الشخصي
|
profile_details: تفاصيل الملف الشخصي
|
||||||
sign_up_with_google: الاشتراك باستخدام جوجل
|
sign_up_with_google: الاشتراك باستخدام جوجل
|
||||||
sign_up_with_microsoft: الاشتراك باستخدام مايكروسوفت
|
sign_up_with_microsoft: الاشتراك باستخدام مايكروسوفت
|
||||||
|
redirecting_to_provider: جارٍ إعادة التوجيه إلى %{provider}...
|
||||||
by_creating_an_account_you_agree_to_our_html: 'من خلال إنشاء حساب، فإنك توافق على <a target="_blank" href="https://www.docuseal.com/privacy">سياسة الخصوصية</a> و<a target="_blank" href="https://www.docuseal.com/terms">شروط الخدمة</a> الخاصة بنا.'
|
by_creating_an_account_you_agree_to_our_html: 'من خلال إنشاء حساب، فإنك توافق على <a target="_blank" href="https://www.docuseal.com/privacy">سياسة الخصوصية</a> و<a target="_blank" href="https://www.docuseal.com/terms">شروط الخدمة</a> الخاصة بنا.'
|
||||||
enter_email_to_continue: أدخل البريد الإلكتروني للمتابعة
|
enter_email_to_continue: أدخل البريد الإلكتروني للمتابعة
|
||||||
the_code_has_been_sent_to_your_email: تم إرسال الرمز إلى بريدك الإلكتروني.
|
the_code_has_been_sent_to_your_email: تم إرسال الرمز إلى بريدك الإلكتروني.
|
||||||
enter_the_verification_code_from_your_email: أدخل رمز التحقق من بريدك الإلكتروني.
|
enter_the_verification_code_from_your_email: أدخل رمز التحقق من بريدك الإلكتروني.
|
||||||
too_many_attempts: عدد المحاولات كبير جدًا.
|
too_many_attempts: عدد المحاولات كبير جدًا.
|
||||||
|
verification_email_bounced: "تعذر إرسال رمز التحقق: تم رفض البريد الإلكتروني المرسل إلى هذا العنوان."
|
||||||
|
back: رجوع
|
||||||
verification_code: رمز التحقق
|
verification_code: رمز التحقق
|
||||||
resend_code: إعادة إرسال الرمز
|
resend_code: إعادة إرسال الرمز
|
||||||
powered_by: 'مشغل بواسطة'
|
powered_by: 'مشغل بواسطة'
|
||||||
@@ -8520,11 +8626,14 @@ ko:
|
|||||||
profile_details: 프로필 세부 정보
|
profile_details: 프로필 세부 정보
|
||||||
sign_up_with_google: Google로 가입
|
sign_up_with_google: Google로 가입
|
||||||
sign_up_with_microsoft: Microsoft로 가입
|
sign_up_with_microsoft: Microsoft로 가입
|
||||||
|
redirecting_to_provider: "%{provider}(으)로 이동 중..."
|
||||||
by_creating_an_account_you_agree_to_our_html: '계정을 생성함으로써, <a target="_blank" href="https://www.docuseal.com/privacy">개인정보 처리방침</a> 및 <a target="_blank" href="https://www.docuseal.com/terms">서비스 약관</a>에 동의하게 됩니다.'
|
by_creating_an_account_you_agree_to_our_html: '계정을 생성함으로써, <a target="_blank" href="https://www.docuseal.com/privacy">개인정보 처리방침</a> 및 <a target="_blank" href="https://www.docuseal.com/terms">서비스 약관</a>에 동의하게 됩니다.'
|
||||||
enter_email_to_continue: 계속하려면 이메일을 입력하세요
|
enter_email_to_continue: 계속하려면 이메일을 입력하세요
|
||||||
the_code_has_been_sent_to_your_email: 코드가 이메일로 전송되었습니다.
|
the_code_has_been_sent_to_your_email: 코드가 이메일로 전송되었습니다.
|
||||||
enter_the_verification_code_from_your_email: 이메일로 받은 인증 코드를 입력하세요.
|
enter_the_verification_code_from_your_email: 이메일로 받은 인증 코드를 입력하세요.
|
||||||
too_many_attempts: 시도 횟수가 너무 많습니다.
|
too_many_attempts: 시도 횟수가 너무 많습니다.
|
||||||
|
verification_email_bounced: "인증 코드를 보낼 수 없습니다: 이 주소로 보낸 이메일이 반송되었습니다."
|
||||||
|
back: 뒤로
|
||||||
verification_code: 인증 코드
|
verification_code: 인증 코드
|
||||||
resend_code: 코드 재전송
|
resend_code: 코드 재전송
|
||||||
powered_by: '제공:'
|
powered_by: '제공:'
|
||||||
@@ -8631,11 +8740,14 @@ ja:
|
|||||||
profile_details: プロフィールの詳細
|
profile_details: プロフィールの詳細
|
||||||
sign_up_with_google: Googleで登録
|
sign_up_with_google: Googleで登録
|
||||||
sign_up_with_microsoft: Microsoftで登録
|
sign_up_with_microsoft: Microsoftで登録
|
||||||
|
redirecting_to_provider: "%{provider} にリダイレクトしています..."
|
||||||
by_creating_an_account_you_agree_to_our_html: '<a target="_blank" href="https://www.docuseal.com/privacy">プライバシーポリシー</a>および<a target="_blank" href="https://www.docuseal.com/terms">利用規約</a>に同意の上、アカウントを作成します。'
|
by_creating_an_account_you_agree_to_our_html: '<a target="_blank" href="https://www.docuseal.com/privacy">プライバシーポリシー</a>および<a target="_blank" href="https://www.docuseal.com/terms">利用規約</a>に同意の上、アカウントを作成します。'
|
||||||
enter_email_to_continue: 続行するにはメールを入力してください
|
enter_email_to_continue: 続行するにはメールを入力してください
|
||||||
the_code_has_been_sent_to_your_email: コードがあなたのメールに送信されました
|
the_code_has_been_sent_to_your_email: コードがあなたのメールに送信されました
|
||||||
enter_the_verification_code_from_your_email: メールに記載された認証コードを入力してください
|
enter_the_verification_code_from_your_email: メールに記載された認証コードを入力してください
|
||||||
too_many_attempts: 試行回数が多すぎます
|
too_many_attempts: 試行回数が多すぎます
|
||||||
|
verification_email_bounced: "認証コードを送信できません:このアドレス宛のメールが配信できませんでした。"
|
||||||
|
back: 戻る
|
||||||
verification_code: 認証コード
|
verification_code: 認証コード
|
||||||
resend_code: コードを再送信
|
resend_code: コードを再送信
|
||||||
powered_by: '提供元:'
|
powered_by: '提供元:'
|
||||||
|
|||||||
+6
-2
@@ -142,7 +142,8 @@ Rails.application.routes.draw do
|
|||||||
get :completed
|
get :completed
|
||||||
end
|
end
|
||||||
|
|
||||||
resource :resubmit_form, controller: 'start_form', only: :update
|
resource :resubmit_form, controller: 'start_form_resubmit', only: :update
|
||||||
|
resources :start_form_self, only: :update
|
||||||
resource :submit_form_email_2fa, only: %i[create update]
|
resource :submit_form_email_2fa, only: %i[create update]
|
||||||
resources :start_form_email_2fa_send, only: :create
|
resources :start_form_email_2fa_send, only: :create
|
||||||
|
|
||||||
@@ -174,10 +175,13 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
resources :submitters, only: %i[] do
|
resources :submitters, only: %i[] do
|
||||||
resources :download, only: %i[index], controller: 'submitters_download', constraints: { submitter_id: /\d+/ }
|
resources :download, only: %i[index], controller: 'submitters_download', constraints: { submitter_id: /\d+/ }
|
||||||
resources :download, only: %i[index], controller: 'submit_form_completed_download'
|
|
||||||
resources :send_email, only: %i[create], controller: 'submitters_send_email'
|
resources :send_email, only: %i[create], controller: 'submitters_send_email'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :submitters, only: %i[], param: 'slug' do
|
||||||
|
resources :download, only: %i[index], controller: 'submit_form_completed_download'
|
||||||
|
end
|
||||||
|
|
||||||
resources :settings, only: %i[index]
|
resources :settings, only: %i[index]
|
||||||
|
|
||||||
scope '/settings', as: :settings do
|
scope '/settings', as: :settings do
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
development:
|
|
||||||
secret_key_base: 5e626815c7ef00cc5d7b46ff3099160f25e5271b00ced3b8eba1d5045eb0db0ddb925c85b2fea6b2acb4cbdcb779ba9c5a31600f31e6ee8944ada47a768f6a7f
|
|
||||||
|
|
||||||
test:
|
|
||||||
secret_key_base: 7d754998067691dc61859c6c3109966c44e1756a46fd2409f1d81403599605c103ff84b12102673b498bdbaba04d9949d8d7a2f7af47529ee6dc4dd19f0aef3e
|
|
||||||
|
|
||||||
production:
|
|
||||||
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddSubmissionsAccountTemplateStatistics < ActiveRecord::Migration[8.1]
|
||||||
|
def up
|
||||||
|
return unless adapter_name == 'PostgreSQL'
|
||||||
|
|
||||||
|
execute <<~SQL.squish
|
||||||
|
CREATE STATISTICS IF NOT EXISTS submissions_account_template_stats (dependencies, ndistinct)
|
||||||
|
ON account_id, template_id FROM submissions
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
return unless adapter_name == 'PostgreSQL'
|
||||||
|
|
||||||
|
execute 'DROP STATISTICS IF EXISTS submissions_account_template_stats'
|
||||||
|
end
|
||||||
|
end
|
||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.1].define(version: 2026_07_07_055354) do
|
ActiveRecord::Schema[8.1].define(version: 2026_08_19_091500) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "btree_gin"
|
enable_extension "btree_gin"
|
||||||
enable_extension "pg_catalog.plpgsql"
|
enable_extension "pg_catalog.plpgsql"
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module FilenameUtils
|
||||||
|
DANGEROUS_EXTENSIONS = %w[
|
||||||
|
exe com bat cmd scr pif vbs vbe js jse wsf wsh msi msp
|
||||||
|
hta cpl jar app deb rpm dmg pkg mpkg dll so dylib sys
|
||||||
|
inf reg ps1 psm1 psd1 ps1xml psc1 pssc vb vba
|
||||||
|
sh bash zsh fish run out bin elf gadget workflow lnk scf
|
||||||
|
url desktop application action apk ipa xap appx
|
||||||
|
appxbundle msix msixbundle diagcab diagpkg msc ocx
|
||||||
|
drv ins isp mst paf prf shb shs slk ws wsc inf1 inf2
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
DANGEROUS_EXTENSIONS_REGEXP = /\.(#{Regexp.union(DANGEROUS_EXTENSIONS).source})\W*\z/i
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def dangerous_extension(filename)
|
||||||
|
ActiveStorage::Filename.wrap(filename).sanitized[DANGEROUS_EXTENSIONS_REGEXP, 1]&.downcase
|
||||||
|
end
|
||||||
|
end
|
||||||
+18
-8
@@ -1,23 +1,33 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ImageUtils
|
module ImageUtils
|
||||||
|
PNG_REGEXP = %r{\Aimage/(?:png|apng|vnd\.mozilla\.apng)\z}
|
||||||
|
JPEG_REGEXP = %r{\Aimage/(?:jpeg|jpg|pjpeg)\z}
|
||||||
ICO_REGEXP = %r{\Aimage/(?:x-icon|vnd\.microsoft\.icon)\z}
|
ICO_REGEXP = %r{\Aimage/(?:x-icon|vnd\.microsoft\.icon)\z}
|
||||||
BMP_REGEXP = %r{\Aimage/(?:bmp|x-bmp|x-ms-bmp)\z}
|
BMP_REGEXP = %r{\Aimage/(?:bmp|x-bmp|x-ms-bmp)\z}
|
||||||
|
|
||||||
|
UnsupportedFormat = Class.new(StandardError)
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def load_vips(data, content_type: nil, autorot: false)
|
def load_vips(data, content_type: nil, autorot: false)
|
||||||
content_type ||= Marcel::MimeType.for(data)
|
content_type ||= Marcel::MimeType.for(data)
|
||||||
|
|
||||||
if ICO_REGEXP.match?(content_type)
|
image =
|
||||||
LoadIco.call(data)
|
case content_type
|
||||||
elsif BMP_REGEXP.match?(content_type)
|
when PNG_REGEXP
|
||||||
LoadBmp.call(data)
|
Vips::Image.pngload_buffer(data)
|
||||||
else
|
when JPEG_REGEXP
|
||||||
image = Vips::Image.new_from_buffer(data, '')
|
Vips::Image.jpegload_buffer(data)
|
||||||
|
when ICO_REGEXP
|
||||||
|
LoadIco.call(data)
|
||||||
|
when BMP_REGEXP
|
||||||
|
LoadBmp.call(data)
|
||||||
|
else
|
||||||
|
raise UnsupportedFormat, content_type.to_s
|
||||||
|
end
|
||||||
|
|
||||||
autorot ? image.autorot : image
|
autorot ? image.autorot : image
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?(image)
|
def blank?(image)
|
||||||
|
|||||||
+34
-78
@@ -3,6 +3,9 @@
|
|||||||
module LoadBmp
|
module LoadBmp
|
||||||
BPPS = [1, 4, 8, 24, 32].freeze
|
BPPS = [1, 4, 8, 24, 32].freeze
|
||||||
|
|
||||||
|
MAX_COORD = ENV.fetch('VIPS_MAX_COORD', '17000').to_i
|
||||||
|
MAX_PIXELS = 145_000_000
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
@@ -18,40 +21,28 @@ module LoadBmp
|
|||||||
header_data[:height]
|
header_data[:height]
|
||||||
)
|
)
|
||||||
|
|
||||||
if header_data[:bpp] <= 8
|
padded_rows = Vips::Image.new_from_memory_copy(
|
||||||
final_pixel_data = decode_indexed_pixel_data(
|
raw_pixel_data_from_file,
|
||||||
raw_pixel_data_from_file,
|
header_data[:bmp_stride],
|
||||||
header_data[:bpp],
|
header_data[:height],
|
||||||
header_data[:width],
|
1,
|
||||||
header_data[:height],
|
:uchar
|
||||||
header_data[:bmp_stride],
|
)
|
||||||
header_data[:color_table]
|
|
||||||
)
|
|
||||||
bands = 3
|
|
||||||
else
|
|
||||||
final_pixel_data = prepare_unpadded_pixel_data_string(
|
|
||||||
raw_pixel_data_from_file,
|
|
||||||
header_data[:bpp],
|
|
||||||
header_data[:width],
|
|
||||||
header_data[:height],
|
|
||||||
header_data[:bmp_stride]
|
|
||||||
)
|
|
||||||
bands = header_data[:bpp] / 8
|
|
||||||
end
|
|
||||||
|
|
||||||
image = Vips::Image.new_from_memory_copy(final_pixel_data, header_data[:width], header_data[:height], bands, :uchar)
|
|
||||||
|
|
||||||
image = image.flip(:vertical) if header_data[:orientation] == -1
|
|
||||||
|
|
||||||
image_rgb =
|
image_rgb =
|
||||||
if header_data[:bpp] <= 8
|
if header_data[:bpp] <= 8
|
||||||
image
|
decode_indexed_pixel_data(padded_rows, header_data[:bpp], header_data[:width], header_data[:color_table])
|
||||||
elsif bands == 3
|
else
|
||||||
image.recomb(band3_recomb)
|
bands = header_data[:bpp] / 8
|
||||||
elsif bands == 4
|
|
||||||
image.recomb(band4_recomb)
|
image = padded_rows.extract_area(0, 0, header_data[:width] * bands, header_data[:height])
|
||||||
|
.bandfold(factor: bands)
|
||||||
|
|
||||||
|
bands == 3 ? image.recomb(band3_recomb) : image.recomb(band4_recomb)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
image_rgb = image_rgb.flip(:vertical) if header_data[:orientation] == -1
|
||||||
|
|
||||||
image_rgb = image_rgb.copy(interpretation: :srgb) if image_rgb.interpretation != :srgb
|
image_rgb = image_rgb.copy(interpretation: :srgb) if image_rgb.interpretation != :srgb
|
||||||
|
|
||||||
image_rgb
|
image_rgb
|
||||||
@@ -98,6 +89,9 @@ module LoadBmp
|
|||||||
|
|
||||||
raise ArgumentError, 'BMP width must be positive.' if width <= 0
|
raise ArgumentError, 'BMP width must be positive.' if width <= 0
|
||||||
raise ArgumentError, 'BMP height must be positive.' if height <= 0
|
raise ArgumentError, 'BMP height must be positive.' if height <= 0
|
||||||
|
if width > MAX_COORD || height > MAX_COORD || width * height > MAX_PIXELS
|
||||||
|
raise ArgumentError, "BMP dimensions are too large: #{width}x#{height}."
|
||||||
|
end
|
||||||
|
|
||||||
if compression != 0
|
if compression != 0
|
||||||
raise ArgumentError,
|
raise ArgumentError,
|
||||||
@@ -164,62 +158,24 @@ module LoadBmp
|
|||||||
raw_pixel_data_from_file
|
raw_pixel_data_from_file
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_unpadded_pixel_data_string(raw_pixel_data_from_file, bpp, width, height, bmp_stride)
|
def decode_indexed_pixel_data(padded_rows, bpp, width, color_table)
|
||||||
bytes_per_pixel = bpp / 8
|
pixels_per_byte = 8 / bpp
|
||||||
actual_row_width_bytes = width * bytes_per_pixel
|
|
||||||
|
|
||||||
unpadded_rows = Array.new(height)
|
image = padded_rows.maplut(build_palette_lut(bpp, color_table))
|
||||||
current_offset_in_blob = 0
|
image = image.bandunfold.bandfold(factor: 3) if pixels_per_byte > 1
|
||||||
|
|
||||||
height.times do |i|
|
image.extract_area(0, 0, width, padded_rows.height)
|
||||||
if current_offset_in_blob + actual_row_width_bytes > raw_pixel_data_from_file.bytesize
|
|
||||||
raise ArgumentError,
|
|
||||||
"Not enough data in pixel blob for row #{i}. Offset #{current_offset_in_blob}, " \
|
|
||||||
"row width #{actual_row_width_bytes}, blob size #{raw_pixel_data_from_file.bytesize}"
|
|
||||||
end
|
|
||||||
|
|
||||||
unpadded_row_slice = raw_pixel_data_from_file.byteslice(current_offset_in_blob, actual_row_width_bytes)
|
|
||||||
|
|
||||||
if unpadded_row_slice.nil? || unpadded_row_slice.bytesize < actual_row_width_bytes
|
|
||||||
raise ArgumentError, "Failed to slice a full unpadded row from pixel data blob for row #{i}."
|
|
||||||
end
|
|
||||||
|
|
||||||
unpadded_rows[i] = unpadded_row_slice
|
|
||||||
current_offset_in_blob += bmp_stride
|
|
||||||
end
|
|
||||||
|
|
||||||
unpadded_rows.join
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_indexed_pixel_data(raw_data, bpp, width, height, bmp_stride, color_table)
|
def build_palette_lut(bpp, color_table)
|
||||||
palette = color_table.map { |r, g, b| [r, g, b].pack('CCC') }
|
pixels_per_byte = 8 / bpp
|
||||||
|
mask = (1 << bpp) - 1
|
||||||
|
|
||||||
output = String.new(capacity: width * height * 3)
|
entries = Array.new(256) do |byte_val|
|
||||||
|
(0...pixels_per_byte).flat_map { |i| color_table[(byte_val >> ((pixels_per_byte - 1 - i) * bpp)) & mask] }
|
||||||
height.times do |y|
|
|
||||||
row_offset = y * bmp_stride
|
|
||||||
|
|
||||||
case bpp
|
|
||||||
when 1
|
|
||||||
width.times do |x|
|
|
||||||
byte_val = raw_data.getbyte(row_offset + (x >> 3))
|
|
||||||
index = (byte_val >> (7 - (x & 7))) & 0x01
|
|
||||||
output << palette[index]
|
|
||||||
end
|
|
||||||
when 4
|
|
||||||
width.times do |x|
|
|
||||||
byte_val = raw_data.getbyte(row_offset + (x >> 1))
|
|
||||||
index = x.even? ? (byte_val >> 4) & 0x0F : byte_val & 0x0F
|
|
||||||
output << palette[index]
|
|
||||||
end
|
|
||||||
when 8
|
|
||||||
width.times do |x|
|
|
||||||
output << palette[raw_data.getbyte(row_offset + x)]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
output
|
Vips::Image.new_from_memory_copy(entries.flatten.pack('C*'), 256, 1, pixels_per_byte * 3, :uchar)
|
||||||
end
|
end
|
||||||
|
|
||||||
def band3_recomb
|
def band3_recomb
|
||||||
|
|||||||
+1
-1
@@ -94,7 +94,7 @@ module LoadIco
|
|||||||
|
|
||||||
palette = []
|
palette = []
|
||||||
if dib_bpp <= 8
|
if dib_bpp <= 8
|
||||||
num_palette_entries = dib_clr_used.zero? ? (1 << dib_bpp) : dib_clr_used
|
num_palette_entries = [dib_clr_used.zero? ? (1 << dib_bpp) : dib_clr_used, 1 << dib_bpp].min
|
||||||
num_palette_entries.times do
|
num_palette_entries.times do
|
||||||
palette_color_bytes = dib_io.read(4)
|
palette_color_bytes = dib_io.read(4)
|
||||||
return nil unless palette_color_bytes && palette_color_bytes.bytesize == 4
|
return nil unless palette_color_bytes && palette_color_bytes.bytesize == 4
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ module Params
|
|||||||
in_path(params, :message) do |message_params|
|
in_path(params, :message) do |message_params|
|
||||||
type(message_params, :subject, String)
|
type(message_params, :subject, String)
|
||||||
type(message_params, :body, String)
|
type(message_params, :body, String)
|
||||||
|
|
||||||
required(message_params, :body)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -53,8 +51,6 @@ module Params
|
|||||||
in_path(params, :message, skip_blank: true) do |message_params|
|
in_path(params, :message, skip_blank: true) do |message_params|
|
||||||
type(message_params, :subject, String)
|
type(message_params, :subject, String)
|
||||||
type(message_params, :body, String)
|
type(message_params, :body, String)
|
||||||
|
|
||||||
required(message_params, :body)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
value_in(params, :order, %w[preserved random], allow_nil: true)
|
value_in(params, :order, %w[preserved random], allow_nil: true)
|
||||||
@@ -119,8 +115,6 @@ module Params
|
|||||||
in_path(params, :message) do |message_params|
|
in_path(params, :message) do |message_params|
|
||||||
type(message_params, :subject, String)
|
type(message_params, :subject, String)
|
||||||
type(message_params, :body, String)
|
type(message_params, :body, String)
|
||||||
|
|
||||||
required(message_params, :body)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
value_in(params, :order, %w[preserved random], allow_nil: true)
|
value_in(params, :order, %w[preserved random], allow_nil: true)
|
||||||
|
|||||||
+12
-36
@@ -6,43 +6,19 @@ module PdfUtils
|
|||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def encrypted?(data, password: nil)
|
def merge(io_files)
|
||||||
HexaPDF::Document.new(io: StringIO.new(data), decryption_opts: { password: })
|
|
||||||
|
|
||||||
false
|
|
||||||
rescue HexaPDF::EncryptionError
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def decrypt(data, password)
|
|
||||||
encrypted_doc = HexaPDF::Document.new(io: StringIO.new(data), decryption_opts: { password: })
|
|
||||||
|
|
||||||
decrypted_doc = HexaPDF::Document.new
|
|
||||||
|
|
||||||
encrypted_doc.pages.each do |page|
|
|
||||||
decrypted_doc.pages << decrypted_doc.import(page)
|
|
||||||
end
|
|
||||||
|
|
||||||
decrypted_io = StringIO.new
|
|
||||||
|
|
||||||
decrypted_doc.write(decrypted_io, validate: false)
|
|
||||||
|
|
||||||
decrypted_io.tap(&:rewind).read
|
|
||||||
end
|
|
||||||
|
|
||||||
def merge(files)
|
|
||||||
merged_pdf = HexaPDF::Document.new
|
|
||||||
|
|
||||||
files.each do |file|
|
|
||||||
pdf = HexaPDF::Document.new(io: file)
|
|
||||||
pdf.pages.each { |page| merged_pdf.pages << merged_pdf.import(page) }
|
|
||||||
end
|
|
||||||
|
|
||||||
merged_content = StringIO.new
|
merged_content = StringIO.new
|
||||||
merged_pdf.validate(auto_correct: true)
|
|
||||||
merged_pdf.write(merged_content, validate: false)
|
|
||||||
merged_content.rewind
|
|
||||||
|
|
||||||
merged_content
|
Pdfium.with_instance do
|
||||||
|
Pdfium::Document.create do |merged_pdf|
|
||||||
|
io_files.each do |io|
|
||||||
|
Pdfium::Document.open_io(io) { |pdf| merged_pdf.import_pages(pdf) }
|
||||||
|
end
|
||||||
|
|
||||||
|
merged_pdf.save(merged_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
merged_content.tap(&:rewind)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
+894
-18
File diff suppressed because it is too large.
Load diff
@@ -54,6 +54,7 @@ Puma::Plugin.create do
|
|||||||
Dir.chdir(ENV.fetch('WORKDIR', nil)) unless ENV['WORKDIR'].to_s.empty?
|
Dir.chdir(ENV.fetch('WORKDIR', nil)) unless ENV['WORKDIR'].to_s.empty?
|
||||||
|
|
||||||
exec('redis-server', '--requirepass', Digest::SHA1.hexdigest("redis#{ENV.fetch('SECRET_KEY_BASE', '')}"),
|
exec('redis-server', '--requirepass', Digest::SHA1.hexdigest("redis#{ENV.fetch('SECRET_KEY_BASE', '')}"),
|
||||||
|
'--bind', '127.0.0.1', '--port', '16379',
|
||||||
'--loglevel', 'warning')
|
'--loglevel', 'warning')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loaded 100 of 124 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user