From 7dddef4c94e93923a0d6034e463f3a60bc1165e2 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 3 Aug 2026 22:21:58 +0300 Subject: [PATCH 01/16] fix date time formats --- app/javascript/submission_form/area.vue | 2 +- config/locales/i18n.yml | 284 ++++++++++++++++++++++-- 2 files changed, 271 insertions(+), 15 deletions(-) diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index d73aee07..648b8a44 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -101,7 +101,7 @@
- {{ new Date(signature.created_at).toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'short' }) }} + {{ new Date(signature.created_at).toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'short' }) }}
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 776aea52..3d24c049 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -1113,9 +1113,16 @@ en: &en submission_submitters: Submitters list submission_link: Submission link documents_link: Documents link + date: + formats: + default: "%m/%d/%Y" + short: "%b %-d" + long: "%B %-d, %Y" time: formats: - detailed: "%B %d, %Y %H:%M:%S" + short: "%b %-d, %-I:%M %p" + long: "%B %-d, %Y at %-I:%M %p" + detailed: "%B %-d, %Y at %-I:%M:%S %p" es: &es knowledge_based_authentication: Autenticación basada en el conocimiento @@ -2210,9 +2217,30 @@ es: &es submission_submitters: Lista de firmantes submission_link: Enlace del envío documents_link: Enlace de los documentos + date: + abbr_month_names: + - + - ene + - feb + - mar + - abr + - may + - jun + - jul + - ago + - sept + - oct + - nov + - dic + formats: + default: "%d/%m/%Y" + short: "%-d %b" + long: "%-d de %B de %Y" time: formats: - detailed: "%-d de %B de %Y %H:%M:%S" + short: "%-d %b, %-H:%M" + long: "%-d de %B de %Y a las %-H:%M" + detailed: "%-d de %B de %Y a las %-H:%M:%S" it: &it knowledge_based_authentication: Autenticazione basata sulla conoscenza @@ -3307,9 +3335,16 @@ it: &it submission_submitters: Lista dei firmatari submission_link: Link dell'invio documents_link: Link dei documenti + date: + formats: + default: "%d/%m/%Y" + short: "%-d %b" + long: "%-d %B %Y" time: formats: - detailed: "%d %B %Y %H:%M:%S" + short: "%-d %b, %H:%M" + long: "%-d %B %Y alle ore %H:%M" + detailed: "%-d %B %Y alle ore %H:%M:%S" fr: &fr knowledge_based_authentication: Authentification basée sur la connaissance @@ -4401,9 +4436,29 @@ fr: &fr submission_submitters: Liste des signataires submission_link: Lien de la soumission documents_link: Lien des documents + date: + abbr_month_names: + - + - janv. + - févr. + - mars + - avr. + - mai + - juin + - juil. + - août + - sept. + - oct. + - nov. + - déc. + formats: + short: "%-d %b" + long: "%-d %B %Y" time: formats: - detailed: "%A %d %B %Y %Hh%Mm%Ss" + short: "%-d %b, %H:%M" + long: "%-d %B %Y à %H:%M" + detailed: "%-d %B %Y à %H:%M:%S" pt: &pt knowledge_based_authentication: Autenticação baseada em conhecimento @@ -5498,9 +5553,29 @@ pt: &pt submission_submitters: Lista de signatários submission_link: Link da submissão documents_link: Link dos documentos + date: + abbr_month_names: + - + - jan. + - fev. + - mar. + - abr. + - mai. + - jun. + - jul. + - ago. + - set. + - out. + - nov. + - dez. + formats: + short: "%-d de %b" + long: "%-d de %B de %Y" time: formats: - detailed: "%A, %d de %B de %Y, %H:%M:%Sh" + short: "%-d de %b, %H:%M" + long: "%-d de %B de %Y às %H:%M" + detailed: "%-d de %B de %Y às %H:%M:%S" de: &de knowledge_based_authentication: Wissensbasierte Authentifizierung @@ -6595,11 +6670,42 @@ de: &de submission_submitters: Liste der Unterzeichner submission_link: Link der Einreichung documents_link: Link der Dokumente + date: + abbr_month_names: + - + - Jan. + - Feb. + - März + - Apr. + - Mai + - Juni + - Juli + - Aug. + - Sept. + - Okt. + - Nov. + - Dez. + formats: + default: "%d.%m.%Y" + short: "%-d. %b" + long: "%-d. %B %Y" time: formats: - detailed: "%A, %d. %B %Y, %H:%M:%S Uhr" + short: "%-d. %b, %H:%M" + long: "%-d. %B %Y um %H:%M" + detailed: "%-d. %B %Y um %H:%M:%S" pl: + date: + formats: + default: "%d.%m.%Y" + short: "%-d %b" + long: "%-d %B %Y" + time: + formats: + short: "%-d %b, %H:%M" + long: "%-d %B %Y %H:%M" + detailed: "%-d %B %Y %H:%M:%S" require_phone_2fa_to_open: Wymagaj uwierzytelniania telefonicznego 2FA do otwarcia the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Nadawca zażądał uwierzytelnienia dwuskładnikowego poprzez jednorazowe hasło wysłane na Twój numer telefonu %{phone}. send_verification_code: Wyślij kod weryfikacyjny @@ -6701,6 +6807,44 @@ pl: too_many_requests_try_again_later: Zbyt wiele żądań. Spróbuj ponownie później. uk: + date: + month_names: + - + - січня + - лютого + - березня + - квітня + - травня + - червня + - липня + - серпня + - вересня + - жовтня + - листопада + - грудня + abbr_month_names: + - + - січ. + - лют. + - бер. + - квіт. + - трав. + - черв. + - лип. + - серп. + - вер. + - жовт. + - лист. + - груд. + formats: + default: "%d.%m.%Y" + short: "%-d %b" + long: "%-d %B %Y р." + time: + formats: + short: "%-d %b, %H:%M" + long: "%-d %B %Y р. о %H:%M" + detailed: "%-d %B %Y р. о %H:%M:%S" require_phone_2fa_to_open: Вимагати двофакторну автентифікацію через телефон для відкриття the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Відправник запитав двофакторну автентифікацію за допомогою одноразового пароля, надісланого на ваш номер телефону %{phone}. send_verification_code: Надіслати код перевірки @@ -6802,6 +6946,44 @@ uk: too_many_requests_try_again_later: Забагато запитів. Спробуйте пізніше. cs: + date: + month_names: + - + - ledna + - února + - března + - dubna + - května + - června + - července + - srpna + - září + - října + - listopadu + - prosince + abbr_month_names: + - + - led + - úno + - bře + - dub + - kvě + - čvn + - čvc + - srp + - zář + - říj + - lis + - pro + formats: + default: "%d. %m. %Y" + short: "%-d. %-m." + long: "%-d. %B %Y" + time: + formats: + short: "%-d. %-m. %-H:%M" + long: "%-d. %B %Y v %-H:%M" + detailed: "%-d. %B %Y v %-H:%M:%S" require_phone_2fa_to_open: Vyžadovat otevření pomocí telefonního 2FA the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: Odesílatel požádal o dvoufaktorové ověření pomocí jednorázového hesla zaslaného na vaše telefonní číslo %{phone}. send_verification_code: Odeslat ověřovací kód @@ -6903,6 +7085,30 @@ cs: too_many_requests_try_again_later: Příliš mnoho požadavků. Zkuste to později. he: + date: + abbr_month_names: + - + - ינו׳ + - פבר׳ + - מרץ + - אפר׳ + - מאי + - יוני + - יולי + - אוג׳ + - ספט׳ + - אוק׳ + - נוב׳ + - דצמ׳ + formats: + default: "%d.%m.%Y" + short: "%-d ב%b" + long: "%-d ב%B %Y" + time: + formats: + short: "%-d ב%b, %-H:%M" + long: "%-d ב%B %Y בשעה %-H:%M" + detailed: "%-d ב%B %Y בשעה %-H:%M:%S" require_phone_2fa_to_open: דרוש אימות דו-שלבי באמצעות טלפון לפתיחה the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: השולח ביקש אימות דו-שלבי באמצעות סיסמה חד פעמית שנשלחה למספר הטלפון שלך %{phone}. send_verification_code: שלח קוד אימות @@ -8093,11 +8299,29 @@ nl: &nl submission_submitters: Lijst van ondertekenaars submission_link: Link van de inzending documents_link: Link van de documenten + date: + formats: + short: "%-d %b" + long: "%-d %B %Y" time: formats: - detailed: "%d %B %Y %H:%M:%S" + short: "%-d %b, %H:%M" + long: "%-d %B %Y om %H:%M" + detailed: "%-d %B %Y om %H:%M:%S" ar: + date: + formats: + default: "%d‏/%m‏/%Y" + short: "%-d %b" + long: "%-d %B %Y" + time: + am: "ص" + pm: "م" + formats: + short: "%-d %b، %-I:%M %p" + long: "%-d %B %Y في %-I:%M %p" + detailed: "%-d %B %Y في %-I:%M:%S %p" require_phone_2fa_to_open: "يتطلب المصادقة الثنائية عبر الهاتف للفتح" the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: "طلب المرسل المصادقة الثنائية عبر كلمة مرور لمرة واحدة مرسلة إلى رقم هاتفك %{phone}." send_verification_code: "إرسال رمز التحقق" @@ -8199,6 +8423,16 @@ ar: too_many_requests_try_again_later: طلبات كثيرة جدًا. حاول مرة أخرى لاحقًا. ko: + date: + formats: + default: "%Y. %m. %d." + short: "%b %-d일" + long: "%Y년 %B %-d일" + time: + formats: + short: "%b %-d일 %p %-I:%M" + long: "%Y년 %B %-d일 %p %-I:%M" + detailed: "%Y년 %B %-d일 %p %-I:%M:%S" require_phone_2fa_to_open: 열려면 휴대폰 2FA 요구 the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: 발신자가 %{phone} 전화번호로 전송된 일회용 비밀번호를 통해 2단계 인증을 요청했습니다. send_verification_code: 인증 코드 보내기 @@ -8300,6 +8534,16 @@ ko: too_many_requests_try_again_later: 요청이 너무 많습니다. 나중에 다시 시도하세요. ja: + date: + formats: + default: "%Y/%m/%d" + short: "%-m月%-d日" + long: "%Y年%-m月%-d日" + time: + formats: + short: "%-m月%-d日 %-H:%M" + long: "%Y年%-m月%-d日 %-H:%M" + detailed: "%Y年%-m月%-d日 %-H:%M:%S" require_phone_2fa_to_open: 電話による2段階認証が必要です the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: 送信者は、%{phone} に送信されたワンタイムパスワードによる2段階認証を要求しました。 send_verification_code: 認証コードを送信 @@ -8402,21 +8646,33 @@ ja: en-US: <<: *en - date: - formats: - default: "%m/%d/%Y" - time: - formats: - detailed: "%B %d, %Y %I:%M:%S %p" en-GB: <<: *en date: + abbr_month_names: + - + - Jan + - Feb + - Mar + - Apr + - May + - Jun + - Jul + - Aug + - Sept + - Oct + - Nov + - Dec formats: default: "%d/%m/%Y" + short: "%-d %b" + long: "%-d %B %Y" time: formats: - detailed: "%d %B, %Y %H:%M:%S" + short: "%-d %b, %H:%M" + long: "%-d %B %Y at %H:%M" + detailed: "%-d %B %Y at %H:%M:%S" es-ES: <<: *es From dc1de72e18ad5111c321714e17f7ce51316a9469 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 6 Aug 2026 17:50:03 +0300 Subject: [PATCH 02/16] fix mysql migrations --- db/migrate/20250915060548_create_lock_events.rb | 3 ++- .../20251121092044_add_is_first_to_completed_submitters.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/db/migrate/20250915060548_create_lock_events.rb b/db/migrate/20250915060548_create_lock_events.rb index eb1a5597..0ff49ddd 100644 --- a/db/migrate/20250915060548_create_lock_events.rb +++ b/db/migrate/20250915060548_create_lock_events.rb @@ -6,7 +6,8 @@ class CreateLockEvents < ActiveRecord::Migration[8.0] t.string :key, index: true, null: false t.string :event_name, null: false - t.index %i[event_name key], unique: adapter_name != 'Mysql2', where: "event_name IN ('start', 'complete')" + t.index %i[event_name key], unique: connection.supports_partial_index?, + where: "event_name IN ('start', 'complete')" t.timestamps end diff --git a/db/migrate/20251121092044_add_is_first_to_completed_submitters.rb b/db/migrate/20251121092044_add_is_first_to_completed_submitters.rb index 1b3adf0f..56869043 100644 --- a/db/migrate/20251121092044_add_is_first_to_completed_submitters.rb +++ b/db/migrate/20251121092044_add_is_first_to_completed_submitters.rb @@ -10,7 +10,7 @@ class AddIsFirstToCompletedSubmitters < ActiveRecord::Migration[8.0] where: 'is_first = TRUE', name: 'index_completed_submitters_account_id_completed_at_is_first' - add_index :completed_submitters, :submission_id, unique: adapter_name != 'Mysql2', + add_index :completed_submitters, :submission_id, unique: connection.supports_partial_index?, where: 'is_first = TRUE' end end From b6498ec0496d1035e4b5a2386fce46f8c1a8a21d Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Fri, 7 Aug 2026 01:15:30 +0300 Subject: [PATCH 03/16] add PayPal translations --- config/locales/i18n.yml | 49 +++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 3d24c049..a14d53aa 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -57,8 +57,11 @@ en: &en paypal_account_has_been_connected: PayPal account has been connected. re_connect_paypal: Re-connect PayPal disconnect: Disconnect - failed_to_connect_paypal: Failed to connect PayPal. Please try again. - paypal_connect_not_completed: PayPal connection was not completed. + failed_to_connect_paypal_please_try_again: Failed to connect PayPal. Please try again. + paypal_connection_was_not_completed: PayPal connection was not completed. + disconnecting_your_paypal_account_will_prevent_you_from_offering_paypal_services_and_products_on_your_website_do_you_wish_to_continue: Disconnecting your PayPal account will prevent you from offering PayPal services and products on your website. Do you wish to continue? + attention_you_currently_cannot_receive_payments_due_to_restriction_on_your_paypal_account_please_reach_out_to_paypal_customer_support_or_connect_to_paypal_com_for_more_information: 'Attention: You currently cannot receive payments due to restriction on your PayPal account. Please reach out to PayPal Customer Support or connect to https://www.paypal.com for more information.' + attention_please_confirm_your_email_address_on_paypal_com_in_order_to_receive_payments_you_currently_cannot_receive_payments: 'Attention: Please confirm your email address on https://www.paypal.com/businessprofile/settings in order to receive payments! You currently cannot receive payments.' payment_receipt: Payment Receipt amount_paid: Amount paid transaction: Transaction @@ -1154,8 +1157,11 @@ es: &es paypal_integration: Integración con PayPal paypal_account_has_been_connected: La cuenta de PayPal ha sido conectada. re_connect_paypal: Volver a conectar PayPal - failed_to_connect_paypal: No se pudo conectar PayPal. Por favor, inténtalo de nuevo. - paypal_connect_not_completed: La conexión con PayPal no se completó. + failed_to_connect_paypal_please_try_again: No se pudo conectar PayPal. Por favor, inténtalo de nuevo. + paypal_connection_was_not_completed: La conexión con PayPal no se completó. + disconnecting_your_paypal_account_will_prevent_you_from_offering_paypal_services_and_products_on_your_website_do_you_wish_to_continue: Desconectar tu cuenta de PayPal impedirá que ofrezcas servicios y productos de PayPal en tu sitio web. ¿Deseas continuar? + attention_you_currently_cannot_receive_payments_due_to_restriction_on_your_paypal_account_please_reach_out_to_paypal_customer_support_or_connect_to_paypal_com_for_more_information: 'Atención: Actualmente no puedes recibir pagos debido a una restricción en tu cuenta de PayPal. Por favor, contacta con el Soporte al Cliente de PayPal o visita https://www.paypal.com para más información.' + attention_please_confirm_your_email_address_on_paypal_com_in_order_to_receive_payments_you_currently_cannot_receive_payments: 'Atención: Por favor, confirma tu dirección de correo electrónico en https://www.paypal.com/businessprofile/settings para poder recibir pagos. Actualmente no puedes recibir pagos.' payment_receipt: Recibo de pago amount_paid: Importe pagado transaction: Transacción @@ -2272,8 +2278,11 @@ it: &it paypal_integration: Integrazione PayPal paypal_account_has_been_connected: L'account PayPal è stato collegato. re_connect_paypal: Ricollega PayPal - failed_to_connect_paypal: Impossibile collegare PayPal. Riprova. - paypal_connect_not_completed: La connessione con PayPal non è stata completata. + failed_to_connect_paypal_please_try_again: Impossibile collegare PayPal. Riprova. + paypal_connection_was_not_completed: La connessione con PayPal non è stata completata. + disconnecting_your_paypal_account_will_prevent_you_from_offering_paypal_services_and_products_on_your_website_do_you_wish_to_continue: La disconnessione del tuo account PayPal ti impedirà di offrire servizi e prodotti PayPal sul tuo sito web. Vuoi continuare? + attention_you_currently_cannot_receive_payments_due_to_restriction_on_your_paypal_account_please_reach_out_to_paypal_customer_support_or_connect_to_paypal_com_for_more_information: 'Attenzione: Al momento non puoi ricevere pagamenti a causa di una restrizione sul tuo account PayPal. Contatta l''Assistenza Clienti PayPal o visita https://www.paypal.com per maggiori informazioni.' + attention_please_confirm_your_email_address_on_paypal_com_in_order_to_receive_payments_you_currently_cannot_receive_payments: 'Attenzione: Conferma il tuo indirizzo email su https://www.paypal.com/businessprofile/settings per poter ricevere pagamenti! Al momento non puoi ricevere pagamenti.' payment_receipt: Ricevuta di pagamento amount_paid: Importo pagato transaction: Transazione @@ -3376,8 +3385,11 @@ fr: &fr paypal_integration: Intégration PayPal paypal_account_has_been_connected: Le compte PayPal a été connecté. re_connect_paypal: Reconnecter PayPal - failed_to_connect_paypal: Échec de la connexion à PayPal. Veuillez réessayer. - paypal_connect_not_completed: La connexion à PayPal n'a pas été complétée. + failed_to_connect_paypal_please_try_again: Échec de la connexion à PayPal. Veuillez réessayer. + paypal_connection_was_not_completed: La connexion à PayPal n'a pas été complétée. + disconnecting_your_paypal_account_will_prevent_you_from_offering_paypal_services_and_products_on_your_website_do_you_wish_to_continue: Déconnecter votre compte PayPal vous empêchera de proposer des services et produits PayPal sur votre site web. Voulez-vous continuer ? + attention_you_currently_cannot_receive_payments_due_to_restriction_on_your_paypal_account_please_reach_out_to_paypal_customer_support_or_connect_to_paypal_com_for_more_information: 'Attention : Vous ne pouvez actuellement pas recevoir de paiements en raison d''une restriction sur votre compte PayPal. Veuillez contacter le service client PayPal ou visiter https://www.paypal.com pour plus d''informations.' + attention_please_confirm_your_email_address_on_paypal_com_in_order_to_receive_payments_you_currently_cannot_receive_payments: 'Attention : Veuillez confirmer votre adresse e-mail sur https://www.paypal.com/businessprofile/settings afin de pouvoir recevoir des paiements ! Vous ne pouvez actuellement pas recevoir de paiements.' payment_receipt: Reçu de paiement amount_paid: Montant payé transaction: Transaction @@ -4490,8 +4502,11 @@ pt: &pt paypal_integration: Integração com PayPal paypal_account_has_been_connected: Conta PayPal foi conectada. re_connect_paypal: Reconectar PayPal - failed_to_connect_paypal: Falha ao conectar PayPal. Tente novamente. - paypal_connect_not_completed: A conexão com PayPal não foi concluída. + failed_to_connect_paypal_please_try_again: Falha ao conectar PayPal. Tente novamente. + paypal_connection_was_not_completed: A conexão com PayPal não foi concluída. + disconnecting_your_paypal_account_will_prevent_you_from_offering_paypal_services_and_products_on_your_website_do_you_wish_to_continue: Desconectar sua conta PayPal impedirá que você ofereça serviços e produtos PayPal no seu site. Deseja continuar? + attention_you_currently_cannot_receive_payments_due_to_restriction_on_your_paypal_account_please_reach_out_to_paypal_customer_support_or_connect_to_paypal_com_for_more_information: 'Atenção: No momento você não pode receber pagamentos devido a uma restrição na sua conta PayPal. Entre em contato com o Suporte ao Cliente PayPal ou acesse https://www.paypal.com para mais informações.' + attention_please_confirm_your_email_address_on_paypal_com_in_order_to_receive_payments_you_currently_cannot_receive_payments: 'Atenção: Por favor, confirme seu endereço de e-mail em https://www.paypal.com/businessprofile/settings para poder receber pagamentos! No momento você não pode receber pagamentos.' payment_receipt: Recibo de pagamento amount_paid: Valor pago transaction: Transação @@ -5617,8 +5632,11 @@ de: &de paypal_integration: PayPal-Integration paypal_account_has_been_connected: PayPal-Konto wurde verbunden. re_connect_paypal: PayPal erneut verbinden - failed_to_connect_paypal: PayPal konnte nicht verbunden werden. Bitte erneut versuchen. - paypal_connect_not_completed: Die PayPal-Verbindung wurde nicht abgeschlossen. + failed_to_connect_paypal_please_try_again: PayPal konnte nicht verbunden werden. Bitte erneut versuchen. + paypal_connection_was_not_completed: Die PayPal-Verbindung wurde nicht abgeschlossen. + disconnecting_your_paypal_account_will_prevent_you_from_offering_paypal_services_and_products_on_your_website_do_you_wish_to_continue: Das Trennen Ihres PayPal-Kontos verhindert, dass Sie PayPal-Dienste und -Produkte auf Ihrer Website anbieten können. Möchten Sie fortfahren? + attention_you_currently_cannot_receive_payments_due_to_restriction_on_your_paypal_account_please_reach_out_to_paypal_customer_support_or_connect_to_paypal_com_for_more_information: 'Achtung: Sie können derzeit aufgrund einer Einschränkung Ihres PayPal-Kontos keine Zahlungen empfangen. Bitte wenden Sie sich an den PayPal-Kundenservice oder besuchen Sie https://www.paypal.com für weitere Informationen.' + attention_please_confirm_your_email_address_on_paypal_com_in_order_to_receive_payments_you_currently_cannot_receive_payments: 'Achtung: Bitte bestätigen Sie Ihre E-Mail-Adresse auf https://www.paypal.com/businessprofile/settings, um Zahlungen empfangen zu können! Sie können derzeit keine Zahlungen empfangen.' payment_receipt: Zahlungsbeleg amount_paid: Gezahlter Betrag transaction: Transaktion @@ -7249,8 +7267,11 @@ nl: &nl paypal_integration: PayPal-integratie paypal_account_has_been_connected: PayPal-account is verbonden. re_connect_paypal: PayPal opnieuw verbinden - failed_to_connect_paypal: PayPal kon niet worden verbonden. Probeer het opnieuw. - paypal_connect_not_completed: De verbinding met PayPal is niet voltooid. + failed_to_connect_paypal_please_try_again: PayPal kon niet worden verbonden. Probeer het opnieuw. + paypal_connection_was_not_completed: De verbinding met PayPal is niet voltooid. + disconnecting_your_paypal_account_will_prevent_you_from_offering_paypal_services_and_products_on_your_website_do_you_wish_to_continue: Het loskoppelen van uw PayPal-account voorkomt dat u PayPal-diensten en -producten op uw website kunt aanbieden. Wilt u doorgaan? + attention_you_currently_cannot_receive_payments_due_to_restriction_on_your_paypal_account_please_reach_out_to_paypal_customer_support_or_connect_to_paypal_com_for_more_information: 'Let op: U kunt momenteel geen betalingen ontvangen vanwege een beperking op uw PayPal-account. Neem contact op met de PayPal-klantenservice of ga naar https://www.paypal.com voor meer informatie.' + attention_please_confirm_your_email_address_on_paypal_com_in_order_to_receive_payments_you_currently_cannot_receive_payments: 'Let op: Bevestig uw e-mailadres op https://www.paypal.com/businessprofile/settings om betalingen te kunnen ontvangen! U kunt momenteel geen betalingen ontvangen.' payment_receipt: Betalingsbewijs amount_paid: Betaald bedrag transaction: Transactie From 485b7f2c8f9f8da6efaf3bc71ed4be28cc6268e9 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 7 Aug 2026 13:27:29 +0300 Subject: [PATCH 04/16] fix dynamic variable parse --- .../template_builder/dynamic_variables_schema.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/javascript/template_builder/dynamic_variables_schema.js b/app/javascript/template_builder/dynamic_variables_schema.js index f085b0b4..64869bc3 100644 --- a/app/javascript/template_builder/dynamic_variables_schema.js +++ b/app/javascript/template_builder/dynamic_variables_schema.js @@ -417,10 +417,8 @@ function buildOperators (tags) { break } case 'end': { - const popped = stack.pop() - - if (popped.operator) { - popped.operator.endTag = tag + if (stack.length > 1) { + stack.pop().operator.endTag = tag } break @@ -434,7 +432,13 @@ function buildOperators (tags) { return operators } +function hasBlankKey (keyString) { + return !keyString || keyString.split('.').some((key) => !key.trim()) +} + function assignNestedSchema (propertiesHash, parentProperties, keyString, value) { + if (hasBlankKey(keyString)) return + const keys = keyString.split('.') const lastKey = keys.pop() @@ -460,6 +464,8 @@ function assignNestedSchema (propertiesHash, parentProperties, keyString, value) } function assignNestedSchemaWithPriority (propertiesHash, parentProperties, keyString, newType) { + if (hasBlankKey(keyString)) return + const keys = keyString.split('.') const lastKey = keys.pop() @@ -523,6 +529,8 @@ function processOperators (operators, propertiesHash = {}, parentProperties = {} processOperators(op.elseChildren, propertiesHash, parentProperties) break case 'for': { + if (hasBlankKey(op.variableName)) break + const parts = op.variableName.split('.') const singularKey = singularize(parts[parts.length - 1]) From eeec852b3bc9ca95e4e959cca5871a546ea248db Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 7 Aug 2026 21:45:56 +0300 Subject: [PATCH 05/16] adjust preview cache --- .../preview_document_page_controller.rb | 65 ++++++++++++++----- lib/pdfium.rb | 38 +++++++++++ lib/templates/process_document.rb | 4 +- 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/app/controllers/preview_document_page_controller.rb b/app/controllers/preview_document_page_controller.rb index 5f5a8554..05b848da 100644 --- a/app/controllers/preview_document_page_controller.rb +++ b/app/controllers/preview_document_page_controller.rb @@ -5,6 +5,10 @@ class PreviewDocumentPageController < ActionController::API FORMAT = Templates::ProcessDocument::FORMAT + TMPFILE_PREFIX = 'attachment-' + TMPFILE_TTL = 5.minutes + TMPFILE_MAX_TOTAL_SIZE = Docuseal.multitenant? ? 400.megabytes : 1.gigabyte + def show result_data = ApplicationRecord.signed_id_verifier.verified(params[:signed_key], purpose: :attachment) @@ -30,34 +34,63 @@ class PreviewDocumentPageController < ActionController::API allow_other_host: true end - file_path = - if attachment.service.name == :disk - ActiveStorage::Blob.service.path_for(attachment.key) - else - find_or_create_document_tempfile_path(attachment) - end - preview_image = - Templates::ProcessDocument.generate_pdf_preview_from_file(attachment, file_path, params[:id].to_i) + open_attachment_io(attachment) do |io| + Templates::ProcessDocument.generate_pdf_preview_from_io(attachment, io, params[:id].to_i) + end redirect_to preview_image.url(time: ActiveStorage::Attachment.service_url_time), allow_other_host: true end - def find_or_create_document_tempfile_path(attachment) - file_path = "#{Dir.tmpdir}/attachment-#{Digest::SHA1.hexdigest("#{attachment.id}-#{attachment.uuid}")}" + def open_attachment_io(attachment, &) + return File.open(ActiveStorage::Blob.service.path_for(attachment.key), 'rb', &) if attachment.service.name == :disk - File.open(file_path, File::RDWR | File::CREAT, 0o644) do |f| - f.flock(File::LOCK_EX) + file_name = "#{TMPFILE_PREFIX}#{Digest::SHA1.hexdigest("#{attachment.id}-#{attachment.uuid}")}" + + file_path = File.join(Dir.tmpdir, file_name) + + File.open(file_path, File::RDWR | File::CREAT, 0o644) do |file| + file.flock(File::LOCK_EX) # rubocop:disable Style/ZeroLengthPredicate - if f.size.zero? - f.binmode + if file.size.zero? + cleanup_stale_tempfiles + + file.binmode - f.write(attachment.download) + file.write(attachment.download) + else + FileUtils.touch(file_path) end # rubocop:enable Style/ZeroLengthPredicate + + file.flock(File::LOCK_UN) + + yield file end + end + + def cleanup_stale_tempfiles + entries = + Dir.glob(File.join(Dir.tmpdir, "#{TMPFILE_PREFIX}*")).filter_map do |path| + stat = File.stat(path) + + [path, stat.mtime, stat.size] + rescue Errno::ENOENT + nil + end - file_path + total_size = entries.sum(&:last) + stale_time = TMPFILE_TTL.ago + + entries.sort_by(&:second).each do |path, mtime, size| + break if mtime > stale_time && total_size <= TMPFILE_MAX_TOTAL_SIZE + + File.unlink(path) + + total_size -= size + rescue Errno::ENOENT + nil + end end end diff --git a/lib/pdfium.rb b/lib/pdfium.rb index 06d22475..e7c5ec40 100644 --- a/lib/pdfium.rb +++ b/lib/pdfium.rb @@ -98,6 +98,7 @@ class Pdfium attach_function :FPDF_LoadDocument, %i[string FPDF_STRING], :FPDF_DOCUMENT attach_function :FPDF_LoadMemDocument, %i[pointer int FPDF_STRING], :FPDF_DOCUMENT + attach_function :FPDF_LoadCustomDocument, %i[pointer FPDF_STRING], :FPDF_DOCUMENT attach_function :FPDF_CloseDocument, [:FPDF_DOCUMENT], :void attach_function :FPDF_GetPageCount, [:FPDF_DOCUMENT], :int attach_function :FPDF_GetLastError, [], :ulong @@ -463,6 +464,43 @@ class Pdfium end end + def self.open_io(io, password = nil) + io.binmode + + get_block = FFI::Function.new(:int, %i[pointer ulong pointer ulong]) do |_param, position, out, size| + io.seek(position) + + bytes = io.read(size).to_s + + out.put_bytes(0, bytes) + + bytes.bytesize == size ? 1 : 0 + end + + file_access = Pdfium::FPDF_FILEACCESS.new + file_access[:m_FileLen] = io.size + file_access[:m_GetBlock] = get_block + file_access[:m_Param] = FFI::Pointer::NULL + + doc_ptr = Pdfium.FPDF_LoadCustomDocument(file_access, password) + + if doc_ptr.null? + Pdfium.check_last_error('Failed to load document from IO') + + raise PdfiumError, 'Failed to load document from IO, pointer is NULL.' + end + + doc = new(doc_ptr, [file_access, get_block, io]) + + return doc unless block_given? + + begin + yield doc + ensure + doc.close + end + end + def closed? @closed end diff --git a/lib/templates/process_document.rb b/lib/templates/process_document.rb index 9db405f6..4e69a4c1 100644 --- a/lib/templates/process_document.rb +++ b/lib/templates/process_document.rb @@ -192,8 +192,8 @@ module Templates end end - def generate_pdf_preview_from_file(attachment, file_path, page_number) - doc = Pdfium::Document.open_file(file_path) + def generate_pdf_preview_from_io(attachment, io, page_number) + doc = Pdfium::Document.open_io(io) doc_page = doc.get_page(page_number) From f35510ffd664847d2f00146c24cb45b10e203908 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 8 Aug 2026 09:00:35 +0300 Subject: [PATCH 06/16] adjust with reason --- lib/submitters/submit_values.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb index 5484f238..6c83a1ef 100644 --- a/lib/submitters/submit_values.rb +++ b/lib/submitters/submit_values.rb @@ -114,7 +114,7 @@ module Submitters end reason_field = submitter.submission.template_fields.find do |e| - e['uuid'] == reason_field_uuid && e['submitter_uuid'] == submitter.uuid + e['uuid'] == reason_field_uuid end if reason_field From c59c47032150fd6b11fc40d488c98c9ce0d3d639 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 9 Aug 2026 16:01:12 +0300 Subject: [PATCH 07/16] sanitize search query --- app/controllers/submitters_autocomplete_controller.rb | 2 +- lib/submitters.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/submitters_autocomplete_controller.rb b/app/controllers/submitters_autocomplete_controller.rb index 22599e58..f93c8b96 100644 --- a/app/controllers/submitters_autocomplete_controller.rb +++ b/app/controllers/submitters_autocomplete_controller.rb @@ -36,7 +36,7 @@ class SubmittersAutocompleteController < ApplicationController else column = Submitter.arel_table[field.to_sym] - term = "#{params[:q].downcase}%" + term = "#{ActiveRecord::Base.sanitize_sql_like(params[:q].downcase)}%" submitters.where(column.matches(term)) end diff --git a/lib/submitters.rb b/lib/submitters.rb index 09852001..979ba6bc 100644 --- a/lib/submitters.rb +++ b/lib/submitters.rb @@ -93,7 +93,8 @@ module Submitters def plain_search(submitters, keyword) return submitters if keyword.blank? - term = "%#{keyword.downcase}%" + sanitized = ActiveRecord::Base.sanitize_sql_like(keyword.downcase) + term = "%#{sanitized}%" arel_table = Submitter.arel_table From ffb3247cdc39bd04ebbe69506f5d6b9e13805a61 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 9 Aug 2026 18:37:53 +0300 Subject: [PATCH 08/16] add authorize user --- app/controllers/users_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2d8f818f..0b611381 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -75,6 +75,8 @@ class UsersController < ApplicationController authorize!(:manage, account) @user.account = account + + authorize!(:create, @user) end if @user.update(attrs.except(*(current_user == @user ? %i[password otp_required_for_login role] : %i[password]))) From 2dd38260ba1e0ac37abc4300081df9038387eaae Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 9 Aug 2026 19:51:07 +0300 Subject: [PATCH 09/16] guard email 2fa send --- .../start_form_email_2fa_send_controller.rb | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/controllers/start_form_email_2fa_send_controller.rb b/app/controllers/start_form_email_2fa_send_controller.rb index 6359debd..eb20dfc7 100644 --- a/app/controllers/start_form_email_2fa_send_controller.rb +++ b/app/controllers/start_form_email_2fa_send_controller.rb @@ -6,9 +6,10 @@ class StartFormEmail2faSendController < ApplicationController skip_before_action :authenticate_user! skip_authorization_check - def create - @template = Template.find_by!(slug: params[:slug]) + before_action :load_template + before_action :authorize_start! + def create @submitter = @template.submissions.new(account_id: @template.account_id) .submitters.new(**submitter_params, account_id: @template.account_id) @@ -25,6 +26,23 @@ class StartFormEmail2faSendController < ApplicationController private + def load_template + @template = Template.find_by!(slug: params[:slug]) + end + + def authorize_start! + is_archived = @template.archived_at? || @template.account.archived_at? + + return redirect_to start_form_path(@template.slug) if is_archived + + return if (@template.shared_link? || (current_user && current_ability.can?(:read, @template))) && + @template.preferences['shared_link_2fa'] == true + + Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar) + + redirect_to start_form_path(@template.slug) + end + def submitter_params params.require(:submitter).permit(:name, :email, :phone) end From 09310f369060aa01c07b65c52320eb0474e7cd72 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 9 Aug 2026 19:53:19 +0300 Subject: [PATCH 10/16] guard save template message --- app/controllers/submissions_controller.rb | 2 +- app/views/submissions/_send_email_base.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 96ae4972..0f04c0df 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -40,7 +40,7 @@ class SubmissionsController < ApplicationController def create return redirect_to template_path(@template), alert: I18n.t('template_has_been_archived') if @template.archived_at? - save_template_message(@template, params) if params[:save_message] == '1' + save_template_message(@template, params) if params[:save_message] == '1' && can?(:update, @template) [params.delete(:subject), params.delete(:body)] if params[:is_custom_message] != '1' diff --git a/app/views/submissions/_send_email_base.html.erb b/app/views/submissions/_send_email_base.html.erb index 4e640d70..24d25e73 100644 --- a/app/views/submissions/_send_email_base.html.erb +++ b/app/views/submissions/_send_email_base.html.erb @@ -69,7 +69,7 @@ <%= f.label :message, t('body'), class: 'label' %> <% body_variables = AccountConfig::EMAIL_VARIABLES[AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY] %> <%= render 'personalization_settings/email_body_editor', name: f.field_name(:body), value: local_assigns[:submitter_email_message]&.normalized_body.presence || (is_edit_viewer ? view_template_body : nil) || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_body').presence || (is_edit_viewer ? view_config.value['body'] : default_body), variables: body_variables %> - <% if !local_assigns.fetch(:disable_save_as_default_template_option, false) && config.value['body_type'] != 'html' %> + <% if !local_assigns.fetch(:disable_save_as_default_template_option, false) && config.value['body_type'] != 'html' && template && can?(:update, template) %>