From 223baf2c4e8bcf793ddfcec7a6d860c3bd3fff87 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 23 Jun 2026 17:09:37 +0530 Subject: [PATCH 01/35] feat(entries): pass per-log retry metadata through the logs REST response The entry activity-log REST endpoint whitelisted only id/title/timestamp/messages. Pass through a sanitized 'retry' => { type, id } field when present on a log row, so the Pro entries UI can render a per-row Retry button for a specific failed webhook / native integration. No behavior change when the key is absent. --- inc/rest-api.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/inc/rest-api.php b/inc/rest-api.php index 6d12f4a37..d9c000277 100644 --- a/inc/rest-api.php +++ b/inc/rest-api.php @@ -953,12 +953,24 @@ public function get_entry_logs( $request ) { if ( ! is_array( $log ) ) { continue; } - $formatted_logs[] = [ + $formatted_log = [ 'id' => $offset + $index, // Use offset-based ID for consistent deletion. 'title' => $log['title'] ?? '', 'timestamp' => $log['timestamp'] ?? time(), 'messages' => $log['messages'] ?? [], ]; + + // Pass through (sanitized) retry metadata so an integration/webhook log row can + // offer a "Retry" action for that specific failed trigger. Set by the Pro + // webhook / native-integration dispatchers as [ 'type' => webhook|native, 'id' => ]. + if ( isset( $log['retry'] ) && is_array( $log['retry'] ) && ! empty( $log['retry']['type'] ) && isset( $log['retry']['id'] ) ) { + $formatted_log['retry'] = [ + 'type' => sanitize_text_field( Helper::get_string_value( $log['retry']['type'] ) ), + 'id' => sanitize_text_field( Helper::get_string_value( $log['retry']['id'] ) ), + ]; + } + + $formatted_logs[] = $formatted_log; } $response_data = [ From d48fe210a9833970be20c8d9b23c47385f9867e7 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Thu, 25 Jun 2026 19:22:41 +0530 Subject: [PATCH 02/35] fix(editor): import createRoot from @wordpress/element instead of react-dom (React 19 readiness) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three single-form-settings modules imported `createRoot` directly from `react-dom/client`. While the build externalizes it to the WP-provided ReactDOM global (no react-dom runtime is bundled), the direct react-dom/client import is the fragile, non-preferred path under React 19 — Gutenberg/WP will move React + react-dom to v19, and code should always go through the WP-supplied runtime via @wordpress/element. Switch the three call sites to `import { createRoot } from '@wordpress/element'` (drop-in; identical createRoot().render() API, resolves to wp.element at runtime) and declare @wordpress/element in package.json. - src/admin/single-form-settings/InstantForm.js - src/admin/single-form-settings/components/SRFMEditorHeader.js - src/admin/single-form-settings/components/useSubmitButton.js No bundled react-dom runtime before or after (verified). SureForms Pro is unaffected — it already mounts exclusively via @wordpress/element. --- package-lock.json | 2 ++ package.json | 1 + src/admin/single-form-settings/InstantForm.js | 3 +-- src/admin/single-form-settings/components/SRFMEditorHeader.js | 3 +-- src/admin/single-form-settings/components/useSubmitButton.js | 3 +-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a5a56d96..566afa018 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@tanstack/react-query": "^5.90.5", "@tomickigrzegorz/react-circular-progress-bar": "^1.1.2", "@wordpress/api-fetch": "^6.42.0", + "@wordpress/element": "^5.35.0", "@wordpress/icons": "^9.18.0", "autoprefixer": "^10.4.16", "classnames": "^2.3.2", @@ -5811,6 +5812,7 @@ "version": "5.35.0", "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.35.0.tgz", "integrity": "sha512-puswpGcIdS+0A2g28uHriMkZqqRCmzFczue5Tk99VNtzBdehyk7Ae+DZ4xw5yT6GqYai8NTqv6MRwCB78uh5Mw==", + "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", "@types/react": "^18.2.79", diff --git a/package.json b/package.json index 7dfe21803..f03e6fd69 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "@tanstack/react-query": "^5.90.5", "@tomickigrzegorz/react-circular-progress-bar": "^1.1.2", "@wordpress/api-fetch": "^6.42.0", + "@wordpress/element": "^5.35.0", "@wordpress/icons": "^9.18.0", "autoprefixer": "^10.4.16", "classnames": "^2.3.2", diff --git a/src/admin/single-form-settings/InstantForm.js b/src/admin/single-form-settings/InstantForm.js index 2e034ad46..b2df8099b 100644 --- a/src/admin/single-form-settings/InstantForm.js +++ b/src/admin/single-form-settings/InstantForm.js @@ -7,11 +7,10 @@ import { ExternalLink, FormToggle, Popover } from '@wordpress/components'; import { useCopyToClipboard } from '@wordpress/compose'; import { select, useDispatch, useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -import { useId, useState, useEffect } from '@wordpress/element'; +import { useId, useState, useEffect, createRoot } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { cleanForSlug } from '@wordpress/url'; import parse from 'html-react-parser'; -import { createRoot } from 'react-dom/client'; import ConversationalFormSettingsPreview from './components/ConversationalFormSettingsPreview'; import { applyFilters } from '@wordpress/hooks'; import { getInstantFormAdditionalSettings } from '@Components/hooks'; diff --git a/src/admin/single-form-settings/components/SRFMEditorHeader.js b/src/admin/single-form-settings/components/SRFMEditorHeader.js index 2edbc7da0..ede515545 100644 --- a/src/admin/single-form-settings/components/SRFMEditorHeader.js +++ b/src/admin/single-form-settings/components/SRFMEditorHeader.js @@ -2,8 +2,7 @@ import { __ } from '@wordpress/i18n'; import { useEntityProp } from '@wordpress/core-data'; import { TextControl } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; -import { createRoot } from 'react-dom/client'; -import { useEffect, useRef } from '@wordpress/element'; +import { useEffect, useRef, createRoot } from '@wordpress/element'; const SRFMEditorHeader = () => { const postId = useSelect( ( select ) => { diff --git a/src/admin/single-form-settings/components/useSubmitButton.js b/src/admin/single-form-settings/components/useSubmitButton.js index 9fd4a215c..5eb3c843b 100644 --- a/src/admin/single-form-settings/components/useSubmitButton.js +++ b/src/admin/single-form-settings/components/useSubmitButton.js @@ -1,7 +1,6 @@ -import { useEffect, useRef } from '@wordpress/element'; +import { useEffect, useRef, createRoot } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -import { createRoot } from 'react-dom/client'; import { RichText } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { BlockInserterWrapper } from '../Inserter.js'; From 2d30f5118a3f07d0fb67d7e6e3453690255902b2 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Thu, 25 Jun 2026 20:08:01 +0530 Subject: [PATCH 03/35] fix(quick-action-sidebar): import createRoot from @wordpress/element Fourth and final react-dom/client import in the plugin (this one lives under modules/, outside the earlier src/ sweep). Same React-19-readiness change: route createRoot through @wordpress/element (wp.element) instead of react-dom/client, so it always uses the WP-provided runtime. The quickActionSidebar bundle now externalizes wp-element instead of react-dom, with no react-dom runtime bundled (verified). --- modules/quick-action-sidebar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/quick-action-sidebar/index.js b/modules/quick-action-sidebar/index.js index 338c5c708..0d11e559e 100644 --- a/modules/quick-action-sidebar/index.js +++ b/modules/quick-action-sidebar/index.js @@ -1,7 +1,7 @@ /** * The Quick Access React App for Spectra Enhanced Editor. */ -import { createRoot } from 'react-dom/client'; +import { createRoot } from '@wordpress/element'; import Sidebar from './components/Sidebar'; // Toggles the sidebar based on the url parameters. From 9f0c1efe85093ecee391a240cf85396427411968 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Fri, 26 Jun 2026 08:32:28 +0530 Subject: [PATCH 04/35] chore: expand sync-public strip surface (nested CLAUDE.md, docs/, TODO.md) The strip loop matched only the root CLAUDE.md, so nested CLAUDE.md files (inc/abilities/, tests/play/specs/) and the docs/ tree leaked into the public mirror PR diff. Strip all CLAUDE.md at any depth via git ls-files, add docs/ and tests/play/specs/TODO.md to the list, and add a markdown sanity check (README.md is the only expected public doc). --- .claude/commands/sureforms:sync-public.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.claude/commands/sureforms:sync-public.md b/.claude/commands/sureforms:sync-public.md index a75cdf20b..bb9f103eb 100644 --- a/.claude/commands/sureforms:sync-public.md +++ b/.claude/commands/sureforms:sync-public.md @@ -17,11 +17,13 @@ Edit this list when the leak surface changes — there is no other place to upda .claude .scripts/git-hooks internal-docs -CLAUDE.md +docs +CLAUDE.md # ALL CLAUDE.md at ANY depth (root + nested, e.g. inc/abilities/, tests/play/specs/) — stripped via `git ls-files '*CLAUDE.md' 'CLAUDE.md'`, NOT just the root path ARCHITECTURE.md COMPREHENSIVE_ANALYSIS.md PRODUCT_ANALYSIS.md TECHNICAL_OVERVIEW.md +tests/play/specs/TODO.md .github/workflows/push-to-deploy.yml .github/workflows/push-asset-readme-update.yml .github/workflows/release-tag-draft.yml @@ -32,7 +34,9 @@ bin/checkout-and-build bin/i18n.sh ``` -These paths must NOT appear on the public mirror. They legitimately exist on private `master` and stay there (internal release CI, AI tooling, internal team wiki). +These paths must NOT appear on the public mirror. They legitimately exist on private `master` and stay there (internal release CI, AI tooling, internal team wiki, internal dev docs under `docs/`). + +> **README.md is the ONLY markdown doc that should appear in the public PR diff.** After stripping, sanity-check with `git ls-files '*.md'` — anything beyond `README.md`, third-party `inc/lib/**` readmes, `modules/gutenberg/readme.md`, and `tests/play/README.md` is a likely leak. (A prior sync leaked `inc/abilities/CLAUDE.md`, `tests/play/specs/CLAUDE.md`, and the whole `docs/` tree because the strip matched only the root `CLAUDE.md`.) ## Preconditions @@ -119,11 +123,12 @@ for p in \ .claude \ .scripts/git-hooks \ internal-docs \ - CLAUDE.md \ + docs \ ARCHITECTURE.md \ COMPREHENSIVE_ANALYSIS.md \ PRODUCT_ANALYSIS.md \ TECHNICAL_OVERVIEW.md \ + tests/play/specs/TODO.md \ .github/workflows/push-to-deploy.yml \ .github/workflows/push-asset-readme-update.yml \ .github/workflows/release-tag-draft.yml \ @@ -138,10 +143,19 @@ for p in \ fi done +# Strip ALL CLAUDE.md at any depth (root + nested) — NOT just the root path +git ls-files '*CLAUDE.md' 'CLAUDE.md' | while read -r f; do + git rm --quiet "$f" +done + # Remove now-empty .scripts directory if applicable if [ -d .scripts ] && [ -z "$(ls -A .scripts 2>/dev/null)" ]; then rmdir .scripts fi + +# Sanity check: README.md must be the ONLY non-third-party markdown doc left +git ls-files '*.md' | grep -vE '^(README\.md|inc/lib/|modules/gutenberg/readme\.md|tests/play/README\.md|inc/abilities/ABILITIES\.md)$' \ + && echo "WARNING: unexpected .md files remain — review before continuing" || echo "OK: only README.md + known readmes" ``` ### Step 6: Commit the strip, then bulk re-sign ALL commits From 1e767d222dd432b0f26771a83250bc5ac13d4c4b Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Fri, 26 Jun 2026 09:01:28 +0530 Subject: [PATCH 05/35] feat(email): enforce RFC 5321 length limits with filter (#2904) Cap the Email field's local part at 64 and domain at 255 chars (split on the last @), overridable via the `srfm_email_field_char_limits` filter. Prevents DB bloat from oversized email submissions while staying customizable. - Server-side (authoritative): validate_form_data() in inc/field-validation.php, mirroring the textarea min-length block. Only the main value is submitted (the confirm input has no `name`), so the server validates that; the confirm is enforced client-side and must match the main value. - Client-side (UX): assets/js/unminified/validation.js email block validates the main and confirm inputs pre-submit, showing the same message. - New translatable message `srfm_email_char_limit` registered in Translatable::dynamic_messages()/dynamic_messages_source() and added to the global-settings allowlists so it's editable from the admin. Closes #2904 --- assets/js/unminified/validation.js | 36 +++++++++++++++++++ .../settings/update-global-settings.php | 1 + inc/field-validation.php | 25 +++++++++++++ inc/global-settings/global-settings.php | 1 + inc/translatable.php | 3 ++ 5 files changed, 66 insertions(+) diff --git a/assets/js/unminified/validation.js b/assets/js/unminified/validation.js index 1bce70b9d..8c6cdf79a 100644 --- a/assets/js/unminified/validation.js +++ b/assets/js/unminified/validation.js @@ -321,6 +321,30 @@ export async function fieldValidation( validateResult = true; } + // RFC 5321 length limits — local part <= 64, domain <= 255 (split on the + // last @). Mirrors the server-side check (which is authoritative, so a + // `srfm_email_field_char_limits` filter override is still enforced there). + const exceedsEmailLength = ( val ) => { + if ( typeof val !== 'string' || ! val.includes( '@' ) ) { + return false; + } + const at = val.lastIndexOf( '@' ); + return ( + val.slice( 0, at ).length > 64 || + val.slice( at + 1 ).length > 255 + ); + }; + + if ( inputValue && exceedsEmailLength( inputValue ) ) { + if ( errorMessage ) { + errorMessage.textContent = + window?.srfm_submit?.messages?.srfm_email_char_limit; + } + window?.srfm?.toggleErrorState( parent, true ); + setFirstErrorInput( inputField, parent ); + validateResult = true; + } + if ( confirmParent ) { const confirmInput = confirmParent.querySelector( '.srfm-input-email-confirm' @@ -358,6 +382,18 @@ export async function fieldValidation( window?.srfm?.toggleErrorState( confirmParent, false ); } + // Length check on the confirm value too (it must match the main + // value, but flag it directly so the error surfaces on this input). + if ( confirmValue && exceedsEmailLength( confirmValue ) ) { + if ( confirmError ) { + confirmError.textContent = + window?.srfm_submit?.messages?.srfm_email_char_limit; + } + window?.srfm?.toggleErrorState( confirmParent, true ); + setFirstErrorInput( confirmInput, confirmParent ); + validateResult = true; + } + // remove the error message on input of the email confirm field confirmInput.addEventListener( 'input', () => { window?.srfm?.toggleErrorState( confirmParent, false ); diff --git a/inc/abilities/settings/update-global-settings.php b/inc/abilities/settings/update-global-settings.php index a473db6ba..9308ff4e5 100644 --- a/inc/abilities/settings/update-global-settings.php +++ b/inc/abilities/settings/update-global-settings.php @@ -68,6 +68,7 @@ class Update_Global_Settings extends Abstract_Ability { 'srfm_confirm_email_same', 'srfm_valid_email', 'srfm_textarea_min_chars', + 'srfm_email_char_limit', 'srfm_input_min_value', 'srfm_input_max_value', 'srfm_dropdown_min_selections', diff --git a/inc/field-validation.php b/inc/field-validation.php index e02c0e9d2..9c7461cfc 100644 --- a/inc/field-validation.php +++ b/inc/field-validation.php @@ -295,6 +295,31 @@ public static function validate_form_data( $form_data, $current_form_id ) { $not_valid_fields[ $key ] = sprintf( $min_chars_message, $min_length ); } } + + // Email field RFC 5321 length limits (local part / domain), overridable via filter. + // Only the main email value is in form data (the confirm input has no `name`), + // so the server validates that value; the client mirrors this for both inputs. + // Split on the LAST @ per RFC 5321 so the local part may contain a quoted @. + $at_pos = is_string( $value ) && '' !== $value ? strrpos( $value, '@' ) : false; + if ( 'srfm-email' === $get_field_name && is_string( $value ) && false !== $at_pos ) { + $email_limits = apply_filters( + 'srfm_email_field_char_limits', + [ + 'local' => 64, + 'domain' => 255, + ] + ); + $local_max = isset( $email_limits['local'] ) ? absint( $email_limits['local'] ) : 64; + $domain_max = isset( $email_limits['domain'] ) ? absint( $email_limits['domain'] ) : 255; + $local_len = mb_strlen( substr( $value, 0, $at_pos ) ); + $domain_len = mb_strlen( substr( $value, $at_pos + 1 ) ); + if ( ( $local_max > 0 && $local_len > $local_max ) || ( $domain_max > 0 && $domain_len > $domain_max ) ) { + $dynamic_messages = Translatable::dynamic_validation_messages(); + $not_valid_fields[ $key ] = isset( $dynamic_messages['srfm_email_char_limit'] ) && is_string( $dynamic_messages['srfm_email_char_limit'] ) && '' !== $dynamic_messages['srfm_email_char_limit'] + ? $dynamic_messages['srfm_email_char_limit'] + : __( 'Please enter a shorter email address.', 'sureforms' ); + } + } } // Return the array of invalid fields and their error messages. diff --git a/inc/global-settings/global-settings.php b/inc/global-settings/global-settings.php index 6a916ee71..2e4db5352 100644 --- a/inc/global-settings/global-settings.php +++ b/inc/global-settings/global-settings.php @@ -225,6 +225,7 @@ public static function srfm_save_general_settings_dynamic_opt( $setting_options 'srfm_confirm_email_same', 'srfm_valid_email', 'srfm_textarea_min_chars', + 'srfm_email_char_limit', 'srfm_input_min_value', 'srfm_input_max_value', 'srfm_dropdown_min_selections', diff --git a/inc/translatable.php b/inc/translatable.php index ce83be19d..7b829410c 100644 --- a/inc/translatable.php +++ b/inc/translatable.php @@ -93,6 +93,8 @@ public static function dynamic_messages() { /* translators: %s represents the minimum number of characters required */ 'srfm_textarea_min_chars' => __( 'Please enter at least %s characters.', 'sureforms' ), + 'srfm_email_char_limit' => __( 'Please enter a shorter email address.', 'sureforms' ), + /* translators: %s represents the minimum acceptable value */ 'srfm_input_min_value' => __( 'Minimum value is %s.', 'sureforms' ), @@ -132,6 +134,7 @@ public static function dynamic_messages_source() { 'srfm_confirm_email_same' => 'Confirmation email does not match.', 'srfm_valid_email' => 'Enter a valid email address.', 'srfm_textarea_min_chars' => 'Please enter at least %s characters.', + 'srfm_email_char_limit' => 'Please enter a shorter email address.', 'srfm_input_min_value' => 'Minimum value is %s.', 'srfm_input_max_value' => 'Maximum value is %s.', 'srfm_dropdown_min_selections' => 'Select at least %s options.', From ffa3ca06f05e23142d47edac20bd4e1ab076085c Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Fri, 26 Jun 2026 10:21:13 +0530 Subject: [PATCH 06/35] fix(email): reveal the char-limit error message (was border-only) The email field's `.srfm-error-message` is hidden by default and only shown via an explicit inline display:block (as the format-error and confirm-mismatch handlers already do). The char-limit error set the message text + red border but not display:block, so a too-long email showed the border with no message. - Submit path: set `display = 'block'` on the main and confirm error elements when flagging an over-length email. - Blur handler: also flag a valid-format-but-too-long value live (mirroring the format error), showing the length-specific message. --- assets/js/unminified/validation.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/assets/js/unminified/validation.js b/assets/js/unminified/validation.js index 8c6cdf79a..c7f4c2b00 100644 --- a/assets/js/unminified/validation.js +++ b/assets/js/unminified/validation.js @@ -339,6 +339,11 @@ export async function fieldValidation( if ( errorMessage ) { errorMessage.textContent = window?.srfm_submit?.messages?.srfm_email_char_limit; + // The email field's error message is hidden by default and only + // revealed via an explicit display:block (same as the format-error + // and confirm-mismatch handlers) — without this the red border + // shows but the message stays hidden. + errorMessage.style.display = 'block'; } window?.srfm?.toggleErrorState( parent, true ); setFirstErrorInput( inputField, parent ); @@ -388,6 +393,7 @@ export async function fieldValidation( if ( confirmError ) { confirmError.textContent = window?.srfm_submit?.messages?.srfm_email_char_limit; + confirmError.style.display = 'block'; } window?.srfm?.toggleErrorState( confirmParent, true ); setFirstErrorInput( confirmInput, confirmParent ); @@ -1125,14 +1131,25 @@ function addEmailBlurListener( areaInput, blockClass ) { confirmErrorContainer.style.display = 'none'; } + // RFC 5321 length limits (local <= 64, domain <= 255, split on the last @). + // A too-long address can still be a valid format, so flag it here too and + // show the length-specific message; otherwise fall back to the format error. + const emailValueAt = emailField.value.lastIndexOf( '@' ); + const exceedsEmailLength = + emailValueAt !== -1 && + ( emailField.value.slice( 0, emailValueAt ).length > 64 || + emailField.value.slice( emailValueAt + 1 ).length > 255 ); + // Handle general email validation - if ( '' !== emailField?.value && ! isValidEmail ) { + if ( '' !== emailField?.value && ( ! isValidEmail || exceedsEmailLength ) ) { inputBlock.parentElement.classList.add( 'srfm-valid-email-error' ); errorContainer.style.display = 'block'; errorContainer.innerHTML = - window?.srfm_submit?.messages?.srfm_valid_email; + exceedsEmailLength && isValidEmail + ? window?.srfm_submit?.messages?.srfm_email_char_limit + : window?.srfm_submit?.messages?.srfm_valid_email; errorContainer.id = errorContainer.getAttribute( 'data-srfm-id' ); } else { From 95af6e6b2efcd0e6b8f7a2004731fd5393d37268 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Fri, 26 Jun 2026 10:46:05 +0530 Subject: [PATCH 07/35] refactor(email): split the length error into part-specific messages Replace the single generic "Please enter a shorter email address." with two parameterized messages, so the user knows which part is too long and the cap: - srfm_email_local_max_length: "The part before @ may not exceed %s characters." - srfm_email_domain_max_length: "The part after @ may not exceed %s characters." Server (field-validation.php) picks the local or domain message and sprintf()s the relevant limit; client (validation.js) does the same via srfmSprintfString in the submit + blur paths. Registered in both translatable arrays and both global-settings allowlists (replacing srfm_email_char_limit). --- assets/js/unminified/validation.js | 71 ++++++++++++++----- .../settings/update-global-settings.php | 3 +- inc/field-validation.php | 19 +++-- inc/global-settings/global-settings.php | 3 +- inc/translatable.php | 9 ++- 5 files changed, 77 insertions(+), 28 deletions(-) diff --git a/assets/js/unminified/validation.js b/assets/js/unminified/validation.js index c7f4c2b00..1c3650f36 100644 --- a/assets/js/unminified/validation.js +++ b/assets/js/unminified/validation.js @@ -324,21 +324,35 @@ export async function fieldValidation( // RFC 5321 length limits — local part <= 64, domain <= 255 (split on the // last @). Mirrors the server-side check (which is authoritative, so a // `srfm_email_field_char_limits` filter override is still enforced there). - const exceedsEmailLength = ( val ) => { + // Returns the part-specific, value-filled message, or '' when within limits. + const emailLengthError = ( val ) => { if ( typeof val !== 'string' || ! val.includes( '@' ) ) { - return false; + return ''; } const at = val.lastIndexOf( '@' ); - return ( - val.slice( 0, at ).length > 64 || - val.slice( at + 1 ).length > 255 - ); + if ( val.slice( 0, at ).length > 64 ) { + return window?.srfm?.srfmSprintfString( + window?.srfm_submit?.messages + ?.srfm_email_local_max_length, + 64 + ); + } + if ( val.slice( at + 1 ).length > 255 ) { + return window?.srfm?.srfmSprintfString( + window?.srfm_submit?.messages + ?.srfm_email_domain_max_length, + 255 + ); + } + return ''; }; - if ( inputValue && exceedsEmailLength( inputValue ) ) { + const mainLengthError = inputValue + ? emailLengthError( inputValue ) + : ''; + if ( mainLengthError ) { if ( errorMessage ) { - errorMessage.textContent = - window?.srfm_submit?.messages?.srfm_email_char_limit; + errorMessage.textContent = mainLengthError; // The email field's error message is hidden by default and only // revealed via an explicit display:block (same as the format-error // and confirm-mismatch handlers) — without this the red border @@ -389,10 +403,12 @@ export async function fieldValidation( // Length check on the confirm value too (it must match the main // value, but flag it directly so the error surfaces on this input). - if ( confirmValue && exceedsEmailLength( confirmValue ) ) { + const confirmLengthError = confirmValue + ? emailLengthError( confirmValue ) + : ''; + if ( confirmLengthError ) { if ( confirmError ) { - confirmError.textContent = - window?.srfm_submit?.messages?.srfm_email_char_limit; + confirmError.textContent = confirmLengthError; confirmError.style.display = 'block'; } window?.srfm?.toggleErrorState( confirmParent, true ); @@ -1135,20 +1151,37 @@ function addEmailBlurListener( areaInput, blockClass ) { // A too-long address can still be a valid format, so flag it here too and // show the length-specific message; otherwise fall back to the format error. const emailValueAt = emailField.value.lastIndexOf( '@' ); - const exceedsEmailLength = - emailValueAt !== -1 && - ( emailField.value.slice( 0, emailValueAt ).length > 64 || - emailField.value.slice( emailValueAt + 1 ).length > 255 ); + let lengthErrorMessage = ''; + if ( emailValueAt !== -1 ) { + if ( emailField.value.slice( 0, emailValueAt ).length > 64 ) { + lengthErrorMessage = window?.srfm?.srfmSprintfString( + window?.srfm_submit?.messages + ?.srfm_email_local_max_length, + 64 + ); + } else if ( + emailField.value.slice( emailValueAt + 1 ).length > 255 + ) { + lengthErrorMessage = window?.srfm?.srfmSprintfString( + window?.srfm_submit?.messages + ?.srfm_email_domain_max_length, + 255 + ); + } + } // Handle general email validation - if ( '' !== emailField?.value && ( ! isValidEmail || exceedsEmailLength ) ) { + if ( + '' !== emailField?.value && + ( ! isValidEmail || lengthErrorMessage ) + ) { inputBlock.parentElement.classList.add( 'srfm-valid-email-error' ); errorContainer.style.display = 'block'; errorContainer.innerHTML = - exceedsEmailLength && isValidEmail - ? window?.srfm_submit?.messages?.srfm_email_char_limit + lengthErrorMessage && isValidEmail + ? lengthErrorMessage : window?.srfm_submit?.messages?.srfm_valid_email; errorContainer.id = errorContainer.getAttribute( 'data-srfm-id' ); diff --git a/inc/abilities/settings/update-global-settings.php b/inc/abilities/settings/update-global-settings.php index 9308ff4e5..3d88a2af3 100644 --- a/inc/abilities/settings/update-global-settings.php +++ b/inc/abilities/settings/update-global-settings.php @@ -68,7 +68,8 @@ class Update_Global_Settings extends Abstract_Ability { 'srfm_confirm_email_same', 'srfm_valid_email', 'srfm_textarea_min_chars', - 'srfm_email_char_limit', + 'srfm_email_local_max_length', + 'srfm_email_domain_max_length', 'srfm_input_min_value', 'srfm_input_max_value', 'srfm_dropdown_min_selections', diff --git a/inc/field-validation.php b/inc/field-validation.php index 9c7461cfc..9620790d4 100644 --- a/inc/field-validation.php +++ b/inc/field-validation.php @@ -313,11 +313,20 @@ public static function validate_form_data( $form_data, $current_form_id ) { $domain_max = isset( $email_limits['domain'] ) ? absint( $email_limits['domain'] ) : 255; $local_len = mb_strlen( substr( $value, 0, $at_pos ) ); $domain_len = mb_strlen( substr( $value, $at_pos + 1 ) ); - if ( ( $local_max > 0 && $local_len > $local_max ) || ( $domain_max > 0 && $domain_len > $domain_max ) ) { - $dynamic_messages = Translatable::dynamic_validation_messages(); - $not_valid_fields[ $key ] = isset( $dynamic_messages['srfm_email_char_limit'] ) && is_string( $dynamic_messages['srfm_email_char_limit'] ) && '' !== $dynamic_messages['srfm_email_char_limit'] - ? $dynamic_messages['srfm_email_char_limit'] - : __( 'Please enter a shorter email address.', 'sureforms' ); + + $dynamic_messages = Translatable::dynamic_validation_messages(); + if ( $local_max > 0 && $local_len > $local_max ) { + $local_message = isset( $dynamic_messages['srfm_email_local_max_length'] ) && is_string( $dynamic_messages['srfm_email_local_max_length'] ) && '' !== $dynamic_messages['srfm_email_local_max_length'] + ? $dynamic_messages['srfm_email_local_max_length'] + /* translators: %s: maximum characters allowed before the @ symbol. */ + : __( 'The part before @ may not exceed %s characters.', 'sureforms' ); + $not_valid_fields[ $key ] = sprintf( $local_message, $local_max ); + } elseif ( $domain_max > 0 && $domain_len > $domain_max ) { + $domain_message = isset( $dynamic_messages['srfm_email_domain_max_length'] ) && is_string( $dynamic_messages['srfm_email_domain_max_length'] ) && '' !== $dynamic_messages['srfm_email_domain_max_length'] + ? $dynamic_messages['srfm_email_domain_max_length'] + /* translators: %s: maximum characters allowed after the @ symbol. */ + : __( 'The part after @ may not exceed %s characters.', 'sureforms' ); + $not_valid_fields[ $key ] = sprintf( $domain_message, $domain_max ); } } } diff --git a/inc/global-settings/global-settings.php b/inc/global-settings/global-settings.php index 2e4db5352..14856005a 100644 --- a/inc/global-settings/global-settings.php +++ b/inc/global-settings/global-settings.php @@ -225,7 +225,8 @@ public static function srfm_save_general_settings_dynamic_opt( $setting_options 'srfm_confirm_email_same', 'srfm_valid_email', 'srfm_textarea_min_chars', - 'srfm_email_char_limit', + 'srfm_email_local_max_length', + 'srfm_email_domain_max_length', 'srfm_input_min_value', 'srfm_input_max_value', 'srfm_dropdown_min_selections', diff --git a/inc/translatable.php b/inc/translatable.php index 7b829410c..6a59be242 100644 --- a/inc/translatable.php +++ b/inc/translatable.php @@ -93,7 +93,11 @@ public static function dynamic_messages() { /* translators: %s represents the minimum number of characters required */ 'srfm_textarea_min_chars' => __( 'Please enter at least %s characters.', 'sureforms' ), - 'srfm_email_char_limit' => __( 'Please enter a shorter email address.', 'sureforms' ), + /* translators: %s: maximum characters allowed before the @ symbol. */ + 'srfm_email_local_max_length' => __( 'The part before @ may not exceed %s characters.', 'sureforms' ), + + /* translators: %s: maximum characters allowed after the @ symbol. */ + 'srfm_email_domain_max_length' => __( 'The part after @ may not exceed %s characters.', 'sureforms' ), /* translators: %s represents the minimum acceptable value */ 'srfm_input_min_value' => __( 'Minimum value is %s.', 'sureforms' ), @@ -134,7 +138,8 @@ public static function dynamic_messages_source() { 'srfm_confirm_email_same' => 'Confirmation email does not match.', 'srfm_valid_email' => 'Enter a valid email address.', 'srfm_textarea_min_chars' => 'Please enter at least %s characters.', - 'srfm_email_char_limit' => 'Please enter a shorter email address.', + 'srfm_email_local_max_length' => 'The part before @ may not exceed %s characters.', + 'srfm_email_domain_max_length' => 'The part after @ may not exceed %s characters.', 'srfm_input_min_value' => 'Minimum value is %s.', 'srfm_input_max_value' => 'Maximum value is %s.', 'srfm_dropdown_min_selections' => 'Select at least %s options.', From 6dbce65b359710541714f50725bffb496ca567f3 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Fri, 26 Jun 2026 11:19:32 +0530 Subject: [PATCH 08/35] chore: make sync-public sanity check fail-closed; address review Addresses Om's (osk02) review on #2910: - Step 5 markdown sanity check now aborts the sync (exit 1) instead of only warning, so a detected leak halts the publish. Runs in the temp worktree before any push, so nothing internal reaches the mirror. - Fix the inc/lib/ allowlist branch ('inc/lib/.*' not 'inc/lib/') so the third-party readmes don't trip the now-enforcing check on every sync. - Strip nested CLAUDE.md without a pipe-to-while subshell so a failing git rm surfaces instead of being swallowed. - Drop the redundant 'CLAUDE.md' literal ('*CLAUDE.md' matches root too). --- .claude/commands/sureforms:sync-public.md | 28 ++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/.claude/commands/sureforms:sync-public.md b/.claude/commands/sureforms:sync-public.md index bb9f103eb..08f2bba4c 100644 --- a/.claude/commands/sureforms:sync-public.md +++ b/.claude/commands/sureforms:sync-public.md @@ -18,7 +18,7 @@ Edit this list when the leak surface changes — there is no other place to upda .scripts/git-hooks internal-docs docs -CLAUDE.md # ALL CLAUDE.md at ANY depth (root + nested, e.g. inc/abilities/, tests/play/specs/) — stripped via `git ls-files '*CLAUDE.md' 'CLAUDE.md'`, NOT just the root path +CLAUDE.md # ALL CLAUDE.md at ANY depth (root + nested, e.g. inc/abilities/, tests/play/specs/) — stripped via `git ls-files '*CLAUDE.md'` (matches the root file too), NOT just the root path ARCHITECTURE.md COMPREHENSIVE_ANALYSIS.md PRODUCT_ANALYSIS.md @@ -143,21 +143,33 @@ for p in \ fi done -# Strip ALL CLAUDE.md at any depth (root + nested) — NOT just the root path -git ls-files '*CLAUDE.md' 'CLAUDE.md' | while read -r f; do - git rm --quiet "$f" -done +# Strip ALL CLAUDE.md at any depth (root + nested) — NOT just the root path. +# No pipe-to-while: a subshell would swallow a failing `git rm`. These paths never +# contain spaces, so word-splitting the list is safe. +CLAUDE_FILES=$(git ls-files '*CLAUDE.md') +if [ -n "$CLAUDE_FILES" ]; then + git rm --quiet $CLAUDE_FILES +fi # Remove now-empty .scripts directory if applicable if [ -d .scripts ] && [ -z "$(ls -A .scripts 2>/dev/null)" ]; then rmdir .scripts fi -# Sanity check: README.md must be the ONLY non-third-party markdown doc left -git ls-files '*.md' | grep -vE '^(README\.md|inc/lib/|modules/gutenberg/readme\.md|tests/play/README\.md|inc/abilities/ABILITIES\.md)$' \ - && echo "WARNING: unexpected .md files remain — review before continuing" || echo "OK: only README.md + known readmes" +# Sanity check (FAIL-CLOSED): README.md must be the ONLY non-third-party markdown +# doc left. This control already leaked once — a detected anomaly must HALT the +# sync, not just warn. The check runs in the temp worktree before any push, so +# aborting here means nothing internal reaches the mirror. +LEAKS=$(git ls-files '*.md' | grep -vE '^(README\.md|inc/lib/.*|modules/gutenberg/readme\.md|tests/play/README\.md|inc/abilities/ABILITIES\.md)$') || true +if [ -n "$LEAKS" ]; then + printf 'ABORT: unexpected markdown would be published:\n%s\n' "$LEAKS" + exit 1 +fi +echo "OK: only README.md + known readmes" ``` +> If the sanity check aborts (`exit 1`), treat it as a failed step: jump to **Error Recovery** to tear down the temp worktree and restore the developer's branch/stash before investigating which path needs adding to the **Stripped paths** list. + ### Step 6: Commit the strip, then bulk re-sign ALL commits First commit the strip (committer is the running user — must be the verified email, see ruleset constraints): From e1ff28ef8675a532641bd1b4812b854b03ea0ff8 Mon Sep 17 00:00:00 2001 From: Avinash Kumar Sharma Date: Fri, 26 Jun 2026 15:43:29 +0530 Subject: [PATCH 09/35] fix: dispatch srfm_form_reset event and reset Dropdown on form reset The native form.reset() only resets standard HTML elements. Custom JS-controlled fields (TomSelect dropdown, date/time pickers, signature pads, sliders, file upload, rating) were left in their previous state after a "Reset Form" submission action. - Dispatch a new `srfm_form_reset` CustomEvent (with `form` in detail) immediately after `form.reset()` in `showSuccessMessage()`. - Add a `srfm_form_reset` listener in dropdown.js that calls `tomInputInstance.clear()` and clears the companion hidden input for every TomSelect dropdown inside the submitted form. Pro fields (Date, Time, Slider, Upload, Rating, Signature) will handle the same event in sureforms-pro. Fixes #2530 Co-Authored-By: Claude Sonnet 4.6 --- assets/js/unminified/blocks/dropdown.js | 25 +++++++++++++++++++++++++ assets/js/unminified/form-submit.js | 8 ++++++++ 2 files changed, 33 insertions(+) diff --git a/assets/js/unminified/blocks/dropdown.js b/assets/js/unminified/blocks/dropdown.js index 0f7c00731..3a9714a80 100644 --- a/assets/js/unminified/blocks/dropdown.js +++ b/assets/js/unminified/blocks/dropdown.js @@ -437,3 +437,28 @@ document.addEventListener( 'srfm_form_before_submission', ( e ) => { // Make dropdown initialization function available globally for repeater fields window.srfmInitializeDropdownField = initializeDropdown; window.srfmDestroyDropdownField = destroyTomSelect; + +// Reset TomSelect dropdowns when the form is reset. +document.addEventListener( 'srfm_form_reset', ( e ) => { + const form = e.detail?.form; + if ( ! form ) { + return; + } + + form.querySelectorAll( '.srfm-dropdown-common' ).forEach( ( dropdown ) => { + const inputName = dropdown.getAttribute( 'name' ); + const instance = window?.srfm?.[ inputName ]; + if ( instance ) { + instance.clear(); + } + + // Also clear the hidden input that holds the submitted value. + const hiddenInput = dropdown + .closest( '.srfm-dropdown-block' ) + ?.querySelector( '.srfm-input-dropdown-hidden' ); + if ( hiddenInput ) { + hiddenInput.setAttribute( 'value', '' ); + hiddenInput.dispatchEvent( new Event( 'change', { bubbles: true } ) ); + } + } ); +} ); diff --git a/assets/js/unminified/form-submit.js b/assets/js/unminified/form-submit.js index 4b7fde776..cca718ce4 100644 --- a/assets/js/unminified/form-submit.js +++ b/assets/js/unminified/form-submit.js @@ -437,6 +437,14 @@ function showSuccessMessage( }, 500 ); } else if ( afterSubmission === 'reset form' ) { form.reset(); + // Dispatch event so custom field implementations (TomSelect dropdowns, + // date/time pickers, signature pads, etc.) can reset their own state, + // since the native form.reset() only resets standard HTML elements. + document.dispatchEvent( + new CustomEvent( 'srfm_form_reset', { + detail: { form }, + } ) + ); } element.innerHTML = message; container.classList.add( 'srfm-active' ); From df055e5bff2a74db118fb2754945efbdf6965081 Mon Sep 17 00:00:00 2001 From: Avinash Kumar Sharma Date: Fri, 26 Jun 2026 22:59:41 +0530 Subject: [PATCH 10/35] fix: restore bullet point visibility in react-quill admin editor The vendor quill.snow.css (Quill 2.x) renders bullet markers via .ql-ui::before child elements, but react-quill bundles Quill 1.3.7 which uses ul>li::before pseudo-elements. This mismatch made bullets invisible in all admin react-quill contexts. Fix by adding Quill 1.x-compatible li::before rules: - Via wp_add_inline_style() on the reactQuill handle for both the form editor page and the global settings page (sidebar QuillEditor) - Via the srfm-textarea-quill-styles SASS mixin for the block editor canvas textarea block preview Fixes #2149 Co-Authored-By: Claude Sonnet 4.6 --- admin/admin.php | 14 +++++++++++ sass/blocks/default/utils/mixins.scss | 35 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/admin/admin.php b/admin/admin.php index 5f9dd8de2..fa2b062e3 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -894,6 +894,13 @@ public function enqueue_styles() { wp_enqueue_style( SRFM_SLUG . '-intl', $vendor_css_uri . 'intl/intlTelInput-backend.min.css', [], SRFM_VER ); wp_enqueue_style( SRFM_SLUG . '-common', $css_uri . 'common' . $file_prefix . '.css', [], SRFM_VER ); wp_enqueue_style( SRFM_SLUG . '-reactQuill', $vendor_css_uri . 'quill/quill.snow.css', [], SRFM_VER ); + // Quill 1.x (react-quill) renders bullet list markers via CSS ::before pseudo-elements, + // whereas the vendor quill.snow.css targets .ql-ui child elements (Quill 2.x approach). + // These rules restore bullet-point visibility when react-quill is used in the admin editor. + wp_add_inline_style( + SRFM_SLUG . '-reactQuill', + '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}' + ); wp_enqueue_style( SRFM_SLUG . '-single-form-modal', $css_uri . 'single-form-setting' . $file_prefix . '.css', [], SRFM_VER ); // if version is equal to or lower than 6.6.2 then add compatibility css. @@ -1334,6 +1341,13 @@ public function enqueue_scripts() { // Enqueue Tailwind and Quill editor styles for the settings page. wp_enqueue_style( SRFM_SLUG . '-settings-build', SRFM_URL . 'assets/build/settings.css', [], SRFM_VER, 'all' ); wp_enqueue_style( SRFM_SLUG . '-reactQuill', SRFM_URL . 'assets/css/minified/deps/quill/quill.snow.css', [], SRFM_VER ); + // Quill 1.x (react-quill) renders bullet list markers via CSS ::before pseudo-elements, + // whereas the vendor quill.snow.css targets .ql-ui child elements (Quill 2.x approach). + // These rules restore bullet-point visibility when react-quill is used in the admin editor. + wp_add_inline_style( + SRFM_SLUG . '-reactQuill', + '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}' + ); $script_translations_handlers[] = SRFM_SLUG . '-settings'; } diff --git a/sass/blocks/default/utils/mixins.scss b/sass/blocks/default/utils/mixins.scss index 1a0f810af..a18fbb7a2 100644 --- a/sass/blocks/default/utils/mixins.scss +++ b/sass/blocks/default/utils/mixins.scss @@ -449,6 +449,41 @@ box-shadow: none; // Override astra theme styling for the box shadow } } + // Quill 1.x (react-quill) renders bullet list markers via CSS ::before pseudo-elements, + // whereas the vendor quill.snow.css targets .ql-ui child elements (Quill 2.x approach). + // These rules ensure bullet points are visible when react-quill is used in the admin editor. + .ql-editor { + ol, + ul { + padding-left: 1.5em; + } + + ol > li, + ul > li { + list-style-type: none; + } + + ul > li::before { + content: '\2022'; + } + + li::before { + display: inline-block; + white-space: nowrap; + width: 1.2em; + } + + li:not( .ql-direction-rtl )::before { + margin-left: -1.5em; + margin-right: 0.3em; + text-align: right; + } + + li.ql-direction-rtl::before { + margin-left: 0.3em; + margin-right: -1.5em; + } + } } // Row Gap and Column Gap Mixin. @mixin srfm-gap-styles { From 91ae043467ffc3e6bd173459938a713c695b642d Mon Sep 17 00:00:00 2001 From: Avinash Kumar Sharma Date: Fri, 26 Jun 2026 23:12:03 +0530 Subject: [PATCH 11/35] fix: restore ordered list numbering in react-quill admin editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quill 1.x (react-quill) uses CSS counters on ol li::before for numbered lists, but the vendor quill.snow.css (Quill 2.x) uses .ql-ui child elements — leaving ordered lists without numbers in the admin editor. Add the full set of Quill 1.x counter rules (list-0 through list-9, decimal → lower-alpha → lower-roman per indent level) via: - wp_add_inline_style() on the reactQuill handle (form editor + settings) - srfm-textarea-quill-styles SASS mixin (block editor canvas) Co-Authored-By: Claude Sonnet 4.6 --- admin/admin.php | 4 +- sass/blocks/default/utils/mixins.scss | 89 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/admin/admin.php b/admin/admin.php index fa2b062e3..a9e8f1a28 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -899,7 +899,7 @@ public function enqueue_styles() { // These rules restore bullet-point visibility when react-quill is used in the admin editor. wp_add_inline_style( SRFM_SLUG . '-reactQuill', - '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}' + '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}.ql-editor ol li{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;counter-increment:list-0}.ql-editor ol li::before{content:counter(list-0,decimal) ". "}.ql-editor ol li.ql-indent-1{counter-increment:list-1;counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-1::before{content:counter(list-1,lower-alpha) ". "}.ql-editor ol li.ql-indent-2{counter-increment:list-2;counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-2::before{content:counter(list-2,lower-roman) ". "}.ql-editor ol li.ql-indent-3{counter-increment:list-3;counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-3::before{content:counter(list-3,decimal) ". "}.ql-editor ol li.ql-indent-4{counter-increment:list-4;counter-reset:list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-4::before{content:counter(list-4,lower-alpha) ". "}.ql-editor ol li.ql-indent-5{counter-increment:list-5;counter-reset:list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-5::before{content:counter(list-5,lower-roman) ". "}.ql-editor ol li.ql-indent-6{counter-increment:list-6;counter-reset:list-7 list-8 list-9}.ql-editor ol li.ql-indent-6::before{content:counter(list-6,decimal) ". "}.ql-editor ol li.ql-indent-7{counter-increment:list-7;counter-reset:list-8 list-9}.ql-editor ol li.ql-indent-7::before{content:counter(list-7,lower-alpha) ". "}.ql-editor ol li.ql-indent-8{counter-increment:list-8;counter-reset:list-9}.ql-editor ol li.ql-indent-8::before{content:counter(list-8,lower-roman) ". "}.ql-editor ol li.ql-indent-9{counter-increment:list-9}.ql-editor ol li.ql-indent-9::before{content:counter(list-9,decimal) ". "}' ); wp_enqueue_style( SRFM_SLUG . '-single-form-modal', $css_uri . 'single-form-setting' . $file_prefix . '.css', [], SRFM_VER ); @@ -1346,7 +1346,7 @@ public function enqueue_scripts() { // These rules restore bullet-point visibility when react-quill is used in the admin editor. wp_add_inline_style( SRFM_SLUG . '-reactQuill', - '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}' + '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}.ql-editor ol li{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;counter-increment:list-0}.ql-editor ol li::before{content:counter(list-0,decimal) ". "}.ql-editor ol li.ql-indent-1{counter-increment:list-1;counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-1::before{content:counter(list-1,lower-alpha) ". "}.ql-editor ol li.ql-indent-2{counter-increment:list-2;counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-2::before{content:counter(list-2,lower-roman) ". "}.ql-editor ol li.ql-indent-3{counter-increment:list-3;counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-3::before{content:counter(list-3,decimal) ". "}.ql-editor ol li.ql-indent-4{counter-increment:list-4;counter-reset:list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-4::before{content:counter(list-4,lower-alpha) ". "}.ql-editor ol li.ql-indent-5{counter-increment:list-5;counter-reset:list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-5::before{content:counter(list-5,lower-roman) ". "}.ql-editor ol li.ql-indent-6{counter-increment:list-6;counter-reset:list-7 list-8 list-9}.ql-editor ol li.ql-indent-6::before{content:counter(list-6,decimal) ". "}.ql-editor ol li.ql-indent-7{counter-increment:list-7;counter-reset:list-8 list-9}.ql-editor ol li.ql-indent-7::before{content:counter(list-7,lower-alpha) ". "}.ql-editor ol li.ql-indent-8{counter-increment:list-8;counter-reset:list-9}.ql-editor ol li.ql-indent-8::before{content:counter(list-8,lower-roman) ". "}.ql-editor ol li.ql-indent-9{counter-increment:list-9}.ql-editor ol li.ql-indent-9::before{content:counter(list-9,decimal) ". "}' ); $script_translations_handlers[] = SRFM_SLUG . '-settings'; diff --git a/sass/blocks/default/utils/mixins.scss b/sass/blocks/default/utils/mixins.scss index a18fbb7a2..1187d9922 100644 --- a/sass/blocks/default/utils/mixins.scss +++ b/sass/blocks/default/utils/mixins.scss @@ -483,6 +483,95 @@ margin-left: 0.3em; margin-right: -1.5em; } + + ol li { + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + counter-increment: list-0; + } + + ol li::before { + content: counter( list-0, decimal ) '. '; + } + + ol li.ql-indent-1 { + counter-increment: list-1; + counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-1::before { + content: counter( list-1, lower-alpha ) '. '; + } + + ol li.ql-indent-2 { + counter-increment: list-2; + counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-2::before { + content: counter( list-2, lower-roman ) '. '; + } + + ol li.ql-indent-3 { + counter-increment: list-3; + counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-3::before { + content: counter( list-3, decimal ) '. '; + } + + ol li.ql-indent-4 { + counter-increment: list-4; + counter-reset: list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-4::before { + content: counter( list-4, lower-alpha ) '. '; + } + + ol li.ql-indent-5 { + counter-increment: list-5; + counter-reset: list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-5::before { + content: counter( list-5, lower-roman ) '. '; + } + + ol li.ql-indent-6 { + counter-increment: list-6; + counter-reset: list-7 list-8 list-9; + } + + ol li.ql-indent-6::before { + content: counter( list-6, decimal ) '. '; + } + + ol li.ql-indent-7 { + counter-increment: list-7; + counter-reset: list-8 list-9; + } + + ol li.ql-indent-7::before { + content: counter( list-7, lower-alpha ) '. '; + } + + ol li.ql-indent-8 { + counter-increment: list-8; + counter-reset: list-9; + } + + ol li.ql-indent-8::before { + content: counter( list-8, lower-roman ) '. '; + } + + ol li.ql-indent-9 { + counter-increment: list-9; + } + + ol li.ql-indent-9::before { + content: counter( list-9, decimal ) '. '; + } } } // Row Gap and Column Gap Mixin. From 5dd4d8aeeba6aaf62bdc0e1074da110b572b0310 Mon Sep 17 00:00:00 2001 From: Avinash Kumar Sharma Date: Fri, 26 Jun 2026 23:25:51 +0530 Subject: [PATCH 12/35] fix: restore bullet and number list visibility on the frontend Two separate frontend issues were caused by the Quill 1.x vs 2.x CSS mismatch: 1. Confirmation message (static HTML in .srfm-success-box-description): Plain ul/ol/li HTML has no .ql-editor wrapper so the Quill-scoped CSS never applied. Tailwind/theme resets stripped default list markers. Fix: add list-style-type:disc/decimal with padding-left to the .srfm-success-box-description styles in sass/frontend/form.scss. 2. Frontend Textarea field (Quill 2.x): The shared mixin contained Quill 1.x li::before / CSS counter rules that conflicted with Quill 2.x .ql-ui child-element markers, causing misalignment and double markers. Fix: remove the Quill 1.x marker rules from the mixin (keeping only the neutral padding-left / list-style-type:none overrides) and move them into backend.scss scoped to .srfm-textarea-quill (the Quill 1.x block-editor canvas context only). Co-Authored-By: Claude Sonnet 4.6 --- sass/blocks/default/backend.scss | 120 +++++++++++++++++++++++++- sass/blocks/default/utils/mixins.scss | 117 +------------------------ sass/frontend/form.scss | 15 ++++ 3 files changed, 136 insertions(+), 116 deletions(-) diff --git a/sass/blocks/default/backend.scss b/sass/blocks/default/backend.scss index a23d961d0..a85c1bc4f 100644 --- a/sass/blocks/default/backend.scss +++ b/sass/blocks/default/backend.scss @@ -83,6 +83,123 @@ .srfm-textarea-quill { @include srfm-textarea-quill-styles; + // Quill 1.x (react-quill) renders list markers via CSS ::before pseudo-elements. + // The vendor quill.snow.css uses .ql-ui child elements (Quill 2.x approach) which + // only applies to the frontend textarea. These rules cover the block-editor canvas. + .ql-editor { + min-height: 200px; + + ul > li::before { + content: '\2022'; + } + + li::before { + display: inline-block; + white-space: nowrap; + width: 1.2em; + } + + li:not( .ql-direction-rtl )::before { + margin-left: -1.5em; + margin-right: 0.3em; + text-align: right; + } + + li.ql-direction-rtl::before { + margin-left: 0.3em; + margin-right: -1.5em; + } + + ol li { + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + counter-increment: list-0; + } + + ol li::before { + content: counter( list-0, decimal ) '. '; + } + + ol li.ql-indent-1 { + counter-increment: list-1; + counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-1::before { + content: counter( list-1, lower-alpha ) '. '; + } + + ol li.ql-indent-2 { + counter-increment: list-2; + counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-2::before { + content: counter( list-2, lower-roman ) '. '; + } + + ol li.ql-indent-3 { + counter-increment: list-3; + counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-3::before { + content: counter( list-3, decimal ) '. '; + } + + ol li.ql-indent-4 { + counter-increment: list-4; + counter-reset: list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-4::before { + content: counter( list-4, lower-alpha ) '. '; + } + + ol li.ql-indent-5 { + counter-increment: list-5; + counter-reset: list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-5::before { + content: counter( list-5, lower-roman ) '. '; + } + + ol li.ql-indent-6 { + counter-increment: list-6; + counter-reset: list-7 list-8 list-9; + } + + ol li.ql-indent-6::before { + content: counter( list-6, decimal ) '. '; + } + + ol li.ql-indent-7 { + counter-increment: list-7; + counter-reset: list-8 list-9; + } + + ol li.ql-indent-7::before { + content: counter( list-7, lower-alpha ) '. '; + } + + ol li.ql-indent-8 { + counter-increment: list-8; + counter-reset: list-9; + } + + ol li.ql-indent-8::before { + content: counter( list-8, lower-roman ) '. '; + } + + ol li.ql-indent-9 { + counter-increment: list-9; + } + + ol li.ql-indent-9::before { + content: counter( list-9, decimal ) '. '; + } + } + // Add the background color for the quill textarea input. .quill { background-color: var( --srfm-color-input-background ); @@ -93,9 +210,6 @@ .ql-container.ql-snow { border: none; } - .ql-editor { - min-height: 200px; - } } } diff --git a/sass/blocks/default/utils/mixins.scss b/sass/blocks/default/utils/mixins.scss index 1187d9922..2ea292058 100644 --- a/sass/blocks/default/utils/mixins.scss +++ b/sass/blocks/default/utils/mixins.scss @@ -449,9 +449,10 @@ box-shadow: none; // Override astra theme styling for the box shadow } } - // Quill 1.x (react-quill) renders bullet list markers via CSS ::before pseudo-elements, - // whereas the vendor quill.snow.css targets .ql-ui child elements (Quill 2.x approach). - // These rules ensure bullet points are visible when react-quill is used in the admin editor. + // Override any theme CSS reset that may strip padding/markers from list elements + // inside the Quill editor. The Quill 1.x vs 2.x list-marker rules are NOT included + // here because they differ per context — see backend.scss (.srfm-textarea-quill) for + // the Quill 1.x rules used in the block-editor canvas (react-quill). .ql-editor { ol, ul { @@ -462,116 +463,6 @@ ul > li { list-style-type: none; } - - ul > li::before { - content: '\2022'; - } - - li::before { - display: inline-block; - white-space: nowrap; - width: 1.2em; - } - - li:not( .ql-direction-rtl )::before { - margin-left: -1.5em; - margin-right: 0.3em; - text-align: right; - } - - li.ql-direction-rtl::before { - margin-left: 0.3em; - margin-right: -1.5em; - } - - ol li { - counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; - counter-increment: list-0; - } - - ol li::before { - content: counter( list-0, decimal ) '. '; - } - - ol li.ql-indent-1 { - counter-increment: list-1; - counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-1::before { - content: counter( list-1, lower-alpha ) '. '; - } - - ol li.ql-indent-2 { - counter-increment: list-2; - counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-2::before { - content: counter( list-2, lower-roman ) '. '; - } - - ol li.ql-indent-3 { - counter-increment: list-3; - counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-3::before { - content: counter( list-3, decimal ) '. '; - } - - ol li.ql-indent-4 { - counter-increment: list-4; - counter-reset: list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-4::before { - content: counter( list-4, lower-alpha ) '. '; - } - - ol li.ql-indent-5 { - counter-increment: list-5; - counter-reset: list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-5::before { - content: counter( list-5, lower-roman ) '. '; - } - - ol li.ql-indent-6 { - counter-increment: list-6; - counter-reset: list-7 list-8 list-9; - } - - ol li.ql-indent-6::before { - content: counter( list-6, decimal ) '. '; - } - - ol li.ql-indent-7 { - counter-increment: list-7; - counter-reset: list-8 list-9; - } - - ol li.ql-indent-7::before { - content: counter( list-7, lower-alpha ) '. '; - } - - ol li.ql-indent-8 { - counter-increment: list-8; - counter-reset: list-9; - } - - ol li.ql-indent-8::before { - content: counter( list-8, lower-roman ) '. '; - } - - ol li.ql-indent-9 { - counter-increment: list-9; - } - - ol li.ql-indent-9::before { - content: counter( list-9, decimal ) '. '; - } } } // Row Gap and Column Gap Mixin. diff --git a/sass/frontend/form.scss b/sass/frontend/form.scss index 19307c990..29b1d54ea 100644 --- a/sass/frontend/form.scss +++ b/sass/frontend/form.scss @@ -219,6 +219,21 @@ display: flex; flex-direction: column; gap: 8px; + + // The confirmation message is stored as plain HTML (ul/ol/li) from the + // Quill editor. Restore list markers that may be reset by theme or Tailwind. + ul, + ol { + padding-left: 1.5em; + } + + ul li { + list-style-type: disc; + } + + ol li { + list-style-type: decimal; + } } } From e62abb324477972a3c06d36a4ba4c4e7ed30d97d Mon Sep 17 00:00:00 2001 From: Avinash Kumar Sharma Date: Sat, 27 Jun 2026 09:33:21 +0530 Subject: [PATCH 13/35] fix: apply Quill editor formatting in the confirmation message display The confirmation message HTML (headings, blockquote, code, font size, font family, indent, links, images, lists) is injected as plain HTML into .srfm-success-box-description, outside any .ql-editor wrapper. Quill and theme/Tailwind CSS resets strip most of these styles. Reproduce the full set of Quill 1.x snow-theme display rules inside .srfm-success-box-description so colour (inline style), alignment (inline style), bold/italic/underline (HTML tags), headings, blockquote, code blocks, font sizes, font families, indentation, and list markers all render correctly after form submission. Co-Authored-By: Claude Sonnet 4.6 --- sass/frontend/form.scss | 95 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/sass/frontend/form.scss b/sass/frontend/form.scss index 29b1d54ea..9654c02b9 100644 --- a/sass/frontend/form.scss +++ b/sass/frontend/form.scss @@ -220,20 +220,97 @@ flex-direction: column; gap: 8px; - // The confirmation message is stored as plain HTML (ul/ol/li) from the - // Quill editor. Restore list markers that may be reset by theme or Tailwind. - ul, - ol { - padding-left: 1.5em; + // The confirmation message is stored as plain HTML from the Quill 1.x + // editor and rendered outside .ql-editor, so we reproduce the necessary + // formatting rules here (matching quill.snow.css behaviour). + + // Headings. + h1 { font-size: 2em; font-weight: bold; } + h2 { font-size: 1.5em; font-weight: bold; } + h3 { font-size: 1.17em; font-weight: bold; } + h4 { font-size: 1em; font-weight: bold; } + h5 { font-size: 0.83em; font-weight: bold; } + h6 { font-size: 0.67em; font-weight: bold; } + + // Links. + a { text-decoration: underline; } + + // Blockquote. + blockquote { + border-left: 4px solid #ccc; + margin-top: 5px; + margin-bottom: 5px; + padding-left: 16px; + } + + // Code / pre. + code, + pre { + background-color: #f0f0f0; + border-radius: 3px; + } + + pre { + white-space: pre-wrap; + margin-top: 5px; + margin-bottom: 5px; + padding: 5px 10px; } - ul li { - list-style-type: disc; + code { + font-size: 85%; + padding: 2px 4px; } - ol li { - list-style-type: decimal; + pre.ql-syntax { + background-color: #23241f; + color: #f8f8f2; + overflow: visible; } + + // Images. + img { max-width: 100%; } + + // Lists — restore markers stripped by theme / Tailwind reset. + ul, + ol { + padding-left: 1.5em; + } + + ul li { list-style-type: disc; } + ol li { list-style-type: decimal; } + + // Font sizes. + .ql-size-small { font-size: 0.75em; } + .ql-size-large { font-size: 1.5em; } + .ql-size-huge { font-size: 2.5em; } + + // Font families. + .ql-font-serif { font-family: Georgia, 'Times New Roman', serif; } + .ql-font-monospace { font-family: Monaco, 'Courier New', monospace; } + + // RTL. + .ql-direction-rtl { direction: rtl; text-align: inherit; } + + // Indentation (3 em per level; list items add 1.5 em for the marker gutter). + .ql-indent-1:not( .ql-direction-rtl ) { padding-left: 3em; } + li.ql-indent-1:not( .ql-direction-rtl ) { padding-left: 4.5em; } + .ql-indent-2:not( .ql-direction-rtl ) { padding-left: 6em; } + li.ql-indent-2:not( .ql-direction-rtl ) { padding-left: 7.5em; } + .ql-indent-3:not( .ql-direction-rtl ) { padding-left: 9em; } + li.ql-indent-3:not( .ql-direction-rtl ) { padding-left: 10.5em; } + .ql-indent-4:not( .ql-direction-rtl ) { padding-left: 12em; } + li.ql-indent-4:not( .ql-direction-rtl ) { padding-left: 13.5em; } + .ql-indent-5:not( .ql-direction-rtl ) { padding-left: 15em; } + li.ql-indent-5:not( .ql-direction-rtl ) { padding-left: 16.5em; } + .ql-indent-6:not( .ql-direction-rtl ) { padding-left: 18em; } + li.ql-indent-6:not( .ql-direction-rtl ) { padding-left: 19.5em; } + .ql-indent-7:not( .ql-direction-rtl ) { padding-left: 21em; } + li.ql-indent-7:not( .ql-direction-rtl ) { padding-left: 22.5em; } + .ql-indent-8:not( .ql-direction-rtl ) { padding-left: 24em; } + li.ql-indent-8:not( .ql-direction-rtl ) { padding-left: 25.5em; } + .ql-indent-9:not( .ql-direction-rtl ) { padding-left: 27em; } + li.ql-indent-9:not( .ql-direction-rtl ) { padding-left: 28.5em; } } } From 58757acfddc7efdc69a04907f6930e92b0df5cf4 Mon Sep 17 00:00:00 2001 From: Avinash Kumar Sharma Date: Sat, 27 Jun 2026 10:01:21 +0530 Subject: [PATCH 14/35] fix: use inline ::before markers in confirmation message for correct alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit list-style-type: disc/decimal places markers outside the text flow, so when list items carry text-align: center (from the Quill editor state) the marker is pinned to the far left while text is centered — a wide gap. Switch to the same Quill 1.x ::before pseudo-element approach used in the editor: the marker is an inline element and travels with the text, so center-aligned list items render with the marker and text together (same visual as the editor). Covers bullet and ordered list at all 9 indent levels. Co-Authored-By: Claude Sonnet 4.6 --- sass/frontend/form.scss | 118 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/sass/frontend/form.scss b/sass/frontend/form.scss index 9654c02b9..9d3185acb 100644 --- a/sass/frontend/form.scss +++ b/sass/frontend/form.scss @@ -272,13 +272,127 @@ img { max-width: 100%; } // Lists — restore markers stripped by theme / Tailwind reset. + // Use Quill 1.x ::before pseudo-element approach (not list-style-type) + // so markers stay inline with the text and respect text-align (e.g. center). ul, ol { padding-left: 1.5em; } - ul li { list-style-type: disc; } - ol li { list-style-type: decimal; } + ul > li, + ol > li { + list-style-type: none; + } + + ul > li::before { + content: '\2022'; + } + + li::before { + display: inline-block; + white-space: nowrap; + width: 1.2em; + } + + li:not( .ql-direction-rtl )::before { + margin-left: -1.5em; + margin-right: 0.3em; + text-align: right; + } + + li.ql-direction-rtl::before { + margin-left: 0.3em; + margin-right: -1.5em; + } + + ol li { + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + counter-increment: list-0; + } + + ol li::before { + content: counter( list-0, decimal ) '. '; + } + + ol li.ql-indent-1 { + counter-increment: list-1; + counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-1::before { + content: counter( list-1, lower-alpha ) '. '; + } + + ol li.ql-indent-2 { + counter-increment: list-2; + counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-2::before { + content: counter( list-2, lower-roman ) '. '; + } + + ol li.ql-indent-3 { + counter-increment: list-3; + counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-3::before { + content: counter( list-3, decimal ) '. '; + } + + ol li.ql-indent-4 { + counter-increment: list-4; + counter-reset: list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-4::before { + content: counter( list-4, lower-alpha ) '. '; + } + + ol li.ql-indent-5 { + counter-increment: list-5; + counter-reset: list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-5::before { + content: counter( list-5, lower-roman ) '. '; + } + + ol li.ql-indent-6 { + counter-increment: list-6; + counter-reset: list-7 list-8 list-9; + } + + ol li.ql-indent-6::before { + content: counter( list-6, decimal ) '. '; + } + + ol li.ql-indent-7 { + counter-increment: list-7; + counter-reset: list-8 list-9; + } + + ol li.ql-indent-7::before { + content: counter( list-7, lower-alpha ) '. '; + } + + ol li.ql-indent-8 { + counter-increment: list-8; + counter-reset: list-9; + } + + ol li.ql-indent-8::before { + content: counter( list-8, lower-roman ) '. '; + } + + ol li.ql-indent-9 { + counter-increment: list-9; + } + + ol li.ql-indent-9::before { + content: counter( list-9, decimal ) '. '; + } // Font sizes. .ql-size-small { font-size: 0.75em; } From 821584501c16a06594eb24837a059a6d0cd82ea3 Mon Sep 17 00:00:00 2001 From: mohitbsftester Date: Thu, 14 May 2026 15:11:30 +0530 Subject: [PATCH 15/35] Add dashboard AI quick draft flow with analytics tracking --- admin/admin.php | 134 ++++++++++++++++++ admin/analytics.php | 1 + .../AiFormBuilderForm.js | 10 ++ .../components/AiFormBuilder.js | 40 +++++- 4 files changed, 184 insertions(+), 1 deletion(-) diff --git a/admin/admin.php b/admin/admin.php index 5f9dd8de2..f2c29e745 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -120,6 +120,7 @@ public function __construct() { add_action( 'wp_ajax_sureforms_dismiss_pointer', [ $this, 'pointer_dismissed' ] ); add_action( 'wp_ajax_sureforms_accept_cta', [ $this, 'pointer_accepted_cta' ] ); add_action( 'wp_ajax_srfm_notice_response', [ $this, 'handle_notice_response' ] ); + add_action( 'wp_ajax_srfm_ai_widget_usage', [ $this, 'track_ai_widget_usage' ] ); // Register dashboard widget only if there are recent entries. add_action( 'admin_init', [ $this, 'maybe_register_dashboard_widget' ] ); @@ -1921,6 +1922,9 @@ public function maybe_register_dashboard_widget() { return; } + // Always register the AI quick draft dashboard widget. + add_action( 'wp_dashboard_setup', [ $this, 'register_ai_dashboard_widget' ] ); + // Quick check if there are any entries in the last 7 days. $seven_days_ago = strtotime( '-7 days' ); $total_entries = Entries::get_entries_count_after( $seven_days_ago ); @@ -1956,6 +1960,136 @@ public function register_dashboard_widget() { ); } + /** + * Register the AI quick draft dashboard widget. + * + * @return void + * @since 2.7.0 + */ + public function register_ai_dashboard_widget() { + wp_add_dashboard_widget( + 'sureforms_ai_quick_draft', + __( 'SureForms AI Quick Draft', 'sureforms' ), + [ $this, 'render_ai_dashboard_widget' ], + null, + null, + 'normal', + 'high' + ); + } + + /** + * Render AI quick draft dashboard widget content. + * + * @return void + * @since 2.7.0 + */ + public function render_ai_dashboard_widget() { + $redirect_url = admin_url( 'admin.php?page=add-new-form' ); + ?> +
+

+ +

+ + +

+ + 0/2000 +

+
+ + __( 'Invalid nonce.', 'sureforms' ) ], 403 ); + } + + if ( ! Helper::current_user_can() ) { + wp_send_json_error( [ 'message' => __( 'Unauthorized user.', 'sureforms' ) ], 403 ); + } + + $current_count = (int) Helper::get_srfm_option( 'ai_dashboard_widget_uses', 0 ); + Helper::update_srfm_option( 'ai_dashboard_widget_uses', $current_count + 1 ); + + wp_send_json_success(); + } + /** * Render the dashboard widget content. * diff --git a/admin/analytics.php b/admin/analytics.php index 20c78ab4b..7fa044881 100644 --- a/admin/analytics.php +++ b/admin/analytics.php @@ -142,6 +142,7 @@ public function add_srfm_analytics_data( $stats_data ) { 'forms_using_custom_css' => $this->forms_using_custom_css(), 'ai_generated_forms' => $this->ai_generated_forms(), 'ai_generated_payment_forms' => $this->ai_generated_forms( 'payments' ), + 'ai_dashboard_widget_uses' => (int) Helper::get_srfm_option( 'ai_dashboard_widget_uses', 0 ), 'payment_forms' => $this->get_payment_forms_count(), 'total_entries' => Entries::get_total_entries_by_status(), 'restricted_forms' => $this->get_restricted_forms(), diff --git a/src/admin/components/template-picker/ai-form-builder-components/AiFormBuilderForm.js b/src/admin/components/template-picker/ai-form-builder-components/AiFormBuilderForm.js index 8fc7d700c..114a3fdc8 100644 --- a/src/admin/components/template-picker/ai-form-builder-components/AiFormBuilderForm.js +++ b/src/admin/components/template-picker/ai-form-builder-components/AiFormBuilderForm.js @@ -61,6 +61,7 @@ export default ( props ) => { showCreditDetailsPopup, setShowCreditDetailsPopup, features, + prefilledPrompt, } = props; const [ isListening, setIsListening ] = useState( false ); // State to manage voice recording @@ -303,6 +304,15 @@ export default ( props ) => { } }, [ showGenerateTip ] ); + // Prefill prompt when coming from WordPress dashboard widget. + useEffect( () => { + if ( ! prefilledPrompt ) { + return; + } + setText( prefilledPrompt ); + setCharacterCount( prefilledPrompt.length ); + }, [ prefilledPrompt ] ); + const formCreationleft = srfm_admin?.srfm_ai_usage_details?.remaining ?? 0; const isRegistered = diff --git a/src/admin/components/template-picker/components/AiFormBuilder.js b/src/admin/components/template-picker/components/AiFormBuilder.js index 943f53375..ca7e0d66e 100644 --- a/src/admin/components/template-picker/components/AiFormBuilder.js +++ b/src/admin/components/template-picker/components/AiFormBuilder.js @@ -20,7 +20,6 @@ const AiFormBuilder = () => { const [ message, setMessage ] = useState( __( 'Connecting to AI service', 'sureforms' ) ); - const [ isBuildingForm, setIsBuildingForm ] = useState( false ); const [ percentBuild, setPercentBuild ] = useState( 0 ); const [ formCreationErr, setFormCreationErr ] = useState( '' ); const [ showAuthErrorPopup, setShowAuthErrorPopup ] = useState( false ); @@ -47,6 +46,15 @@ const AiFormBuilder = () => { const urlParams = new URLSearchParams( window.location.search ); const accessKey = urlParams.get( 'access_key' ); + const dashboardPromptFromWidget = ( + urlParams.get( 'srfm_ai_dashboard_prompt' ) || '' + ).trim(); + const [ initialPromptFromWidget ] = useState( dashboardPromptFromWidget ); + const [ hasHandledWidgetPrompt, setHasHandledWidgetPrompt ] = + useState( false ); + const [ isBuildingForm, setIsBuildingForm ] = useState( + Boolean( dashboardPromptFromWidget ) + ); const isRTL = srfm_admin?.is_rtl; const toasterPosition = isRTL ? 'bottom-left' : 'bottom-right'; @@ -240,6 +248,35 @@ const AiFormBuilder = () => { const errorCode = srfm_admin?.srfm_ai_usage_details?.code; const resetAt = srfm_admin?.srfm_ai_usage_details?.resetAt; + useEffect( () => { + if ( ! initialPromptFromWidget || hasHandledWidgetPrompt ) { + return; + } + + setHasHandledWidgetPrompt( true ); + + const normalizedUrl = new URL( window.location.href ); + normalizedUrl.searchParams.delete( 'srfm_ai_dashboard_prompt' ); + window.history.replaceState( {}, '', normalizedUrl.toString() ); + + const shouldProceedWithGeneration = + formCreationleft > 0 && ! errorCode && ! accessKey; + + if ( shouldProceedWithGeneration ) { + handleCreateAiForm( initialPromptFromWidget, [], true ); + setIsBuildingForm( true ); + return; + } + + setIsBuildingForm( false ); + }, [ + accessKey, + errorCode, + formCreationleft, + hasHandledWidgetPrompt, + initialPromptFromWidget, + ] ); + const isRegistered = srfm_admin?.srfm_ai_usage_details?.type === 'registered'; const finalFormCreationCountRemaining = @@ -415,6 +452,7 @@ const AiFormBuilder = () => {
Date: Mon, 29 Jun 2026 09:40:09 +0530 Subject: [PATCH 16/35] test: cover AI quick draft dashboard widget functions --- tests/unit/admin/test-admin.php | 116 ++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/tests/unit/admin/test-admin.php b/tests/unit/admin/test-admin.php index d5a31ba39..aba6ccb30 100644 --- a/tests/unit/admin/test-admin.php +++ b/tests/unit/admin/test-admin.php @@ -292,6 +292,122 @@ public function test_render_dashboard_widget() { $this->assertStringContainsString( 'Recent Entries', $output ); $this->assertStringContainsString( 'Contact Form', $output ); } + + /** + * Test maybe_register_dashboard_widget wires up the dashboard widgets. + * + * It always hooks the AI quick draft widget onto wp_dashboard_setup and + * returns void without throwing. (With no logged-in user the capability + * check short-circuits, so this also covers the early-return path.) + * + * @since 2.13.0 + */ + public function test_maybe_register_dashboard_widget() { + $admin = Admin::get_instance(); + + // Should not throw; returns void. + $this->assertNull( $admin->maybe_register_dashboard_widget() ); + + $reflection = new \ReflectionMethod( $admin, 'maybe_register_dashboard_widget' ); + $source_file = $reflection->getFileName(); + $start_line = $reflection->getStartLine(); + $end_line = $reflection->getEndLine(); + $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + + $this->assertStringContainsString( 'register_ai_dashboard_widget', $source, 'AI quick draft widget should always be registered.' ); + $this->assertStringContainsString( 'wp_dashboard_setup', $source, 'Widgets should register on the wp_dashboard_setup hook.' ); + } + + /** + * Test register_dashboard_widget registers the recent-entries widget. + * + * @since 2.13.0 + */ + public function test_register_dashboard_widget() { + $admin = Admin::get_instance(); + + $reflection = new \ReflectionMethod( $admin, 'register_dashboard_widget' ); + $source_file = $reflection->getFileName(); + $start_line = $reflection->getStartLine(); + $end_line = $reflection->getEndLine(); + $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + + $this->assertStringContainsString( 'sureforms_recent_entries', $source, 'Recent entries widget should use the sureforms_recent_entries id.' ); + $this->assertStringContainsString( 'render_dashboard_widget', $source, 'Recent entries widget should render via render_dashboard_widget.' ); + + // Calling it should not throw when the dashboard API is available. + if ( function_exists( 'wp_add_dashboard_widget' ) ) { + $this->assertNull( $admin->register_dashboard_widget() ); + } + } + + /** + * Test register_ai_dashboard_widget registers the AI quick draft widget. + * + * @since 2.13.0 + */ + public function test_register_ai_dashboard_widget() { + $admin = Admin::get_instance(); + + $reflection = new \ReflectionMethod( $admin, 'register_ai_dashboard_widget' ); + $source_file = $reflection->getFileName(); + $start_line = $reflection->getStartLine(); + $end_line = $reflection->getEndLine(); + $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + + $this->assertStringContainsString( 'sureforms_ai_quick_draft', $source, 'AI dashboard widget should use the sureforms_ai_quick_draft id.' ); + $this->assertStringContainsString( 'render_ai_dashboard_widget', $source, 'AI dashboard widget should render via render_ai_dashboard_widget.' ); + + // Calling it should not throw when the dashboard API is available. + if ( function_exists( 'wp_add_dashboard_widget' ) ) { + $this->assertNull( $admin->register_ai_dashboard_widget() ); + } + } + + /** + * Test render_ai_dashboard_widget outputs the AI quick draft widget markup. + * + * @since 2.13.0 + */ + public function test_render_ai_dashboard_widget() { + $admin = Admin::get_instance(); + + ob_start(); + $admin->render_ai_dashboard_widget(); + $output = ob_get_clean(); + + $this->assertStringContainsString( 'srfm-ai-dashboard-widget', $output, 'Widget should render its wrapper.' ); + $this->assertStringContainsString( 'srfm-ai-dashboard-prompt', $output, 'Widget should render the prompt field.' ); + $this->assertStringContainsString( 'srfm-ai-dashboard-generate', $output, 'Widget should render the generate button.' ); + } + + /** + * Test track_ai_widget_usage verifies the nonce, checks capability, and + * increments the usage counter. + * + * The method is an AJAX handler that terminates via wp_send_json_*; rather + * than invoke it (which would exit the test runner), we assert its contract + * by inspecting the resolved source — matching the established pattern for + * AJAX handlers in this suite. + * + * @since 2.13.0 + */ + public function test_track_ai_widget_usage() { + $admin = Admin::get_instance(); + $this->assertTrue( method_exists( $admin, 'track_ai_widget_usage' ) ); + + $reflection = new \ReflectionMethod( $admin, 'track_ai_widget_usage' ); + $this->assertTrue( $reflection->isPublic(), 'track_ai_widget_usage should be a public method.' ); + + $source_file = $reflection->getFileName(); + $start_line = $reflection->getStartLine(); + $end_line = $reflection->getEndLine(); + $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + + $this->assertStringContainsString( "check_ajax_referer( 'srfm_ai_widget_usage'", $source, 'Usage tracking must verify the srfm_ai_widget_usage nonce.' ); + $this->assertStringContainsString( 'current_user_can', $source, 'Usage tracking must check the user capability.' ); + $this->assertStringContainsString( 'ai_dashboard_widget_uses', $source, 'Usage tracking must increment the ai_dashboard_widget_uses counter.' ); + } } /** From 371e6de2a9a1a548844074cf94a3dca1225b614a Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Mon, 29 Jun 2026 09:40:25 +0530 Subject: [PATCH 17/35] refactor: clarify dashboard widget registration comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AI quick draft widget registers only after the capability gate, not 'always' — reword to avoid the misleading comment flagged in review. --- admin/admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/admin.php b/admin/admin.php index f2c29e745..4bf821a47 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -1922,7 +1922,7 @@ public function maybe_register_dashboard_widget() { return; } - // Always register the AI quick draft dashboard widget. + // Register the AI quick draft widget for capable users (the capability gate above applies); unlike the recent-entries widget below, it is not conditional on having entries. add_action( 'wp_dashboard_setup', [ $this, 'register_ai_dashboard_widget' ] ); // Quick check if there are any entries in the last 7 days. From 99afb2e7460434e7330af5c3e1f8caba60e068c5 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Mon, 29 Jun 2026 10:32:25 +0530 Subject: [PATCH 18/35] fix(payments): pin one-time Stripe intent payload to card The create-intent payload sent automatic_payment_methods, contradicting the client Payment Element (scoped to card via #2884) and the middleware intent (card-only via #85). Send payment_method_types: ['card'] so plugin, client, and server agree; avoids the manual-capture/automatic-methods 400. Ref #1487065. --- inc/payments/front-end.php | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/inc/payments/front-end.php b/inc/payments/front-end.php index 23945979b..4e7ea2f3e 100644 --- a/inc/payments/front-end.php +++ b/inc/payments/front-end.php @@ -136,18 +136,19 @@ public function create_payment_intent() { // Create payment intent with confirm: true for immediate processing. $payment_intent_data = [ - 'secret_key' => $secret_key, - 'amount' => $amount, - 'currency' => strtolower( $currency ), - 'description' => $description, - 'confirm' => false, // Will be confirmed by frontend. - 'receipt_email' => $customer_email, - 'license_key' => $license_key, - 'automatic_payment_methods' => [ - 'enabled' => true, - 'allow_redirects' => 'never', - ], - 'metadata' => [ + 'secret_key' => $secret_key, + 'amount' => $amount, + 'currency' => strtolower( $currency ), + 'description' => $description, + 'confirm' => false, // Will be confirmed by frontend. + 'receipt_email' => $customer_email, + 'license_key' => $license_key, + // One-time payments use manual capture; methods that don't support it (Bacs, Link, Cash App, BNPL) make + // Stripe reject the deferred Elements session in live mode, and an automatic-payment-methods intent can't + // be confirmed by the card-scoped client Element. Pin to card so the client Element, this payload, and the + // middleware intent all agree (Apple/Google Pay are still surfaced through 'card'). + 'payment_method_types' => [ 'card' ], + 'metadata' => [ 'source' => 'SureForms', 'block_id' => $block_id, 'original_amount' => $amount, From 44db5c46ddb27c3e008a60c2430e4cde5e9ef346 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Mon, 29 Jun 2026 16:35:22 +0530 Subject: [PATCH 19/35] fix(payment): list fields nested in container blocks in Email/Name mappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Payment block's Customer Email / Name (and variable-amount) field dropdowns enumerated top-level blocks only, so fields inside container blocks were never listed — notably the User Registration block (srfm/register), whose Email (srfm/email) and First/Last/Username (srfm/input) live as innerBlocks, and the Address block (srfm/address). Flatten the block tree (innerBlocks) before filtering so nested fields are selectable. Filters are unchanged. --- src/blocks/payment/edit.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/blocks/payment/edit.js b/src/blocks/payment/edit.js index 4352a2383..ce977332f 100644 --- a/src/blocks/payment/edit.js +++ b/src/blocks/payment/edit.js @@ -24,6 +24,26 @@ import MultiButtonsControl from '@Components/multi-buttons-control'; import BillingCyclesControl from './components/billing-cycles-control.js'; // BOTH MODE: "both" mode reuses the same single-select controls as subscription mode. +/** + * Recursively flatten a block tree into a single list (parents + all descendants). + * + * Field-mapping dropdowns must see fields nested inside container blocks — e.g. the + * User Registration (`srfm/register`) and Address (`srfm/address`) blocks hold their + * `srfm/email` / `srfm/input` fields as innerBlocks. Without flattening, those nested + * fields never appear in the Email / Name / amount selectors. + * + * @param {Array} blocks Block list (each may have an `innerBlocks` array). + * @return {Array} Flat list of all blocks including nested innerBlocks. + */ +const flattenBlocks = ( blocks ) => + ( blocks || [] ).reduce( ( acc, block ) => { + acc.push( block ); + if ( block?.innerBlocks?.length ) { + acc.push( ...flattenBlocks( block.innerBlocks ) ); + } + return acc; + }, [] ); + const Edit = ( props ) => { const { clientId, attributes, setAttributes, isSelected } = props; const { @@ -82,7 +102,9 @@ const Edit = ( props ) => { } try { - const blocks = getBlocks(); + // Flatten so fields nested inside container blocks (User Registration, + // Address, …) are enumerated too, not just top-level blocks. + const blocks = flattenBlocks( getBlocks() ); const emailsFields = []; const nameFields = []; const variableAmountFields = []; From 15fda4c33958051ebc5e922d622f6d1f2f9954b1 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 30 Jun 2026 19:21:40 +0530 Subject: [PATCH 20/35] fix: address adi review feedback on AI dashboard quick draft - Track ai_dashboard_widget_used as an analytics event (events_record) instead of numeric_values, which isn't ingested downstream - Use @since x.x.x placeholders for the new functions and tests - Rewrite dashboard-widget tests to assert behavior (widget registration and counter increment) instead of grepping method source - Move the AI dashboard widget script out of the render callback via wp_add_inline_script + wp_localize_script (Plugin Check friendly) - Add a server-side prompt length cap in generate_ai_form - Document the intentional react-hooks/exhaustive-deps disable in AiFormBuilder --- admin/admin.php | 173 +++++++++++------- admin/analytics.php | 1 - inc/ai-form-builder/ai-form-builder.php | 12 ++ .../components/AiFormBuilder.js | 4 + tests/unit/admin/test-admin.php | 155 ++++++++++------ 5 files changed, 220 insertions(+), 125 deletions(-) diff --git a/admin/admin.php b/admin/admin.php index 4bf821a47..af92fcb06 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -125,6 +125,9 @@ public function __construct() { // Register dashboard widget only if there are recent entries. add_action( 'admin_init', [ $this, 'maybe_register_dashboard_widget' ] ); + // Enqueue the AI quick draft widget script on the dashboard screen. + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_ai_dashboard_widget_assets' ] ); + // Save first form creation time stamp. add_action( 'admin_init', [ $this, 'save_first_form_creation_time_stamp' ] ); add_action( 'admin_notices', [ $this, 'display_srfm_rating_notice' ] ); @@ -1964,7 +1967,7 @@ public function register_dashboard_widget() { * Register the AI quick draft dashboard widget. * * @return void - * @since 2.7.0 + * @since x.x.x */ public function register_ai_dashboard_widget() { wp_add_dashboard_widget( @@ -1982,10 +1985,9 @@ public function register_ai_dashboard_widget() { * Render AI quick draft dashboard widget content. * * @return void - * @since 2.7.0 + * @since x.x.x */ public function render_ai_dashboard_widget() { - $redirect_url = admin_url( 'admin.php?page=add-new-form' ); ?>

@@ -2008,72 +2010,110 @@ class="widefat" 0/2000

- in the render callback, so it passes Plugin Check and keeps server values + * out of the markup. Server values are passed through wp_localize_script. + * + * @param string $hook_suffix The current admin page hook suffix. + * @return void + * @since x.x.x + */ + public function enqueue_ai_dashboard_widget_assets( $hook_suffix ) { + // Only on the main dashboard, and only for capable users (matches the widget gate). + if ( 'index.php' !== $hook_suffix || ! Helper::current_user_can() ) { + return; + } + + // Register an inline-only handle (empty src) — the WordPress-core pattern for attaching + // localized data plus an inline script without shipping a separate asset file. + wp_register_script( 'srfm-ai-dashboard-widget', '', [], SRFM_VER, true ); + wp_enqueue_script( 'srfm-ai-dashboard-widget' ); + + wp_localize_script( + 'srfm-ai-dashboard-widget', + 'srfmAiDashboardWidget', + [ + 'redirectUrl' => admin_url( 'admin.php?page=add-new-form' ), + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'srfm_ai_widget_usage' ), + 'redirectingTxt' => __( 'Redirecting...', 'sureforms' ), + ] + ); + + $inline_script = <<<'JS' +( function () { + const config = window.srfmAiDashboardWidget || {}; + const generateButton = document.getElementById( 'srfm-ai-dashboard-generate' ); + const promptField = document.getElementById( 'srfm-ai-dashboard-prompt' ); + const charCount = document.getElementById( 'srfm-ai-dashboard-char-count' ); + if ( ! generateButton || ! promptField ) { + return; + } + + const updateWidgetState = function () { + const promptValue = promptField.value.trim(); + generateButton.disabled = ! promptValue; + if ( charCount ) { + charCount.textContent = `${ promptField.value.length }/2000`; + } + }; + + const triggerGeneration = function () { + const prompt = promptField.value.trim(); + if ( ! prompt ) { + promptField.focus(); + return; + } + + generateButton.disabled = true; + generateButton.textContent = config.redirectingTxt; + + const redirectUrl = new URL( config.redirectUrl, window.location.origin ); + redirectUrl.searchParams.set( 'srfm_ai_dashboard_prompt', prompt ); + + const requestBody = new URLSearchParams(); + requestBody.append( 'action', 'srfm_ai_widget_usage' ); + requestBody.append( 'nonce', config.nonce ); + + fetch( config.ajaxUrl, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + body: requestBody.toString(), + } ).finally( function () { + window.location.href = redirectUrl.toString(); + } ); + }; + + promptField.addEventListener( 'input', updateWidgetState ); + generateButton.addEventListener( 'click', triggerGeneration ); + promptField.addEventListener( 'keydown', function ( event ) { + if ( event.key === 'Enter' && ( event.metaKey || event.ctrlKey ) ) { + event.preventDefault(); + triggerGeneration(); + } + } ); + + updateWidgetState(); +}() ); +JS; + + wp_add_inline_script( 'srfm-ai-dashboard-widget', $inline_script ); + } + /** * Track AI dashboard widget usage. * * @return void - * @since 2.7.0 + * @since x.x.x */ public function track_ai_widget_usage() { if ( ! check_ajax_referer( 'srfm_ai_widget_usage', 'nonce', false ) ) { @@ -2084,8 +2124,13 @@ public function track_ai_widget_usage() { wp_send_json_error( [ 'message' => __( 'Unauthorized user.', 'sureforms' ) ], 403 ); } - $current_count = (int) Helper::get_srfm_option( 'ai_dashboard_widget_uses', 0 ); - Helper::update_srfm_option( 'ai_dashboard_widget_uses', $current_count + 1 ); + $current_count = (int) Helper::get_srfm_option( 'ai_dashboard_widget_uses', 0 ) + 1; + Helper::update_srfm_option( 'ai_dashboard_widget_uses', $current_count ); + + // Emit an analytics event so usage lands in the warehouse via events_record. + // $force = true because this is a cumulative counter, not a one-time event — + // it must re-send the latest count each cycle (bypasses one-time dedup). + Analytics::events()->track( 'ai_dashboard_widget_used', (string) $current_count, [], true ); wp_send_json_success(); } diff --git a/admin/analytics.php b/admin/analytics.php index 7fa044881..20c78ab4b 100644 --- a/admin/analytics.php +++ b/admin/analytics.php @@ -142,7 +142,6 @@ public function add_srfm_analytics_data( $stats_data ) { 'forms_using_custom_css' => $this->forms_using_custom_css(), 'ai_generated_forms' => $this->ai_generated_forms(), 'ai_generated_payment_forms' => $this->ai_generated_forms( 'payments' ), - 'ai_dashboard_widget_uses' => (int) Helper::get_srfm_option( 'ai_dashboard_widget_uses', 0 ), 'payment_forms' => $this->get_payment_forms_count(), 'total_entries' => Entries::get_total_entries_by_status(), 'restricted_forms' => $this->get_restricted_forms(), diff --git a/inc/ai-form-builder/ai-form-builder.php b/inc/ai-form-builder/ai-form-builder.php index c4886f2af..39aca1b88 100644 --- a/inc/ai-form-builder/ai-form-builder.php +++ b/inc/ai-form-builder/ai-form-builder.php @@ -52,6 +52,18 @@ public function generate_ai_form( $request ) { array_unshift( $messages, $current_message ); } + // Bail if no usable prompt remained after filtering empty messages. + if ( empty( $messages ) || empty( $messages[0]['content'] ) ) { + wp_send_json_error( [ 'message' => __( 'No prompt was supplied.', 'sureforms' ) ] ); + } + + // Server-side prompt-length cap. The UI enforces a 2000-char limit via maxlength, but that + // is client-side only and can be bypassed by a crafted request, so mirror it here. This is + // cost/resource hardening — output is always escaped, so this is not an XSS concern. + if ( mb_strlen( (string) $messages[0]['content'] ) > 2000 ) { + wp_send_json_error( [ 'message' => __( 'The prompt is too long. Please shorten it and try again.', 'sureforms' ) ] ); + } + // Get the response from the endpoint. $response = AI_Helper::get_chat_completions_response( apply_filters( diff --git a/src/admin/components/template-picker/components/AiFormBuilder.js b/src/admin/components/template-picker/components/AiFormBuilder.js index ca7e0d66e..2c21e1f78 100644 --- a/src/admin/components/template-picker/components/AiFormBuilder.js +++ b/src/admin/components/template-picker/components/AiFormBuilder.js @@ -269,6 +269,10 @@ const AiFormBuilder = () => { } setIsBuildingForm( false ); + // Fires once for the dashboard widget prompt with the default form type. handleCreateAiForm + // is intentionally omitted — adding it (a new ref each render) would risk re-triggering + // generation. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ accessKey, errorCode, diff --git a/tests/unit/admin/test-admin.php b/tests/unit/admin/test-admin.php index aba6ccb30..6a5793d20 100644 --- a/tests/unit/admin/test-admin.php +++ b/tests/unit/admin/test-admin.php @@ -294,80 +294,91 @@ public function test_render_dashboard_widget() { } /** - * Test maybe_register_dashboard_widget wires up the dashboard widgets. + * Test maybe_register_dashboard_widget wires the AI quick draft widget onto wp_dashboard_setup. * - * It always hooks the AI quick draft widget onto wp_dashboard_setup and - * returns void without throwing. (With no logged-in user the capability - * check short-circuits, so this also covers the early-return path.) + * For a capable user it always hooks register_ai_dashboard_widget; with no logged-in user the + * capability check short-circuits and nothing is wired (the early-return path). * - * @since 2.13.0 + * @since x.x.x */ public function test_maybe_register_dashboard_widget() { $admin = Admin::get_instance(); - // Should not throw; returns void. - $this->assertNull( $admin->maybe_register_dashboard_widget() ); + // Capable user: the AI widget hook is wired onto wp_dashboard_setup. + $user_id = wp_insert_user( + [ + 'user_login' => 'srfm_admin_' . uniqid(), + 'user_pass' => 'password', + 'role' => 'administrator', + ] + ); + wp_set_current_user( $user_id ); - $reflection = new \ReflectionMethod( $admin, 'maybe_register_dashboard_widget' ); - $source_file = $reflection->getFileName(); - $start_line = $reflection->getStartLine(); - $end_line = $reflection->getEndLine(); - $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + remove_action( 'wp_dashboard_setup', [ $admin, 'register_ai_dashboard_widget' ] ); + $this->assertNull( $admin->maybe_register_dashboard_widget() ); + $this->assertNotFalse( + has_action( 'wp_dashboard_setup', [ $admin, 'register_ai_dashboard_widget' ] ), + 'AI quick draft widget should hook onto wp_dashboard_setup for capable users.' + ); - $this->assertStringContainsString( 'register_ai_dashboard_widget', $source, 'AI quick draft widget should always be registered.' ); - $this->assertStringContainsString( 'wp_dashboard_setup', $source, 'Widgets should register on the wp_dashboard_setup hook.' ); + // No user: the capability gate short-circuits and nothing is wired. + wp_set_current_user( 0 ); + remove_action( 'wp_dashboard_setup', [ $admin, 'register_ai_dashboard_widget' ] ); + $this->assertNull( $admin->maybe_register_dashboard_widget() ); + $this->assertFalse( + has_action( 'wp_dashboard_setup', [ $admin, 'register_ai_dashboard_widget' ] ), + 'No widget should be wired when the capability check fails.' + ); } /** - * Test register_dashboard_widget registers the recent-entries widget. + * Test register_dashboard_widget registers the recent-entries widget into $wp_meta_boxes. * - * @since 2.13.0 + * @since x.x.x */ public function test_register_dashboard_widget() { - $admin = Admin::get_instance(); + require_once ABSPATH . 'wp-admin/includes/template.php'; + require_once ABSPATH . 'wp-admin/includes/dashboard.php'; - $reflection = new \ReflectionMethod( $admin, 'register_dashboard_widget' ); - $source_file = $reflection->getFileName(); - $start_line = $reflection->getStartLine(); - $end_line = $reflection->getEndLine(); - $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + $admin = Admin::get_instance(); + set_current_screen( 'dashboard' ); - $this->assertStringContainsString( 'sureforms_recent_entries', $source, 'Recent entries widget should use the sureforms_recent_entries id.' ); - $this->assertStringContainsString( 'render_dashboard_widget', $source, 'Recent entries widget should render via render_dashboard_widget.' ); + $admin->register_dashboard_widget(); - // Calling it should not throw when the dashboard API is available. - if ( function_exists( 'wp_add_dashboard_widget' ) ) { - $this->assertNull( $admin->register_dashboard_widget() ); - } + global $wp_meta_boxes; + $this->assertArrayHasKey( + 'sureforms_recent_entries', + $wp_meta_boxes['dashboard']['normal']['high'], + 'Recent entries widget should be registered with the sureforms_recent_entries id.' + ); } /** - * Test register_ai_dashboard_widget registers the AI quick draft widget. + * Test register_ai_dashboard_widget registers the AI quick draft widget into $wp_meta_boxes. * - * @since 2.13.0 + * @since x.x.x */ public function test_register_ai_dashboard_widget() { - $admin = Admin::get_instance(); + require_once ABSPATH . 'wp-admin/includes/template.php'; + require_once ABSPATH . 'wp-admin/includes/dashboard.php'; - $reflection = new \ReflectionMethod( $admin, 'register_ai_dashboard_widget' ); - $source_file = $reflection->getFileName(); - $start_line = $reflection->getStartLine(); - $end_line = $reflection->getEndLine(); - $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + $admin = Admin::get_instance(); + set_current_screen( 'dashboard' ); - $this->assertStringContainsString( 'sureforms_ai_quick_draft', $source, 'AI dashboard widget should use the sureforms_ai_quick_draft id.' ); - $this->assertStringContainsString( 'render_ai_dashboard_widget', $source, 'AI dashboard widget should render via render_ai_dashboard_widget.' ); + $admin->register_ai_dashboard_widget(); - // Calling it should not throw when the dashboard API is available. - if ( function_exists( 'wp_add_dashboard_widget' ) ) { - $this->assertNull( $admin->register_ai_dashboard_widget() ); - } + global $wp_meta_boxes; + $this->assertArrayHasKey( + 'sureforms_ai_quick_draft', + $wp_meta_boxes['dashboard']['normal']['high'], + 'AI dashboard widget should be registered with the sureforms_ai_quick_draft id.' + ); } /** * Test render_ai_dashboard_widget outputs the AI quick draft widget markup. * - * @since 2.13.0 + * @since x.x.x */ public function test_render_ai_dashboard_widget() { $admin = Admin::get_instance(); @@ -382,31 +393,55 @@ public function test_render_ai_dashboard_widget() { } /** - * Test track_ai_widget_usage verifies the nonce, checks capability, and - * increments the usage counter. + * Test track_ai_widget_usage increments the usage counter for a valid, capable request. * - * The method is an AJAX handler that terminates via wp_send_json_*; rather - * than invoke it (which would exit the test runner), we assert its contract - * by inspecting the resolved source — matching the established pattern for - * AJAX handlers in this suite. + * The handler ends in wp_send_json_success() (which calls wp_die), so the wp_die handlers are + * filtered to throw WPDieException — letting the runner survive while we assert the side effect + * (the incremented counter) rather than grepping the method source. * - * @since 2.13.0 + * @since x.x.x */ public function test_track_ai_widget_usage() { $admin = Admin::get_instance(); - $this->assertTrue( method_exists( $admin, 'track_ai_widget_usage' ) ); - $reflection = new \ReflectionMethod( $admin, 'track_ai_widget_usage' ); - $this->assertTrue( $reflection->isPublic(), 'track_ai_widget_usage should be a public method.' ); + $user_id = wp_insert_user( + [ + 'user_login' => 'srfm_admin_' . uniqid(), + 'user_pass' => 'password', + 'role' => 'administrator', + ] + ); + wp_set_current_user( $user_id ); + + $_POST['nonce'] = wp_create_nonce( 'srfm_ai_widget_usage' ); + $_REQUEST['nonce'] = $_POST['nonce']; - $source_file = $reflection->getFileName(); - $start_line = $reflection->getStartLine(); - $end_line = $reflection->getEndLine(); - $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + $before = (int) Helper::get_srfm_option( 'ai_dashboard_widget_uses', 0 ); + + // wp_send_json_success() terminates via wp_die(); make the handlers throw so the test runner + // survives and we can assert behavior after the call. + $throw_handler = static function () { + return static function () { + throw new \WPDieException( 'srfm_test_die' ); + }; + }; + add_filter( 'wp_die_ajax_handler', $throw_handler ); + add_filter( 'wp_die_handler', $throw_handler ); + + ob_start(); + try { + $admin->track_ai_widget_usage(); + } catch ( \WPDieException $e ) { + // Expected — the handler exits via wp_send_json_success(). + } finally { + ob_end_clean(); + remove_filter( 'wp_die_ajax_handler', $throw_handler ); + remove_filter( 'wp_die_handler', $throw_handler ); + unset( $_POST['nonce'], $_REQUEST['nonce'] ); + } - $this->assertStringContainsString( "check_ajax_referer( 'srfm_ai_widget_usage'", $source, 'Usage tracking must verify the srfm_ai_widget_usage nonce.' ); - $this->assertStringContainsString( 'current_user_can', $source, 'Usage tracking must check the user capability.' ); - $this->assertStringContainsString( 'ai_dashboard_widget_uses', $source, 'Usage tracking must increment the ai_dashboard_widget_uses counter.' ); + $after = (int) Helper::get_srfm_option( 'ai_dashboard_widget_uses', 0 ); + $this->assertSame( $before + 1, $after, 'Usage tracking must increment the ai_dashboard_widget_uses counter.' ); } } From 031cf707396564bb7befc9f5abc450e4bf3a8f4e Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 30 Jun 2026 20:00:38 +0530 Subject: [PATCH 21/35] fix(payment): exclude repeater children from field mappers, extract flattenBlocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review feedback on #2918: - Exclude srfm/repeater descendants from the Email/Name/amount mappers — they share one slug class and submit as indexed arrays, so they cannot resolve to a single payment customer email/name value (broke Stripe and PayPal) - Extract the recursive block-flatten into a single exported flattenBlocks helper in Helpers.js (repeater guard exposed via an excludeChildrenOf option); remove the inlined copy in the Payment block - Drop srfm/address inner inputs (City/State/Line 1) from the Customer Name dropdown to cut UX noise; they still resolve everywhere else --- src/blocks/payment/edit.js | 57 ++++++++++++++++++++------------------ src/utils/Helpers.js | 27 ++++++++++++++++++ 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/blocks/payment/edit.js b/src/blocks/payment/edit.js index ce977332f..fbb306bc4 100644 --- a/src/blocks/payment/edit.js +++ b/src/blocks/payment/edit.js @@ -13,6 +13,7 @@ import InspectorTab, { import SRFMAdvancedPanelBody from '@Components/advanced-panel-body'; import SRFMTextControl from '@Components/text-control'; import { useGetCurrentFormId } from '../../blocks-attributes/getFormId'; +import { flattenBlocks } from '@Utils/Helpers'; import { PaymentComponent } from './components/default.js'; import AddInitialAttr from '@Controls/addInitialAttr'; import { compose } from '@wordpress/compose'; @@ -24,26 +25,6 @@ import MultiButtonsControl from '@Components/multi-buttons-control'; import BillingCyclesControl from './components/billing-cycles-control.js'; // BOTH MODE: "both" mode reuses the same single-select controls as subscription mode. -/** - * Recursively flatten a block tree into a single list (parents + all descendants). - * - * Field-mapping dropdowns must see fields nested inside container blocks — e.g. the - * User Registration (`srfm/register`) and Address (`srfm/address`) blocks hold their - * `srfm/email` / `srfm/input` fields as innerBlocks. Without flattening, those nested - * fields never appear in the Email / Name / amount selectors. - * - * @param {Array} blocks Block list (each may have an `innerBlocks` array). - * @return {Array} Flat list of all blocks including nested innerBlocks. - */ -const flattenBlocks = ( blocks ) => - ( blocks || [] ).reduce( ( acc, block ) => { - acc.push( block ); - if ( block?.innerBlocks?.length ) { - acc.push( ...flattenBlocks( block.innerBlocks ) ); - } - return acc; - }, [] ); - const Edit = ( props ) => { const { clientId, attributes, setAttributes, isSelected } = props; const { @@ -102,9 +83,28 @@ const Edit = ( props ) => { } try { + const allBlocks = getBlocks(); // Flatten so fields nested inside container blocks (User Registration, - // Address, …) are enumerated too, not just top-level blocks. - const blocks = flattenBlocks( getBlocks() ); + // Address, …) are enumerated too, not just top-level blocks. Repeater + // children are excluded: every instance shares the same slug class and + // values submit as indexed arrays, so they can't resolve to a single + // payment customer email/name value. + const blocks = flattenBlocks( allBlocks, { + excludeChildrenOf: [ 'srfm/repeater' ], + } ); + + // Address inner inputs (City / State / Line 1) resolve fine but clutter the + // Customer Name dropdown, so collect their slugs to skip them from that list. + const addressDescendantSlugs = new Set( + flattenBlocks( allBlocks ) + .filter( ( block ) => block?.name === 'srfm/address' ) + .flatMap( ( addressBlock ) => + flattenBlocks( addressBlock.innerBlocks || [] ) + .map( ( child ) => child.attributes?.slug ) + .filter( Boolean ) + ) + ); + const emailsFields = []; const nameFields = []; const variableAmountFields = []; @@ -127,11 +127,14 @@ const Edit = ( props ) => { type: 'email', } ); } else if ( blockName === 'srfm/input' ) { - nameFields.push( { - slug, - label: `${ label } (input)`, - type: 'input', - } ); + // Skip Address inner inputs — UX noise in the Name dropdown. + if ( ! addressDescendantSlugs.has( slug ) ) { + nameFields.push( { + slug, + label: `${ label } (input)`, + type: 'input', + } ); + } } else if ( blockName === 'srfm/number' ) { variableAmountFields.push( { slug, diff --git a/src/utils/Helpers.js b/src/utils/Helpers.js index b03781adb..25dc98f3d 100644 --- a/src/utils/Helpers.js +++ b/src/utils/Helpers.js @@ -412,6 +412,33 @@ export const getWithoutSlugBlocks = () => 'srfm/html', ] ); +/** + * Recursively flatten a block tree into a single list (parents + all descendants). + * + * Lets consumers enumerate fields nested inside container blocks (e.g. User Registration + * `srfm/register`, Address `srfm/address`) instead of just the top-level blocks. + * + * @param {Array} blocks Block list (each may have an `innerBlocks` array). + * @param {Object} [options] Options. + * @param {string[]} [options.excludeChildrenOf] Block names whose innerBlocks are NOT descended + * into (the parent itself is still included). E.g. + * `[ 'srfm/repeater' ]` — repeater children share one + * slug class and submit as indexed arrays, so they + * can't resolve to a single value. + * @return {Array} Flat list of all blocks. + */ +export const flattenBlocks = ( blocks, { excludeChildrenOf = [] } = {} ) => + ( blocks || [] ).reduce( ( acc, block ) => { + acc.push( block ); + if ( + ! excludeChildrenOf.includes( block?.name ) && + block?.innerBlocks?.length + ) { + acc.push( ...flattenBlocks( block.innerBlocks, { excludeChildrenOf } ) ); + } + return acc; + }, [] ); + export const setFormSpecificSmartTags = ( updateBlockAttributes ) => { const { getBlocks } = select( editorStore ); let savedBlocks = getBlocks(); From 72a158eed4ddfac8156092e5897d32ea0bc6b72a Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 30 Jun 2026 20:04:25 +0530 Subject: [PATCH 22/35] test: add coverage for enqueue_ai_dashboard_widget_assets The check-test-coverage CI job requires a test for the new enqueue_ai_dashboard_widget_assets() function. Assert the dashboard-only gate and that the widget script is enqueued with its localized config for a capable user. --- tests/unit/admin/test-admin.php | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/unit/admin/test-admin.php b/tests/unit/admin/test-admin.php index 6a5793d20..220e9768a 100644 --- a/tests/unit/admin/test-admin.php +++ b/tests/unit/admin/test-admin.php @@ -392,6 +392,49 @@ public function test_render_ai_dashboard_widget() { $this->assertStringContainsString( 'srfm-ai-dashboard-generate', $output, 'Widget should render the generate button.' ); } + /** + * Test enqueue_ai_dashboard_widget_assets enqueues the widget script only on the dashboard. + * + * Asserts the gate (no enqueue outside index.php) and that, on the dashboard for a capable user, + * the script is enqueued with its localized config — instead of grepping the method source. + * + * @since x.x.x + */ + public function test_enqueue_ai_dashboard_widget_assets() { + $admin = Admin::get_instance(); + + $user_id = wp_insert_user( + [ + 'user_login' => 'srfm_admin_' . uniqid(), + 'user_pass' => 'password', + 'role' => 'administrator', + ] + ); + wp_set_current_user( $user_id ); + + // Not the dashboard: nothing should be enqueued. + wp_dequeue_script( 'srfm-ai-dashboard-widget' ); + wp_deregister_script( 'srfm-ai-dashboard-widget' ); + $admin->enqueue_ai_dashboard_widget_assets( 'edit.php' ); + $this->assertFalse( + wp_script_is( 'srfm-ai-dashboard-widget', 'enqueued' ), + 'Widget script should not load outside the dashboard.' + ); + + // Dashboard + capable user: the script is enqueued with its localized config. + $admin->enqueue_ai_dashboard_widget_assets( 'index.php' ); + $this->assertTrue( + wp_script_is( 'srfm-ai-dashboard-widget', 'enqueued' ), + 'Widget script should be enqueued on the dashboard for capable users.' + ); + $data = wp_scripts()->get_data( 'srfm-ai-dashboard-widget', 'data' ); + $this->assertStringContainsString( + 'srfmAiDashboardWidget', + (string) $data, + 'Localized config object should be attached to the widget script.' + ); + } + /** * Test track_ai_widget_usage increments the usage counter for a valid, capable request. * From cda2a4e4495d1ebe9d6eb8bbe5ed7b139527cde5 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 30 Jun 2026 20:42:24 +0530 Subject: [PATCH 23/35] =?UTF-8?q?fix(email):=20resolve=20osk=20review=20?= =?UTF-8?q?=E2=80=94=20shared=20limits=20source,=20blur=20message,=20filte?= =?UTF-8?q?r=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review feedback on #2911: - Drop the && isValidEmail gate in the blur handler so the RFC 5321 length message wins whenever set, matching the submit path - Extract a documented Field_Validation::get_email_char_limits() helper holding the srfm_email_field_char_limits filter (docblock + @since, 0-disables noted); validate_form_data() now consumes it - Localize the resolved limits into srfm_submit and read them client-side via a single getEmailCharLimits() helper, deduping the hardcoded 64/255 literals so a filter that raises a limit no longer false-blocks valid input client-side - Add test_get_email_char_limits() covering defaults and a filter override --- assets/js/unminified/validation.js | 52 ++++++++++++++++++------ inc/field-validation.php | 47 +++++++++++++++++---- inc/frontend-assets.php | 11 +++-- tests/unit/inc/test-field-validation.php | 37 +++++++++++++++++ 4 files changed, 122 insertions(+), 25 deletions(-) diff --git a/assets/js/unminified/validation.js b/assets/js/unminified/validation.js index 1c3650f36..5d33ce519 100644 --- a/assets/js/unminified/validation.js +++ b/assets/js/unminified/validation.js @@ -1,5 +1,22 @@ import { applyFilters } from '@wordpress/hooks'; +/** + * Resolve the RFC 5321 email character limits. + * + * Reads the server-resolved limits localized into `srfm_submit` (which honor the + * `srfm_email_field_char_limits` filter), falling back to the RFC defaults so a site that + * raises a limit isn't false-blocked client-side with the wrong number. + * + * @return {{local: number, domain: number}} Resolved limits (0 disables a check). + */ +function getEmailCharLimits() { + const limits = window?.srfm_submit?.email_char_limits; + return { + local: limits?.local ?? 64, + domain: limits?.domain ?? 255, + }; +} + async function getUniqueValidationData( checkData, formId, ajaxUrl, token ) { let queryString = 'action=validation_ajax_action&token=' + @@ -329,19 +346,24 @@ export async function fieldValidation( if ( typeof val !== 'string' || ! val.includes( '@' ) ) { return ''; } + const { local: localMax, domain: domainMax } = + getEmailCharLimits(); const at = val.lastIndexOf( '@' ); - if ( val.slice( 0, at ).length > 64 ) { + if ( localMax > 0 && val.slice( 0, at ).length > localMax ) { return window?.srfm?.srfmSprintfString( window?.srfm_submit?.messages ?.srfm_email_local_max_length, - 64 + localMax ); } - if ( val.slice( at + 1 ).length > 255 ) { + if ( + domainMax > 0 && + val.slice( at + 1 ).length > domainMax + ) { return window?.srfm?.srfmSprintfString( window?.srfm_submit?.messages ?.srfm_email_domain_max_length, - 255 + domainMax ); } return ''; @@ -1150,22 +1172,27 @@ function addEmailBlurListener( areaInput, blockClass ) { // RFC 5321 length limits (local <= 64, domain <= 255, split on the last @). // A too-long address can still be a valid format, so flag it here too and // show the length-specific message; otherwise fall back to the format error. + const { local: localMax, domain: domainMax } = getEmailCharLimits(); const emailValueAt = emailField.value.lastIndexOf( '@' ); let lengthErrorMessage = ''; if ( emailValueAt !== -1 ) { - if ( emailField.value.slice( 0, emailValueAt ).length > 64 ) { + if ( + localMax > 0 && + emailField.value.slice( 0, emailValueAt ).length > localMax + ) { lengthErrorMessage = window?.srfm?.srfmSprintfString( window?.srfm_submit?.messages ?.srfm_email_local_max_length, - 64 + localMax ); } else if ( - emailField.value.slice( emailValueAt + 1 ).length > 255 + domainMax > 0 && + emailField.value.slice( emailValueAt + 1 ).length > domainMax ) { lengthErrorMessage = window?.srfm?.srfmSprintfString( window?.srfm_submit?.messages ?.srfm_email_domain_max_length, - 255 + domainMax ); } } @@ -1179,10 +1206,11 @@ function addEmailBlurListener( areaInput, blockClass ) { 'srfm-valid-email-error' ); errorContainer.style.display = 'block'; - errorContainer.innerHTML = - lengthErrorMessage && isValidEmail - ? lengthErrorMessage - : window?.srfm_submit?.messages?.srfm_valid_email; + // Length message wins whenever set (matches the submit path); fall back + // to the generic format error only when there's no length error. + errorContainer.innerHTML = lengthErrorMessage + ? lengthErrorMessage + : window?.srfm_submit?.messages?.srfm_valid_email; errorContainer.id = errorContainer.getAttribute( 'data-srfm-id' ); } else { diff --git a/inc/field-validation.php b/inc/field-validation.php index 9620790d4..af4595e10 100644 --- a/inc/field-validation.php +++ b/inc/field-validation.php @@ -302,15 +302,9 @@ public static function validate_form_data( $form_data, $current_form_id ) { // Split on the LAST @ per RFC 5321 so the local part may contain a quoted @. $at_pos = is_string( $value ) && '' !== $value ? strrpos( $value, '@' ) : false; if ( 'srfm-email' === $get_field_name && is_string( $value ) && false !== $at_pos ) { - $email_limits = apply_filters( - 'srfm_email_field_char_limits', - [ - 'local' => 64, - 'domain' => 255, - ] - ); - $local_max = isset( $email_limits['local'] ) ? absint( $email_limits['local'] ) : 64; - $domain_max = isset( $email_limits['domain'] ) ? absint( $email_limits['domain'] ) : 255; + $email_limits = self::get_email_char_limits(); + $local_max = $email_limits['local']; + $domain_max = $email_limits['domain']; $local_len = mb_strlen( substr( $value, 0, $at_pos ) ); $domain_len = mb_strlen( substr( $value, $at_pos + 1 ) ); @@ -336,6 +330,41 @@ public static function validate_form_data( $form_data, $current_form_id ) { return $not_valid_fields; } + /** + * Resolve the Email field character limits (RFC 5321), split on the last @. + * + * Single source of truth shared by the server validation and the limits localized to the + * frontend script, so a filter override applies consistently to both. + * + * @return array{local:int,domain:int} Resolved limits. A value of 0 disables that check. + * @since x.x.x + */ + public static function get_email_char_limits() { + /** + * Filters the Email field character limits (RFC 5321). + * + * @param array $limits { + * Character limits for the email value, split on the last @. + * + * @type int $local Max characters before the @. 0 disables the check. Default 64. + * @type int $domain Max characters after the @. 0 disables the check. Default 255. + * } + * @since x.x.x + */ + $email_limits = apply_filters( + 'srfm_email_field_char_limits', + [ + 'local' => 64, + 'domain' => 255, + ] + ); + + return [ + 'local' => isset( $email_limits['local'] ) ? absint( $email_limits['local'] ) : 64, + 'domain' => isset( $email_limits['domain'] ) ? absint( $email_limits['domain'] ) : 255, + ]; + } + /** * Process payment block configuration. * diff --git a/inc/frontend-assets.php b/inc/frontend-assets.php index a5c05bb39..f9cbd003b 100644 --- a/inc/frontend-assets.php +++ b/inc/frontend-assets.php @@ -142,10 +142,13 @@ public function register_scripts() { SRFM_SLUG . '-form-submit', SRFM_SLUG . '_submit', [ - 'site_url' => site_url(), - 'nonce' => wp_create_nonce( 'wp_rest' ), - 'messages' => $validation_messages, - 'is_rtl' => $is_rtl, + 'site_url' => site_url(), + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'messages' => $validation_messages, + 'is_rtl' => $is_rtl, + // Resolved RFC 5321 email limits so the client honors the + // srfm_email_field_char_limits filter instead of hardcoding 64/255. + 'email_char_limits' => Field_Validation::get_email_char_limits(), ] ); diff --git a/tests/unit/inc/test-field-validation.php b/tests/unit/inc/test-field-validation.php index 868281b4e..2da33b7d2 100644 --- a/tests/unit/inc/test-field-validation.php +++ b/tests/unit/inc/test-field-validation.php @@ -442,4 +442,41 @@ public function test_process_payment_block_defaults() { $this->assertEquals( 10.0, $config['pay-default']['fixed_amount'] ); $this->assertEquals( 0.0, $config['pay-default']['minimum_amount'] ); } + + /** + * Test get_email_char_limits returns RFC 5321 defaults and honors the filter override. + * + * @since x.x.x + */ + public function test_get_email_char_limits() { + // Defaults when no filter is attached. + $this->assertSame( + [ + 'local' => 64, + 'domain' => 255, + ], + Field_Validation::get_email_char_limits(), + 'Should return the RFC 5321 defaults (64 local / 255 domain).' + ); + + // A filter override is reflected and run through absint. + $override = static function () { + return [ + 'local' => 128, + 'domain' => 320, + ]; + }; + add_filter( 'srfm_email_field_char_limits', $override ); + $limits = Field_Validation::get_email_char_limits(); + remove_filter( 'srfm_email_field_char_limits', $override ); + + $this->assertSame( + [ + 'local' => 128, + 'domain' => 320, + ], + $limits, + 'A srfm_email_field_char_limits override should be reflected in the resolved limits.' + ); + } } From 8bdc1c24035f79a216a8e532985777f6bdc3896a Mon Sep 17 00:00:00 2001 From: Avinash Kumar Sharma Date: Wed, 1 Jul 2026 09:40:40 +0530 Subject: [PATCH 24/35] fix: address code review comments for Quill 1.x list marker CSS - Add missing per-
  • base padding-left/padding-right (canonical Quill 1.3.7) to mixins.scss, backend.scss, form.scss, and admin.php - Add RTL indent rules (indent-1 through indent-9) to confirmation message in form.scss, which renders outside .ql-editor with no vendor-stylesheet fallback - Extract @mixin srfm-quill-1x-list-markers in mixins.scss and replace four copies of the ~30-line counter block with a single @include - Hoist the two identical wp_add_inline_style CSS strings in admin.php into a shared Admin::QUILL_1X_INLINE_CSS class constant Co-Authored-By: Claude Sonnet 4.6 --- admin/admin.php | 27 +++-- sass/blocks/default/backend.scss | 110 +------------------- sass/blocks/default/utils/mixins.scss | 143 +++++++++++++++++++++++++- sass/frontend/form.scss | 134 +++++------------------- 4 files changed, 178 insertions(+), 236 deletions(-) diff --git a/admin/admin.php b/admin/admin.php index a9e8f1a28..5011fb0ce 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -40,6 +40,17 @@ class Admin { */ public const RATING_NOTICE_THRESHOLD = 3; + /** + * Inline CSS for Quill 1.x (react-quill) list markers. + * + * Quill 1.x renders bullet/numbered list markers via CSS ::before pseudo-elements, + * whereas the vendor quill.snow.css targets .ql-ui child elements (Quill 2.x approach). + * This constant is shared by enqueue_styles() and enqueue_scripts() to prevent drift. + * + * @since 2.5.2 + */ + public const QUILL_1X_INLINE_CSS = '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ol li:not(.ql-direction-rtl),.ql-editor ul li:not(.ql-direction-rtl){padding-left:1.5em}.ql-editor ol li.ql-direction-rtl,.ql-editor ul li.ql-direction-rtl{padding-right:1.5em}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}.ql-editor ol li{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;counter-increment:list-0}.ql-editor ol li::before{content:counter(list-0,decimal) ". "}.ql-editor ol li.ql-indent-1{counter-increment:list-1;counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-1::before{content:counter(list-1,lower-alpha) ". "}.ql-editor ol li.ql-indent-2{counter-increment:list-2;counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-2::before{content:counter(list-2,lower-roman) ". "}.ql-editor ol li.ql-indent-3{counter-increment:list-3;counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-3::before{content:counter(list-3,decimal) ". "}.ql-editor ol li.ql-indent-4{counter-increment:list-4;counter-reset:list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-4::before{content:counter(list-4,lower-alpha) ". "}.ql-editor ol li.ql-indent-5{counter-increment:list-5;counter-reset:list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-5::before{content:counter(list-5,lower-roman) ". "}.ql-editor ol li.ql-indent-6{counter-increment:list-6;counter-reset:list-7 list-8 list-9}.ql-editor ol li.ql-indent-6::before{content:counter(list-6,decimal) ". "}.ql-editor ol li.ql-indent-7{counter-increment:list-7;counter-reset:list-8 list-9}.ql-editor ol li.ql-indent-7::before{content:counter(list-7,lower-alpha) ". "}.ql-editor ol li.ql-indent-8{counter-increment:list-8;counter-reset:list-9}.ql-editor ol li.ql-indent-8::before{content:counter(list-8,lower-roman) ". "}.ql-editor ol li.ql-indent-9{counter-increment:list-9}.ql-editor ol li.ql-indent-9::before{content:counter(list-9,decimal) ". "}'; + /** * Dashboard widget entries data. * @@ -894,13 +905,7 @@ public function enqueue_styles() { wp_enqueue_style( SRFM_SLUG . '-intl', $vendor_css_uri . 'intl/intlTelInput-backend.min.css', [], SRFM_VER ); wp_enqueue_style( SRFM_SLUG . '-common', $css_uri . 'common' . $file_prefix . '.css', [], SRFM_VER ); wp_enqueue_style( SRFM_SLUG . '-reactQuill', $vendor_css_uri . 'quill/quill.snow.css', [], SRFM_VER ); - // Quill 1.x (react-quill) renders bullet list markers via CSS ::before pseudo-elements, - // whereas the vendor quill.snow.css targets .ql-ui child elements (Quill 2.x approach). - // These rules restore bullet-point visibility when react-quill is used in the admin editor. - wp_add_inline_style( - SRFM_SLUG . '-reactQuill', - '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}.ql-editor ol li{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;counter-increment:list-0}.ql-editor ol li::before{content:counter(list-0,decimal) ". "}.ql-editor ol li.ql-indent-1{counter-increment:list-1;counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-1::before{content:counter(list-1,lower-alpha) ". "}.ql-editor ol li.ql-indent-2{counter-increment:list-2;counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-2::before{content:counter(list-2,lower-roman) ". "}.ql-editor ol li.ql-indent-3{counter-increment:list-3;counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-3::before{content:counter(list-3,decimal) ". "}.ql-editor ol li.ql-indent-4{counter-increment:list-4;counter-reset:list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-4::before{content:counter(list-4,lower-alpha) ". "}.ql-editor ol li.ql-indent-5{counter-increment:list-5;counter-reset:list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-5::before{content:counter(list-5,lower-roman) ". "}.ql-editor ol li.ql-indent-6{counter-increment:list-6;counter-reset:list-7 list-8 list-9}.ql-editor ol li.ql-indent-6::before{content:counter(list-6,decimal) ". "}.ql-editor ol li.ql-indent-7{counter-increment:list-7;counter-reset:list-8 list-9}.ql-editor ol li.ql-indent-7::before{content:counter(list-7,lower-alpha) ". "}.ql-editor ol li.ql-indent-8{counter-increment:list-8;counter-reset:list-9}.ql-editor ol li.ql-indent-8::before{content:counter(list-8,lower-roman) ". "}.ql-editor ol li.ql-indent-9{counter-increment:list-9}.ql-editor ol li.ql-indent-9::before{content:counter(list-9,decimal) ". "}' - ); + wp_add_inline_style( SRFM_SLUG . '-reactQuill', self::QUILL_1X_INLINE_CSS ); wp_enqueue_style( SRFM_SLUG . '-single-form-modal', $css_uri . 'single-form-setting' . $file_prefix . '.css', [], SRFM_VER ); // if version is equal to or lower than 6.6.2 then add compatibility css. @@ -1341,13 +1346,7 @@ public function enqueue_scripts() { // Enqueue Tailwind and Quill editor styles for the settings page. wp_enqueue_style( SRFM_SLUG . '-settings-build', SRFM_URL . 'assets/build/settings.css', [], SRFM_VER, 'all' ); wp_enqueue_style( SRFM_SLUG . '-reactQuill', SRFM_URL . 'assets/css/minified/deps/quill/quill.snow.css', [], SRFM_VER ); - // Quill 1.x (react-quill) renders bullet list markers via CSS ::before pseudo-elements, - // whereas the vendor quill.snow.css targets .ql-ui child elements (Quill 2.x approach). - // These rules restore bullet-point visibility when react-quill is used in the admin editor. - wp_add_inline_style( - SRFM_SLUG . '-reactQuill', - '.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor ul>li,.ql-editor ol>li{list-style-type:none}.ql-editor ul>li::before{content:"\2022"}.ql-editor li::before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl)::before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl::before{margin-left:.3em;margin-right:-1.5em}.ql-editor ol li{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;counter-increment:list-0}.ql-editor ol li::before{content:counter(list-0,decimal) ". "}.ql-editor ol li.ql-indent-1{counter-increment:list-1;counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-1::before{content:counter(list-1,lower-alpha) ". "}.ql-editor ol li.ql-indent-2{counter-increment:list-2;counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-2::before{content:counter(list-2,lower-roman) ". "}.ql-editor ol li.ql-indent-3{counter-increment:list-3;counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-3::before{content:counter(list-3,decimal) ". "}.ql-editor ol li.ql-indent-4{counter-increment:list-4;counter-reset:list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-4::before{content:counter(list-4,lower-alpha) ". "}.ql-editor ol li.ql-indent-5{counter-increment:list-5;counter-reset:list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-5::before{content:counter(list-5,lower-roman) ". "}.ql-editor ol li.ql-indent-6{counter-increment:list-6;counter-reset:list-7 list-8 list-9}.ql-editor ol li.ql-indent-6::before{content:counter(list-6,decimal) ". "}.ql-editor ol li.ql-indent-7{counter-increment:list-7;counter-reset:list-8 list-9}.ql-editor ol li.ql-indent-7::before{content:counter(list-7,lower-alpha) ". "}.ql-editor ol li.ql-indent-8{counter-increment:list-8;counter-reset:list-9}.ql-editor ol li.ql-indent-8::before{content:counter(list-8,lower-roman) ". "}.ql-editor ol li.ql-indent-9{counter-increment:list-9}.ql-editor ol li.ql-indent-9::before{content:counter(list-9,decimal) ". "}' - ); + wp_add_inline_style( SRFM_SLUG . '-reactQuill', self::QUILL_1X_INLINE_CSS ); $script_translations_handlers[] = SRFM_SLUG . '-settings'; } diff --git a/sass/blocks/default/backend.scss b/sass/blocks/default/backend.scss index a85c1bc4f..634bcfa15 100644 --- a/sass/blocks/default/backend.scss +++ b/sass/blocks/default/backend.scss @@ -89,115 +89,7 @@ .ql-editor { min-height: 200px; - ul > li::before { - content: '\2022'; - } - - li::before { - display: inline-block; - white-space: nowrap; - width: 1.2em; - } - - li:not( .ql-direction-rtl )::before { - margin-left: -1.5em; - margin-right: 0.3em; - text-align: right; - } - - li.ql-direction-rtl::before { - margin-left: 0.3em; - margin-right: -1.5em; - } - - ol li { - counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; - counter-increment: list-0; - } - - ol li::before { - content: counter( list-0, decimal ) '. '; - } - - ol li.ql-indent-1 { - counter-increment: list-1; - counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-1::before { - content: counter( list-1, lower-alpha ) '. '; - } - - ol li.ql-indent-2 { - counter-increment: list-2; - counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-2::before { - content: counter( list-2, lower-roman ) '. '; - } - - ol li.ql-indent-3 { - counter-increment: list-3; - counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-3::before { - content: counter( list-3, decimal ) '. '; - } - - ol li.ql-indent-4 { - counter-increment: list-4; - counter-reset: list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-4::before { - content: counter( list-4, lower-alpha ) '. '; - } - - ol li.ql-indent-5 { - counter-increment: list-5; - counter-reset: list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-5::before { - content: counter( list-5, lower-roman ) '. '; - } - - ol li.ql-indent-6 { - counter-increment: list-6; - counter-reset: list-7 list-8 list-9; - } - - ol li.ql-indent-6::before { - content: counter( list-6, decimal ) '. '; - } - - ol li.ql-indent-7 { - counter-increment: list-7; - counter-reset: list-8 list-9; - } - - ol li.ql-indent-7::before { - content: counter( list-7, lower-alpha ) '. '; - } - - ol li.ql-indent-8 { - counter-increment: list-8; - counter-reset: list-9; - } - - ol li.ql-indent-8::before { - content: counter( list-8, lower-roman ) '. '; - } - - ol li.ql-indent-9 { - counter-increment: list-9; - } - - ol li.ql-indent-9::before { - content: counter( list-9, decimal ) '. '; - } + @include srfm-quill-1x-list-markers; } // Add the background color for the quill textarea input. diff --git a/sass/blocks/default/utils/mixins.scss b/sass/blocks/default/utils/mixins.scss index 2ea292058..13324a46f 100644 --- a/sass/blocks/default/utils/mixins.scss +++ b/sass/blocks/default/utils/mixins.scss @@ -450,21 +450,158 @@ } } // Override any theme CSS reset that may strip padding/markers from list elements - // inside the Quill editor. The Quill 1.x vs 2.x list-marker rules are NOT included - // here because they differ per context — see backend.scss (.srfm-textarea-quill) for - // the Quill 1.x rules used in the block-editor canvas (react-quill). + // inside the Quill editor. Use @include srfm-quill-1x-list-markers inside the + // consuming .ql-editor block for the full Quill 1.x ::before marker rule set. .ql-editor { ol, ul { padding-left: 1.5em; } + // Per-
  • base padding — canonical Quill 1.3.7. + ol li:not( .ql-direction-rtl ), + ul li:not( .ql-direction-rtl ) { + padding-left: 1.5em; + } + + ol li.ql-direction-rtl, + ul li.ql-direction-rtl { + padding-right: 1.5em; + } + ol > li, ul > li { list-style-type: none; } } } + +// Quill 1.x list-marker rules (::before pseudo-element approach used by react-quill). +// Include this mixin inside a .ql-editor block (admin/backend) or directly inside the +// confirmation-message container (form.scss) where no .ql-editor wrapper is present. +@mixin srfm-quill-1x-list-markers { + // Per-
  • base padding — canonical Quill 1.3.7, separate from the container ol/ul padding. + ol li:not( .ql-direction-rtl ), + ul li:not( .ql-direction-rtl ) { + padding-left: 1.5em; + } + + ol li.ql-direction-rtl, + ul li.ql-direction-rtl { + padding-right: 1.5em; + } + + ul > li::before { + content: '\2022'; + } + + li::before { + display: inline-block; + white-space: nowrap; + width: 1.2em; + } + + li:not( .ql-direction-rtl )::before { + margin-left: -1.5em; + margin-right: 0.3em; + text-align: right; + } + + li.ql-direction-rtl::before { + margin-left: 0.3em; + margin-right: -1.5em; + } + + ol li { + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + counter-increment: list-0; + } + + ol li::before { + content: counter( list-0, decimal ) '. '; + } + + ol li.ql-indent-1 { + counter-increment: list-1; + counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-1::before { + content: counter( list-1, lower-alpha ) '. '; + } + + ol li.ql-indent-2 { + counter-increment: list-2; + counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-2::before { + content: counter( list-2, lower-roman ) '. '; + } + + ol li.ql-indent-3 { + counter-increment: list-3; + counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-3::before { + content: counter( list-3, decimal ) '. '; + } + + ol li.ql-indent-4 { + counter-increment: list-4; + counter-reset: list-5 list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-4::before { + content: counter( list-4, lower-alpha ) '. '; + } + + ol li.ql-indent-5 { + counter-increment: list-5; + counter-reset: list-6 list-7 list-8 list-9; + } + + ol li.ql-indent-5::before { + content: counter( list-5, lower-roman ) '. '; + } + + ol li.ql-indent-6 { + counter-increment: list-6; + counter-reset: list-7 list-8 list-9; + } + + ol li.ql-indent-6::before { + content: counter( list-6, decimal ) '. '; + } + + ol li.ql-indent-7 { + counter-increment: list-7; + counter-reset: list-8 list-9; + } + + ol li.ql-indent-7::before { + content: counter( list-7, lower-alpha ) '. '; + } + + ol li.ql-indent-8 { + counter-increment: list-8; + counter-reset: list-9; + } + + ol li.ql-indent-8::before { + content: counter( list-8, lower-roman ) '. '; + } + + ol li.ql-indent-9 { + counter-increment: list-9; + } + + ol li.ql-indent-9::before { + content: counter( list-9, decimal ) '. '; + } +} + // Row Gap and Column Gap Mixin. @mixin srfm-gap-styles { row-gap: var( --srfm-row-gap-between-blocks ); diff --git a/sass/frontend/form.scss b/sass/frontend/form.scss index 9d3185acb..741dc95f8 100644 --- a/sass/frontend/form.scss +++ b/sass/frontend/form.scss @@ -284,115 +284,7 @@ list-style-type: none; } - ul > li::before { - content: '\2022'; - } - - li::before { - display: inline-block; - white-space: nowrap; - width: 1.2em; - } - - li:not( .ql-direction-rtl )::before { - margin-left: -1.5em; - margin-right: 0.3em; - text-align: right; - } - - li.ql-direction-rtl::before { - margin-left: 0.3em; - margin-right: -1.5em; - } - - ol li { - counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; - counter-increment: list-0; - } - - ol li::before { - content: counter( list-0, decimal ) '. '; - } - - ol li.ql-indent-1 { - counter-increment: list-1; - counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-1::before { - content: counter( list-1, lower-alpha ) '. '; - } - - ol li.ql-indent-2 { - counter-increment: list-2; - counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-2::before { - content: counter( list-2, lower-roman ) '. '; - } - - ol li.ql-indent-3 { - counter-increment: list-3; - counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-3::before { - content: counter( list-3, decimal ) '. '; - } - - ol li.ql-indent-4 { - counter-increment: list-4; - counter-reset: list-5 list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-4::before { - content: counter( list-4, lower-alpha ) '. '; - } - - ol li.ql-indent-5 { - counter-increment: list-5; - counter-reset: list-6 list-7 list-8 list-9; - } - - ol li.ql-indent-5::before { - content: counter( list-5, lower-roman ) '. '; - } - - ol li.ql-indent-6 { - counter-increment: list-6; - counter-reset: list-7 list-8 list-9; - } - - ol li.ql-indent-6::before { - content: counter( list-6, decimal ) '. '; - } - - ol li.ql-indent-7 { - counter-increment: list-7; - counter-reset: list-8 list-9; - } - - ol li.ql-indent-7::before { - content: counter( list-7, lower-alpha ) '. '; - } - - ol li.ql-indent-8 { - counter-increment: list-8; - counter-reset: list-9; - } - - ol li.ql-indent-8::before { - content: counter( list-8, lower-roman ) '. '; - } - - ol li.ql-indent-9 { - counter-increment: list-9; - } - - ol li.ql-indent-9::before { - content: counter( list-9, decimal ) '. '; - } + @include srfm-quill-1x-list-markers; // Font sizes. .ql-size-small { font-size: 0.75em; } @@ -406,7 +298,7 @@ // RTL. .ql-direction-rtl { direction: rtl; text-align: inherit; } - // Indentation (3 em per level; list items add 1.5 em for the marker gutter). + // LTR indentation (3 em per level; list items add 1.5 em for the marker gutter). .ql-indent-1:not( .ql-direction-rtl ) { padding-left: 3em; } li.ql-indent-1:not( .ql-direction-rtl ) { padding-left: 4.5em; } .ql-indent-2:not( .ql-direction-rtl ) { padding-left: 6em; } @@ -425,6 +317,28 @@ li.ql-indent-8:not( .ql-direction-rtl ) { padding-left: 25.5em; } .ql-indent-9:not( .ql-direction-rtl ) { padding-left: 27em; } li.ql-indent-9:not( .ql-direction-rtl ) { padding-left: 28.5em; } + + // RTL indentation — canonical Quill 1.3.7 mirror of the LTR rules above. + // Required here because the confirmation message renders outside .ql-editor + // and has no vendor-stylesheet fallback. + .ql-indent-1.ql-direction-rtl.ql-align-right { padding-right: 3em; } + li.ql-indent-1.ql-direction-rtl.ql-align-right { padding-right: 4.5em; } + .ql-indent-2.ql-direction-rtl.ql-align-right { padding-right: 6em; } + li.ql-indent-2.ql-direction-rtl.ql-align-right { padding-right: 7.5em; } + .ql-indent-3.ql-direction-rtl.ql-align-right { padding-right: 9em; } + li.ql-indent-3.ql-direction-rtl.ql-align-right { padding-right: 10.5em; } + .ql-indent-4.ql-direction-rtl.ql-align-right { padding-right: 12em; } + li.ql-indent-4.ql-direction-rtl.ql-align-right { padding-right: 13.5em; } + .ql-indent-5.ql-direction-rtl.ql-align-right { padding-right: 15em; } + li.ql-indent-5.ql-direction-rtl.ql-align-right { padding-right: 16.5em; } + .ql-indent-6.ql-direction-rtl.ql-align-right { padding-right: 18em; } + li.ql-indent-6.ql-direction-rtl.ql-align-right { padding-right: 19.5em; } + .ql-indent-7.ql-direction-rtl.ql-align-right { padding-right: 21em; } + li.ql-indent-7.ql-direction-rtl.ql-align-right { padding-right: 22.5em; } + .ql-indent-8.ql-direction-rtl.ql-align-right { padding-right: 24em; } + li.ql-indent-8.ql-direction-rtl.ql-align-right { padding-right: 25.5em; } + .ql-indent-9.ql-direction-rtl.ql-align-right { padding-right: 27em; } + li.ql-indent-9.ql-direction-rtl.ql-align-right { padding-right: 28.5em; } } } From afb23590d6631c67e6c6402b7245fe90c9d02b94 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Sat, 4 Jul 2026 19:46:58 +0530 Subject: [PATCH 25/35] test(admin): add enqueue_styles coverage for Quill 1.x inline CSS Covers the new wp_add_inline_style( ..., QUILL_1X_INLINE_CSS ) wiring in Admin::enqueue_styles(), mirroring the existing test_enqueue_scripts() reflection-based check. Satisfies the check-test-coverage gate. --- tests/unit/admin/test-admin.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/unit/admin/test-admin.php b/tests/unit/admin/test-admin.php index d5a31ba39..12aa887eb 100644 --- a/tests/unit/admin/test-admin.php +++ b/tests/unit/admin/test-admin.php @@ -116,6 +116,33 @@ public function test_enqueue_scripts() { $this->assertStringContainsString( 'current_user_login', $source, 'Localization data should include current_user_login.' ); } + /** + * Test that enqueue_styles exists and attaches the Quill 1.x inline list-marker CSS. + */ + public function test_enqueue_styles() { + // Verify the enqueue_styles method exists on the Admin class. + $this->assertTrue( + method_exists( Admin::class, 'enqueue_styles' ), + 'The enqueue_styles method should exist on the Admin class.' + ); + + // Verify it is a public instance method. + $reflection = new \ReflectionMethod( Admin::class, 'enqueue_styles' ); + $this->assertTrue( $reflection->isPublic(), 'enqueue_styles should be a public method.' ); + $this->assertFalse( $reflection->isStatic(), 'enqueue_styles should be an instance method, not static.' ); + + // Verify the Quill 1.x inline list-marker CSS is attached to the reactQuill handle by + // inspecting the source. Reading the method body confirms the contract is maintained + // without booting a full WP style registry in the unit environment. + $source_file = $reflection->getFileName(); + $start_line = $reflection->getStartLine(); + $end_line = $reflection->getEndLine(); + $source = implode( '', array_slice( file( $source_file ), $start_line - 1, $end_line - $start_line + 1 ) ); + + $this->assertStringContainsString( 'wp_add_inline_style', $source, 'enqueue_styles should attach inline styles.' ); + $this->assertStringContainsString( 'QUILL_1X_INLINE_CSS', $source, 'enqueue_styles should attach the shared Quill 1.x list-marker CSS.' ); + } + /** * Test add_learn_page registers the Learn submenu page. */ From df9507f56ba377bbcd46fef948c911e0efd5bf56 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Mon, 6 Jul 2026 08:10:17 +0530 Subject: [PATCH 26/35] Version Bump 2.12.1 --- README.md | 12 +- admin/admin.php | 8 +- inc/field-validation.php | 4 +- languages/sureforms.pot | 515 +++++++++++++---------- package-lock.json | 4 +- package.json | 2 +- readme.txt | 12 +- sureforms.php | 6 +- tests/unit/admin/test-admin.php | 12 +- tests/unit/inc/test-field-validation.php | 2 +- 10 files changed, 327 insertions(+), 250 deletions(-) diff --git a/README.md b/README.md index 374c99865..3fc2f9dd8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **Requires at least:** 6.4 **Tested up to:** 7.0 **Requires PHP:** 7.4 -**Stable tag:** 2.12.0 +**Stable tag:** 2.12.1 **License:** GPLv2 or later **License URI:** http://www.gnu.org/licenses/gpl-2.0.html @@ -458,6 +458,13 @@ Yes. SureForms Business includes fully functional user registration forms and lo You can report security issues through our [Bug Bounty Program](https://brainstormforce.com/bug-bounty-program/). We collaborate with Patchstack to provide opportunities for researchers to report vulnerabilities. The Patchstack team will help validate, triage, and handle any reported security issues. ## Changelog ## +### 2.12.1 - 6th July 2026 ### +* New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. +* New: Added the ability to manually retry failed webhooks and native integrations, with retry details now surfaced in the logs. +* Fix: Reset Form now resets the Dropdown field and triggers a reset for Pro fields so every field returns to its default state. +* Fix: Resolved a Stripe one-time payment error that could cause card payments to fail with an HTTP 400 response. +* Fix: Restored bullet point visibility in the rich text admin editor so formatted lists display correctly. +* Fix: The Payment block Email and Name field mappers now include child fields from User Registration container blocks. ### 2.12.0 - 24th June 2026 ### * New: Added action hooks around payment success, cancellation, and refund events so plugins such as SureMembers, LMS, and CRMs can grant or revoke access for both Stripe and PayPal. * Fix: Cancel Subscription now routes through the correct payment gateway so PayPal subscriptions cancel properly instead of always calling Stripe. @@ -468,9 +475,6 @@ You can report security issues through our [Bug Bounty Program](https://brainsto * Fix: Phone field auto country detection always resolved to the United States. * Fix: Corrected the Cloudflare Turnstile "Get Keys" link. * Fix: This update addressed a security bug. Props to Yaswanth Reddy Sunkara for reporting it responsibly to our team. -### 2.11.0 - 10th June 2026 ### -* New: Added a Form Migrator to import forms from Contact Form 7, WPForms, Gravity Forms, and Ninja Forms in a single click. -* New: Added native WPML support to translate each form individually using String Packages. The full changelog is available [here](https://sureforms.com/whats-new/?utm_source=wordpress.org&utm_medium=whats_new). ## Upgrade Notice ## diff --git a/admin/admin.php b/admin/admin.php index 5b5e113b6..09c7a1a7e 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -1980,7 +1980,7 @@ public function register_dashboard_widget() { * Register the AI quick draft dashboard widget. * * @return void - * @since x.x.x + * @since 2.12.1 */ public function register_ai_dashboard_widget() { wp_add_dashboard_widget( @@ -1998,7 +1998,7 @@ public function register_ai_dashboard_widget() { * Render AI quick draft dashboard widget content. * * @return void - * @since x.x.x + * @since 2.12.1 */ public function render_ai_dashboard_widget() { ?> @@ -2035,7 +2035,7 @@ class="widefat" * * @param string $hook_suffix The current admin page hook suffix. * @return void - * @since x.x.x + * @since 2.12.1 */ public function enqueue_ai_dashboard_widget_assets( $hook_suffix ) { // Only on the main dashboard, and only for capable users (matches the widget gate). @@ -2126,7 +2126,7 @@ public function enqueue_ai_dashboard_widget_assets( $hook_suffix ) { * Track AI dashboard widget usage. * * @return void - * @since x.x.x + * @since 2.12.1 */ public function track_ai_widget_usage() { if ( ! check_ajax_referer( 'srfm_ai_widget_usage', 'nonce', false ) ) { diff --git a/inc/field-validation.php b/inc/field-validation.php index af4595e10..8e4c3e77d 100644 --- a/inc/field-validation.php +++ b/inc/field-validation.php @@ -337,7 +337,7 @@ public static function validate_form_data( $form_data, $current_form_id ) { * frontend script, so a filter override applies consistently to both. * * @return array{local:int,domain:int} Resolved limits. A value of 0 disables that check. - * @since x.x.x + * @since 2.12.1 */ public static function get_email_char_limits() { /** @@ -349,7 +349,7 @@ public static function get_email_char_limits() { * @type int $local Max characters before the @. 0 disables the check. Default 64. * @type int $domain Max characters after the @. 0 disables the check. Default 255. * } - * @since x.x.x + * @since 2.12.1 */ $email_limits = apply_filters( 'srfm_email_field_char_limits', diff --git a/languages/sureforms.pot b/languages/sureforms.pot index 7891e6890..95c23fd6d 100644 --- a/languages/sureforms.pot +++ b/languages/sureforms.pot @@ -2,14 +2,14 @@ # This file is distributed under the GPLv2 or later. msgid "" msgstr "" -"Project-Id-Version: SureForms 2.12.0\n" +"Project-Id-Version: SureForms 2.12.1\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/sureforms\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-06-24T06:55:32+00:00\n" +"POT-Creation-Date: 2026-07-06T02:38:45+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: sureforms\n" @@ -17,9 +17,9 @@ msgstr "" #. Plugin Name of the plugin #. Author of the plugin #: sureforms.php -#: admin/admin.php:391 -#: admin/admin.php:392 -#: admin/admin.php:1950 +#: admin/admin.php:406 +#: admin/admin.php:407 +#: admin/admin.php:1970 #: inc/abilities/abilities-registrar.php:139 #: inc/gutenberg-hooks.php:109 #: inc/page-builders/bricks/elements/form-widget.php:67 @@ -46,12 +46,12 @@ msgstr "" msgid "https://sureforms.com/" msgstr "" -#: admin/admin.php:314 +#: admin/admin.php:329 msgid "Get SureForms Pro" msgstr "" -#: admin/admin.php:404 -#: admin/admin.php:405 +#: admin/admin.php:419 +#: admin/admin.php:420 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -66,9 +66,9 @@ msgstr "" msgid "Dashboard" msgstr "" -#: admin/admin.php:422 -#: admin/admin.php:423 -#: admin/admin.php:855 +#: admin/admin.php:437 +#: admin/admin.php:438 +#: admin/admin.php:870 #: inc/compatibility/multilingual/string-collector.php:148 #: inc/compatibility/multilingual/string-collector.php:216 #: assets/build/blocks.js:172 @@ -85,9 +85,9 @@ msgstr "" msgid "Settings" msgstr "" -#: admin/admin.php:475 -#: admin/admin.php:476 -#: admin/admin.php:2211 +#: admin/admin.php:490 +#: admin/admin.php:491 +#: admin/admin.php:2403 #: assets/build/blocks.js:172 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -95,42 +95,42 @@ msgstr "" msgid "Upgrade" msgstr "" -#: admin/admin.php:491 +#: admin/admin.php:506 msgid "Quiz Entries" msgstr "" -#: admin/admin.php:492 -#: admin/admin.php:1085 +#: admin/admin.php:507 +#: admin/admin.php:1101 #: assets/build/formEditor.js:172 msgid "Quizzes" msgstr "" -#: admin/admin.php:494 -#: admin/admin.php:527 -#: admin/admin.php:560 +#: admin/admin.php:509 +#: admin/admin.php:542 +#: admin/admin.php:575 #: assets/build/settings.js:172 msgid "New" msgstr "" -#: admin/admin.php:524 -#: admin/admin.php:525 -#: admin/admin.php:1090 +#: admin/admin.php:539 +#: admin/admin.php:540 +#: admin/admin.php:1106 msgid "Survey Reports" msgstr "" -#: admin/admin.php:557 -#: admin/admin.php:558 -#: admin/admin.php:1095 +#: admin/admin.php:572 +#: admin/admin.php:573 +#: admin/admin.php:1111 msgid "Partial Entries" msgstr "" -#: admin/admin.php:590 -#: admin/admin.php:591 +#: admin/admin.php:605 +#: admin/admin.php:606 msgid "SMTP" msgstr "" -#: admin/admin.php:653 -#: admin/admin.php:654 +#: admin/admin.php:668 +#: admin/admin.php:669 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -144,8 +144,8 @@ msgstr "" msgid "Learn" msgstr "" -#: admin/admin.php:682 -#: admin/admin.php:683 +#: admin/admin.php:697 +#: admin/admin.php:698 #: inc/post-types.php:235 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -160,15 +160,15 @@ msgstr "" msgid "Forms" msgstr "" -#: admin/admin.php:691 -#: admin/admin.php:692 +#: admin/admin.php:706 +#: admin/admin.php:707 #: inc/post-types.php:231 msgid "New Form" msgstr "" -#: admin/admin.php:700 -#: admin/admin.php:701 -#: admin/admin.php:1987 +#: admin/admin.php:715 +#: admin/admin.php:716 +#: admin/admin.php:2179 #: inc/global-settings/email-summary.php:225 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -183,8 +183,8 @@ msgstr "" msgid "Entries" msgstr "" -#: admin/admin.php:710 -#: admin/admin.php:711 +#: admin/admin.php:725 +#: admin/admin.php:726 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -199,16 +199,16 @@ msgid "Payments" msgstr "" #. Translators: Post Title. -#: admin/admin.php:942 +#: admin/admin.php:958 #, php-format msgid "Edit %1$s" msgstr "" -#: admin/admin.php:1014 +#: admin/admin.php:1030 msgid "Activating..." msgstr "" -#: admin/admin.php:1015 +#: admin/admin.php:1031 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/formEditor.js:172 @@ -223,83 +223,84 @@ msgstr "" msgid "Activated" msgstr "" -#: admin/admin.php:1016 +#: admin/admin.php:1032 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Activate" msgstr "" -#: admin/admin.php:1017 +#: admin/admin.php:1033 msgid "Installing..." msgstr "" -#: admin/admin.php:1018 +#: admin/admin.php:1034 msgid "Installed" msgstr "" #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version. -#: admin/admin.php:1514 +#: admin/admin.php:1531 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version." msgstr "" -#: admin/admin.php:1527 +#: admin/admin.php:1544 msgid "Update Now" msgstr "" #. translators: %1$s: Opening anchor tag with URL, %2$s: Closing anchor tag, %3$s: SureForms Pro Plugin Name. -#: admin/admin.php:1579 +#: admin/admin.php:1596 #, php-format msgid "Please %1$sactivate%2$s your copy of %3$s to get new features, access support, receive update notifications, and more." msgstr "" #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version, %4$s: Anchor tag open, %5$s: Closing anchor tag. -#: admin/admin.php:1597 +#: admin/admin.php:1614 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version from %4$shere%5$s." msgstr "" -#: admin/admin.php:1640 +#: admin/admin.php:1657 msgid "Amazing! SureForms is powering your forms and submissions - let's keep growing together!" msgstr "" -#: admin/admin.php:1641 +#: admin/admin.php:1658 msgid "If SureForms has been helpful, would you mind taking a moment to leave a 5-star review on WordPress.org?" msgstr "" -#: admin/admin.php:1643 +#: admin/admin.php:1660 msgid "Rate SureForms" msgstr "" -#: admin/admin.php:1644 -#: admin/admin.php:1688 +#: admin/admin.php:1661 +#: admin/admin.php:1705 msgid "Maybe later" msgstr "" -#: admin/admin.php:1645 +#: admin/admin.php:1662 msgid "I already did" msgstr "" -#: admin/admin.php:1684 +#: admin/admin.php:1701 msgid "SureForms is ready to power your forms — explore what's possible!" msgstr "" -#: admin/admin.php:1685 +#: admin/admin.php:1702 msgid "Manage your forms, track submissions, and discover features like AI Form Builder, payment integrations, and more from the SureForms dashboard." msgstr "" -#: admin/admin.php:1687 +#: admin/admin.php:1704 msgid "Go to Dashboard" msgstr "" -#: admin/admin.php:1689 +#: admin/admin.php:1706 msgid "I already know" msgstr "" -#: admin/admin.php:1745 -#: admin/admin.php:1844 -#: admin/admin.php:1882 -#: admin/admin.php:1903 +#: admin/admin.php:1762 +#: admin/admin.php:1861 +#: admin/admin.php:1899 +#: admin/admin.php:1920 +#: admin/admin.php:2133 #: inc/admin-ajax.php:162 #: inc/payments/admin/admin-handler.php:638 #: inc/payments/stripe/admin-stripe-handler.php:80 @@ -307,91 +308,118 @@ msgstr "" msgid "Invalid nonce." msgstr "" -#: admin/admin.php:1749 -#: admin/admin.php:1840 -#: admin/admin.php:1878 -#: admin/admin.php:1899 +#: admin/admin.php:1766 +#: admin/admin.php:1857 +#: admin/admin.php:1895 +#: admin/admin.php:1916 +#: admin/admin.php:2137 msgid "Unauthorized user." msgstr "" -#: admin/admin.php:1769 +#: admin/admin.php:1786 msgid "Invalid parameters." msgstr "" #. translators: 1: opening span, 2: opening strong (inline), 3: closing strong, 4: closing span, 5: opening strong (block), 6: closing strong -#: admin/admin.php:1849 +#: admin/admin.php:1866 #, php-format msgid "%1$sGet started by %2$sbuilding your first form%3$s.%4$s%5$sExperience the power of our intuitive AI Form Builder%6$s" msgstr "" -#: admin/admin.php:1860 +#: admin/admin.php:1877 msgid "SureForms is waiting for you!" msgstr "" -#: admin/admin.php:1862 +#: admin/admin.php:1879 msgid "Build My First Form" msgstr "" -#: admin/admin.php:1863 +#: admin/admin.php:1880 msgid "Dismiss" msgstr "" -#: admin/admin.php:1974 +#: admin/admin.php:1988 +msgid "SureForms AI Quick Draft" +msgstr "" + +#: admin/admin.php:2007 +msgid "Describe the form and let SureForms AI generate it for you." +msgstr "" + +#: admin/admin.php:2010 +msgid "Describe your form" +msgstr "" + +#: admin/admin.php:2017 +msgid "Example: Create a contact form with name, email, phone, and message fields." +msgstr "" + +#: admin/admin.php:2021 +#: inc/page-builders/elementor/form-widget.php:346 +#: assets/build/dashboard.js:2 +msgid "Create New Form" +msgstr "" + +#: admin/admin.php:2058 +msgid "Redirecting..." +msgstr "" + +#: admin/admin.php:2166 msgid "Recent Entries" msgstr "" -#: admin/admin.php:1975 +#: admin/admin.php:2167 msgid "( Last 7 days )" msgstr "" -#: admin/admin.php:1978 +#: admin/admin.php:2170 msgid "View" msgstr "" -#: admin/admin.php:1986 +#: admin/admin.php:2178 #: inc/global-settings/email-summary.php:224 #: assets/build/entries.js:172 #: assets/build/payments.js:172 msgid "Form Name" msgstr "" -#: admin/admin.php:2152 +#: admin/admin.php:2344 msgid "Use Conditional Logic to show only what matters" msgstr "" -#: admin/admin.php:2153 +#: admin/admin.php:2345 msgid "Split your form into steps to keep it easy" msgstr "" -#: admin/admin.php:2154 +#: admin/admin.php:2346 msgid "Let people upload files directly to your form" msgstr "" -#: admin/admin.php:2155 +#: admin/admin.php:2347 msgid "Turn responses into downloadable PDFs automatically" msgstr "" -#: admin/admin.php:2156 +#: admin/admin.php:2348 msgid "Let users sign with a simple signature field" msgstr "" -#: admin/admin.php:2157 +#: admin/admin.php:2349 msgid "Connect your form to other tools using webhooks" msgstr "" -#: admin/admin.php:2158 +#: admin/admin.php:2350 msgid "Use Conversational Forms for a chat-like experience" msgstr "" -#: admin/admin.php:2159 +#: admin/admin.php:2351 msgid "Let users register or log in through your form" msgstr "" -#: admin/admin.php:2160 +#: admin/admin.php:2352 msgid "Build forms that create WordPress user accounts" msgstr "" -#: admin/admin.php:2161 +#: admin/admin.php:2353 msgid "Add calculations to auto-total scores or prices" msgstr "" @@ -903,41 +931,41 @@ msgstr "" #: inc/abilities/settings/get-global-settings.php:192 #: inc/global-settings/email-summary.php:571 -#: inc/global-settings/global-settings.php:262 -#: inc/global-settings/global-settings.php:689 +#: inc/global-settings/global-settings.php:264 +#: inc/global-settings/global-settings.php:691 #: assets/build/settings.js:172 msgid "Monday" msgstr "" -#: inc/abilities/settings/update-global-settings.php:106 +#: inc/abilities/settings/update-global-settings.php:108 msgid "Update Global Settings" msgstr "" -#: inc/abilities/settings/update-global-settings.php:107 +#: inc/abilities/settings/update-global-settings.php:109 msgid "Update SureForms global settings for a specific category: general, validation-messages, email-summary, or security." msgstr "" -#: inc/abilities/settings/update-global-settings.php:140 +#: inc/abilities/settings/update-global-settings.php:142 msgid "The settings category to update." msgstr "" -#: inc/abilities/settings/update-global-settings.php:145 +#: inc/abilities/settings/update-global-settings.php:147 msgid "Key-value pairs of settings to update." msgstr "" -#: inc/abilities/settings/update-global-settings.php:181 +#: inc/abilities/settings/update-global-settings.php:183 msgid "Settings category is required." msgstr "" -#: inc/abilities/settings/update-global-settings.php:189 +#: inc/abilities/settings/update-global-settings.php:191 msgid "Settings data is required." msgstr "" -#: inc/abilities/settings/update-global-settings.php:200 +#: inc/abilities/settings/update-global-settings.php:202 msgid "No valid settings keys provided for this category." msgstr "" -#: inc/abilities/settings/update-global-settings.php:226 +#: inc/abilities/settings/update-global-settings.php:228 msgid "Invalid settings category." msgstr "" @@ -1098,7 +1126,7 @@ msgstr "" #: inc/form-submit.php:1015 #: inc/migrator/bootstrap.php:357 #: inc/payments/front-end.php:78 -#: inc/payments/front-end.php:263 +#: inc/payments/front-end.php:264 #: inc/rest-api.php:124 #: inc/rest-api.php:183 #: inc/rest-api.php:349 @@ -1111,8 +1139,8 @@ msgstr "" #: inc/rest-api.php:742 #: inc/rest-api.php:775 #: inc/rest-api.php:914 -#: inc/rest-api.php:987 -#: inc/rest-api.php:1047 +#: inc/rest-api.php:999 +#: inc/rest-api.php:1059 #: inc/single-form-settings/form-settings-api.php:89 msgid "Security verification failed. Please refresh the page and try again." msgstr "" @@ -1149,7 +1177,7 @@ msgstr "" #: inc/export.php:176 #: inc/forms-data.php:88 #: inc/global-settings/global-settings.php:88 -#: inc/global-settings/global-settings.php:630 +#: inc/global-settings/global-settings.php:632 #: inc/rest-api.php:203 msgid "Nonce verification failed." msgstr "" @@ -1190,21 +1218,29 @@ msgstr "" msgid "The message array was not supplied" msgstr "" -#: inc/ai-form-builder/ai-form-builder.php:68 -#: inc/ai-form-builder/ai-form-builder.php:87 +#: inc/ai-form-builder/ai-form-builder.php:57 +msgid "No prompt was supplied." +msgstr "" + +#: inc/ai-form-builder/ai-form-builder.php:64 +msgid "The prompt is too long. Please shorten it and try again." +msgstr "" + +#: inc/ai-form-builder/ai-form-builder.php:80 +#: inc/ai-form-builder/ai-form-builder.php:99 #: inc/ai-form-builder/ai-helper.php:294 msgid "The SureForms AI Middleware encountered an error." msgstr "" -#: inc/ai-form-builder/ai-form-builder.php:96 +#: inc/ai-form-builder/ai-form-builder.php:108 msgid "The AI did not return a form. Please refine your prompt and try again." msgstr "" -#: inc/ai-form-builder/ai-form-builder.php:104 +#: inc/ai-form-builder/ai-form-builder.php:116 msgid "The AI response is missing a form title. Please try again." msgstr "" -#: inc/ai-form-builder/ai-form-builder.php:115 +#: inc/ai-form-builder/ai-form-builder.php:127 #: inc/ai-form-builder/field-mapping.php:69 msgid "The AI was unable to generate form fields. Please try again." msgstr "" @@ -1654,6 +1690,20 @@ msgstr "" msgid "Please enter at least %s characters." msgstr "" +#. translators: %s: maximum characters allowed before the @ symbol. +#: inc/field-validation.php:316 +#: inc/translatable.php:97 +#, php-format +msgid "The part before @ may not exceed %s characters." +msgstr "" + +#. translators: %s: maximum characters allowed after the @ symbol. +#: inc/field-validation.php:322 +#: inc/translatable.php:100 +#, php-format +msgid "The part after @ may not exceed %s characters." +msgstr "" + #: inc/fields/dropdown-markup.php:112 #: inc/gutenberg-hooks.php:220 msgid "Select an option" @@ -1951,70 +2001,70 @@ msgstr "" msgid "HCaptcha sitekey verification failed. Please contact your site administrator." msgstr "" -#: inc/frontend-assets.php:300 +#: inc/frontend-assets.php:303 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgctxt "Quill heading picker: default paragraph style" msgid "Normal" msgstr "" -#: inc/frontend-assets.php:301 +#: inc/frontend-assets.php:304 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 1" msgstr "" -#: inc/frontend-assets.php:302 +#: inc/frontend-assets.php:305 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 2" msgstr "" -#: inc/frontend-assets.php:303 +#: inc/frontend-assets.php:306 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 3" msgstr "" -#: inc/frontend-assets.php:304 +#: inc/frontend-assets.php:307 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 4" msgstr "" -#: inc/frontend-assets.php:305 +#: inc/frontend-assets.php:308 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 5" msgstr "" -#: inc/frontend-assets.php:306 +#: inc/frontend-assets.php:309 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 6" msgstr "" -#: inc/frontend-assets.php:307 +#: inc/frontend-assets.php:310 msgctxt "Quill link tooltip label" msgid "Visit URL:" msgstr "" -#: inc/frontend-assets.php:308 +#: inc/frontend-assets.php:311 msgctxt "Quill link tooltip label" msgid "Enter link:" msgstr "" -#: inc/frontend-assets.php:309 +#: inc/frontend-assets.php:312 msgctxt "Quill link tooltip action" msgid "Edit" msgstr "" -#: inc/frontend-assets.php:310 +#: inc/frontend-assets.php:313 msgctxt "Quill link tooltip action" msgid "Save" msgstr "" -#: inc/frontend-assets.php:311 +#: inc/frontend-assets.php:314 msgctxt "Quill link tooltip action" msgid "Remove" msgstr "" @@ -2140,7 +2190,7 @@ msgstr "" #. translators: %s: Form title smart tag #. translators: %s: {form_title} smart tag placeholder. #: inc/global-settings/global-settings-defaults.php:173 -#: inc/global-settings/global-settings.php:586 +#: inc/global-settings/global-settings.php:588 #: inc/post-types.php:1005 #: assets/build/settings.js:172 #, php-format,js-format @@ -2148,8 +2198,8 @@ msgid "New Form Submission - %s" msgstr "" #: inc/global-settings/global-settings-defaults.php:260 -#: inc/global-settings/global-settings.php:425 -#: inc/global-settings/global-settings.php:613 +#: inc/global-settings/global-settings.php:427 +#: inc/global-settings/global-settings.php:615 #: assets/build/settings.js:172 msgid "This form is now closed as we've received all the entries." msgstr "" @@ -2167,19 +2217,19 @@ msgstr "" msgid "Settings Saved Successfully." msgstr "" -#: inc/global-settings/global-settings.php:481 +#: inc/global-settings/global-settings.php:483 #: assets/build/settings.js:172 msgid "Thank you for contacting us! We will be in touch with you shortly." msgstr "" -#: inc/global-settings/global-settings.php:528 +#: inc/global-settings/global-settings.php:530 #: inc/migrator/base-migrator.php:548 #: inc/post-types.php:1081 #: assets/build/formEditor.js:172 msgid "Thank you" msgstr "" -#: inc/global-settings/global-settings.php:529 +#: inc/global-settings/global-settings.php:531 #: inc/migrator/base-migrator.php:549 #: inc/post-types.php:1081 msgid "Your form has been submitted successfully. We'll review your details and get back to you soon." @@ -2978,11 +3028,6 @@ msgstr "" msgid "Edit" msgstr "" -#: inc/page-builders/elementor/form-widget.php:346 -#: assets/build/dashboard.js:2 -msgid "Create New Form" -msgstr "" - #: inc/page-builders/elementor/form-widget.php:348 msgid "Create" msgstr "" @@ -3236,8 +3281,8 @@ msgstr "" #: inc/payments/admin/admin-handler.php:1501 #: inc/payments/admin/admin-handler.php:1505 #: inc/payments/admin/admin-handler.php:1509 -#: inc/payments/front-end.php:865 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:866 +#: inc/payments/front-end.php:1275 msgid "Guest User" msgstr "" @@ -3246,7 +3291,7 @@ msgid "Invalid payment amount." msgstr "" #: inc/payments/front-end.php:98 -#: inc/payments/front-end.php:303 +#: inc/payments/front-end.php:304 #: inc/payments/payment-helper.php:596 msgid "Invalid form configuration." msgstr "" @@ -3256,7 +3301,7 @@ msgid "Valid customer email is required for payments." msgstr "" #: inc/payments/front-end.php:115 -#: inc/payments/front-end.php:340 +#: inc/payments/front-end.php:341 #: inc/payments/stripe/admin-stripe-handler.php:861 #: inc/payments/stripe/payments-settings.php:349 #: inc/payments/stripe/payments-settings.php:474 @@ -3267,140 +3312,140 @@ msgid "Stripe is not connected." msgstr "" #: inc/payments/front-end.php:121 -#: inc/payments/front-end.php:346 -#: inc/payments/front-end.php:634 -#: inc/payments/front-end.php:1153 +#: inc/payments/front-end.php:347 +#: inc/payments/front-end.php:635 +#: inc/payments/front-end.php:1154 msgid "Stripe secret key not found." msgstr "" #. translators: %s: formatted payment amount -#: inc/payments/front-end.php:203 +#: inc/payments/front-end.php:204 #, php-format msgid "The payment amount (%s) is below the minimum allowed. Stripe only processes amounts above 50¢." msgstr "" #. translators: %s: Field name -#: inc/payments/front-end.php:273 +#: inc/payments/front-end.php:274 #, php-format msgid "Missing required field: %s" msgstr "" -#: inc/payments/front-end.php:292 +#: inc/payments/front-end.php:293 msgid "Valid customer email is required for subscriptions." msgstr "" -#: inc/payments/front-end.php:297 +#: inc/payments/front-end.php:298 msgid "Customer name is required for subscriptions." msgstr "" -#: inc/payments/front-end.php:314 +#: inc/payments/front-end.php:315 msgid "Amount must be greater than 0" msgstr "" -#: inc/payments/front-end.php:322 +#: inc/payments/front-end.php:323 msgid "Invalid billing interval" msgstr "" -#: inc/payments/front-end.php:333 +#: inc/payments/front-end.php:334 msgid "Billing interval does not match the form configuration." msgstr "" -#: inc/payments/front-end.php:357 +#: inc/payments/front-end.php:358 msgid "Failed to create customer for subscription." msgstr "" -#: inc/payments/front-end.php:390 -#: inc/payments/front-end.php:406 +#: inc/payments/front-end.php:391 +#: inc/payments/front-end.php:407 msgid "Failed to create subscription through middleware." msgstr "" -#: inc/payments/front-end.php:411 +#: inc/payments/front-end.php:412 msgid "Empty response from subscription creation." msgstr "" -#: inc/payments/front-end.php:416 +#: inc/payments/front-end.php:417 msgid "Invalid JSON response from subscription creation." msgstr "" -#: inc/payments/front-end.php:420 -#: inc/payments/front-end.php:424 +#: inc/payments/front-end.php:421 +#: inc/payments/front-end.php:425 msgid "Invalid subscription data." msgstr "" -#: inc/payments/front-end.php:431 +#: inc/payments/front-end.php:432 msgid "Failed to create subscription." msgstr "" #. translators: %s: Error message -#: inc/payments/front-end.php:464 +#: inc/payments/front-end.php:465 #, php-format msgid "Unexpected error: %s" msgstr "" -#: inc/payments/front-end.php:604 +#: inc/payments/front-end.php:605 #: inc/payments/stripe/admin-stripe-handler.php:103 #: inc/payments/stripe/admin-stripe-handler.php:165 #: inc/payments/stripe/admin-stripe-handler.php:471 msgid "Subscription ID not found." msgstr "" -#: inc/payments/front-end.php:623 +#: inc/payments/front-end.php:624 msgid "Customer ID not found for the payment." msgstr "" -#: inc/payments/front-end.php:651 +#: inc/payments/front-end.php:652 msgid "Failed to retrieve setup intent." msgstr "" -#: inc/payments/front-end.php:699 +#: inc/payments/front-end.php:700 msgid "Failed to update subscription." msgstr "" -#: inc/payments/front-end.php:707 +#: inc/payments/front-end.php:708 msgid "Latest invoice not found on subscription." msgstr "" -#: inc/payments/front-end.php:720 +#: inc/payments/front-end.php:721 msgid "Failed to retrieve invoice." msgstr "" -#: inc/payments/front-end.php:742 +#: inc/payments/front-end.php:743 msgid "Payment intent not found on invoice." msgstr "" -#: inc/payments/front-end.php:757 +#: inc/payments/front-end.php:758 msgid "Failed to confirm payment." msgstr "" -#: inc/payments/front-end.php:773 +#: inc/payments/front-end.php:774 msgid "Failed to retrieve subscription." msgstr "" -#: inc/payments/front-end.php:788 +#: inc/payments/front-end.php:789 msgid "Subscription not found for the payment." msgstr "" #. translators: %d: User ID -#: inc/payments/front-end.php:863 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:864 +#: inc/payments/front-end.php:1275 #, php-format msgid "User ID: %d" msgstr "" #. translators: %s: Invoice status -#: inc/payments/front-end.php:871 +#: inc/payments/front-end.php:872 #, php-format msgid "Invoice Status: %s" msgstr "" #. translators: Title for subscription verification log -#: inc/payments/front-end.php:878 +#: inc/payments/front-end.php:879 msgid "Subscription Verification" msgstr "" #. translators: %s: Subscription ID #. translators: %s: Stripe subscription ID -#: inc/payments/front-end.php:882 +#: inc/payments/front-end.php:883 #: inc/payments/stripe/admin-stripe-handler.php:185 #: inc/payments/stripe/admin-stripe-handler.php:487 #: inc/payments/stripe/stripe-webhook.php:572 @@ -3415,8 +3460,8 @@ msgstr "" #. translators: %s: payment gateway name (e.g., Stripe) #. translators: %s: payment gateway #. translators: %s: Payment gateway name (e.g., Stripe). -#: inc/payments/front-end.php:884 -#: inc/payments/front-end.php:1285 +#: inc/payments/front-end.php:885 +#: inc/payments/front-end.php:1286 #: inc/payments/stripe/admin-stripe-handler.php:190 #: inc/payments/stripe/admin-stripe-handler.php:492 #: inc/payments/stripe/admin-stripe-handler.php:667 @@ -3430,13 +3475,13 @@ msgid "Payment Gateway: %s" msgstr "" #. translators: %s: Payment Intent ID -#: inc/payments/front-end.php:886 +#: inc/payments/front-end.php:887 #, php-format msgid "Payment Intent ID: %s" msgstr "" #. translators: %s: Charge ID -#: inc/payments/front-end.php:888 +#: inc/payments/front-end.php:889 #: inc/payments/stripe/stripe-webhook.php:1084 #, php-format msgid "Charge ID: %s" @@ -3444,7 +3489,7 @@ msgstr "" #. translators: %s: Subscription Status #. translators: %s: subscription status -#: inc/payments/front-end.php:890 +#: inc/payments/front-end.php:891 #: inc/payments/stripe/admin-stripe-handler.php:195 #: inc/payments/stripe/admin-stripe-handler.php:497 #, php-format @@ -3452,7 +3497,7 @@ msgid "Subscription Status: %s" msgstr "" #. translators: %s: Customer ID -#: inc/payments/front-end.php:892 +#: inc/payments/front-end.php:893 #: inc/payments/stripe/stripe-webhook.php:1174 #, php-format msgid "Customer ID: %s" @@ -3460,8 +3505,8 @@ msgstr "" #. translators: 1: Amount, 2: Currency #. translators: %1$s: amount, %2$s: currency. -#: inc/payments/front-end.php:894 -#: inc/payments/front-end.php:1287 +#: inc/payments/front-end.php:895 +#: inc/payments/front-end.php:1288 #: inc/payments/stripe/stripe-webhook.php:1089 #: inc/payments/stripe/stripe-webhook.php:1166 #, php-format @@ -3470,34 +3515,34 @@ msgstr "" #. translators: %s: Payment mode (e.g. Live or Test) #. translators: %s: payment mode -#: inc/payments/front-end.php:897 -#: inc/payments/front-end.php:1292 +#: inc/payments/front-end.php:898 +#: inc/payments/front-end.php:1293 #, php-format msgid "Mode: %s" msgstr "" -#: inc/payments/front-end.php:931 +#: inc/payments/front-end.php:932 msgid "Failed to verify subscription." msgstr "" -#: inc/payments/front-end.php:1186 -#: inc/payments/front-end.php:1204 -#: inc/payments/front-end.php:1212 -#: inc/payments/front-end.php:1219 +#: inc/payments/front-end.php:1187 +#: inc/payments/front-end.php:1205 +#: inc/payments/front-end.php:1213 +#: inc/payments/front-end.php:1220 msgid "Failed to retrieve payment intent." msgstr "" -#: inc/payments/front-end.php:1227 +#: inc/payments/front-end.php:1228 msgid "Payment was not confirmed successfully." msgstr "" -#: inc/payments/front-end.php:1279 +#: inc/payments/front-end.php:1280 msgid "Payment Verification" msgstr "" #. translators: %s: Stripe transaction ID #. translators: %s: Charge ID -#: inc/payments/front-end.php:1283 +#: inc/payments/front-end.php:1284 #: inc/payments/stripe/stripe-webhook.php:1162 #, php-format msgid "Transaction ID: %s" @@ -3505,18 +3550,18 @@ msgstr "" #. translators: %s: payment status #. translators: %s: Status -#: inc/payments/front-end.php:1289 +#: inc/payments/front-end.php:1290 #: inc/payments/stripe/stripe-webhook.php:576 #: inc/payments/stripe/stripe-webhook.php:1168 #, php-format msgid "Status: %s" msgstr "" -#: inc/payments/front-end.php:1395 +#: inc/payments/front-end.php:1396 msgid "Failed to create Stripe customer." msgstr "" -#: inc/payments/front-end.php:1459 +#: inc/payments/front-end.php:1460 msgid "Failed to create Stripe guest customer." msgstr "" @@ -4946,41 +4991,41 @@ msgstr "" msgid "Entry ID is required." msgstr "" -#: inc/rest-api.php:1061 +#: inc/rest-api.php:1073 msgid "Select at least one form and specify an action." msgstr "" -#: inc/rest-api.php:1076 +#: inc/rest-api.php:1088 msgid "Form not found or is not a valid form type." msgstr "" -#: inc/rest-api.php:1088 +#: inc/rest-api.php:1100 msgid "This form is already in the trash." msgstr "" -#: inc/rest-api.php:1099 +#: inc/rest-api.php:1111 msgid "This form is not in the trash." msgstr "" -#: inc/rest-api.php:1115 +#: inc/rest-api.php:1127 msgid "Use the restore action to recover a trashed form before switching it to draft." msgstr "" -#: inc/rest-api.php:1120 +#: inc/rest-api.php:1132 msgid "This form is already a draft." msgstr "" -#: inc/rest-api.php:1135 +#: inc/rest-api.php:1147 msgid "Invalid action." msgstr "" #. translators: %s: action name -#: inc/rest-api.php:1150 +#: inc/rest-api.php:1162 #, php-format msgid "Failed to %s this form. Please try again." msgstr "" -#: inc/rest-api.php:1839 +#: inc/rest-api.php:1851 msgid " (Copy)" msgstr "" @@ -5096,32 +5141,32 @@ msgid "Enter a valid email address." msgstr "" #. translators: %s represents the minimum acceptable value -#: inc/translatable.php:97 +#: inc/translatable.php:103 #, php-format msgid "Minimum value is %s." msgstr "" #. translators: %s represents the maximum acceptable value -#: inc/translatable.php:100 +#: inc/translatable.php:106 #, php-format msgid "Maximum value is %s." msgstr "" #. translators: %s represents the minimum number of options to select -#: inc/translatable.php:103 #: inc/translatable.php:109 +#: inc/translatable.php:115 #, php-format msgid "Select at least %s options." msgstr "" #. translators: %s represents the maximum number of options that can be selected -#: inc/translatable.php:106 #: inc/translatable.php:112 +#: inc/translatable.php:118 #, php-format msgid "You can select up to %s options." msgstr "" -#: inc/translatable.php:151 +#: inc/translatable.php:159 msgid "This form is now closed as we have reached the maximum number of entries." msgstr "" @@ -14914,6 +14959,38 @@ msgstr "" msgid "Re-arrange block inside Quick Action Bar" msgstr "" +#: assets/build/formEditor.js:172 +msgid "Show Labels" +msgstr "" + +#: assets/build/formEditor.js:172 +msgid "First Page Label" +msgstr "" + +#: assets/build/formEditor.js:172 +msgid "Progress Indicator" +msgstr "" + +#: assets/build/formEditor.js:172 +msgid "Progress Bar" +msgstr "" + +#: assets/build/formEditor.js:172 +msgid "Connector" +msgstr "" + +#: assets/build/formEditor.js:172 +msgid "Steps" +msgstr "" + +#: assets/build/formEditor.js:172 +msgid "Next Button Text" +msgstr "" + +#: assets/build/formEditor.js:172 +msgid "Back Button Text" +msgstr "" + #: assets/build/formEditor.js:172 msgid "OttoKit Settings" msgstr "" @@ -15728,63 +15805,55 @@ msgid "Automate scoring and lead segmentation for better insights." msgstr "" #: assets/build/formEditor.js:172 -msgid "SureTriggers" +msgid "Show Entries" msgstr "" #: assets/build/formEditor.js:172 -msgid "Form Behavior" +msgid "Display this form’s entries in a table on the frontend." msgstr "" #: assets/build/formEditor.js:172 -msgid "You have unsaved changes. Discard them to continue, or stay to save your changes." +msgid "Publish this form’s submissions as a sortable, paginated table on any page — perfect for public directories, listings, and team dashboards, with per-role view access." msgstr "" #: assets/build/formEditor.js:172 -msgid "Discard & continue" +msgid "Embed a sortable, paginated entries table with a simple shortcode." msgstr "" #: assets/build/formEditor.js:172 -msgid "Use Labels as Placeholders" -msgstr "" - -#: assets/build/formEditor.js:172 -msgid "Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview." -msgstr "" - -#: assets/build/formEditor.js:172 -msgid "Page Break" +msgid "Choose exactly which fields appear as columns." msgstr "" #: assets/build/formEditor.js:172 -msgid "Show Labels" +msgid "Restrict who can view entries by user role." msgstr "" #: assets/build/formEditor.js:172 -msgid "First Page Label" +msgid "SureTriggers" msgstr "" #: assets/build/formEditor.js:172 -msgid "Progress Indicator" +msgid "Form Behavior" msgstr "" #: assets/build/formEditor.js:172 -msgid "Progress Bar" +msgid "You have unsaved changes. Discard them to continue, or stay to save your changes." msgstr "" #: assets/build/formEditor.js:172 -msgid "Connector" +msgid "Discard & continue" msgstr "" #: assets/build/formEditor.js:172 -msgid "Steps" +msgid "Use Labels as Placeholders" msgstr "" #: assets/build/formEditor.js:172 -msgid "Next Button Text" +msgid "Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview." msgstr "" #: assets/build/formEditor.js:172 -msgid "Back Button Text" +msgid "Page Break" msgstr "" #: assets/build/formEditor.js:172 @@ -16249,7 +16318,7 @@ msgid "This will create a copy of \"%s\" with all its settings." msgstr "" #: assets/build/formSubmit.js:2 -#: assets/js/unminified/form-submit.js:487 +#: assets/js/unminified/form-submit.js:495 msgid "An error occurred while submitting your form. Please try again." msgstr "" diff --git a/package-lock.json b/package-lock.json index 566afa018..484dbe1ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sureforms", - "version": "2.12.0", + "version": "2.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sureforms", - "version": "2.12.0", + "version": "2.12.1", "license": "GPL", "dependencies": { "@bsf/force-ui": "git+https://github.com/brainstormforce/bsf-admin-ui#v1.7.7", diff --git a/package.json b/package.json index f03e6fd69..f731b4add 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sureforms", - "version": "2.12.0", + "version": "2.12.1", "description": "A simple yet powerful way to add forms to your website.", "main": "index.js", "scripts": { diff --git a/readme.txt b/readme.txt index 131fdf251..4acfd587e 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: forms, contact form, form builder, survey, payment form Requires at least: 6.4 Tested up to: 7.0 Requires PHP: 7.4 -Stable tag: 2.12.0 +Stable tag: 2.12.1 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -458,6 +458,13 @@ Yes. SureForms Business includes fully functional user registration forms and lo You can report security issues through our [Bug Bounty Program](https://brainstormforce.com/bug-bounty-program/). We collaborate with Patchstack to provide opportunities for researchers to report vulnerabilities. The Patchstack team will help validate, triage, and handle any reported security issues. == Changelog == += 2.12.1 - 6th July 2026 = +* New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. +* New: Added the ability to manually retry failed webhooks and native integrations, with retry details now surfaced in the logs. +* Fix: Reset Form now resets the Dropdown field and triggers a reset for Pro fields so every field returns to its default state. +* Fix: Resolved a Stripe one-time payment error that could cause card payments to fail with an HTTP 400 response. +* Fix: Restored bullet point visibility in the rich text admin editor so formatted lists display correctly. +* Fix: The Payment block Email and Name field mappers now include child fields from User Registration container blocks. = 2.12.0 - 24th June 2026 = * New: Added action hooks around payment success, cancellation, and refund events so plugins such as SureMembers, LMS, and CRMs can grant or revoke access for both Stripe and PayPal. * Fix: Cancel Subscription now routes through the correct payment gateway so PayPal subscriptions cancel properly instead of always calling Stripe. @@ -468,9 +475,6 @@ You can report security issues through our [Bug Bounty Program](https://brainsto * Fix: Phone field auto country detection always resolved to the United States. * Fix: Corrected the Cloudflare Turnstile "Get Keys" link. * Fix: This update addressed a security bug. Props to Yaswanth Reddy Sunkara for reporting it responsibly to our team. -= 2.11.0 - 10th June 2026 = -* New: Added a Form Migrator to import forms from Contact Form 7, WPForms, Gravity Forms, and Ninja Forms in a single click. -* New: Added native WPML support to translate each form individually using String Packages. The full changelog is available [here](https://sureforms.com/whats-new/?utm_source=wordpress.org&utm_medium=whats_new). == Upgrade Notice == diff --git a/sureforms.php b/sureforms.php index 2dc072243..f161d9429 100644 --- a/sureforms.php +++ b/sureforms.php @@ -7,7 +7,7 @@ * Requires PHP: 7.4 * Author: SureForms * Author URI: https://sureforms.com/ - * Version: 2.12.0 + * Version: 2.12.1 * License: GPLv2 or later * Text Domain: sureforms * @@ -25,7 +25,7 @@ define( 'SRFM_BASENAME', plugin_basename( SRFM_FILE ) ); define( 'SRFM_DIR', plugin_dir_path( SRFM_FILE ) ); define( 'SRFM_URL', plugins_url( '/', SRFM_FILE ) ); -define( 'SRFM_VER', '2.12.0' ); +define( 'SRFM_VER', '2.12.1' ); define( 'SRFM_SLUG', 'srfm' ); // ------ ADDITIONAL CONSTANTS ------- // define( 'SRFM_FORMS_POST_TYPE', 'sureforms_form' ); @@ -35,7 +35,7 @@ define( 'SRFM_AI_MIDDLEWARE', 'https://credits.startertemplates.com/sureforms/' ); define( 'SRFM_MIDDLEWARE_BASE_URL', 'https://api.sureforms.com/' ); define( 'SRFM_BILLING_PORTAL', 'https://billing.sureforms.com/' ); -define( 'SRFM_PRO_RECOMMENDED_VER', '2.12.0' ); +define( 'SRFM_PRO_RECOMMENDED_VER', '2.12.1' ); define( 'SRFM_SURETRIGGERS_INTEGRATION_BASE_URL', 'https://app.ottokit.com/' ); diff --git a/tests/unit/admin/test-admin.php b/tests/unit/admin/test-admin.php index 71cc8c75c..c3f2d23e5 100644 --- a/tests/unit/admin/test-admin.php +++ b/tests/unit/admin/test-admin.php @@ -326,7 +326,7 @@ public function test_render_dashboard_widget() { * For a capable user it always hooks register_ai_dashboard_widget; with no logged-in user the * capability check short-circuits and nothing is wired (the early-return path). * - * @since x.x.x + * @since 2.12.1 */ public function test_maybe_register_dashboard_widget() { $admin = Admin::get_instance(); @@ -361,7 +361,7 @@ public function test_maybe_register_dashboard_widget() { /** * Test register_dashboard_widget registers the recent-entries widget into $wp_meta_boxes. * - * @since x.x.x + * @since 2.12.1 */ public function test_register_dashboard_widget() { require_once ABSPATH . 'wp-admin/includes/template.php'; @@ -383,7 +383,7 @@ public function test_register_dashboard_widget() { /** * Test register_ai_dashboard_widget registers the AI quick draft widget into $wp_meta_boxes. * - * @since x.x.x + * @since 2.12.1 */ public function test_register_ai_dashboard_widget() { require_once ABSPATH . 'wp-admin/includes/template.php'; @@ -405,7 +405,7 @@ public function test_register_ai_dashboard_widget() { /** * Test render_ai_dashboard_widget outputs the AI quick draft widget markup. * - * @since x.x.x + * @since 2.12.1 */ public function test_render_ai_dashboard_widget() { $admin = Admin::get_instance(); @@ -425,7 +425,7 @@ public function test_render_ai_dashboard_widget() { * Asserts the gate (no enqueue outside index.php) and that, on the dashboard for a capable user, * the script is enqueued with its localized config — instead of grepping the method source. * - * @since x.x.x + * @since 2.12.1 */ public function test_enqueue_ai_dashboard_widget_assets() { $admin = Admin::get_instance(); @@ -469,7 +469,7 @@ public function test_enqueue_ai_dashboard_widget_assets() { * filtered to throw WPDieException — letting the runner survive while we assert the side effect * (the incremented counter) rather than grepping the method source. * - * @since x.x.x + * @since 2.12.1 */ public function test_track_ai_widget_usage() { $admin = Admin::get_instance(); diff --git a/tests/unit/inc/test-field-validation.php b/tests/unit/inc/test-field-validation.php index 2da33b7d2..05d6f3700 100644 --- a/tests/unit/inc/test-field-validation.php +++ b/tests/unit/inc/test-field-validation.php @@ -446,7 +446,7 @@ public function test_process_payment_block_defaults() { /** * Test get_email_char_limits returns RFC 5321 defaults and honors the filter override. * - * @since x.x.x + * @since 2.12.1 */ public function test_get_email_char_limits() { // Defaults when no filter is attached. From a5cccf5b96bdfdf74ab0d905d3878606a11ef60f Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Mon, 6 Jul 2026 09:25:23 +0530 Subject: [PATCH 27/35] updated change log --- README.md | 4 +--- readme.txt | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3fc2f9dd8..b53719f47 100644 --- a/README.md +++ b/README.md @@ -460,11 +460,9 @@ You can report security issues through our [Bug Bounty Program](https://brainsto ## Changelog ## ### 2.12.1 - 6th July 2026 ### * New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. -* New: Added the ability to manually retry failed webhooks and native integrations, with retry details now surfaced in the logs. -* Fix: Reset Form now resets the Dropdown field and triggers a reset for Pro fields so every field returns to its default state. +* Fix: Reset form option not Working for some fields. * Fix: Resolved a Stripe one-time payment error that could cause card payments to fail with an HTTP 400 response. * Fix: Restored bullet point visibility in the rich text admin editor so formatted lists display correctly. -* Fix: The Payment block Email and Name field mappers now include child fields from User Registration container blocks. ### 2.12.0 - 24th June 2026 ### * New: Added action hooks around payment success, cancellation, and refund events so plugins such as SureMembers, LMS, and CRMs can grant or revoke access for both Stripe and PayPal. * Fix: Cancel Subscription now routes through the correct payment gateway so PayPal subscriptions cancel properly instead of always calling Stripe. diff --git a/readme.txt b/readme.txt index 4acfd587e..82402aed2 100644 --- a/readme.txt +++ b/readme.txt @@ -460,11 +460,9 @@ You can report security issues through our [Bug Bounty Program](https://brainsto == Changelog == = 2.12.1 - 6th July 2026 = * New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. -* New: Added the ability to manually retry failed webhooks and native integrations, with retry details now surfaced in the logs. -* Fix: Reset Form now resets the Dropdown field and triggers a reset for Pro fields so every field returns to its default state. +* Fix: Reset form option not Working for some fields. * Fix: Resolved a Stripe one-time payment error that could cause card payments to fail with an HTTP 400 response. * Fix: Restored bullet point visibility in the rich text admin editor so formatted lists display correctly. -* Fix: The Payment block Email and Name field mappers now include child fields from User Registration container blocks. = 2.12.0 - 24th June 2026 = * New: Added action hooks around payment success, cancellation, and refund events so plugins such as SureMembers, LMS, and CRMs can grant or revoke access for both Stripe and PayPal. * Fix: Cancel Subscription now routes through the correct payment gateway so PayPal subscriptions cancel properly instead of always calling Stripe. From 15e752383bb8baf9ee5a6a81d2eccd61eb165881 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Mon, 6 Jul 2026 09:31:09 +0530 Subject: [PATCH 28/35] updated change log --- README.md | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b53719f47..d96d6ce68 100644 --- a/README.md +++ b/README.md @@ -460,7 +460,7 @@ You can report security issues through our [Bug Bounty Program](https://brainsto ## Changelog ## ### 2.12.1 - 6th July 2026 ### * New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. -* Fix: Reset form option not Working for some fields. +* Fix: Reset form option not working for some fields. * Fix: Resolved a Stripe one-time payment error that could cause card payments to fail with an HTTP 400 response. * Fix: Restored bullet point visibility in the rich text admin editor so formatted lists display correctly. ### 2.12.0 - 24th June 2026 ### diff --git a/readme.txt b/readme.txt index 82402aed2..e1e12ef47 100644 --- a/readme.txt +++ b/readme.txt @@ -460,7 +460,7 @@ You can report security issues through our [Bug Bounty Program](https://brainsto == Changelog == = 2.12.1 - 6th July 2026 = * New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. -* Fix: Reset form option not Working for some fields. +* Fix: Reset form option not working for some fields. * Fix: Resolved a Stripe one-time payment error that could cause card payments to fail with an HTTP 400 response. * Fix: Restored bullet point visibility in the rich text admin editor so formatted lists display correctly. = 2.12.0 - 24th June 2026 = From 40eaec0074c944b05720fea8d760d8559281c6f7 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Mon, 6 Jul 2026 12:19:49 +0530 Subject: [PATCH 29/35] Add changelog entry for email field RFC 5321 length limits (#2904) --- README.md | 1 + readme.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index d96d6ce68..85e0e02aa 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,7 @@ You can report security issues through our [Bug Bounty Program](https://brainsto ## Changelog ## ### 2.12.1 - 6th July 2026 ### * New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. +* Improvement: Email fields now enforce standard RFC 5321 length limits (a 64-character local part and 255-character domain) to prevent oversized submissions, with a filter to customize the limits. * Fix: Reset form option not working for some fields. * Fix: Resolved a Stripe one-time payment error that could cause card payments to fail with an HTTP 400 response. * Fix: Restored bullet point visibility in the rich text admin editor so formatted lists display correctly. diff --git a/readme.txt b/readme.txt index e1e12ef47..4b6386e8b 100644 --- a/readme.txt +++ b/readme.txt @@ -460,6 +460,7 @@ You can report security issues through our [Bug Bounty Program](https://brainsto == Changelog == = 2.12.1 - 6th July 2026 = * New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. +* Improvement: Email fields now enforce standard RFC 5321 length limits (a 64-character local part and 255-character domain) to prevent oversized submissions, with a filter to customize the limits. * Fix: Reset form option not working for some fields. * Fix: Resolved a Stripe one-time payment error that could cause card payments to fail with an HTTP 400 response. * Fix: Restored bullet point visibility in the rich text admin editor so formatted lists display correctly. From 297c3a06b6989b093cbf2cb79ad7a96fd10d7d89 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 7 Jul 2026 11:03:27 +0530 Subject: [PATCH 30/35] chore: update BSF Analytics library to 1.1.29 Sync inc/lib/bsf-analytics from brainstormforce/bsf-analytics tag 1.1.29: - Add 'spectra-blocks' slug to UTM analytics (modules/utm-analytics.php) - Bump version.json to 1.1.29 and update changelog.txt --- inc/lib/bsf-analytics/changelog.txt | 3 +++ inc/lib/bsf-analytics/modules/utm-analytics.php | 1 + inc/lib/bsf-analytics/version.json | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/inc/lib/bsf-analytics/changelog.txt b/inc/lib/bsf-analytics/changelog.txt index e6b2d6cf6..3f050dd9a 100644 --- a/inc/lib/bsf-analytics/changelog.txt +++ b/inc/lib/bsf-analytics/changelog.txt @@ -1,3 +1,6 @@ +v1.1.29 - 30-June-2026 +- Improvement: Added `spectra-blocks` slug to UTM analytics. + v1.1.28 - 22-June-2026 - Fix: Deactivation survey loaded a non-existent RTL stylesheet (`feedback.min-rtl.css`) producing a 404 in RTL locales. Registered the file suffix so the correct `feedback-rtl.min.css` is requested. diff --git a/inc/lib/bsf-analytics/modules/utm-analytics.php b/inc/lib/bsf-analytics/modules/utm-analytics.php index 86479f048..8f8bd2a66 100644 --- a/inc/lib/bsf-analytics/modules/utm-analytics.php +++ b/inc/lib/bsf-analytics/modules/utm-analytics.php @@ -45,6 +45,7 @@ class BSF_UTM_Analytics { 'power-coupons', 'presto-player', 'sigmize', + 'spectra-blocks', 'surecart', 'surecontact', 'surecookie', diff --git a/inc/lib/bsf-analytics/version.json b/inc/lib/bsf-analytics/version.json index c25d50351..06d738c31 100644 --- a/inc/lib/bsf-analytics/version.json +++ b/inc/lib/bsf-analytics/version.json @@ -1,3 +1,3 @@ { - "bsf-analytics-ver": "1.1.28" + "bsf-analytics-ver": "1.1.29" } From e86978353ff97ab783cd8d6274d481cd1037856c Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Wed, 8 Jul 2026 11:31:13 +0530 Subject: [PATCH 31/35] chore: update 2.12.1 release date to 8th July 2026 --- README.md | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 85e0e02aa..1b92b8049 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ Yes. SureForms Business includes fully functional user registration forms and lo You can report security issues through our [Bug Bounty Program](https://brainstormforce.com/bug-bounty-program/). We collaborate with Patchstack to provide opportunities for researchers to report vulnerabilities. The Patchstack team will help validate, triage, and handle any reported security issues. ## Changelog ## -### 2.12.1 - 6th July 2026 ### +### 2.12.1 - 8th July 2026 ### * New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. * Improvement: Email fields now enforce standard RFC 5321 length limits (a 64-character local part and 255-character domain) to prevent oversized submissions, with a filter to customize the limits. * Fix: Reset form option not working for some fields. diff --git a/readme.txt b/readme.txt index 4b6386e8b..371a5aafe 100644 --- a/readme.txt +++ b/readme.txt @@ -458,7 +458,7 @@ Yes. SureForms Business includes fully functional user registration forms and lo You can report security issues through our [Bug Bounty Program](https://brainstormforce.com/bug-bounty-program/). We collaborate with Patchstack to provide opportunities for researchers to report vulnerabilities. The Patchstack team will help validate, triage, and handle any reported security issues. == Changelog == -= 2.12.1 - 6th July 2026 = += 2.12.1 - 8th July 2026 = * New: Added an AI-powered quick draft flow on the dashboard to generate forms faster, with built-in usage tracking. * Improvement: Email fields now enforce standard RFC 5321 length limits (a 64-character local part and 255-character domain) to prevent oversized submissions, with a filter to customize the limits. * Fix: Reset form option not working for some fields. From 508bbfbfc9915e5ac503a12e2f6dbdeca2ec580a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 06:15:05 +0000 Subject: [PATCH 32/35] chore: update i18n translations Auto-generated by /i18n command on PR #2923 --- ...e_DE-1cb9ecd067cd971ff5d9db0b4dae2891.json | 2 +- languages/sureforms-de_DE.mo | Bin 321267 -> 323274 bytes languages/sureforms-de_DE.po | 459 ++++++++++-------- ...s_ES-1cb9ecd067cd971ff5d9db0b4dae2891.json | 2 +- languages/sureforms-es_ES.mo | Bin 323140 -> 325080 bytes languages/sureforms-es_ES.po | 459 ++++++++++-------- ...r_FR-1cb9ecd067cd971ff5d9db0b4dae2891.json | 2 +- languages/sureforms-fr_FR.mo | Bin 327342 -> 329336 bytes languages/sureforms-fr_FR.po | 459 ++++++++++-------- ...t_IT-1cb9ecd067cd971ff5d9db0b4dae2891.json | 2 +- languages/sureforms-it_IT.mo | Bin 316761 -> 318658 bytes languages/sureforms-it_IT.po | 459 ++++++++++-------- ...l_NL-1cb9ecd067cd971ff5d9db0b4dae2891.json | 2 +- languages/sureforms-nl_NL.mo | Bin 310020 -> 311920 bytes languages/sureforms-nl_NL.po | 459 ++++++++++-------- ...l_PL-1cb9ecd067cd971ff5d9db0b4dae2891.json | 2 +- languages/sureforms-pl_PL.mo | Bin 317935 -> 319835 bytes languages/sureforms-pl_PL.po | 459 ++++++++++-------- ...t_PT-1cb9ecd067cd971ff5d9db0b4dae2891.json | 2 +- languages/sureforms-pt_PT.mo | Bin 320119 -> 322057 bytes languages/sureforms-pt_PT.po | 459 ++++++++++-------- 21 files changed, 1855 insertions(+), 1372 deletions(-) diff --git a/languages/sureforms-de_DE-1cb9ecd067cd971ff5d9db0b4dae2891.json b/languages/sureforms-de_DE-1cb9ecd067cd971ff5d9db0b4dae2891.json index 04377d44b..0ff2a30fb 100644 --- a/languages/sureforms-de_DE-1cb9ecd067cd971ff5d9db0b4dae2891.json +++ b/languages/sureforms-de_DE-1cb9ecd067cd971ff5d9db0b4dae2891.json @@ -1 +1 @@ -{"translation-revision-date":"2024-12-13T16:14:52+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Status"],"Form":["Formular"],"Fields":["Felder"],"Image":["Bild"],"Activated":["Aktiviert"],"Activate":["Aktivieren"],"Submit":["Einreichen"],"Global Settings":["Globale Einstellungen"],"Form Title":["Formulartitel"],"Edit":["Bearbeiten"],"Please enter a valid URL.":["Bitte geben Sie eine g\u00fcltige URL ein."],"Desktop":["Desktop"],"Medium":["Mittel"],"Mobile":["Mobil"],"Repeat":["Wiederholen"],"Scroll":["Scrollen"],"Signature":["Unterschrift"],"Tablet":["Tablet"],"Upload":["Hochladen"],"Basic":["Grundlegend"],"Form Settings":["Formulareinstellungen"],"General":["Allgemein"],"Style":["Stil"],"Advanced":["Fortgeschritten"],"No tags available":["Keine Tags verf\u00fcgbar"],"Device":["Ger\u00e4t"],"Select Shortcodes":["Shortcodes ausw\u00e4hlen"],"Page Break Label":["Seitenumbruch-Label"],"Next":["Weiter"],"Back":["Zur\u00fcck"],"Reset":["Zur\u00fccksetzen"],"Generic tags":["Allgemeine Tags"],"Pixel":["Pixel"],"Em":["Em"],"Select Units":["Einheiten ausw\u00e4hlen"],"%s units":["%s Einheiten"],"Margin":["Marge"],"None":["Keine"],"Custom":["Benutzerdefiniert"],"Please add a option props to MultiButtonsControl":["Bitte f\u00fcgen Sie eine Option props zu MultiButtonsControl hinzu"],"Confirm":["Best\u00e4tigen"],"Cancel":["Abbrechen"],"Processing\u2026":["Verarbeitung\u2026"],"Select Video":["Video ausw\u00e4hlen"],"Change Video":["Video \u00e4ndern"],"Select Lottie Animation":["Lottie-Animation ausw\u00e4hlen"],"Change Lottie Animation":["Lottie-Animation \u00e4ndern"],"Upload SVG":["SVG hochladen"],"Change SVG":["SVG \u00e4ndern"],"Select Image":["Bild ausw\u00e4hlen"],"Change Image":["Bild \u00e4ndern"],"Upload SVG?":["SVG hochladen?"],"Upload SVG can be potentially risky. Are you sure?":["Das Hochladen von SVG kann potenziell riskant sein. Bist du sicher?"],"Upload Anyway":["Trotzdem hochladen"],"Full Width":["Volle Breite"],"Install":["Installieren"],"Plugin Installation failed, Please try again later.":["Plugin-Installation fehlgeschlagen, bitte versuchen Sie es sp\u00e4ter erneut."],"Plugin activation failed, Please try again later.":["Plugin-Aktivierung fehlgeschlagen, bitte versuchen Sie es sp\u00e4ter erneut."],"Integrations":["Integrationen"],"%s Removed from Quick Action Bar.":["%s wurde aus der Schnellzugriffsleiste entfernt."],"Add to Quick Action Bar":["Zur Schnellzugriffsleiste hinzuf\u00fcgen"],"%s Added to Quick Action Bar.":["%s zur Schnellzugriffsleiste hinzugef\u00fcgt."],"Already Present in Quick Action Bar":["Bereits in der Schnellzugriffsleiste vorhanden"],"No results found.":["Keine Ergebnisse gefunden."],"data object is empty":["Datenobjekt ist leer"],"Add blocks to Quick Action Bar":["Bl\u00f6cke zur Schnellzugriffsleiste hinzuf\u00fcgen"],"Re-arrange block inside Quick Action Bar":["Block im Schnellaktionsbereich neu anordnen"],"Upgrade":["Aktualisieren"],"Connecting\u2026":["Verbinden\u2026"],"Install & Activate":["Installieren & Aktivieren"],"Compliance Settings":["Compliance-Einstellungen"],"Enable GDPR Compliance":["Aktivieren Sie die DSGVO-Konformit\u00e4t"],"Never store entry data after form submission":["Speichern Sie niemals Eingabedaten nach dem Absenden des Formulars"],"When enabled this form will never store Entries.":["Wenn aktiviert, speichert dieses Formular niemals Eintr\u00e4ge."],"Automatically delete entries":["Eintr\u00e4ge automatisch l\u00f6schen"],"When enabled this form will automatically delete entries after a certain period of time.":["Wenn aktiviert, wird dieses Formular Eintr\u00e4ge nach einer bestimmten Zeitspanne automatisch l\u00f6schen."],"Entries older than the days set will be deleted automatically.":["Eintr\u00e4ge, die \u00e4lter sind als die festgelegten Tage, werden automatisch gel\u00f6scht."],"Custom CSS":["Benutzerdefiniertes CSS"],"The following CSS styles added below will only apply to this form container.":["Die folgenden unten hinzugef\u00fcgten CSS-Stile gelten nur f\u00fcr diesen Formularcontainer."],"Visual":["Visuell"],"HTML":["HTML"],"All Data":["Alle Daten"],"Add Shortcode":["Shortcode hinzuf\u00fcgen"],"Form input tags":["Formulareingabetags"],"Comma separated values are also accepted.":["Kommagetrennte Werte werden ebenfalls akzeptiert."],"Email Notification":["E-Mail-Benachrichtigung"],"Name":["Name"],"Send Email To":["E-Mail senden an"],"Subject":["Betreff"],"CC":["CC"],"BCC":["BCC"],"Reply To":["Antworten an"],"Add Notification":["Benachrichtigung hinzuf\u00fcgen"],"Add Key":["Schl\u00fcssel hinzuf\u00fcgen"],"Add Value":["Wert hinzuf\u00fcgen"],"Add":["Hinzuf\u00fcgen"],"Confirmation Message":["Best\u00e4tigungsnachricht"],"After Form Submission":["Nach dem Absenden des Formulars"],"Hide Form":["Formular ausblenden"],"Reset Form":["Formular zur\u00fccksetzen"],"Custom URL":["Benutzerdefinierte URL"],"Add Query Parameters":["Abfrageparameter hinzuf\u00fcgen"],"Select if you want to add key-value pairs for form fields to include in query parameters":["W\u00e4hlen Sie, ob Sie Schl\u00fcssel-Wert-Paare f\u00fcr Formularfelder hinzuf\u00fcgen m\u00f6chten, die in Abfrageparametern enthalten sein sollen"],"Query Parameters":["Abfrageparameter"],"Please select a page.":["Bitte w\u00e4hlen Sie eine Seite aus."],"Suggestion: URL should use HTTPS":["Vorschlag: Die URL sollte HTTPS verwenden"],"Success Message":["Erfolgsmeldung"],"Redirect":["Weiterleiten"],"Redirect to":["Weiterleiten zu"],"Page":["Seite"],"Form Confirmation":["Formularbest\u00e4tigung"],"Confirmation Type":["Best\u00e4tigungstyp"],"Use Labels as Placeholders":["Verwenden Sie Labels als Platzhalter"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["Diese Einstellung platziert die Beschriftungen innerhalb der Felder als Platzhalter (wo m\u00f6glich). Diese Einstellung wirkt sich nur auf der Live-Seite aus, nicht in der Editor-Vorschau."],"Page Break":["Seitenumbruch"],"Show Labels":["Etiketten anzeigen"],"First Page Label":["Erstes Seitenetikett"],"Progress Indicator":["Fortschrittsanzeige"],"Progress Bar":["Fortschrittsbalken"],"Connector":["Verbinder"],"Steps":["Schritte"],"Next Button Text":["Text der Schaltfl\u00e4che \"Weiter\""],"Back Button Text":["Zur\u00fcck-Schaltfl\u00e4chentext"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["Sind Sie sicher, dass Sie schlie\u00dfen m\u00f6chten? Ihre nicht gespeicherten \u00c4nderungen gehen verloren, da Sie einige Validierungsfehler haben."],"There are few unsaved changes. Please save your changes to reflect the updates.":["Es gibt einige nicht gespeicherte \u00c4nderungen. Bitte speichern Sie Ihre \u00c4nderungen, um die Aktualisierungen zu \u00fcbernehmen."],"Form Behavior":["Formverhalten"],"Clear":["Klar"],"Select Color":["Farbe ausw\u00e4hlen"],"Primary Color":["Prim\u00e4rfarbe"],"Text Color":["Textfarbe"],"Text Color on Primary":["Textfarbe auf Prim\u00e4r"],"Field Spacing":["Feldabstand"],"Small":["Klein"],"Large":["Gro\u00df"],"Left":["Links"],"Center":["Zentrum"],"Right":["Richtig"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Unsichtbar"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Datumsauswahl"],"Time Picker":["Zeitw\u00e4hler"],"Hidden":["Versteckt"],"Slider":["Schieberegler"],"Rating":["Bewertung"],"Upgrade to Unlock These Fields":["Upgrade, um diese Felder freizuschalten"],"Add Block":["Block hinzuf\u00fcgen"],"Customize with SureForms":["Anpassen mit SureForms"],"Page break":["Seitenumbruch"],"Previous":["Zur\u00fcck"],"Thank you":["Danke"],"Form submitted successfully!":["Formular erfolgreich eingereicht!"],"Instant Form":["Sofortformular"],"Enable Instant Form":["Sofortformular aktivieren"],"Enable Preview":["Vorschau aktivieren"],"Show Title":["Titel anzeigen"],"Site Logo":["Seitenlogo"],"Banner Background":["Banner-Hintergrund"],"Color":["Farbe"],"Upload Image":["Bild hochladen"],"Background Color":["Hintergrundfarbe"],"Use banner as page background":["Verwenden Sie das Banner als Seitenhintergrund"],"Form Width":["Formularbreite"],"URL":["URL"],"URL Slug":["URL-Slug"],"The last part of the URL.":["Der letzte Teil der URL."],"Learn more.":["Erfahren Sie mehr."],"SureForms Description":["SureForms Beschreibung"],"Form Options":["Formularoptionen"],"Form Shortcode":["Formular-Kurzcode"],"Paste this shortcode on the page or post to render this form.":["F\u00fcgen Sie diesen Shortcode auf der Seite oder im Beitrag ein, um dieses Formular anzuzeigen."],"Spam Protection":["Spam-Schutz"],"Auto":["Auto"],"Normal":["Normal"],"%":["%"],"Top":["Oben"],"Bottom":["Unten"],"Solid":["Fest"],"Width":["Breite"],"Size":["Gr\u00f6\u00dfe"],"EM":["EM"],"Padding":["Polsterung"],"Color 1":["Farbe 1"],"Color 2":["Farbe 2"],"Type":["Typ"],"Linear":["Linear"],"Radial":["Radial"],"Location 1":["Standort 1"],"Location 2":["Standort 2"],"Angle":["Winkel"],"Classic":["Klassisch"],"Gradient":["Gradient"],"Background":["Hintergrund"],"Cover":["Abdeckung"],"Contain":["Enthalten"],"Overlay":["\u00dcberlagerung"],"No Repeat":["Keine Wiederholung"],"Overlay Opacity":["\u00dcberlagerungsdeckkraft"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Klassennamen sollten durch Leerzeichen getrennt werden. Jeder Klassenname darf nicht mit einer Ziffer, einem Bindestrich oder einem Unterstrich beginnen. Sie d\u00fcrfen nur Buchstaben (einschlie\u00dflich Unicode-Zeichen), Zahlen, Bindestriche und Unterstriche enthalten."],"Conversational Layout":["Konversationelles Layout"],"Unlock Conversational Forms":["Konversationelle Formulare freischalten"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Mit dem SureForms Pro Plan k\u00f6nnen Sie Ihre Formulare in ansprechende, dialogorientierte Layouts verwandeln, um ein nahtloses Benutzererlebnis zu schaffen."],"Premium":["Premium"],"Overlay Type":["Overlay-Typ"],"Image Overlay Color":["Bild\u00fcberlagerungsfarbe"],"Image Position":["Bildposition"],"Attachment":["Anhang"],"Fixed":["Fest"],"Blend Mode":["Mischmodus"],"Multiply":["Multiplizieren"],"Screen":["Bildschirm"],"Darken":["Verdunkeln"],"Lighten":["Erleichtern"],"Color Dodge":["Farb-Abwedeln"],"Saturation":["S\u00e4ttigung"],"Repeat-x":["Wiederholen-x"],"Repeat-y":["Wiederhole-y"],"PX":["PX"],"Button":["Schaltfl\u00e4che"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Mit OttoKit verbinden"],"SUREFORMS PREMIUM FIELDS":["SUREFORMS PREMIUM-FELDER"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Die aktuelle 'Von-E-Mail'-Adresse stimmt nicht mit Ihrem Website-Domainnamen (%1$s) \u00fcberein. Dies kann dazu f\u00fchren, dass Ihre Benachrichtigungs-E-Mails blockiert oder als Spam markiert werden. Alternativ versuchen Sie, eine Von-Adresse zu verwenden, die mit Ihrem Website-Domainnamen \u00fcbereinstimmt (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Die aktuelle 'Von-E-Mail'-Adresse stimmt nicht mit Ihrem Website-Domainnamen (%s) \u00fcberein. Dies kann dazu f\u00fchren, dass Ihre Benachrichtigungs-E-Mails blockiert oder als Spam markiert werden."],"We strongly recommend that you install the free ":["Wir empfehlen Ihnen dringend, die kostenlose"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["Plugin! Der Einrichtungsassistent macht es einfach, Ihre E-Mails zu reparieren."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Alternativ k\u00f6nnen Sie versuchen, eine Absenderadresse zu verwenden, die mit Ihrer Website-Domain \u00fcbereinstimmt (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Bitte geben Sie eine g\u00fcltige E-Mail-Adresse ein. Ihre Benachrichtigungen werden nicht gesendet, wenn das Feld nicht korrekt ausgef\u00fcllt ist."],"From Name":["Von Name"],"From Email":["Von E-Mail"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Die aktuelle 'Von-E-Mail'-Adresse stimmt m\u00f6glicherweise nicht mit Ihrem Website-Domainnamen (%1$s) \u00fcberein. Dies kann dazu f\u00fchren, dass Ihre Benachrichtigungs-E-Mails blockiert oder als Spam markiert werden. Alternativ versuchen Sie, eine Von-Adresse zu verwenden, die mit Ihrem Website-Domainnamen \u00fcbereinstimmt (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Die aktuelle 'Von-E-Mail'-Adresse stimmt m\u00f6glicherweise nicht mit Ihrem Website-Domainnamen (%s) \u00fcberein. Dies kann dazu f\u00fchren, dass Ihre Benachrichtigungs-E-Mails blockiert oder als Spam markiert werden."],"Border Radius":["Randradius"],"Form Theme":["Formular-Thema"],"Instant Form Padding":["Sofortige Formularpolsterung"],"Instant Form Border Radius":["Sofortige Formular-Randradius"],"Select Gradient":["Gradient ausw\u00e4hlen"],"Upgrade Now":["Jetzt upgraden"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["Eintr\u00e4ge, die \u00e4lter als die ausgew\u00e4hlten Tage sind, werden gel\u00f6scht."],"Entries Time Period":["Eintr\u00e4ge Zeitraum"],"Custom CSS Panel":["Benutzerdefiniertes CSS-Panel"],"Notifications can use only one From Email so please enter a single address.":["Benachrichtigungen k\u00f6nnen nur eine Absender-E-Mail verwenden, daher geben Sie bitte eine einzelne Adresse ein."],"Email Notifications":["E-Mail-Benachrichtigungen"],"Actions":["Aktionen"],"Duplicate":["Duplikat"],"Delete":["L\u00f6schen"],"Select Page to redirect":["Seite zum Weiterleiten ausw\u00e4hlen"],"Search for a page":["Suche nach einer Seite"],"Select a page":["W\u00e4hlen Sie eine Seite aus"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Speichern"],"Login":["Anmelden"],"Register":["Registrieren"],"Date":["Datum"],"Advanced Settings":["Erweiterte Einstellungen"],"Form Restriction":["Formulareinschr\u00e4nkung"],"Maximum Number of Entries":["Maximale Anzahl von Eintr\u00e4gen"],"Maximum Entries":["Maximale Eintr\u00e4ge"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["Die Einstellung des Zeitraums funktioniert gem\u00e4\u00df der Zeitzone Ihrer WordPress-Website. Klicken Sie hier<\/a>, um Ihre allgemeinen WordPress-Einstellungen zu \u00f6ffnen, wo Sie diese \u00fcberpr\u00fcfen und aktualisieren k\u00f6nnen."],"Click here":["Klicken Sie hier"],"Response Description After Maximum Entries":["Antwortbeschreibung nach maximalen Eintr\u00e4gen"],"All changes will be saved automatically when you press back.":["Alle \u00c4nderungen werden automatisch gespeichert, wenn Sie zur\u00fcck dr\u00fccken."],"Repeater":["Wiederholer"],"OttoKit Settings":["OttoKit-Einstellungen"],"Get Started":["Loslegen"],"Connect Native Integrations with SureForms":["Native Integrationen mit SureForms verbinden"],"Expected format for emails - email@sureforms.com or John Doe ":["Erwartetes Format f\u00fcr E-Mails - email@sureforms.com oder John Doe "],"delete":["l\u00f6schen"],"Please type \"%s\" in the input box":["Bitte geben Sie \"%s\" in das Eingabefeld ein"],"To confirm, type \"%s\" in the box below:":["Um zu best\u00e4tigen, geben Sie \"%s\" in das Feld unten ein:"],"Type \"%s\"":["Geben Sie \"%s\" ein"],"Payment":["Zahlung"],"%s - Order ID":["%s - Bestell-ID"],"%s - Amount":["%s - Betrag"],"%s - Customer Email":["%s - Kunden-E-Mail"],"%s - Customer Name":["%s - Kundenname"],"%s - Status":["%s - Status"],"Add custom CSS rules to style this specific form independently of global styles.":["F\u00fcgen Sie benutzerdefinierte CSS-Regeln hinzu, um dieses spezielle Formular unabh\u00e4ngig von globalen Stilen zu gestalten."],"Spam Protection Type":["Spam-Schutztyp"],"Select Security Type":["Sicherheitstyp ausw\u00e4hlen"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Hinweis: Die Verwendung verschiedener reCAPTCHA-Versionen (V2-Checkbox und V3) auf derselben Seite f\u00fchrt zu Konflikten zwischen den Versionen. Bitte vermeiden Sie die Verwendung unterschiedlicher Versionen auf derselben Seite."],"Select Version":["Version ausw\u00e4hlen"],"Please configure the API keys correctly from the settings":["Bitte konfigurieren Sie die API-Schl\u00fcssel korrekt in den Einstellungen."],"Control email alerts sent to admins or users after a form submission.":["Steuern Sie E-Mail-Benachrichtigungen, die nach einer Formular\u00fcbermittlung an Administratoren oder Benutzer gesendet werden."],"Customize the confirmation message or redirect the users after submitting the form.":["Passen Sie die Best\u00e4tigungsnachricht an oder leiten Sie die Benutzer nach dem Absenden des Formulars weiter."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Legen Sie fest, wie oft ein Formular eingereicht werden kann, und verwalten Sie Compliance-Optionen, einschlie\u00dflich DSGVO und Datenaufbewahrung."],"Go to OttoKit Settings":["Gehe zu den OttoKit-Einstellungen"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Verbinden Sie SureForms mit Ihren Lieblings-Apps, um Aufgaben zu automatisieren und Daten nahtlos zu synchronisieren."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Schalten Sie leistungsstarke Integrationen im Premium-Plan frei, um Ihre Arbeitsabl\u00e4ufe zu automatisieren und SureForms direkt mit Ihren Lieblingstools zu verbinden."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Senden Sie Formular\u00fcbermittlungen direkt an CRMs, E-Mail- und Marketingplattformen."],"Automate repetitive tasks with seamless data syncing.":["Automatisiere repetitive Aufgaben mit nahtloser Datensynchronisierung."],"Access exclusive native integrations for faster workflows.":["Zugriff auf exklusive native Integrationen f\u00fcr schnellere Arbeitsabl\u00e4ufe."],"PDF Generation":["PDF-Erstellung"],"Generate and customize PDF copies of form submissions.":["Erstellen und anpassen von PDF-Kopien von Formulareinsendungen."],"Generate Submission PDFs":["Einreichungs-PDFs erstellen"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Verwandeln Sie jeden Formulareintrag in eine polierte PDF-Datei, die sich perfekt f\u00fcr Berichte, Aufzeichnungen oder zum Teilen eignet."],"Automatically generate PDFs from your form submissions.":["Automatisch PDFs aus Ihren Formulareinsendungen erstellen."],"Customize PDF templates with your branding.":["Passen Sie PDF-Vorlagen mit Ihrem Branding an."],"Download or email PDFs instantly.":["Laden Sie PDFs sofort herunter oder senden Sie sie per E-Mail."],"User Registration":["Benutzerregistrierung"],"Onboard new users or update existing accounts through beautiful looking forms.":["Neue Benutzer an Bord nehmen oder bestehende Konten \u00fcber ansprechend gestaltete Formulare aktualisieren."],"Register Users with SureForms":["Benutzer mit SureForms registrieren"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Optimieren Sie den gesamten Benutzer-Onboarding-Prozess f\u00fcr Ihre Websites mit nahtlosen, formularbasierten Anmeldungen und Registrierungen."],"Register new users directly via your form submissions.":["Registrieren Sie neue Benutzer direkt \u00fcber Ihre Formular\u00fcbermittlungen."],"Create or update existing accounts by mapping form data to user fields.":["Erstellen oder aktualisieren Sie bestehende Konten, indem Sie Formulardaten auf Benutzerfelder abbilden."],"Assign roles and control access automatically.":["Rollen zuweisen und den Zugriff automatisch steuern."],"Post Feed":["Beitrags-Feed"],"Transform your form submission into WordPress posts.":["Verwandeln Sie Ihre Formular\u00fcbermittlung in WordPress-Beitr\u00e4ge."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Formulareinsendungen automatisch in WordPress-Beitr\u00e4ge, Seiten oder benutzerdefinierte Beitragstypen umwandeln. Sparen Sie viel Zeit und lassen Sie Ihre Formulare Inhalte direkt ver\u00f6ffentlichen."],"Create posts, pages, or CPTs from your form entries.":["Erstellen Sie Beitr\u00e4ge, Seiten oder CPTs aus Ihren Formulareintr\u00e4gen."],"Map form fields to your post fields easily.":["Ordnen Sie Formularfelder einfach Ihren Beitragsfeldern zu."],"Automate the content publishing flow with few simple steps.":["Automatisieren Sie den Ver\u00f6ffentlichungsprozess f\u00fcr Inhalte mit wenigen einfachen Schritten."],"Automations":["Automatisierungen"],"Unlock Advanced Styling":["Erweiterte Stiloptionen freischalten"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Erhalten Sie die volle Kontrolle \u00fcber das Erscheinungsbild Ihres Formulars mit benutzerdefinierten Farben, Schriftarten und Layouts."],"Button Alignment":["Schaltfl\u00e4chenanordnung"],"Add Custom CSS Class(es)":["Benutzerdefinierte CSS-Klasse(n) hinzuf\u00fcgen"],"Set the total number of submissions allowed for this form.":["Legen Sie die Gesamtanzahl der zul\u00e4ssigen Einreichungen f\u00fcr dieses Formular fest."],"Save & Progress":["Speichern & Fortfahren"],"Allow users to save their progress and continue form completion later.":["Erm\u00f6glichen Sie Benutzern, ihren Fortschritt zu speichern und die Formularausf\u00fcllung sp\u00e4ter fortzusetzen."],"Save & Progress in SureForms":["Speichern & Fortschritt in SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["Geben Sie Ihren Nutzern die Flexibilit\u00e4t, Formulare in ihrem eigenen Tempo auszuf\u00fcllen, indem Sie ihnen erlauben, den Fortschritt zu speichern und jederzeit zur\u00fcckzukehren."],"Let users pause long or multi-step forms and continue later.":["Lassen Sie Benutzer lange oder mehrstufige Formulare pausieren und sp\u00e4ter fortsetzen."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Reduzieren Sie das Abbrechen von Formularen mit praktischen Wiederaufnahme-Links und greifen Sie von \u00fcberall auf deren Fortschritt zu."],"Improve user experience for lengthy, complex, or multi-page forms.":["Verbessern Sie die Benutzererfahrung f\u00fcr lange, komplexe oder mehrseitige Formulare."],"This form is not yet available. Please check back after the scheduled start time.":["Dieses Formular ist noch nicht verf\u00fcgbar. Bitte schauen Sie nach der geplanten Startzeit wieder vorbei."],"This form is no longer accepting submissions. The submission period has ended.":["Dieses Formular nimmt keine Einsendungen mehr an. Die Einreichungsfrist ist abgelaufen."],"The start date and time must be before the end date and time.":["Das Startdatum und die Startzeit m\u00fcssen vor dem Enddatum und der Endzeit liegen."],"Form Scheduling":["Formularplanung"],"Enable Form Scheduling":["Formularplanung aktivieren"],"Set a time period during which this form will be available for submissions.":["Legen Sie einen Zeitraum fest, in dem dieses Formular f\u00fcr Einreichungen verf\u00fcgbar sein wird."],"Start Date & Time":["Startdatum und -zeit"],"End Date & Time":["Enddatum & Uhrzeit"],"Response Description Before Start Date":["Antwortbeschreibung vor dem Startdatum"],"Response Description After End Date":["Antwortbeschreibung nach dem Enddatum"],"Conditional Confirmations":["Bedingte Best\u00e4tigungen"],"Set up the message or redirect users will see after submitting the form.":["Richten Sie die Nachricht ein oder leiten Sie die Benutzer weiter, die sie nach dem Absenden des Formulars sehen werden."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Zeigen Sie die richtige Nachricht dem richtigen Benutzer basierend darauf, wie sie reagieren. Personalisieren Sie Best\u00e4tigungen mit intelligenten Bedingungen und f\u00fchren Sie Benutzer automatisch zum n\u00e4chsten besten Schritt."],"Display different confirmation messages based on form responses.":["Zeigen Sie je nach Formularantworten unterschiedliche Best\u00e4tigungsnachrichten an."],"Redirect users to specific pages or URLs conditionally.":["Leiten Sie Benutzer bedingt auf bestimmte Seiten oder URLs weiter."],"Create personalized thank-you messages without extra forms.":["Erstellen Sie personalisierte Dankesnachrichten ohne zus\u00e4tzliche Formulare."],"Lost Password":["Passwort vergessen"],"Reset Password":["Passwort zur\u00fccksetzen"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["W\u00e4hlen Sie einen Spam-Schutzdienst aus. Konfigurieren Sie API-Schl\u00fcssel in den globalen Einstellungen, bevor Sie ihn aktivieren."],"Send as Raw HTML":["Als Roh-HTML senden"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Wenn aktiviert, wird der HTML-Inhalt der E-Mail genau so beibehalten, wie er geschrieben wurde, und in eine professionelle E-Mail-Vorlage eingebettet."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["Intelligente Tags, die auf vom Benutzer \u00fcbermittelte Felder verweisen, werden im Roh-HTML-Modus nicht maskiert. Vermeiden Sie es, nicht vertrauensw\u00fcrdige Feldwerte direkt in den E-Mail-Text einzuf\u00fcgen."],"Please provide a recipient email address and subject line.":["Bitte geben Sie eine Empf\u00e4nger-E-Mail-Adresse und eine Betreffzeile an."],"Email notification duplicated!":["E-Mail-Benachrichtigung dupliziert!"],"Are you sure you want to delete this email notification?":["Sind Sie sicher, dass Sie diese E-Mail-Benachrichtigung l\u00f6schen m\u00f6chten?"],"Email notification deleted!":["E-Mail-Benachrichtigung gel\u00f6scht!"],"URL is missing Top Level Domain (TLD).":["Die URL fehlt die Top-Level-Domain (TLD)."],"This form is now closed as the maximum number of entries has been received.":["Dieses Formular ist jetzt geschlossen, da die maximale Anzahl an Eintr\u00e4gen erreicht wurde."],"Publish Your Form":["Ver\u00f6ffentlichen Sie Ihr Formular"],"Enable This to Instantly Publish the Form":["Aktivieren Sie dies, um das Formular sofort zu ver\u00f6ffentlichen"],"Style Your Instant Form Page Here":["Gestalten Sie hier Ihre Sofortformularseite"],"Quizzes":["Quizze"],"%s - Description":["%s - Beschreibung"],"Send entries to 100+ popular apps.":["Eintr\u00e4ge an \u00fcber 100 beliebte Apps senden."],"Build automated workflows that run instantly.":["Erstellen Sie automatisierte Workflows, die sofort ausgef\u00fchrt werden."],"Create custom app integrations using our Custom App feature.":["Erstellen Sie benutzerdefinierte App-Integrationen mit unserer benutzerdefinierten App-Funktion."],"Keep your tools in sync automatically.":["Halten Sie Ihre Werkzeuge automatisch synchron."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["Dies wird OttoKit auf Ihrer WordPress-Seite installieren und aktivieren, um Automatisierungsfunktionen zu erm\u00f6glichen."],"Automate Your Forms with OttoKit":["Automatisieren Sie Ihre Formulare mit OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Jede Formular\u00fcbermittlung sollte etwas ausl\u00f6sen \u2014 eine Slack-Benachrichtigung, einen CRM-Lead, eine Follow-up-E-Mail oder eine neue Zeile in Google Sheets."],"Create interactive quizzes to engage your audience and gather insights.":["Erstellen Sie interaktive Quizze, um Ihr Publikum zu begeistern und Erkenntnisse zu gewinnen."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Entwerfen Sie ansprechende Quizze mit verschiedenen Fragetypen, personalisiertem Feedback und automatisierter Bewertung, um Ihr Publikum zu fesseln und wertvolle Einblicke zu gewinnen."],"Create interactive quizzes with multiple question types.":["Erstellen Sie interaktive Quizze mit verschiedenen Fragetypen."],"Provide personalized feedback based on user responses.":["Geben Sie personalisiertes Feedback basierend auf den Benutzerantworten."],"Automate scoring and lead segmentation for better insights.":["Automatisieren Sie die Bewertung und Lead-Segmentierung f\u00fcr bessere Einblicke."],"Heading 1":["\u00dcberschrift 1"],"Heading 2":["\u00dcberschrift 2"],"Heading 3":["\u00dcberschrift 3"],"Heading 4":["\u00dcberschrift 4"],"Heading 5":["\u00dcberschrift 5"],"Heading 6":["\u00dcberschrift 6"],"Form settings saved.":["Formulareinstellungen gespeichert."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["Die Eingabebegrenzung st\u00fctzt sich auf gespeicherte Eintr\u00e4ge, um Einsendungen zu z\u00e4hlen. Solange in den Compliance-Einstellungen die Option \"Eingabedaten nach dem Absenden des Formulars niemals speichern\" aktiviert ist, wird dieses Limit nicht durchgesetzt. Deaktivieren Sie diese Option oder entfernen Sie die Eingabebegrenzung, um diese Funktion zu nutzen."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Einstellungen gespeichert, aber die Beitragseigenschaften (Passwort \/ Titel \/ Inhalt) konnten nicht aktualisiert werden. Versuchen Sie es erneut, um sie zu speichern."],"Failed to save form settings.":["Speichern der Formulareinstellungen fehlgeschlagen."],"Saving\u2026":["Speichern\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Wenn aktiviert, speichert dieses Formular die IP-Adresse des Benutzers, den Browsernamen und den Ger\u00e4tenamen nicht in den Eintr\u00e4gen."],"Failed to save. Please try again.":["Speichern fehlgeschlagen. Bitte versuchen Sie es erneut."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["reCAPTCHA-API-Schl\u00fcssel f\u00fcr die ausgew\u00e4hlte Version sind nicht konfiguriert. Stellen Sie sie in den globalen Einstellungen ein."],"Please select a reCAPTCHA version.":["Bitte w\u00e4hlen Sie eine reCAPTCHA-Version aus."],"hCaptcha API keys are not configured. Set them in Global Settings.":["hCaptcha-API-Schl\u00fcssel sind nicht konfiguriert. Legen Sie sie in den globalen Einstellungen fest."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["Cloudflare Turnstile-API-Schl\u00fcssel sind nicht konfiguriert. Legen Sie sie in den globalen Einstellungen fest."],"Form data":["Formulardaten"],"Some fields need attention":["Einige Felder ben\u00f6tigen Aufmerksamkeit"],"Unsaved changes":["Nicht gespeicherte \u00c4nderungen"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Eine Empf\u00e4nger-E-Mail-Adresse und eine Betreffzeile sind erforderlich, bevor diese Benachrichtigung gespeichert werden kann. Beheben Sie die hervorgehobenen Felder oder verwerfen Sie Ihre \u00c4nderungen, um zur\u00fcckzugehen."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Sie haben ungespeicherte \u00c4nderungen f\u00fcr diese Benachrichtigung. Verwerfen Sie sie, um zur\u00fcckzugehen, oder bleiben Sie, um sie zu speichern."],"Discard & go back":["Verwerfen & zur\u00fcckgehen"],"Stay & fix":["Bleiben & reparieren"],"Keep editing":["Weiter bearbeiten"],"Please provide a recipient email address.":["Bitte geben Sie eine Empf\u00e4nger-E-Mail-Adresse an."],"Please provide a subject line.":["Bitte geben Sie eine Betreffzeile an."],"Please provide a custom URL.":["Bitte geben Sie eine benutzerdefinierte URL an."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Sie haben ungespeicherte \u00c4nderungen. Verwerfen Sie diese, um fortzufahren, oder bleiben Sie, um Ihre \u00c4nderungen zu speichern."],"Discard & continue":["Verwerfen & fortfahren"],"Quill heading picker: default paragraph style\u0004Normal":["Normal"]}}} \ No newline at end of file +{"translation-revision-date":"2024-12-13T16:14:52+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Status"],"Form":["Formular"],"Fields":["Felder"],"Image":["Bild"],"Activated":["Aktiviert"],"Activate":["Aktivieren"],"Submit":["Einreichen"],"Global Settings":["Globale Einstellungen"],"Form Title":["Formulartitel"],"Edit":["Bearbeiten"],"Please enter a valid URL.":["Bitte geben Sie eine g\u00fcltige URL ein."],"Desktop":["Desktop"],"Medium":["Mittel"],"Mobile":["Mobil"],"Repeat":["Wiederholen"],"Scroll":["Scrollen"],"Signature":["Unterschrift"],"Tablet":["Tablet"],"Upload":["Hochladen"],"Basic":["Grundlegend"],"Form Settings":["Formulareinstellungen"],"General":["Allgemein"],"Style":["Stil"],"Advanced":["Fortgeschritten"],"No tags available":["Keine Tags verf\u00fcgbar"],"Device":["Ger\u00e4t"],"Select Shortcodes":["Shortcodes ausw\u00e4hlen"],"Page Break Label":["Seitenumbruch-Label"],"Next":["Weiter"],"Back":["Zur\u00fcck"],"Reset":["Zur\u00fccksetzen"],"Generic tags":["Allgemeine Tags"],"Pixel":["Pixel"],"Em":["Em"],"Select Units":["Einheiten ausw\u00e4hlen"],"%s units":["%s Einheiten"],"Margin":["Marge"],"None":["Keine"],"Custom":["Benutzerdefiniert"],"Please add a option props to MultiButtonsControl":["Bitte f\u00fcgen Sie eine Option props zu MultiButtonsControl hinzu"],"Confirm":["Best\u00e4tigen"],"Cancel":["Abbrechen"],"Processing\u2026":["Verarbeitung\u2026"],"Select Video":["Video ausw\u00e4hlen"],"Change Video":["Video \u00e4ndern"],"Select Lottie Animation":["Lottie-Animation ausw\u00e4hlen"],"Change Lottie Animation":["Lottie-Animation \u00e4ndern"],"Upload SVG":["SVG hochladen"],"Change SVG":["SVG \u00e4ndern"],"Select Image":["Bild ausw\u00e4hlen"],"Change Image":["Bild \u00e4ndern"],"Upload SVG?":["SVG hochladen?"],"Upload SVG can be potentially risky. Are you sure?":["Das Hochladen von SVG kann potenziell riskant sein. Bist du sicher?"],"Upload Anyway":["Trotzdem hochladen"],"Full Width":["Volle Breite"],"Install":["Installieren"],"Plugin Installation failed, Please try again later.":["Plugin-Installation fehlgeschlagen, bitte versuchen Sie es sp\u00e4ter erneut."],"Plugin activation failed, Please try again later.":["Plugin-Aktivierung fehlgeschlagen, bitte versuchen Sie es sp\u00e4ter erneut."],"Integrations":["Integrationen"],"%s Removed from Quick Action Bar.":["%s wurde aus der Schnellzugriffsleiste entfernt."],"Add to Quick Action Bar":["Zur Schnellzugriffsleiste hinzuf\u00fcgen"],"%s Added to Quick Action Bar.":["%s zur Schnellzugriffsleiste hinzugef\u00fcgt."],"Already Present in Quick Action Bar":["Bereits in der Schnellzugriffsleiste vorhanden"],"No results found.":["Keine Ergebnisse gefunden."],"data object is empty":["Datenobjekt ist leer"],"Add blocks to Quick Action Bar":["Bl\u00f6cke zur Schnellzugriffsleiste hinzuf\u00fcgen"],"Re-arrange block inside Quick Action Bar":["Block im Schnellaktionsbereich neu anordnen"],"Upgrade":["Aktualisieren"],"Connecting\u2026":["Verbinden\u2026"],"Install & Activate":["Installieren & Aktivieren"],"Compliance Settings":["Compliance-Einstellungen"],"Enable GDPR Compliance":["Aktivieren Sie die DSGVO-Konformit\u00e4t"],"Never store entry data after form submission":["Speichern Sie niemals Eingabedaten nach dem Absenden des Formulars"],"When enabled this form will never store Entries.":["Wenn aktiviert, speichert dieses Formular niemals Eintr\u00e4ge."],"Automatically delete entries":["Eintr\u00e4ge automatisch l\u00f6schen"],"When enabled this form will automatically delete entries after a certain period of time.":["Wenn aktiviert, wird dieses Formular Eintr\u00e4ge nach einer bestimmten Zeitspanne automatisch l\u00f6schen."],"Entries older than the days set will be deleted automatically.":["Eintr\u00e4ge, die \u00e4lter sind als die festgelegten Tage, werden automatisch gel\u00f6scht."],"Custom CSS":["Benutzerdefiniertes CSS"],"The following CSS styles added below will only apply to this form container.":["Die folgenden unten hinzugef\u00fcgten CSS-Stile gelten nur f\u00fcr diesen Formularcontainer."],"Visual":["Visuell"],"HTML":["HTML"],"All Data":["Alle Daten"],"Add Shortcode":["Shortcode hinzuf\u00fcgen"],"Form input tags":["Formulareingabetags"],"Comma separated values are also accepted.":["Kommagetrennte Werte werden ebenfalls akzeptiert."],"Email Notification":["E-Mail-Benachrichtigung"],"Name":["Name"],"Send Email To":["E-Mail senden an"],"Subject":["Betreff"],"CC":["CC"],"BCC":["BCC"],"Reply To":["Antworten an"],"Add Notification":["Benachrichtigung hinzuf\u00fcgen"],"Add Key":["Schl\u00fcssel hinzuf\u00fcgen"],"Add Value":["Wert hinzuf\u00fcgen"],"Add":["Hinzuf\u00fcgen"],"Confirmation Message":["Best\u00e4tigungsnachricht"],"After Form Submission":["Nach dem Absenden des Formulars"],"Hide Form":["Formular ausblenden"],"Reset Form":["Formular zur\u00fccksetzen"],"Custom URL":["Benutzerdefinierte URL"],"Add Query Parameters":["Abfrageparameter hinzuf\u00fcgen"],"Select if you want to add key-value pairs for form fields to include in query parameters":["W\u00e4hlen Sie, ob Sie Schl\u00fcssel-Wert-Paare f\u00fcr Formularfelder hinzuf\u00fcgen m\u00f6chten, die in Abfrageparametern enthalten sein sollen"],"Query Parameters":["Abfrageparameter"],"Please select a page.":["Bitte w\u00e4hlen Sie eine Seite aus."],"Suggestion: URL should use HTTPS":["Vorschlag: Die URL sollte HTTPS verwenden"],"Success Message":["Erfolgsmeldung"],"Redirect":["Weiterleiten"],"Redirect to":["Weiterleiten zu"],"Page":["Seite"],"Form Confirmation":["Formularbest\u00e4tigung"],"Confirmation Type":["Best\u00e4tigungstyp"],"Use Labels as Placeholders":["Verwenden Sie Labels als Platzhalter"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["Diese Einstellung platziert die Beschriftungen innerhalb der Felder als Platzhalter (wo m\u00f6glich). Diese Einstellung wirkt sich nur auf der Live-Seite aus, nicht in der Editor-Vorschau."],"Page Break":["Seitenumbruch"],"Show Labels":["Etiketten anzeigen"],"First Page Label":["Erstes Seitenetikett"],"Progress Indicator":["Fortschrittsanzeige"],"Progress Bar":["Fortschrittsbalken"],"Connector":["Verbinder"],"Steps":["Schritte"],"Next Button Text":["Text der Schaltfl\u00e4che \"Weiter\""],"Back Button Text":["Zur\u00fcck-Schaltfl\u00e4chentext"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["Sind Sie sicher, dass Sie schlie\u00dfen m\u00f6chten? Ihre nicht gespeicherten \u00c4nderungen gehen verloren, da Sie einige Validierungsfehler haben."],"There are few unsaved changes. Please save your changes to reflect the updates.":["Es gibt einige nicht gespeicherte \u00c4nderungen. Bitte speichern Sie Ihre \u00c4nderungen, um die Aktualisierungen zu \u00fcbernehmen."],"Form Behavior":["Formverhalten"],"Clear":["Klar"],"Select Color":["Farbe ausw\u00e4hlen"],"Primary Color":["Prim\u00e4rfarbe"],"Text Color":["Textfarbe"],"Text Color on Primary":["Textfarbe auf Prim\u00e4r"],"Field Spacing":["Feldabstand"],"Small":["Klein"],"Large":["Gro\u00df"],"Left":["Links"],"Center":["Zentrum"],"Right":["Richtig"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Unsichtbar"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Datumsauswahl"],"Time Picker":["Zeitw\u00e4hler"],"Hidden":["Versteckt"],"Slider":["Schieberegler"],"Rating":["Bewertung"],"Upgrade to Unlock These Fields":["Upgrade, um diese Felder freizuschalten"],"Add Block":["Block hinzuf\u00fcgen"],"Customize with SureForms":["Anpassen mit SureForms"],"Page break":["Seitenumbruch"],"Previous":["Zur\u00fcck"],"Thank you":["Danke"],"Form submitted successfully!":["Formular erfolgreich eingereicht!"],"Instant Form":["Sofortformular"],"Enable Instant Form":["Sofortformular aktivieren"],"Enable Preview":["Vorschau aktivieren"],"Show Title":["Titel anzeigen"],"Site Logo":["Seitenlogo"],"Banner Background":["Banner-Hintergrund"],"Color":["Farbe"],"Upload Image":["Bild hochladen"],"Background Color":["Hintergrundfarbe"],"Use banner as page background":["Verwenden Sie das Banner als Seitenhintergrund"],"Form Width":["Formularbreite"],"URL":["URL"],"URL Slug":["URL-Slug"],"The last part of the URL.":["Der letzte Teil der URL."],"Learn more.":["Erfahren Sie mehr."],"SureForms Description":["SureForms Beschreibung"],"Form Options":["Formularoptionen"],"Form Shortcode":["Formular-Kurzcode"],"Paste this shortcode on the page or post to render this form.":["F\u00fcgen Sie diesen Shortcode auf der Seite oder im Beitrag ein, um dieses Formular anzuzeigen."],"Spam Protection":["Spam-Schutz"],"Auto":["Auto"],"Normal":["Normal"],"%":["%"],"Top":["Oben"],"Bottom":["Unten"],"Solid":["Fest"],"Width":["Breite"],"Size":["Gr\u00f6\u00dfe"],"EM":["EM"],"Padding":["Polsterung"],"Color 1":["Farbe 1"],"Color 2":["Farbe 2"],"Type":["Typ"],"Linear":["Linear"],"Radial":["Radial"],"Location 1":["Standort 1"],"Location 2":["Standort 2"],"Angle":["Winkel"],"Classic":["Klassisch"],"Gradient":["Gradient"],"Background":["Hintergrund"],"Cover":["Abdeckung"],"Contain":["Enthalten"],"Overlay":["\u00dcberlagerung"],"No Repeat":["Keine Wiederholung"],"Overlay Opacity":["\u00dcberlagerungsdeckkraft"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Klassennamen sollten durch Leerzeichen getrennt werden. Jeder Klassenname darf nicht mit einer Ziffer, einem Bindestrich oder einem Unterstrich beginnen. Sie d\u00fcrfen nur Buchstaben (einschlie\u00dflich Unicode-Zeichen), Zahlen, Bindestriche und Unterstriche enthalten."],"Conversational Layout":["Konversationelles Layout"],"Unlock Conversational Forms":["Konversationelle Formulare freischalten"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Mit dem SureForms Pro Plan k\u00f6nnen Sie Ihre Formulare in ansprechende, dialogorientierte Layouts verwandeln, um ein nahtloses Benutzererlebnis zu schaffen."],"Premium":["Premium"],"Overlay Type":["Overlay-Typ"],"Image Overlay Color":["Bild\u00fcberlagerungsfarbe"],"Image Position":["Bildposition"],"Attachment":["Anhang"],"Fixed":["Fest"],"Blend Mode":["Mischmodus"],"Multiply":["Multiplizieren"],"Screen":["Bildschirm"],"Darken":["Verdunkeln"],"Lighten":["Erleichtern"],"Color Dodge":["Farb-Abwedeln"],"Saturation":["S\u00e4ttigung"],"Repeat-x":["Wiederholen-x"],"Repeat-y":["Wiederhole-y"],"PX":["PX"],"Button":["Schaltfl\u00e4che"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Mit OttoKit verbinden"],"SUREFORMS PREMIUM FIELDS":["SUREFORMS PREMIUM-FELDER"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Die aktuelle 'Von-E-Mail'-Adresse stimmt nicht mit Ihrem Website-Domainnamen (%1$s) \u00fcberein. Dies kann dazu f\u00fchren, dass Ihre Benachrichtigungs-E-Mails blockiert oder als Spam markiert werden. Alternativ versuchen Sie, eine Von-Adresse zu verwenden, die mit Ihrem Website-Domainnamen \u00fcbereinstimmt (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Die aktuelle 'Von-E-Mail'-Adresse stimmt nicht mit Ihrem Website-Domainnamen (%s) \u00fcberein. Dies kann dazu f\u00fchren, dass Ihre Benachrichtigungs-E-Mails blockiert oder als Spam markiert werden."],"We strongly recommend that you install the free ":["Wir empfehlen Ihnen dringend, die kostenlose"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["Plugin! Der Einrichtungsassistent macht es einfach, Ihre E-Mails zu reparieren."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Alternativ k\u00f6nnen Sie versuchen, eine Absenderadresse zu verwenden, die mit Ihrer Website-Domain \u00fcbereinstimmt (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Bitte geben Sie eine g\u00fcltige E-Mail-Adresse ein. Ihre Benachrichtigungen werden nicht gesendet, wenn das Feld nicht korrekt ausgef\u00fcllt ist."],"From Name":["Von Name"],"From Email":["Von E-Mail"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Die aktuelle 'Von-E-Mail'-Adresse stimmt m\u00f6glicherweise nicht mit Ihrem Website-Domainnamen (%1$s) \u00fcberein. Dies kann dazu f\u00fchren, dass Ihre Benachrichtigungs-E-Mails blockiert oder als Spam markiert werden. Alternativ versuchen Sie, eine Von-Adresse zu verwenden, die mit Ihrem Website-Domainnamen \u00fcbereinstimmt (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Die aktuelle 'Von-E-Mail'-Adresse stimmt m\u00f6glicherweise nicht mit Ihrem Website-Domainnamen (%s) \u00fcberein. Dies kann dazu f\u00fchren, dass Ihre Benachrichtigungs-E-Mails blockiert oder als Spam markiert werden."],"Border Radius":["Randradius"],"Form Theme":["Formular-Thema"],"Instant Form Padding":["Sofortige Formularpolsterung"],"Instant Form Border Radius":["Sofortige Formular-Randradius"],"Select Gradient":["Gradient ausw\u00e4hlen"],"Upgrade Now":["Jetzt upgraden"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["Eintr\u00e4ge, die \u00e4lter als die ausgew\u00e4hlten Tage sind, werden gel\u00f6scht."],"Entries Time Period":["Eintr\u00e4ge Zeitraum"],"Custom CSS Panel":["Benutzerdefiniertes CSS-Panel"],"Notifications can use only one From Email so please enter a single address.":["Benachrichtigungen k\u00f6nnen nur eine Absender-E-Mail verwenden, daher geben Sie bitte eine einzelne Adresse ein."],"Email Notifications":["E-Mail-Benachrichtigungen"],"Actions":["Aktionen"],"Duplicate":["Duplikat"],"Delete":["L\u00f6schen"],"Select Page to redirect":["Seite zum Weiterleiten ausw\u00e4hlen"],"Search for a page":["Suche nach einer Seite"],"Select a page":["W\u00e4hlen Sie eine Seite aus"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Speichern"],"Login":["Anmelden"],"Register":["Registrieren"],"Date":["Datum"],"Advanced Settings":["Erweiterte Einstellungen"],"Form Restriction":["Formulareinschr\u00e4nkung"],"Maximum Number of Entries":["Maximale Anzahl von Eintr\u00e4gen"],"Maximum Entries":["Maximale Eintr\u00e4ge"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["Die Einstellung des Zeitraums funktioniert gem\u00e4\u00df der Zeitzone Ihrer WordPress-Website. Klicken Sie hier<\/a>, um Ihre allgemeinen WordPress-Einstellungen zu \u00f6ffnen, wo Sie diese \u00fcberpr\u00fcfen und aktualisieren k\u00f6nnen."],"Click here":["Klicken Sie hier"],"Response Description After Maximum Entries":["Antwortbeschreibung nach maximalen Eintr\u00e4gen"],"All changes will be saved automatically when you press back.":["Alle \u00c4nderungen werden automatisch gespeichert, wenn Sie zur\u00fcck dr\u00fccken."],"Repeater":["Wiederholer"],"OttoKit Settings":["OttoKit-Einstellungen"],"Get Started":["Loslegen"],"Connect Native Integrations with SureForms":["Native Integrationen mit SureForms verbinden"],"Expected format for emails - email@sureforms.com or John Doe ":["Erwartetes Format f\u00fcr E-Mails - email@sureforms.com oder John Doe "],"delete":["l\u00f6schen"],"Please type \"%s\" in the input box":["Bitte geben Sie \"%s\" in das Eingabefeld ein"],"To confirm, type \"%s\" in the box below:":["Um zu best\u00e4tigen, geben Sie \"%s\" in das Feld unten ein:"],"Type \"%s\"":["Geben Sie \"%s\" ein"],"Payment":["Zahlung"],"%s - Order ID":["%s - Bestell-ID"],"%s - Amount":["%s - Betrag"],"%s - Customer Email":["%s - Kunden-E-Mail"],"%s - Customer Name":["%s - Kundenname"],"%s - Status":["%s - Status"],"Add custom CSS rules to style this specific form independently of global styles.":["F\u00fcgen Sie benutzerdefinierte CSS-Regeln hinzu, um dieses spezielle Formular unabh\u00e4ngig von globalen Stilen zu gestalten."],"Spam Protection Type":["Spam-Schutztyp"],"Select Security Type":["Sicherheitstyp ausw\u00e4hlen"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Hinweis: Die Verwendung verschiedener reCAPTCHA-Versionen (V2-Checkbox und V3) auf derselben Seite f\u00fchrt zu Konflikten zwischen den Versionen. Bitte vermeiden Sie die Verwendung unterschiedlicher Versionen auf derselben Seite."],"Select Version":["Version ausw\u00e4hlen"],"Please configure the API keys correctly from the settings":["Bitte konfigurieren Sie die API-Schl\u00fcssel korrekt in den Einstellungen."],"Control email alerts sent to admins or users after a form submission.":["Steuern Sie E-Mail-Benachrichtigungen, die nach einer Formular\u00fcbermittlung an Administratoren oder Benutzer gesendet werden."],"Customize the confirmation message or redirect the users after submitting the form.":["Passen Sie die Best\u00e4tigungsnachricht an oder leiten Sie die Benutzer nach dem Absenden des Formulars weiter."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Legen Sie fest, wie oft ein Formular eingereicht werden kann, und verwalten Sie Compliance-Optionen, einschlie\u00dflich DSGVO und Datenaufbewahrung."],"Go to OttoKit Settings":["Gehe zu den OttoKit-Einstellungen"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Verbinden Sie SureForms mit Ihren Lieblings-Apps, um Aufgaben zu automatisieren und Daten nahtlos zu synchronisieren."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Schalten Sie leistungsstarke Integrationen im Premium-Plan frei, um Ihre Arbeitsabl\u00e4ufe zu automatisieren und SureForms direkt mit Ihren Lieblingstools zu verbinden."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Senden Sie Formular\u00fcbermittlungen direkt an CRMs, E-Mail- und Marketingplattformen."],"Automate repetitive tasks with seamless data syncing.":["Automatisiere repetitive Aufgaben mit nahtloser Datensynchronisierung."],"Access exclusive native integrations for faster workflows.":["Zugriff auf exklusive native Integrationen f\u00fcr schnellere Arbeitsabl\u00e4ufe."],"PDF Generation":["PDF-Erstellung"],"Generate and customize PDF copies of form submissions.":["Erstellen und anpassen von PDF-Kopien von Formulareinsendungen."],"Generate Submission PDFs":["Einreichungs-PDFs erstellen"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Verwandeln Sie jeden Formulareintrag in eine polierte PDF-Datei, die sich perfekt f\u00fcr Berichte, Aufzeichnungen oder zum Teilen eignet."],"Automatically generate PDFs from your form submissions.":["Automatisch PDFs aus Ihren Formulareinsendungen erstellen."],"Customize PDF templates with your branding.":["Passen Sie PDF-Vorlagen mit Ihrem Branding an."],"Download or email PDFs instantly.":["Laden Sie PDFs sofort herunter oder senden Sie sie per E-Mail."],"User Registration":["Benutzerregistrierung"],"Onboard new users or update existing accounts through beautiful looking forms.":["Neue Benutzer an Bord nehmen oder bestehende Konten \u00fcber ansprechend gestaltete Formulare aktualisieren."],"Register Users with SureForms":["Benutzer mit SureForms registrieren"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Optimieren Sie den gesamten Benutzer-Onboarding-Prozess f\u00fcr Ihre Websites mit nahtlosen, formularbasierten Anmeldungen und Registrierungen."],"Register new users directly via your form submissions.":["Registrieren Sie neue Benutzer direkt \u00fcber Ihre Formular\u00fcbermittlungen."],"Create or update existing accounts by mapping form data to user fields.":["Erstellen oder aktualisieren Sie bestehende Konten, indem Sie Formulardaten auf Benutzerfelder abbilden."],"Assign roles and control access automatically.":["Rollen zuweisen und den Zugriff automatisch steuern."],"Post Feed":["Beitrags-Feed"],"Transform your form submission into WordPress posts.":["Verwandeln Sie Ihre Formular\u00fcbermittlung in WordPress-Beitr\u00e4ge."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Formulareinsendungen automatisch in WordPress-Beitr\u00e4ge, Seiten oder benutzerdefinierte Beitragstypen umwandeln. Sparen Sie viel Zeit und lassen Sie Ihre Formulare Inhalte direkt ver\u00f6ffentlichen."],"Create posts, pages, or CPTs from your form entries.":["Erstellen Sie Beitr\u00e4ge, Seiten oder CPTs aus Ihren Formulareintr\u00e4gen."],"Map form fields to your post fields easily.":["Ordnen Sie Formularfelder einfach Ihren Beitragsfeldern zu."],"Automate the content publishing flow with few simple steps.":["Automatisieren Sie den Ver\u00f6ffentlichungsprozess f\u00fcr Inhalte mit wenigen einfachen Schritten."],"Automations":["Automatisierungen"],"Unlock Advanced Styling":["Erweiterte Stiloptionen freischalten"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Erhalten Sie die volle Kontrolle \u00fcber das Erscheinungsbild Ihres Formulars mit benutzerdefinierten Farben, Schriftarten und Layouts."],"Button Alignment":["Schaltfl\u00e4chenanordnung"],"Add Custom CSS Class(es)":["Benutzerdefinierte CSS-Klasse(n) hinzuf\u00fcgen"],"Set the total number of submissions allowed for this form.":["Legen Sie die Gesamtanzahl der zul\u00e4ssigen Einreichungen f\u00fcr dieses Formular fest."],"Save & Progress":["Speichern & Fortfahren"],"Allow users to save their progress and continue form completion later.":["Erm\u00f6glichen Sie Benutzern, ihren Fortschritt zu speichern und die Formularausf\u00fcllung sp\u00e4ter fortzusetzen."],"Save & Progress in SureForms":["Speichern & Fortschritt in SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["Geben Sie Ihren Nutzern die Flexibilit\u00e4t, Formulare in ihrem eigenen Tempo auszuf\u00fcllen, indem Sie ihnen erlauben, den Fortschritt zu speichern und jederzeit zur\u00fcckzukehren."],"Let users pause long or multi-step forms and continue later.":["Lassen Sie Benutzer lange oder mehrstufige Formulare pausieren und sp\u00e4ter fortsetzen."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Reduzieren Sie das Abbrechen von Formularen mit praktischen Wiederaufnahme-Links und greifen Sie von \u00fcberall auf deren Fortschritt zu."],"Improve user experience for lengthy, complex, or multi-page forms.":["Verbessern Sie die Benutzererfahrung f\u00fcr lange, komplexe oder mehrseitige Formulare."],"This form is not yet available. Please check back after the scheduled start time.":["Dieses Formular ist noch nicht verf\u00fcgbar. Bitte schauen Sie nach der geplanten Startzeit wieder vorbei."],"This form is no longer accepting submissions. The submission period has ended.":["Dieses Formular nimmt keine Einsendungen mehr an. Die Einreichungsfrist ist abgelaufen."],"The start date and time must be before the end date and time.":["Das Startdatum und die Startzeit m\u00fcssen vor dem Enddatum und der Endzeit liegen."],"Form Scheduling":["Formularplanung"],"Enable Form Scheduling":["Formularplanung aktivieren"],"Set a time period during which this form will be available for submissions.":["Legen Sie einen Zeitraum fest, in dem dieses Formular f\u00fcr Einreichungen verf\u00fcgbar sein wird."],"Start Date & Time":["Startdatum und -zeit"],"End Date & Time":["Enddatum & Uhrzeit"],"Response Description Before Start Date":["Antwortbeschreibung vor dem Startdatum"],"Response Description After End Date":["Antwortbeschreibung nach dem Enddatum"],"Conditional Confirmations":["Bedingte Best\u00e4tigungen"],"Set up the message or redirect users will see after submitting the form.":["Richten Sie die Nachricht ein oder leiten Sie die Benutzer weiter, die sie nach dem Absenden des Formulars sehen werden."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Zeigen Sie die richtige Nachricht dem richtigen Benutzer basierend darauf, wie sie reagieren. Personalisieren Sie Best\u00e4tigungen mit intelligenten Bedingungen und f\u00fchren Sie Benutzer automatisch zum n\u00e4chsten besten Schritt."],"Display different confirmation messages based on form responses.":["Zeigen Sie je nach Formularantworten unterschiedliche Best\u00e4tigungsnachrichten an."],"Redirect users to specific pages or URLs conditionally.":["Leiten Sie Benutzer bedingt auf bestimmte Seiten oder URLs weiter."],"Create personalized thank-you messages without extra forms.":["Erstellen Sie personalisierte Dankesnachrichten ohne zus\u00e4tzliche Formulare."],"Lost Password":["Passwort vergessen"],"Reset Password":["Passwort zur\u00fccksetzen"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["W\u00e4hlen Sie einen Spam-Schutzdienst aus. Konfigurieren Sie API-Schl\u00fcssel in den globalen Einstellungen, bevor Sie ihn aktivieren."],"Send as Raw HTML":["Als Roh-HTML senden"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Wenn aktiviert, wird der HTML-Inhalt der E-Mail genau so beibehalten, wie er geschrieben wurde, und in eine professionelle E-Mail-Vorlage eingebettet."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["Intelligente Tags, die auf vom Benutzer \u00fcbermittelte Felder verweisen, werden im Roh-HTML-Modus nicht maskiert. Vermeiden Sie es, nicht vertrauensw\u00fcrdige Feldwerte direkt in den E-Mail-Text einzuf\u00fcgen."],"Please provide a recipient email address and subject line.":["Bitte geben Sie eine Empf\u00e4nger-E-Mail-Adresse und eine Betreffzeile an."],"Email notification duplicated!":["E-Mail-Benachrichtigung dupliziert!"],"Are you sure you want to delete this email notification?":["Sind Sie sicher, dass Sie diese E-Mail-Benachrichtigung l\u00f6schen m\u00f6chten?"],"Email notification deleted!":["E-Mail-Benachrichtigung gel\u00f6scht!"],"URL is missing Top Level Domain (TLD).":["Die URL fehlt die Top-Level-Domain (TLD)."],"This form is now closed as the maximum number of entries has been received.":["Dieses Formular ist jetzt geschlossen, da die maximale Anzahl an Eintr\u00e4gen erreicht wurde."],"Publish Your Form":["Ver\u00f6ffentlichen Sie Ihr Formular"],"Enable This to Instantly Publish the Form":["Aktivieren Sie dies, um das Formular sofort zu ver\u00f6ffentlichen"],"Style Your Instant Form Page Here":["Gestalten Sie hier Ihre Sofortformularseite"],"Quizzes":["Quizze"],"%s - Description":["%s - Beschreibung"],"Send entries to 100+ popular apps.":["Eintr\u00e4ge an \u00fcber 100 beliebte Apps senden."],"Build automated workflows that run instantly.":["Erstellen Sie automatisierte Workflows, die sofort ausgef\u00fchrt werden."],"Create custom app integrations using our Custom App feature.":["Erstellen Sie benutzerdefinierte App-Integrationen mit unserer benutzerdefinierten App-Funktion."],"Keep your tools in sync automatically.":["Halten Sie Ihre Werkzeuge automatisch synchron."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["Dies wird OttoKit auf Ihrer WordPress-Seite installieren und aktivieren, um Automatisierungsfunktionen zu erm\u00f6glichen."],"Automate Your Forms with OttoKit":["Automatisieren Sie Ihre Formulare mit OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Jede Formular\u00fcbermittlung sollte etwas ausl\u00f6sen \u2014 eine Slack-Benachrichtigung, einen CRM-Lead, eine Follow-up-E-Mail oder eine neue Zeile in Google Sheets."],"Create interactive quizzes to engage your audience and gather insights.":["Erstellen Sie interaktive Quizze, um Ihr Publikum zu begeistern und Erkenntnisse zu gewinnen."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Entwerfen Sie ansprechende Quizze mit verschiedenen Fragetypen, personalisiertem Feedback und automatisierter Bewertung, um Ihr Publikum zu fesseln und wertvolle Einblicke zu gewinnen."],"Create interactive quizzes with multiple question types.":["Erstellen Sie interaktive Quizze mit verschiedenen Fragetypen."],"Provide personalized feedback based on user responses.":["Geben Sie personalisiertes Feedback basierend auf den Benutzerantworten."],"Automate scoring and lead segmentation for better insights.":["Automatisieren Sie die Bewertung und Lead-Segmentierung f\u00fcr bessere Einblicke."],"Heading 1":["\u00dcberschrift 1"],"Heading 2":["\u00dcberschrift 2"],"Heading 3":["\u00dcberschrift 3"],"Heading 4":["\u00dcberschrift 4"],"Heading 5":["\u00dcberschrift 5"],"Heading 6":["\u00dcberschrift 6"],"Form settings saved.":["Formulareinstellungen gespeichert."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["Die Eingabebegrenzung st\u00fctzt sich auf gespeicherte Eintr\u00e4ge, um Einsendungen zu z\u00e4hlen. Solange in den Compliance-Einstellungen die Option \"Eingabedaten nach dem Absenden des Formulars niemals speichern\" aktiviert ist, wird dieses Limit nicht durchgesetzt. Deaktivieren Sie diese Option oder entfernen Sie die Eingabebegrenzung, um diese Funktion zu nutzen."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Einstellungen gespeichert, aber die Beitragseigenschaften (Passwort \/ Titel \/ Inhalt) konnten nicht aktualisiert werden. Versuchen Sie es erneut, um sie zu speichern."],"Failed to save form settings.":["Speichern der Formulareinstellungen fehlgeschlagen."],"Saving\u2026":["Speichern\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Wenn aktiviert, speichert dieses Formular die IP-Adresse des Benutzers, den Browsernamen und den Ger\u00e4tenamen nicht in den Eintr\u00e4gen."],"Failed to save. Please try again.":["Speichern fehlgeschlagen. Bitte versuchen Sie es erneut."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["reCAPTCHA-API-Schl\u00fcssel f\u00fcr die ausgew\u00e4hlte Version sind nicht konfiguriert. Stellen Sie sie in den globalen Einstellungen ein."],"Please select a reCAPTCHA version.":["Bitte w\u00e4hlen Sie eine reCAPTCHA-Version aus."],"hCaptcha API keys are not configured. Set them in Global Settings.":["hCaptcha-API-Schl\u00fcssel sind nicht konfiguriert. Legen Sie sie in den globalen Einstellungen fest."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["Cloudflare Turnstile-API-Schl\u00fcssel sind nicht konfiguriert. Legen Sie sie in den globalen Einstellungen fest."],"Form data":["Formulardaten"],"Some fields need attention":["Einige Felder ben\u00f6tigen Aufmerksamkeit"],"Unsaved changes":["Nicht gespeicherte \u00c4nderungen"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Eine Empf\u00e4nger-E-Mail-Adresse und eine Betreffzeile sind erforderlich, bevor diese Benachrichtigung gespeichert werden kann. Beheben Sie die hervorgehobenen Felder oder verwerfen Sie Ihre \u00c4nderungen, um zur\u00fcckzugehen."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Sie haben ungespeicherte \u00c4nderungen f\u00fcr diese Benachrichtigung. Verwerfen Sie sie, um zur\u00fcckzugehen, oder bleiben Sie, um sie zu speichern."],"Discard & go back":["Verwerfen & zur\u00fcckgehen"],"Stay & fix":["Bleiben & reparieren"],"Keep editing":["Weiter bearbeiten"],"Please provide a recipient email address.":["Bitte geben Sie eine Empf\u00e4nger-E-Mail-Adresse an."],"Please provide a subject line.":["Bitte geben Sie eine Betreffzeile an."],"Please provide a custom URL.":["Bitte geben Sie eine benutzerdefinierte URL an."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Sie haben ungespeicherte \u00c4nderungen. Verwerfen Sie diese, um fortzufahren, oder bleiben Sie, um Ihre \u00c4nderungen zu speichern."],"Discard & continue":["Verwerfen & fortfahren"],"Show Entries":["Eintr\u00e4ge anzeigen"],"Display this form\u2019s entries in a table on the frontend.":["Zeigen Sie die Eintr\u00e4ge dieses Formulars in einer Tabelle im Frontend an."],"Publish this form\u2019s submissions as a sortable, paginated table on any page \u2014 perfect for public directories, listings, and team dashboards, with per-role view access.":["Ver\u00f6ffentlichen Sie die Einsendungen dieses Formulars als sortierbare, paginierte Tabelle auf jeder Seite \u2014 ideal f\u00fcr \u00f6ffentliche Verzeichnisse, Listen und Team-Dashboards, mit Zugriff nach Rolle."],"Embed a sortable, paginated entries table with a simple shortcode.":["Betten Sie eine sortierbare, paginierte Eintragstabelle mit einem einfachen Shortcode ein."],"Choose exactly which fields appear as columns.":["W\u00e4hlen Sie genau aus, welche Felder als Spalten erscheinen."],"Restrict who can view entries by user role.":["Beschr\u00e4nken Sie, wer Eintr\u00e4ge nach Benutzerrolle anzeigen kann."],"Quill heading picker: default paragraph style\u0004Normal":["Normal"]}}} \ No newline at end of file diff --git a/languages/sureforms-de_DE.mo b/languages/sureforms-de_DE.mo index da91d541fbb03776289958524ab200846ab4b236..d6a1f53724c464a13dc51f092ffbb9ef9c139e30 100644 GIT binary patch delta 72204 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 Bcb5PF delta 70493 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 T3{0O}wNRp3*6(8z`-lD?igxps diff --git a/languages/sureforms-de_DE.po b/languages/sureforms-de_DE.po index 56ab01ad4..f120aef4b 100644 --- a/languages/sureforms-de_DE.po +++ b/languages/sureforms-de_DE.po @@ -14,9 +14,9 @@ msgstr "" #. Plugin Name of the plugin #. Author of the plugin #: sureforms.php -#: admin/admin.php:391 -#: admin/admin.php:392 -#: admin/admin.php:1950 +#: admin/admin.php:406 +#: admin/admin.php:407 +#: admin/admin.php:1970 #: inc/abilities/abilities-registrar.php:139 #: inc/gutenberg-hooks.php:109 #: inc/page-builders/bricks/elements/form-widget.php:67 @@ -43,8 +43,8 @@ msgstr "Eine einfache, aber leistungsstarke Möglichkeit, moderne Formulare für msgid "https://sureforms.com/" msgstr "https://sureforms.com/" -#: admin/admin.php:404 -#: admin/admin.php:405 +#: admin/admin.php:419 +#: admin/admin.php:420 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -59,9 +59,9 @@ msgstr "https://sureforms.com/" msgid "Dashboard" msgstr "Armaturenbrett" -#: admin/admin.php:422 -#: admin/admin.php:423 -#: admin/admin.php:855 +#: admin/admin.php:437 +#: admin/admin.php:438 +#: admin/admin.php:870 #: inc/compatibility/multilingual/string-collector.php:148 #: inc/compatibility/multilingual/string-collector.php:216 #: assets/build/blocks.js:172 @@ -78,15 +78,15 @@ msgstr "Armaturenbrett" msgid "Settings" msgstr "Einstellungen" -#: admin/admin.php:691 -#: admin/admin.php:692 +#: admin/admin.php:706 +#: admin/admin.php:707 #: inc/post-types.php:231 msgid "New Form" msgstr "Neues Formular" -#: admin/admin.php:700 -#: admin/admin.php:701 -#: admin/admin.php:1987 +#: admin/admin.php:715 +#: admin/admin.php:716 +#: admin/admin.php:2179 #: inc/global-settings/email-summary.php:225 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -102,7 +102,7 @@ msgid "Entries" msgstr "Einträge" #. Translators: Post Title. -#: admin/admin.php:942 +#: admin/admin.php:958 #, php-format msgid "Edit %1$s" msgstr "%1$s bearbeiten" @@ -117,24 +117,24 @@ msgstr "%1$s bearbeiten" #: inc/export.php:176 #: inc/forms-data.php:88 #: inc/global-settings/global-settings.php:88 -#: inc/global-settings/global-settings.php:630 +#: inc/global-settings/global-settings.php:632 #: inc/rest-api.php:203 msgid "Nonce verification failed." msgstr "Überprüfung des Nonce ist fehlgeschlagen." #. translators: %1$s: Opening anchor tag with URL, %2$s: Closing anchor tag, %3$s: SureForms Pro Plugin Name. -#: admin/admin.php:1579 +#: admin/admin.php:1596 #, php-format msgid "Please %1$sactivate%2$s your copy of %3$s to get new features, access support, receive update notifications, and more." msgstr "Bitte %1$saktivieren%2$s Sie Ihre Kopie von %3$s, um neue Funktionen zu erhalten, Support zu nutzen, Update-Benachrichtigungen zu erhalten und mehr." #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version, %4$s: Anchor tag open, %5$s: Closing anchor tag. -#: admin/admin.php:1597 +#: admin/admin.php:1614 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version from %4$shere%5$s." msgstr "SureForms %1$s erfordert mindestens %2$s %3$s, um ordnungsgemäß zu funktionieren. Bitte aktualisieren Sie auf die neueste Version von %4$shier%5$s." -#: admin/admin.php:1986 +#: admin/admin.php:2178 #: inc/global-settings/email-summary.php:224 #: assets/build/entries.js:172 #: assets/build/payments.js:172 @@ -204,7 +204,7 @@ msgstr "Müll" msgid "Published" msgstr "Veröffentlicht" -#: admin/admin.php:1978 +#: admin/admin.php:2170 msgid "View" msgstr "Ansehen" @@ -296,11 +296,11 @@ msgstr "Kein Plugin angegeben" msgid "Plugin Successfully Activated" msgstr "Plugin erfolgreich aktiviert" -#: admin/admin.php:1014 +#: admin/admin.php:1030 msgid "Activating..." msgstr "Aktivieren..." -#: admin/admin.php:1015 +#: admin/admin.php:1031 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/formEditor.js:172 @@ -315,17 +315,17 @@ msgstr "Aktivieren..." msgid "Activated" msgstr "Aktiviert" -#: admin/admin.php:1016 +#: admin/admin.php:1032 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Activate" msgstr "Aktivieren" -#: admin/admin.php:1017 +#: admin/admin.php:1033 msgid "Installing..." msgstr "Installation läuft..." -#: admin/admin.php:1018 +#: admin/admin.php:1034 msgid "Installed" msgstr "Installiert" @@ -333,10 +333,11 @@ msgstr "Installiert" msgid "You do not have permission to access this page." msgstr "Sie haben keine Berechtigung, auf diese Seite zuzugreifen." -#: admin/admin.php:1745 -#: admin/admin.php:1844 -#: admin/admin.php:1882 -#: admin/admin.php:1903 +#: admin/admin.php:1762 +#: admin/admin.php:1861 +#: admin/admin.php:1899 +#: admin/admin.php:1920 +#: admin/admin.php:2133 #: inc/admin-ajax.php:162 #: inc/payments/admin/admin-handler.php:638 #: inc/payments/stripe/admin-stripe-handler.php:80 @@ -439,8 +440,8 @@ msgstr "Keine Benutzer-E-Mail in den entschlüsselten Daten gefunden." msgid "The message array was not supplied" msgstr "Das Nachrichtenarray wurde nicht bereitgestellt" -#: inc/ai-form-builder/ai-form-builder.php:68 -#: inc/ai-form-builder/ai-form-builder.php:87 +#: inc/ai-form-builder/ai-form-builder.php:80 +#: inc/ai-form-builder/ai-form-builder.php:99 #: inc/ai-form-builder/ai-helper.php:294 msgid "The SureForms AI Middleware encountered an error." msgstr "Die SureForms AI-Middleware ist auf einen Fehler gestoßen." @@ -627,8 +628,8 @@ msgstr "Keine Formulare gefunden." #: inc/abilities/settings/get-global-settings.php:192 #: inc/global-settings/email-summary.php:571 -#: inc/global-settings/global-settings.php:262 -#: inc/global-settings/global-settings.php:689 +#: inc/global-settings/global-settings.php:264 +#: inc/global-settings/global-settings.php:691 #: assets/build/settings.js:172 msgid "Monday" msgstr "Montag" @@ -716,6 +717,7 @@ msgstr "Formular bearbeiten" msgid "Edit" msgstr "Bearbeiten" +#: admin/admin.php:2021 #: inc/page-builders/elementor/form-widget.php:346 #: assets/build/dashboard.js:2 msgid "Create New Form" @@ -752,8 +754,8 @@ msgstr "Formular anzeigen" msgid "View Forms" msgstr "Formulare anzeigen" -#: admin/admin.php:682 -#: admin/admin.php:683 +#: admin/admin.php:697 +#: admin/admin.php:698 #: inc/post-types.php:235 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -798,7 +800,7 @@ msgstr "Admin-Benachrichtigungs-E-Mail" #. translators: %s: Form title smart tag #. translators: %s: {form_title} smart tag placeholder. #: inc/global-settings/global-settings-defaults.php:173 -#: inc/global-settings/global-settings.php:586 +#: inc/global-settings/global-settings.php:588 #: inc/post-types.php:1005 #: assets/build/settings.js:172 #, php-format,js-format @@ -8876,9 +8878,9 @@ msgstr "Blöcke zur Schnellzugriffsleiste hinzufügen" msgid "Re-arrange block inside Quick Action Bar" msgstr "Block im Schnellaktionsbereich neu anordnen" -#: admin/admin.php:475 -#: admin/admin.php:476 -#: admin/admin.php:2211 +#: admin/admin.php:490 +#: admin/admin.php:491 +#: admin/admin.php:2403 #: assets/build/blocks.js:172 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -9319,7 +9321,7 @@ msgstr "Seitenumbruch" msgid "Previous" msgstr "Zurück" -#: inc/global-settings/global-settings.php:528 +#: inc/global-settings/global-settings.php:530 #: inc/migrator/base-migrator.php:548 #: inc/post-types.php:1081 #: assets/build/formEditor.js:172 @@ -10500,7 +10502,7 @@ msgstr "Fügen Sie Ihrer Website atemberaubende anpassbare Symbole hinzu." msgid "icon" msgstr "Symbol" -#: admin/admin.php:1643 +#: admin/admin.php:1660 msgid "Rate SureForms" msgstr "Bewerten Sie SureForms" @@ -10610,7 +10612,7 @@ msgstr "Konversationelle Formulare freischalten" msgid "With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience." msgstr "Mit dem SureForms Pro Plan können Sie Ihre Formulare in ansprechende, dialogorientierte Layouts verwandeln, um ein nahtloses Benutzererlebnis zu schaffen." -#: admin/admin.php:314 +#: admin/admin.php:329 msgid "Get SureForms Pro" msgstr "Holen Sie sich SureForms Pro" @@ -11236,8 +11238,8 @@ msgstr "Beschreiben Sie das Formular, das Sie erstellen möchten" msgid "We are building your form…" msgstr "Wir erstellen Ihr Formular…" -#: admin/admin.php:590 -#: admin/admin.php:591 +#: admin/admin.php:605 +#: admin/admin.php:606 msgid "SMTP" msgstr "SMTP" @@ -11369,28 +11371,29 @@ msgstr "Admin-Benachrichtigung aktivieren" msgid "Admin notifications keep you informed about new form entries since your last visit." msgstr "Admin-Benachrichtigungen halten Sie über neue Formulareinträge seit Ihrem letzten Besuch auf dem Laufenden." -#: admin/admin.php:1749 -#: admin/admin.php:1840 -#: admin/admin.php:1878 -#: admin/admin.php:1899 +#: admin/admin.php:1766 +#: admin/admin.php:1857 +#: admin/admin.php:1895 +#: admin/admin.php:1916 +#: admin/admin.php:2137 msgid "Unauthorized user." msgstr "Unbefugter Benutzer." #. translators: 1: opening span, 2: opening strong (inline), 3: closing strong, 4: closing span, 5: opening strong (block), 6: closing strong -#: admin/admin.php:1849 +#: admin/admin.php:1866 #, php-format msgid "%1$sGet started by %2$sbuilding your first form%3$s.%4$s%5$sExperience the power of our intuitive AI Form Builder%6$s" msgstr "%1$sBeginnen Sie damit, %2$sIhr erstes Formular zu erstellen%3$s.%4$s%5$sErleben Sie die Leistungsfähigkeit unseres intuitiven KI-Formularerstellers%6$s" -#: admin/admin.php:1860 +#: admin/admin.php:1877 msgid "SureForms is waiting for you!" msgstr "SureForms wartet auf Sie!" -#: admin/admin.php:1862 +#: admin/admin.php:1879 msgid "Build My First Form" msgstr "Erstelle mein erstes Formular" -#: admin/admin.php:1863 +#: admin/admin.php:1880 msgid "Dismiss" msgstr "Verwerfen" @@ -11410,51 +11413,51 @@ msgstr "Anmelden" msgid "Register" msgstr "Registrieren" -#: admin/admin.php:1974 +#: admin/admin.php:2166 msgid "Recent Entries" msgstr "Neueste Einträge" -#: admin/admin.php:1975 +#: admin/admin.php:2167 msgid "( Last 7 days )" msgstr "( Letzte 7 Tage )" -#: admin/admin.php:2152 +#: admin/admin.php:2344 msgid "Use Conditional Logic to show only what matters" msgstr "Verwenden Sie bedingte Logik, um nur das Wichtige anzuzeigen" -#: admin/admin.php:2153 +#: admin/admin.php:2345 msgid "Split your form into steps to keep it easy" msgstr "Teilen Sie Ihr Formular in Schritte auf, um es einfach zu halten." -#: admin/admin.php:2154 +#: admin/admin.php:2346 msgid "Let people upload files directly to your form" msgstr "Lassen Sie Personen Dateien direkt in Ihr Formular hochladen" -#: admin/admin.php:2155 +#: admin/admin.php:2347 msgid "Turn responses into downloadable PDFs automatically" msgstr "Antworten automatisch in herunterladbare PDFs umwandeln" -#: admin/admin.php:2156 +#: admin/admin.php:2348 msgid "Let users sign with a simple signature field" msgstr "Lassen Sie Benutzer mit einem einfachen Unterschriftsfeld unterschreiben" -#: admin/admin.php:2157 +#: admin/admin.php:2349 msgid "Connect your form to other tools using webhooks" msgstr "Verbinden Sie Ihr Formular mit anderen Tools über Webhooks" -#: admin/admin.php:2158 +#: admin/admin.php:2350 msgid "Use Conversational Forms for a chat-like experience" msgstr "Verwenden Sie Konversationsformulare für ein chatähnliches Erlebnis" -#: admin/admin.php:2159 +#: admin/admin.php:2351 msgid "Let users register or log in through your form" msgstr "Lassen Sie Benutzer sich über Ihr Formular registrieren oder anmelden" -#: admin/admin.php:2160 +#: admin/admin.php:2352 msgid "Build forms that create WordPress user accounts" msgstr "Erstellen Sie Formulare, die WordPress-Benutzerkonten erstellen" -#: admin/admin.php:2161 +#: admin/admin.php:2353 msgid "Add calculations to auto-total scores or prices" msgstr "Berechnungen hinzufügen, um die Punktzahlen oder Preise automatisch zu summieren" @@ -11944,8 +11947,8 @@ msgstr "SureForms Video-Miniaturansicht" msgid "Expected format for emails - email@sureforms.com or John Doe " msgstr "Erwartetes Format für E-Mails - email@sureforms.com oder John Doe " -#: admin/admin.php:710 -#: admin/admin.php:711 +#: admin/admin.php:725 +#: admin/admin.php:726 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -12322,8 +12325,8 @@ msgstr "Unbekannt" #: inc/payments/admin/admin-handler.php:1501 #: inc/payments/admin/admin-handler.php:1505 #: inc/payments/admin/admin-handler.php:1509 -#: inc/payments/front-end.php:865 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:866 +#: inc/payments/front-end.php:1275 msgid "Guest User" msgstr "Gastbenutzer" @@ -12336,7 +12339,7 @@ msgid "Valid customer email is required for payments." msgstr "Eine gültige Kunden-E-Mail ist für Zahlungen erforderlich." #: inc/payments/front-end.php:115 -#: inc/payments/front-end.php:340 +#: inc/payments/front-end.php:341 #: inc/payments/stripe/admin-stripe-handler.php:861 #: inc/payments/stripe/payments-settings.php:349 #: inc/payments/stripe/payments-settings.php:474 @@ -12347,136 +12350,136 @@ msgid "Stripe is not connected." msgstr "Stripe ist nicht verbunden." #: inc/payments/front-end.php:121 -#: inc/payments/front-end.php:346 -#: inc/payments/front-end.php:634 -#: inc/payments/front-end.php:1153 +#: inc/payments/front-end.php:347 +#: inc/payments/front-end.php:635 +#: inc/payments/front-end.php:1154 msgid "Stripe secret key not found." msgstr "Stripe-Geheimschlüssel nicht gefunden." #. translators: %s: formatted payment amount -#: inc/payments/front-end.php:203 +#: inc/payments/front-end.php:204 #, php-format msgid "The payment amount (%s) is below the minimum allowed. Stripe only processes amounts above 50¢." msgstr "Der Zahlungsbetrag (%s) liegt unter dem zulässigen Minimum. Stripe verarbeitet nur Beträge über 50¢." #. translators: %s: Field name -#: inc/payments/front-end.php:273 +#: inc/payments/front-end.php:274 #, php-format msgid "Missing required field: %s" msgstr "Fehlendes erforderliches Feld: %s" -#: inc/payments/front-end.php:292 +#: inc/payments/front-end.php:293 msgid "Valid customer email is required for subscriptions." msgstr "Eine gültige Kunden-E-Mail ist für Abonnements erforderlich." -#: inc/payments/front-end.php:297 +#: inc/payments/front-end.php:298 msgid "Customer name is required for subscriptions." msgstr "Der Name des Kunden ist für Abonnements erforderlich." -#: inc/payments/front-end.php:314 +#: inc/payments/front-end.php:315 msgid "Amount must be greater than 0" msgstr "Betrag muss größer als 0 sein" -#: inc/payments/front-end.php:322 +#: inc/payments/front-end.php:323 msgid "Invalid billing interval" msgstr "Ungültiges Abrechnungsintervall" -#: inc/payments/front-end.php:357 +#: inc/payments/front-end.php:358 msgid "Failed to create customer for subscription." msgstr "Erstellung des Kunden für das Abonnement fehlgeschlagen." -#: inc/payments/front-end.php:390 -#: inc/payments/front-end.php:406 +#: inc/payments/front-end.php:391 +#: inc/payments/front-end.php:407 msgid "Failed to create subscription through middleware." msgstr "Fehler beim Erstellen des Abonnements über die Middleware." -#: inc/payments/front-end.php:411 +#: inc/payments/front-end.php:412 msgid "Empty response from subscription creation." msgstr "Leere Antwort von der Abonnementerstellung." -#: inc/payments/front-end.php:416 +#: inc/payments/front-end.php:417 msgid "Invalid JSON response from subscription creation." msgstr "Ungültige JSON-Antwort von der Abonnementerstellung." -#: inc/payments/front-end.php:420 -#: inc/payments/front-end.php:424 +#: inc/payments/front-end.php:421 +#: inc/payments/front-end.php:425 msgid "Invalid subscription data." msgstr "Ungültige Abonnementdaten." -#: inc/payments/front-end.php:431 +#: inc/payments/front-end.php:432 msgid "Failed to create subscription." msgstr "Erstellung des Abonnements fehlgeschlagen." #. translators: %s: Error message -#: inc/payments/front-end.php:464 +#: inc/payments/front-end.php:465 #, php-format msgid "Unexpected error: %s" msgstr "Unerwarteter Fehler: %s" -#: inc/payments/front-end.php:604 +#: inc/payments/front-end.php:605 #: inc/payments/stripe/admin-stripe-handler.php:103 #: inc/payments/stripe/admin-stripe-handler.php:165 #: inc/payments/stripe/admin-stripe-handler.php:471 msgid "Subscription ID not found." msgstr "Abonnement-ID nicht gefunden." -#: inc/payments/front-end.php:623 +#: inc/payments/front-end.php:624 msgid "Customer ID not found for the payment." msgstr "Kunden-ID für die Zahlung nicht gefunden." -#: inc/payments/front-end.php:651 +#: inc/payments/front-end.php:652 msgid "Failed to retrieve setup intent." msgstr "Fehler beim Abrufen der Einrichtungsvorgabe." -#: inc/payments/front-end.php:699 +#: inc/payments/front-end.php:700 msgid "Failed to update subscription." msgstr "Aktualisierung des Abonnements fehlgeschlagen." -#: inc/payments/front-end.php:707 +#: inc/payments/front-end.php:708 msgid "Latest invoice not found on subscription." msgstr "Neueste Rechnung im Abonnement nicht gefunden." -#: inc/payments/front-end.php:720 +#: inc/payments/front-end.php:721 msgid "Failed to retrieve invoice." msgstr "Rechnung konnte nicht abgerufen werden." -#: inc/payments/front-end.php:742 +#: inc/payments/front-end.php:743 msgid "Payment intent not found on invoice." msgstr "Zahlungsabsicht auf der Rechnung nicht gefunden." -#: inc/payments/front-end.php:757 +#: inc/payments/front-end.php:758 msgid "Failed to confirm payment." msgstr "Zahlung konnte nicht bestätigt werden." -#: inc/payments/front-end.php:773 +#: inc/payments/front-end.php:774 msgid "Failed to retrieve subscription." msgstr "Abonnement konnte nicht abgerufen werden." -#: inc/payments/front-end.php:788 +#: inc/payments/front-end.php:789 msgid "Subscription not found for the payment." msgstr "Abonnement für die Zahlung nicht gefunden." #. translators: %d: User ID -#: inc/payments/front-end.php:863 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:864 +#: inc/payments/front-end.php:1275 #, php-format msgid "User ID: %d" msgstr "Benutzer-ID: %d" #. translators: %s: Invoice status -#: inc/payments/front-end.php:871 +#: inc/payments/front-end.php:872 #, php-format msgid "Invoice Status: %s" msgstr "Rechnungsstatus: %s" #. translators: Title for subscription verification log -#: inc/payments/front-end.php:878 +#: inc/payments/front-end.php:879 msgid "Subscription Verification" msgstr "Abonnement-Verifizierung" #. translators: %s: Subscription ID #. translators: %s: Stripe subscription ID -#: inc/payments/front-end.php:882 +#: inc/payments/front-end.php:883 #: inc/payments/stripe/admin-stripe-handler.php:185 #: inc/payments/stripe/admin-stripe-handler.php:487 #: inc/payments/stripe/stripe-webhook.php:572 @@ -12491,8 +12494,8 @@ msgstr "Abonnement-ID: %s" #. translators: %s: payment gateway name (e.g., Stripe) #. translators: %s: payment gateway #. translators: %s: Payment gateway name (e.g., Stripe). -#: inc/payments/front-end.php:884 -#: inc/payments/front-end.php:1285 +#: inc/payments/front-end.php:885 +#: inc/payments/front-end.php:1286 #: inc/payments/stripe/admin-stripe-handler.php:190 #: inc/payments/stripe/admin-stripe-handler.php:492 #: inc/payments/stripe/admin-stripe-handler.php:667 @@ -12506,13 +12509,13 @@ msgid "Payment Gateway: %s" msgstr "Zahlungs-Gateway: %s" #. translators: %s: Payment Intent ID -#: inc/payments/front-end.php:886 +#: inc/payments/front-end.php:887 #, php-format msgid "Payment Intent ID: %s" msgstr "Zahlungsabsichts-ID: %s" #. translators: %s: Charge ID -#: inc/payments/front-end.php:888 +#: inc/payments/front-end.php:889 #: inc/payments/stripe/stripe-webhook.php:1084 #, php-format msgid "Charge ID: %s" @@ -12520,7 +12523,7 @@ msgstr "Charge-ID: %s" #. translators: %s: Subscription Status #. translators: %s: subscription status -#: inc/payments/front-end.php:890 +#: inc/payments/front-end.php:891 #: inc/payments/stripe/admin-stripe-handler.php:195 #: inc/payments/stripe/admin-stripe-handler.php:497 #, php-format @@ -12528,7 +12531,7 @@ msgid "Subscription Status: %s" msgstr "Abonnementstatus: %s" #. translators: %s: Customer ID -#: inc/payments/front-end.php:892 +#: inc/payments/front-end.php:893 #: inc/payments/stripe/stripe-webhook.php:1174 #, php-format msgid "Customer ID: %s" @@ -12536,8 +12539,8 @@ msgstr "Kunden-ID: %s" #. translators: 1: Amount, 2: Currency #. translators: %1$s: amount, %2$s: currency. -#: inc/payments/front-end.php:894 -#: inc/payments/front-end.php:1287 +#: inc/payments/front-end.php:895 +#: inc/payments/front-end.php:1288 #: inc/payments/stripe/stripe-webhook.php:1089 #: inc/payments/stripe/stripe-webhook.php:1166 #, php-format @@ -12546,34 +12549,34 @@ msgstr "Betrag: %1$s %2$s" #. translators: %s: Payment mode (e.g. Live or Test) #. translators: %s: payment mode -#: inc/payments/front-end.php:897 -#: inc/payments/front-end.php:1292 +#: inc/payments/front-end.php:898 +#: inc/payments/front-end.php:1293 #, php-format msgid "Mode: %s" msgstr "Modus: %s" -#: inc/payments/front-end.php:931 +#: inc/payments/front-end.php:932 msgid "Failed to verify subscription." msgstr "Überprüfung des Abonnements fehlgeschlagen." -#: inc/payments/front-end.php:1186 -#: inc/payments/front-end.php:1204 -#: inc/payments/front-end.php:1212 -#: inc/payments/front-end.php:1219 +#: inc/payments/front-end.php:1187 +#: inc/payments/front-end.php:1205 +#: inc/payments/front-end.php:1213 +#: inc/payments/front-end.php:1220 msgid "Failed to retrieve payment intent." msgstr "Fehler beim Abrufen des Zahlungsziels." -#: inc/payments/front-end.php:1227 +#: inc/payments/front-end.php:1228 msgid "Payment was not confirmed successfully." msgstr "Die Zahlung wurde nicht erfolgreich bestätigt." -#: inc/payments/front-end.php:1279 +#: inc/payments/front-end.php:1280 msgid "Payment Verification" msgstr "Zahlungsüberprüfung" #. translators: %s: Stripe transaction ID #. translators: %s: Charge ID -#: inc/payments/front-end.php:1283 +#: inc/payments/front-end.php:1284 #: inc/payments/stripe/stripe-webhook.php:1162 #, php-format msgid "Transaction ID: %s" @@ -12581,18 +12584,18 @@ msgstr "Transaktions-ID: %s" #. translators: %s: payment status #. translators: %s: Status -#: inc/payments/front-end.php:1289 +#: inc/payments/front-end.php:1290 #: inc/payments/stripe/stripe-webhook.php:576 #: inc/payments/stripe/stripe-webhook.php:1168 #, php-format msgid "Status: %s" msgstr "Status: %s" -#: inc/payments/front-end.php:1395 +#: inc/payments/front-end.php:1396 msgid "Failed to create Stripe customer." msgstr "Erstellung des Stripe-Kunden fehlgeschlagen." -#: inc/payments/front-end.php:1459 +#: inc/payments/front-end.php:1460 msgid "Failed to create Stripe guest customer." msgstr "Erstellung des Stripe-Gastkunden fehlgeschlagen." @@ -13457,7 +13460,7 @@ msgstr "Erstellt über den Abrechnungszyklus des Abonnements" msgid "Edit this form" msgstr "Bearbeiten Sie dieses Formular" -#: inc/global-settings/global-settings.php:529 +#: inc/global-settings/global-settings.php:531 #: inc/migrator/base-migrator.php:549 #: inc/post-types.php:1081 msgid "Your form has been submitted successfully. We'll review your details and get back to you soon." @@ -15080,7 +15083,7 @@ msgid "Failed to create duplicate form." msgstr "Erstellung des doppelten Formulars fehlgeschlagen." #: inc/payments/front-end.php:98 -#: inc/payments/front-end.php:303 +#: inc/payments/front-end.php:304 #: inc/payments/payment-helper.php:596 msgid "Invalid form configuration." msgstr "Ungültige Formular-Konfiguration." @@ -15156,7 +15159,7 @@ msgstr "Der Wert des variablen Betragsfeldes ist erforderlich." msgid "Payment amount below minimum. Minimum: %1$s, received %2$s." msgstr "Zahlungsbetrag unter dem Minimum. Minimum: %1$s, erhalten %2$s." -#: inc/rest-api.php:1839 +#: inc/rest-api.php:1851 msgid " (Copy)" msgstr "(Kopie)" @@ -15355,12 +15358,12 @@ msgid "Stripe Settings" msgstr "Stripe-Einstellungen" #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version. -#: admin/admin.php:1514 +#: admin/admin.php:1531 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version." msgstr "SureForms %1$s erfordert mindestens %2$s %3$s, um ordnungsgemäß zu funktionieren. Bitte aktualisieren Sie auf die neueste Version." -#: admin/admin.php:1527 +#: admin/admin.php:1544 msgid "Update Now" msgstr "Jetzt aktualisieren" @@ -15408,8 +15411,8 @@ msgstr "Position des Währungszeichens" msgid "Select the position of the currency symbol relative to the amount." msgstr "Wählen Sie die Position des Währungssymbols relativ zum Betrag aus." -#: admin/admin.php:653 -#: admin/admin.php:654 +#: admin/admin.php:668 +#: admin/admin.php:669 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -15423,40 +15426,40 @@ msgstr "Wählen Sie die Position des Währungssymbols relativ zum Betrag aus." msgid "Learn" msgstr "Lernen" -#: admin/admin.php:1640 +#: admin/admin.php:1657 msgid "Amazing! SureForms is powering your forms and submissions - let's keep growing together!" msgstr "Erstaunlich! SureForms treibt Ihre Formulare und Einsendungen an - lassen Sie uns gemeinsam weiter wachsen!" -#: admin/admin.php:1641 +#: admin/admin.php:1658 msgid "If SureForms has been helpful, would you mind taking a moment to leave a 5-star review on WordPress.org?" msgstr "Wenn SureForms hilfreich war, würden Sie sich einen Moment Zeit nehmen, um eine 5-Sterne-Bewertung auf WordPress.org zu hinterlassen?" -#: admin/admin.php:1644 -#: admin/admin.php:1688 +#: admin/admin.php:1661 +#: admin/admin.php:1705 msgid "Maybe later" msgstr "Vielleicht später" -#: admin/admin.php:1645 +#: admin/admin.php:1662 msgid "I already did" msgstr "Ich habe es bereits getan" -#: admin/admin.php:1684 +#: admin/admin.php:1701 msgid "SureForms is ready to power your forms — explore what's possible!" msgstr "SureForms ist bereit, Ihre Formulare zu unterstützen — entdecken Sie, was möglich ist!" -#: admin/admin.php:1685 +#: admin/admin.php:1702 msgid "Manage your forms, track submissions, and discover features like AI Form Builder, payment integrations, and more from the SureForms dashboard." msgstr "Verwalten Sie Ihre Formulare, verfolgen Sie Einsendungen und entdecken Sie Funktionen wie den AI Form Builder, Zahlungsintegrationen und mehr im SureForms-Dashboard." -#: admin/admin.php:1687 +#: admin/admin.php:1704 msgid "Go to Dashboard" msgstr "Zum Dashboard gehen" -#: admin/admin.php:1689 +#: admin/admin.php:1706 msgid "I already know" msgstr "Ich weiß schon" -#: admin/admin.php:1769 +#: admin/admin.php:1786 msgid "Invalid parameters." msgstr "Ungültige Parameter." @@ -15850,7 +15853,7 @@ msgstr "Dieses Formular ist noch nicht verfügbar. Schauen Sie nach der geplante #: inc/form-submit.php:1015 #: inc/migrator/bootstrap.php:357 #: inc/payments/front-end.php:78 -#: inc/payments/front-end.php:263 +#: inc/payments/front-end.php:264 #: inc/rest-api.php:124 #: inc/rest-api.php:183 #: inc/rest-api.php:349 @@ -15863,8 +15866,8 @@ msgstr "Dieses Formular ist noch nicht verfügbar. Schauen Sie nach der geplante #: inc/rest-api.php:742 #: inc/rest-api.php:775 #: inc/rest-api.php:914 -#: inc/rest-api.php:987 -#: inc/rest-api.php:1047 +#: inc/rest-api.php:999 +#: inc/rest-api.php:1059 #: inc/single-form-settings/form-settings-api.php:89 msgid "Security verification failed. Please refresh the page and try again." msgstr "Sicherheitsüberprüfung fehlgeschlagen. Bitte aktualisieren Sie die Seite und versuchen Sie es erneut." @@ -16163,28 +16166,28 @@ msgstr "Ungültige Aktion. Verwenden Sie \"lesen\" oder \"ungelesen\"." msgid "Invalid action. Use \"trash\" or \"restore\"." msgstr "Ungültige Aktion. Verwenden Sie \"trash\" oder \"restore\"." -#: inc/rest-api.php:1061 +#: inc/rest-api.php:1073 msgid "Select at least one form and specify an action." msgstr "Wählen Sie mindestens ein Formular aus und geben Sie eine Aktion an." -#: inc/rest-api.php:1076 +#: inc/rest-api.php:1088 msgid "Form not found or is not a valid form type." msgstr "Formular nicht gefunden oder ist kein gültiger Formular-Typ." -#: inc/rest-api.php:1088 +#: inc/rest-api.php:1100 msgid "This form is already in the trash." msgstr "Dieses Formular befindet sich bereits im Papierkorb." -#: inc/rest-api.php:1099 +#: inc/rest-api.php:1111 msgid "This form is not in the trash." msgstr "Dieses Formular befindet sich nicht im Papierkorb." -#: inc/rest-api.php:1135 +#: inc/rest-api.php:1147 msgid "Invalid action." msgstr "Ungültige Aktion." #. translators: %s: action name -#: inc/rest-api.php:1150 +#: inc/rest-api.php:1162 #, php-format msgid "Failed to %s this form. Please try again." msgstr "Das %s dieses Formulars ist fehlgeschlagen. Bitte versuchen Sie es erneut." @@ -16202,32 +16205,32 @@ msgid "Enter a valid email address." msgstr "Geben Sie eine gültige E-Mail-Adresse ein." #. translators: %s represents the minimum acceptable value -#: inc/translatable.php:97 +#: inc/translatable.php:103 #, php-format msgid "Minimum value is %s." msgstr "Mindestwert ist %s." #. translators: %s represents the maximum acceptable value -#: inc/translatable.php:100 +#: inc/translatable.php:106 #, php-format msgid "Maximum value is %s." msgstr "Der Höchstwert ist %s." #. translators: %s represents the minimum number of options to select -#: inc/translatable.php:103 #: inc/translatable.php:109 +#: inc/translatable.php:115 #, php-format msgid "Select at least %s options." msgstr "Wählen Sie mindestens %s Optionen aus." #. translators: %s represents the maximum number of options that can be selected -#: inc/translatable.php:106 #: inc/translatable.php:112 +#: inc/translatable.php:118 #, php-format msgid "You can select up to %s options." msgstr "Sie können bis zu %s Optionen auswählen." -#: inc/translatable.php:151 +#: inc/translatable.php:159 msgid "This form is now closed as we have reached the maximum number of entries." msgstr "Dieses Formular ist jetzt geschlossen, da wir die maximale Anzahl an Einträgen erreicht haben." @@ -16423,7 +16426,7 @@ msgid "Duplicate form?" msgstr "Formular duplizieren?" #: assets/build/formSubmit.js:2 -#: assets/js/unminified/form-submit.js:487 +#: assets/js/unminified/form-submit.js:495 msgid "An error occurred while submitting your form. Please try again." msgstr "Beim Absenden Ihres Formulars ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut." @@ -16750,35 +16753,35 @@ msgstr "Rufen Sie die globalen Einstellungen von SureForms ab. Optional nach Kat msgid "Setting categories to retrieve. Omit for all categories." msgstr "Kategorien zum Abrufen festlegen. Auslassen für alle Kategorien." -#: inc/abilities/settings/update-global-settings.php:106 +#: inc/abilities/settings/update-global-settings.php:108 msgid "Update Global Settings" msgstr "Globale Einstellungen aktualisieren" -#: inc/abilities/settings/update-global-settings.php:107 +#: inc/abilities/settings/update-global-settings.php:109 msgid "Update SureForms global settings for a specific category: general, validation-messages, email-summary, or security." msgstr "Aktualisieren Sie die globalen Einstellungen von SureForms für eine bestimmte Kategorie: allgemein, Validierungsnachrichten, E-Mail-Zusammenfassung oder Sicherheit." -#: inc/abilities/settings/update-global-settings.php:140 +#: inc/abilities/settings/update-global-settings.php:142 msgid "The settings category to update." msgstr "Die zu aktualisierende Einstellungskategorie." -#: inc/abilities/settings/update-global-settings.php:145 +#: inc/abilities/settings/update-global-settings.php:147 msgid "Key-value pairs of settings to update." msgstr "Schlüssel-Wert-Paare von Einstellungen zum Aktualisieren." -#: inc/abilities/settings/update-global-settings.php:181 +#: inc/abilities/settings/update-global-settings.php:183 msgid "Settings category is required." msgstr "Die Einstellungskategorie ist erforderlich." -#: inc/abilities/settings/update-global-settings.php:189 +#: inc/abilities/settings/update-global-settings.php:191 msgid "Settings data is required." msgstr "Einstellungsdaten sind erforderlich." -#: inc/abilities/settings/update-global-settings.php:200 +#: inc/abilities/settings/update-global-settings.php:202 msgid "No valid settings keys provided for this category." msgstr "Für diese Kategorie wurden keine gültigen Einstellungsschlüssel bereitgestellt." -#: inc/abilities/settings/update-global-settings.php:226 +#: inc/abilities/settings/update-global-settings.php:228 msgid "Invalid settings category." msgstr "Ungültige Einstellungskategorie." @@ -16794,19 +16797,19 @@ msgstr "Webhook-Geheimnis nicht konfiguriert." msgid "Invalid webhook signature." msgstr "Ungültige Webhook-Signatur." -#: admin/admin.php:492 -#: admin/admin.php:1085 +#: admin/admin.php:507 +#: admin/admin.php:1101 #: assets/build/formEditor.js:172 msgid "Quizzes" msgstr "Quizze" -#: admin/admin.php:491 +#: admin/admin.php:506 msgid "Quiz Entries" msgstr "Quiz-Einträge" -#: admin/admin.php:494 -#: admin/admin.php:527 -#: admin/admin.php:560 +#: admin/admin.php:509 +#: admin/admin.php:542 +#: admin/admin.php:575 #: assets/build/settings.js:172 msgid "New" msgstr "Neu" @@ -17324,43 +17327,43 @@ msgstr "Perfekt für Bildung, Schulungen und unterhaltsame Quizfragen" msgid "Select this to create a quiz with scored questions and graded results." msgstr "Wählen Sie dies, um ein Quiz mit bewerteten Fragen und benoteten Ergebnissen zu erstellen." -#: admin/admin.php:524 -#: admin/admin.php:525 -#: admin/admin.php:1090 +#: admin/admin.php:539 +#: admin/admin.php:540 +#: admin/admin.php:1106 msgid "Survey Reports" msgstr "Umfrageberichte" -#: inc/frontend-assets.php:301 +#: inc/frontend-assets.php:304 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 1" msgstr "Überschrift 1" -#: inc/frontend-assets.php:302 +#: inc/frontend-assets.php:305 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 2" msgstr "Überschrift 2" -#: inc/frontend-assets.php:303 +#: inc/frontend-assets.php:306 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 3" msgstr "Überschrift 3" -#: inc/frontend-assets.php:304 +#: inc/frontend-assets.php:307 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 4" msgstr "Überschrift 4" -#: inc/frontend-assets.php:305 +#: inc/frontend-assets.php:308 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 5" msgstr "Überschrift 5" -#: inc/frontend-assets.php:306 +#: inc/frontend-assets.php:309 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 6" @@ -17694,15 +17697,15 @@ msgstr "Ungültiger Beitrag." msgid "You cannot edit this post." msgstr "Sie können diesen Beitrag nicht bearbeiten." -#: inc/ai-form-builder/ai-form-builder.php:96 +#: inc/ai-form-builder/ai-form-builder.php:108 msgid "The AI did not return a form. Please refine your prompt and try again." msgstr "Die KI hat kein Formular zurückgegeben. Bitte verfeinern Sie Ihre Eingabeaufforderung und versuchen Sie es erneut." -#: inc/ai-form-builder/ai-form-builder.php:104 +#: inc/ai-form-builder/ai-form-builder.php:116 msgid "The AI response is missing a form title. Please try again." msgstr "Die KI-Antwort fehlt ein Formular-Titel. Bitte versuchen Sie es erneut." -#: inc/ai-form-builder/ai-form-builder.php:115 +#: inc/ai-form-builder/ai-form-builder.php:127 #: inc/ai-form-builder/field-mapping.php:69 msgid "The AI was unable to generate form fields. Please try again." msgstr "Die KI konnte keine Formularfelder generieren. Bitte versuchen Sie es erneut." @@ -17743,7 +17746,7 @@ msgstr "Einmalige Zahlung" msgid "Choose payment type" msgstr "Wählen Sie die Zahlungsart" -#: inc/payments/front-end.php:333 +#: inc/payments/front-end.php:334 msgid "Billing interval does not match the form configuration." msgstr "Das Abrechnungsintervall stimmt nicht mit der Formular-Konfiguration überein." @@ -17887,15 +17890,15 @@ msgstr "Die KI-Antwort war leer. Bitte verfeinern Sie Ihre Eingabeaufforderung u msgid "Unable to build form fields from the AI response." msgstr "Formularfelder können nicht aus der KI-Antwort erstellt werden." -#: admin/admin.php:557 -#: admin/admin.php:558 -#: admin/admin.php:1095 +#: admin/admin.php:572 +#: admin/admin.php:573 +#: admin/admin.php:1111 msgid "Partial Entries" msgstr "Teilweise Einträge" #: inc/global-settings/global-settings-defaults.php:260 -#: inc/global-settings/global-settings.php:425 -#: inc/global-settings/global-settings.php:613 +#: inc/global-settings/global-settings.php:427 +#: inc/global-settings/global-settings.php:615 #: assets/build/settings.js:172 msgid "This form is now closed as we've received all the entries." msgstr "Dieses Formular ist jetzt geschlossen, da wir alle Einsendungen erhalten haben." @@ -17904,16 +17907,16 @@ msgstr "Dieses Formular ist jetzt geschlossen, da wir alle Einsendungen erhalten msgid "Invalid settings tab." msgstr "Ungültiger Einstellungsreiter." -#: inc/global-settings/global-settings.php:481 +#: inc/global-settings/global-settings.php:483 #: assets/build/settings.js:172 msgid "Thank you for contacting us! We will be in touch with you shortly." msgstr "Vielen Dank, dass Sie uns kontaktiert haben! Wir werden uns in Kürze mit Ihnen in Verbindung setzen." -#: inc/rest-api.php:1115 +#: inc/rest-api.php:1127 msgid "Use the restore action to recover a trashed form before switching it to draft." msgstr "Verwenden Sie die Wiederherstellungsaktion, um ein gelöschtes Formular wiederherzustellen, bevor Sie es in den Entwurfsmodus versetzen." -#: inc/rest-api.php:1120 +#: inc/rest-api.php:1132 msgid "This form is already a draft." msgstr "Dieses Formular ist bereits ein Entwurf." @@ -18760,6 +18763,72 @@ msgstr "Der Zahlungsbetrag konnte für dieses Formular nicht verifiziert werden. msgid "Cancellation is not supported for this payment gateway." msgstr "Eine Stornierung wird für dieses Zahlungs-Gateway nicht unterstützt." +#: admin/admin.php:1988 +msgid "SureForms AI Quick Draft" +msgstr "SureForms AI Schnellentwurf" + +#: admin/admin.php:2007 +msgid "Describe the form and let SureForms AI generate it for you." +msgstr "Beschreiben Sie das Formular und lassen Sie SureForms AI es für Sie erstellen." + +#: admin/admin.php:2010 +msgid "Describe your form" +msgstr "Beschreiben Sie Ihr Formular" + +#: admin/admin.php:2017 +msgid "Example: Create a contact form with name, email, phone, and message fields." +msgstr "Beispiel: Erstellen Sie ein Kontaktformular mit Feldern für Name, E-Mail, Telefon und Nachricht." + +#: admin/admin.php:2058 +msgid "Redirecting..." +msgstr "Weiterleitung..." + +#: inc/ai-form-builder/ai-form-builder.php:57 +msgid "No prompt was supplied." +msgstr "Es wurde keine Eingabe bereitgestellt." + +#: inc/ai-form-builder/ai-form-builder.php:64 +msgid "The prompt is too long. Please shorten it and try again." +msgstr "Die Eingabe ist zu lang. Bitte kürzen Sie sie und versuchen Sie es erneut." + +#. translators: %s: maximum characters allowed before the @ symbol. +#: inc/field-validation.php:316 +#: inc/translatable.php:97 +#, php-format +msgid "The part before @ may not exceed %s characters." +msgstr "Der Teil vor dem @ darf %s Zeichen nicht überschreiten." + +#. translators: %s: maximum characters allowed after the @ symbol. +#: inc/field-validation.php:322 +#: inc/translatable.php:100 +#, php-format +msgid "The part after @ may not exceed %s characters." +msgstr "Der Teil nach dem @ darf %s Zeichen nicht überschreiten." + +#: assets/build/formEditor.js:172 +msgid "Show Entries" +msgstr "Einträge anzeigen" + +#: assets/build/formEditor.js:172 +msgid "Display this form’s entries in a table on the frontend." +msgstr "Zeigen Sie die Einträge dieses Formulars in einer Tabelle im Frontend an." + +#: assets/build/formEditor.js:172 +msgid "Publish this form’s submissions as a sortable, paginated table on any page — perfect for public directories, listings, and team dashboards, with per-role view access." +msgstr "Veröffentlichen Sie die Einsendungen dieses Formulars als sortierbare, paginierte Tabelle auf jeder Seite — ideal für öffentliche Verzeichnisse, Listen und Team-Dashboards, mit Zugriff nach Rolle." + +#: assets/build/formEditor.js:172 +msgid "Embed a sortable, paginated entries table with a simple shortcode." +msgstr "Betten Sie eine sortierbare, paginierte Eintragstabelle mit einem einfachen Shortcode ein." + +#: assets/build/formEditor.js:172 +msgid "Choose exactly which fields appear as columns." +msgstr "Wählen Sie genau aus, welche Felder als Spalten erscheinen." + +#: assets/build/formEditor.js:172 +msgid "Restrict who can view entries by user role." +msgstr "Beschränken Sie, wer Einträge nach Benutzerrolle anzeigen kann." + #: inc/post-types.php:226 msgctxt "post type general name" msgid "Forms" @@ -19187,34 +19256,34 @@ msgctxt "block keyword" msgid "color" msgstr "Farbe" -#: inc/frontend-assets.php:300 +#: inc/frontend-assets.php:303 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgctxt "Quill heading picker: default paragraph style" msgid "Normal" msgstr "Normal" -#: inc/frontend-assets.php:307 +#: inc/frontend-assets.php:310 msgctxt "Quill link tooltip label" msgid "Visit URL:" msgstr "URL besuchen:" -#: inc/frontend-assets.php:308 +#: inc/frontend-assets.php:311 msgctxt "Quill link tooltip label" msgid "Enter link:" msgstr "Link eingeben:" -#: inc/frontend-assets.php:309 +#: inc/frontend-assets.php:312 msgctxt "Quill link tooltip action" msgid "Edit" msgstr "Bearbeiten" -#: inc/frontend-assets.php:310 +#: inc/frontend-assets.php:313 msgctxt "Quill link tooltip action" msgid "Save" msgstr "Speichern" -#: inc/frontend-assets.php:311 +#: inc/frontend-assets.php:314 msgctxt "Quill link tooltip action" msgid "Remove" msgstr "Entfernen" diff --git a/languages/sureforms-es_ES-1cb9ecd067cd971ff5d9db0b4dae2891.json b/languages/sureforms-es_ES-1cb9ecd067cd971ff5d9db0b4dae2891.json index 2b5a8fac9..7484c5a67 100644 --- a/languages/sureforms-es_ES-1cb9ecd067cd971ff5d9db0b4dae2891.json +++ b/languages/sureforms-es_ES-1cb9ecd067cd971ff5d9db0b4dae2891.json @@ -1 +1 @@ -{"translation-revision-date":"2024-12-13T16:34:28+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Estado"],"Form":["Formulario"],"Fields":["Campos"],"Image":["Imagen"],"Activated":["Activado"],"Activate":["Activar"],"Submit":["Enviar"],"Global Settings":["Configuraci\u00f3n global"],"Form Title":["T\u00edtulo del formulario"],"Edit":["Editar"],"Please enter a valid URL.":["Por favor, introduce una URL v\u00e1lida."],"Desktop":["Escritorio"],"Medium":["Medio"],"Mobile":["M\u00f3vil"],"Repeat":["Repetir"],"Scroll":["Desplazar"],"Signature":["Firma"],"Tablet":["Tableta"],"Upload":["Subir"],"Basic":["B\u00e1sico"],"Form Settings":["Configuraci\u00f3n del formulario"],"General":["General"],"Style":["Estilo"],"Advanced":["Avanzado"],"No tags available":["No hay etiquetas disponibles"],"Device":["Dispositivo"],"Select Shortcodes":["Seleccionar c\u00f3digos cortos"],"Page Break Label":["Etiqueta de salto de p\u00e1gina"],"Next":["Siguiente"],"Back":["Atr\u00e1s"],"Reset":["Restablecer"],"Generic tags":["Etiquetas gen\u00e9ricas"],"Pixel":["P\u00edxel"],"Em":["Em"],"Select Units":["Seleccionar unidades"],"%s units":["%s unidades"],"Margin":["Margen"],"None":["Ninguno"],"Custom":["Personalizado"],"Please add a option props to MultiButtonsControl":["Por favor, a\u00f1ade una opci\u00f3n de propiedades a MultiButtonsControl"],"Confirm":["Confirmar"],"Cancel":["Cancelar"],"Processing\u2026":["Procesando\u2026"],"Select Video":["Seleccionar video"],"Change Video":["Cambiar video"],"Select Lottie Animation":["Seleccionar animaci\u00f3n Lottie"],"Change Lottie Animation":["Cambiar animaci\u00f3n Lottie"],"Upload SVG":["Subir SVG"],"Change SVG":["Cambiar SVG"],"Select Image":["Seleccionar imagen"],"Change Image":["Cambiar imagen"],"Upload SVG?":["\u00bfSubir SVG?"],"Upload SVG can be potentially risky. Are you sure?":["Subir SVG puede ser potencialmente arriesgado. \u00bfEst\u00e1s seguro?"],"Upload Anyway":["Subir de todos modos"],"Full Width":["Ancho completo"],"Install":["Instalar"],"Plugin Installation failed, Please try again later.":["La instalaci\u00f3n del complemento fall\u00f3, por favor intente de nuevo m\u00e1s tarde."],"Plugin activation failed, Please try again later.":["La activaci\u00f3n del complemento fall\u00f3, por favor intente de nuevo m\u00e1s tarde."],"Integrations":["Integraciones"],"%s Removed from Quick Action Bar.":["%s eliminado de la barra de acci\u00f3n r\u00e1pida."],"Add to Quick Action Bar":["Agregar a la barra de acci\u00f3n r\u00e1pida"],"%s Added to Quick Action Bar.":["%s a\u00f1adido a la barra de acci\u00f3n r\u00e1pida."],"Already Present in Quick Action Bar":["Ya presente en la barra de acci\u00f3n r\u00e1pida"],"No results found.":["No se encontraron resultados."],"data object is empty":["el objeto de datos est\u00e1 vac\u00edo"],"Add blocks to Quick Action Bar":["Agregar bloques a la barra de acci\u00f3n r\u00e1pida"],"Re-arrange block inside Quick Action Bar":["Reorganizar bloque dentro de la Barra de Acci\u00f3n R\u00e1pida"],"Upgrade":["Actualizar"],"Connecting\u2026":["Conectando\u2026"],"Install & Activate":["Instalar y activar"],"Compliance Settings":["Configuraci\u00f3n de Cumplimiento"],"Enable GDPR Compliance":["Habilitar el cumplimiento del RGPD"],"Never store entry data after form submission":["Nunca almacenes los datos de entrada despu\u00e9s de enviar el formulario"],"When enabled this form will never store Entries.":["Cuando est\u00e9 habilitado, este formulario nunca almacenar\u00e1 entradas."],"Automatically delete entries":["Eliminar autom\u00e1ticamente las entradas"],"When enabled this form will automatically delete entries after a certain period of time.":["Cuando est\u00e9 habilitado, este formulario eliminar\u00e1 autom\u00e1ticamente las entradas despu\u00e9s de un cierto per\u00edodo de tiempo."],"Entries older than the days set will be deleted automatically.":["Las entradas m\u00e1s antiguas que los d\u00edas establecidos se eliminar\u00e1n autom\u00e1ticamente."],"Custom CSS":["CSS personalizado"],"The following CSS styles added below will only apply to this form container.":["Los siguientes estilos CSS a\u00f1adidos a continuaci\u00f3n solo se aplicar\u00e1n a este contenedor de formulario."],"Visual":["Visual"],"HTML":["HTML"],"All Data":["Todos los datos"],"Add Shortcode":["Agregar c\u00f3digo corto"],"Form input tags":["Etiquetas de entrada de formulario"],"Comma separated values are also accepted.":["Tambi\u00e9n se aceptan valores separados por comas."],"Email Notification":["Notificaci\u00f3n de correo electr\u00f3nico"],"Name":["Nombre"],"Send Email To":["Enviar correo electr\u00f3nico a"],"Subject":["Asunto"],"CC":["CC"],"BCC":["BCC"],"Reply To":["Responder a"],"Add Notification":["Agregar notificaci\u00f3n"],"Add Key":["Agregar clave"],"Add Value":["A\u00f1adir valor"],"Add":["A\u00f1adir"],"Confirmation Message":["Mensaje de confirmaci\u00f3n"],"After Form Submission":["Despu\u00e9s del env\u00edo del formulario"],"Hide Form":["Ocultar formulario"],"Reset Form":["Restablecer formulario"],"Custom URL":["URL personalizada"],"Add Query Parameters":["Agregar par\u00e1metros de consulta"],"Select if you want to add key-value pairs for form fields to include in query parameters":["Seleccione si desea agregar pares clave-valor para los campos del formulario que se incluir\u00e1n en los par\u00e1metros de consulta"],"Query Parameters":["Par\u00e1metros de consulta"],"Please select a page.":["Por favor, seleccione una p\u00e1gina."],"Suggestion: URL should use HTTPS":["Sugerencia: la URL deber\u00eda usar HTTPS"],"Success Message":["Mensaje de \u00e9xito"],"Redirect":["Redirigir"],"Redirect to":["Redirigir a"],"Page":["P\u00e1gina"],"Form Confirmation":["Confirmaci\u00f3n de formulario"],"Confirmation Type":["Tipo de confirmaci\u00f3n"],"Use Labels as Placeholders":["Usa etiquetas como marcadores de posici\u00f3n"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["La configuraci\u00f3n anterior colocar\u00e1 las etiquetas dentro de los campos como marcadores de posici\u00f3n (donde sea posible). Esta configuraci\u00f3n solo tiene efecto en la p\u00e1gina en vivo, no en la vista previa del editor."],"Page Break":["Salto de p\u00e1gina"],"Show Labels":["Mostrar etiquetas"],"First Page Label":["Etiqueta de la primera p\u00e1gina"],"Progress Indicator":["Indicador de progreso"],"Progress Bar":["Barra de progreso"],"Connector":["Conector"],"Steps":["Pasos"],"Next Button Text":["Texto del bot\u00f3n Siguiente"],"Back Button Text":["Texto del bot\u00f3n de retroceso"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["\u00bfEst\u00e1 seguro de que desea cerrar? Sus cambios no guardados se perder\u00e1n ya que tiene algunos errores de validaci\u00f3n."],"There are few unsaved changes. Please save your changes to reflect the updates.":["Hay algunos cambios no guardados. Por favor, guarde sus cambios para reflejar las actualizaciones."],"Form Behavior":["Comportamiento del formulario"],"Clear":["Claro"],"Select Color":["Seleccionar color"],"Primary Color":["Color primario"],"Text Color":["Color del texto"],"Text Color on Primary":["Color del texto en primario"],"Field Spacing":["Espaciado de campo"],"Small":["Peque\u00f1o"],"Large":["Grande"],"Left":["Izquierda"],"Center":["Centro"],"Right":["Correcto"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Invisible"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Selector de fecha"],"Time Picker":["Selector de tiempo"],"Hidden":["Oculto"],"Slider":["Deslizador"],"Rating":["Calificaci\u00f3n"],"Upgrade to Unlock These Fields":["Actualiza para desbloquear estos campos"],"Add Block":["Agregar bloque"],"Customize with SureForms":["Personaliza con SureForms"],"Page break":["Salto de p\u00e1gina"],"Previous":["Anterior"],"Thank you":["Gracias"],"Form submitted successfully!":["\u00a1Formulario enviado con \u00e9xito!"],"Instant Form":["Formulario Instant\u00e1neo"],"Enable Instant Form":["Habilitar formulario instant\u00e1neo"],"Enable Preview":["Habilitar vista previa"],"Show Title":["Mostrar t\u00edtulo"],"Site Logo":["Logo del sitio"],"Banner Background":["Fondo del Banner"],"Color":["Color"],"Upload Image":["Subir imagen"],"Background Color":["Color de fondo"],"Use banner as page background":["Usa el banner como fondo de p\u00e1gina"],"Form Width":["Ancho del formulario"],"URL":["URL"],"URL Slug":["Slug de URL"],"The last part of the URL.":["La \u00faltima parte de la URL."],"Learn more.":["Aprende m\u00e1s."],"SureForms Description":["Descripci\u00f3n de SureForms"],"Form Options":["Opciones de formulario"],"Form Shortcode":["C\u00f3digo corto del formulario"],"Paste this shortcode on the page or post to render this form.":["Pega este c\u00f3digo corto en la p\u00e1gina o publicaci\u00f3n para mostrar este formulario."],"Spam Protection":["Protecci\u00f3n contra el spam"],"Auto":["Auto"],"Normal":["Normal"],"%":["%"],"Top":["Superior"],"Bottom":["Fondo"],"Solid":["S\u00f3lido"],"Width":["Ancho"],"Size":["Tama\u00f1o"],"EM":["EM"],"Padding":["Relleno"],"Color 1":["Color 1"],"Color 2":["Color 2"],"Type":["Tipo"],"Linear":["Lineal"],"Radial":["Radial"],"Location 1":["Ubicaci\u00f3n 1"],"Location 2":["Ubicaci\u00f3n 2"],"Angle":["\u00c1ngulo"],"Classic":["Cl\u00e1sico"],"Gradient":["Gradiente"],"Background":["Antecedentes"],"Cover":["Cubrir"],"Contain":["Contener"],"Overlay":["Superposici\u00f3n"],"No Repeat":["No repetir"],"Overlay Opacity":["Opacidad de superposici\u00f3n"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Los nombres de clase deben estar separados por espacios. Cada nombre de clase no debe comenzar con un d\u00edgito, guion o guion bajo. Solo pueden incluir letras (incluidos caracteres Unicode), n\u00fameros, guiones y guiones bajos."],"Conversational Layout":["Dise\u00f1o Conversacional"],"Unlock Conversational Forms":["Desbloquear formularios conversacionales"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Con el Plan SureForms Pro, puedes transformar tus formularios en dise\u00f1os conversacionales atractivos para una experiencia de usuario fluida."],"Premium":["Premium"],"Overlay Type":["Tipo de superposici\u00f3n"],"Image Overlay Color":["Color de superposici\u00f3n de imagen"],"Image Position":["Posici\u00f3n de la imagen"],"Attachment":["Adjunto"],"Fixed":["Fijo"],"Blend Mode":["Modo de fusi\u00f3n"],"Multiply":["Multiplicar"],"Screen":["Pantalla"],"Darken":["Oscurecer"],"Lighten":["Aligerar"],"Color Dodge":["Sobreexponer color"],"Saturation":["Saturaci\u00f3n"],"Repeat-x":["Repetir-x"],"Repeat-y":["Repetir-y"],"PX":["PX"],"Button":["Bot\u00f3n"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Con\u00e9ctate con OttoKit"],"SUREFORMS PREMIUM FIELDS":["CAMPOS PREMIUM DE SUREFORMS"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["La direcci\u00f3n de 'Correo Electr\u00f3nico del Remitente' actual no coincide con el nombre de dominio de su sitio web (%1$s). Esto puede hacer que sus correos electr\u00f3nicos de notificaci\u00f3n sean bloqueados o marcados como spam. Alternativamente, intente usar una direcci\u00f3n de remitente que coincida con el dominio de su sitio web (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["La direcci\u00f3n de 'Correo Electr\u00f3nico del Remitente' actual no coincide con el nombre de dominio de su sitio web (%s). Esto puede hacer que sus correos electr\u00f3nicos de notificaci\u00f3n sean bloqueados o marcados como spam."],"We strongly recommend that you install the free ":["Recomendamos encarecidamente que instale el gratuito"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["\u00a1Complemento! El asistente de configuraci\u00f3n facilita la correcci\u00f3n de tus correos electr\u00f3nicos."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Alternativamente, intenta usar una direcci\u00f3n de remitente que coincida con el dominio de tu sitio web (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Por favor, introduce una direcci\u00f3n de correo electr\u00f3nico v\u00e1lida. Tus notificaciones no se enviar\u00e1n si el campo no est\u00e1 rellenado correctamente."],"From Name":["De Nombre"],"From Email":["De correo electr\u00f3nico"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["La direcci\u00f3n de 'Correo Electr\u00f3nico del Remitente' actual puede no coincidir con el nombre de dominio de su sitio web (%1$s). Esto puede hacer que sus correos electr\u00f3nicos de notificaci\u00f3n sean bloqueados o marcados como spam. Alternativamente, intente usar una Direcci\u00f3n del Remitente que coincida con el dominio de su sitio web (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["La direcci\u00f3n de 'Correo Electr\u00f3nico del Remitente' actual puede no coincidir con el nombre de dominio de su sitio web (%s). Esto puede hacer que sus correos electr\u00f3nicos de notificaci\u00f3n sean bloqueados o marcados como spam."],"Border Radius":["Radio de borde"],"Form Theme":["Tema del formulario"],"Instant Form Padding":["Relleno Instant\u00e1neo de Formularios"],"Instant Form Border Radius":["Radio de Borde de Formulario Instant\u00e1neo"],"Select Gradient":["Seleccionar degradado"],"Upgrade Now":["Actualiza ahora"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["Las entradas anteriores a los d\u00edas seleccionados ser\u00e1n eliminadas."],"Entries Time Period":["Per\u00edodo de tiempo de las entradas"],"Custom CSS Panel":["Panel de CSS personalizado"],"Notifications can use only one From Email so please enter a single address.":["Las notificaciones solo pueden usar un \u00fanico correo electr\u00f3nico de origen, as\u00ed que por favor ingrese una sola direcci\u00f3n."],"Email Notifications":["Notificaciones por correo electr\u00f3nico"],"Actions":["Acciones"],"Duplicate":["Duplicado"],"Delete":["Eliminar"],"Select Page to redirect":["Seleccionar p\u00e1gina para redirigir"],"Search for a page":["Buscar una p\u00e1gina"],"Select a page":["Selecciona una p\u00e1gina"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Guardar"],"Login":["Iniciar sesi\u00f3n"],"Register":["Registrar"],"Date":["Fecha"],"Advanced Settings":["Configuraci\u00f3n avanzada"],"Form Restriction":["Restricci\u00f3n de formulario"],"Maximum Number of Entries":["N\u00famero m\u00e1ximo de entradas"],"Maximum Entries":["Entradas M\u00e1ximas"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["La configuraci\u00f3n del Per\u00edodo de Tiempo funciona seg\u00fan la zona horaria de tu sitio de WordPress. Haz clic aqu\u00ed<\/a> para abrir la Configuraci\u00f3n General de WordPress, donde puedes verificarla y actualizarla."],"Click here":["Haz clic aqu\u00ed"],"Response Description After Maximum Entries":["Descripci\u00f3n de la respuesta despu\u00e9s del m\u00e1ximo de entradas"],"All changes will be saved automatically when you press back.":["Todos los cambios se guardar\u00e1n autom\u00e1ticamente cuando presiones atr\u00e1s."],"Repeater":["Repetidor"],"OttoKit Settings":["Configuraci\u00f3n de OttoKit"],"Get Started":["Comenzar"],"Connect Native Integrations with SureForms":["Conecta integraciones nativas con SureForms"],"Expected format for emails - email@sureforms.com or John Doe ":["Formato esperado para correos electr\u00f3nicos: email@sureforms.com o John Doe "],"delete":["eliminar"],"Please type \"%s\" in the input box":["Por favor, escribe \"%s\" en el cuadro de entrada"],"To confirm, type \"%s\" in the box below:":["Para confirmar, escriba \"%s\" en el cuadro de abajo:"],"Type \"%s\"":["Escribe \"%s\""],"Payment":["Pago"],"%s - Order ID":["%s - ID de pedido"],"%s - Amount":["%s - Cantidad"],"%s - Customer Email":["%s - Correo Electr\u00f3nico del Cliente"],"%s - Customer Name":["%s - Nombre del Cliente"],"%s - Status":["%s - Estado"],"Add custom CSS rules to style this specific form independently of global styles.":["Agrega reglas CSS personalizadas para estilizar este formulario espec\u00edfico independientemente de los estilos globales."],"Spam Protection Type":["Tipo de Protecci\u00f3n contra Spam"],"Select Security Type":["Seleccionar tipo de seguridad"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Nota: Usar diferentes versiones de reCAPTCHA (V2 checkbox y V3) en la misma p\u00e1gina crear\u00e1 conflictos entre las versiones. Por favor, evite usar diferentes versiones en la misma p\u00e1gina."],"Select Version":["Seleccionar versi\u00f3n"],"Please configure the API keys correctly from the settings":["Por favor, configure las claves API correctamente desde la configuraci\u00f3n"],"Control email alerts sent to admins or users after a form submission.":["Controla las alertas de correo electr\u00f3nico enviadas a los administradores o usuarios despu\u00e9s de una presentaci\u00f3n de formulario."],"Customize the confirmation message or redirect the users after submitting the form.":["Personaliza el mensaje de confirmaci\u00f3n o redirige a los usuarios despu\u00e9s de enviar el formulario."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Establezca l\u00edmites en la cantidad de veces que se puede enviar un formulario y gestione las opciones de cumplimiento, incluidas GDPR y la retenci\u00f3n de datos."],"Go to OttoKit Settings":["Ve a la configuraci\u00f3n de OttoKit"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Conecta SureForms con tus aplicaciones favoritas para automatizar tareas y sincronizar datos sin problemas."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Desbloquea potentes integraciones en el plan Premium para automatizar tus flujos de trabajo y conectar SureForms directamente con tus herramientas favoritas."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Env\u00eda las presentaciones de formularios directamente a los CRM, correo electr\u00f3nico y plataformas de marketing."],"Automate repetitive tasks with seamless data syncing.":["Automatiza tareas repetitivas con una sincronizaci\u00f3n de datos sin problemas."],"Access exclusive native integrations for faster workflows.":["Accede a integraciones nativas exclusivas para flujos de trabajo m\u00e1s r\u00e1pidos."],"PDF Generation":["Generaci\u00f3n de PDF"],"Generate and customize PDF copies of form submissions.":["Genera y personaliza copias en PDF de los env\u00edos de formularios."],"Generate Submission PDFs":["Generar PDFs de env\u00edo"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Convierte cada entrada de formulario en un archivo PDF pulido, haci\u00e9ndolo perfecto para informes, registros o compartir."],"Automatically generate PDFs from your form submissions.":["Genera autom\u00e1ticamente PDFs a partir de tus env\u00edos de formularios."],"Customize PDF templates with your branding.":["Personaliza las plantillas de PDF con tu marca."],"Download or email PDFs instantly.":["Descarga o env\u00eda por correo electr\u00f3nico los PDFs al instante."],"User Registration":["Registro de Usuario"],"Onboard new users or update existing accounts through beautiful looking forms.":["Incorpore nuevos usuarios o actualice cuentas existentes a trav\u00e9s de formularios de aspecto atractivo."],"Register Users with SureForms":["Registrar usuarios con SureForms"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Optimiza todo el proceso de incorporaci\u00f3n de usuarios para tus sitios con inicios de sesi\u00f3n y registros fluidos impulsados por formularios."],"Register new users directly via your form submissions.":["Registra nuevos usuarios directamente a trav\u00e9s de tus env\u00edos de formularios."],"Create or update existing accounts by mapping form data to user fields.":["Cree o actualice cuentas existentes asignando datos del formulario a campos de usuario."],"Assign roles and control access automatically.":["Asigna roles y controla el acceso autom\u00e1ticamente."],"Post Feed":["Publicar en el feed"],"Transform your form submission into WordPress posts.":["Transforma tu env\u00edo de formulario en publicaciones de WordPress."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Convierte autom\u00e1ticamente las presentaciones de formularios en publicaciones, p\u00e1ginas o tipos de publicaciones personalizadas de WordPress. Ahorra mucho tiempo y permite que tus formularios publiquen contenido directamente."],"Create posts, pages, or CPTs from your form entries.":["Crea publicaciones, p\u00e1ginas o CPTs a partir de las entradas de tu formulario."],"Map form fields to your post fields easily.":["Mapea los campos del formulario a los campos de tu publicaci\u00f3n f\u00e1cilmente."],"Automate the content publishing flow with few simple steps.":["Automatiza el flujo de publicaci\u00f3n de contenido con unos pocos pasos simples."],"Automations":["Automatizaciones"],"Unlock Advanced Styling":["Desbloquear estilo avanzado"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Obt\u00e9n control total sobre el aspecto de tu formulario con colores, fuentes y dise\u00f1os personalizados."],"Button Alignment":["Alineaci\u00f3n del bot\u00f3n"],"Add Custom CSS Class(es)":["Agregar clase(s) CSS personalizada(s)"],"Set the total number of submissions allowed for this form.":["Establezca el n\u00famero total de env\u00edos permitidos para este formulario."],"Save & Progress":["Guardar y Progresar"],"Allow users to save their progress and continue form completion later.":["Permitir a los usuarios guardar su progreso y continuar completando el formulario m\u00e1s tarde."],"Save & Progress in SureForms":["Guardar y Progresar en SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["Brinda a tus usuarios la flexibilidad de completar formularios a su propio ritmo permiti\u00e9ndoles guardar el progreso y regresar en cualquier momento."],"Let users pause long or multi-step forms and continue later.":["Permitir a los usuarios pausar formularios largos o de varios pasos y continuar m\u00e1s tarde."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Reduzca el abandono de formularios con enlaces de reanudaci\u00f3n convenientes y acceda a su progreso desde cualquier lugar."],"Improve user experience for lengthy, complex, or multi-page forms.":["Mejorar la experiencia del usuario para formularios extensos, complejos o de varias p\u00e1ginas."],"This form is not yet available. Please check back after the scheduled start time.":["Este formulario a\u00fan no est\u00e1 disponible. Por favor, vuelva a consultar despu\u00e9s de la hora de inicio programada."],"This form is no longer accepting submissions. The submission period has ended.":["Este formulario ya no acepta env\u00edos. El per\u00edodo de env\u00edo ha terminado."],"The start date and time must be before the end date and time.":["La fecha y hora de inicio deben ser anteriores a la fecha y hora de finalizaci\u00f3n."],"Form Scheduling":["Programaci\u00f3n de formularios"],"Enable Form Scheduling":["Habilitar la programaci\u00f3n de formularios"],"Set a time period during which this form will be available for submissions.":["Establezca un per\u00edodo de tiempo durante el cual este formulario estar\u00e1 disponible para env\u00edos."],"Start Date & Time":["Fecha y hora de inicio"],"End Date & Time":["Fecha y hora de finalizaci\u00f3n"],"Response Description Before Start Date":["Descripci\u00f3n de la respuesta antes de la fecha de inicio"],"Response Description After End Date":["Descripci\u00f3n de la respuesta despu\u00e9s de la fecha de finalizaci\u00f3n"],"Conditional Confirmations":["Confirmaciones Condicionales"],"Set up the message or redirect users will see after submitting the form.":["Configure el mensaje o la redirecci\u00f3n que los usuarios ver\u00e1n despu\u00e9s de enviar el formulario."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Muestra el mensaje correcto al usuario adecuado seg\u00fan c\u00f3mo respondan. Personaliza las confirmaciones con condiciones inteligentes y gu\u00eda a los usuarios al siguiente mejor paso autom\u00e1ticamente."],"Display different confirmation messages based on form responses.":["Muestra diferentes mensajes de confirmaci\u00f3n seg\u00fan las respuestas del formulario."],"Redirect users to specific pages or URLs conditionally.":["Redirige a los usuarios a p\u00e1ginas o URLs espec\u00edficas de manera condicional."],"Create personalized thank-you messages without extra forms.":["Crea mensajes de agradecimiento personalizados sin formularios adicionales."],"Lost Password":["Contrase\u00f1a perdida"],"Reset Password":["Restablecer contrase\u00f1a"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["Seleccione un servicio de protecci\u00f3n contra spam. Configure las claves API en Configuraci\u00f3n Global antes de habilitarlo."],"Send as Raw HTML":["Enviar como HTML sin procesar"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Cuando est\u00e9 habilitado, el cuerpo del correo electr\u00f3nico en HTML se conservar\u00e1 exactamente como est\u00e1 escrito y se envolver\u00e1 en una plantilla de correo electr\u00f3nico profesional."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["Las etiquetas inteligentes que hacen referencia a campos enviados por el usuario no se escapar\u00e1n en el modo HTML sin procesar. Evita insertar valores de campos no confiables directamente en el cuerpo del correo electr\u00f3nico."],"Please provide a recipient email address and subject line.":["Por favor, proporcione una direcci\u00f3n de correo electr\u00f3nico del destinatario y una l\u00ednea de asunto."],"Email notification duplicated!":["\u00a1Notificaci\u00f3n de correo electr\u00f3nico duplicada!"],"Are you sure you want to delete this email notification?":["\u00bfEst\u00e1s seguro de que deseas eliminar esta notificaci\u00f3n por correo electr\u00f3nico?"],"Email notification deleted!":["\u00a1Notificaci\u00f3n de correo electr\u00f3nico eliminada!"],"URL is missing Top Level Domain (TLD).":["Falta el dominio de nivel superior (TLD) en la URL."],"This form is now closed as the maximum number of entries has been received.":["Este formulario est\u00e1 cerrado ya que se ha recibido el n\u00famero m\u00e1ximo de entradas."],"Publish Your Form":["Publica tu formulario"],"Enable This to Instantly Publish the Form":["Habilitar esto para publicar el formulario instant\u00e1neamente"],"Style Your Instant Form Page Here":["Estiliza tu p\u00e1gina de formulario instant\u00e1neo aqu\u00ed"],"Quizzes":["Cuestionarios"],"%s - Description":["%s - Descripci\u00f3n"],"Send entries to 100+ popular apps.":["Env\u00eda entradas a m\u00e1s de 100 aplicaciones populares."],"Build automated workflows that run instantly.":["Crea flujos de trabajo automatizados que se ejecutan al instante."],"Create custom app integrations using our Custom App feature.":["Crea integraciones de aplicaciones personalizadas utilizando nuestra funci\u00f3n de Aplicaci\u00f3n Personalizada."],"Keep your tools in sync automatically.":["Mant\u00e9n tus herramientas sincronizadas autom\u00e1ticamente."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["Esto instalar\u00e1 y activar\u00e1 OttoKit en su sitio de WordPress para habilitar funciones de automatizaci\u00f3n."],"Automate Your Forms with OttoKit":["Automatiza tus formularios con OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Cada env\u00edo de formulario deber\u00eda activar algo: una alerta de Slack, un cliente potencial en el CRM, un correo electr\u00f3nico de seguimiento o una nueva fila en Google Sheets."],"Create interactive quizzes to engage your audience and gather insights.":["Crea cuestionarios interactivos para involucrar a tu audiencia y recopilar informaci\u00f3n."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Dise\u00f1a cuestionarios atractivos con varios tipos de preguntas, retroalimentaci\u00f3n personalizada y puntuaci\u00f3n automatizada para cautivar a tu audiencia y obtener valiosos conocimientos."],"Create interactive quizzes with multiple question types.":["Crea cuestionarios interactivos con m\u00faltiples tipos de preguntas."],"Provide personalized feedback based on user responses.":["Proporciona comentarios personalizados basados en las respuestas del usuario."],"Automate scoring and lead segmentation for better insights.":["Automatiza la puntuaci\u00f3n y la segmentaci\u00f3n de leads para obtener mejores insights."],"Heading 1":["Encabezado 1"],"Heading 2":["Encabezado 2"],"Heading 3":["Encabezado 3"],"Heading 4":["Encabezado 4"],"Heading 5":["Encabezado 5"],"Heading 6":["Encabezado 6"],"Form settings saved.":["Configuraci\u00f3n del formulario guardada."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["El l\u00edmite de entradas se basa en las entradas almacenadas para contar las presentaciones. Mientras la Configuraci\u00f3n de Cumplimiento tenga habilitada la opci\u00f3n \"Nunca almacenar datos de entrada despu\u00e9s del env\u00edo del formulario\", este l\u00edmite no se aplicar\u00e1. Desactive esa opci\u00f3n o elimine el l\u00edmite de entradas para usar esta funci\u00f3n."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Configuraciones guardadas, pero los atributos de la publicaci\u00f3n (contrase\u00f1a \/ t\u00edtulo \/ contenido) no se actualizaron. Intente de nuevo para persistirlos."],"Failed to save form settings.":["Error al guardar la configuraci\u00f3n del formulario."],"Saving\u2026":["Guardando\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Cuando est\u00e9 habilitado, este formulario no almacenar\u00e1 la IP del usuario, el nombre del navegador ni el nombre del dispositivo en las entradas."],"Failed to save. Please try again.":["Error al guardar. Por favor, int\u00e9ntalo de nuevo."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["Las claves de la API de reCAPTCHA para la versi\u00f3n seleccionada no est\u00e1n configuradas. Establ\u00e9zcalas en Configuraci\u00f3n Global."],"Please select a reCAPTCHA version.":["Por favor, seleccione una versi\u00f3n de reCAPTCHA."],"hCaptcha API keys are not configured. Set them in Global Settings.":["Las claves de API de hCaptcha no est\u00e1n configuradas. Establ\u00e9celas en Configuraci\u00f3n Global."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["Las claves API de Cloudflare Turnstile no est\u00e1n configuradas. Establ\u00e9zcalas en Configuraci\u00f3n Global."],"Form data":["Datos del formulario"],"Some fields need attention":["Algunos campos necesitan atenci\u00f3n"],"Unsaved changes":["Cambios no guardados"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Se requiere una direcci\u00f3n de correo electr\u00f3nico del destinatario y una l\u00ednea de asunto antes de que se pueda guardar esta notificaci\u00f3n. Corrija los campos resaltados o descarte sus cambios para volver atr\u00e1s."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Tienes cambios no guardados para esta notificaci\u00f3n. Desc\u00e1rtalos para volver, o qu\u00e9date para guardarlos."],"Discard & go back":["Descartar y volver"],"Stay & fix":["Qu\u00e9date y arregla"],"Keep editing":["Sigue editando"],"Please provide a recipient email address.":["Por favor, proporcione una direcci\u00f3n de correo electr\u00f3nico del destinatario."],"Please provide a subject line.":["Por favor, proporcione una l\u00ednea de asunto."],"Please provide a custom URL.":["Por favor, proporcione una URL personalizada."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Tienes cambios no guardados. Des\u00e9chalos para continuar, o qu\u00e9date para guardar tus cambios."],"Discard & continue":["Descartar y continuar"],"Quill heading picker: default paragraph style\u0004Normal":["Normal"]}}} \ No newline at end of file +{"translation-revision-date":"2024-12-13T16:34:28+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Estado"],"Form":["Formulario"],"Fields":["Campos"],"Image":["Imagen"],"Activated":["Activado"],"Activate":["Activar"],"Submit":["Enviar"],"Global Settings":["Configuraci\u00f3n global"],"Form Title":["T\u00edtulo del formulario"],"Edit":["Editar"],"Please enter a valid URL.":["Por favor, introduce una URL v\u00e1lida."],"Desktop":["Escritorio"],"Medium":["Medio"],"Mobile":["M\u00f3vil"],"Repeat":["Repetir"],"Scroll":["Desplazar"],"Signature":["Firma"],"Tablet":["Tableta"],"Upload":["Subir"],"Basic":["B\u00e1sico"],"Form Settings":["Configuraci\u00f3n del formulario"],"General":["General"],"Style":["Estilo"],"Advanced":["Avanzado"],"No tags available":["No hay etiquetas disponibles"],"Device":["Dispositivo"],"Select Shortcodes":["Seleccionar c\u00f3digos cortos"],"Page Break Label":["Etiqueta de salto de p\u00e1gina"],"Next":["Siguiente"],"Back":["Atr\u00e1s"],"Reset":["Restablecer"],"Generic tags":["Etiquetas gen\u00e9ricas"],"Pixel":["P\u00edxel"],"Em":["Em"],"Select Units":["Seleccionar unidades"],"%s units":["%s unidades"],"Margin":["Margen"],"None":["Ninguno"],"Custom":["Personalizado"],"Please add a option props to MultiButtonsControl":["Por favor, a\u00f1ade una opci\u00f3n de propiedades a MultiButtonsControl"],"Confirm":["Confirmar"],"Cancel":["Cancelar"],"Processing\u2026":["Procesando\u2026"],"Select Video":["Seleccionar video"],"Change Video":["Cambiar video"],"Select Lottie Animation":["Seleccionar animaci\u00f3n Lottie"],"Change Lottie Animation":["Cambiar animaci\u00f3n Lottie"],"Upload SVG":["Subir SVG"],"Change SVG":["Cambiar SVG"],"Select Image":["Seleccionar imagen"],"Change Image":["Cambiar imagen"],"Upload SVG?":["\u00bfSubir SVG?"],"Upload SVG can be potentially risky. Are you sure?":["Subir SVG puede ser potencialmente arriesgado. \u00bfEst\u00e1s seguro?"],"Upload Anyway":["Subir de todos modos"],"Full Width":["Ancho completo"],"Install":["Instalar"],"Plugin Installation failed, Please try again later.":["La instalaci\u00f3n del complemento fall\u00f3, por favor intente de nuevo m\u00e1s tarde."],"Plugin activation failed, Please try again later.":["La activaci\u00f3n del complemento fall\u00f3, por favor intente de nuevo m\u00e1s tarde."],"Integrations":["Integraciones"],"%s Removed from Quick Action Bar.":["%s eliminado de la barra de acci\u00f3n r\u00e1pida."],"Add to Quick Action Bar":["Agregar a la barra de acci\u00f3n r\u00e1pida"],"%s Added to Quick Action Bar.":["%s a\u00f1adido a la barra de acci\u00f3n r\u00e1pida."],"Already Present in Quick Action Bar":["Ya presente en la barra de acci\u00f3n r\u00e1pida"],"No results found.":["No se encontraron resultados."],"data object is empty":["el objeto de datos est\u00e1 vac\u00edo"],"Add blocks to Quick Action Bar":["Agregar bloques a la barra de acci\u00f3n r\u00e1pida"],"Re-arrange block inside Quick Action Bar":["Reorganizar bloque dentro de la Barra de Acci\u00f3n R\u00e1pida"],"Upgrade":["Actualizar"],"Connecting\u2026":["Conectando\u2026"],"Install & Activate":["Instalar y activar"],"Compliance Settings":["Configuraci\u00f3n de Cumplimiento"],"Enable GDPR Compliance":["Habilitar el cumplimiento del RGPD"],"Never store entry data after form submission":["Nunca almacenes los datos de entrada despu\u00e9s de enviar el formulario"],"When enabled this form will never store Entries.":["Cuando est\u00e9 habilitado, este formulario nunca almacenar\u00e1 entradas."],"Automatically delete entries":["Eliminar autom\u00e1ticamente las entradas"],"When enabled this form will automatically delete entries after a certain period of time.":["Cuando est\u00e9 habilitado, este formulario eliminar\u00e1 autom\u00e1ticamente las entradas despu\u00e9s de un cierto per\u00edodo de tiempo."],"Entries older than the days set will be deleted automatically.":["Las entradas m\u00e1s antiguas que los d\u00edas establecidos se eliminar\u00e1n autom\u00e1ticamente."],"Custom CSS":["CSS personalizado"],"The following CSS styles added below will only apply to this form container.":["Los siguientes estilos CSS a\u00f1adidos a continuaci\u00f3n solo se aplicar\u00e1n a este contenedor de formulario."],"Visual":["Visual"],"HTML":["HTML"],"All Data":["Todos los datos"],"Add Shortcode":["Agregar c\u00f3digo corto"],"Form input tags":["Etiquetas de entrada de formulario"],"Comma separated values are also accepted.":["Tambi\u00e9n se aceptan valores separados por comas."],"Email Notification":["Notificaci\u00f3n de correo electr\u00f3nico"],"Name":["Nombre"],"Send Email To":["Enviar correo electr\u00f3nico a"],"Subject":["Asunto"],"CC":["CC"],"BCC":["BCC"],"Reply To":["Responder a"],"Add Notification":["Agregar notificaci\u00f3n"],"Add Key":["Agregar clave"],"Add Value":["A\u00f1adir valor"],"Add":["A\u00f1adir"],"Confirmation Message":["Mensaje de confirmaci\u00f3n"],"After Form Submission":["Despu\u00e9s del env\u00edo del formulario"],"Hide Form":["Ocultar formulario"],"Reset Form":["Restablecer formulario"],"Custom URL":["URL personalizada"],"Add Query Parameters":["Agregar par\u00e1metros de consulta"],"Select if you want to add key-value pairs for form fields to include in query parameters":["Seleccione si desea agregar pares clave-valor para los campos del formulario que se incluir\u00e1n en los par\u00e1metros de consulta"],"Query Parameters":["Par\u00e1metros de consulta"],"Please select a page.":["Por favor, seleccione una p\u00e1gina."],"Suggestion: URL should use HTTPS":["Sugerencia: la URL deber\u00eda usar HTTPS"],"Success Message":["Mensaje de \u00e9xito"],"Redirect":["Redirigir"],"Redirect to":["Redirigir a"],"Page":["P\u00e1gina"],"Form Confirmation":["Confirmaci\u00f3n de formulario"],"Confirmation Type":["Tipo de confirmaci\u00f3n"],"Use Labels as Placeholders":["Usa etiquetas como marcadores de posici\u00f3n"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["La configuraci\u00f3n anterior colocar\u00e1 las etiquetas dentro de los campos como marcadores de posici\u00f3n (donde sea posible). Esta configuraci\u00f3n solo tiene efecto en la p\u00e1gina en vivo, no en la vista previa del editor."],"Page Break":["Salto de p\u00e1gina"],"Show Labels":["Mostrar etiquetas"],"First Page Label":["Etiqueta de la primera p\u00e1gina"],"Progress Indicator":["Indicador de progreso"],"Progress Bar":["Barra de progreso"],"Connector":["Conector"],"Steps":["Pasos"],"Next Button Text":["Texto del bot\u00f3n Siguiente"],"Back Button Text":["Texto del bot\u00f3n de retroceso"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["\u00bfEst\u00e1 seguro de que desea cerrar? Sus cambios no guardados se perder\u00e1n ya que tiene algunos errores de validaci\u00f3n."],"There are few unsaved changes. Please save your changes to reflect the updates.":["Hay algunos cambios no guardados. Por favor, guarde sus cambios para reflejar las actualizaciones."],"Form Behavior":["Comportamiento del formulario"],"Clear":["Claro"],"Select Color":["Seleccionar color"],"Primary Color":["Color primario"],"Text Color":["Color del texto"],"Text Color on Primary":["Color del texto en primario"],"Field Spacing":["Espaciado de campo"],"Small":["Peque\u00f1o"],"Large":["Grande"],"Left":["Izquierda"],"Center":["Centro"],"Right":["Correcto"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Invisible"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Selector de fecha"],"Time Picker":["Selector de tiempo"],"Hidden":["Oculto"],"Slider":["Deslizador"],"Rating":["Calificaci\u00f3n"],"Upgrade to Unlock These Fields":["Actualiza para desbloquear estos campos"],"Add Block":["Agregar bloque"],"Customize with SureForms":["Personaliza con SureForms"],"Page break":["Salto de p\u00e1gina"],"Previous":["Anterior"],"Thank you":["Gracias"],"Form submitted successfully!":["\u00a1Formulario enviado con \u00e9xito!"],"Instant Form":["Formulario Instant\u00e1neo"],"Enable Instant Form":["Habilitar formulario instant\u00e1neo"],"Enable Preview":["Habilitar vista previa"],"Show Title":["Mostrar t\u00edtulo"],"Site Logo":["Logo del sitio"],"Banner Background":["Fondo del Banner"],"Color":["Color"],"Upload Image":["Subir imagen"],"Background Color":["Color de fondo"],"Use banner as page background":["Usa el banner como fondo de p\u00e1gina"],"Form Width":["Ancho del formulario"],"URL":["URL"],"URL Slug":["Slug de URL"],"The last part of the URL.":["La \u00faltima parte de la URL."],"Learn more.":["Aprende m\u00e1s."],"SureForms Description":["Descripci\u00f3n de SureForms"],"Form Options":["Opciones de formulario"],"Form Shortcode":["C\u00f3digo corto del formulario"],"Paste this shortcode on the page or post to render this form.":["Pega este c\u00f3digo corto en la p\u00e1gina o publicaci\u00f3n para mostrar este formulario."],"Spam Protection":["Protecci\u00f3n contra el spam"],"Auto":["Auto"],"Normal":["Normal"],"%":["%"],"Top":["Superior"],"Bottom":["Fondo"],"Solid":["S\u00f3lido"],"Width":["Ancho"],"Size":["Tama\u00f1o"],"EM":["EM"],"Padding":["Relleno"],"Color 1":["Color 1"],"Color 2":["Color 2"],"Type":["Tipo"],"Linear":["Lineal"],"Radial":["Radial"],"Location 1":["Ubicaci\u00f3n 1"],"Location 2":["Ubicaci\u00f3n 2"],"Angle":["\u00c1ngulo"],"Classic":["Cl\u00e1sico"],"Gradient":["Gradiente"],"Background":["Antecedentes"],"Cover":["Cubrir"],"Contain":["Contener"],"Overlay":["Superposici\u00f3n"],"No Repeat":["No repetir"],"Overlay Opacity":["Opacidad de superposici\u00f3n"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Los nombres de clase deben estar separados por espacios. Cada nombre de clase no debe comenzar con un d\u00edgito, guion o guion bajo. Solo pueden incluir letras (incluidos caracteres Unicode), n\u00fameros, guiones y guiones bajos."],"Conversational Layout":["Dise\u00f1o Conversacional"],"Unlock Conversational Forms":["Desbloquear formularios conversacionales"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Con el Plan SureForms Pro, puedes transformar tus formularios en dise\u00f1os conversacionales atractivos para una experiencia de usuario fluida."],"Premium":["Premium"],"Overlay Type":["Tipo de superposici\u00f3n"],"Image Overlay Color":["Color de superposici\u00f3n de imagen"],"Image Position":["Posici\u00f3n de la imagen"],"Attachment":["Adjunto"],"Fixed":["Fijo"],"Blend Mode":["Modo de fusi\u00f3n"],"Multiply":["Multiplicar"],"Screen":["Pantalla"],"Darken":["Oscurecer"],"Lighten":["Aligerar"],"Color Dodge":["Sobreexponer color"],"Saturation":["Saturaci\u00f3n"],"Repeat-x":["Repetir-x"],"Repeat-y":["Repetir-y"],"PX":["PX"],"Button":["Bot\u00f3n"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Con\u00e9ctate con OttoKit"],"SUREFORMS PREMIUM FIELDS":["CAMPOS PREMIUM DE SUREFORMS"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["La direcci\u00f3n de 'Correo Electr\u00f3nico del Remitente' actual no coincide con el nombre de dominio de su sitio web (%1$s). Esto puede hacer que sus correos electr\u00f3nicos de notificaci\u00f3n sean bloqueados o marcados como spam. Alternativamente, intente usar una direcci\u00f3n de remitente que coincida con el dominio de su sitio web (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["La direcci\u00f3n de 'Correo Electr\u00f3nico del Remitente' actual no coincide con el nombre de dominio de su sitio web (%s). Esto puede hacer que sus correos electr\u00f3nicos de notificaci\u00f3n sean bloqueados o marcados como spam."],"We strongly recommend that you install the free ":["Recomendamos encarecidamente que instale el gratuito"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["\u00a1Complemento! El asistente de configuraci\u00f3n facilita la correcci\u00f3n de tus correos electr\u00f3nicos."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Alternativamente, intenta usar una direcci\u00f3n de remitente que coincida con el dominio de tu sitio web (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Por favor, introduce una direcci\u00f3n de correo electr\u00f3nico v\u00e1lida. Tus notificaciones no se enviar\u00e1n si el campo no est\u00e1 rellenado correctamente."],"From Name":["De Nombre"],"From Email":["De correo electr\u00f3nico"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["La direcci\u00f3n de 'Correo Electr\u00f3nico del Remitente' actual puede no coincidir con el nombre de dominio de su sitio web (%1$s). Esto puede hacer que sus correos electr\u00f3nicos de notificaci\u00f3n sean bloqueados o marcados como spam. Alternativamente, intente usar una Direcci\u00f3n del Remitente que coincida con el dominio de su sitio web (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["La direcci\u00f3n de 'Correo Electr\u00f3nico del Remitente' actual puede no coincidir con el nombre de dominio de su sitio web (%s). Esto puede hacer que sus correos electr\u00f3nicos de notificaci\u00f3n sean bloqueados o marcados como spam."],"Border Radius":["Radio de borde"],"Form Theme":["Tema del formulario"],"Instant Form Padding":["Relleno Instant\u00e1neo de Formularios"],"Instant Form Border Radius":["Radio de Borde de Formulario Instant\u00e1neo"],"Select Gradient":["Seleccionar degradado"],"Upgrade Now":["Actualiza ahora"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["Las entradas anteriores a los d\u00edas seleccionados ser\u00e1n eliminadas."],"Entries Time Period":["Per\u00edodo de tiempo de las entradas"],"Custom CSS Panel":["Panel de CSS personalizado"],"Notifications can use only one From Email so please enter a single address.":["Las notificaciones solo pueden usar un \u00fanico correo electr\u00f3nico de origen, as\u00ed que por favor ingrese una sola direcci\u00f3n."],"Email Notifications":["Notificaciones por correo electr\u00f3nico"],"Actions":["Acciones"],"Duplicate":["Duplicado"],"Delete":["Eliminar"],"Select Page to redirect":["Seleccionar p\u00e1gina para redirigir"],"Search for a page":["Buscar una p\u00e1gina"],"Select a page":["Selecciona una p\u00e1gina"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Guardar"],"Login":["Iniciar sesi\u00f3n"],"Register":["Registrar"],"Date":["Fecha"],"Advanced Settings":["Configuraci\u00f3n avanzada"],"Form Restriction":["Restricci\u00f3n de formulario"],"Maximum Number of Entries":["N\u00famero m\u00e1ximo de entradas"],"Maximum Entries":["Entradas M\u00e1ximas"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["La configuraci\u00f3n del Per\u00edodo de Tiempo funciona seg\u00fan la zona horaria de tu sitio de WordPress. Haz clic aqu\u00ed<\/a> para abrir la Configuraci\u00f3n General de WordPress, donde puedes verificarla y actualizarla."],"Click here":["Haz clic aqu\u00ed"],"Response Description After Maximum Entries":["Descripci\u00f3n de la respuesta despu\u00e9s del m\u00e1ximo de entradas"],"All changes will be saved automatically when you press back.":["Todos los cambios se guardar\u00e1n autom\u00e1ticamente cuando presiones atr\u00e1s."],"Repeater":["Repetidor"],"OttoKit Settings":["Configuraci\u00f3n de OttoKit"],"Get Started":["Comenzar"],"Connect Native Integrations with SureForms":["Conecta integraciones nativas con SureForms"],"Expected format for emails - email@sureforms.com or John Doe ":["Formato esperado para correos electr\u00f3nicos: email@sureforms.com o John Doe "],"delete":["eliminar"],"Please type \"%s\" in the input box":["Por favor, escribe \"%s\" en el cuadro de entrada"],"To confirm, type \"%s\" in the box below:":["Para confirmar, escriba \"%s\" en el cuadro de abajo:"],"Type \"%s\"":["Escribe \"%s\""],"Payment":["Pago"],"%s - Order ID":["%s - ID de pedido"],"%s - Amount":["%s - Cantidad"],"%s - Customer Email":["%s - Correo Electr\u00f3nico del Cliente"],"%s - Customer Name":["%s - Nombre del Cliente"],"%s - Status":["%s - Estado"],"Add custom CSS rules to style this specific form independently of global styles.":["Agrega reglas CSS personalizadas para estilizar este formulario espec\u00edfico independientemente de los estilos globales."],"Spam Protection Type":["Tipo de Protecci\u00f3n contra Spam"],"Select Security Type":["Seleccionar tipo de seguridad"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Nota: Usar diferentes versiones de reCAPTCHA (V2 checkbox y V3) en la misma p\u00e1gina crear\u00e1 conflictos entre las versiones. Por favor, evite usar diferentes versiones en la misma p\u00e1gina."],"Select Version":["Seleccionar versi\u00f3n"],"Please configure the API keys correctly from the settings":["Por favor, configure las claves API correctamente desde la configuraci\u00f3n"],"Control email alerts sent to admins or users after a form submission.":["Controla las alertas de correo electr\u00f3nico enviadas a los administradores o usuarios despu\u00e9s de una presentaci\u00f3n de formulario."],"Customize the confirmation message or redirect the users after submitting the form.":["Personaliza el mensaje de confirmaci\u00f3n o redirige a los usuarios despu\u00e9s de enviar el formulario."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Establezca l\u00edmites en la cantidad de veces que se puede enviar un formulario y gestione las opciones de cumplimiento, incluidas GDPR y la retenci\u00f3n de datos."],"Go to OttoKit Settings":["Ve a la configuraci\u00f3n de OttoKit"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Conecta SureForms con tus aplicaciones favoritas para automatizar tareas y sincronizar datos sin problemas."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Desbloquea potentes integraciones en el plan Premium para automatizar tus flujos de trabajo y conectar SureForms directamente con tus herramientas favoritas."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Env\u00eda las presentaciones de formularios directamente a los CRM, correo electr\u00f3nico y plataformas de marketing."],"Automate repetitive tasks with seamless data syncing.":["Automatiza tareas repetitivas con una sincronizaci\u00f3n de datos sin problemas."],"Access exclusive native integrations for faster workflows.":["Accede a integraciones nativas exclusivas para flujos de trabajo m\u00e1s r\u00e1pidos."],"PDF Generation":["Generaci\u00f3n de PDF"],"Generate and customize PDF copies of form submissions.":["Genera y personaliza copias en PDF de los env\u00edos de formularios."],"Generate Submission PDFs":["Generar PDFs de env\u00edo"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Convierte cada entrada de formulario en un archivo PDF pulido, haci\u00e9ndolo perfecto para informes, registros o compartir."],"Automatically generate PDFs from your form submissions.":["Genera autom\u00e1ticamente PDFs a partir de tus env\u00edos de formularios."],"Customize PDF templates with your branding.":["Personaliza las plantillas de PDF con tu marca."],"Download or email PDFs instantly.":["Descarga o env\u00eda por correo electr\u00f3nico los PDFs al instante."],"User Registration":["Registro de Usuario"],"Onboard new users or update existing accounts through beautiful looking forms.":["Incorpore nuevos usuarios o actualice cuentas existentes a trav\u00e9s de formularios de aspecto atractivo."],"Register Users with SureForms":["Registrar usuarios con SureForms"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Optimiza todo el proceso de incorporaci\u00f3n de usuarios para tus sitios con inicios de sesi\u00f3n y registros fluidos impulsados por formularios."],"Register new users directly via your form submissions.":["Registra nuevos usuarios directamente a trav\u00e9s de tus env\u00edos de formularios."],"Create or update existing accounts by mapping form data to user fields.":["Cree o actualice cuentas existentes asignando datos del formulario a campos de usuario."],"Assign roles and control access automatically.":["Asigna roles y controla el acceso autom\u00e1ticamente."],"Post Feed":["Publicar en el feed"],"Transform your form submission into WordPress posts.":["Transforma tu env\u00edo de formulario en publicaciones de WordPress."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Convierte autom\u00e1ticamente las presentaciones de formularios en publicaciones, p\u00e1ginas o tipos de publicaciones personalizadas de WordPress. Ahorra mucho tiempo y permite que tus formularios publiquen contenido directamente."],"Create posts, pages, or CPTs from your form entries.":["Crea publicaciones, p\u00e1ginas o CPTs a partir de las entradas de tu formulario."],"Map form fields to your post fields easily.":["Mapea los campos del formulario a los campos de tu publicaci\u00f3n f\u00e1cilmente."],"Automate the content publishing flow with few simple steps.":["Automatiza el flujo de publicaci\u00f3n de contenido con unos pocos pasos simples."],"Automations":["Automatizaciones"],"Unlock Advanced Styling":["Desbloquear estilo avanzado"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Obt\u00e9n control total sobre el aspecto de tu formulario con colores, fuentes y dise\u00f1os personalizados."],"Button Alignment":["Alineaci\u00f3n del bot\u00f3n"],"Add Custom CSS Class(es)":["Agregar clase(s) CSS personalizada(s)"],"Set the total number of submissions allowed for this form.":["Establezca el n\u00famero total de env\u00edos permitidos para este formulario."],"Save & Progress":["Guardar y Progresar"],"Allow users to save their progress and continue form completion later.":["Permitir a los usuarios guardar su progreso y continuar completando el formulario m\u00e1s tarde."],"Save & Progress in SureForms":["Guardar y Progresar en SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["Brinda a tus usuarios la flexibilidad de completar formularios a su propio ritmo permiti\u00e9ndoles guardar el progreso y regresar en cualquier momento."],"Let users pause long or multi-step forms and continue later.":["Permitir a los usuarios pausar formularios largos o de varios pasos y continuar m\u00e1s tarde."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Reduzca el abandono de formularios con enlaces de reanudaci\u00f3n convenientes y acceda a su progreso desde cualquier lugar."],"Improve user experience for lengthy, complex, or multi-page forms.":["Mejorar la experiencia del usuario para formularios extensos, complejos o de varias p\u00e1ginas."],"This form is not yet available. Please check back after the scheduled start time.":["Este formulario a\u00fan no est\u00e1 disponible. Por favor, vuelva a consultar despu\u00e9s de la hora de inicio programada."],"This form is no longer accepting submissions. The submission period has ended.":["Este formulario ya no acepta env\u00edos. El per\u00edodo de env\u00edo ha terminado."],"The start date and time must be before the end date and time.":["La fecha y hora de inicio deben ser anteriores a la fecha y hora de finalizaci\u00f3n."],"Form Scheduling":["Programaci\u00f3n de formularios"],"Enable Form Scheduling":["Habilitar la programaci\u00f3n de formularios"],"Set a time period during which this form will be available for submissions.":["Establezca un per\u00edodo de tiempo durante el cual este formulario estar\u00e1 disponible para env\u00edos."],"Start Date & Time":["Fecha y hora de inicio"],"End Date & Time":["Fecha y hora de finalizaci\u00f3n"],"Response Description Before Start Date":["Descripci\u00f3n de la respuesta antes de la fecha de inicio"],"Response Description After End Date":["Descripci\u00f3n de la respuesta despu\u00e9s de la fecha de finalizaci\u00f3n"],"Conditional Confirmations":["Confirmaciones Condicionales"],"Set up the message or redirect users will see after submitting the form.":["Configure el mensaje o la redirecci\u00f3n que los usuarios ver\u00e1n despu\u00e9s de enviar el formulario."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Muestra el mensaje correcto al usuario adecuado seg\u00fan c\u00f3mo respondan. Personaliza las confirmaciones con condiciones inteligentes y gu\u00eda a los usuarios al siguiente mejor paso autom\u00e1ticamente."],"Display different confirmation messages based on form responses.":["Muestra diferentes mensajes de confirmaci\u00f3n seg\u00fan las respuestas del formulario."],"Redirect users to specific pages or URLs conditionally.":["Redirige a los usuarios a p\u00e1ginas o URLs espec\u00edficas de manera condicional."],"Create personalized thank-you messages without extra forms.":["Crea mensajes de agradecimiento personalizados sin formularios adicionales."],"Lost Password":["Contrase\u00f1a perdida"],"Reset Password":["Restablecer contrase\u00f1a"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["Seleccione un servicio de protecci\u00f3n contra spam. Configure las claves API en Configuraci\u00f3n Global antes de habilitarlo."],"Send as Raw HTML":["Enviar como HTML sin procesar"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Cuando est\u00e9 habilitado, el cuerpo del correo electr\u00f3nico en HTML se conservar\u00e1 exactamente como est\u00e1 escrito y se envolver\u00e1 en una plantilla de correo electr\u00f3nico profesional."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["Las etiquetas inteligentes que hacen referencia a campos enviados por el usuario no se escapar\u00e1n en el modo HTML sin procesar. Evita insertar valores de campos no confiables directamente en el cuerpo del correo electr\u00f3nico."],"Please provide a recipient email address and subject line.":["Por favor, proporcione una direcci\u00f3n de correo electr\u00f3nico del destinatario y una l\u00ednea de asunto."],"Email notification duplicated!":["\u00a1Notificaci\u00f3n de correo electr\u00f3nico duplicada!"],"Are you sure you want to delete this email notification?":["\u00bfEst\u00e1s seguro de que deseas eliminar esta notificaci\u00f3n por correo electr\u00f3nico?"],"Email notification deleted!":["\u00a1Notificaci\u00f3n de correo electr\u00f3nico eliminada!"],"URL is missing Top Level Domain (TLD).":["Falta el dominio de nivel superior (TLD) en la URL."],"This form is now closed as the maximum number of entries has been received.":["Este formulario est\u00e1 cerrado ya que se ha recibido el n\u00famero m\u00e1ximo de entradas."],"Publish Your Form":["Publica tu formulario"],"Enable This to Instantly Publish the Form":["Habilitar esto para publicar el formulario instant\u00e1neamente"],"Style Your Instant Form Page Here":["Estiliza tu p\u00e1gina de formulario instant\u00e1neo aqu\u00ed"],"Quizzes":["Cuestionarios"],"%s - Description":["%s - Descripci\u00f3n"],"Send entries to 100+ popular apps.":["Env\u00eda entradas a m\u00e1s de 100 aplicaciones populares."],"Build automated workflows that run instantly.":["Crea flujos de trabajo automatizados que se ejecutan al instante."],"Create custom app integrations using our Custom App feature.":["Crea integraciones de aplicaciones personalizadas utilizando nuestra funci\u00f3n de Aplicaci\u00f3n Personalizada."],"Keep your tools in sync automatically.":["Mant\u00e9n tus herramientas sincronizadas autom\u00e1ticamente."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["Esto instalar\u00e1 y activar\u00e1 OttoKit en su sitio de WordPress para habilitar funciones de automatizaci\u00f3n."],"Automate Your Forms with OttoKit":["Automatiza tus formularios con OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Cada env\u00edo de formulario deber\u00eda activar algo: una alerta de Slack, un cliente potencial en el CRM, un correo electr\u00f3nico de seguimiento o una nueva fila en Google Sheets."],"Create interactive quizzes to engage your audience and gather insights.":["Crea cuestionarios interactivos para involucrar a tu audiencia y recopilar informaci\u00f3n."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Dise\u00f1a cuestionarios atractivos con varios tipos de preguntas, retroalimentaci\u00f3n personalizada y puntuaci\u00f3n automatizada para cautivar a tu audiencia y obtener valiosos conocimientos."],"Create interactive quizzes with multiple question types.":["Crea cuestionarios interactivos con m\u00faltiples tipos de preguntas."],"Provide personalized feedback based on user responses.":["Proporciona comentarios personalizados basados en las respuestas del usuario."],"Automate scoring and lead segmentation for better insights.":["Automatiza la puntuaci\u00f3n y la segmentaci\u00f3n de leads para obtener mejores insights."],"Heading 1":["Encabezado 1"],"Heading 2":["Encabezado 2"],"Heading 3":["Encabezado 3"],"Heading 4":["Encabezado 4"],"Heading 5":["Encabezado 5"],"Heading 6":["Encabezado 6"],"Form settings saved.":["Configuraci\u00f3n del formulario guardada."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["El l\u00edmite de entradas se basa en las entradas almacenadas para contar las presentaciones. Mientras la Configuraci\u00f3n de Cumplimiento tenga habilitada la opci\u00f3n \"Nunca almacenar datos de entrada despu\u00e9s del env\u00edo del formulario\", este l\u00edmite no se aplicar\u00e1. Desactive esa opci\u00f3n o elimine el l\u00edmite de entradas para usar esta funci\u00f3n."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Configuraciones guardadas, pero los atributos de la publicaci\u00f3n (contrase\u00f1a \/ t\u00edtulo \/ contenido) no se actualizaron. Intente de nuevo para persistirlos."],"Failed to save form settings.":["Error al guardar la configuraci\u00f3n del formulario."],"Saving\u2026":["Guardando\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Cuando est\u00e9 habilitado, este formulario no almacenar\u00e1 la IP del usuario, el nombre del navegador ni el nombre del dispositivo en las entradas."],"Failed to save. Please try again.":["Error al guardar. Por favor, int\u00e9ntalo de nuevo."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["Las claves de la API de reCAPTCHA para la versi\u00f3n seleccionada no est\u00e1n configuradas. Establ\u00e9zcalas en Configuraci\u00f3n Global."],"Please select a reCAPTCHA version.":["Por favor, seleccione una versi\u00f3n de reCAPTCHA."],"hCaptcha API keys are not configured. Set them in Global Settings.":["Las claves de API de hCaptcha no est\u00e1n configuradas. Establ\u00e9celas en Configuraci\u00f3n Global."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["Las claves API de Cloudflare Turnstile no est\u00e1n configuradas. Establ\u00e9zcalas en Configuraci\u00f3n Global."],"Form data":["Datos del formulario"],"Some fields need attention":["Algunos campos necesitan atenci\u00f3n"],"Unsaved changes":["Cambios no guardados"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Se requiere una direcci\u00f3n de correo electr\u00f3nico del destinatario y una l\u00ednea de asunto antes de que se pueda guardar esta notificaci\u00f3n. Corrija los campos resaltados o descarte sus cambios para volver atr\u00e1s."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Tienes cambios no guardados para esta notificaci\u00f3n. Desc\u00e1rtalos para volver, o qu\u00e9date para guardarlos."],"Discard & go back":["Descartar y volver"],"Stay & fix":["Qu\u00e9date y arregla"],"Keep editing":["Sigue editando"],"Please provide a recipient email address.":["Por favor, proporcione una direcci\u00f3n de correo electr\u00f3nico del destinatario."],"Please provide a subject line.":["Por favor, proporcione una l\u00ednea de asunto."],"Please provide a custom URL.":["Por favor, proporcione una URL personalizada."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Tienes cambios no guardados. Des\u00e9chalos para continuar, o qu\u00e9date para guardar tus cambios."],"Discard & continue":["Descartar y continuar"],"Show Entries":["Mostrar Entradas"],"Display this form\u2019s entries in a table on the frontend.":["Muestra las entradas de este formulario en una tabla en el frontend."],"Publish this form\u2019s submissions as a sortable, paginated table on any page \u2014 perfect for public directories, listings, and team dashboards, with per-role view access.":["Publica las presentaciones de este formulario como una tabla ordenable y paginada en cualquier p\u00e1gina, perfecta para directorios p\u00fablicos, listados y paneles de equipo, con acceso de vista por rol."],"Embed a sortable, paginated entries table with a simple shortcode.":["Incrusta una tabla de entradas ordenable y paginada con un shortcode simple."],"Choose exactly which fields appear as columns.":["Elige exactamente qu\u00e9 campos aparecen como columnas."],"Restrict who can view entries by user role.":["Restringe qui\u00e9n puede ver las entradas por rol de usuario."],"Quill heading picker: default paragraph style\u0004Normal":["Normal"]}}} \ No newline at end of file diff --git a/languages/sureforms-es_ES.mo b/languages/sureforms-es_ES.mo index 1a155cbbc1f7af0c9c7629a878316fea66d28d22..0bfb47bcf3ae4512b2080739e723dba32748fb53 100644 GIT binary patch delta 72169 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 F{{g3EB%J^N delta 70483 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 diff --git a/languages/sureforms-it_IT.po b/languages/sureforms-it_IT.po index 2d7bdd888..2a4a71589 100644 --- a/languages/sureforms-it_IT.po +++ b/languages/sureforms-it_IT.po @@ -14,9 +14,9 @@ msgstr "" #. Plugin Name of the plugin #. Author of the plugin #: sureforms.php -#: admin/admin.php:391 -#: admin/admin.php:392 -#: admin/admin.php:1950 +#: admin/admin.php:406 +#: admin/admin.php:407 +#: admin/admin.php:1970 #: inc/abilities/abilities-registrar.php:139 #: inc/gutenberg-hooks.php:109 #: inc/page-builders/bricks/elements/form-widget.php:67 @@ -43,8 +43,8 @@ msgstr "Un modo semplice ma potente per creare moduli moderni per il tuo sito we msgid "https://sureforms.com/" msgstr "https://sureforms.com/" -#: admin/admin.php:404 -#: admin/admin.php:405 +#: admin/admin.php:419 +#: admin/admin.php:420 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -59,9 +59,9 @@ msgstr "https://sureforms.com/" msgid "Dashboard" msgstr "Cruscotto" -#: admin/admin.php:422 -#: admin/admin.php:423 -#: admin/admin.php:855 +#: admin/admin.php:437 +#: admin/admin.php:438 +#: admin/admin.php:870 #: inc/compatibility/multilingual/string-collector.php:148 #: inc/compatibility/multilingual/string-collector.php:216 #: assets/build/blocks.js:172 @@ -78,15 +78,15 @@ msgstr "Cruscotto" msgid "Settings" msgstr "Impostazioni" -#: admin/admin.php:691 -#: admin/admin.php:692 +#: admin/admin.php:706 +#: admin/admin.php:707 #: inc/post-types.php:231 msgid "New Form" msgstr "Nuovo modulo" -#: admin/admin.php:700 -#: admin/admin.php:701 -#: admin/admin.php:1987 +#: admin/admin.php:715 +#: admin/admin.php:716 +#: admin/admin.php:2179 #: inc/global-settings/email-summary.php:225 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -102,7 +102,7 @@ msgid "Entries" msgstr "Voci" #. Translators: Post Title. -#: admin/admin.php:942 +#: admin/admin.php:958 #, php-format msgid "Edit %1$s" msgstr "Modifica %1$s" @@ -117,24 +117,24 @@ msgstr "Modifica %1$s" #: inc/export.php:176 #: inc/forms-data.php:88 #: inc/global-settings/global-settings.php:88 -#: inc/global-settings/global-settings.php:630 +#: inc/global-settings/global-settings.php:632 #: inc/rest-api.php:203 msgid "Nonce verification failed." msgstr "Verifica del nonce fallita." #. translators: %1$s: Opening anchor tag with URL, %2$s: Closing anchor tag, %3$s: SureForms Pro Plugin Name. -#: admin/admin.php:1579 +#: admin/admin.php:1596 #, php-format msgid "Please %1$sactivate%2$s your copy of %3$s to get new features, access support, receive update notifications, and more." msgstr "Per favore %1$sattiva%2$s la tua copia di %3$s per ottenere nuove funzionalità, accedere al supporto, ricevere notifiche di aggiornamento e altro ancora." #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version, %4$s: Anchor tag open, %5$s: Closing anchor tag. -#: admin/admin.php:1597 +#: admin/admin.php:1614 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version from %4$shere%5$s." msgstr "SureForms %1$s richiede almeno %2$s %3$s per funzionare correttamente. Si prega di aggiornare all'ultima versione da %4$squi%5$s." -#: admin/admin.php:1986 +#: admin/admin.php:2178 #: inc/global-settings/email-summary.php:224 #: assets/build/entries.js:172 #: assets/build/payments.js:172 @@ -204,7 +204,7 @@ msgstr "Spazzatura" msgid "Published" msgstr "Pubblicato" -#: admin/admin.php:1978 +#: admin/admin.php:2170 msgid "View" msgstr "Visualizza" @@ -296,11 +296,11 @@ msgstr "Nessun plugin specificato" msgid "Plugin Successfully Activated" msgstr "Plugin attivato con successo" -#: admin/admin.php:1014 +#: admin/admin.php:1030 msgid "Activating..." msgstr "Attivazione in corso..." -#: admin/admin.php:1015 +#: admin/admin.php:1031 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/formEditor.js:172 @@ -315,17 +315,17 @@ msgstr "Attivazione in corso..." msgid "Activated" msgstr "Attivato" -#: admin/admin.php:1016 +#: admin/admin.php:1032 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Activate" msgstr "Attiva" -#: admin/admin.php:1017 +#: admin/admin.php:1033 msgid "Installing..." msgstr "Installazione in corso..." -#: admin/admin.php:1018 +#: admin/admin.php:1034 msgid "Installed" msgstr "Installato" @@ -333,10 +333,11 @@ msgstr "Installato" msgid "You do not have permission to access this page." msgstr "Non hai il permesso di accedere a questa pagina." -#: admin/admin.php:1745 -#: admin/admin.php:1844 -#: admin/admin.php:1882 -#: admin/admin.php:1903 +#: admin/admin.php:1762 +#: admin/admin.php:1861 +#: admin/admin.php:1899 +#: admin/admin.php:1920 +#: admin/admin.php:2133 #: inc/admin-ajax.php:162 #: inc/payments/admin/admin-handler.php:638 #: inc/payments/stripe/admin-stripe-handler.php:80 @@ -439,8 +440,8 @@ msgstr "Nessuna email utente trovata nei dati decrittografati." msgid "The message array was not supplied" msgstr "L'array di messaggi non è stato fornito" -#: inc/ai-form-builder/ai-form-builder.php:68 -#: inc/ai-form-builder/ai-form-builder.php:87 +#: inc/ai-form-builder/ai-form-builder.php:80 +#: inc/ai-form-builder/ai-form-builder.php:99 #: inc/ai-form-builder/ai-helper.php:294 msgid "The SureForms AI Middleware encountered an error." msgstr "Il middleware AI di SureForms ha riscontrato un errore." @@ -627,8 +628,8 @@ msgstr "Nessun modulo trovato." #: inc/abilities/settings/get-global-settings.php:192 #: inc/global-settings/email-summary.php:571 -#: inc/global-settings/global-settings.php:262 -#: inc/global-settings/global-settings.php:689 +#: inc/global-settings/global-settings.php:264 +#: inc/global-settings/global-settings.php:691 #: assets/build/settings.js:172 msgid "Monday" msgstr "Lunedì" @@ -662,7 +663,7 @@ msgstr "Il valore deve essere unico." msgid "Sorry, you are not allowed to perform this action." msgstr "Spiacente, non ti è permesso eseguire questa azione." -#: admin/admin.php:1643 +#: admin/admin.php:1660 msgid "Rate SureForms" msgstr "Valuta SureForms" @@ -720,6 +721,7 @@ msgstr "Modifica modulo" msgid "Edit" msgstr "Modifica" +#: admin/admin.php:2021 #: inc/page-builders/elementor/form-widget.php:346 #: assets/build/dashboard.js:2 msgid "Create New Form" @@ -756,8 +758,8 @@ msgstr "Visualizza modulo" msgid "View Forms" msgstr "Visualizza moduli" -#: admin/admin.php:682 -#: admin/admin.php:683 +#: admin/admin.php:697 +#: admin/admin.php:698 #: inc/post-types.php:235 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -802,7 +804,7 @@ msgstr "Email di notifica amministrativa" #. translators: %s: Form title smart tag #. translators: %s: {form_title} smart tag placeholder. #: inc/global-settings/global-settings-defaults.php:173 -#: inc/global-settings/global-settings.php:586 +#: inc/global-settings/global-settings.php:588 #: inc/post-types.php:1005 #: assets/build/settings.js:172 #, php-format,js-format @@ -8880,9 +8882,9 @@ msgstr "Aggiungi blocchi alla barra delle azioni rapide" msgid "Re-arrange block inside Quick Action Bar" msgstr "Riorganizza il blocco all'interno della Barra delle Azioni Rapide" -#: admin/admin.php:475 -#: admin/admin.php:476 -#: admin/admin.php:2211 +#: admin/admin.php:490 +#: admin/admin.php:491 +#: admin/admin.php:2403 #: assets/build/blocks.js:172 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -9323,7 +9325,7 @@ msgstr "Interruzione di pagina" msgid "Previous" msgstr "Precedente" -#: inc/global-settings/global-settings.php:528 +#: inc/global-settings/global-settings.php:530 #: inc/migrator/base-migrator.php:548 #: inc/post-types.php:1081 #: assets/build/formEditor.js:172 @@ -10610,7 +10612,7 @@ msgstr "Sblocca moduli conversazionali" msgid "With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience." msgstr "Con il piano SureForms Pro, puoi trasformare i tuoi moduli in layout conversazionali coinvolgenti per un'esperienza utente senza interruzioni." -#: admin/admin.php:314 +#: admin/admin.php:329 msgid "Get SureForms Pro" msgstr "Ottieni SureForms Pro" @@ -11236,8 +11238,8 @@ msgstr "Descrivi il modulo che desideri creare" msgid "We are building your form…" msgstr "Stiamo creando il tuo modulo…" -#: admin/admin.php:590 -#: admin/admin.php:591 +#: admin/admin.php:605 +#: admin/admin.php:606 msgid "SMTP" msgstr "SMTP" @@ -11369,28 +11371,29 @@ msgstr "Abilita la Notifica Amministratore" msgid "Admin notifications keep you informed about new form entries since your last visit." msgstr "Le notifiche dell'amministratore ti tengono informato sui nuovi invii di moduli dalla tua ultima visita." -#: admin/admin.php:1749 -#: admin/admin.php:1840 -#: admin/admin.php:1878 -#: admin/admin.php:1899 +#: admin/admin.php:1766 +#: admin/admin.php:1857 +#: admin/admin.php:1895 +#: admin/admin.php:1916 +#: admin/admin.php:2137 msgid "Unauthorized user." msgstr "Utente non autorizzato." #. translators: 1: opening span, 2: opening strong (inline), 3: closing strong, 4: closing span, 5: opening strong (block), 6: closing strong -#: admin/admin.php:1849 +#: admin/admin.php:1866 #, php-format msgid "%1$sGet started by %2$sbuilding your first form%3$s.%4$s%5$sExperience the power of our intuitive AI Form Builder%6$s" msgstr "%1$sInizia %2$scostruendo il tuo primo modulo%3$s.%4$s%5$sScopri la potenza del nostro intuitivo Costruttore di Moduli AI%6$s" -#: admin/admin.php:1860 +#: admin/admin.php:1877 msgid "SureForms is waiting for you!" msgstr "SureForms ti sta aspettando!" -#: admin/admin.php:1862 +#: admin/admin.php:1879 msgid "Build My First Form" msgstr "Crea il mio primo modulo" -#: admin/admin.php:1863 +#: admin/admin.php:1880 msgid "Dismiss" msgstr "Chiudi" @@ -11410,51 +11413,51 @@ msgstr "Accesso" msgid "Register" msgstr "Registrati" -#: admin/admin.php:1974 +#: admin/admin.php:2166 msgid "Recent Entries" msgstr "Voci recenti" -#: admin/admin.php:1975 +#: admin/admin.php:2167 msgid "( Last 7 days )" msgstr "( Ultimi 7 giorni )" -#: admin/admin.php:2152 +#: admin/admin.php:2344 msgid "Use Conditional Logic to show only what matters" msgstr "Usa la logica condizionale per mostrare solo ciò che conta" -#: admin/admin.php:2153 +#: admin/admin.php:2345 msgid "Split your form into steps to keep it easy" msgstr "Dividi il tuo modulo in passaggi per mantenerlo semplice" -#: admin/admin.php:2154 +#: admin/admin.php:2346 msgid "Let people upload files directly to your form" msgstr "Consenti alle persone di caricare file direttamente nel tuo modulo" -#: admin/admin.php:2155 +#: admin/admin.php:2347 msgid "Turn responses into downloadable PDFs automatically" msgstr "Trasforma automaticamente le risposte in PDF scaricabili" -#: admin/admin.php:2156 +#: admin/admin.php:2348 msgid "Let users sign with a simple signature field" msgstr "Consenti agli utenti di firmare con un semplice campo firma" -#: admin/admin.php:2157 +#: admin/admin.php:2349 msgid "Connect your form to other tools using webhooks" msgstr "Collega il tuo modulo ad altri strumenti utilizzando i webhook" -#: admin/admin.php:2158 +#: admin/admin.php:2350 msgid "Use Conversational Forms for a chat-like experience" msgstr "Usa moduli conversazionali per un'esperienza simile a una chat" -#: admin/admin.php:2159 +#: admin/admin.php:2351 msgid "Let users register or log in through your form" msgstr "Consenti agli utenti di registrarsi o accedere tramite il tuo modulo" -#: admin/admin.php:2160 +#: admin/admin.php:2352 msgid "Build forms that create WordPress user accounts" msgstr "Crea moduli che creano account utente WordPress" -#: admin/admin.php:2161 +#: admin/admin.php:2353 msgid "Add calculations to auto-total scores or prices" msgstr "Aggiungi calcoli per totalizzare automaticamente i punteggi o i prezzi" @@ -11944,8 +11947,8 @@ msgstr "Miniatura video di SureForms" msgid "Expected format for emails - email@sureforms.com or John Doe " msgstr "Formato previsto per le email - email@sureforms.com o John Doe " -#: admin/admin.php:710 -#: admin/admin.php:711 +#: admin/admin.php:725 +#: admin/admin.php:726 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -12322,8 +12325,8 @@ msgstr "Sconosciuto" #: inc/payments/admin/admin-handler.php:1501 #: inc/payments/admin/admin-handler.php:1505 #: inc/payments/admin/admin-handler.php:1509 -#: inc/payments/front-end.php:865 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:866 +#: inc/payments/front-end.php:1275 msgid "Guest User" msgstr "Utente Ospite" @@ -12336,7 +12339,7 @@ msgid "Valid customer email is required for payments." msgstr "È richiesta un'email cliente valida per i pagamenti." #: inc/payments/front-end.php:115 -#: inc/payments/front-end.php:340 +#: inc/payments/front-end.php:341 #: inc/payments/stripe/admin-stripe-handler.php:861 #: inc/payments/stripe/payments-settings.php:349 #: inc/payments/stripe/payments-settings.php:474 @@ -12347,136 +12350,136 @@ msgid "Stripe is not connected." msgstr "Stripe non è connesso." #: inc/payments/front-end.php:121 -#: inc/payments/front-end.php:346 -#: inc/payments/front-end.php:634 -#: inc/payments/front-end.php:1153 +#: inc/payments/front-end.php:347 +#: inc/payments/front-end.php:635 +#: inc/payments/front-end.php:1154 msgid "Stripe secret key not found." msgstr "Chiave segreta di Stripe non trovata." #. translators: %s: formatted payment amount -#: inc/payments/front-end.php:203 +#: inc/payments/front-end.php:204 #, php-format msgid "The payment amount (%s) is below the minimum allowed. Stripe only processes amounts above 50¢." msgstr "L'importo del pagamento (%s) è inferiore al minimo consentito. Stripe elabora solo importi superiori a 50¢." #. translators: %s: Field name -#: inc/payments/front-end.php:273 +#: inc/payments/front-end.php:274 #, php-format msgid "Missing required field: %s" msgstr "Manca il campo richiesto: %s" -#: inc/payments/front-end.php:292 +#: inc/payments/front-end.php:293 msgid "Valid customer email is required for subscriptions." msgstr "È richiesta un'email valida del cliente per gli abbonamenti." -#: inc/payments/front-end.php:297 +#: inc/payments/front-end.php:298 msgid "Customer name is required for subscriptions." msgstr "Il nome del cliente è richiesto per gli abbonamenti." -#: inc/payments/front-end.php:314 +#: inc/payments/front-end.php:315 msgid "Amount must be greater than 0" msgstr "L'importo deve essere maggiore di 0" -#: inc/payments/front-end.php:322 +#: inc/payments/front-end.php:323 msgid "Invalid billing interval" msgstr "Intervallo di fatturazione non valido" -#: inc/payments/front-end.php:357 +#: inc/payments/front-end.php:358 msgid "Failed to create customer for subscription." msgstr "Impossibile creare il cliente per l'abbonamento." -#: inc/payments/front-end.php:390 -#: inc/payments/front-end.php:406 +#: inc/payments/front-end.php:391 +#: inc/payments/front-end.php:407 msgid "Failed to create subscription through middleware." msgstr "Impossibile creare l'abbonamento tramite middleware." -#: inc/payments/front-end.php:411 +#: inc/payments/front-end.php:412 msgid "Empty response from subscription creation." msgstr "Risposta vuota dalla creazione dell'abbonamento." -#: inc/payments/front-end.php:416 +#: inc/payments/front-end.php:417 msgid "Invalid JSON response from subscription creation." msgstr "Risposta JSON non valida dalla creazione dell'abbonamento." -#: inc/payments/front-end.php:420 -#: inc/payments/front-end.php:424 +#: inc/payments/front-end.php:421 +#: inc/payments/front-end.php:425 msgid "Invalid subscription data." msgstr "Dati di abbonamento non validi." -#: inc/payments/front-end.php:431 +#: inc/payments/front-end.php:432 msgid "Failed to create subscription." msgstr "Creazione dell'abbonamento non riuscita." #. translators: %s: Error message -#: inc/payments/front-end.php:464 +#: inc/payments/front-end.php:465 #, php-format msgid "Unexpected error: %s" msgstr "Errore imprevisto: %s" -#: inc/payments/front-end.php:604 +#: inc/payments/front-end.php:605 #: inc/payments/stripe/admin-stripe-handler.php:103 #: inc/payments/stripe/admin-stripe-handler.php:165 #: inc/payments/stripe/admin-stripe-handler.php:471 msgid "Subscription ID not found." msgstr "ID di abbonamento non trovato." -#: inc/payments/front-end.php:623 +#: inc/payments/front-end.php:624 msgid "Customer ID not found for the payment." msgstr "ID cliente non trovato per il pagamento." -#: inc/payments/front-end.php:651 +#: inc/payments/front-end.php:652 msgid "Failed to retrieve setup intent." msgstr "Impossibile recuperare l'intento di configurazione." -#: inc/payments/front-end.php:699 +#: inc/payments/front-end.php:700 msgid "Failed to update subscription." msgstr "Impossibile aggiornare l'abbonamento." -#: inc/payments/front-end.php:707 +#: inc/payments/front-end.php:708 msgid "Latest invoice not found on subscription." msgstr "Ultima fattura non trovata nell'abbonamento." -#: inc/payments/front-end.php:720 +#: inc/payments/front-end.php:721 msgid "Failed to retrieve invoice." msgstr "Impossibile recuperare la fattura." -#: inc/payments/front-end.php:742 +#: inc/payments/front-end.php:743 msgid "Payment intent not found on invoice." msgstr "Intento di pagamento non trovato sulla fattura." -#: inc/payments/front-end.php:757 +#: inc/payments/front-end.php:758 msgid "Failed to confirm payment." msgstr "Impossibile confermare il pagamento." -#: inc/payments/front-end.php:773 +#: inc/payments/front-end.php:774 msgid "Failed to retrieve subscription." msgstr "Impossibile recuperare l'abbonamento." -#: inc/payments/front-end.php:788 +#: inc/payments/front-end.php:789 msgid "Subscription not found for the payment." msgstr "Abbonamento non trovato per il pagamento." #. translators: %d: User ID -#: inc/payments/front-end.php:863 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:864 +#: inc/payments/front-end.php:1275 #, php-format msgid "User ID: %d" msgstr "ID utente: %d" #. translators: %s: Invoice status -#: inc/payments/front-end.php:871 +#: inc/payments/front-end.php:872 #, php-format msgid "Invoice Status: %s" msgstr "Stato della fattura: %s" #. translators: Title for subscription verification log -#: inc/payments/front-end.php:878 +#: inc/payments/front-end.php:879 msgid "Subscription Verification" msgstr "Verifica dell'abbonamento" #. translators: %s: Subscription ID #. translators: %s: Stripe subscription ID -#: inc/payments/front-end.php:882 +#: inc/payments/front-end.php:883 #: inc/payments/stripe/admin-stripe-handler.php:185 #: inc/payments/stripe/admin-stripe-handler.php:487 #: inc/payments/stripe/stripe-webhook.php:572 @@ -12491,8 +12494,8 @@ msgstr "ID di abbonamento: %s" #. translators: %s: payment gateway name (e.g., Stripe) #. translators: %s: payment gateway #. translators: %s: Payment gateway name (e.g., Stripe). -#: inc/payments/front-end.php:884 -#: inc/payments/front-end.php:1285 +#: inc/payments/front-end.php:885 +#: inc/payments/front-end.php:1286 #: inc/payments/stripe/admin-stripe-handler.php:190 #: inc/payments/stripe/admin-stripe-handler.php:492 #: inc/payments/stripe/admin-stripe-handler.php:667 @@ -12506,13 +12509,13 @@ msgid "Payment Gateway: %s" msgstr "Gateway di Pagamento: %s" #. translators: %s: Payment Intent ID -#: inc/payments/front-end.php:886 +#: inc/payments/front-end.php:887 #, php-format msgid "Payment Intent ID: %s" msgstr "ID dell'intento di pagamento: %s" #. translators: %s: Charge ID -#: inc/payments/front-end.php:888 +#: inc/payments/front-end.php:889 #: inc/payments/stripe/stripe-webhook.php:1084 #, php-format msgid "Charge ID: %s" @@ -12520,7 +12523,7 @@ msgstr "ID di addebito: %s" #. translators: %s: Subscription Status #. translators: %s: subscription status -#: inc/payments/front-end.php:890 +#: inc/payments/front-end.php:891 #: inc/payments/stripe/admin-stripe-handler.php:195 #: inc/payments/stripe/admin-stripe-handler.php:497 #, php-format @@ -12528,7 +12531,7 @@ msgid "Subscription Status: %s" msgstr "Stato dell'abbonamento: %s" #. translators: %s: Customer ID -#: inc/payments/front-end.php:892 +#: inc/payments/front-end.php:893 #: inc/payments/stripe/stripe-webhook.php:1174 #, php-format msgid "Customer ID: %s" @@ -12536,8 +12539,8 @@ msgstr "ID cliente: %s" #. translators: 1: Amount, 2: Currency #. translators: %1$s: amount, %2$s: currency. -#: inc/payments/front-end.php:894 -#: inc/payments/front-end.php:1287 +#: inc/payments/front-end.php:895 +#: inc/payments/front-end.php:1288 #: inc/payments/stripe/stripe-webhook.php:1089 #: inc/payments/stripe/stripe-webhook.php:1166 #, php-format @@ -12546,34 +12549,34 @@ msgstr "Importo: %1$s %2$s" #. translators: %s: Payment mode (e.g. Live or Test) #. translators: %s: payment mode -#: inc/payments/front-end.php:897 -#: inc/payments/front-end.php:1292 +#: inc/payments/front-end.php:898 +#: inc/payments/front-end.php:1293 #, php-format msgid "Mode: %s" msgstr "Modalità: %s" -#: inc/payments/front-end.php:931 +#: inc/payments/front-end.php:932 msgid "Failed to verify subscription." msgstr "Verifica dell'abbonamento non riuscita." -#: inc/payments/front-end.php:1186 -#: inc/payments/front-end.php:1204 -#: inc/payments/front-end.php:1212 -#: inc/payments/front-end.php:1219 +#: inc/payments/front-end.php:1187 +#: inc/payments/front-end.php:1205 +#: inc/payments/front-end.php:1213 +#: inc/payments/front-end.php:1220 msgid "Failed to retrieve payment intent." msgstr "Impossibile recuperare l'intento di pagamento." -#: inc/payments/front-end.php:1227 +#: inc/payments/front-end.php:1228 msgid "Payment was not confirmed successfully." msgstr "Il pagamento non è stato confermato con successo." -#: inc/payments/front-end.php:1279 +#: inc/payments/front-end.php:1280 msgid "Payment Verification" msgstr "Verifica del pagamento" #. translators: %s: Stripe transaction ID #. translators: %s: Charge ID -#: inc/payments/front-end.php:1283 +#: inc/payments/front-end.php:1284 #: inc/payments/stripe/stripe-webhook.php:1162 #, php-format msgid "Transaction ID: %s" @@ -12581,18 +12584,18 @@ msgstr "ID transazione: %s" #. translators: %s: payment status #. translators: %s: Status -#: inc/payments/front-end.php:1289 +#: inc/payments/front-end.php:1290 #: inc/payments/stripe/stripe-webhook.php:576 #: inc/payments/stripe/stripe-webhook.php:1168 #, php-format msgid "Status: %s" msgstr "Stato: %s" -#: inc/payments/front-end.php:1395 +#: inc/payments/front-end.php:1396 msgid "Failed to create Stripe customer." msgstr "Creazione del cliente Stripe non riuscita." -#: inc/payments/front-end.php:1459 +#: inc/payments/front-end.php:1460 msgid "Failed to create Stripe guest customer." msgstr "Impossibile creare un cliente ospite di Stripe." @@ -13457,7 +13460,7 @@ msgstr "Creato tramite ciclo di fatturazione in abbonamento" msgid "Edit this form" msgstr "Modifica questo modulo" -#: inc/global-settings/global-settings.php:529 +#: inc/global-settings/global-settings.php:531 #: inc/migrator/base-migrator.php:549 #: inc/post-types.php:1081 msgid "Your form has been submitted successfully. We'll review your details and get back to you soon." @@ -15080,7 +15083,7 @@ msgid "Failed to create duplicate form." msgstr "Impossibile creare un modulo duplicato." #: inc/payments/front-end.php:98 -#: inc/payments/front-end.php:303 +#: inc/payments/front-end.php:304 #: inc/payments/payment-helper.php:596 msgid "Invalid form configuration." msgstr "Configurazione del modulo non valida." @@ -15156,7 +15159,7 @@ msgstr "È richiesto il valore del campo importo variabile." msgid "Payment amount below minimum. Minimum: %1$s, received %2$s." msgstr "Importo del pagamento inferiore al minimo. Minimo: %1$s, ricevuto %2$s." -#: inc/rest-api.php:1839 +#: inc/rest-api.php:1851 msgid " (Copy)" msgstr "(Copia)" @@ -15355,12 +15358,12 @@ msgid "Stripe Settings" msgstr "Impostazioni di Stripe" #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version. -#: admin/admin.php:1514 +#: admin/admin.php:1531 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version." msgstr "SureForms %1$s richiede almeno %2$s %3$s per funzionare correttamente. Si prega di aggiornare all'ultima versione." -#: admin/admin.php:1527 +#: admin/admin.php:1544 msgid "Update Now" msgstr "Aggiorna ora" @@ -15408,8 +15411,8 @@ msgstr "Posizione del simbolo di valuta" msgid "Select the position of the currency symbol relative to the amount." msgstr "Seleziona la posizione del simbolo della valuta rispetto all'importo." -#: admin/admin.php:653 -#: admin/admin.php:654 +#: admin/admin.php:668 +#: admin/admin.php:669 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -15423,40 +15426,40 @@ msgstr "Seleziona la posizione del simbolo della valuta rispetto all'importo." msgid "Learn" msgstr "Impara" -#: admin/admin.php:1640 +#: admin/admin.php:1657 msgid "Amazing! SureForms is powering your forms and submissions - let's keep growing together!" msgstr "Incredibile! SureForms sta alimentando i tuoi moduli e le tue sottomissioni - continuiamo a crescere insieme!" -#: admin/admin.php:1641 +#: admin/admin.php:1658 msgid "If SureForms has been helpful, would you mind taking a moment to leave a 5-star review on WordPress.org?" msgstr "Se SureForms ti è stato utile, ti dispiacerebbe prendere un momento per lasciare una recensione a 5 stelle su WordPress.org?" -#: admin/admin.php:1644 -#: admin/admin.php:1688 +#: admin/admin.php:1661 +#: admin/admin.php:1705 msgid "Maybe later" msgstr "Forse più tardi" -#: admin/admin.php:1645 +#: admin/admin.php:1662 msgid "I already did" msgstr "L'ho già fatto" -#: admin/admin.php:1684 +#: admin/admin.php:1701 msgid "SureForms is ready to power your forms — explore what's possible!" msgstr "SureForms è pronto a potenziare i tuoi moduli — scopri cosa è possibile!" -#: admin/admin.php:1685 +#: admin/admin.php:1702 msgid "Manage your forms, track submissions, and discover features like AI Form Builder, payment integrations, and more from the SureForms dashboard." msgstr "Gestisci i tuoi moduli, traccia le sottomissioni e scopri funzionalità come AI Form Builder, integrazioni di pagamento e altro ancora dalla dashboard di SureForms." -#: admin/admin.php:1687 +#: admin/admin.php:1704 msgid "Go to Dashboard" msgstr "Vai al cruscotto" -#: admin/admin.php:1689 +#: admin/admin.php:1706 msgid "I already know" msgstr "Lo so già" -#: admin/admin.php:1769 +#: admin/admin.php:1786 msgid "Invalid parameters." msgstr "Parametri non validi." @@ -15850,7 +15853,7 @@ msgstr "Questo modulo non è ancora disponibile. Ricontrolla dopo l'orario di in #: inc/form-submit.php:1015 #: inc/migrator/bootstrap.php:357 #: inc/payments/front-end.php:78 -#: inc/payments/front-end.php:263 +#: inc/payments/front-end.php:264 #: inc/rest-api.php:124 #: inc/rest-api.php:183 #: inc/rest-api.php:349 @@ -15863,8 +15866,8 @@ msgstr "Questo modulo non è ancora disponibile. Ricontrolla dopo l'orario di in #: inc/rest-api.php:742 #: inc/rest-api.php:775 #: inc/rest-api.php:914 -#: inc/rest-api.php:987 -#: inc/rest-api.php:1047 +#: inc/rest-api.php:999 +#: inc/rest-api.php:1059 #: inc/single-form-settings/form-settings-api.php:89 msgid "Security verification failed. Please refresh the page and try again." msgstr "Verifica di sicurezza fallita. Si prega di aggiornare la pagina e riprovare." @@ -16163,28 +16166,28 @@ msgstr "Azione non valida. Usa \"read\" o \"unread\"." msgid "Invalid action. Use \"trash\" or \"restore\"." msgstr "Azione non valida. Usa \"trash\" o \"restore\"." -#: inc/rest-api.php:1061 +#: inc/rest-api.php:1073 msgid "Select at least one form and specify an action." msgstr "Seleziona almeno un modulo e specifica un'azione." -#: inc/rest-api.php:1076 +#: inc/rest-api.php:1088 msgid "Form not found or is not a valid form type." msgstr "Modulo non trovato o non è un tipo di modulo valido." -#: inc/rest-api.php:1088 +#: inc/rest-api.php:1100 msgid "This form is already in the trash." msgstr "Questo modulo è già nel cestino." -#: inc/rest-api.php:1099 +#: inc/rest-api.php:1111 msgid "This form is not in the trash." msgstr "Questo modulo non è nel cestino." -#: inc/rest-api.php:1135 +#: inc/rest-api.php:1147 msgid "Invalid action." msgstr "Azione non valida." #. translators: %s: action name -#: inc/rest-api.php:1150 +#: inc/rest-api.php:1162 #, php-format msgid "Failed to %s this form. Please try again." msgstr "Impossibile %s questo modulo. Per favore riprova." @@ -16202,32 +16205,32 @@ msgid "Enter a valid email address." msgstr "Inserisci un indirizzo email valido." #. translators: %s represents the minimum acceptable value -#: inc/translatable.php:97 +#: inc/translatable.php:103 #, php-format msgid "Minimum value is %s." msgstr "Il valore minimo è %s." #. translators: %s represents the maximum acceptable value -#: inc/translatable.php:100 +#: inc/translatable.php:106 #, php-format msgid "Maximum value is %s." msgstr "Il valore massimo è %s." #. translators: %s represents the minimum number of options to select -#: inc/translatable.php:103 #: inc/translatable.php:109 +#: inc/translatable.php:115 #, php-format msgid "Select at least %s options." msgstr "Seleziona almeno %s opzioni." #. translators: %s represents the maximum number of options that can be selected -#: inc/translatable.php:106 #: inc/translatable.php:112 +#: inc/translatable.php:118 #, php-format msgid "You can select up to %s options." msgstr "Puoi selezionare fino a %s opzioni." -#: inc/translatable.php:151 +#: inc/translatable.php:159 msgid "This form is now closed as we have reached the maximum number of entries." msgstr "Questo modulo è ora chiuso poiché abbiamo raggiunto il numero massimo di iscrizioni." @@ -16423,7 +16426,7 @@ msgid "Duplicate form?" msgstr "Duplicare il modulo?" #: assets/build/formSubmit.js:2 -#: assets/js/unminified/form-submit.js:487 +#: assets/js/unminified/form-submit.js:495 msgid "An error occurred while submitting your form. Please try again." msgstr "Si è verificato un errore durante l'invio del modulo. Si prega di riprovare." @@ -16750,35 +16753,35 @@ msgstr "Recupera le impostazioni globali di SureForms. Facoltativamente, filtra msgid "Setting categories to retrieve. Omit for all categories." msgstr "Impostazione delle categorie da recuperare. Omesso per tutte le categorie." -#: inc/abilities/settings/update-global-settings.php:106 +#: inc/abilities/settings/update-global-settings.php:108 msgid "Update Global Settings" msgstr "Aggiorna impostazioni globali" -#: inc/abilities/settings/update-global-settings.php:107 +#: inc/abilities/settings/update-global-settings.php:109 msgid "Update SureForms global settings for a specific category: general, validation-messages, email-summary, or security." msgstr "Aggiorna le impostazioni globali di SureForms per una categoria specifica: generale, messaggi di validazione, riepilogo email o sicurezza." -#: inc/abilities/settings/update-global-settings.php:140 +#: inc/abilities/settings/update-global-settings.php:142 msgid "The settings category to update." msgstr "La categoria di impostazioni da aggiornare." -#: inc/abilities/settings/update-global-settings.php:145 +#: inc/abilities/settings/update-global-settings.php:147 msgid "Key-value pairs of settings to update." msgstr "Coppie chiave-valore delle impostazioni da aggiornare." -#: inc/abilities/settings/update-global-settings.php:181 +#: inc/abilities/settings/update-global-settings.php:183 msgid "Settings category is required." msgstr "La categoria delle impostazioni è obbligatoria." -#: inc/abilities/settings/update-global-settings.php:189 +#: inc/abilities/settings/update-global-settings.php:191 msgid "Settings data is required." msgstr "È necessario fornire i dati delle impostazioni." -#: inc/abilities/settings/update-global-settings.php:200 +#: inc/abilities/settings/update-global-settings.php:202 msgid "No valid settings keys provided for this category." msgstr "Nessuna chiave di impostazione valida fornita per questa categoria." -#: inc/abilities/settings/update-global-settings.php:226 +#: inc/abilities/settings/update-global-settings.php:228 msgid "Invalid settings category." msgstr "Categoria di impostazioni non valida." @@ -16794,19 +16797,19 @@ msgstr "Segreto del webhook non configurato." msgid "Invalid webhook signature." msgstr "Firma del webhook non valida." -#: admin/admin.php:492 -#: admin/admin.php:1085 +#: admin/admin.php:507 +#: admin/admin.php:1101 #: assets/build/formEditor.js:172 msgid "Quizzes" msgstr "Quiz" -#: admin/admin.php:491 +#: admin/admin.php:506 msgid "Quiz Entries" msgstr "Voci del quiz" -#: admin/admin.php:494 -#: admin/admin.php:527 -#: admin/admin.php:560 +#: admin/admin.php:509 +#: admin/admin.php:542 +#: admin/admin.php:575 #: assets/build/settings.js:172 msgid "New" msgstr "Nuovo" @@ -17324,43 +17327,43 @@ msgstr "Perfetto per l'istruzione, la formazione e i quiz divertenti" msgid "Select this to create a quiz with scored questions and graded results." msgstr "Seleziona questo per creare un quiz con domande a punteggio e risultati valutati." -#: admin/admin.php:524 -#: admin/admin.php:525 -#: admin/admin.php:1090 +#: admin/admin.php:539 +#: admin/admin.php:540 +#: admin/admin.php:1106 msgid "Survey Reports" msgstr "Rapporti di indagine" -#: inc/frontend-assets.php:301 +#: inc/frontend-assets.php:304 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 1" msgstr "Intestazione 1" -#: inc/frontend-assets.php:302 +#: inc/frontend-assets.php:305 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 2" msgstr "Intestazione 2" -#: inc/frontend-assets.php:303 +#: inc/frontend-assets.php:306 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 3" msgstr "Intestazione 3" -#: inc/frontend-assets.php:304 +#: inc/frontend-assets.php:307 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 4" msgstr "Intestazione 4" -#: inc/frontend-assets.php:305 +#: inc/frontend-assets.php:308 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 5" msgstr "Intestazione 5" -#: inc/frontend-assets.php:306 +#: inc/frontend-assets.php:309 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 6" @@ -17694,15 +17697,15 @@ msgstr "Post non valido." msgid "You cannot edit this post." msgstr "Non puoi modificare questo post." -#: inc/ai-form-builder/ai-form-builder.php:96 +#: inc/ai-form-builder/ai-form-builder.php:108 msgid "The AI did not return a form. Please refine your prompt and try again." msgstr "L'IA non ha restituito un modulo. Per favore, perfeziona il tuo prompt e riprova." -#: inc/ai-form-builder/ai-form-builder.php:104 +#: inc/ai-form-builder/ai-form-builder.php:116 msgid "The AI response is missing a form title. Please try again." msgstr "La risposta dell'IA manca di un titolo del modulo. Per favore riprova." -#: inc/ai-form-builder/ai-form-builder.php:115 +#: inc/ai-form-builder/ai-form-builder.php:127 #: inc/ai-form-builder/field-mapping.php:69 msgid "The AI was unable to generate form fields. Please try again." msgstr "L'IA non è riuscita a generare i campi del modulo. Per favore, riprova." @@ -17743,7 +17746,7 @@ msgstr "Pagamento una tantum" msgid "Choose payment type" msgstr "Scegli il tipo di pagamento" -#: inc/payments/front-end.php:333 +#: inc/payments/front-end.php:334 msgid "Billing interval does not match the form configuration." msgstr "L'intervallo di fatturazione non corrisponde alla configurazione del modulo." @@ -17887,15 +17890,15 @@ msgstr "La risposta dell'IA era vuota. Per favore, perfeziona il tuo prompt e ri msgid "Unable to build form fields from the AI response." msgstr "Impossibile creare i campi del modulo dalla risposta dell'AI." -#: admin/admin.php:557 -#: admin/admin.php:558 -#: admin/admin.php:1095 +#: admin/admin.php:572 +#: admin/admin.php:573 +#: admin/admin.php:1111 msgid "Partial Entries" msgstr "Voci parziali" #: inc/global-settings/global-settings-defaults.php:260 -#: inc/global-settings/global-settings.php:425 -#: inc/global-settings/global-settings.php:613 +#: inc/global-settings/global-settings.php:427 +#: inc/global-settings/global-settings.php:615 #: assets/build/settings.js:172 msgid "This form is now closed as we've received all the entries." msgstr "Questo modulo è ora chiuso poiché abbiamo ricevuto tutte le iscrizioni." @@ -17904,16 +17907,16 @@ msgstr "Questo modulo è ora chiuso poiché abbiamo ricevuto tutte le iscrizioni msgid "Invalid settings tab." msgstr "Scheda delle impostazioni non valida." -#: inc/global-settings/global-settings.php:481 +#: inc/global-settings/global-settings.php:483 #: assets/build/settings.js:172 msgid "Thank you for contacting us! We will be in touch with you shortly." msgstr "Grazie per averci contattato! Saremo in contatto con te a breve." -#: inc/rest-api.php:1115 +#: inc/rest-api.php:1127 msgid "Use the restore action to recover a trashed form before switching it to draft." msgstr "Utilizza l'azione di ripristino per recuperare un modulo cestinato prima di passarlo a bozza." -#: inc/rest-api.php:1120 +#: inc/rest-api.php:1132 msgid "This form is already a draft." msgstr "Questo modulo è già una bozza." @@ -18760,6 +18763,72 @@ msgstr "L'importo del pagamento non può essere verificato per questo modulo. Mo msgid "Cancellation is not supported for this payment gateway." msgstr "La cancellazione non è supportata per questo gateway di pagamento." +#: admin/admin.php:1988 +msgid "SureForms AI Quick Draft" +msgstr "SureForms AI Bozza Rapida" + +#: admin/admin.php:2007 +msgid "Describe the form and let SureForms AI generate it for you." +msgstr "Descrivi il modulo e lascia che SureForms AI lo generi per te." + +#: admin/admin.php:2010 +msgid "Describe your form" +msgstr "Descrivi il tuo modulo" + +#: admin/admin.php:2017 +msgid "Example: Create a contact form with name, email, phone, and message fields." +msgstr "Esempio: Crea un modulo di contatto con campi per nome, email, telefono e messaggio." + +#: admin/admin.php:2058 +msgid "Redirecting..." +msgstr "Reindirizzamento in corso..." + +#: inc/ai-form-builder/ai-form-builder.php:57 +msgid "No prompt was supplied." +msgstr "Nessun prompt fornito." + +#: inc/ai-form-builder/ai-form-builder.php:64 +msgid "The prompt is too long. Please shorten it and try again." +msgstr "Il prompt è troppo lungo. Per favore accorcialo e riprova." + +#. translators: %s: maximum characters allowed before the @ symbol. +#: inc/field-validation.php:316 +#: inc/translatable.php:97 +#, php-format +msgid "The part before @ may not exceed %s characters." +msgstr "La parte prima di @ non può superare %s caratteri." + +#. translators: %s: maximum characters allowed after the @ symbol. +#: inc/field-validation.php:322 +#: inc/translatable.php:100 +#, php-format +msgid "The part after @ may not exceed %s characters." +msgstr "La parte dopo @ non può superare %s caratteri." + +#: assets/build/formEditor.js:172 +msgid "Show Entries" +msgstr "Mostra Voci" + +#: assets/build/formEditor.js:172 +msgid "Display this form’s entries in a table on the frontend." +msgstr "Visualizza le voci di questo modulo in una tabella nel frontend." + +#: assets/build/formEditor.js:172 +msgid "Publish this form’s submissions as a sortable, paginated table on any page — perfect for public directories, listings, and team dashboards, with per-role view access." +msgstr "Pubblica le invii di questo modulo come una tabella ordinabile e paginata su qualsiasi pagina — perfetto per directory pubbliche, elenchi e dashboard di team, con accesso alla visualizzazione per ruolo." + +#: assets/build/formEditor.js:172 +msgid "Embed a sortable, paginated entries table with a simple shortcode." +msgstr "Incorpora una tabella di voci ordinabile e paginata con un semplice shortcode." + +#: assets/build/formEditor.js:172 +msgid "Choose exactly which fields appear as columns." +msgstr "Scegli esattamente quali campi appaiono come colonne." + +#: assets/build/formEditor.js:172 +msgid "Restrict who can view entries by user role." +msgstr "Limita chi può visualizzare le voci in base al ruolo utente." + #: inc/post-types.php:226 msgctxt "post type general name" msgid "Forms" @@ -19187,34 +19256,34 @@ msgctxt "block keyword" msgid "color" msgstr "colore" -#: inc/frontend-assets.php:300 +#: inc/frontend-assets.php:303 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgctxt "Quill heading picker: default paragraph style" msgid "Normal" msgstr "Normale" -#: inc/frontend-assets.php:307 +#: inc/frontend-assets.php:310 msgctxt "Quill link tooltip label" msgid "Visit URL:" msgstr "Visita l'URL:" -#: inc/frontend-assets.php:308 +#: inc/frontend-assets.php:311 msgctxt "Quill link tooltip label" msgid "Enter link:" msgstr "Inserisci il link:" -#: inc/frontend-assets.php:309 +#: inc/frontend-assets.php:312 msgctxt "Quill link tooltip action" msgid "Edit" msgstr "Modifica" -#: inc/frontend-assets.php:310 +#: inc/frontend-assets.php:313 msgctxt "Quill link tooltip action" msgid "Save" msgstr "Salva" -#: inc/frontend-assets.php:311 +#: inc/frontend-assets.php:314 msgctxt "Quill link tooltip action" msgid "Remove" msgstr "Rimuovi" diff --git a/languages/sureforms-nl_NL-1cb9ecd067cd971ff5d9db0b4dae2891.json b/languages/sureforms-nl_NL-1cb9ecd067cd971ff5d9db0b4dae2891.json index 9a4a7cbcd..ed89c3ea3 100644 --- a/languages/sureforms-nl_NL-1cb9ecd067cd971ff5d9db0b4dae2891.json +++ b/languages/sureforms-nl_NL-1cb9ecd067cd971ff5d9db0b4dae2891.json @@ -1 +1 @@ -{"translation-revision-date":"2024-12-13T12:33:48+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Status"],"Form":["Formulier"],"Fields":["Velden"],"Image":["Afbeelding"],"Activated":["Geactiveerd"],"Activate":["Activeren"],"Submit":["Indienen"],"Global Settings":["Globale instellingen"],"Form Title":["Formuliertitel"],"Edit":["Bewerken"],"Please enter a valid URL.":["Voer een geldige URL in."],"Desktop":["Bureaublad"],"Medium":["Middelgroot"],"Mobile":["Mobiel"],"Repeat":["Herhaal"],"Scroll":["Scrollen"],"Signature":["Handtekening"],"Tablet":["Tablet"],"Upload":["Uploaden"],"Basic":["Basis"],"Form Settings":["Formulierinstellingen"],"General":["Algemeen"],"Style":["Stijl"],"Advanced":["Geavanceerd"],"No tags available":["Geen tags beschikbaar"],"Device":["Apparaat"],"Select Shortcodes":["Selecteer shortcodes"],"Page Break Label":["Pagina-einde label"],"Next":["Volgende"],"Back":["Terug"],"Reset":["Resetten"],"Generic tags":["Algemene tags"],"Pixel":["Pixel"],"Em":["Em"],"Select Units":["Selecteer eenheden"],"%s units":["%s eenheden"],"Margin":["Marge"],"None":["Geen"],"Custom":["Aangepast"],"Please add a option props to MultiButtonsControl":["Voeg alstublieft een optie-eigenschap toe aan MultiButtonsControl"],"Confirm":["Bevestigen"],"Cancel":["Annuleren"],"Processing\u2026":["Bezig met verwerken\u2026"],"Select Video":["Selecteer video"],"Change Video":["Video wijzigen"],"Select Lottie Animation":["Selecteer Lottie-animatie"],"Change Lottie Animation":["Wijzig Lottie-animatie"],"Upload SVG":["SVG uploaden"],"Change SVG":["SVG wijzigen"],"Select Image":["Selecteer afbeelding"],"Change Image":["Afbeelding wijzigen"],"Upload SVG?":["SVG uploaden?"],"Upload SVG can be potentially risky. Are you sure?":["Het uploaden van SVG kan potentieel riskant zijn. Weet je het zeker?"],"Upload Anyway":["Toch uploaden"],"Full Width":["Volledige breedte"],"Install":["Installeren"],"Plugin Installation failed, Please try again later.":["Installatie van de plugin mislukt, probeer het later opnieuw."],"Plugin activation failed, Please try again later.":["Plug-in activering mislukt, probeer het later opnieuw."],"Integrations":["Integraties"],"%s Removed from Quick Action Bar.":["%s verwijderd van de snelle actiebalk."],"Add to Quick Action Bar":["Toevoegen aan de Snelle Actiebalk"],"%s Added to Quick Action Bar.":["%s toegevoegd aan de Snelle Actiebalk."],"Already Present in Quick Action Bar":["Al aanwezig in de snelle actiebalk"],"No results found.":["Geen resultaten gevonden."],"data object is empty":["gegevensobject is leeg"],"Add blocks to Quick Action Bar":["Blokken toevoegen aan de Snelle Actiebalk"],"Re-arrange block inside Quick Action Bar":["Blok herschikken in de Snelle Actiebalk"],"Upgrade":["Upgrade"],"Connecting\u2026":["Verbinden\u2026"],"Install & Activate":["Installeren & Activeren"],"Compliance Settings":["Instellingen voor naleving"],"Enable GDPR Compliance":["Schakel GDPR-naleving in"],"Never store entry data after form submission":["Sla nooit invoergegevens op na het indienen van het formulier"],"When enabled this form will never store Entries.":["Wanneer ingeschakeld, zal dit formulier nooit inzendingen opslaan."],"Automatically delete entries":["Automatisch items verwijderen"],"When enabled this form will automatically delete entries after a certain period of time.":["Wanneer ingeschakeld, zal dit formulier automatisch inzendingen na een bepaalde periode verwijderen."],"Entries older than the days set will be deleted automatically.":["Inzendingen ouder dan de ingestelde dagen worden automatisch verwijderd."],"Custom CSS":["Aangepaste CSS"],"The following CSS styles added below will only apply to this form container.":["De volgende CSS-stijlen die hieronder worden toegevoegd, zijn alleen van toepassing op deze formuliercontainer."],"Visual":["Visueel"],"HTML":["HTML"],"All Data":["Alle gegevens"],"Add Shortcode":["Voeg shortcode toe"],"Form input tags":["Formulierveldlabels"],"Comma separated values are also accepted.":["Door komma's gescheiden waarden worden ook geaccepteerd."],"Email Notification":["E-mailmelding"],"Name":["Naam"],"Send Email To":["E-mail verzenden naar"],"Subject":["Onderwerp"],"CC":["CC"],"BCC":["BCC"],"Reply To":["Antwoord aan"],"Add Notification":["Melding toevoegen"],"Add Key":["Sleutel toevoegen"],"Add Value":["Waarde toevoegen"],"Add":["Toevoegen"],"Confirmation Message":["Bevestigingsbericht"],"After Form Submission":["Na het indienen van het formulier"],"Hide Form":["Formulier verbergen"],"Reset Form":["Formulier opnieuw instellen"],"Custom URL":["Aangepaste URL"],"Add Query Parameters":["Queryparameters toevoegen"],"Select if you want to add key-value pairs for form fields to include in query parameters":["Selecteer of u sleutel-waardeparen wilt toevoegen voor formuliervelden om op te nemen in queryparameters"],"Query Parameters":["Queryparameters"],"Please select a page.":["Selecteer een pagina."],"Suggestion: URL should use HTTPS":["Suggestie: URL moet HTTPS gebruiken"],"Success Message":["Succesbericht"],"Redirect":["Doorsturen"],"Redirect to":["Doorsturen naar"],"Page":["Pagina"],"Form Confirmation":["Formulierbevestiging"],"Confirmation Type":["Bevestigingstype"],"Use Labels as Placeholders":["Gebruik labels als placeholders"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["Bovenstaande instelling plaatst de labels binnen de velden als placeholders (waar mogelijk). Deze instelling is alleen van toepassing op de live pagina, niet in de voorbeeldweergave van de editor."],"Page Break":["Pagina-einde"],"Show Labels":["Labels weergeven"],"First Page Label":["Eerste paginalabel"],"Progress Indicator":["Voortgangsindicator"],"Progress Bar":["Voortgangsbalk"],"Connector":["Connector"],"Steps":["Stappen"],"Next Button Text":["Volgende knoptekst"],"Back Button Text":["Terugknoptekst"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["Weet je zeker dat je wilt afsluiten? Je niet-opgeslagen wijzigingen gaan verloren omdat je enkele validatiefouten hebt."],"There are few unsaved changes. Please save your changes to reflect the updates.":["Er zijn enkele niet-opgeslagen wijzigingen. Sla uw wijzigingen op om de updates door te voeren."],"Form Behavior":["Formuliergedrag"],"Clear":["Duidelijk"],"Select Color":["Selecteer kleur"],"Primary Color":["Primaire kleur"],"Text Color":["Tekstkleur"],"Text Color on Primary":["Tekstkleur op primair"],"Field Spacing":["Veldafstand"],"Small":["Klein"],"Large":["Groot"],"Left":["Links"],"Center":["Centrum"],"Right":["Rechts"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Onzichtbaar"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Datumkiezer"],"Time Picker":["Tijdkiezer"],"Hidden":["Verborgen"],"Slider":["Schuifregelaar"],"Rating":["Beoordeling"],"Upgrade to Unlock These Fields":["Upgrade om deze velden te ontgrendelen"],"Add Block":["Blok toevoegen"],"Customize with SureForms":["Pas aan met SureForms"],"Page break":["Pagina-einde"],"Previous":["Vorige"],"Thank you":["Dank je"],"Form submitted successfully!":["Formulier succesvol ingediend!"],"Instant Form":["Direct formulier"],"Enable Instant Form":["Instant Form inschakelen"],"Enable Preview":["Voorbeeld inschakelen"],"Show Title":["Toon titel"],"Site Logo":["Site-logo"],"Banner Background":["Banner Achtergrond"],"Color":["Kleur"],"Upload Image":["Afbeelding uploaden"],"Background Color":["Achtergrondkleur"],"Use banner as page background":["Gebruik banner als paginabackground"],"Form Width":["Formulierbreedte"],"URL":["URL"],"URL Slug":["URL-slug"],"The last part of the URL.":["Het laatste deel van de URL."],"Learn more.":["Meer informatie."],"SureForms Description":["SureForms Beschrijving"],"Form Options":["Formulieropties"],"Form Shortcode":["Formulier Shortcode"],"Paste this shortcode on the page or post to render this form.":["Plaats deze shortcode op de pagina of het bericht om dit formulier weer te geven."],"Spam Protection":["Spam Bescherming"],"Auto":["Auto"],"Normal":["Normaal"],"%":["%"],"Top":["Top"],"Bottom":["Onderkant"],"Solid":["Solide"],"Width":["Breedte"],"Size":["Grootte"],"EM":["EM"],"Padding":["Opvulling"],"Color 1":["Kleur 1"],"Color 2":["Kleur 2"],"Type":["Type"],"Linear":["Lineair"],"Radial":["Radiaal"],"Location 1":["Locatie 1"],"Location 2":["Locatie 2"],"Angle":["Hoek"],"Classic":["Klassiek"],"Gradient":["Gradi\u00ebnt"],"Background":["Achtergrond"],"Cover":["Omslag"],"Contain":["Bevatten"],"Overlay":["Overlay"],"No Repeat":["Geen herhaling"],"Overlay Opacity":["Overlay-opaciteit"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Klasnamen moeten gescheiden worden door spaties. Elke klasnaam mag niet beginnen met een cijfer, streepje of onderstrepingsteken. Ze mogen alleen letters (inclusief Unicode-tekens), cijfers, streepjes en onderstrepingstekens bevatten."],"Conversational Layout":["Gespreksindeling"],"Unlock Conversational Forms":["Ontgrendel Gespreksformulieren"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Met het SureForms Pro Plan kun je je formulieren omzetten in boeiende, conversatiegerichte lay-outs voor een naadloze gebruikerservaring."],"Premium":["Premium"],"Overlay Type":["Overlaytype"],"Image Overlay Color":["Afbeelding Overlay Kleur"],"Image Position":["Afbeeldingspositie"],"Attachment":["Bijlage"],"Fixed":["Vast"],"Blend Mode":["Overvloeimodus"],"Multiply":["Vermenigvuldigen"],"Screen":["Scherm"],"Darken":["Verduisteren"],"Lighten":["Verlichten"],"Color Dodge":["Kleur Dodge"],"Saturation":["Verzadiging"],"Repeat-x":["Herhaal-x"],"Repeat-y":["Herhaal-y"],"PX":["PX"],"Button":["Knop"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Verbind met OttoKit"],"SUREFORMS PREMIUM FIELDS":["SUREFORMS PREMIUM VELDEN"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Het huidige 'Van E-mailadres' komt niet overeen met de domeinnaam van uw website (%1$s). Dit kan ervoor zorgen dat uw notificatie-e-mails worden geblokkeerd of als spam worden gemarkeerd. Probeer in plaats daarvan een Van-adres te gebruiken dat overeenkomt met uw website-domein (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Het huidige 'Van E-mailadres' komt niet overeen met de domeinnaam van uw website (%s). Dit kan ervoor zorgen dat uw notificatie-e-mails worden geblokkeerd of als spam worden gemarkeerd."],"We strongly recommend that you install the free ":["We raden ten zeerste aan dat u de gratis installeert"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["plugin! De Setup Wizard maakt het gemakkelijk om je e-mails te repareren."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Probeer afwisselend een Van-adres te gebruiken dat overeenkomt met uw website-domein (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Voer een geldig e-mailadres in. Uw meldingen worden niet verzonden als het veld niet correct is ingevuld."],"From Name":["Van Naam"],"From Email":["Van e-mail"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Het huidige 'Van E-mailadres' komt mogelijk niet overeen met de domeinnaam van uw website (%1$s). Dit kan ervoor zorgen dat uw notificatie-e-mails worden geblokkeerd of als spam worden gemarkeerd. Probeer in plaats daarvan een Van-adres te gebruiken dat overeenkomt met uw website domein (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Het huidige 'Van E-mailadres' komt mogelijk niet overeen met de domeinnaam van uw website (%s). Dit kan ervoor zorgen dat uw notificatie-e-mails worden geblokkeerd of als spam worden gemarkeerd."],"Border Radius":["Randstraal"],"Form Theme":["Formulier Thema"],"Instant Form Padding":["Directe Formuliervulling"],"Instant Form Border Radius":["Directe Formulier Randstraal"],"Select Gradient":["Selecteer verloop"],"Upgrade Now":["Nu upgraden"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["Inzendingen ouder dan de geselecteerde dagen worden verwijderd."],"Entries Time Period":["Invoerperiode"],"Custom CSS Panel":["Aangepast CSS-paneel"],"Notifications can use only one From Email so please enter a single address.":["Meldingen kunnen slechts \u00e9\u00e9n Van E-mailadres gebruiken, dus voer alstublieft \u00e9\u00e9n enkel adres in."],"Email Notifications":["E-mailmeldingen"],"Actions":["Acties"],"Duplicate":["Duplicaat"],"Delete":["Verwijderen"],"Select Page to redirect":["Selecteer pagina om te omleiden"],"Search for a page":["Zoek naar een pagina"],"Select a page":["Selecteer een pagina"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Opslaan"],"Login":["Inloggen"],"Register":["Registreren"],"Date":["Datum"],"Advanced Settings":["Geavanceerde instellingen"],"Form Restriction":["Formulierbeperking"],"Maximum Number of Entries":["Maximaal aantal vermeldingen"],"Maximum Entries":["Maximale invoer"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["De instelling voor de tijdsperiode werkt volgens de tijdzone van je WordPress-site. Klik hier<\/a> om je WordPress Algemene Instellingen te openen, waar je deze kunt controleren en bijwerken."],"Click here":["Klik hier"],"Response Description After Maximum Entries":["Beschrijving van de reactie na maximale invoer"],"All changes will be saved automatically when you press back.":["Alle wijzigingen worden automatisch opgeslagen wanneer u op terug drukt."],"Repeater":["Herhaler"],"OttoKit Settings":["OttoKit-instellingen"],"Get Started":["Beginnen"],"Connect Native Integrations with SureForms":["Verbind native integraties met SureForms"],"Expected format for emails - email@sureforms.com or John Doe ":["Verwacht formaat voor e-mails - email@sureforms.com of John Doe "],"delete":["verwijderen"],"Please type \"%s\" in the input box":["Typ alstublieft \"%s\" in het invoerveld"],"To confirm, type \"%s\" in the box below:":["Om te bevestigen, typ \"%s\" in het vak hieronder:"],"Type \"%s\"":["Typ \"%s\""],"Payment":["Betaling"],"%s - Order ID":["%s - Bestel-ID"],"%s - Amount":["%s - Bedrag"],"%s - Customer Email":["%s - Klant e-mail"],"%s - Customer Name":["%s - Klantnaam"],"%s - Status":["%s - Status"],"Add custom CSS rules to style this specific form independently of global styles.":["Voeg aangepaste CSS-regels toe om dit specifieke formulier onafhankelijk van globale stijlen te stylen."],"Spam Protection Type":["Type spambeveiliging"],"Select Security Type":["Selecteer beveiligingstype"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Opmerking: Het gebruik van verschillende reCAPTCHA-versies (V2 checkbox en V3) op dezelfde pagina zal conflicten tussen de versies veroorzaken. Vermijd alstublieft het gebruik van verschillende versies op dezelfde pagina."],"Select Version":["Selecteer versie"],"Please configure the API keys correctly from the settings":["Configureer de API-sleutels correct via de instellingen"],"Control email alerts sent to admins or users after a form submission.":["Beheer e-mailmeldingen die naar beheerders of gebruikers worden verzonden na een formulierinzending."],"Customize the confirmation message or redirect the users after submitting the form.":["Pas het bevestigingsbericht aan of leid de gebruikers om nadat ze het formulier hebben ingediend."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Stel limieten in voor het aantal keren dat een formulier kan worden ingediend en beheer nalevingsopties, waaronder AVG en gegevensbewaring."],"Go to OttoKit Settings":["Ga naar OttoKit-instellingen"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Verbind SureForms met je favoriete apps om taken te automatiseren en gegevens naadloos te synchroniseren."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Ontgrendel krachtige integraties in het Premium-plan om uw workflows te automatiseren en SureForms direct te verbinden met uw favoriete tools."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Stuur formulierinzendingen rechtstreeks naar CRM's, e-mail en marketingplatforms."],"Automate repetitive tasks with seamless data syncing.":["Automatiseer repetitieve taken met naadloze gegevenssynchronisatie."],"Access exclusive native integrations for faster workflows.":["Toegang tot exclusieve native integraties voor snellere workflows."],"PDF Generation":["PDF-generatie"],"Generate and customize PDF copies of form submissions.":["Genereer en pas PDF-kopie\u00ebn van formulierinzendingen aan."],"Generate Submission PDFs":["Genereer indienings-PDF's"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Zet elke formulierinvoer om in een gepolijste PDF-bestand, waardoor het perfect is voor rapporten, archieven of om te delen."],"Automatically generate PDFs from your form submissions.":["Genereer automatisch PDF's van uw formulierinzendingen."],"Customize PDF templates with your branding.":["Pas PDF-sjablonen aan met uw branding."],"Download or email PDFs instantly.":["Download of e-mail PDF's direct."],"User Registration":["Gebruikersregistratie"],"Onboard new users or update existing accounts through beautiful looking forms.":["Nieuwe gebruikers onboarden of bestaande accounts bijwerken via prachtig uitziende formulieren."],"Register Users with SureForms":["Registreer gebruikers met SureForms"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Stroomlijn het gehele onboardingproces voor gebruikers op uw sites met naadloze, formuliergestuurde logins en registraties."],"Register new users directly via your form submissions.":["Registreer nieuwe gebruikers direct via uw formulierinzendingen."],"Create or update existing accounts by mapping form data to user fields.":["Maak nieuwe accounts aan of werk bestaande accounts bij door formuliergegevens aan gebruikersvelden te koppelen."],"Assign roles and control access automatically.":["Wijs rollen toe en beheer de toegang automatisch."],"Post Feed":["Berichtenfeed"],"Transform your form submission into WordPress posts.":["Transformeer je formulierinzending in WordPress-berichten."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Zet automatisch formulierinzendingen om in WordPress-berichten, pagina's of aangepaste berichttypen. Bespaar veel tijd en laat je formulieren direct content publiceren."],"Create posts, pages, or CPTs from your form entries.":["Maak berichten, pagina's of CPT's van uw formulierinzendingen."],"Map form fields to your post fields easily.":["Wijs formulier velden eenvoudig toe aan je berichtvelden."],"Automate the content publishing flow with few simple steps.":["Automatiseer de inhoudspublicatiestroom met een paar eenvoudige stappen."],"Automations":["Automatiseringen"],"Unlock Advanced Styling":["Ontgrendel geavanceerde opmaak"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Krijg volledige controle over het uiterlijk van uw formulier met aangepaste kleuren, lettertypen en lay-outs."],"Button Alignment":["Knopuitlijning"],"Add Custom CSS Class(es)":["Voeg Aangepaste CSS Klasse(n) Toe"],"Set the total number of submissions allowed for this form.":["Stel het totale aantal toegestane inzendingen voor dit formulier in."],"Save & Progress":["Opslaan & Voortgang"],"Allow users to save their progress and continue form completion later.":["Sta gebruikers toe hun voortgang op te slaan en later verder te gaan met het invullen van het formulier."],"Save & Progress in SureForms":["Opslaan & Verdergaan in SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["Geef uw gebruikers de flexibiliteit om formulieren in hun eigen tempo in te vullen door hen toe te staan hun voortgang op te slaan en op elk moment terug te keren."],"Let users pause long or multi-step forms and continue later.":["Laat gebruikers lange of meerstapsformulieren pauzeren en later doorgaan."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Verminder het verlaten van formulieren met handige hervattingslinks en krijg overal toegang tot hun voortgang."],"Improve user experience for lengthy, complex, or multi-page forms.":["Verbeter de gebruikerservaring voor lange, complexe of meerpaginaformulieren."],"This form is not yet available. Please check back after the scheduled start time.":["Dit formulier is nog niet beschikbaar. Kom na de geplande starttijd terug."],"This form is no longer accepting submissions. The submission period has ended.":["Dit formulier accepteert geen inzendingen meer. De inzendperiode is afgelopen."],"The start date and time must be before the end date and time.":["De startdatum en -tijd moeten v\u00f3\u00f3r de einddatum en -tijd liggen."],"Form Scheduling":["Formulierplanning"],"Enable Form Scheduling":["Formulierplanning inschakelen"],"Set a time period during which this form will be available for submissions.":["Stel een periode in waarin dit formulier beschikbaar zal zijn voor inzendingen."],"Start Date & Time":["Startdatum en tijd"],"End Date & Time":["Einddatum en tijd"],"Response Description Before Start Date":["Reactiebeschrijving v\u00f3\u00f3r de startdatum"],"Response Description After End Date":["Reactiebeschrijving na einddatum"],"Conditional Confirmations":["Voorwaardelijke Bevestigingen"],"Set up the message or redirect users will see after submitting the form.":["Stel het bericht in of leid gebruikers om dat ze zullen zien na het indienen van het formulier."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Toon het juiste bericht aan de juiste gebruiker op basis van hun reactie. Personaliseer bevestigingen met slimme voorwaarden en begeleid gebruikers automatisch naar de volgende beste stap."],"Display different confirmation messages based on form responses.":["Toon verschillende bevestigingsberichten op basis van formulierreacties."],"Redirect users to specific pages or URLs conditionally.":["Leid gebruikers voorwaardelijk naar specifieke pagina's of URL's om."],"Create personalized thank-you messages without extra forms.":["Maak gepersonaliseerde bedankberichten zonder extra formulieren."],"Lost Password":["Wachtwoord Vergeten"],"Reset Password":["Wachtwoord opnieuw instellen"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["Selecteer een spambeveiligingsdienst. Configureer API-sleutels in de globale instellingen voordat u deze inschakelt."],"Send as Raw HTML":["Als ruwe HTML verzenden"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Wanneer ingeschakeld, wordt de HTML van de e-mailtekst exact bewaard zoals geschreven en verpakt in een professioneel e-mailsjabloon."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["Smart tags die verwijzen naar door gebruikers ingediende velden, worden niet ge\u00ebscaped in de ruwe HTML-modus. Vermijd het direct invoegen van niet-vertrouwde veldwaarden in de e-mailinhoud."],"Please provide a recipient email address and subject line.":["Geef alstublieft een e-mailadres van de ontvanger en een onderwerpregel op."],"Email notification duplicated!":["E-mailmelding gedupliceerd!"],"Are you sure you want to delete this email notification?":["Weet je zeker dat je deze e-mailmelding wilt verwijderen?"],"Email notification deleted!":["E-mailmelding verwijderd!"],"URL is missing Top Level Domain (TLD).":["URL mist het Top Level Domain (TLD)."],"This form is now closed as the maximum number of entries has been received.":["Dit formulier is nu gesloten omdat het maximale aantal inzendingen is ontvangen."],"Publish Your Form":["Publiceer uw formulier"],"Enable This to Instantly Publish the Form":["Schakel dit in om het formulier direct te publiceren"],"Style Your Instant Form Page Here":["Stijl hier je instant formulierpagina"],"Quizzes":["Quizzen"],"%s - Description":["%s - Beschrijving"],"Send entries to 100+ popular apps.":["Verzend inzendingen naar meer dan 100 populaire apps."],"Build automated workflows that run instantly.":["Bouw geautomatiseerde workflows die direct worden uitgevoerd."],"Create custom app integrations using our Custom App feature.":["Maak aangepaste app-integraties met behulp van onze functie Aangepaste App."],"Keep your tools in sync automatically.":["Houd je gereedschap automatisch gesynchroniseerd."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["Hiermee wordt OttoKit op je WordPress-site ge\u00efnstalleerd en geactiveerd om automatiseringsfuncties mogelijk te maken."],"Automate Your Forms with OttoKit":["Automatiseer uw formulieren met OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Elke formulierinzending zou iets moeten activeren \u2014 een Slack-melding, een CRM-lead, een opvolg-e-mail of een nieuwe rij in Google Sheets."],"Create interactive quizzes to engage your audience and gather insights.":["Maak interactieve quizzen om je publiek te betrekken en inzichten te verzamelen."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Ontwerp boeiende quizzen met verschillende vraagtypen, gepersonaliseerde feedback en geautomatiseerde scoring om je publiek te boeien en waardevolle inzichten te verkrijgen."],"Create interactive quizzes with multiple question types.":["Maak interactieve quizzen met meerdere vraagtypen."],"Provide personalized feedback based on user responses.":["Geef gepersonaliseerde feedback op basis van gebruikersreacties."],"Automate scoring and lead segmentation for better insights.":["Automatiseer scoring en leadsegmentatie voor betere inzichten."],"Heading 1":["Kop 1"],"Heading 2":["Kop 2"],"Heading 3":["Kop 3"],"Heading 4":["Kop 4"],"Heading 5":["Kop 5"],"Heading 6":["Kop 6"],"Form settings saved.":["Formulierinstellingen opgeslagen."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["De invoerlimiet is afhankelijk van opgeslagen invoeren om inzendingen te tellen. Als in de nalevingsinstellingen \"Nooit invoergegevens opslaan na formulierinzending\" is ingeschakeld, wordt deze limiet niet afgedwongen. Schakel die optie uit, of verwijder de invoerlimiet, om deze functie te gebruiken."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Instellingen opgeslagen, maar postattributen (wachtwoord \/ titel \/ inhoud) konden niet worden bijgewerkt. Probeer opnieuw om ze te behouden."],"Failed to save form settings.":["Opslaan van formulierinstellingen mislukt."],"Saving\u2026":["Opslaan\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Wanneer ingeschakeld, zal dit formulier het IP-adres van de gebruiker, de naam van de browser en de naam van het apparaat niet opslaan in de inzendingen."],"Failed to save. Please try again.":["Opslaan mislukt. Probeer het opnieuw."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["reCAPTCHA API-sleutels voor de geselecteerde versie zijn niet geconfigureerd. Stel ze in bij de Globale Instellingen."],"Please select a reCAPTCHA version.":["Selecteer een reCAPTCHA-versie."],"hCaptcha API keys are not configured. Set them in Global Settings.":["hCaptcha API-sleutels zijn niet geconfigureerd. Stel ze in bij de Algemene Instellingen."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["Cloudflare Turnstile API-sleutels zijn niet geconfigureerd. Stel ze in bij de Globale Instellingen."],"Form data":["Formuliergegevens"],"Some fields need attention":["Sommige velden hebben aandacht nodig"],"Unsaved changes":["Niet-opgeslagen wijzigingen"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Een e-mailadres van de ontvanger en een onderwerpregel zijn vereist voordat deze melding kan worden opgeslagen. Corrigeer de gemarkeerde velden of verwijder uw wijzigingen om terug te gaan."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Je hebt niet-opgeslagen wijzigingen voor deze melding. Verwijder ze om terug te gaan, of blijf om ze op te slaan."],"Discard & go back":["Verwijderen & teruggaan"],"Stay & fix":["Blijf & repareer"],"Keep editing":["Blijf bewerken"],"Please provide a recipient email address.":["Geef alstublieft een e-mailadres van de ontvanger op."],"Please provide a subject line.":["Geef alstublieft een onderwerpregel op."],"Please provide a custom URL.":["Geef een aangepaste URL op."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Je hebt niet-opgeslagen wijzigingen. Verwijder ze om door te gaan, of blijf om je wijzigingen op te slaan."],"Discard & continue":["Weggooien & doorgaan"],"Quill heading picker: default paragraph style\u0004Normal":["Normaal"]}}} \ No newline at end of file +{"translation-revision-date":"2024-12-13T12:33:48+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Status"],"Form":["Formulier"],"Fields":["Velden"],"Image":["Afbeelding"],"Activated":["Geactiveerd"],"Activate":["Activeren"],"Submit":["Indienen"],"Global Settings":["Globale instellingen"],"Form Title":["Formuliertitel"],"Edit":["Bewerken"],"Please enter a valid URL.":["Voer een geldige URL in."],"Desktop":["Bureaublad"],"Medium":["Middelgroot"],"Mobile":["Mobiel"],"Repeat":["Herhaal"],"Scroll":["Scrollen"],"Signature":["Handtekening"],"Tablet":["Tablet"],"Upload":["Uploaden"],"Basic":["Basis"],"Form Settings":["Formulierinstellingen"],"General":["Algemeen"],"Style":["Stijl"],"Advanced":["Geavanceerd"],"No tags available":["Geen tags beschikbaar"],"Device":["Apparaat"],"Select Shortcodes":["Selecteer shortcodes"],"Page Break Label":["Pagina-einde label"],"Next":["Volgende"],"Back":["Terug"],"Reset":["Resetten"],"Generic tags":["Algemene tags"],"Pixel":["Pixel"],"Em":["Em"],"Select Units":["Selecteer eenheden"],"%s units":["%s eenheden"],"Margin":["Marge"],"None":["Geen"],"Custom":["Aangepast"],"Please add a option props to MultiButtonsControl":["Voeg alstublieft een optie-eigenschap toe aan MultiButtonsControl"],"Confirm":["Bevestigen"],"Cancel":["Annuleren"],"Processing\u2026":["Bezig met verwerken\u2026"],"Select Video":["Selecteer video"],"Change Video":["Video wijzigen"],"Select Lottie Animation":["Selecteer Lottie-animatie"],"Change Lottie Animation":["Wijzig Lottie-animatie"],"Upload SVG":["SVG uploaden"],"Change SVG":["SVG wijzigen"],"Select Image":["Selecteer afbeelding"],"Change Image":["Afbeelding wijzigen"],"Upload SVG?":["SVG uploaden?"],"Upload SVG can be potentially risky. Are you sure?":["Het uploaden van SVG kan potentieel riskant zijn. Weet je het zeker?"],"Upload Anyway":["Toch uploaden"],"Full Width":["Volledige breedte"],"Install":["Installeren"],"Plugin Installation failed, Please try again later.":["Installatie van de plugin mislukt, probeer het later opnieuw."],"Plugin activation failed, Please try again later.":["Plug-in activering mislukt, probeer het later opnieuw."],"Integrations":["Integraties"],"%s Removed from Quick Action Bar.":["%s verwijderd van de snelle actiebalk."],"Add to Quick Action Bar":["Toevoegen aan de Snelle Actiebalk"],"%s Added to Quick Action Bar.":["%s toegevoegd aan de Snelle Actiebalk."],"Already Present in Quick Action Bar":["Al aanwezig in de snelle actiebalk"],"No results found.":["Geen resultaten gevonden."],"data object is empty":["gegevensobject is leeg"],"Add blocks to Quick Action Bar":["Blokken toevoegen aan de Snelle Actiebalk"],"Re-arrange block inside Quick Action Bar":["Blok herschikken in de Snelle Actiebalk"],"Upgrade":["Upgrade"],"Connecting\u2026":["Verbinden\u2026"],"Install & Activate":["Installeren & Activeren"],"Compliance Settings":["Instellingen voor naleving"],"Enable GDPR Compliance":["Schakel GDPR-naleving in"],"Never store entry data after form submission":["Sla nooit invoergegevens op na het indienen van het formulier"],"When enabled this form will never store Entries.":["Wanneer ingeschakeld, zal dit formulier nooit inzendingen opslaan."],"Automatically delete entries":["Automatisch items verwijderen"],"When enabled this form will automatically delete entries after a certain period of time.":["Wanneer ingeschakeld, zal dit formulier automatisch inzendingen na een bepaalde periode verwijderen."],"Entries older than the days set will be deleted automatically.":["Inzendingen ouder dan de ingestelde dagen worden automatisch verwijderd."],"Custom CSS":["Aangepaste CSS"],"The following CSS styles added below will only apply to this form container.":["De volgende CSS-stijlen die hieronder worden toegevoegd, zijn alleen van toepassing op deze formuliercontainer."],"Visual":["Visueel"],"HTML":["HTML"],"All Data":["Alle gegevens"],"Add Shortcode":["Voeg shortcode toe"],"Form input tags":["Formulierveldlabels"],"Comma separated values are also accepted.":["Door komma's gescheiden waarden worden ook geaccepteerd."],"Email Notification":["E-mailmelding"],"Name":["Naam"],"Send Email To":["E-mail verzenden naar"],"Subject":["Onderwerp"],"CC":["CC"],"BCC":["BCC"],"Reply To":["Antwoord aan"],"Add Notification":["Melding toevoegen"],"Add Key":["Sleutel toevoegen"],"Add Value":["Waarde toevoegen"],"Add":["Toevoegen"],"Confirmation Message":["Bevestigingsbericht"],"After Form Submission":["Na het indienen van het formulier"],"Hide Form":["Formulier verbergen"],"Reset Form":["Formulier opnieuw instellen"],"Custom URL":["Aangepaste URL"],"Add Query Parameters":["Queryparameters toevoegen"],"Select if you want to add key-value pairs for form fields to include in query parameters":["Selecteer of u sleutel-waardeparen wilt toevoegen voor formuliervelden om op te nemen in queryparameters"],"Query Parameters":["Queryparameters"],"Please select a page.":["Selecteer een pagina."],"Suggestion: URL should use HTTPS":["Suggestie: URL moet HTTPS gebruiken"],"Success Message":["Succesbericht"],"Redirect":["Doorsturen"],"Redirect to":["Doorsturen naar"],"Page":["Pagina"],"Form Confirmation":["Formulierbevestiging"],"Confirmation Type":["Bevestigingstype"],"Use Labels as Placeholders":["Gebruik labels als placeholders"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["Bovenstaande instelling plaatst de labels binnen de velden als placeholders (waar mogelijk). Deze instelling is alleen van toepassing op de live pagina, niet in de voorbeeldweergave van de editor."],"Page Break":["Pagina-einde"],"Show Labels":["Labels weergeven"],"First Page Label":["Eerste paginalabel"],"Progress Indicator":["Voortgangsindicator"],"Progress Bar":["Voortgangsbalk"],"Connector":["Connector"],"Steps":["Stappen"],"Next Button Text":["Volgende knoptekst"],"Back Button Text":["Terugknoptekst"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["Weet je zeker dat je wilt afsluiten? Je niet-opgeslagen wijzigingen gaan verloren omdat je enkele validatiefouten hebt."],"There are few unsaved changes. Please save your changes to reflect the updates.":["Er zijn enkele niet-opgeslagen wijzigingen. Sla uw wijzigingen op om de updates door te voeren."],"Form Behavior":["Formuliergedrag"],"Clear":["Duidelijk"],"Select Color":["Selecteer kleur"],"Primary Color":["Primaire kleur"],"Text Color":["Tekstkleur"],"Text Color on Primary":["Tekstkleur op primair"],"Field Spacing":["Veldafstand"],"Small":["Klein"],"Large":["Groot"],"Left":["Links"],"Center":["Centrum"],"Right":["Rechts"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Onzichtbaar"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Datumkiezer"],"Time Picker":["Tijdkiezer"],"Hidden":["Verborgen"],"Slider":["Schuifregelaar"],"Rating":["Beoordeling"],"Upgrade to Unlock These Fields":["Upgrade om deze velden te ontgrendelen"],"Add Block":["Blok toevoegen"],"Customize with SureForms":["Pas aan met SureForms"],"Page break":["Pagina-einde"],"Previous":["Vorige"],"Thank you":["Dank je"],"Form submitted successfully!":["Formulier succesvol ingediend!"],"Instant Form":["Direct formulier"],"Enable Instant Form":["Instant Form inschakelen"],"Enable Preview":["Voorbeeld inschakelen"],"Show Title":["Toon titel"],"Site Logo":["Site-logo"],"Banner Background":["Banner Achtergrond"],"Color":["Kleur"],"Upload Image":["Afbeelding uploaden"],"Background Color":["Achtergrondkleur"],"Use banner as page background":["Gebruik banner als paginabackground"],"Form Width":["Formulierbreedte"],"URL":["URL"],"URL Slug":["URL-slug"],"The last part of the URL.":["Het laatste deel van de URL."],"Learn more.":["Meer informatie."],"SureForms Description":["SureForms Beschrijving"],"Form Options":["Formulieropties"],"Form Shortcode":["Formulier Shortcode"],"Paste this shortcode on the page or post to render this form.":["Plaats deze shortcode op de pagina of het bericht om dit formulier weer te geven."],"Spam Protection":["Spam Bescherming"],"Auto":["Auto"],"Normal":["Normaal"],"%":["%"],"Top":["Top"],"Bottom":["Onderkant"],"Solid":["Solide"],"Width":["Breedte"],"Size":["Grootte"],"EM":["EM"],"Padding":["Opvulling"],"Color 1":["Kleur 1"],"Color 2":["Kleur 2"],"Type":["Type"],"Linear":["Lineair"],"Radial":["Radiaal"],"Location 1":["Locatie 1"],"Location 2":["Locatie 2"],"Angle":["Hoek"],"Classic":["Klassiek"],"Gradient":["Gradi\u00ebnt"],"Background":["Achtergrond"],"Cover":["Omslag"],"Contain":["Bevatten"],"Overlay":["Overlay"],"No Repeat":["Geen herhaling"],"Overlay Opacity":["Overlay-opaciteit"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Klasnamen moeten gescheiden worden door spaties. Elke klasnaam mag niet beginnen met een cijfer, streepje of onderstrepingsteken. Ze mogen alleen letters (inclusief Unicode-tekens), cijfers, streepjes en onderstrepingstekens bevatten."],"Conversational Layout":["Gespreksindeling"],"Unlock Conversational Forms":["Ontgrendel Gespreksformulieren"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Met het SureForms Pro Plan kun je je formulieren omzetten in boeiende, conversatiegerichte lay-outs voor een naadloze gebruikerservaring."],"Premium":["Premium"],"Overlay Type":["Overlaytype"],"Image Overlay Color":["Afbeelding Overlay Kleur"],"Image Position":["Afbeeldingspositie"],"Attachment":["Bijlage"],"Fixed":["Vast"],"Blend Mode":["Overvloeimodus"],"Multiply":["Vermenigvuldigen"],"Screen":["Scherm"],"Darken":["Verduisteren"],"Lighten":["Verlichten"],"Color Dodge":["Kleur Dodge"],"Saturation":["Verzadiging"],"Repeat-x":["Herhaal-x"],"Repeat-y":["Herhaal-y"],"PX":["PX"],"Button":["Knop"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Verbind met OttoKit"],"SUREFORMS PREMIUM FIELDS":["SUREFORMS PREMIUM VELDEN"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Het huidige 'Van E-mailadres' komt niet overeen met de domeinnaam van uw website (%1$s). Dit kan ervoor zorgen dat uw notificatie-e-mails worden geblokkeerd of als spam worden gemarkeerd. Probeer in plaats daarvan een Van-adres te gebruiken dat overeenkomt met uw website-domein (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Het huidige 'Van E-mailadres' komt niet overeen met de domeinnaam van uw website (%s). Dit kan ervoor zorgen dat uw notificatie-e-mails worden geblokkeerd of als spam worden gemarkeerd."],"We strongly recommend that you install the free ":["We raden ten zeerste aan dat u de gratis installeert"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["plugin! De Setup Wizard maakt het gemakkelijk om je e-mails te repareren."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Probeer afwisselend een Van-adres te gebruiken dat overeenkomt met uw website-domein (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Voer een geldig e-mailadres in. Uw meldingen worden niet verzonden als het veld niet correct is ingevuld."],"From Name":["Van Naam"],"From Email":["Van e-mail"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Het huidige 'Van E-mailadres' komt mogelijk niet overeen met de domeinnaam van uw website (%1$s). Dit kan ervoor zorgen dat uw notificatie-e-mails worden geblokkeerd of als spam worden gemarkeerd. Probeer in plaats daarvan een Van-adres te gebruiken dat overeenkomt met uw website domein (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Het huidige 'Van E-mailadres' komt mogelijk niet overeen met de domeinnaam van uw website (%s). Dit kan ervoor zorgen dat uw notificatie-e-mails worden geblokkeerd of als spam worden gemarkeerd."],"Border Radius":["Randstraal"],"Form Theme":["Formulier Thema"],"Instant Form Padding":["Directe Formuliervulling"],"Instant Form Border Radius":["Directe Formulier Randstraal"],"Select Gradient":["Selecteer verloop"],"Upgrade Now":["Nu upgraden"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["Inzendingen ouder dan de geselecteerde dagen worden verwijderd."],"Entries Time Period":["Invoerperiode"],"Custom CSS Panel":["Aangepast CSS-paneel"],"Notifications can use only one From Email so please enter a single address.":["Meldingen kunnen slechts \u00e9\u00e9n Van E-mailadres gebruiken, dus voer alstublieft \u00e9\u00e9n enkel adres in."],"Email Notifications":["E-mailmeldingen"],"Actions":["Acties"],"Duplicate":["Duplicaat"],"Delete":["Verwijderen"],"Select Page to redirect":["Selecteer pagina om te omleiden"],"Search for a page":["Zoek naar een pagina"],"Select a page":["Selecteer een pagina"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Opslaan"],"Login":["Inloggen"],"Register":["Registreren"],"Date":["Datum"],"Advanced Settings":["Geavanceerde instellingen"],"Form Restriction":["Formulierbeperking"],"Maximum Number of Entries":["Maximaal aantal vermeldingen"],"Maximum Entries":["Maximale invoer"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["De instelling voor de tijdsperiode werkt volgens de tijdzone van je WordPress-site. Klik hier<\/a> om je WordPress Algemene Instellingen te openen, waar je deze kunt controleren en bijwerken."],"Click here":["Klik hier"],"Response Description After Maximum Entries":["Beschrijving van de reactie na maximale invoer"],"All changes will be saved automatically when you press back.":["Alle wijzigingen worden automatisch opgeslagen wanneer u op terug drukt."],"Repeater":["Herhaler"],"OttoKit Settings":["OttoKit-instellingen"],"Get Started":["Beginnen"],"Connect Native Integrations with SureForms":["Verbind native integraties met SureForms"],"Expected format for emails - email@sureforms.com or John Doe ":["Verwacht formaat voor e-mails - email@sureforms.com of John Doe "],"delete":["verwijderen"],"Please type \"%s\" in the input box":["Typ alstublieft \"%s\" in het invoerveld"],"To confirm, type \"%s\" in the box below:":["Om te bevestigen, typ \"%s\" in het vak hieronder:"],"Type \"%s\"":["Typ \"%s\""],"Payment":["Betaling"],"%s - Order ID":["%s - Bestel-ID"],"%s - Amount":["%s - Bedrag"],"%s - Customer Email":["%s - Klant e-mail"],"%s - Customer Name":["%s - Klantnaam"],"%s - Status":["%s - Status"],"Add custom CSS rules to style this specific form independently of global styles.":["Voeg aangepaste CSS-regels toe om dit specifieke formulier onafhankelijk van globale stijlen te stylen."],"Spam Protection Type":["Type spambeveiliging"],"Select Security Type":["Selecteer beveiligingstype"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Opmerking: Het gebruik van verschillende reCAPTCHA-versies (V2 checkbox en V3) op dezelfde pagina zal conflicten tussen de versies veroorzaken. Vermijd alstublieft het gebruik van verschillende versies op dezelfde pagina."],"Select Version":["Selecteer versie"],"Please configure the API keys correctly from the settings":["Configureer de API-sleutels correct via de instellingen"],"Control email alerts sent to admins or users after a form submission.":["Beheer e-mailmeldingen die naar beheerders of gebruikers worden verzonden na een formulierinzending."],"Customize the confirmation message or redirect the users after submitting the form.":["Pas het bevestigingsbericht aan of leid de gebruikers om nadat ze het formulier hebben ingediend."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Stel limieten in voor het aantal keren dat een formulier kan worden ingediend en beheer nalevingsopties, waaronder AVG en gegevensbewaring."],"Go to OttoKit Settings":["Ga naar OttoKit-instellingen"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Verbind SureForms met je favoriete apps om taken te automatiseren en gegevens naadloos te synchroniseren."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Ontgrendel krachtige integraties in het Premium-plan om uw workflows te automatiseren en SureForms direct te verbinden met uw favoriete tools."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Stuur formulierinzendingen rechtstreeks naar CRM's, e-mail en marketingplatforms."],"Automate repetitive tasks with seamless data syncing.":["Automatiseer repetitieve taken met naadloze gegevenssynchronisatie."],"Access exclusive native integrations for faster workflows.":["Toegang tot exclusieve native integraties voor snellere workflows."],"PDF Generation":["PDF-generatie"],"Generate and customize PDF copies of form submissions.":["Genereer en pas PDF-kopie\u00ebn van formulierinzendingen aan."],"Generate Submission PDFs":["Genereer indienings-PDF's"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Zet elke formulierinvoer om in een gepolijste PDF-bestand, waardoor het perfect is voor rapporten, archieven of om te delen."],"Automatically generate PDFs from your form submissions.":["Genereer automatisch PDF's van uw formulierinzendingen."],"Customize PDF templates with your branding.":["Pas PDF-sjablonen aan met uw branding."],"Download or email PDFs instantly.":["Download of e-mail PDF's direct."],"User Registration":["Gebruikersregistratie"],"Onboard new users or update existing accounts through beautiful looking forms.":["Nieuwe gebruikers onboarden of bestaande accounts bijwerken via prachtig uitziende formulieren."],"Register Users with SureForms":["Registreer gebruikers met SureForms"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Stroomlijn het gehele onboardingproces voor gebruikers op uw sites met naadloze, formuliergestuurde logins en registraties."],"Register new users directly via your form submissions.":["Registreer nieuwe gebruikers direct via uw formulierinzendingen."],"Create or update existing accounts by mapping form data to user fields.":["Maak nieuwe accounts aan of werk bestaande accounts bij door formuliergegevens aan gebruikersvelden te koppelen."],"Assign roles and control access automatically.":["Wijs rollen toe en beheer de toegang automatisch."],"Post Feed":["Berichtenfeed"],"Transform your form submission into WordPress posts.":["Transformeer je formulierinzending in WordPress-berichten."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Zet automatisch formulierinzendingen om in WordPress-berichten, pagina's of aangepaste berichttypen. Bespaar veel tijd en laat je formulieren direct content publiceren."],"Create posts, pages, or CPTs from your form entries.":["Maak berichten, pagina's of CPT's van uw formulierinzendingen."],"Map form fields to your post fields easily.":["Wijs formulier velden eenvoudig toe aan je berichtvelden."],"Automate the content publishing flow with few simple steps.":["Automatiseer de inhoudspublicatiestroom met een paar eenvoudige stappen."],"Automations":["Automatiseringen"],"Unlock Advanced Styling":["Ontgrendel geavanceerde opmaak"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Krijg volledige controle over het uiterlijk van uw formulier met aangepaste kleuren, lettertypen en lay-outs."],"Button Alignment":["Knopuitlijning"],"Add Custom CSS Class(es)":["Voeg Aangepaste CSS Klasse(n) Toe"],"Set the total number of submissions allowed for this form.":["Stel het totale aantal toegestane inzendingen voor dit formulier in."],"Save & Progress":["Opslaan & Voortgang"],"Allow users to save their progress and continue form completion later.":["Sta gebruikers toe hun voortgang op te slaan en later verder te gaan met het invullen van het formulier."],"Save & Progress in SureForms":["Opslaan & Verdergaan in SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["Geef uw gebruikers de flexibiliteit om formulieren in hun eigen tempo in te vullen door hen toe te staan hun voortgang op te slaan en op elk moment terug te keren."],"Let users pause long or multi-step forms and continue later.":["Laat gebruikers lange of meerstapsformulieren pauzeren en later doorgaan."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Verminder het verlaten van formulieren met handige hervattingslinks en krijg overal toegang tot hun voortgang."],"Improve user experience for lengthy, complex, or multi-page forms.":["Verbeter de gebruikerservaring voor lange, complexe of meerpaginaformulieren."],"This form is not yet available. Please check back after the scheduled start time.":["Dit formulier is nog niet beschikbaar. Kom na de geplande starttijd terug."],"This form is no longer accepting submissions. The submission period has ended.":["Dit formulier accepteert geen inzendingen meer. De inzendperiode is afgelopen."],"The start date and time must be before the end date and time.":["De startdatum en -tijd moeten v\u00f3\u00f3r de einddatum en -tijd liggen."],"Form Scheduling":["Formulierplanning"],"Enable Form Scheduling":["Formulierplanning inschakelen"],"Set a time period during which this form will be available for submissions.":["Stel een periode in waarin dit formulier beschikbaar zal zijn voor inzendingen."],"Start Date & Time":["Startdatum en tijd"],"End Date & Time":["Einddatum en tijd"],"Response Description Before Start Date":["Reactiebeschrijving v\u00f3\u00f3r de startdatum"],"Response Description After End Date":["Reactiebeschrijving na einddatum"],"Conditional Confirmations":["Voorwaardelijke Bevestigingen"],"Set up the message or redirect users will see after submitting the form.":["Stel het bericht in of leid gebruikers om dat ze zullen zien na het indienen van het formulier."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Toon het juiste bericht aan de juiste gebruiker op basis van hun reactie. Personaliseer bevestigingen met slimme voorwaarden en begeleid gebruikers automatisch naar de volgende beste stap."],"Display different confirmation messages based on form responses.":["Toon verschillende bevestigingsberichten op basis van formulierreacties."],"Redirect users to specific pages or URLs conditionally.":["Leid gebruikers voorwaardelijk naar specifieke pagina's of URL's om."],"Create personalized thank-you messages without extra forms.":["Maak gepersonaliseerde bedankberichten zonder extra formulieren."],"Lost Password":["Wachtwoord Vergeten"],"Reset Password":["Wachtwoord opnieuw instellen"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["Selecteer een spambeveiligingsdienst. Configureer API-sleutels in de globale instellingen voordat u deze inschakelt."],"Send as Raw HTML":["Als ruwe HTML verzenden"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Wanneer ingeschakeld, wordt de HTML van de e-mailtekst exact bewaard zoals geschreven en verpakt in een professioneel e-mailsjabloon."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["Smart tags die verwijzen naar door gebruikers ingediende velden, worden niet ge\u00ebscaped in de ruwe HTML-modus. Vermijd het direct invoegen van niet-vertrouwde veldwaarden in de e-mailinhoud."],"Please provide a recipient email address and subject line.":["Geef alstublieft een e-mailadres van de ontvanger en een onderwerpregel op."],"Email notification duplicated!":["E-mailmelding gedupliceerd!"],"Are you sure you want to delete this email notification?":["Weet je zeker dat je deze e-mailmelding wilt verwijderen?"],"Email notification deleted!":["E-mailmelding verwijderd!"],"URL is missing Top Level Domain (TLD).":["URL mist het Top Level Domain (TLD)."],"This form is now closed as the maximum number of entries has been received.":["Dit formulier is nu gesloten omdat het maximale aantal inzendingen is ontvangen."],"Publish Your Form":["Publiceer uw formulier"],"Enable This to Instantly Publish the Form":["Schakel dit in om het formulier direct te publiceren"],"Style Your Instant Form Page Here":["Stijl hier je instant formulierpagina"],"Quizzes":["Quizzen"],"%s - Description":["%s - Beschrijving"],"Send entries to 100+ popular apps.":["Verzend inzendingen naar meer dan 100 populaire apps."],"Build automated workflows that run instantly.":["Bouw geautomatiseerde workflows die direct worden uitgevoerd."],"Create custom app integrations using our Custom App feature.":["Maak aangepaste app-integraties met behulp van onze functie Aangepaste App."],"Keep your tools in sync automatically.":["Houd je gereedschap automatisch gesynchroniseerd."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["Hiermee wordt OttoKit op je WordPress-site ge\u00efnstalleerd en geactiveerd om automatiseringsfuncties mogelijk te maken."],"Automate Your Forms with OttoKit":["Automatiseer uw formulieren met OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Elke formulierinzending zou iets moeten activeren \u2014 een Slack-melding, een CRM-lead, een opvolg-e-mail of een nieuwe rij in Google Sheets."],"Create interactive quizzes to engage your audience and gather insights.":["Maak interactieve quizzen om je publiek te betrekken en inzichten te verzamelen."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Ontwerp boeiende quizzen met verschillende vraagtypen, gepersonaliseerde feedback en geautomatiseerde scoring om je publiek te boeien en waardevolle inzichten te verkrijgen."],"Create interactive quizzes with multiple question types.":["Maak interactieve quizzen met meerdere vraagtypen."],"Provide personalized feedback based on user responses.":["Geef gepersonaliseerde feedback op basis van gebruikersreacties."],"Automate scoring and lead segmentation for better insights.":["Automatiseer scoring en leadsegmentatie voor betere inzichten."],"Heading 1":["Kop 1"],"Heading 2":["Kop 2"],"Heading 3":["Kop 3"],"Heading 4":["Kop 4"],"Heading 5":["Kop 5"],"Heading 6":["Kop 6"],"Form settings saved.":["Formulierinstellingen opgeslagen."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["De invoerlimiet is afhankelijk van opgeslagen invoeren om inzendingen te tellen. Als in de nalevingsinstellingen \"Nooit invoergegevens opslaan na formulierinzending\" is ingeschakeld, wordt deze limiet niet afgedwongen. Schakel die optie uit, of verwijder de invoerlimiet, om deze functie te gebruiken."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Instellingen opgeslagen, maar postattributen (wachtwoord \/ titel \/ inhoud) konden niet worden bijgewerkt. Probeer opnieuw om ze te behouden."],"Failed to save form settings.":["Opslaan van formulierinstellingen mislukt."],"Saving\u2026":["Opslaan\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Wanneer ingeschakeld, zal dit formulier het IP-adres van de gebruiker, de naam van de browser en de naam van het apparaat niet opslaan in de inzendingen."],"Failed to save. Please try again.":["Opslaan mislukt. Probeer het opnieuw."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["reCAPTCHA API-sleutels voor de geselecteerde versie zijn niet geconfigureerd. Stel ze in bij de Globale Instellingen."],"Please select a reCAPTCHA version.":["Selecteer een reCAPTCHA-versie."],"hCaptcha API keys are not configured. Set them in Global Settings.":["hCaptcha API-sleutels zijn niet geconfigureerd. Stel ze in bij de Algemene Instellingen."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["Cloudflare Turnstile API-sleutels zijn niet geconfigureerd. Stel ze in bij de Globale Instellingen."],"Form data":["Formuliergegevens"],"Some fields need attention":["Sommige velden hebben aandacht nodig"],"Unsaved changes":["Niet-opgeslagen wijzigingen"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Een e-mailadres van de ontvanger en een onderwerpregel zijn vereist voordat deze melding kan worden opgeslagen. Corrigeer de gemarkeerde velden of verwijder uw wijzigingen om terug te gaan."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Je hebt niet-opgeslagen wijzigingen voor deze melding. Verwijder ze om terug te gaan, of blijf om ze op te slaan."],"Discard & go back":["Verwijderen & teruggaan"],"Stay & fix":["Blijf & repareer"],"Keep editing":["Blijf bewerken"],"Please provide a recipient email address.":["Geef alstublieft een e-mailadres van de ontvanger op."],"Please provide a subject line.":["Geef alstublieft een onderwerpregel op."],"Please provide a custom URL.":["Geef een aangepaste URL op."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Je hebt niet-opgeslagen wijzigingen. Verwijder ze om door te gaan, of blijf om je wijzigingen op te slaan."],"Discard & continue":["Weggooien & doorgaan"],"Show Entries":["Toon Inzendingen"],"Display this form\u2019s entries in a table on the frontend.":["Toon de inzendingen van dit formulier in een tabel aan de voorkant."],"Publish this form\u2019s submissions as a sortable, paginated table on any page \u2014 perfect for public directories, listings, and team dashboards, with per-role view access.":["Publiceer de inzendingen van dit formulier als een sorteerbare, gepagineerde tabel op elke pagina \u2014 perfect voor openbare directories, lijsten en teamdashboards, met toegang per rol."],"Embed a sortable, paginated entries table with a simple shortcode.":["Embed een sorteerbare, gepagineerde inzendingentabel met een eenvoudige shortcode."],"Choose exactly which fields appear as columns.":["Kies precies welke velden als kolommen verschijnen."],"Restrict who can view entries by user role.":["Beperk wie inzendingen kan bekijken op basis van gebruikersrol."],"Quill heading picker: default paragraph style\u0004Normal":["Normaal"]}}} \ No newline at end of file diff --git a/languages/sureforms-nl_NL.mo b/languages/sureforms-nl_NL.mo index fa29e858c2185379d9df6b98e60cdc89cede8f5e..1e806a21aa58770b2e55d90b00402b529b2d2b10 100644 GIT binary patch delta 72167 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 delta 70478 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" msgstr "Verwacht formaat voor e-mails - email@sureforms.com of John Doe " -#: admin/admin.php:710 -#: admin/admin.php:711 +#: admin/admin.php:725 +#: admin/admin.php:726 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -12322,8 +12325,8 @@ msgstr "Onbekend" #: inc/payments/admin/admin-handler.php:1501 #: inc/payments/admin/admin-handler.php:1505 #: inc/payments/admin/admin-handler.php:1509 -#: inc/payments/front-end.php:865 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:866 +#: inc/payments/front-end.php:1275 msgid "Guest User" msgstr "Gastgebruiker" @@ -12336,7 +12339,7 @@ msgid "Valid customer email is required for payments." msgstr "Een geldig klant-e-mailadres is vereist voor betalingen." #: inc/payments/front-end.php:115 -#: inc/payments/front-end.php:340 +#: inc/payments/front-end.php:341 #: inc/payments/stripe/admin-stripe-handler.php:861 #: inc/payments/stripe/payments-settings.php:349 #: inc/payments/stripe/payments-settings.php:474 @@ -12347,136 +12350,136 @@ msgid "Stripe is not connected." msgstr "Stripe is niet verbonden." #: inc/payments/front-end.php:121 -#: inc/payments/front-end.php:346 -#: inc/payments/front-end.php:634 -#: inc/payments/front-end.php:1153 +#: inc/payments/front-end.php:347 +#: inc/payments/front-end.php:635 +#: inc/payments/front-end.php:1154 msgid "Stripe secret key not found." msgstr "Stripe geheime sleutel niet gevonden." #. translators: %s: formatted payment amount -#: inc/payments/front-end.php:203 +#: inc/payments/front-end.php:204 #, php-format msgid "The payment amount (%s) is below the minimum allowed. Stripe only processes amounts above 50¢." msgstr "Het betalingsbedrag (%s) is lager dan het toegestane minimum. Stripe verwerkt alleen bedragen boven de 50¢." #. translators: %s: Field name -#: inc/payments/front-end.php:273 +#: inc/payments/front-end.php:274 #, php-format msgid "Missing required field: %s" msgstr "Ontbrekend verplicht veld: %s" -#: inc/payments/front-end.php:292 +#: inc/payments/front-end.php:293 msgid "Valid customer email is required for subscriptions." msgstr "Een geldig klant-e-mailadres is vereist voor abonnementen." -#: inc/payments/front-end.php:297 +#: inc/payments/front-end.php:298 msgid "Customer name is required for subscriptions." msgstr "Naam van de klant is vereist voor abonnementen." -#: inc/payments/front-end.php:314 +#: inc/payments/front-end.php:315 msgid "Amount must be greater than 0" msgstr "Bedrag moet groter zijn dan 0" -#: inc/payments/front-end.php:322 +#: inc/payments/front-end.php:323 msgid "Invalid billing interval" msgstr "Ongeldige factureringsinterval" -#: inc/payments/front-end.php:357 +#: inc/payments/front-end.php:358 msgid "Failed to create customer for subscription." msgstr "Het is niet gelukt om een klant aan te maken voor het abonnement." -#: inc/payments/front-end.php:390 -#: inc/payments/front-end.php:406 +#: inc/payments/front-end.php:391 +#: inc/payments/front-end.php:407 msgid "Failed to create subscription through middleware." msgstr "Het is niet gelukt om een abonnement via de middleware te maken." -#: inc/payments/front-end.php:411 +#: inc/payments/front-end.php:412 msgid "Empty response from subscription creation." msgstr "Lege reactie van abonnementscreatie." -#: inc/payments/front-end.php:416 +#: inc/payments/front-end.php:417 msgid "Invalid JSON response from subscription creation." msgstr "Ongeldige JSON-reactie van abonnementsaanmaak." -#: inc/payments/front-end.php:420 -#: inc/payments/front-end.php:424 +#: inc/payments/front-end.php:421 +#: inc/payments/front-end.php:425 msgid "Invalid subscription data." msgstr "Ongeldige abonnementsgegevens." -#: inc/payments/front-end.php:431 +#: inc/payments/front-end.php:432 msgid "Failed to create subscription." msgstr "Het is niet gelukt om het abonnement te maken." #. translators: %s: Error message -#: inc/payments/front-end.php:464 +#: inc/payments/front-end.php:465 #, php-format msgid "Unexpected error: %s" msgstr "Onverwachte fout: %s" -#: inc/payments/front-end.php:604 +#: inc/payments/front-end.php:605 #: inc/payments/stripe/admin-stripe-handler.php:103 #: inc/payments/stripe/admin-stripe-handler.php:165 #: inc/payments/stripe/admin-stripe-handler.php:471 msgid "Subscription ID not found." msgstr "Abonnement-ID niet gevonden." -#: inc/payments/front-end.php:623 +#: inc/payments/front-end.php:624 msgid "Customer ID not found for the payment." msgstr "Klant-ID niet gevonden voor de betaling." -#: inc/payments/front-end.php:651 +#: inc/payments/front-end.php:652 msgid "Failed to retrieve setup intent." msgstr "Het ophalen van de installatie-intentie is mislukt." -#: inc/payments/front-end.php:699 +#: inc/payments/front-end.php:700 msgid "Failed to update subscription." msgstr "Bijwerken van abonnement mislukt." -#: inc/payments/front-end.php:707 +#: inc/payments/front-end.php:708 msgid "Latest invoice not found on subscription." msgstr "Laatste factuur niet gevonden bij abonnement." -#: inc/payments/front-end.php:720 +#: inc/payments/front-end.php:721 msgid "Failed to retrieve invoice." msgstr "Het is niet gelukt om de factuur op te halen." -#: inc/payments/front-end.php:742 +#: inc/payments/front-end.php:743 msgid "Payment intent not found on invoice." msgstr "Betalingsintentie niet gevonden op factuur." -#: inc/payments/front-end.php:757 +#: inc/payments/front-end.php:758 msgid "Failed to confirm payment." msgstr "Betaling bevestigen is mislukt." -#: inc/payments/front-end.php:773 +#: inc/payments/front-end.php:774 msgid "Failed to retrieve subscription." msgstr "Ophalen van abonnement mislukt." -#: inc/payments/front-end.php:788 +#: inc/payments/front-end.php:789 msgid "Subscription not found for the payment." msgstr "Abonnement niet gevonden voor de betaling." #. translators: %d: User ID -#: inc/payments/front-end.php:863 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:864 +#: inc/payments/front-end.php:1275 #, php-format msgid "User ID: %d" msgstr "Gebruikers-ID: %d" #. translators: %s: Invoice status -#: inc/payments/front-end.php:871 +#: inc/payments/front-end.php:872 #, php-format msgid "Invoice Status: %s" msgstr "Factuurstatus: %s" #. translators: Title for subscription verification log -#: inc/payments/front-end.php:878 +#: inc/payments/front-end.php:879 msgid "Subscription Verification" msgstr "Abonnementsverificatie" #. translators: %s: Subscription ID #. translators: %s: Stripe subscription ID -#: inc/payments/front-end.php:882 +#: inc/payments/front-end.php:883 #: inc/payments/stripe/admin-stripe-handler.php:185 #: inc/payments/stripe/admin-stripe-handler.php:487 #: inc/payments/stripe/stripe-webhook.php:572 @@ -12491,8 +12494,8 @@ msgstr "Abonnement-ID: %s" #. translators: %s: payment gateway name (e.g., Stripe) #. translators: %s: payment gateway #. translators: %s: Payment gateway name (e.g., Stripe). -#: inc/payments/front-end.php:884 -#: inc/payments/front-end.php:1285 +#: inc/payments/front-end.php:885 +#: inc/payments/front-end.php:1286 #: inc/payments/stripe/admin-stripe-handler.php:190 #: inc/payments/stripe/admin-stripe-handler.php:492 #: inc/payments/stripe/admin-stripe-handler.php:667 @@ -12506,13 +12509,13 @@ msgid "Payment Gateway: %s" msgstr "Betalingsgateway: %s" #. translators: %s: Payment Intent ID -#: inc/payments/front-end.php:886 +#: inc/payments/front-end.php:887 #, php-format msgid "Payment Intent ID: %s" msgstr "Betalingsintentie-ID: %s" #. translators: %s: Charge ID -#: inc/payments/front-end.php:888 +#: inc/payments/front-end.php:889 #: inc/payments/stripe/stripe-webhook.php:1084 #, php-format msgid "Charge ID: %s" @@ -12520,7 +12523,7 @@ msgstr "Betaal-ID: %s" #. translators: %s: Subscription Status #. translators: %s: subscription status -#: inc/payments/front-end.php:890 +#: inc/payments/front-end.php:891 #: inc/payments/stripe/admin-stripe-handler.php:195 #: inc/payments/stripe/admin-stripe-handler.php:497 #, php-format @@ -12528,7 +12531,7 @@ msgid "Subscription Status: %s" msgstr "Abonnementsstatus: %s" #. translators: %s: Customer ID -#: inc/payments/front-end.php:892 +#: inc/payments/front-end.php:893 #: inc/payments/stripe/stripe-webhook.php:1174 #, php-format msgid "Customer ID: %s" @@ -12536,8 +12539,8 @@ msgstr "Klant-ID: %s" #. translators: 1: Amount, 2: Currency #. translators: %1$s: amount, %2$s: currency. -#: inc/payments/front-end.php:894 -#: inc/payments/front-end.php:1287 +#: inc/payments/front-end.php:895 +#: inc/payments/front-end.php:1288 #: inc/payments/stripe/stripe-webhook.php:1089 #: inc/payments/stripe/stripe-webhook.php:1166 #, php-format @@ -12546,34 +12549,34 @@ msgstr "Bedrag: %1$s %2$s" #. translators: %s: Payment mode (e.g. Live or Test) #. translators: %s: payment mode -#: inc/payments/front-end.php:897 -#: inc/payments/front-end.php:1292 +#: inc/payments/front-end.php:898 +#: inc/payments/front-end.php:1293 #, php-format msgid "Mode: %s" msgstr "Modus: %s" -#: inc/payments/front-end.php:931 +#: inc/payments/front-end.php:932 msgid "Failed to verify subscription." msgstr "Verificatie van abonnement mislukt." -#: inc/payments/front-end.php:1186 -#: inc/payments/front-end.php:1204 -#: inc/payments/front-end.php:1212 -#: inc/payments/front-end.php:1219 +#: inc/payments/front-end.php:1187 +#: inc/payments/front-end.php:1205 +#: inc/payments/front-end.php:1213 +#: inc/payments/front-end.php:1220 msgid "Failed to retrieve payment intent." msgstr "Het is niet gelukt om de betalingsintentie op te halen." -#: inc/payments/front-end.php:1227 +#: inc/payments/front-end.php:1228 msgid "Payment was not confirmed successfully." msgstr "Betaling werd niet succesvol bevestigd." -#: inc/payments/front-end.php:1279 +#: inc/payments/front-end.php:1280 msgid "Payment Verification" msgstr "Betalingsverificatie" #. translators: %s: Stripe transaction ID #. translators: %s: Charge ID -#: inc/payments/front-end.php:1283 +#: inc/payments/front-end.php:1284 #: inc/payments/stripe/stripe-webhook.php:1162 #, php-format msgid "Transaction ID: %s" @@ -12581,18 +12584,18 @@ msgstr "Transactie-ID: %s" #. translators: %s: payment status #. translators: %s: Status -#: inc/payments/front-end.php:1289 +#: inc/payments/front-end.php:1290 #: inc/payments/stripe/stripe-webhook.php:576 #: inc/payments/stripe/stripe-webhook.php:1168 #, php-format msgid "Status: %s" msgstr "Status: %s" -#: inc/payments/front-end.php:1395 +#: inc/payments/front-end.php:1396 msgid "Failed to create Stripe customer." msgstr "Het is niet gelukt om een Stripe-klant aan te maken." -#: inc/payments/front-end.php:1459 +#: inc/payments/front-end.php:1460 msgid "Failed to create Stripe guest customer." msgstr "Het is niet gelukt om een Stripe-gastklant aan te maken." @@ -13457,7 +13460,7 @@ msgstr "Aangemaakt via abonnementsfactureringscyclus" msgid "Edit this form" msgstr "Bewerk dit formulier" -#: inc/global-settings/global-settings.php:529 +#: inc/global-settings/global-settings.php:531 #: inc/migrator/base-migrator.php:549 #: inc/post-types.php:1081 msgid "Your form has been submitted successfully. We'll review your details and get back to you soon." @@ -15080,7 +15083,7 @@ msgid "Failed to create duplicate form." msgstr "Het is niet gelukt om een duplicaat van het formulier te maken." #: inc/payments/front-end.php:98 -#: inc/payments/front-end.php:303 +#: inc/payments/front-end.php:304 #: inc/payments/payment-helper.php:596 msgid "Invalid form configuration." msgstr "Ongeldige formulierconfiguratie." @@ -15156,7 +15159,7 @@ msgstr "Waarde van het variabele bedrag veld is vereist." msgid "Payment amount below minimum. Minimum: %1$s, received %2$s." msgstr "Betalingsbedrag onder het minimum. Minimum: %1$s, ontvangen %2$s." -#: inc/rest-api.php:1839 +#: inc/rest-api.php:1851 msgid " (Copy)" msgstr "(Kopie)" @@ -15355,12 +15358,12 @@ msgid "Stripe Settings" msgstr "Stripe-instellingen" #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version. -#: admin/admin.php:1514 +#: admin/admin.php:1531 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version." msgstr "SureForms %1$s vereist minimaal %2$s %3$s om goed te functioneren. Werk alstublieft bij naar de nieuwste versie." -#: admin/admin.php:1527 +#: admin/admin.php:1544 msgid "Update Now" msgstr "Nu bijwerken" @@ -15408,8 +15411,8 @@ msgstr "Positie van het valutateken" msgid "Select the position of the currency symbol relative to the amount." msgstr "Selecteer de positie van het valutasymbool ten opzichte van het bedrag." -#: admin/admin.php:653 -#: admin/admin.php:654 +#: admin/admin.php:668 +#: admin/admin.php:669 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -15423,40 +15426,40 @@ msgstr "Selecteer de positie van het valutasymbool ten opzichte van het bedrag." msgid "Learn" msgstr "Leren" -#: admin/admin.php:1640 +#: admin/admin.php:1657 msgid "Amazing! SureForms is powering your forms and submissions - let's keep growing together!" msgstr "Geweldig! SureForms ondersteunt je formulieren en inzendingen - laten we samen blijven groeien!" -#: admin/admin.php:1641 +#: admin/admin.php:1658 msgid "If SureForms has been helpful, would you mind taking a moment to leave a 5-star review on WordPress.org?" msgstr "Als SureForms nuttig is geweest, zou u dan een moment willen nemen om een 5-sterrenbeoordeling achter te laten op WordPress.org?" -#: admin/admin.php:1644 -#: admin/admin.php:1688 +#: admin/admin.php:1661 +#: admin/admin.php:1705 msgid "Maybe later" msgstr "Misschien later" -#: admin/admin.php:1645 +#: admin/admin.php:1662 msgid "I already did" msgstr "Ik heb het al gedaan" -#: admin/admin.php:1684 +#: admin/admin.php:1701 msgid "SureForms is ready to power your forms — explore what's possible!" msgstr "SureForms is klaar om uw formulieren van stroom te voorzien — ontdek wat er mogelijk is!" -#: admin/admin.php:1685 +#: admin/admin.php:1702 msgid "Manage your forms, track submissions, and discover features like AI Form Builder, payment integrations, and more from the SureForms dashboard." msgstr "Beheer uw formulieren, volg inzendingen en ontdek functies zoals AI Form Builder, betalingsintegraties en meer vanaf het SureForms-dashboard." -#: admin/admin.php:1687 +#: admin/admin.php:1704 msgid "Go to Dashboard" msgstr "Ga naar Dashboard" -#: admin/admin.php:1689 +#: admin/admin.php:1706 msgid "I already know" msgstr "Ik weet het al" -#: admin/admin.php:1769 +#: admin/admin.php:1786 msgid "Invalid parameters." msgstr "Ongeldige parameters." @@ -15850,7 +15853,7 @@ msgstr "Dit formulier is nog niet beschikbaar. Kom terug na de geplande starttij #: inc/form-submit.php:1015 #: inc/migrator/bootstrap.php:357 #: inc/payments/front-end.php:78 -#: inc/payments/front-end.php:263 +#: inc/payments/front-end.php:264 #: inc/rest-api.php:124 #: inc/rest-api.php:183 #: inc/rest-api.php:349 @@ -15863,8 +15866,8 @@ msgstr "Dit formulier is nog niet beschikbaar. Kom terug na de geplande starttij #: inc/rest-api.php:742 #: inc/rest-api.php:775 #: inc/rest-api.php:914 -#: inc/rest-api.php:987 -#: inc/rest-api.php:1047 +#: inc/rest-api.php:999 +#: inc/rest-api.php:1059 #: inc/single-form-settings/form-settings-api.php:89 msgid "Security verification failed. Please refresh the page and try again." msgstr "Beveiligingsverificatie mislukt. Vernieuw de pagina en probeer het opnieuw." @@ -16163,28 +16166,28 @@ msgstr "Ongeldige actie. Gebruik \"gelezen\" of \"ongelezen\"." msgid "Invalid action. Use \"trash\" or \"restore\"." msgstr "Ongeldige actie. Gebruik \"prullenbak\" of \"herstellen\"." -#: inc/rest-api.php:1061 +#: inc/rest-api.php:1073 msgid "Select at least one form and specify an action." msgstr "Selecteer ten minste één formulier en specificeer een actie." -#: inc/rest-api.php:1076 +#: inc/rest-api.php:1088 msgid "Form not found or is not a valid form type." msgstr "Formulier niet gevonden of is geen geldig formulier type." -#: inc/rest-api.php:1088 +#: inc/rest-api.php:1100 msgid "This form is already in the trash." msgstr "Dit formulier bevindt zich al in de prullenbak." -#: inc/rest-api.php:1099 +#: inc/rest-api.php:1111 msgid "This form is not in the trash." msgstr "Dit formulier bevindt zich niet in de prullenbak." -#: inc/rest-api.php:1135 +#: inc/rest-api.php:1147 msgid "Invalid action." msgstr "Ongeldige actie." #. translators: %s: action name -#: inc/rest-api.php:1150 +#: inc/rest-api.php:1162 #, php-format msgid "Failed to %s this form. Please try again." msgstr "Het is niet gelukt om dit formulier te %s. Probeer het alstublieft opnieuw." @@ -16202,32 +16205,32 @@ msgid "Enter a valid email address." msgstr "Voer een geldig e-mailadres in." #. translators: %s represents the minimum acceptable value -#: inc/translatable.php:97 +#: inc/translatable.php:103 #, php-format msgid "Minimum value is %s." msgstr "Minimale waarde is %s." #. translators: %s represents the maximum acceptable value -#: inc/translatable.php:100 +#: inc/translatable.php:106 #, php-format msgid "Maximum value is %s." msgstr "Maximale waarde is %s." #. translators: %s represents the minimum number of options to select -#: inc/translatable.php:103 #: inc/translatable.php:109 +#: inc/translatable.php:115 #, php-format msgid "Select at least %s options." msgstr "Selecteer ten minste %s opties." #. translators: %s represents the maximum number of options that can be selected -#: inc/translatable.php:106 #: inc/translatable.php:112 +#: inc/translatable.php:118 #, php-format msgid "You can select up to %s options." msgstr "U kunt maximaal %s opties selecteren." -#: inc/translatable.php:151 +#: inc/translatable.php:159 msgid "This form is now closed as we have reached the maximum number of entries." msgstr "Dit formulier is nu gesloten omdat we het maximale aantal inzendingen hebben bereikt." @@ -16423,7 +16426,7 @@ msgid "Duplicate form?" msgstr "Formulier dupliceren?" #: assets/build/formSubmit.js:2 -#: assets/js/unminified/form-submit.js:487 +#: assets/js/unminified/form-submit.js:495 msgid "An error occurred while submitting your form. Please try again." msgstr "Er is een fout opgetreden bij het indienen van uw formulier. Probeer het alstublieft opnieuw." @@ -16750,35 +16753,35 @@ msgstr "Haal de globale instellingen van SureForms op. Optioneel filteren op cat msgid "Setting categories to retrieve. Omit for all categories." msgstr "Categorieën instellen om op te halen. Laat weg voor alle categorieën." -#: inc/abilities/settings/update-global-settings.php:106 +#: inc/abilities/settings/update-global-settings.php:108 msgid "Update Global Settings" msgstr "Globale instellingen bijwerken" -#: inc/abilities/settings/update-global-settings.php:107 +#: inc/abilities/settings/update-global-settings.php:109 msgid "Update SureForms global settings for a specific category: general, validation-messages, email-summary, or security." msgstr "Werk de globale instellingen van SureForms bij voor een specifieke categorie: algemeen, validatieberichten, e-mailoverzicht of beveiliging." -#: inc/abilities/settings/update-global-settings.php:140 +#: inc/abilities/settings/update-global-settings.php:142 msgid "The settings category to update." msgstr "De instellingen categorie om bij te werken." -#: inc/abilities/settings/update-global-settings.php:145 +#: inc/abilities/settings/update-global-settings.php:147 msgid "Key-value pairs of settings to update." msgstr "Sleutel-waardeparen van instellingen om bij te werken." -#: inc/abilities/settings/update-global-settings.php:181 +#: inc/abilities/settings/update-global-settings.php:183 msgid "Settings category is required." msgstr "Categorie-instellingen zijn vereist." -#: inc/abilities/settings/update-global-settings.php:189 +#: inc/abilities/settings/update-global-settings.php:191 msgid "Settings data is required." msgstr "Instellingen gegevens zijn vereist." -#: inc/abilities/settings/update-global-settings.php:200 +#: inc/abilities/settings/update-global-settings.php:202 msgid "No valid settings keys provided for this category." msgstr "Geen geldige instellingssleutels opgegeven voor deze categorie." -#: inc/abilities/settings/update-global-settings.php:226 +#: inc/abilities/settings/update-global-settings.php:228 msgid "Invalid settings category." msgstr "Ongeldige instellingen categorie." @@ -16794,19 +16797,19 @@ msgstr "Webhook-geheim niet geconfigureerd." msgid "Invalid webhook signature." msgstr "Ongeldige webhook-handtekening." -#: admin/admin.php:492 -#: admin/admin.php:1085 +#: admin/admin.php:507 +#: admin/admin.php:1101 #: assets/build/formEditor.js:172 msgid "Quizzes" msgstr "Quizzen" -#: admin/admin.php:491 +#: admin/admin.php:506 msgid "Quiz Entries" msgstr "Quizinzendingen" -#: admin/admin.php:494 -#: admin/admin.php:527 -#: admin/admin.php:560 +#: admin/admin.php:509 +#: admin/admin.php:542 +#: admin/admin.php:575 #: assets/build/settings.js:172 msgid "New" msgstr "Nieuw" @@ -17324,43 +17327,43 @@ msgstr "Perfect voor educatie, training en leuke trivia" msgid "Select this to create a quiz with scored questions and graded results." msgstr "Selecteer dit om een quiz te maken met gescoorde vragen en beoordeelde resultaten." -#: admin/admin.php:524 -#: admin/admin.php:525 -#: admin/admin.php:1090 +#: admin/admin.php:539 +#: admin/admin.php:540 +#: admin/admin.php:1106 msgid "Survey Reports" msgstr "Onderzoeksrapporten" -#: inc/frontend-assets.php:301 +#: inc/frontend-assets.php:304 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 1" msgstr "Kop 1" -#: inc/frontend-assets.php:302 +#: inc/frontend-assets.php:305 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 2" msgstr "Kop 2" -#: inc/frontend-assets.php:303 +#: inc/frontend-assets.php:306 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 3" msgstr "Kop 3" -#: inc/frontend-assets.php:304 +#: inc/frontend-assets.php:307 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 4" msgstr "Kop 4" -#: inc/frontend-assets.php:305 +#: inc/frontend-assets.php:308 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 5" msgstr "Kop 5" -#: inc/frontend-assets.php:306 +#: inc/frontend-assets.php:309 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 6" @@ -17694,15 +17697,15 @@ msgstr "Ongeldige post." msgid "You cannot edit this post." msgstr "Je kunt dit bericht niet bewerken." -#: inc/ai-form-builder/ai-form-builder.php:96 +#: inc/ai-form-builder/ai-form-builder.php:108 msgid "The AI did not return a form. Please refine your prompt and try again." msgstr "De AI heeft geen formulier geretourneerd. Gelieve uw prompt te verfijnen en het opnieuw te proberen." -#: inc/ai-form-builder/ai-form-builder.php:104 +#: inc/ai-form-builder/ai-form-builder.php:116 msgid "The AI response is missing a form title. Please try again." msgstr "De AI-reactie mist een formulier titel. Probeer het opnieuw." -#: inc/ai-form-builder/ai-form-builder.php:115 +#: inc/ai-form-builder/ai-form-builder.php:127 #: inc/ai-form-builder/field-mapping.php:69 msgid "The AI was unable to generate form fields. Please try again." msgstr "De AI kon geen formuliervelden genereren. Probeer het alstublieft opnieuw." @@ -17743,7 +17746,7 @@ msgstr "Eenmalige betaling" msgid "Choose payment type" msgstr "Kies betaalmethode" -#: inc/payments/front-end.php:333 +#: inc/payments/front-end.php:334 msgid "Billing interval does not match the form configuration." msgstr "Het factureringsinterval komt niet overeen met de formulierconfiguratie." @@ -17887,15 +17890,15 @@ msgstr "De AI-reactie was leeg. Gelieve uw prompt te verfijnen en het opnieuw te msgid "Unable to build form fields from the AI response." msgstr "Kan formuliervelden niet opbouwen vanuit de AI-reactie." -#: admin/admin.php:557 -#: admin/admin.php:558 -#: admin/admin.php:1095 +#: admin/admin.php:572 +#: admin/admin.php:573 +#: admin/admin.php:1111 msgid "Partial Entries" msgstr "Gedeeltelijke invoer" #: inc/global-settings/global-settings-defaults.php:260 -#: inc/global-settings/global-settings.php:425 -#: inc/global-settings/global-settings.php:613 +#: inc/global-settings/global-settings.php:427 +#: inc/global-settings/global-settings.php:615 #: assets/build/settings.js:172 msgid "This form is now closed as we've received all the entries." msgstr "Dit formulier is nu gesloten omdat we alle inzendingen hebben ontvangen." @@ -17904,16 +17907,16 @@ msgstr "Dit formulier is nu gesloten omdat we alle inzendingen hebben ontvangen. msgid "Invalid settings tab." msgstr "Ongeldig instellingen tabblad." -#: inc/global-settings/global-settings.php:481 +#: inc/global-settings/global-settings.php:483 #: assets/build/settings.js:172 msgid "Thank you for contacting us! We will be in touch with you shortly." msgstr "Bedankt dat u contact met ons heeft opgenomen! We nemen binnenkort contact met u op." -#: inc/rest-api.php:1115 +#: inc/rest-api.php:1127 msgid "Use the restore action to recover a trashed form before switching it to draft." msgstr "Gebruik de herstelactie om een verwijderde formulier te herstellen voordat u het naar concept schakelt." -#: inc/rest-api.php:1120 +#: inc/rest-api.php:1132 msgid "This form is already a draft." msgstr "Dit formulier is al een concept." @@ -18760,6 +18763,72 @@ msgstr "Het betalingsbedrag kon niet worden geverifieerd voor dit formulier. Bew msgid "Cancellation is not supported for this payment gateway." msgstr "Annulering wordt niet ondersteund voor deze betalingsgateway." +#: admin/admin.php:1988 +msgid "SureForms AI Quick Draft" +msgstr "SureForms AI Snelle Schets" + +#: admin/admin.php:2007 +msgid "Describe the form and let SureForms AI generate it for you." +msgstr "Beschrijf het formulier en laat SureForms AI het voor je genereren." + +#: admin/admin.php:2010 +msgid "Describe your form" +msgstr "Beschrijf je formulier" + +#: admin/admin.php:2017 +msgid "Example: Create a contact form with name, email, phone, and message fields." +msgstr "Voorbeeld: Maak een contactformulier met velden voor naam, e-mail, telefoon en bericht." + +#: admin/admin.php:2058 +msgid "Redirecting..." +msgstr "Omleiden..." + +#: inc/ai-form-builder/ai-form-builder.php:57 +msgid "No prompt was supplied." +msgstr "Er is geen prompt opgegeven." + +#: inc/ai-form-builder/ai-form-builder.php:64 +msgid "The prompt is too long. Please shorten it and try again." +msgstr "De prompt is te lang. Verkort deze en probeer het opnieuw." + +#. translators: %s: maximum characters allowed before the @ symbol. +#: inc/field-validation.php:316 +#: inc/translatable.php:97 +#, php-format +msgid "The part before @ may not exceed %s characters." +msgstr "Het gedeelte voor @ mag niet meer dan %s tekens bevatten." + +#. translators: %s: maximum characters allowed after the @ symbol. +#: inc/field-validation.php:322 +#: inc/translatable.php:100 +#, php-format +msgid "The part after @ may not exceed %s characters." +msgstr "Het gedeelte na @ mag niet meer dan %s tekens bevatten." + +#: assets/build/formEditor.js:172 +msgid "Show Entries" +msgstr "Toon Inzendingen" + +#: assets/build/formEditor.js:172 +msgid "Display this form’s entries in a table on the frontend." +msgstr "Toon de inzendingen van dit formulier in een tabel aan de voorkant." + +#: assets/build/formEditor.js:172 +msgid "Publish this form’s submissions as a sortable, paginated table on any page — perfect for public directories, listings, and team dashboards, with per-role view access." +msgstr "Publiceer de inzendingen van dit formulier als een sorteerbare, gepagineerde tabel op elke pagina — perfect voor openbare directories, lijsten en teamdashboards, met toegang per rol." + +#: assets/build/formEditor.js:172 +msgid "Embed a sortable, paginated entries table with a simple shortcode." +msgstr "Embed een sorteerbare, gepagineerde inzendingentabel met een eenvoudige shortcode." + +#: assets/build/formEditor.js:172 +msgid "Choose exactly which fields appear as columns." +msgstr "Kies precies welke velden als kolommen verschijnen." + +#: assets/build/formEditor.js:172 +msgid "Restrict who can view entries by user role." +msgstr "Beperk wie inzendingen kan bekijken op basis van gebruikersrol." + #: inc/post-types.php:226 msgctxt "post type general name" msgid "Forms" @@ -19190,34 +19259,34 @@ msgctxt "block keyword" msgid "color" msgstr "kleur" -#: inc/frontend-assets.php:300 +#: inc/frontend-assets.php:303 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgctxt "Quill heading picker: default paragraph style" msgid "Normal" msgstr "Normaal" -#: inc/frontend-assets.php:307 +#: inc/frontend-assets.php:310 msgctxt "Quill link tooltip label" msgid "Visit URL:" msgstr "Bezoek URL:" -#: inc/frontend-assets.php:308 +#: inc/frontend-assets.php:311 msgctxt "Quill link tooltip label" msgid "Enter link:" msgstr "Voer link in:" -#: inc/frontend-assets.php:309 +#: inc/frontend-assets.php:312 msgctxt "Quill link tooltip action" msgid "Edit" msgstr "Bewerken" -#: inc/frontend-assets.php:310 +#: inc/frontend-assets.php:313 msgctxt "Quill link tooltip action" msgid "Save" msgstr "Opslaan" -#: inc/frontend-assets.php:311 +#: inc/frontend-assets.php:314 msgctxt "Quill link tooltip action" msgid "Remove" msgstr "Verwijderen" diff --git a/languages/sureforms-pl_PL-1cb9ecd067cd971ff5d9db0b4dae2891.json b/languages/sureforms-pl_PL-1cb9ecd067cd971ff5d9db0b4dae2891.json index e910a40c3..492957551 100644 --- a/languages/sureforms-pl_PL-1cb9ecd067cd971ff5d9db0b4dae2891.json +++ b/languages/sureforms-pl_PL-1cb9ecd067cd971ff5d9db0b4dae2891.json @@ -1 +1 @@ -{"translation-revision-date":"2025-01-02T08:44:41+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Status"],"Form":["Formularz"],"Fields":["Pola"],"Image":["Obraz"],"Activated":["Aktywowany"],"Activate":["Aktywuj"],"Submit":["Prze\u015blij"],"Global Settings":["Ustawienia globalne"],"Form Title":["Tytu\u0142 formularza"],"Edit":["Edytuj"],"Please enter a valid URL.":["Prosz\u0119 wprowadzi\u0107 prawid\u0142owy adres URL."],"Desktop":["Pulpit"],"Medium":["\u015aredni"],"Mobile":["Telefon kom\u00f3rkowy"],"Repeat":["Powt\u00f3rz"],"Scroll":["Przewi\u0144"],"Signature":["Podpis"],"Tablet":["Tablet"],"Upload":["Prze\u015blij"],"Basic":["Podstawowy"],"Form Settings":["Ustawienia formularza"],"General":["Og\u00f3lny"],"Style":["Styl"],"Advanced":["Zaawansowany"],"No tags available":["Brak dost\u0119pnych tag\u00f3w"],"Device":["Urz\u0105dzenie"],"Select Shortcodes":["Wybierz skr\u00f3ty"],"Page Break Label":["Etykieta podzia\u0142u strony"],"Next":["Dalej"],"Back":["Wstecz"],"Reset":["Resetuj"],"Generic tags":["Og\u00f3lne tagi"],"Pixel":["Piksel"],"Em":["Em"],"Select Units":["Wybierz jednostki"],"%s units":["%s jednostki"],"Margin":["Margines"],"None":["Brak"],"Custom":["Niestandardowy"],"Please add a option props to MultiButtonsControl":["Prosz\u0119 doda\u0107 opcj\u0119 props do MultiButtonsControl"],"Confirm":["Potwierd\u017a"],"Cancel":["Anuluj"],"Processing\u2026":["Przetwarzanie\u2026"],"Select Video":["Wybierz wideo"],"Change Video":["Zmie\u0144 wideo"],"Select Lottie Animation":["Wybierz animacj\u0119 Lottie"],"Change Lottie Animation":["Zmie\u0144 animacj\u0119 Lottie"],"Upload SVG":["Prze\u015blij SVG"],"Change SVG":["Zmie\u0144 SVG"],"Select Image":["Wybierz obraz"],"Change Image":["Zmie\u0144 obraz"],"Upload SVG?":["Przes\u0142a\u0107 SVG?"],"Upload SVG can be potentially risky. Are you sure?":["Przesy\u0142anie plik\u00f3w SVG mo\u017ce by\u0107 potencjalnie ryzykowne. Czy jeste\u015b pewien?"],"Upload Anyway":["Prze\u015blij mimo to"],"Full Width":["Pe\u0142na szeroko\u015b\u0107"],"Install":["Zainstaluj"],"Plugin Installation failed, Please try again later.":["Instalacja wtyczki nie powiod\u0142a si\u0119, spr\u00f3buj ponownie p\u00f3\u017aniej."],"Plugin activation failed, Please try again later.":["Aktywacja wtyczki nie powiod\u0142a si\u0119, spr\u00f3buj ponownie p\u00f3\u017aniej."],"Integrations":["Integracje"],"%s Removed from Quick Action Bar.":["%s usuni\u0119to z paska szybkich akcji."],"Add to Quick Action Bar":["Dodaj do paska szybkich akcji"],"%s Added to Quick Action Bar.":["%s dodano do paska szybkich akcji."],"Already Present in Quick Action Bar":["Ju\u017c obecne na pasku szybkich akcji"],"No results found.":["Nie znaleziono wynik\u00f3w."],"data object is empty":["obiekt danych jest pusty"],"Add blocks to Quick Action Bar":["Dodaj bloki do paska szybkich akcji"],"Re-arrange block inside Quick Action Bar":["Przestaw blok wewn\u0105trz paska szybkich akcji"],"Upgrade":["Aktualizacja"],"Connecting\u2026":["\u0141\u0105czenie\u2026"],"Install & Activate":["Zainstaluj i aktywuj"],"Compliance Settings":["Ustawienia zgodno\u015bci"],"Enable GDPR Compliance":["W\u0142\u0105cz zgodno\u015b\u0107 z RODO"],"Never store entry data after form submission":["Nigdy nie przechowuj danych wej\u015bciowych po przes\u0142aniu formularza"],"When enabled this form will never store Entries.":["Gdy jest w\u0142\u0105czony, ten formularz nigdy nie b\u0119dzie przechowywa\u0142 wpis\u00f3w."],"Automatically delete entries":["Automatycznie usu\u0144 wpisy"],"When enabled this form will automatically delete entries after a certain period of time.":["Po w\u0142\u0105czeniu ten formularz automatycznie usunie wpisy po okre\u015blonym czasie."],"Entries older than the days set will be deleted automatically.":["Wpisy starsze ni\u017c ustawione dni zostan\u0105 usuni\u0119te automatycznie."],"Custom CSS":["Niestandardowy CSS"],"The following CSS styles added below will only apply to this form container.":["Nast\u0119puj\u0105ce style CSS dodane poni\u017cej b\u0119d\u0105 dotyczy\u0107 tylko tego kontenera formularza."],"Visual":["Wizualny"],"HTML":["HTML"],"All Data":["Wszystkie dane"],"Add Shortcode":["Dodaj shortcode"],"Form input tags":["Tagi wej\u015bciowe formularza"],"Comma separated values are also accepted.":["Warto\u015bci oddzielone przecinkami s\u0105 r\u00f3wnie\u017c akceptowane."],"Email Notification":["Powiadomienie e-mail"],"Name":["Imi\u0119"],"Send Email To":["Wy\u015blij e-mail do"],"Subject":["Temat"],"CC":["DW"],"BCC":["UDW"],"Reply To":["Odpowiedz do"],"Add Notification":["Dodaj powiadomienie"],"Add Key":["Dodaj klucz"],"Add Value":["Dodaj warto\u015b\u0107"],"Add":["Dodaj"],"Confirmation Message":["Wiadomo\u015b\u0107 potwierdzaj\u0105ca"],"After Form Submission":["Po przes\u0142aniu formularza"],"Hide Form":["Ukryj formularz"],"Reset Form":["Zresetuj formularz"],"Custom URL":["Niestandardowy URL"],"Add Query Parameters":["Dodaj parametry zapytania"],"Select if you want to add key-value pairs for form fields to include in query parameters":["Wybierz, je\u015bli chcesz doda\u0107 pary klucz-warto\u015b\u0107 dla p\u00f3l formularza do uwzgl\u0119dnienia w parametrach zapytania"],"Query Parameters":["Parametry zapytania"],"Please select a page.":["Prosz\u0119 wybra\u0107 stron\u0119."],"Suggestion: URL should use HTTPS":["Sugestia: URL powinien u\u017cywa\u0107 HTTPS"],"Success Message":["Komunikat o sukcesie"],"Redirect":["Przekieruj"],"Redirect to":["Przekieruj do"],"Page":["Strona"],"Form Confirmation":["Potwierdzenie formularza"],"Confirmation Type":["Typ potwierdzenia"],"Use Labels as Placeholders":["U\u017cyj etykiet jako symboli zast\u0119pczych"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["Powy\u017csze ustawienie umie\u015bci etykiety wewn\u0105trz p\u00f3l jako podpowiedzi (tam, gdzie to mo\u017cliwe). To ustawienie dzia\u0142a tylko na \u017cywej stronie, a nie w podgl\u0105dzie edytora."],"Page Break":["Podzia\u0142 strony"],"Show Labels":["Poka\u017c etykiety"],"First Page Label":["Etykieta pierwszej strony"],"Progress Indicator":["Wska\u017anik post\u0119pu"],"Progress Bar":["Pasek post\u0119pu"],"Connector":["Z\u0142\u0105cze"],"Steps":["Kroki"],"Next Button Text":["Tekst przycisku \"Dalej\""],"Back Button Text":["Tekst przycisku powrotu"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["Czy na pewno chcesz zamkn\u0105\u0107? Twoje niezapisane zmiany zostan\u0105 utracone, poniewa\u017c masz pewne b\u0142\u0119dy walidacji."],"There are few unsaved changes. Please save your changes to reflect the updates.":["Jest kilka niezapisanych zmian. Prosz\u0119 zapisa\u0107 zmiany, aby odzwierciedli\u0107 aktualizacje."],"Form Behavior":["Zachowanie formularza"],"Clear":["Wyczy\u015b\u0107"],"Select Color":["Wybierz kolor"],"Primary Color":["Kolor podstawowy"],"Text Color":["Kolor tekstu"],"Text Color on Primary":["Kolor tekstu na g\u0142\u00f3wnym"],"Field Spacing":["Odst\u0119py mi\u0119dzy polami"],"Small":["Ma\u0142y"],"Large":["Du\u017cy"],"Left":["Lewo"],"Center":["Centrum"],"Right":["Prawo"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Niewidoczna"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Wyb\u00f3r daty"],"Time Picker":["Wyb\u00f3r czasu"],"Hidden":["Ukryty"],"Slider":["Suwak"],"Rating":["Ocena"],"Upgrade to Unlock These Fields":["Zaktualizuj, aby odblokowa\u0107 te pola"],"Add Block":["Dodaj blok"],"Customize with SureForms":["Dostosuj za pomoc\u0105 SureForms"],"Page break":["Podzia\u0142 strony"],"Previous":["Poprzedni"],"Thank you":["Dzi\u0119kuj\u0119"],"Form submitted successfully!":["Formularz zosta\u0142 pomy\u015blnie przes\u0142any!"],"Instant Form":["Formularz natychmiastowy"],"Enable Instant Form":["W\u0142\u0105cz natychmiastowy formularz"],"Enable Preview":["W\u0142\u0105cz podgl\u0105d"],"Show Title":["Tytu\u0142 Pokazu"],"Site Logo":["Logo strony"],"Banner Background":["T\u0142o banera"],"Color":["Kolor"],"Upload Image":["Prze\u015blij obraz"],"Background Color":["Kolor t\u0142a"],"Use banner as page background":["U\u017cyj baneru jako t\u0142a strony"],"Form Width":["Szeroko\u015b\u0107 formularza"],"URL":["URL"],"URL Slug":["Slug URL"],"The last part of the URL.":["Ostatnia cz\u0119\u015b\u0107 adresu URL."],"Learn more.":["Dowiedz si\u0119 wi\u0119cej."],"SureForms Description":["Opis SureForms"],"Form Options":["Opcje formularza"],"Form Shortcode":["Kod skr\u00f3tu formularza"],"Paste this shortcode on the page or post to render this form.":["Wklej ten kr\u00f3tki kod na stron\u0119 lub post, aby wy\u015bwietli\u0107 ten formularz."],"Spam Protection":["Ochrona przed spamem"],"Auto":["Samoch\u00f3d"],"Normal":["Normalny"],"%":["%"],"Top":["G\u00f3ra"],"Bottom":["D\u00f3\u0142"],"Solid":["Solidny"],"Width":["Szeroko\u015b\u0107"],"Size":["Rozmiar"],"EM":["EM"],"Padding":["Wype\u0142nienie"],"Color 1":["Kolor 1"],"Color 2":["Kolor 2"],"Type":["Rodzaj"],"Linear":["Liniowy"],"Radial":["Promieniowy"],"Location 1":["Lokalizacja 1"],"Location 2":["Lokalizacja 2"],"Angle":["K\u0105t"],"Classic":["Klasyczny"],"Gradient":["Gradient"],"Background":["T\u0142o"],"Cover":["Ok\u0142adka"],"Contain":["Zawiera\u0107"],"Overlay":["Nak\u0142adka"],"No Repeat":["Bez powt\u00f3rki"],"Overlay Opacity":["Przezroczysto\u015b\u0107 nak\u0142adki"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Nazwy klas powinny by\u0107 oddzielone spacjami. Ka\u017cda nazwa klasy nie mo\u017ce zaczyna\u0107 si\u0119 od cyfry, my\u015blnika ani podkre\u015blenia. Mog\u0105 zawiera\u0107 tylko litery (w tym znaki Unicode), cyfry, my\u015blniki i podkre\u015blenia."],"Conversational Layout":["Uk\u0142ad konwersacyjny"],"Unlock Conversational Forms":["Odblokuj formularze konwersacyjne"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Dzi\u0119ki planowi SureForms Pro mo\u017cesz przekszta\u0142ci\u0107 swoje formularze w anga\u017cuj\u0105ce, konwersacyjne uk\u0142ady, zapewniaj\u0105c p\u0142ynne do\u015bwiadczenie u\u017cytkownika."],"Premium":["Premium"],"Overlay Type":["Typ nak\u0142adki"],"Image Overlay Color":["Kolor nak\u0142adki obrazu"],"Image Position":["Pozycja obrazu"],"Attachment":["Za\u0142\u0105cznik"],"Fixed":["Naprawione"],"Blend Mode":["Tryb mieszania"],"Multiply":["Mno\u017cenie"],"Screen":["Ekran"],"Darken":["Przyciemnij"],"Lighten":["Rozja\u015bnij"],"Color Dodge":["Rozja\u015bnianie koloru"],"Saturation":["Nasycenie"],"Repeat-x":["Powt\u00f3rz-x"],"Repeat-y":["Powt\u00f3rz-y"],"PX":["PX"],"Button":["Przycisk"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Po\u0142\u0105cz si\u0119 z OttoKit"],"SUREFORMS PREMIUM FIELDS":["Pola premium SureForms"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Bie\u017c\u0105cy adres \u201eOd e-mail\u201d nie pasuje do nazwy domeny Twojej strony internetowej (%1$s). Mo\u017ce to spowodowa\u0107, \u017ce Twoje e-maile z powiadomieniami b\u0119d\u0105 blokowane lub oznaczane jako spam. Alternatywnie, spr\u00f3buj u\u017cy\u0107 adresu Od, kt\u00f3ry pasuje do domeny Twojej strony internetowej (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Obecny adres \u201eOd e-mail\u201d nie pasuje do nazwy domeny Twojej strony internetowej (%s). Mo\u017ce to spowodowa\u0107, \u017ce Twoje e-maile z powiadomieniami zostan\u0105 zablokowane lub oznaczone jako spam."],"We strongly recommend that you install the free ":["Zalecamy zdecydowanie zainstalowanie darmowego"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["wtyczka! Kreator konfiguracji u\u0142atwia napraw\u0119 Twoich e-maili."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Alternatywnie spr\u00f3buj u\u017cy\u0107 adresu nadawcy, kt\u00f3ry pasuje do domeny Twojej strony internetowej (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Prosz\u0119 wprowadzi\u0107 prawid\u0142owy adres e-mail. Powiadomienia nie b\u0119d\u0105 wysy\u0142ane, je\u015bli pole nie zostanie wype\u0142nione poprawnie."],"From Name":["Od Nazwa"],"From Email":["Z e-maila"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Bie\u017c\u0105cy adres \u201eOd e-mail\u201d mo\u017ce nie pasowa\u0107 do nazwy domeny Twojej strony internetowej (%1$s). Mo\u017ce to spowodowa\u0107, \u017ce Twoje e-maile z powiadomieniami zostan\u0105 zablokowane lub oznaczone jako spam. Alternatywnie, spr\u00f3buj u\u017cy\u0107 adresu Od, kt\u00f3ry pasuje do domeny Twojej strony internetowej (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Obecny adres \u201eOd e-mail\u201d mo\u017ce nie pasowa\u0107 do nazwy domeny Twojej strony internetowej (%s). Mo\u017ce to spowodowa\u0107, \u017ce Twoje e-maile z powiadomieniami zostan\u0105 zablokowane lub oznaczone jako spam."],"Border Radius":["Promie\u0144 obramowania"],"Form Theme":["Motyw formularza"],"Instant Form Padding":["Natychmiastowe wype\u0142nianie formularza"],"Instant Form Border Radius":["Natychmiastowy promie\u0144 obramowania formularza"],"Select Gradient":["Wybierz gradient"],"Upgrade Now":["Zaktualizuj teraz"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["Wpisy starsze ni\u017c wybrane dni zostan\u0105 usuni\u0119te."],"Entries Time Period":["Okres czasu wpis\u00f3w"],"Custom CSS Panel":["Panel niestandardowego CSS"],"Notifications can use only one From Email so please enter a single address.":["Powiadomienia mog\u0105 u\u017cywa\u0107 tylko jednego adresu e-mail nadawcy, wi\u0119c prosz\u0119 poda\u0107 jeden adres."],"Email Notifications":["Powiadomienia e-mail"],"Actions":["Akcje"],"Duplicate":["Duplikat"],"Delete":["Usu\u0144"],"Select Page to redirect":["Wybierz stron\u0119 do przekierowania"],"Search for a page":["Wyszukaj stron\u0119"],"Select a page":["Wybierz stron\u0119"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Zapisz"],"Login":["Zaloguj si\u0119"],"Register":["Zarejestruj si\u0119"],"Date":["Data"],"Advanced Settings":["Ustawienia zaawansowane"],"Form Restriction":["Ograniczenie formularza"],"Maximum Number of Entries":["Maksymalna liczba wpis\u00f3w"],"Maximum Entries":["Maksymalna liczba wpis\u00f3w"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["Ustawienie Okresu Czasu dzia\u0142a zgodnie ze stref\u0105 czasow\u0105 Twojej strony WordPress. Kliknij tutaj<\/a>, aby otworzy\u0107 Ustawienia Og\u00f3lne WordPress, gdzie mo\u017cesz je sprawdzi\u0107 i zaktualizowa\u0107."],"Click here":["Kliknij tutaj"],"Response Description After Maximum Entries":["Opis odpowiedzi po maksymalnej liczbie wpis\u00f3w"],"All changes will be saved automatically when you press back.":["Wszystkie zmiany zostan\u0105 zapisane automatycznie po naci\u015bni\u0119ciu przycisku wstecz."],"Repeater":["Repeater"],"OttoKit Settings":["Ustawienia OttoKit"],"Get Started":["Rozpocznij"],"Connect Native Integrations with SureForms":["Po\u0142\u0105cz natywne integracje z SureForms"],"Expected format for emails - email@sureforms.com or John Doe ":["Oczekiwany format dla e-maili - email@sureforms.com lub John Doe "],"delete":["usu\u0144"],"Please type \"%s\" in the input box":["Prosz\u0119 wpisa\u0107 \"%s\" w polu wej\u015bciowym"],"To confirm, type \"%s\" in the box below:":["Aby potwierdzi\u0107, wpisz \"%s\" w polu poni\u017cej:"],"Type \"%s\"":["Wpisz \"%s\""],"Payment":["P\u0142atno\u015b\u0107"],"%s - Order ID":["%s - Identyfikator zam\u00f3wienia"],"%s - Amount":["%s - Kwota"],"%s - Customer Email":["%s - E-mail klienta"],"%s - Customer Name":["%s - Nazwa klienta"],"%s - Status":["%s - Status"],"Add custom CSS rules to style this specific form independently of global styles.":["Dodaj niestandardowe regu\u0142y CSS, aby stylizowa\u0107 ten konkretny formularz niezale\u017cnie od styl\u00f3w globalnych."],"Spam Protection Type":["Typ ochrony przed spamem"],"Select Security Type":["Wybierz typ zabezpiecze\u0144"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Uwaga: U\u017cywanie r\u00f3\u017cnych wersji reCAPTCHA (V2 checkbox i V3) na tej samej stronie spowoduje konflikty mi\u0119dzy wersjami. Prosz\u0119 unika\u0107 u\u017cywania r\u00f3\u017cnych wersji na tej samej stronie."],"Select Version":["Wybierz wersj\u0119"],"Please configure the API keys correctly from the settings":["Prosz\u0119 poprawnie skonfigurowa\u0107 klucze API w ustawieniach"],"Control email alerts sent to admins or users after a form submission.":["Zarz\u0105dzaj powiadomieniami e-mail wysy\u0142anymi do administrator\u00f3w lub u\u017cytkownik\u00f3w po przes\u0142aniu formularza."],"Customize the confirmation message or redirect the users after submitting the form.":["Dostosuj wiadomo\u015b\u0107 potwierdzaj\u0105c\u0105 lub przekieruj u\u017cytkownik\u00f3w po przes\u0142aniu formularza."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Ustal limity dotycz\u0105ce liczby przes\u0142a\u0144 formularza i zarz\u0105dzaj opcjami zgodno\u015bci, w tym RODO i przechowywaniem danych."],"Go to OttoKit Settings":["Przejd\u017a do ustawie\u0144 OttoKit"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Po\u0142\u0105cz SureForms z ulubionymi aplikacjami, aby zautomatyzowa\u0107 zadania i synchronizowa\u0107 dane bezproblemowo."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Odblokuj pot\u0119\u017cne integracje w planie Premium, aby zautomatyzowa\u0107 swoje przep\u0142ywy pracy i po\u0142\u0105czy\u0107 SureForms bezpo\u015brednio z ulubionymi narz\u0119dziami."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Wysy\u0142aj zg\u0142oszenia formularzy bezpo\u015brednio do CRM, e-maili i platform marketingowych."],"Automate repetitive tasks with seamless data syncing.":["Zautomatyzuj powtarzalne zadania dzi\u0119ki p\u0142ynnej synchronizacji danych."],"Access exclusive native integrations for faster workflows.":["Uzyskaj dost\u0119p do ekskluzywnych natywnych integracji dla szybszych przep\u0142yw\u00f3w pracy."],"PDF Generation":["Generowanie PDF"],"Generate and customize PDF copies of form submissions.":["Generuj i dostosowuj kopie PDF przes\u0142anych formularzy."],"Generate Submission PDFs":["Generuj pliki PDF zg\u0142osze\u0144"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Zamie\u0144 ka\u017cde wprowadzenie formularza na dopracowany plik PDF, czyni\u0105c go idealnym do raport\u00f3w, rejestr\u00f3w lub udost\u0119pniania."],"Automatically generate PDFs from your form submissions.":["Automatycznie generuj pliki PDF z przes\u0142anych formularzy."],"Customize PDF templates with your branding.":["Dostosuj szablony PDF do swojej marki."],"Download or email PDFs instantly.":["Pobierz lub wy\u015blij e-mailem pliki PDF natychmiast."],"User Registration":["Rejestracja u\u017cytkownika"],"Onboard new users or update existing accounts through beautiful looking forms.":["Wprowad\u017a nowych u\u017cytkownik\u00f3w lub zaktualizuj istniej\u0105ce konta za pomoc\u0105 pi\u0119knie wygl\u0105daj\u0105cych formularzy."],"Register Users with SureForms":["Zarejestruj u\u017cytkownik\u00f3w za pomoc\u0105 SureForms"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Usprawnij ca\u0142y proces wprowadzania u\u017cytkownik\u00f3w na swoje strony dzi\u0119ki bezproblemowym logowaniom i rejestracjom opartym na formularzach."],"Register new users directly via your form submissions.":["Zarejestruj nowych u\u017cytkownik\u00f3w bezpo\u015brednio poprzez przesy\u0142anie formularzy."],"Create or update existing accounts by mapping form data to user fields.":["Utw\u00f3rz lub zaktualizuj istniej\u0105ce konta, mapuj\u0105c dane formularza na pola u\u017cytkownika."],"Assign roles and control access automatically.":["Przypisuj role i automatycznie kontroluj dost\u0119p."],"Post Feed":["Kana\u0142 post\u00f3w"],"Transform your form submission into WordPress posts.":["Przekszta\u0142\u0107 swoje zg\u0142oszenie formularza w posty WordPress."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Automatycznie przekszta\u0142caj przes\u0142ane formularze w posty, strony lub niestandardowe typy post\u00f3w w WordPressie. Oszcz\u0119dzaj czas i pozw\u00f3l, aby Twoje formularze publikowa\u0142y tre\u015bci bezpo\u015brednio."],"Create posts, pages, or CPTs from your form entries.":["Tw\u00f3rz posty, strony lub CPT z wpis\u00f3w formularza."],"Map form fields to your post fields easily.":["\u0141atwo mapuj pola formularza do p\u00f3l posta."],"Automate the content publishing flow with few simple steps.":["Zautomatyzuj proces publikowania tre\u015bci w kilku prostych krokach."],"Automations":["Automatyzacje"],"Unlock Advanced Styling":["Odblokuj zaawansowane stylizacje"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Uzyskaj pe\u0142n\u0105 kontrol\u0119 nad wygl\u0105dem swojego formularza dzi\u0119ki niestandardowym kolorom, czcionkom i uk\u0142adom."],"Button Alignment":["Wyr\u00f3wnanie przycisku"],"Add Custom CSS Class(es)":["Dodaj niestandardow\u0105 klas\u0119 CSS"],"Set the total number of submissions allowed for this form.":["Ustaw ca\u0142kowit\u0105 liczb\u0119 dozwolonych zg\u0142osze\u0144 dla tego formularza."],"Save & Progress":["Zapisz i kontynuuj"],"Allow users to save their progress and continue form completion later.":["Pozw\u00f3l u\u017cytkownikom zapisa\u0107 post\u0119p i kontynuowa\u0107 wype\u0142nianie formularza p\u00f3\u017aniej."],"Save & Progress in SureForms":["Zapisz i kontynuuj w SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["Daj swoim u\u017cytkownikom mo\u017cliwo\u015b\u0107 wype\u0142niania formularzy we w\u0142asnym tempie, pozwalaj\u0105c im zapisywa\u0107 post\u0119py i wraca\u0107 w dowolnym momencie."],"Let users pause long or multi-step forms and continue later.":["Pozw\u00f3l u\u017cytkownikom wstrzyma\u0107 d\u0142ugie lub wieloetapowe formularze i kontynuowa\u0107 p\u00f3\u017aniej."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Zmniejsz porzucanie formularzy dzi\u0119ki wygodnym linkom do wznowienia i uzyskaj dost\u0119p do ich post\u0119p\u00f3w z dowolnego miejsca."],"Improve user experience for lengthy, complex, or multi-page forms.":["Popraw do\u015bwiadczenie u\u017cytkownika dla d\u0142ugich, z\u0142o\u017conych lub wielostronicowych formularzy."],"This form is not yet available. Please check back after the scheduled start time.":["Ten formularz nie jest jeszcze dost\u0119pny. Prosz\u0119 sprawdzi\u0107 ponownie po zaplanowanym czasie rozpocz\u0119cia."],"This form is no longer accepting submissions. The submission period has ended.":["Formularz ten nie przyjmuje ju\u017c zg\u0142osze\u0144. Okres sk\u0142adania zg\u0142osze\u0144 dobieg\u0142 ko\u0144ca."],"The start date and time must be before the end date and time.":["Data i godzina rozpocz\u0119cia musz\u0105 by\u0107 przed dat\u0105 i godzin\u0105 zako\u0144czenia."],"Form Scheduling":["Planowanie formularza"],"Enable Form Scheduling":["W\u0142\u0105cz planowanie formularza"],"Set a time period during which this form will be available for submissions.":["Ustaw okres, w kt\u00f3rym ten formularz b\u0119dzie dost\u0119pny do sk\u0142adania zg\u0142osze\u0144."],"Start Date & Time":["Data i godzina rozpocz\u0119cia"],"End Date & Time":["Data i godzina zako\u0144czenia"],"Response Description Before Start Date":["Opis odpowiedzi przed dat\u0105 rozpocz\u0119cia"],"Response Description After End Date":["Opis odpowiedzi po dacie zako\u0144czenia"],"Conditional Confirmations":["Warunkowe potwierdzenia"],"Set up the message or redirect users will see after submitting the form.":["Ustaw wiadomo\u015b\u0107 lub przekierowanie, kt\u00f3re u\u017cytkownicy zobacz\u0105 po przes\u0142aniu formularza."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Wy\u015bwietl w\u0142a\u015bciw\u0105 wiadomo\u015b\u0107 w\u0142a\u015bciwemu u\u017cytkownikowi w zale\u017cno\u015bci od tego, jak odpowiadaj\u0105. Personalizuj potwierdzenia za pomoc\u0105 inteligentnych warunk\u00f3w i automatycznie kieruj u\u017cytkownik\u00f3w do kolejnego najlepszego kroku."],"Display different confirmation messages based on form responses.":["Wy\u015bwietlaj r\u00f3\u017cne wiadomo\u015bci potwierdzaj\u0105ce w zale\u017cno\u015bci od odpowiedzi w formularzu."],"Redirect users to specific pages or URLs conditionally.":["Przekieruj u\u017cytkownik\u00f3w na okre\u015blone strony lub adresy URL warunkowo."],"Create personalized thank-you messages without extra forms.":["Tw\u00f3rz spersonalizowane wiadomo\u015bci z podzi\u0119kowaniami bez dodatkowych formularzy."],"Lost Password":["Zapomniane has\u0142o"],"Reset Password":["Zresetuj has\u0142o"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["Wybierz us\u0142ug\u0119 ochrony przed spamem. Skonfiguruj klucze API w Ustawieniach Globalnych przed w\u0142\u0105czeniem."],"Send as Raw HTML":["Wy\u015blij jako surowy HTML"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Gdy jest w\u0142\u0105czone, tre\u015b\u0107 wiadomo\u015bci e-mail w formacie HTML zostanie zachowana dok\u0142adnie tak, jak zosta\u0142a napisana, i opakowana w profesjonalny szablon e-maila."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["Inteligentne tagi, kt\u00f3re odnosz\u0105 si\u0119 do p\u00f3l wprowadzonych przez u\u017cytkownika, nie b\u0119d\u0105 przetwarzane w trybie surowego HTML. Unikaj bezpo\u015bredniego wstawiania niezweryfikowanych warto\u015bci p\u00f3l do tre\u015bci e-maila."],"Please provide a recipient email address and subject line.":["Prosz\u0119 poda\u0107 adres e-mail odbiorcy oraz temat wiadomo\u015bci."],"Email notification duplicated!":["Powiadomienie e-mail zduplikowane!"],"Are you sure you want to delete this email notification?":["Czy na pewno chcesz usun\u0105\u0107 to powiadomienie e-mail?"],"Email notification deleted!":["Powiadomienie e-mail zosta\u0142o usuni\u0119te!"],"URL is missing Top Level Domain (TLD).":["Brakuje domeny najwy\u017cszego poziomu (TLD) w adresie URL."],"This form is now closed as the maximum number of entries has been received.":["Formularz zosta\u0142 zamkni\u0119ty, poniewa\u017c osi\u0105gni\u0119to maksymaln\u0105 liczb\u0119 zg\u0142osze\u0144."],"Publish Your Form":["Opublikuj sw\u00f3j formularz"],"Enable This to Instantly Publish the Form":["W\u0142\u0105cz to, aby natychmiast opublikowa\u0107 formularz"],"Style Your Instant Form Page Here":["Stylizuj swoj\u0105 stron\u0119 formularza natychmiastowego tutaj"],"Quizzes":["Quizy"],"%s - Description":["%s - Opis"],"Send entries to 100+ popular apps.":["Wy\u015blij wpisy do ponad 100 popularnych aplikacji."],"Build automated workflows that run instantly.":["Tw\u00f3rz zautomatyzowane przep\u0142ywy pracy, kt\u00f3re dzia\u0142aj\u0105 natychmiast."],"Create custom app integrations using our Custom App feature.":["Tw\u00f3rz niestandardowe integracje aplikacji za pomoc\u0105 funkcji Niestandardowa Aplikacja."],"Keep your tools in sync automatically.":["Utrzymuj swoje narz\u0119dzia w synchronizacji automatycznie."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["To zainstaluje i aktywuje OttoKit na Twojej stronie WordPress, aby w\u0142\u0105czy\u0107 funkcje automatyzacji."],"Automate Your Forms with OttoKit":["Zautomatyzuj swoje formularze za pomoc\u0105 OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Ka\u017cde przes\u0142anie formularza powinno wywo\u0142a\u0107 jak\u0105\u015b akcj\u0119 \u2014 alert w Slacku, lead w CRM, e-mail z przypomnieniem lub nowy wiersz w Arkuszach Google."],"Create interactive quizzes to engage your audience and gather insights.":["Tw\u00f3rz interaktywne quizy, aby zaanga\u017cowa\u0107 swoj\u0105 publiczno\u015b\u0107 i zbiera\u0107 informacje."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Projektuj anga\u017cuj\u0105ce quizy z r\u00f3\u017cnymi typami pyta\u0144, spersonalizowanymi opiniami i automatycznym ocenianiem, aby przyci\u0105gn\u0105\u0107 swoj\u0105 publiczno\u015b\u0107 i uzyska\u0107 cenne informacje."],"Create interactive quizzes with multiple question types.":["Tw\u00f3rz interaktywne quizy z r\u00f3\u017cnymi typami pyta\u0144."],"Provide personalized feedback based on user responses.":["Zapewnij spersonalizowan\u0105 opini\u0119 na podstawie odpowiedzi u\u017cytkownika."],"Automate scoring and lead segmentation for better insights.":["Zautomatyzuj ocenianie i segmentacj\u0119 lead\u00f3w, aby uzyska\u0107 lepsze wgl\u0105dy."],"Heading 1":["Nag\u0142\u00f3wek 1"],"Heading 2":["Nag\u0142\u00f3wek 2"],"Heading 3":["Nag\u0142\u00f3wek 3"],"Heading 4":["Nag\u0142\u00f3wek 4"],"Heading 5":["Nag\u0142\u00f3wek 5"],"Heading 6":["Nag\u0142\u00f3wek 6"],"Form settings saved.":["Ustawienia formularza zosta\u0142y zapisane."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["Limit wej\u015b\u0107 opiera si\u0119 na zapisanych wpisach do liczenia zg\u0142osze\u0144. Gdy w Ustawieniach Zgodno\u015bci jest w\u0142\u0105czona opcja \"Nigdy nie przechowuj danych wpisu po przes\u0142aniu formularza\", ten limit nie b\u0119dzie egzekwowany. Wy\u0142\u0105cz t\u0119 opcj\u0119 lub usu\u0144 limit wpis\u00f3w, aby skorzysta\u0107 z tej funkcji."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Ustawienia zosta\u0142y zapisane, ale atrybuty posta (has\u0142o \/ tytu\u0142 \/ tre\u015b\u0107) nie zosta\u0142y zaktualizowane. Spr\u00f3buj ponownie, aby je zachowa\u0107."],"Failed to save form settings.":["Nie uda\u0142o si\u0119 zapisa\u0107 ustawie\u0144 formularza."],"Saving\u2026":["Zapisywanie\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Po w\u0142\u0105czeniu tego formularza nie b\u0119d\u0105 przechowywane adres IP u\u017cytkownika, nazwa przegl\u0105darki i nazwa urz\u0105dzenia w wpisach."],"Failed to save. Please try again.":["Nie uda\u0142o si\u0119 zapisa\u0107. Prosz\u0119 spr\u00f3bowa\u0107 ponownie."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["Klucze API reCAPTCHA dla wybranej wersji nie s\u0105 skonfigurowane. Ustaw je w Ustawieniach Globalnych."],"Please select a reCAPTCHA version.":["Prosz\u0119 wybra\u0107 wersj\u0119 reCAPTCHA."],"hCaptcha API keys are not configured. Set them in Global Settings.":["Klucze API hCaptcha nie s\u0105 skonfigurowane. Ustaw je w Ustawieniach Globalnych."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["Klucze API Cloudflare Turnstile nie s\u0105 skonfigurowane. Ustaw je w Ustawieniach Globalnych."],"Form data":["Dane formularza"],"Some fields need attention":["Niekt\u00f3re pola wymagaj\u0105 uwagi"],"Unsaved changes":["Niezapisane zmiany"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Adres e-mail odbiorcy i temat s\u0105 wymagane przed zapisaniem tego powiadomienia. Popraw wyr\u00f3\u017cnione pola lub odrzu\u0107 zmiany, aby wr\u00f3ci\u0107."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Masz niezapisane zmiany dla tego powiadomienia. Odrzu\u0107 je, aby wr\u00f3ci\u0107, lub zosta\u0144, aby je zapisa\u0107."],"Discard & go back":["Odrzu\u0107 i wr\u00f3\u0107"],"Stay & fix":["Zosta\u0144 i napraw"],"Keep editing":["Kontynuuj edytowanie"],"Please provide a recipient email address.":["Prosz\u0119 poda\u0107 adres e-mail odbiorcy."],"Please provide a subject line.":["Prosz\u0119 poda\u0107 temat."],"Please provide a custom URL.":["Prosz\u0119 poda\u0107 niestandardowy adres URL."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Masz niezapisane zmiany. Odrzu\u0107 je, aby kontynuowa\u0107, lub zosta\u0144, aby zapisa\u0107 zmiany."],"Discard & continue":["Odrzu\u0107 i kontynuuj"],"Quill heading picker: default paragraph style\u0004Normal":["Normalny"]}}} \ No newline at end of file +{"translation-revision-date":"2025-01-02T08:44:41+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Status"],"Form":["Formularz"],"Fields":["Pola"],"Image":["Obraz"],"Activated":["Aktywowany"],"Activate":["Aktywuj"],"Submit":["Prze\u015blij"],"Global Settings":["Ustawienia globalne"],"Form Title":["Tytu\u0142 formularza"],"Edit":["Edytuj"],"Please enter a valid URL.":["Prosz\u0119 wprowadzi\u0107 prawid\u0142owy adres URL."],"Desktop":["Pulpit"],"Medium":["\u015aredni"],"Mobile":["Telefon kom\u00f3rkowy"],"Repeat":["Powt\u00f3rz"],"Scroll":["Przewi\u0144"],"Signature":["Podpis"],"Tablet":["Tablet"],"Upload":["Prze\u015blij"],"Basic":["Podstawowy"],"Form Settings":["Ustawienia formularza"],"General":["Og\u00f3lny"],"Style":["Styl"],"Advanced":["Zaawansowany"],"No tags available":["Brak dost\u0119pnych tag\u00f3w"],"Device":["Urz\u0105dzenie"],"Select Shortcodes":["Wybierz skr\u00f3ty"],"Page Break Label":["Etykieta podzia\u0142u strony"],"Next":["Dalej"],"Back":["Wstecz"],"Reset":["Resetuj"],"Generic tags":["Og\u00f3lne tagi"],"Pixel":["Piksel"],"Em":["Em"],"Select Units":["Wybierz jednostki"],"%s units":["%s jednostki"],"Margin":["Margines"],"None":["Brak"],"Custom":["Niestandardowy"],"Please add a option props to MultiButtonsControl":["Prosz\u0119 doda\u0107 opcj\u0119 props do MultiButtonsControl"],"Confirm":["Potwierd\u017a"],"Cancel":["Anuluj"],"Processing\u2026":["Przetwarzanie\u2026"],"Select Video":["Wybierz wideo"],"Change Video":["Zmie\u0144 wideo"],"Select Lottie Animation":["Wybierz animacj\u0119 Lottie"],"Change Lottie Animation":["Zmie\u0144 animacj\u0119 Lottie"],"Upload SVG":["Prze\u015blij SVG"],"Change SVG":["Zmie\u0144 SVG"],"Select Image":["Wybierz obraz"],"Change Image":["Zmie\u0144 obraz"],"Upload SVG?":["Przes\u0142a\u0107 SVG?"],"Upload SVG can be potentially risky. Are you sure?":["Przesy\u0142anie plik\u00f3w SVG mo\u017ce by\u0107 potencjalnie ryzykowne. Czy jeste\u015b pewien?"],"Upload Anyway":["Prze\u015blij mimo to"],"Full Width":["Pe\u0142na szeroko\u015b\u0107"],"Install":["Zainstaluj"],"Plugin Installation failed, Please try again later.":["Instalacja wtyczki nie powiod\u0142a si\u0119, spr\u00f3buj ponownie p\u00f3\u017aniej."],"Plugin activation failed, Please try again later.":["Aktywacja wtyczki nie powiod\u0142a si\u0119, spr\u00f3buj ponownie p\u00f3\u017aniej."],"Integrations":["Integracje"],"%s Removed from Quick Action Bar.":["%s usuni\u0119to z paska szybkich akcji."],"Add to Quick Action Bar":["Dodaj do paska szybkich akcji"],"%s Added to Quick Action Bar.":["%s dodano do paska szybkich akcji."],"Already Present in Quick Action Bar":["Ju\u017c obecne na pasku szybkich akcji"],"No results found.":["Nie znaleziono wynik\u00f3w."],"data object is empty":["obiekt danych jest pusty"],"Add blocks to Quick Action Bar":["Dodaj bloki do paska szybkich akcji"],"Re-arrange block inside Quick Action Bar":["Przestaw blok wewn\u0105trz paska szybkich akcji"],"Upgrade":["Aktualizacja"],"Connecting\u2026":["\u0141\u0105czenie\u2026"],"Install & Activate":["Zainstaluj i aktywuj"],"Compliance Settings":["Ustawienia zgodno\u015bci"],"Enable GDPR Compliance":["W\u0142\u0105cz zgodno\u015b\u0107 z RODO"],"Never store entry data after form submission":["Nigdy nie przechowuj danych wej\u015bciowych po przes\u0142aniu formularza"],"When enabled this form will never store Entries.":["Gdy jest w\u0142\u0105czony, ten formularz nigdy nie b\u0119dzie przechowywa\u0142 wpis\u00f3w."],"Automatically delete entries":["Automatycznie usu\u0144 wpisy"],"When enabled this form will automatically delete entries after a certain period of time.":["Po w\u0142\u0105czeniu ten formularz automatycznie usunie wpisy po okre\u015blonym czasie."],"Entries older than the days set will be deleted automatically.":["Wpisy starsze ni\u017c ustawione dni zostan\u0105 usuni\u0119te automatycznie."],"Custom CSS":["Niestandardowy CSS"],"The following CSS styles added below will only apply to this form container.":["Nast\u0119puj\u0105ce style CSS dodane poni\u017cej b\u0119d\u0105 dotyczy\u0107 tylko tego kontenera formularza."],"Visual":["Wizualny"],"HTML":["HTML"],"All Data":["Wszystkie dane"],"Add Shortcode":["Dodaj shortcode"],"Form input tags":["Tagi wej\u015bciowe formularza"],"Comma separated values are also accepted.":["Warto\u015bci oddzielone przecinkami s\u0105 r\u00f3wnie\u017c akceptowane."],"Email Notification":["Powiadomienie e-mail"],"Name":["Imi\u0119"],"Send Email To":["Wy\u015blij e-mail do"],"Subject":["Temat"],"CC":["DW"],"BCC":["UDW"],"Reply To":["Odpowiedz do"],"Add Notification":["Dodaj powiadomienie"],"Add Key":["Dodaj klucz"],"Add Value":["Dodaj warto\u015b\u0107"],"Add":["Dodaj"],"Confirmation Message":["Wiadomo\u015b\u0107 potwierdzaj\u0105ca"],"After Form Submission":["Po przes\u0142aniu formularza"],"Hide Form":["Ukryj formularz"],"Reset Form":["Zresetuj formularz"],"Custom URL":["Niestandardowy URL"],"Add Query Parameters":["Dodaj parametry zapytania"],"Select if you want to add key-value pairs for form fields to include in query parameters":["Wybierz, je\u015bli chcesz doda\u0107 pary klucz-warto\u015b\u0107 dla p\u00f3l formularza do uwzgl\u0119dnienia w parametrach zapytania"],"Query Parameters":["Parametry zapytania"],"Please select a page.":["Prosz\u0119 wybra\u0107 stron\u0119."],"Suggestion: URL should use HTTPS":["Sugestia: URL powinien u\u017cywa\u0107 HTTPS"],"Success Message":["Komunikat o sukcesie"],"Redirect":["Przekieruj"],"Redirect to":["Przekieruj do"],"Page":["Strona"],"Form Confirmation":["Potwierdzenie formularza"],"Confirmation Type":["Typ potwierdzenia"],"Use Labels as Placeholders":["U\u017cyj etykiet jako symboli zast\u0119pczych"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["Powy\u017csze ustawienie umie\u015bci etykiety wewn\u0105trz p\u00f3l jako podpowiedzi (tam, gdzie to mo\u017cliwe). To ustawienie dzia\u0142a tylko na \u017cywej stronie, a nie w podgl\u0105dzie edytora."],"Page Break":["Podzia\u0142 strony"],"Show Labels":["Poka\u017c etykiety"],"First Page Label":["Etykieta pierwszej strony"],"Progress Indicator":["Wska\u017anik post\u0119pu"],"Progress Bar":["Pasek post\u0119pu"],"Connector":["Z\u0142\u0105cze"],"Steps":["Kroki"],"Next Button Text":["Tekst przycisku \"Dalej\""],"Back Button Text":["Tekst przycisku powrotu"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["Czy na pewno chcesz zamkn\u0105\u0107? Twoje niezapisane zmiany zostan\u0105 utracone, poniewa\u017c masz pewne b\u0142\u0119dy walidacji."],"There are few unsaved changes. Please save your changes to reflect the updates.":["Jest kilka niezapisanych zmian. Prosz\u0119 zapisa\u0107 zmiany, aby odzwierciedli\u0107 aktualizacje."],"Form Behavior":["Zachowanie formularza"],"Clear":["Wyczy\u015b\u0107"],"Select Color":["Wybierz kolor"],"Primary Color":["Kolor podstawowy"],"Text Color":["Kolor tekstu"],"Text Color on Primary":["Kolor tekstu na g\u0142\u00f3wnym"],"Field Spacing":["Odst\u0119py mi\u0119dzy polami"],"Small":["Ma\u0142y"],"Large":["Du\u017cy"],"Left":["Lewo"],"Center":["Centrum"],"Right":["Prawo"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Niewidoczna"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Wyb\u00f3r daty"],"Time Picker":["Wyb\u00f3r czasu"],"Hidden":["Ukryty"],"Slider":["Suwak"],"Rating":["Ocena"],"Upgrade to Unlock These Fields":["Zaktualizuj, aby odblokowa\u0107 te pola"],"Add Block":["Dodaj blok"],"Customize with SureForms":["Dostosuj za pomoc\u0105 SureForms"],"Page break":["Podzia\u0142 strony"],"Previous":["Poprzedni"],"Thank you":["Dzi\u0119kuj\u0119"],"Form submitted successfully!":["Formularz zosta\u0142 pomy\u015blnie przes\u0142any!"],"Instant Form":["Formularz natychmiastowy"],"Enable Instant Form":["W\u0142\u0105cz natychmiastowy formularz"],"Enable Preview":["W\u0142\u0105cz podgl\u0105d"],"Show Title":["Tytu\u0142 Pokazu"],"Site Logo":["Logo strony"],"Banner Background":["T\u0142o banera"],"Color":["Kolor"],"Upload Image":["Prze\u015blij obraz"],"Background Color":["Kolor t\u0142a"],"Use banner as page background":["U\u017cyj baneru jako t\u0142a strony"],"Form Width":["Szeroko\u015b\u0107 formularza"],"URL":["URL"],"URL Slug":["Slug URL"],"The last part of the URL.":["Ostatnia cz\u0119\u015b\u0107 adresu URL."],"Learn more.":["Dowiedz si\u0119 wi\u0119cej."],"SureForms Description":["Opis SureForms"],"Form Options":["Opcje formularza"],"Form Shortcode":["Kod skr\u00f3tu formularza"],"Paste this shortcode on the page or post to render this form.":["Wklej ten kr\u00f3tki kod na stron\u0119 lub post, aby wy\u015bwietli\u0107 ten formularz."],"Spam Protection":["Ochrona przed spamem"],"Auto":["Samoch\u00f3d"],"Normal":["Normalny"],"%":["%"],"Top":["G\u00f3ra"],"Bottom":["D\u00f3\u0142"],"Solid":["Solidny"],"Width":["Szeroko\u015b\u0107"],"Size":["Rozmiar"],"EM":["EM"],"Padding":["Wype\u0142nienie"],"Color 1":["Kolor 1"],"Color 2":["Kolor 2"],"Type":["Rodzaj"],"Linear":["Liniowy"],"Radial":["Promieniowy"],"Location 1":["Lokalizacja 1"],"Location 2":["Lokalizacja 2"],"Angle":["K\u0105t"],"Classic":["Klasyczny"],"Gradient":["Gradient"],"Background":["T\u0142o"],"Cover":["Ok\u0142adka"],"Contain":["Zawiera\u0107"],"Overlay":["Nak\u0142adka"],"No Repeat":["Bez powt\u00f3rki"],"Overlay Opacity":["Przezroczysto\u015b\u0107 nak\u0142adki"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Nazwy klas powinny by\u0107 oddzielone spacjami. Ka\u017cda nazwa klasy nie mo\u017ce zaczyna\u0107 si\u0119 od cyfry, my\u015blnika ani podkre\u015blenia. Mog\u0105 zawiera\u0107 tylko litery (w tym znaki Unicode), cyfry, my\u015blniki i podkre\u015blenia."],"Conversational Layout":["Uk\u0142ad konwersacyjny"],"Unlock Conversational Forms":["Odblokuj formularze konwersacyjne"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Dzi\u0119ki planowi SureForms Pro mo\u017cesz przekszta\u0142ci\u0107 swoje formularze w anga\u017cuj\u0105ce, konwersacyjne uk\u0142ady, zapewniaj\u0105c p\u0142ynne do\u015bwiadczenie u\u017cytkownika."],"Premium":["Premium"],"Overlay Type":["Typ nak\u0142adki"],"Image Overlay Color":["Kolor nak\u0142adki obrazu"],"Image Position":["Pozycja obrazu"],"Attachment":["Za\u0142\u0105cznik"],"Fixed":["Naprawione"],"Blend Mode":["Tryb mieszania"],"Multiply":["Mno\u017cenie"],"Screen":["Ekran"],"Darken":["Przyciemnij"],"Lighten":["Rozja\u015bnij"],"Color Dodge":["Rozja\u015bnianie koloru"],"Saturation":["Nasycenie"],"Repeat-x":["Powt\u00f3rz-x"],"Repeat-y":["Powt\u00f3rz-y"],"PX":["PX"],"Button":["Przycisk"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Po\u0142\u0105cz si\u0119 z OttoKit"],"SUREFORMS PREMIUM FIELDS":["Pola premium SureForms"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Bie\u017c\u0105cy adres \u201eOd e-mail\u201d nie pasuje do nazwy domeny Twojej strony internetowej (%1$s). Mo\u017ce to spowodowa\u0107, \u017ce Twoje e-maile z powiadomieniami b\u0119d\u0105 blokowane lub oznaczane jako spam. Alternatywnie, spr\u00f3buj u\u017cy\u0107 adresu Od, kt\u00f3ry pasuje do domeny Twojej strony internetowej (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Obecny adres \u201eOd e-mail\u201d nie pasuje do nazwy domeny Twojej strony internetowej (%s). Mo\u017ce to spowodowa\u0107, \u017ce Twoje e-maile z powiadomieniami zostan\u0105 zablokowane lub oznaczone jako spam."],"We strongly recommend that you install the free ":["Zalecamy zdecydowanie zainstalowanie darmowego"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["wtyczka! Kreator konfiguracji u\u0142atwia napraw\u0119 Twoich e-maili."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Alternatywnie spr\u00f3buj u\u017cy\u0107 adresu nadawcy, kt\u00f3ry pasuje do domeny Twojej strony internetowej (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Prosz\u0119 wprowadzi\u0107 prawid\u0142owy adres e-mail. Powiadomienia nie b\u0119d\u0105 wysy\u0142ane, je\u015bli pole nie zostanie wype\u0142nione poprawnie."],"From Name":["Od Nazwa"],"From Email":["Z e-maila"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["Bie\u017c\u0105cy adres \u201eOd e-mail\u201d mo\u017ce nie pasowa\u0107 do nazwy domeny Twojej strony internetowej (%1$s). Mo\u017ce to spowodowa\u0107, \u017ce Twoje e-maile z powiadomieniami zostan\u0105 zablokowane lub oznaczone jako spam. Alternatywnie, spr\u00f3buj u\u017cy\u0107 adresu Od, kt\u00f3ry pasuje do domeny Twojej strony internetowej (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["Obecny adres \u201eOd e-mail\u201d mo\u017ce nie pasowa\u0107 do nazwy domeny Twojej strony internetowej (%s). Mo\u017ce to spowodowa\u0107, \u017ce Twoje e-maile z powiadomieniami zostan\u0105 zablokowane lub oznaczone jako spam."],"Border Radius":["Promie\u0144 obramowania"],"Form Theme":["Motyw formularza"],"Instant Form Padding":["Natychmiastowe wype\u0142nianie formularza"],"Instant Form Border Radius":["Natychmiastowy promie\u0144 obramowania formularza"],"Select Gradient":["Wybierz gradient"],"Upgrade Now":["Zaktualizuj teraz"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["Wpisy starsze ni\u017c wybrane dni zostan\u0105 usuni\u0119te."],"Entries Time Period":["Okres czasu wpis\u00f3w"],"Custom CSS Panel":["Panel niestandardowego CSS"],"Notifications can use only one From Email so please enter a single address.":["Powiadomienia mog\u0105 u\u017cywa\u0107 tylko jednego adresu e-mail nadawcy, wi\u0119c prosz\u0119 poda\u0107 jeden adres."],"Email Notifications":["Powiadomienia e-mail"],"Actions":["Akcje"],"Duplicate":["Duplikat"],"Delete":["Usu\u0144"],"Select Page to redirect":["Wybierz stron\u0119 do przekierowania"],"Search for a page":["Wyszukaj stron\u0119"],"Select a page":["Wybierz stron\u0119"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Zapisz"],"Login":["Zaloguj si\u0119"],"Register":["Zarejestruj si\u0119"],"Date":["Data"],"Advanced Settings":["Ustawienia zaawansowane"],"Form Restriction":["Ograniczenie formularza"],"Maximum Number of Entries":["Maksymalna liczba wpis\u00f3w"],"Maximum Entries":["Maksymalna liczba wpis\u00f3w"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["Ustawienie Okresu Czasu dzia\u0142a zgodnie ze stref\u0105 czasow\u0105 Twojej strony WordPress. Kliknij tutaj<\/a>, aby otworzy\u0107 Ustawienia Og\u00f3lne WordPress, gdzie mo\u017cesz je sprawdzi\u0107 i zaktualizowa\u0107."],"Click here":["Kliknij tutaj"],"Response Description After Maximum Entries":["Opis odpowiedzi po maksymalnej liczbie wpis\u00f3w"],"All changes will be saved automatically when you press back.":["Wszystkie zmiany zostan\u0105 zapisane automatycznie po naci\u015bni\u0119ciu przycisku wstecz."],"Repeater":["Repeater"],"OttoKit Settings":["Ustawienia OttoKit"],"Get Started":["Rozpocznij"],"Connect Native Integrations with SureForms":["Po\u0142\u0105cz natywne integracje z SureForms"],"Expected format for emails - email@sureforms.com or John Doe ":["Oczekiwany format dla e-maili - email@sureforms.com lub John Doe "],"delete":["usu\u0144"],"Please type \"%s\" in the input box":["Prosz\u0119 wpisa\u0107 \"%s\" w polu wej\u015bciowym"],"To confirm, type \"%s\" in the box below:":["Aby potwierdzi\u0107, wpisz \"%s\" w polu poni\u017cej:"],"Type \"%s\"":["Wpisz \"%s\""],"Payment":["P\u0142atno\u015b\u0107"],"%s - Order ID":["%s - Identyfikator zam\u00f3wienia"],"%s - Amount":["%s - Kwota"],"%s - Customer Email":["%s - E-mail klienta"],"%s - Customer Name":["%s - Nazwa klienta"],"%s - Status":["%s - Status"],"Add custom CSS rules to style this specific form independently of global styles.":["Dodaj niestandardowe regu\u0142y CSS, aby stylizowa\u0107 ten konkretny formularz niezale\u017cnie od styl\u00f3w globalnych."],"Spam Protection Type":["Typ ochrony przed spamem"],"Select Security Type":["Wybierz typ zabezpiecze\u0144"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Uwaga: U\u017cywanie r\u00f3\u017cnych wersji reCAPTCHA (V2 checkbox i V3) na tej samej stronie spowoduje konflikty mi\u0119dzy wersjami. Prosz\u0119 unika\u0107 u\u017cywania r\u00f3\u017cnych wersji na tej samej stronie."],"Select Version":["Wybierz wersj\u0119"],"Please configure the API keys correctly from the settings":["Prosz\u0119 poprawnie skonfigurowa\u0107 klucze API w ustawieniach"],"Control email alerts sent to admins or users after a form submission.":["Zarz\u0105dzaj powiadomieniami e-mail wysy\u0142anymi do administrator\u00f3w lub u\u017cytkownik\u00f3w po przes\u0142aniu formularza."],"Customize the confirmation message or redirect the users after submitting the form.":["Dostosuj wiadomo\u015b\u0107 potwierdzaj\u0105c\u0105 lub przekieruj u\u017cytkownik\u00f3w po przes\u0142aniu formularza."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Ustal limity dotycz\u0105ce liczby przes\u0142a\u0144 formularza i zarz\u0105dzaj opcjami zgodno\u015bci, w tym RODO i przechowywaniem danych."],"Go to OttoKit Settings":["Przejd\u017a do ustawie\u0144 OttoKit"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Po\u0142\u0105cz SureForms z ulubionymi aplikacjami, aby zautomatyzowa\u0107 zadania i synchronizowa\u0107 dane bezproblemowo."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Odblokuj pot\u0119\u017cne integracje w planie Premium, aby zautomatyzowa\u0107 swoje przep\u0142ywy pracy i po\u0142\u0105czy\u0107 SureForms bezpo\u015brednio z ulubionymi narz\u0119dziami."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Wysy\u0142aj zg\u0142oszenia formularzy bezpo\u015brednio do CRM, e-maili i platform marketingowych."],"Automate repetitive tasks with seamless data syncing.":["Zautomatyzuj powtarzalne zadania dzi\u0119ki p\u0142ynnej synchronizacji danych."],"Access exclusive native integrations for faster workflows.":["Uzyskaj dost\u0119p do ekskluzywnych natywnych integracji dla szybszych przep\u0142yw\u00f3w pracy."],"PDF Generation":["Generowanie PDF"],"Generate and customize PDF copies of form submissions.":["Generuj i dostosowuj kopie PDF przes\u0142anych formularzy."],"Generate Submission PDFs":["Generuj pliki PDF zg\u0142osze\u0144"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Zamie\u0144 ka\u017cde wprowadzenie formularza na dopracowany plik PDF, czyni\u0105c go idealnym do raport\u00f3w, rejestr\u00f3w lub udost\u0119pniania."],"Automatically generate PDFs from your form submissions.":["Automatycznie generuj pliki PDF z przes\u0142anych formularzy."],"Customize PDF templates with your branding.":["Dostosuj szablony PDF do swojej marki."],"Download or email PDFs instantly.":["Pobierz lub wy\u015blij e-mailem pliki PDF natychmiast."],"User Registration":["Rejestracja u\u017cytkownika"],"Onboard new users or update existing accounts through beautiful looking forms.":["Wprowad\u017a nowych u\u017cytkownik\u00f3w lub zaktualizuj istniej\u0105ce konta za pomoc\u0105 pi\u0119knie wygl\u0105daj\u0105cych formularzy."],"Register Users with SureForms":["Zarejestruj u\u017cytkownik\u00f3w za pomoc\u0105 SureForms"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Usprawnij ca\u0142y proces wprowadzania u\u017cytkownik\u00f3w na swoje strony dzi\u0119ki bezproblemowym logowaniom i rejestracjom opartym na formularzach."],"Register new users directly via your form submissions.":["Zarejestruj nowych u\u017cytkownik\u00f3w bezpo\u015brednio poprzez przesy\u0142anie formularzy."],"Create or update existing accounts by mapping form data to user fields.":["Utw\u00f3rz lub zaktualizuj istniej\u0105ce konta, mapuj\u0105c dane formularza na pola u\u017cytkownika."],"Assign roles and control access automatically.":["Przypisuj role i automatycznie kontroluj dost\u0119p."],"Post Feed":["Kana\u0142 post\u00f3w"],"Transform your form submission into WordPress posts.":["Przekszta\u0142\u0107 swoje zg\u0142oszenie formularza w posty WordPress."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Automatycznie przekszta\u0142caj przes\u0142ane formularze w posty, strony lub niestandardowe typy post\u00f3w w WordPressie. Oszcz\u0119dzaj czas i pozw\u00f3l, aby Twoje formularze publikowa\u0142y tre\u015bci bezpo\u015brednio."],"Create posts, pages, or CPTs from your form entries.":["Tw\u00f3rz posty, strony lub CPT z wpis\u00f3w formularza."],"Map form fields to your post fields easily.":["\u0141atwo mapuj pola formularza do p\u00f3l posta."],"Automate the content publishing flow with few simple steps.":["Zautomatyzuj proces publikowania tre\u015bci w kilku prostych krokach."],"Automations":["Automatyzacje"],"Unlock Advanced Styling":["Odblokuj zaawansowane stylizacje"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Uzyskaj pe\u0142n\u0105 kontrol\u0119 nad wygl\u0105dem swojego formularza dzi\u0119ki niestandardowym kolorom, czcionkom i uk\u0142adom."],"Button Alignment":["Wyr\u00f3wnanie przycisku"],"Add Custom CSS Class(es)":["Dodaj niestandardow\u0105 klas\u0119 CSS"],"Set the total number of submissions allowed for this form.":["Ustaw ca\u0142kowit\u0105 liczb\u0119 dozwolonych zg\u0142osze\u0144 dla tego formularza."],"Save & Progress":["Zapisz i kontynuuj"],"Allow users to save their progress and continue form completion later.":["Pozw\u00f3l u\u017cytkownikom zapisa\u0107 post\u0119p i kontynuowa\u0107 wype\u0142nianie formularza p\u00f3\u017aniej."],"Save & Progress in SureForms":["Zapisz i kontynuuj w SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["Daj swoim u\u017cytkownikom mo\u017cliwo\u015b\u0107 wype\u0142niania formularzy we w\u0142asnym tempie, pozwalaj\u0105c im zapisywa\u0107 post\u0119py i wraca\u0107 w dowolnym momencie."],"Let users pause long or multi-step forms and continue later.":["Pozw\u00f3l u\u017cytkownikom wstrzyma\u0107 d\u0142ugie lub wieloetapowe formularze i kontynuowa\u0107 p\u00f3\u017aniej."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Zmniejsz porzucanie formularzy dzi\u0119ki wygodnym linkom do wznowienia i uzyskaj dost\u0119p do ich post\u0119p\u00f3w z dowolnego miejsca."],"Improve user experience for lengthy, complex, or multi-page forms.":["Popraw do\u015bwiadczenie u\u017cytkownika dla d\u0142ugich, z\u0142o\u017conych lub wielostronicowych formularzy."],"This form is not yet available. Please check back after the scheduled start time.":["Ten formularz nie jest jeszcze dost\u0119pny. Prosz\u0119 sprawdzi\u0107 ponownie po zaplanowanym czasie rozpocz\u0119cia."],"This form is no longer accepting submissions. The submission period has ended.":["Formularz ten nie przyjmuje ju\u017c zg\u0142osze\u0144. Okres sk\u0142adania zg\u0142osze\u0144 dobieg\u0142 ko\u0144ca."],"The start date and time must be before the end date and time.":["Data i godzina rozpocz\u0119cia musz\u0105 by\u0107 przed dat\u0105 i godzin\u0105 zako\u0144czenia."],"Form Scheduling":["Planowanie formularza"],"Enable Form Scheduling":["W\u0142\u0105cz planowanie formularza"],"Set a time period during which this form will be available for submissions.":["Ustaw okres, w kt\u00f3rym ten formularz b\u0119dzie dost\u0119pny do sk\u0142adania zg\u0142osze\u0144."],"Start Date & Time":["Data i godzina rozpocz\u0119cia"],"End Date & Time":["Data i godzina zako\u0144czenia"],"Response Description Before Start Date":["Opis odpowiedzi przed dat\u0105 rozpocz\u0119cia"],"Response Description After End Date":["Opis odpowiedzi po dacie zako\u0144czenia"],"Conditional Confirmations":["Warunkowe potwierdzenia"],"Set up the message or redirect users will see after submitting the form.":["Ustaw wiadomo\u015b\u0107 lub przekierowanie, kt\u00f3re u\u017cytkownicy zobacz\u0105 po przes\u0142aniu formularza."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Wy\u015bwietl w\u0142a\u015bciw\u0105 wiadomo\u015b\u0107 w\u0142a\u015bciwemu u\u017cytkownikowi w zale\u017cno\u015bci od tego, jak odpowiadaj\u0105. Personalizuj potwierdzenia za pomoc\u0105 inteligentnych warunk\u00f3w i automatycznie kieruj u\u017cytkownik\u00f3w do kolejnego najlepszego kroku."],"Display different confirmation messages based on form responses.":["Wy\u015bwietlaj r\u00f3\u017cne wiadomo\u015bci potwierdzaj\u0105ce w zale\u017cno\u015bci od odpowiedzi w formularzu."],"Redirect users to specific pages or URLs conditionally.":["Przekieruj u\u017cytkownik\u00f3w na okre\u015blone strony lub adresy URL warunkowo."],"Create personalized thank-you messages without extra forms.":["Tw\u00f3rz spersonalizowane wiadomo\u015bci z podzi\u0119kowaniami bez dodatkowych formularzy."],"Lost Password":["Zapomniane has\u0142o"],"Reset Password":["Zresetuj has\u0142o"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["Wybierz us\u0142ug\u0119 ochrony przed spamem. Skonfiguruj klucze API w Ustawieniach Globalnych przed w\u0142\u0105czeniem."],"Send as Raw HTML":["Wy\u015blij jako surowy HTML"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Gdy jest w\u0142\u0105czone, tre\u015b\u0107 wiadomo\u015bci e-mail w formacie HTML zostanie zachowana dok\u0142adnie tak, jak zosta\u0142a napisana, i opakowana w profesjonalny szablon e-maila."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["Inteligentne tagi, kt\u00f3re odnosz\u0105 si\u0119 do p\u00f3l wprowadzonych przez u\u017cytkownika, nie b\u0119d\u0105 przetwarzane w trybie surowego HTML. Unikaj bezpo\u015bredniego wstawiania niezweryfikowanych warto\u015bci p\u00f3l do tre\u015bci e-maila."],"Please provide a recipient email address and subject line.":["Prosz\u0119 poda\u0107 adres e-mail odbiorcy oraz temat wiadomo\u015bci."],"Email notification duplicated!":["Powiadomienie e-mail zduplikowane!"],"Are you sure you want to delete this email notification?":["Czy na pewno chcesz usun\u0105\u0107 to powiadomienie e-mail?"],"Email notification deleted!":["Powiadomienie e-mail zosta\u0142o usuni\u0119te!"],"URL is missing Top Level Domain (TLD).":["Brakuje domeny najwy\u017cszego poziomu (TLD) w adresie URL."],"This form is now closed as the maximum number of entries has been received.":["Formularz zosta\u0142 zamkni\u0119ty, poniewa\u017c osi\u0105gni\u0119to maksymaln\u0105 liczb\u0119 zg\u0142osze\u0144."],"Publish Your Form":["Opublikuj sw\u00f3j formularz"],"Enable This to Instantly Publish the Form":["W\u0142\u0105cz to, aby natychmiast opublikowa\u0107 formularz"],"Style Your Instant Form Page Here":["Stylizuj swoj\u0105 stron\u0119 formularza natychmiastowego tutaj"],"Quizzes":["Quizy"],"%s - Description":["%s - Opis"],"Send entries to 100+ popular apps.":["Wy\u015blij wpisy do ponad 100 popularnych aplikacji."],"Build automated workflows that run instantly.":["Tw\u00f3rz zautomatyzowane przep\u0142ywy pracy, kt\u00f3re dzia\u0142aj\u0105 natychmiast."],"Create custom app integrations using our Custom App feature.":["Tw\u00f3rz niestandardowe integracje aplikacji za pomoc\u0105 funkcji Niestandardowa Aplikacja."],"Keep your tools in sync automatically.":["Utrzymuj swoje narz\u0119dzia w synchronizacji automatycznie."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["To zainstaluje i aktywuje OttoKit na Twojej stronie WordPress, aby w\u0142\u0105czy\u0107 funkcje automatyzacji."],"Automate Your Forms with OttoKit":["Zautomatyzuj swoje formularze za pomoc\u0105 OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Ka\u017cde przes\u0142anie formularza powinno wywo\u0142a\u0107 jak\u0105\u015b akcj\u0119 \u2014 alert w Slacku, lead w CRM, e-mail z przypomnieniem lub nowy wiersz w Arkuszach Google."],"Create interactive quizzes to engage your audience and gather insights.":["Tw\u00f3rz interaktywne quizy, aby zaanga\u017cowa\u0107 swoj\u0105 publiczno\u015b\u0107 i zbiera\u0107 informacje."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Projektuj anga\u017cuj\u0105ce quizy z r\u00f3\u017cnymi typami pyta\u0144, spersonalizowanymi opiniami i automatycznym ocenianiem, aby przyci\u0105gn\u0105\u0107 swoj\u0105 publiczno\u015b\u0107 i uzyska\u0107 cenne informacje."],"Create interactive quizzes with multiple question types.":["Tw\u00f3rz interaktywne quizy z r\u00f3\u017cnymi typami pyta\u0144."],"Provide personalized feedback based on user responses.":["Zapewnij spersonalizowan\u0105 opini\u0119 na podstawie odpowiedzi u\u017cytkownika."],"Automate scoring and lead segmentation for better insights.":["Zautomatyzuj ocenianie i segmentacj\u0119 lead\u00f3w, aby uzyska\u0107 lepsze wgl\u0105dy."],"Heading 1":["Nag\u0142\u00f3wek 1"],"Heading 2":["Nag\u0142\u00f3wek 2"],"Heading 3":["Nag\u0142\u00f3wek 3"],"Heading 4":["Nag\u0142\u00f3wek 4"],"Heading 5":["Nag\u0142\u00f3wek 5"],"Heading 6":["Nag\u0142\u00f3wek 6"],"Form settings saved.":["Ustawienia formularza zosta\u0142y zapisane."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["Limit wej\u015b\u0107 opiera si\u0119 na zapisanych wpisach do liczenia zg\u0142osze\u0144. Gdy w Ustawieniach Zgodno\u015bci jest w\u0142\u0105czona opcja \"Nigdy nie przechowuj danych wpisu po przes\u0142aniu formularza\", ten limit nie b\u0119dzie egzekwowany. Wy\u0142\u0105cz t\u0119 opcj\u0119 lub usu\u0144 limit wpis\u00f3w, aby skorzysta\u0107 z tej funkcji."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Ustawienia zosta\u0142y zapisane, ale atrybuty posta (has\u0142o \/ tytu\u0142 \/ tre\u015b\u0107) nie zosta\u0142y zaktualizowane. Spr\u00f3buj ponownie, aby je zachowa\u0107."],"Failed to save form settings.":["Nie uda\u0142o si\u0119 zapisa\u0107 ustawie\u0144 formularza."],"Saving\u2026":["Zapisywanie\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Po w\u0142\u0105czeniu tego formularza nie b\u0119d\u0105 przechowywane adres IP u\u017cytkownika, nazwa przegl\u0105darki i nazwa urz\u0105dzenia w wpisach."],"Failed to save. Please try again.":["Nie uda\u0142o si\u0119 zapisa\u0107. Prosz\u0119 spr\u00f3bowa\u0107 ponownie."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["Klucze API reCAPTCHA dla wybranej wersji nie s\u0105 skonfigurowane. Ustaw je w Ustawieniach Globalnych."],"Please select a reCAPTCHA version.":["Prosz\u0119 wybra\u0107 wersj\u0119 reCAPTCHA."],"hCaptcha API keys are not configured. Set them in Global Settings.":["Klucze API hCaptcha nie s\u0105 skonfigurowane. Ustaw je w Ustawieniach Globalnych."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["Klucze API Cloudflare Turnstile nie s\u0105 skonfigurowane. Ustaw je w Ustawieniach Globalnych."],"Form data":["Dane formularza"],"Some fields need attention":["Niekt\u00f3re pola wymagaj\u0105 uwagi"],"Unsaved changes":["Niezapisane zmiany"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Adres e-mail odbiorcy i temat s\u0105 wymagane przed zapisaniem tego powiadomienia. Popraw wyr\u00f3\u017cnione pola lub odrzu\u0107 zmiany, aby wr\u00f3ci\u0107."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Masz niezapisane zmiany dla tego powiadomienia. Odrzu\u0107 je, aby wr\u00f3ci\u0107, lub zosta\u0144, aby je zapisa\u0107."],"Discard & go back":["Odrzu\u0107 i wr\u00f3\u0107"],"Stay & fix":["Zosta\u0144 i napraw"],"Keep editing":["Kontynuuj edytowanie"],"Please provide a recipient email address.":["Prosz\u0119 poda\u0107 adres e-mail odbiorcy."],"Please provide a subject line.":["Prosz\u0119 poda\u0107 temat."],"Please provide a custom URL.":["Prosz\u0119 poda\u0107 niestandardowy adres URL."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Masz niezapisane zmiany. Odrzu\u0107 je, aby kontynuowa\u0107, lub zosta\u0144, aby zapisa\u0107 zmiany."],"Discard & continue":["Odrzu\u0107 i kontynuuj"],"Show Entries":["Poka\u017c wpisy"],"Display this form\u2019s entries in a table on the frontend.":["Wy\u015bwietl wpisy tego formularza w tabeli na froncie."],"Publish this form\u2019s submissions as a sortable, paginated table on any page \u2014 perfect for public directories, listings, and team dashboards, with per-role view access.":["Opublikuj zg\u0142oszenia tego formularza jako sortowaln\u0105, stronicowan\u0105 tabel\u0119 na dowolnej stronie \u2014 idealne dla publicznych katalog\u00f3w, list i pulpit\u00f3w zespo\u0142owych, z dost\u0119pem do widoku wed\u0142ug roli."],"Embed a sortable, paginated entries table with a simple shortcode.":["Osad\u017a sortowaln\u0105, stronicowan\u0105 tabel\u0119 wpis\u00f3w za pomoc\u0105 prostego shortcode."],"Choose exactly which fields appear as columns.":["Wybierz dok\u0142adnie, kt\u00f3re pola pojawi\u0105 si\u0119 jako kolumny."],"Restrict who can view entries by user role.":["Ogranicz, kto mo\u017ce przegl\u0105da\u0107 wpisy wed\u0142ug roli u\u017cytkownika."],"Quill heading picker: default paragraph style\u0004Normal":["Normalny"]}}} \ No newline at end of file diff --git a/languages/sureforms-pl_PL.mo b/languages/sureforms-pl_PL.mo index 09207617f3eef1544101dbdc8becf38306468ea8..894781d9aa962c54c5d9eff3058545a9ee6949ef 100644 GIT binary patch delta 72172 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%0A#@GAfNsxMv4;EqEiO#i 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 Bs&)VX diff --git a/languages/sureforms-pl_PL.po b/languages/sureforms-pl_PL.po index 212feeedc..ec0998b63 100644 --- a/languages/sureforms-pl_PL.po +++ b/languages/sureforms-pl_PL.po @@ -14,9 +14,9 @@ msgstr "" #. Plugin Name of the plugin #. Author of the plugin #: sureforms.php -#: admin/admin.php:391 -#: admin/admin.php:392 -#: admin/admin.php:1950 +#: admin/admin.php:406 +#: admin/admin.php:407 +#: admin/admin.php:1970 #: inc/abilities/abilities-registrar.php:139 #: inc/gutenberg-hooks.php:109 #: inc/page-builders/bricks/elements/form-widget.php:67 @@ -43,8 +43,8 @@ msgstr "Prosty, ale potężny sposób na tworzenie nowoczesnych formularzy na Tw msgid "https://sureforms.com/" msgstr "https://sureforms.com/" -#: admin/admin.php:404 -#: admin/admin.php:405 +#: admin/admin.php:419 +#: admin/admin.php:420 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -59,9 +59,9 @@ msgstr "https://sureforms.com/" msgid "Dashboard" msgstr "Panel sterowania" -#: admin/admin.php:422 -#: admin/admin.php:423 -#: admin/admin.php:855 +#: admin/admin.php:437 +#: admin/admin.php:438 +#: admin/admin.php:870 #: inc/compatibility/multilingual/string-collector.php:148 #: inc/compatibility/multilingual/string-collector.php:216 #: assets/build/blocks.js:172 @@ -78,15 +78,15 @@ msgstr "Panel sterowania" msgid "Settings" msgstr "Ustawienia" -#: admin/admin.php:691 -#: admin/admin.php:692 +#: admin/admin.php:706 +#: admin/admin.php:707 #: inc/post-types.php:231 msgid "New Form" msgstr "Nowy formularz" -#: admin/admin.php:700 -#: admin/admin.php:701 -#: admin/admin.php:1987 +#: admin/admin.php:715 +#: admin/admin.php:716 +#: admin/admin.php:2179 #: inc/global-settings/email-summary.php:225 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -102,7 +102,7 @@ msgid "Entries" msgstr "Wpisy" #. Translators: Post Title. -#: admin/admin.php:942 +#: admin/admin.php:958 #, php-format msgid "Edit %1$s" msgstr "Edytuj %1$s" @@ -117,24 +117,24 @@ msgstr "Edytuj %1$s" #: inc/export.php:176 #: inc/forms-data.php:88 #: inc/global-settings/global-settings.php:88 -#: inc/global-settings/global-settings.php:630 +#: inc/global-settings/global-settings.php:632 #: inc/rest-api.php:203 msgid "Nonce verification failed." msgstr "Weryfikacja nonce nie powiodła się." #. translators: %1$s: Opening anchor tag with URL, %2$s: Closing anchor tag, %3$s: SureForms Pro Plugin Name. -#: admin/admin.php:1579 +#: admin/admin.php:1596 #, php-format msgid "Please %1$sactivate%2$s your copy of %3$s to get new features, access support, receive update notifications, and more." msgstr "Proszę %1$saktywować%2$s swoją kopię %3$s, aby uzyskać nowe funkcje, dostęp do wsparcia, otrzymywać powiadomienia o aktualizacjach i więcej." #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version, %4$s: Anchor tag open, %5$s: Closing anchor tag. -#: admin/admin.php:1597 +#: admin/admin.php:1614 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version from %4$shere%5$s." msgstr "SureForms %1$s wymaga co najmniej %2$s %3$s, aby działać poprawnie. Proszę zaktualizować do najnowszej wersji z %4$stąd%5$s." -#: admin/admin.php:1986 +#: admin/admin.php:2178 #: inc/global-settings/email-summary.php:224 #: assets/build/entries.js:172 #: assets/build/payments.js:172 @@ -204,7 +204,7 @@ msgstr "Śmieci" msgid "Published" msgstr "Opublikowano" -#: admin/admin.php:1978 +#: admin/admin.php:2170 msgid "View" msgstr "Widok" @@ -296,11 +296,11 @@ msgstr "Nie określono wtyczki" msgid "Plugin Successfully Activated" msgstr "Wtyczka została pomyślnie aktywowana" -#: admin/admin.php:1014 +#: admin/admin.php:1030 msgid "Activating..." msgstr "Aktywowanie..." -#: admin/admin.php:1015 +#: admin/admin.php:1031 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/formEditor.js:172 @@ -315,17 +315,17 @@ msgstr "Aktywowanie..." msgid "Activated" msgstr "Aktywowany" -#: admin/admin.php:1016 +#: admin/admin.php:1032 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Activate" msgstr "Aktywuj" -#: admin/admin.php:1017 +#: admin/admin.php:1033 msgid "Installing..." msgstr "Instalowanie..." -#: admin/admin.php:1018 +#: admin/admin.php:1034 msgid "Installed" msgstr "Zainstalowano" @@ -333,10 +333,11 @@ msgstr "Zainstalowano" msgid "You do not have permission to access this page." msgstr "Nie masz uprawnień do dostępu do tej strony." -#: admin/admin.php:1745 -#: admin/admin.php:1844 -#: admin/admin.php:1882 -#: admin/admin.php:1903 +#: admin/admin.php:1762 +#: admin/admin.php:1861 +#: admin/admin.php:1899 +#: admin/admin.php:1920 +#: admin/admin.php:2133 #: inc/admin-ajax.php:162 #: inc/payments/admin/admin-handler.php:638 #: inc/payments/stripe/admin-stripe-handler.php:80 @@ -439,8 +440,8 @@ msgstr "Nie znaleziono adresu e-mail użytkownika w odszyfrowanych danych." msgid "The message array was not supplied" msgstr "Tablica wiadomości nie została dostarczona" -#: inc/ai-form-builder/ai-form-builder.php:68 -#: inc/ai-form-builder/ai-form-builder.php:87 +#: inc/ai-form-builder/ai-form-builder.php:80 +#: inc/ai-form-builder/ai-form-builder.php:99 #: inc/ai-form-builder/ai-helper.php:294 msgid "The SureForms AI Middleware encountered an error." msgstr "Oprogramowanie pośredniczące SureForms AI napotkało błąd." @@ -627,8 +628,8 @@ msgstr "Nie znaleziono formularzy." #: inc/abilities/settings/get-global-settings.php:192 #: inc/global-settings/email-summary.php:571 -#: inc/global-settings/global-settings.php:262 -#: inc/global-settings/global-settings.php:689 +#: inc/global-settings/global-settings.php:264 +#: inc/global-settings/global-settings.php:691 #: assets/build/settings.js:172 msgid "Monday" msgstr "Poniedziałek" @@ -662,7 +663,7 @@ msgstr "Wartość musi być unikalna." msgid "Sorry, you are not allowed to perform this action." msgstr "Przepraszam, nie masz uprawnień do wykonania tej czynności." -#: admin/admin.php:1643 +#: admin/admin.php:1660 msgid "Rate SureForms" msgstr "Oceń SureForms" @@ -720,6 +721,7 @@ msgstr "Edytuj formularz" msgid "Edit" msgstr "Edytuj" +#: admin/admin.php:2021 #: inc/page-builders/elementor/form-widget.php:346 #: assets/build/dashboard.js:2 msgid "Create New Form" @@ -756,8 +758,8 @@ msgstr "Wyświetl formularz" msgid "View Forms" msgstr "Wyświetl formularze" -#: admin/admin.php:682 -#: admin/admin.php:683 +#: admin/admin.php:697 +#: admin/admin.php:698 #: inc/post-types.php:235 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -802,7 +804,7 @@ msgstr "Powiadomienie e-mail dla administratora" #. translators: %s: Form title smart tag #. translators: %s: {form_title} smart tag placeholder. #: inc/global-settings/global-settings-defaults.php:173 -#: inc/global-settings/global-settings.php:586 +#: inc/global-settings/global-settings.php:588 #: inc/post-types.php:1005 #: assets/build/settings.js:172 #, php-format,js-format @@ -8880,9 +8882,9 @@ msgstr "Dodaj bloki do paska szybkich akcji" msgid "Re-arrange block inside Quick Action Bar" msgstr "Przestaw blok wewnątrz paska szybkich akcji" -#: admin/admin.php:475 -#: admin/admin.php:476 -#: admin/admin.php:2211 +#: admin/admin.php:490 +#: admin/admin.php:491 +#: admin/admin.php:2403 #: assets/build/blocks.js:172 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 @@ -9323,7 +9325,7 @@ msgstr "Podział strony" msgid "Previous" msgstr "Poprzedni" -#: inc/global-settings/global-settings.php:528 +#: inc/global-settings/global-settings.php:530 #: inc/migrator/base-migrator.php:548 #: inc/post-types.php:1081 #: assets/build/formEditor.js:172 @@ -10610,7 +10612,7 @@ msgstr "Odblokuj formularze konwersacyjne" msgid "With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience." msgstr "Dzięki planowi SureForms Pro możesz przekształcić swoje formularze w angażujące, konwersacyjne układy, zapewniając płynne doświadczenie użytkownika." -#: admin/admin.php:314 +#: admin/admin.php:329 msgid "Get SureForms Pro" msgstr "Pobierz SureForms Pro" @@ -11236,8 +11238,8 @@ msgstr "Opisz formularz, który chcesz utworzyć" msgid "We are building your form…" msgstr "Budujemy Twój formularz…" -#: admin/admin.php:590 -#: admin/admin.php:591 +#: admin/admin.php:605 +#: admin/admin.php:606 msgid "SMTP" msgstr "SMTP" @@ -11369,28 +11371,29 @@ msgstr "Włącz powiadomienia administratora" msgid "Admin notifications keep you informed about new form entries since your last visit." msgstr "Powiadomienia administracyjne informują Cię o nowych wpisach formularza od Twojej ostatniej wizyty." -#: admin/admin.php:1749 -#: admin/admin.php:1840 -#: admin/admin.php:1878 -#: admin/admin.php:1899 +#: admin/admin.php:1766 +#: admin/admin.php:1857 +#: admin/admin.php:1895 +#: admin/admin.php:1916 +#: admin/admin.php:2137 msgid "Unauthorized user." msgstr "Nieautoryzowany użytkownik." #. translators: 1: opening span, 2: opening strong (inline), 3: closing strong, 4: closing span, 5: opening strong (block), 6: closing strong -#: admin/admin.php:1849 +#: admin/admin.php:1866 #, php-format msgid "%1$sGet started by %2$sbuilding your first form%3$s.%4$s%5$sExperience the power of our intuitive AI Form Builder%6$s" msgstr "%1$sZacznij od %2$szbudowania swojego pierwszego formularza%3$s.%4$s%5$sDoświadcz mocy naszego intuicyjnego Kreatora Formularzy AI%6$s" -#: admin/admin.php:1860 +#: admin/admin.php:1877 msgid "SureForms is waiting for you!" msgstr "SureForms czeka na Ciebie!" -#: admin/admin.php:1862 +#: admin/admin.php:1879 msgid "Build My First Form" msgstr "Zbuduj mój pierwszy formularz" -#: admin/admin.php:1863 +#: admin/admin.php:1880 msgid "Dismiss" msgstr "Odrzuć" @@ -11410,51 +11413,51 @@ msgstr "Zaloguj się" msgid "Register" msgstr "Zarejestruj się" -#: admin/admin.php:1974 +#: admin/admin.php:2166 msgid "Recent Entries" msgstr "Ostatnie wpisy" -#: admin/admin.php:1975 +#: admin/admin.php:2167 msgid "( Last 7 days )" msgstr "( Ostatnie 7 dni )" -#: admin/admin.php:2152 +#: admin/admin.php:2344 msgid "Use Conditional Logic to show only what matters" msgstr "Użyj logiki warunkowej, aby pokazać tylko to, co ważne" -#: admin/admin.php:2153 +#: admin/admin.php:2345 msgid "Split your form into steps to keep it easy" msgstr "Podziel swój formularz na etapy, aby był łatwy." -#: admin/admin.php:2154 +#: admin/admin.php:2346 msgid "Let people upload files directly to your form" msgstr "Pozwól ludziom przesyłać pliki bezpośrednio do Twojego formularza" -#: admin/admin.php:2155 +#: admin/admin.php:2347 msgid "Turn responses into downloadable PDFs automatically" msgstr "Automatycznie przekształcaj odpowiedzi w pliki PDF do pobrania" -#: admin/admin.php:2156 +#: admin/admin.php:2348 msgid "Let users sign with a simple signature field" msgstr "Pozwól użytkownikom podpisać się w prostym polu podpisu" -#: admin/admin.php:2157 +#: admin/admin.php:2349 msgid "Connect your form to other tools using webhooks" msgstr "Połącz swój formularz z innymi narzędziami za pomocą webhooków" -#: admin/admin.php:2158 +#: admin/admin.php:2350 msgid "Use Conversational Forms for a chat-like experience" msgstr "Użyj Formularzy Konwersacyjnych, aby uzyskać doświadczenie podobne do czatu" -#: admin/admin.php:2159 +#: admin/admin.php:2351 msgid "Let users register or log in through your form" msgstr "Pozwól użytkownikom zarejestrować się lub zalogować przez Twój formularz" -#: admin/admin.php:2160 +#: admin/admin.php:2352 msgid "Build forms that create WordPress user accounts" msgstr "Twórz formularze, które tworzą konta użytkowników WordPress" -#: admin/admin.php:2161 +#: admin/admin.php:2353 msgid "Add calculations to auto-total scores or prices" msgstr "Dodaj obliczenia do automatycznego sumowania wyników lub cen" @@ -11944,8 +11947,8 @@ msgstr "Miniatura wideo SureForms" msgid "Expected format for emails - email@sureforms.com or John Doe " msgstr "Oczekiwany format dla e-maili - email@sureforms.com lub John Doe " -#: admin/admin.php:710 -#: admin/admin.php:711 +#: admin/admin.php:725 +#: admin/admin.php:726 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -12322,8 +12325,8 @@ msgstr "Nieznany" #: inc/payments/admin/admin-handler.php:1501 #: inc/payments/admin/admin-handler.php:1505 #: inc/payments/admin/admin-handler.php:1509 -#: inc/payments/front-end.php:865 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:866 +#: inc/payments/front-end.php:1275 msgid "Guest User" msgstr "Użytkownik gościnny" @@ -12336,7 +12339,7 @@ msgid "Valid customer email is required for payments." msgstr "Ważny adres e-mail klienta jest wymagany do płatności." #: inc/payments/front-end.php:115 -#: inc/payments/front-end.php:340 +#: inc/payments/front-end.php:341 #: inc/payments/stripe/admin-stripe-handler.php:861 #: inc/payments/stripe/payments-settings.php:349 #: inc/payments/stripe/payments-settings.php:474 @@ -12347,136 +12350,136 @@ msgid "Stripe is not connected." msgstr "Stripe nie jest połączony." #: inc/payments/front-end.php:121 -#: inc/payments/front-end.php:346 -#: inc/payments/front-end.php:634 -#: inc/payments/front-end.php:1153 +#: inc/payments/front-end.php:347 +#: inc/payments/front-end.php:635 +#: inc/payments/front-end.php:1154 msgid "Stripe secret key not found." msgstr "Nie znaleziono tajnego klucza Stripe." #. translators: %s: formatted payment amount -#: inc/payments/front-end.php:203 +#: inc/payments/front-end.php:204 #, php-format msgid "The payment amount (%s) is below the minimum allowed. Stripe only processes amounts above 50¢." msgstr "Kwota płatności (%s) jest poniżej dozwolonego minimum. Stripe przetwarza tylko kwoty powyżej 50¢." #. translators: %s: Field name -#: inc/payments/front-end.php:273 +#: inc/payments/front-end.php:274 #, php-format msgid "Missing required field: %s" msgstr "Brak wymaganego pola: %s" -#: inc/payments/front-end.php:292 +#: inc/payments/front-end.php:293 msgid "Valid customer email is required for subscriptions." msgstr "Ważny adres e-mail klienta jest wymagany do subskrypcji." -#: inc/payments/front-end.php:297 +#: inc/payments/front-end.php:298 msgid "Customer name is required for subscriptions." msgstr "Nazwa klienta jest wymagana do subskrypcji." -#: inc/payments/front-end.php:314 +#: inc/payments/front-end.php:315 msgid "Amount must be greater than 0" msgstr "Kwota musi być większa niż 0" -#: inc/payments/front-end.php:322 +#: inc/payments/front-end.php:323 msgid "Invalid billing interval" msgstr "Nieprawidłowy interwał rozliczeniowy" -#: inc/payments/front-end.php:357 +#: inc/payments/front-end.php:358 msgid "Failed to create customer for subscription." msgstr "Nie udało się utworzyć klienta dla subskrypcji." -#: inc/payments/front-end.php:390 -#: inc/payments/front-end.php:406 +#: inc/payments/front-end.php:391 +#: inc/payments/front-end.php:407 msgid "Failed to create subscription through middleware." msgstr "Nie udało się utworzyć subskrypcji za pośrednictwem oprogramowania pośredniczącego." -#: inc/payments/front-end.php:411 +#: inc/payments/front-end.php:412 msgid "Empty response from subscription creation." msgstr "Pusta odpowiedź z tworzenia subskrypcji." -#: inc/payments/front-end.php:416 +#: inc/payments/front-end.php:417 msgid "Invalid JSON response from subscription creation." msgstr "Nieprawidłowa odpowiedź JSON z tworzenia subskrypcji." -#: inc/payments/front-end.php:420 -#: inc/payments/front-end.php:424 +#: inc/payments/front-end.php:421 +#: inc/payments/front-end.php:425 msgid "Invalid subscription data." msgstr "Nieprawidłowe dane subskrypcji." -#: inc/payments/front-end.php:431 +#: inc/payments/front-end.php:432 msgid "Failed to create subscription." msgstr "Nie udało się utworzyć subskrypcji." #. translators: %s: Error message -#: inc/payments/front-end.php:464 +#: inc/payments/front-end.php:465 #, php-format msgid "Unexpected error: %s" msgstr "Nieoczekiwany błąd: %s" -#: inc/payments/front-end.php:604 +#: inc/payments/front-end.php:605 #: inc/payments/stripe/admin-stripe-handler.php:103 #: inc/payments/stripe/admin-stripe-handler.php:165 #: inc/payments/stripe/admin-stripe-handler.php:471 msgid "Subscription ID not found." msgstr "Nie znaleziono identyfikatora subskrypcji." -#: inc/payments/front-end.php:623 +#: inc/payments/front-end.php:624 msgid "Customer ID not found for the payment." msgstr "Nie znaleziono identyfikatora klienta dla płatności." -#: inc/payments/front-end.php:651 +#: inc/payments/front-end.php:652 msgid "Failed to retrieve setup intent." msgstr "Nie udało się pobrać intencji konfiguracji." -#: inc/payments/front-end.php:699 +#: inc/payments/front-end.php:700 msgid "Failed to update subscription." msgstr "Nie udało się zaktualizować subskrypcji." -#: inc/payments/front-end.php:707 +#: inc/payments/front-end.php:708 msgid "Latest invoice not found on subscription." msgstr "Nie znaleziono najnowszej faktury w subskrypcji." -#: inc/payments/front-end.php:720 +#: inc/payments/front-end.php:721 msgid "Failed to retrieve invoice." msgstr "Nie udało się pobrać faktury." -#: inc/payments/front-end.php:742 +#: inc/payments/front-end.php:743 msgid "Payment intent not found on invoice." msgstr "Nie znaleziono zamiaru płatności na fakturze." -#: inc/payments/front-end.php:757 +#: inc/payments/front-end.php:758 msgid "Failed to confirm payment." msgstr "Nie udało się potwierdzić płatności." -#: inc/payments/front-end.php:773 +#: inc/payments/front-end.php:774 msgid "Failed to retrieve subscription." msgstr "Nie udało się pobrać subskrypcji." -#: inc/payments/front-end.php:788 +#: inc/payments/front-end.php:789 msgid "Subscription not found for the payment." msgstr "Nie znaleziono subskrypcji dla płatności." #. translators: %d: User ID -#: inc/payments/front-end.php:863 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:864 +#: inc/payments/front-end.php:1275 #, php-format msgid "User ID: %d" msgstr "ID użytkownika: %d" #. translators: %s: Invoice status -#: inc/payments/front-end.php:871 +#: inc/payments/front-end.php:872 #, php-format msgid "Invoice Status: %s" msgstr "Status faktury: %s" #. translators: Title for subscription verification log -#: inc/payments/front-end.php:878 +#: inc/payments/front-end.php:879 msgid "Subscription Verification" msgstr "Weryfikacja subskrypcji" #. translators: %s: Subscription ID #. translators: %s: Stripe subscription ID -#: inc/payments/front-end.php:882 +#: inc/payments/front-end.php:883 #: inc/payments/stripe/admin-stripe-handler.php:185 #: inc/payments/stripe/admin-stripe-handler.php:487 #: inc/payments/stripe/stripe-webhook.php:572 @@ -12491,8 +12494,8 @@ msgstr "Identyfikator subskrypcji: %s" #. translators: %s: payment gateway name (e.g., Stripe) #. translators: %s: payment gateway #. translators: %s: Payment gateway name (e.g., Stripe). -#: inc/payments/front-end.php:884 -#: inc/payments/front-end.php:1285 +#: inc/payments/front-end.php:885 +#: inc/payments/front-end.php:1286 #: inc/payments/stripe/admin-stripe-handler.php:190 #: inc/payments/stripe/admin-stripe-handler.php:492 #: inc/payments/stripe/admin-stripe-handler.php:667 @@ -12506,13 +12509,13 @@ msgid "Payment Gateway: %s" msgstr "Bramka płatnicza: %s" #. translators: %s: Payment Intent ID -#: inc/payments/front-end.php:886 +#: inc/payments/front-end.php:887 #, php-format msgid "Payment Intent ID: %s" msgstr "Identyfikator intencji płatności: %s" #. translators: %s: Charge ID -#: inc/payments/front-end.php:888 +#: inc/payments/front-end.php:889 #: inc/payments/stripe/stripe-webhook.php:1084 #, php-format msgid "Charge ID: %s" @@ -12520,7 +12523,7 @@ msgstr "Identyfikator opłaty: %s" #. translators: %s: Subscription Status #. translators: %s: subscription status -#: inc/payments/front-end.php:890 +#: inc/payments/front-end.php:891 #: inc/payments/stripe/admin-stripe-handler.php:195 #: inc/payments/stripe/admin-stripe-handler.php:497 #, php-format @@ -12528,7 +12531,7 @@ msgid "Subscription Status: %s" msgstr "Status subskrypcji: %s" #. translators: %s: Customer ID -#: inc/payments/front-end.php:892 +#: inc/payments/front-end.php:893 #: inc/payments/stripe/stripe-webhook.php:1174 #, php-format msgid "Customer ID: %s" @@ -12536,8 +12539,8 @@ msgstr "ID klienta: %s" #. translators: 1: Amount, 2: Currency #. translators: %1$s: amount, %2$s: currency. -#: inc/payments/front-end.php:894 -#: inc/payments/front-end.php:1287 +#: inc/payments/front-end.php:895 +#: inc/payments/front-end.php:1288 #: inc/payments/stripe/stripe-webhook.php:1089 #: inc/payments/stripe/stripe-webhook.php:1166 #, php-format @@ -12546,34 +12549,34 @@ msgstr "Kwota: %1$s %2$s" #. translators: %s: Payment mode (e.g. Live or Test) #. translators: %s: payment mode -#: inc/payments/front-end.php:897 -#: inc/payments/front-end.php:1292 +#: inc/payments/front-end.php:898 +#: inc/payments/front-end.php:1293 #, php-format msgid "Mode: %s" msgstr "Tryb: %s" -#: inc/payments/front-end.php:931 +#: inc/payments/front-end.php:932 msgid "Failed to verify subscription." msgstr "Nie udało się zweryfikować subskrypcji." -#: inc/payments/front-end.php:1186 -#: inc/payments/front-end.php:1204 -#: inc/payments/front-end.php:1212 -#: inc/payments/front-end.php:1219 +#: inc/payments/front-end.php:1187 +#: inc/payments/front-end.php:1205 +#: inc/payments/front-end.php:1213 +#: inc/payments/front-end.php:1220 msgid "Failed to retrieve payment intent." msgstr "Nie udało się pobrać intencji płatności." -#: inc/payments/front-end.php:1227 +#: inc/payments/front-end.php:1228 msgid "Payment was not confirmed successfully." msgstr "Płatność nie została pomyślnie potwierdzona." -#: inc/payments/front-end.php:1279 +#: inc/payments/front-end.php:1280 msgid "Payment Verification" msgstr "Weryfikacja płatności" #. translators: %s: Stripe transaction ID #. translators: %s: Charge ID -#: inc/payments/front-end.php:1283 +#: inc/payments/front-end.php:1284 #: inc/payments/stripe/stripe-webhook.php:1162 #, php-format msgid "Transaction ID: %s" @@ -12581,18 +12584,18 @@ msgstr "ID transakcji: %s" #. translators: %s: payment status #. translators: %s: Status -#: inc/payments/front-end.php:1289 +#: inc/payments/front-end.php:1290 #: inc/payments/stripe/stripe-webhook.php:576 #: inc/payments/stripe/stripe-webhook.php:1168 #, php-format msgid "Status: %s" msgstr "Status: %s" -#: inc/payments/front-end.php:1395 +#: inc/payments/front-end.php:1396 msgid "Failed to create Stripe customer." msgstr "Nie udało się utworzyć klienta Stripe." -#: inc/payments/front-end.php:1459 +#: inc/payments/front-end.php:1460 msgid "Failed to create Stripe guest customer." msgstr "Nie udało się utworzyć gościa klienta Stripe." @@ -13457,7 +13460,7 @@ msgstr "Utworzono w ramach cyklu rozliczeniowego subskrypcji" msgid "Edit this form" msgstr "Edytuj ten formularz" -#: inc/global-settings/global-settings.php:529 +#: inc/global-settings/global-settings.php:531 #: inc/migrator/base-migrator.php:549 #: inc/post-types.php:1081 msgid "Your form has been submitted successfully. We'll review your details and get back to you soon." @@ -15080,7 +15083,7 @@ msgid "Failed to create duplicate form." msgstr "Nie udało się utworzyć duplikatu formularza." #: inc/payments/front-end.php:98 -#: inc/payments/front-end.php:303 +#: inc/payments/front-end.php:304 #: inc/payments/payment-helper.php:596 msgid "Invalid form configuration." msgstr "Nieprawidłowa konfiguracja formularza." @@ -15156,7 +15159,7 @@ msgstr "Wartość pola zmiennej kwoty jest wymagana." msgid "Payment amount below minimum. Minimum: %1$s, received %2$s." msgstr "Kwota płatności poniżej minimum. Minimum: %1$s, otrzymano %2$s." -#: inc/rest-api.php:1839 +#: inc/rest-api.php:1851 msgid " (Copy)" msgstr "(Kopiuj)" @@ -15355,12 +15358,12 @@ msgid "Stripe Settings" msgstr "Ustawienia Stripe" #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version. -#: admin/admin.php:1514 +#: admin/admin.php:1531 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version." msgstr "SureForms %1$s wymaga co najmniej %2$s %3$s do prawidłowego działania. Proszę zaktualizować do najnowszej wersji." -#: admin/admin.php:1527 +#: admin/admin.php:1544 msgid "Update Now" msgstr "Zaktualizuj teraz" @@ -15408,8 +15411,8 @@ msgstr "Pozycja znaku waluty" msgid "Select the position of the currency symbol relative to the amount." msgstr "Wybierz pozycję symbolu waluty względem kwoty." -#: admin/admin.php:653 -#: admin/admin.php:654 +#: admin/admin.php:668 +#: admin/admin.php:669 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -15423,40 +15426,40 @@ msgstr "Wybierz pozycję symbolu waluty względem kwoty." msgid "Learn" msgstr "Ucz się" -#: admin/admin.php:1640 +#: admin/admin.php:1657 msgid "Amazing! SureForms is powering your forms and submissions - let's keep growing together!" msgstr "Niesamowite! SureForms napędza Twoje formularze i zgłoszenia - rozwijajmy się razem!" -#: admin/admin.php:1641 +#: admin/admin.php:1658 msgid "If SureForms has been helpful, would you mind taking a moment to leave a 5-star review on WordPress.org?" msgstr "Jeśli SureForms okazał się pomocny, czy mógłbyś poświęcić chwilę na pozostawienie 5-gwiazdkowej recenzji na WordPress.org?" -#: admin/admin.php:1644 -#: admin/admin.php:1688 +#: admin/admin.php:1661 +#: admin/admin.php:1705 msgid "Maybe later" msgstr "Może później" -#: admin/admin.php:1645 +#: admin/admin.php:1662 msgid "I already did" msgstr "Już to zrobiłem" -#: admin/admin.php:1684 +#: admin/admin.php:1701 msgid "SureForms is ready to power your forms — explore what's possible!" msgstr "SureForms jest gotowy, aby zasilić Twoje formularze — odkryj, co jest możliwe!" -#: admin/admin.php:1685 +#: admin/admin.php:1702 msgid "Manage your forms, track submissions, and discover features like AI Form Builder, payment integrations, and more from the SureForms dashboard." msgstr "Zarządzaj swoimi formularzami, śledź zgłoszenia i odkrywaj funkcje takie jak AI Form Builder, integracje płatności i inne z poziomu pulpitu SureForms." -#: admin/admin.php:1687 +#: admin/admin.php:1704 msgid "Go to Dashboard" msgstr "Przejdź do pulpitu nawigacyjnego" -#: admin/admin.php:1689 +#: admin/admin.php:1706 msgid "I already know" msgstr "Już wiem" -#: admin/admin.php:1769 +#: admin/admin.php:1786 msgid "Invalid parameters." msgstr "Nieprawidłowe parametry." @@ -15850,7 +15853,7 @@ msgstr "Ten formularz nie jest jeszcze dostępny. Sprawdź ponownie po zaplanowa #: inc/form-submit.php:1015 #: inc/migrator/bootstrap.php:357 #: inc/payments/front-end.php:78 -#: inc/payments/front-end.php:263 +#: inc/payments/front-end.php:264 #: inc/rest-api.php:124 #: inc/rest-api.php:183 #: inc/rest-api.php:349 @@ -15863,8 +15866,8 @@ msgstr "Ten formularz nie jest jeszcze dostępny. Sprawdź ponownie po zaplanowa #: inc/rest-api.php:742 #: inc/rest-api.php:775 #: inc/rest-api.php:914 -#: inc/rest-api.php:987 -#: inc/rest-api.php:1047 +#: inc/rest-api.php:999 +#: inc/rest-api.php:1059 #: inc/single-form-settings/form-settings-api.php:89 msgid "Security verification failed. Please refresh the page and try again." msgstr "Weryfikacja bezpieczeństwa nie powiodła się. Odśwież stronę i spróbuj ponownie." @@ -16163,28 +16166,28 @@ msgstr "Nieprawidłowa akcja. Użyj \"przeczytane\" lub \"nieprzeczytane\"." msgid "Invalid action. Use \"trash\" or \"restore\"." msgstr "Nieprawidłowa akcja. Użyj \"trash\" lub \"restore\"." -#: inc/rest-api.php:1061 +#: inc/rest-api.php:1073 msgid "Select at least one form and specify an action." msgstr "Wybierz co najmniej jeden formularz i określ działanie." -#: inc/rest-api.php:1076 +#: inc/rest-api.php:1088 msgid "Form not found or is not a valid form type." msgstr "Formularz nie został znaleziony lub nie jest prawidłowym typem formularza." -#: inc/rest-api.php:1088 +#: inc/rest-api.php:1100 msgid "This form is already in the trash." msgstr "Ten formularz jest już w koszu." -#: inc/rest-api.php:1099 +#: inc/rest-api.php:1111 msgid "This form is not in the trash." msgstr "Ten formularz nie znajduje się w koszu." -#: inc/rest-api.php:1135 +#: inc/rest-api.php:1147 msgid "Invalid action." msgstr "Nieprawidłowa akcja." #. translators: %s: action name -#: inc/rest-api.php:1150 +#: inc/rest-api.php:1162 #, php-format msgid "Failed to %s this form. Please try again." msgstr "Nie udało się %s tego formularza. Proszę spróbować ponownie." @@ -16202,32 +16205,32 @@ msgid "Enter a valid email address." msgstr "Wprowadź prawidłowy adres e-mail." #. translators: %s represents the minimum acceptable value -#: inc/translatable.php:97 +#: inc/translatable.php:103 #, php-format msgid "Minimum value is %s." msgstr "Minimalna wartość to %s." #. translators: %s represents the maximum acceptable value -#: inc/translatable.php:100 +#: inc/translatable.php:106 #, php-format msgid "Maximum value is %s." msgstr "Maksymalna wartość to %s." #. translators: %s represents the minimum number of options to select -#: inc/translatable.php:103 #: inc/translatable.php:109 +#: inc/translatable.php:115 #, php-format msgid "Select at least %s options." msgstr "Wybierz co najmniej %s opcje." #. translators: %s represents the maximum number of options that can be selected -#: inc/translatable.php:106 #: inc/translatable.php:112 +#: inc/translatable.php:118 #, php-format msgid "You can select up to %s options." msgstr "Możesz wybrać maksymalnie %s opcji." -#: inc/translatable.php:151 +#: inc/translatable.php:159 msgid "This form is now closed as we have reached the maximum number of entries." msgstr "Formularz został zamknięty, ponieważ osiągnęliśmy maksymalną liczbę zgłoszeń." @@ -16423,7 +16426,7 @@ msgid "Duplicate form?" msgstr "Powielić formularz?" #: assets/build/formSubmit.js:2 -#: assets/js/unminified/form-submit.js:487 +#: assets/js/unminified/form-submit.js:495 msgid "An error occurred while submitting your form. Please try again." msgstr "Wystąpił błąd podczas przesyłania formularza. Proszę spróbować ponownie." @@ -16750,35 +16753,35 @@ msgstr "Pobierz globalne ustawienia SureForms. Opcjonalnie filtruj według kateg msgid "Setting categories to retrieve. Omit for all categories." msgstr "Ustawianie kategorii do pobrania. Pomiń, aby pobrać wszystkie kategorie." -#: inc/abilities/settings/update-global-settings.php:106 +#: inc/abilities/settings/update-global-settings.php:108 msgid "Update Global Settings" msgstr "Zaktualizuj ustawienia globalne" -#: inc/abilities/settings/update-global-settings.php:107 +#: inc/abilities/settings/update-global-settings.php:109 msgid "Update SureForms global settings for a specific category: general, validation-messages, email-summary, or security." msgstr "Zaktualizuj globalne ustawienia SureForms dla określonej kategorii: ogólne, komunikaty walidacyjne, podsumowanie e-mail lub bezpieczeństwo." -#: inc/abilities/settings/update-global-settings.php:140 +#: inc/abilities/settings/update-global-settings.php:142 msgid "The settings category to update." msgstr "Kategoria ustawień do zaktualizowania." -#: inc/abilities/settings/update-global-settings.php:145 +#: inc/abilities/settings/update-global-settings.php:147 msgid "Key-value pairs of settings to update." msgstr "Pary klucz-wartość ustawień do zaktualizowania." -#: inc/abilities/settings/update-global-settings.php:181 +#: inc/abilities/settings/update-global-settings.php:183 msgid "Settings category is required." msgstr "Kategoria ustawień jest wymagana." -#: inc/abilities/settings/update-global-settings.php:189 +#: inc/abilities/settings/update-global-settings.php:191 msgid "Settings data is required." msgstr "Wymagane są dane ustawień." -#: inc/abilities/settings/update-global-settings.php:200 +#: inc/abilities/settings/update-global-settings.php:202 msgid "No valid settings keys provided for this category." msgstr "Nie podano prawidłowych kluczy ustawień dla tej kategorii." -#: inc/abilities/settings/update-global-settings.php:226 +#: inc/abilities/settings/update-global-settings.php:228 msgid "Invalid settings category." msgstr "Nieprawidłowa kategoria ustawień." @@ -16794,19 +16797,19 @@ msgstr "Tajny klucz webhooka nie jest skonfigurowany." msgid "Invalid webhook signature." msgstr "Nieprawidłowy podpis webhooka." -#: admin/admin.php:492 -#: admin/admin.php:1085 +#: admin/admin.php:507 +#: admin/admin.php:1101 #: assets/build/formEditor.js:172 msgid "Quizzes" msgstr "Quizy" -#: admin/admin.php:491 +#: admin/admin.php:506 msgid "Quiz Entries" msgstr "Wpisy do quizu" -#: admin/admin.php:494 -#: admin/admin.php:527 -#: admin/admin.php:560 +#: admin/admin.php:509 +#: admin/admin.php:542 +#: admin/admin.php:575 #: assets/build/settings.js:172 msgid "New" msgstr "Nowy" @@ -17324,43 +17327,43 @@ msgstr "Idealne do edukacji, szkoleń i zabawnych ciekawostek" msgid "Select this to create a quiz with scored questions and graded results." msgstr "Wybierz to, aby utworzyć quiz z punktowanymi pytaniami i ocenianymi wynikami." -#: admin/admin.php:524 -#: admin/admin.php:525 -#: admin/admin.php:1090 +#: admin/admin.php:539 +#: admin/admin.php:540 +#: admin/admin.php:1106 msgid "Survey Reports" msgstr "Raporty z ankiet" -#: inc/frontend-assets.php:301 +#: inc/frontend-assets.php:304 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 1" msgstr "Nagłówek 1" -#: inc/frontend-assets.php:302 +#: inc/frontend-assets.php:305 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 2" msgstr "Nagłówek 2" -#: inc/frontend-assets.php:303 +#: inc/frontend-assets.php:306 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 3" msgstr "Nagłówek 3" -#: inc/frontend-assets.php:304 +#: inc/frontend-assets.php:307 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 4" msgstr "Nagłówek 4" -#: inc/frontend-assets.php:305 +#: inc/frontend-assets.php:308 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 5" msgstr "Nagłówek 5" -#: inc/frontend-assets.php:306 +#: inc/frontend-assets.php:309 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 6" @@ -17694,15 +17697,15 @@ msgstr "Nieprawidłowy post." msgid "You cannot edit this post." msgstr "Nie możesz edytować tego posta." -#: inc/ai-form-builder/ai-form-builder.php:96 +#: inc/ai-form-builder/ai-form-builder.php:108 msgid "The AI did not return a form. Please refine your prompt and try again." msgstr "Sztuczna inteligencja nie zwróciła formularza. Proszę dopracować swój prompt i spróbować ponownie." -#: inc/ai-form-builder/ai-form-builder.php:104 +#: inc/ai-form-builder/ai-form-builder.php:116 msgid "The AI response is missing a form title. Please try again." msgstr "Odpowiedź AI nie zawiera tytułu formularza. Proszę spróbować ponownie." -#: inc/ai-form-builder/ai-form-builder.php:115 +#: inc/ai-form-builder/ai-form-builder.php:127 #: inc/ai-form-builder/field-mapping.php:69 msgid "The AI was unable to generate form fields. Please try again." msgstr "Sztuczna inteligencja nie była w stanie wygenerować pól formularza. Proszę spróbować ponownie." @@ -17743,7 +17746,7 @@ msgstr "Płatność jednorazowa" msgid "Choose payment type" msgstr "Wybierz rodzaj płatności" -#: inc/payments/front-end.php:333 +#: inc/payments/front-end.php:334 msgid "Billing interval does not match the form configuration." msgstr "Okres rozliczeniowy nie pasuje do konfiguracji formularza." @@ -17887,15 +17890,15 @@ msgstr "Odpowiedź AI była pusta. Proszę dopracować swój prompt i spróbowa msgid "Unable to build form fields from the AI response." msgstr "Nie można utworzyć pól formularza z odpowiedzi AI." -#: admin/admin.php:557 -#: admin/admin.php:558 -#: admin/admin.php:1095 +#: admin/admin.php:572 +#: admin/admin.php:573 +#: admin/admin.php:1111 msgid "Partial Entries" msgstr "Częściowe wpisy" #: inc/global-settings/global-settings-defaults.php:260 -#: inc/global-settings/global-settings.php:425 -#: inc/global-settings/global-settings.php:613 +#: inc/global-settings/global-settings.php:427 +#: inc/global-settings/global-settings.php:615 #: assets/build/settings.js:172 msgid "This form is now closed as we've received all the entries." msgstr "Formularz jest teraz zamknięty, ponieważ otrzymaliśmy wszystkie zgłoszenia." @@ -17904,16 +17907,16 @@ msgstr "Formularz jest teraz zamknięty, ponieważ otrzymaliśmy wszystkie zgło msgid "Invalid settings tab." msgstr "Nieprawidłowa karta ustawień." -#: inc/global-settings/global-settings.php:481 +#: inc/global-settings/global-settings.php:483 #: assets/build/settings.js:172 msgid "Thank you for contacting us! We will be in touch with you shortly." msgstr "Dziękujemy za skontaktowanie się z nami! Wkrótce się z Tobą skontaktujemy." -#: inc/rest-api.php:1115 +#: inc/rest-api.php:1127 msgid "Use the restore action to recover a trashed form before switching it to draft." msgstr "Użyj akcji przywracania, aby odzyskać usunięty formularz przed przełączeniem go na wersję roboczą." -#: inc/rest-api.php:1120 +#: inc/rest-api.php:1132 msgid "This form is already a draft." msgstr "Ten formularz jest już szkicem." @@ -18760,6 +18763,72 @@ msgstr "Nie można zweryfikować kwoty płatności dla tego formularza. Proszę msgid "Cancellation is not supported for this payment gateway." msgstr "Anulowanie nie jest obsługiwane dla tej bramki płatności." +#: admin/admin.php:1988 +msgid "SureForms AI Quick Draft" +msgstr "SureForms AI Szybki Szkic" + +#: admin/admin.php:2007 +msgid "Describe the form and let SureForms AI generate it for you." +msgstr "Opisz formularz, a SureForms AI wygeneruje go dla Ciebie." + +#: admin/admin.php:2010 +msgid "Describe your form" +msgstr "Opisz swój formularz" + +#: admin/admin.php:2017 +msgid "Example: Create a contact form with name, email, phone, and message fields." +msgstr "Przykład: Utwórz formularz kontaktowy z polami na imię, email, telefon i wiadomość." + +#: admin/admin.php:2058 +msgid "Redirecting..." +msgstr "Przekierowywanie..." + +#: inc/ai-form-builder/ai-form-builder.php:57 +msgid "No prompt was supplied." +msgstr "Nie podano żadnej podpowiedzi." + +#: inc/ai-form-builder/ai-form-builder.php:64 +msgid "The prompt is too long. Please shorten it and try again." +msgstr "Podpowiedź jest zbyt długa. Skróć ją i spróbuj ponownie." + +#. translators: %s: maximum characters allowed before the @ symbol. +#: inc/field-validation.php:316 +#: inc/translatable.php:97 +#, php-format +msgid "The part before @ may not exceed %s characters." +msgstr "Część przed @ nie może przekraczać %s znaków." + +#. translators: %s: maximum characters allowed after the @ symbol. +#: inc/field-validation.php:322 +#: inc/translatable.php:100 +#, php-format +msgid "The part after @ may not exceed %s characters." +msgstr "Część po @ nie może przekraczać %s znaków." + +#: assets/build/formEditor.js:172 +msgid "Show Entries" +msgstr "Pokaż wpisy" + +#: assets/build/formEditor.js:172 +msgid "Display this form’s entries in a table on the frontend." +msgstr "Wyświetl wpisy tego formularza w tabeli na froncie." + +#: assets/build/formEditor.js:172 +msgid "Publish this form’s submissions as a sortable, paginated table on any page — perfect for public directories, listings, and team dashboards, with per-role view access." +msgstr "Opublikuj zgłoszenia tego formularza jako sortowalną, stronicowaną tabelę na dowolnej stronie — idealne dla publicznych katalogów, list i pulpitów zespołowych, z dostępem do widoku według roli." + +#: assets/build/formEditor.js:172 +msgid "Embed a sortable, paginated entries table with a simple shortcode." +msgstr "Osadź sortowalną, stronicowaną tabelę wpisów za pomocą prostego shortcode." + +#: assets/build/formEditor.js:172 +msgid "Choose exactly which fields appear as columns." +msgstr "Wybierz dokładnie, które pola pojawią się jako kolumny." + +#: assets/build/formEditor.js:172 +msgid "Restrict who can view entries by user role." +msgstr "Ogranicz, kto może przeglądać wpisy według roli użytkownika." + #: inc/post-types.php:226 msgctxt "post type general name" msgid "Forms" @@ -19184,34 +19253,34 @@ msgctxt "block keyword" msgid "color" msgstr "kolor" -#: inc/frontend-assets.php:300 +#: inc/frontend-assets.php:303 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgctxt "Quill heading picker: default paragraph style" msgid "Normal" msgstr "Normalny" -#: inc/frontend-assets.php:307 +#: inc/frontend-assets.php:310 msgctxt "Quill link tooltip label" msgid "Visit URL:" msgstr "Odwiedź URL:" -#: inc/frontend-assets.php:308 +#: inc/frontend-assets.php:311 msgctxt "Quill link tooltip label" msgid "Enter link:" msgstr "Wprowadź link:" -#: inc/frontend-assets.php:309 +#: inc/frontend-assets.php:312 msgctxt "Quill link tooltip action" msgid "Edit" msgstr "Edytuj" -#: inc/frontend-assets.php:310 +#: inc/frontend-assets.php:313 msgctxt "Quill link tooltip action" msgid "Save" msgstr "Zapisz" -#: inc/frontend-assets.php:311 +#: inc/frontend-assets.php:314 msgctxt "Quill link tooltip action" msgid "Remove" msgstr "Usuń" diff --git a/languages/sureforms-pt_PT-1cb9ecd067cd971ff5d9db0b4dae2891.json b/languages/sureforms-pt_PT-1cb9ecd067cd971ff5d9db0b4dae2891.json index b5fc8859d..8d34aa530 100644 --- a/languages/sureforms-pt_PT-1cb9ecd067cd971ff5d9db0b4dae2891.json +++ b/languages/sureforms-pt_PT-1cb9ecd067cd971ff5d9db0b4dae2891.json @@ -1 +1 @@ -{"translation-revision-date":"2025-01-02T08:44:42+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Status"],"Form":["Formul\u00e1rio"],"Fields":["Campos"],"Image":["Imagem"],"Activated":["Ativado"],"Activate":["Ativar"],"Submit":["Enviar"],"Global Settings":["Configura\u00e7\u00f5es Globais"],"Form Title":["T\u00edtulo do Formul\u00e1rio"],"Edit":["Editar"],"Please enter a valid URL.":["Por favor, insira um URL v\u00e1lido."],"Desktop":["\u00c1rea de trabalho"],"Medium":["M\u00e9dio"],"Mobile":["M\u00f3vel"],"Repeat":["Repetir"],"Scroll":["Rolar"],"Signature":["Assinatura"],"Tablet":["Tablet"],"Upload":["Carregar"],"Basic":["B\u00e1sico"],"Form Settings":["Configura\u00e7\u00f5es do Formul\u00e1rio"],"General":["Geral"],"Style":["Estilo"],"Advanced":["Avan\u00e7ado"],"No tags available":["Sem etiquetas dispon\u00edveis"],"Device":["Dispositivo"],"Select Shortcodes":["Selecionar C\u00f3digos Curtos"],"Page Break Label":["R\u00f3tulo de Quebra de P\u00e1gina"],"Next":["Pr\u00f3ximo"],"Back":["Voltar"],"Reset":["Redefinir"],"Generic tags":["Etiquetas gen\u00e9ricas"],"Pixel":["Pixel"],"Em":["Em"],"Select Units":["Selecionar Unidades"],"%s units":["%s unidades"],"Margin":["Margem"],"None":["Nenhum"],"Custom":["Personalizado"],"Please add a option props to MultiButtonsControl":["Por favor, adicione uma op\u00e7\u00e3o de propriedades ao MultiButtonsControl"],"Confirm":["Confirmar"],"Cancel":["Cancelar"],"Processing\u2026":["Processando\u2026"],"Select Video":["Selecionar V\u00eddeo"],"Change Video":["Alterar V\u00eddeo"],"Select Lottie Animation":["Selecionar Anima\u00e7\u00e3o Lottie"],"Change Lottie Animation":["Alterar Anima\u00e7\u00e3o Lottie"],"Upload SVG":["Carregar SVG"],"Change SVG":["Alterar SVG"],"Select Image":["Selecionar imagem"],"Change Image":["Alterar Imagem"],"Upload SVG?":["Carregar SVG?"],"Upload SVG can be potentially risky. Are you sure?":["Fazer upload de SVG pode ser potencialmente arriscado. Tem certeza?"],"Upload Anyway":["Carregar Mesmo Assim"],"Full Width":["Largura Total"],"Install":["Instalar"],"Plugin Installation failed, Please try again later.":["A instala\u00e7\u00e3o do plugin falhou, por favor, tente novamente mais tarde."],"Plugin activation failed, Please try again later.":["A ativa\u00e7\u00e3o do plugin falhou, por favor, tente novamente mais tarde."],"Integrations":["Integra\u00e7\u00f5es"],"%s Removed from Quick Action Bar.":["%s removido da Barra de A\u00e7\u00e3o R\u00e1pida."],"Add to Quick Action Bar":["Adicionar \u00e0 Barra de A\u00e7\u00f5es R\u00e1pidas"],"%s Added to Quick Action Bar.":["%s adicionado \u00e0 barra de a\u00e7\u00f5es r\u00e1pidas."],"Already Present in Quick Action Bar":["J\u00e1 presente na barra de a\u00e7\u00f5es r\u00e1pidas"],"No results found.":["Nenhum resultado encontrado."],"data object is empty":["o objeto de dados est\u00e1 vazio"],"Add blocks to Quick Action Bar":["Adicionar blocos \u00e0 Barra de A\u00e7\u00e3o R\u00e1pida"],"Re-arrange block inside Quick Action Bar":["Reorganizar bloco dentro da Barra de A\u00e7\u00e3o R\u00e1pida"],"Upgrade":["Atualizar"],"Connecting\u2026":["Conectando\u2026"],"Install & Activate":["Instalar e Ativar"],"Compliance Settings":["Configura\u00e7\u00f5es de Conformidade"],"Enable GDPR Compliance":["Ativar conformidade com o GDPR"],"Never store entry data after form submission":["Nunca armazene os dados de entrada ap\u00f3s o envio do formul\u00e1rio"],"When enabled this form will never store Entries.":["Quando ativado, este formul\u00e1rio nunca armazenar\u00e1 entradas."],"Automatically delete entries":["Excluir entradas automaticamente"],"When enabled this form will automatically delete entries after a certain period of time.":["Quando ativado, este formul\u00e1rio excluir\u00e1 automaticamente as entradas ap\u00f3s um determinado per\u00edodo de tempo."],"Entries older than the days set will be deleted automatically.":["As entradas mais antigas do que os dias definidos ser\u00e3o exclu\u00eddas automaticamente."],"Custom CSS":["CSS Personalizado"],"The following CSS styles added below will only apply to this form container.":["Os estilos CSS a seguir adicionados abaixo ser\u00e3o aplicados apenas a este cont\u00eainer de formul\u00e1rio."],"Visual":["Visual"],"HTML":["HTML"],"All Data":["Todos os dados"],"Add Shortcode":["Adicionar Shortcode"],"Form input tags":["Etiquetas de entrada de formul\u00e1rio"],"Comma separated values are also accepted.":["Valores separados por v\u00edrgula tamb\u00e9m s\u00e3o aceitos."],"Email Notification":["Notifica\u00e7\u00e3o por Email"],"Name":["Nome"],"Send Email To":["Enviar Email Para"],"Subject":["Assunto"],"CC":["CC"],"BCC":["Cco"],"Reply To":["Responder a"],"Add Notification":["Adicionar Notifica\u00e7\u00e3o"],"Add Key":["Adicionar Chave"],"Add Value":["Adicionar Valor"],"Add":["Adicionar"],"Confirmation Message":["Mensagem de Confirma\u00e7\u00e3o"],"After Form Submission":["Ap\u00f3s o Envio do Formul\u00e1rio"],"Hide Form":["Ocultar Formul\u00e1rio"],"Reset Form":["Redefinir Formul\u00e1rio"],"Custom URL":["URL personalizada"],"Add Query Parameters":["Adicionar Par\u00e2metros de Consulta"],"Select if you want to add key-value pairs for form fields to include in query parameters":["Selecione se deseja adicionar pares chave-valor para campos de formul\u00e1rio a serem inclu\u00eddos nos par\u00e2metros de consulta"],"Query Parameters":["Par\u00e2metros de Consulta"],"Please select a page.":["Por favor, selecione uma p\u00e1gina."],"Suggestion: URL should use HTTPS":["Sugest\u00e3o: a URL deve usar HTTPS"],"Success Message":["Mensagem de Sucesso"],"Redirect":["Redirecionar"],"Redirect to":["Redirecionar para"],"Page":["P\u00e1gina"],"Form Confirmation":["Confirma\u00e7\u00e3o de Formul\u00e1rio"],"Confirmation Type":["Tipo de Confirma\u00e7\u00e3o"],"Use Labels as Placeholders":["Use r\u00f3tulos como marcadores de posi\u00e7\u00e3o"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["A configura\u00e7\u00e3o acima colocar\u00e1 os r\u00f3tulos dentro dos campos como marcadores de posi\u00e7\u00e3o (quando poss\u00edvel). Esta configura\u00e7\u00e3o s\u00f3 tem efeito na p\u00e1gina ao vivo, n\u00e3o na pr\u00e9-visualiza\u00e7\u00e3o do editor."],"Page Break":["Quebra de p\u00e1gina"],"Show Labels":["Mostrar R\u00f3tulos"],"First Page Label":["Etiqueta da Primeira P\u00e1gina"],"Progress Indicator":["Indicador de Progresso"],"Progress Bar":["Barra de Progresso"],"Connector":["Conector"],"Steps":["Passos"],"Next Button Text":["Texto do Bot\u00e3o Seguinte"],"Back Button Text":["Texto do Bot\u00e3o Voltar"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["Tem certeza de que deseja fechar? Suas altera\u00e7\u00f5es n\u00e3o salvas ser\u00e3o perdidas, pois voc\u00ea tem alguns erros de valida\u00e7\u00e3o."],"There are few unsaved changes. Please save your changes to reflect the updates.":["H\u00e1 algumas altera\u00e7\u00f5es n\u00e3o salvas. Por favor, salve suas altera\u00e7\u00f5es para refletir as atualiza\u00e7\u00f5es."],"Form Behavior":["Comportamento do Formul\u00e1rio"],"Clear":["Limpar"],"Select Color":["Selecionar Cor"],"Primary Color":["Cor Prim\u00e1ria"],"Text Color":["Cor do Texto"],"Text Color on Primary":["Cor do Texto no Prim\u00e1rio"],"Field Spacing":["Espa\u00e7amento de Campo"],"Small":["Pequeno"],"Large":["Grande"],"Left":["Esquerda"],"Center":["Centro"],"Right":["Certo"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Invis\u00edvel"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Seletor de Data"],"Time Picker":["Seletor de Hora"],"Hidden":["Oculto"],"Slider":["Controle deslizante"],"Rating":["Avalia\u00e7\u00e3o"],"Upgrade to Unlock These Fields":["Atualize para Desbloquear Estes Campos"],"Add Block":["Adicionar Bloco"],"Customize with SureForms":["Personalize com SureForms"],"Page break":["Quebra de p\u00e1gina"],"Previous":["Anterior"],"Thank you":["Obrigado"],"Form submitted successfully!":["Formul\u00e1rio enviado com sucesso!"],"Instant Form":["Formul\u00e1rio Instant\u00e2neo"],"Enable Instant Form":["Ativar Formul\u00e1rio Instant\u00e2neo"],"Enable Preview":["Ativar visualiza\u00e7\u00e3o"],"Show Title":["T\u00edtulo do Show"],"Site Logo":["Logotipo do Site"],"Banner Background":["Fundo do Banner"],"Color":["Cor"],"Upload Image":["Carregar Imagem"],"Background Color":["Cor de Fundo"],"Use banner as page background":["Usar banner como fundo de p\u00e1gina"],"Form Width":["Largura do Formul\u00e1rio"],"URL":["URL"],"URL Slug":["Slug de URL"],"The last part of the URL.":["A \u00faltima parte do URL."],"Learn more.":["Saiba mais."],"SureForms Description":["Descri\u00e7\u00e3o do SureForms"],"Form Options":["Op\u00e7\u00f5es de Formul\u00e1rio"],"Form Shortcode":["C\u00f3digo Curto do Formul\u00e1rio"],"Paste this shortcode on the page or post to render this form.":["Cole este shortcode na p\u00e1gina ou postagem para renderizar este formul\u00e1rio."],"Spam Protection":["Prote\u00e7\u00e3o contra Spam"],"Auto":["Carro"],"Normal":["Normal"],"%":["%"],"Top":["Topo"],"Bottom":["Inferior"],"Solid":["S\u00f3lido"],"Width":["Largura"],"Size":["Tamanho"],"EM":["EM"],"Padding":["Preenchimento"],"Color 1":["Cor 1"],"Color 2":["Cor 2"],"Type":["Digite"],"Linear":["Linear"],"Radial":["Radial"],"Location 1":["Localiza\u00e7\u00e3o 1"],"Location 2":["Localiza\u00e7\u00e3o 2"],"Angle":["\u00c2ngulo"],"Classic":["Cl\u00e1ssico"],"Gradient":["Gradiente"],"Background":["Fundo"],"Cover":["Cobertura"],"Contain":["Conter"],"Overlay":["Sobreposi\u00e7\u00e3o"],"No Repeat":["Sem repeti\u00e7\u00e3o"],"Overlay Opacity":["Opacidade da Sobreposi\u00e7\u00e3o"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Os nomes das classes devem ser separados por espa\u00e7os. Cada nome de classe n\u00e3o deve come\u00e7ar com um d\u00edgito, h\u00edfen ou sublinhado. Eles podem incluir apenas letras (incluindo caracteres Unicode), n\u00fameros, h\u00edfens e sublinhados."],"Conversational Layout":["Layout Conversacional"],"Unlock Conversational Forms":["Desbloquear Formul\u00e1rios Conversacionais"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Com o Plano SureForms Pro, voc\u00ea pode transformar seus formul\u00e1rios em layouts conversacionais envolventes para uma experi\u00eancia do usu\u00e1rio perfeita."],"Premium":["Premium"],"Overlay Type":["Tipo de Sobreposi\u00e7\u00e3o"],"Image Overlay Color":["Cor de Sobreposi\u00e7\u00e3o da Imagem"],"Image Position":["Posi\u00e7\u00e3o da Imagem"],"Attachment":["Anexo"],"Fixed":["Fixo"],"Blend Mode":["Modo de Mesclagem"],"Multiply":["Multiplicar"],"Screen":["Tela"],"Darken":["Escurecer"],"Lighten":["Iluminar"],"Color Dodge":["Esquiva de Cor"],"Saturation":["Satura\u00e7\u00e3o"],"Repeat-x":["Repetir-x"],"Repeat-y":["Repita-y"],"PX":["PX"],"Button":["Bot\u00e3o"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Conectar com OttoKit"],"SUREFORMS PREMIUM FIELDS":["CAMPOS PREMIUM DO SUREFORMS"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["O endere\u00e7o de 'Email de Remetente' atual n\u00e3o corresponde ao nome de dom\u00ednio do seu site (%1$s). Isso pode fazer com que seus e-mails de notifica\u00e7\u00e3o sejam bloqueados ou marcados como spam. Alternativamente, tente usar um Endere\u00e7o de Remetente que corresponda ao dom\u00ednio do seu site (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["O endere\u00e7o de 'Email de Remetente' atual n\u00e3o corresponde ao nome de dom\u00ednio do seu site (%s). Isso pode fazer com que seus e-mails de notifica\u00e7\u00e3o sejam bloqueados ou marcados como spam."],"We strongly recommend that you install the free ":["Recomendamos fortemente que voc\u00ea instale o gratuito"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["plugin! O Assistente de Configura\u00e7\u00e3o facilita a corre\u00e7\u00e3o dos seus e-mails."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Alternativamente, tente usar um endere\u00e7o de remetente que corresponda ao dom\u00ednio do seu site (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Por favor, insira um endere\u00e7o de e-mail v\u00e1lido. Suas notifica\u00e7\u00f5es n\u00e3o ser\u00e3o enviadas se o campo n\u00e3o for preenchido corretamente."],"From Name":["De Nome"],"From Email":["De Email"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["O endere\u00e7o de 'Email de Remetente' atual pode n\u00e3o corresponder ao nome de dom\u00ednio do seu site (%1$s). Isso pode fazer com que seus e-mails de notifica\u00e7\u00e3o sejam bloqueados ou marcados como spam. Alternativamente, tente usar um Endere\u00e7o de Remetente que corresponda ao dom\u00ednio do seu site (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["O endere\u00e7o de 'Email de Remetente' atual pode n\u00e3o corresponder ao nome de dom\u00ednio do seu site (%s). Isso pode fazer com que seus e-mails de notifica\u00e7\u00e3o sejam bloqueados ou marcados como spam."],"Border Radius":["Raio da Borda"],"Form Theme":["Tema do Formul\u00e1rio"],"Instant Form Padding":["Preenchimento Instant\u00e2neo de Formul\u00e1rio"],"Instant Form Border Radius":["Raio de Borda do Formul\u00e1rio Instant\u00e2neo"],"Select Gradient":["Selecionar Gradiente"],"Upgrade Now":["Atualize agora"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["As entradas mais antigas que os dias selecionados ser\u00e3o exclu\u00eddas."],"Entries Time Period":["Per\u00edodo de Tempo das Entradas"],"Custom CSS Panel":["Painel de CSS Personalizado"],"Notifications can use only one From Email so please enter a single address.":["As notifica\u00e7\u00f5es podem usar apenas um \u00fanico endere\u00e7o de e-mail de remetente, portanto, insira um \u00fanico endere\u00e7o."],"Email Notifications":["Notifica\u00e7\u00f5es de Email"],"Actions":["A\u00e7\u00f5es"],"Duplicate":["Duplicar"],"Delete":["Excluir"],"Select Page to redirect":["Selecione a p\u00e1gina para redirecionar"],"Search for a page":["Procurar uma p\u00e1gina"],"Select a page":["Selecione uma p\u00e1gina"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Salvar"],"Login":["Entrar"],"Register":["Registrar"],"Date":["Data"],"Advanced Settings":["Configura\u00e7\u00f5es Avan\u00e7adas"],"Form Restriction":["Restri\u00e7\u00e3o de Formul\u00e1rio"],"Maximum Number of Entries":["N\u00famero M\u00e1ximo de Entradas"],"Maximum Entries":["Entradas M\u00e1ximas"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["A configura\u00e7\u00e3o do Per\u00edodo de Tempo funciona de acordo com o fuso hor\u00e1rio do seu site WordPress. Clique aqui<\/a> para abrir as Configura\u00e7\u00f5es Gerais do WordPress, onde voc\u00ea pode verificar e atualiz\u00e1-lo."],"Click here":["Clique aqui"],"Response Description After Maximum Entries":["Descri\u00e7\u00e3o da Resposta Ap\u00f3s o M\u00e1ximo de Entradas"],"All changes will be saved automatically when you press back.":["Todas as altera\u00e7\u00f5es ser\u00e3o salvas automaticamente quando voc\u00ea pressionar voltar."],"Repeater":["Repetidor"],"OttoKit Settings":["Configura\u00e7\u00f5es do OttoKit"],"Get Started":["Come\u00e7ar"],"Connect Native Integrations with SureForms":["Conecte Integra\u00e7\u00f5es Nativas com SureForms"],"Expected format for emails - email@sureforms.com or John Doe ":["Formato esperado para e-mails - email@sureforms.com ou John Doe "],"delete":["excluir"],"Please type \"%s\" in the input box":["Por favor, digite \"%s\" na caixa de entrada"],"To confirm, type \"%s\" in the box below:":["Para confirmar, digite \"%s\" na caixa abaixo:"],"Type \"%s\"":["Digite \"%s\""],"Payment":["Pagamento"],"%s - Order ID":["%s - ID do Pedido"],"%s - Amount":["%s - Quantidade"],"%s - Customer Email":["%s - Email do Cliente"],"%s - Customer Name":["%s - Nome do Cliente"],"%s - Status":["%s - Status"],"Add custom CSS rules to style this specific form independently of global styles.":["Adicione regras CSS personalizadas para estilizar este formul\u00e1rio espec\u00edfico independentemente dos estilos globais."],"Spam Protection Type":["Tipo de Prote\u00e7\u00e3o contra Spam"],"Select Security Type":["Selecione o Tipo de Seguran\u00e7a"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Nota: Usar diferentes vers\u00f5es do reCAPTCHA (V2 checkbox e V3) na mesma p\u00e1gina criar\u00e1 conflitos entre as vers\u00f5es. Por favor, evite usar diferentes vers\u00f5es na mesma p\u00e1gina."],"Select Version":["Selecionar Vers\u00e3o"],"Please configure the API keys correctly from the settings":["Por favor, configure as chaves da API corretamente nas configura\u00e7\u00f5es"],"Control email alerts sent to admins or users after a form submission.":["Controle os alertas de e-mail enviados aos administradores ou usu\u00e1rios ap\u00f3s o envio de um formul\u00e1rio."],"Customize the confirmation message or redirect the users after submitting the form.":["Personalize a mensagem de confirma\u00e7\u00e3o ou redirecione os usu\u00e1rios ap\u00f3s enviar o formul\u00e1rio."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Defina limites sobre quantas vezes um formul\u00e1rio pode ser enviado e gerencie op\u00e7\u00f5es de conformidade, incluindo GDPR e reten\u00e7\u00e3o de dados."],"Go to OttoKit Settings":["V\u00e1 para as Configura\u00e7\u00f5es do OttoKit"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Conecte o SureForms com seus aplicativos favoritos para automatizar tarefas e sincronizar dados perfeitamente."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Desbloqueie integra\u00e7\u00f5es poderosas no plano Premium para automatizar seus fluxos de trabalho e conectar o SureForms diretamente com suas ferramentas favoritas."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Envie envios de formul\u00e1rios diretamente para CRMs, e-mail e plataformas de marketing."],"Automate repetitive tasks with seamless data syncing.":["Automatize tarefas repetitivas com sincroniza\u00e7\u00e3o de dados perfeita."],"Access exclusive native integrations for faster workflows.":["Acesse integra\u00e7\u00f5es nativas exclusivas para fluxos de trabalho mais r\u00e1pidos."],"PDF Generation":["Gera\u00e7\u00e3o de PDF"],"Generate and customize PDF copies of form submissions.":["Gerar e personalizar c\u00f3pias em PDF das submiss\u00f5es de formul\u00e1rios."],"Generate Submission PDFs":["Gerar PDFs de Submiss\u00e3o"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Transforme cada entrada de formul\u00e1rio em um arquivo PDF refinado, tornando-o perfeito para relat\u00f3rios, registros ou compartilhamento."],"Automatically generate PDFs from your form submissions.":["Gerar automaticamente PDFs a partir das suas submiss\u00f5es de formul\u00e1rios."],"Customize PDF templates with your branding.":["Personalize modelos de PDF com sua marca."],"Download or email PDFs instantly.":["Baixe ou envie PDFs por e-mail instantaneamente."],"User Registration":["Registro de Usu\u00e1rio"],"Onboard new users or update existing accounts through beautiful looking forms.":["Integre novos usu\u00e1rios ou atualize contas existentes atrav\u00e9s de formul\u00e1rios visualmente atraentes."],"Register Users with SureForms":["Registrar Usu\u00e1rios com SureForms"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Simplifique todo o processo de integra\u00e7\u00e3o de usu\u00e1rios para seus sites com logins e registros perfeitos alimentados por formul\u00e1rios."],"Register new users directly via your form submissions.":["Registre novos usu\u00e1rios diretamente atrav\u00e9s das suas submiss\u00f5es de formul\u00e1rio."],"Create or update existing accounts by mapping form data to user fields.":["Crie ou atualize contas existentes mapeando os dados do formul\u00e1rio para os campos do usu\u00e1rio."],"Assign roles and control access automatically.":["Atribua fun\u00e7\u00f5es e controle o acesso automaticamente."],"Post Feed":["Feed de Publica\u00e7\u00f5es"],"Transform your form submission into WordPress posts.":["Transforme o envio do seu formul\u00e1rio em publica\u00e7\u00f5es do WordPress."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Transforme automaticamente envios de formul\u00e1rios em posts, p\u00e1ginas ou tipos de post personalizados no WordPress. Economize tempo e deixe seus formul\u00e1rios publicarem conte\u00fado diretamente."],"Create posts, pages, or CPTs from your form entries.":["Crie posts, p\u00e1ginas ou CPTs a partir das suas entradas de formul\u00e1rio."],"Map form fields to your post fields easily.":["Mapeie os campos do formul\u00e1rio para os campos da sua postagem facilmente."],"Automate the content publishing flow with few simple steps.":["Automatize o fluxo de publica\u00e7\u00e3o de conte\u00fado com alguns passos simples."],"Automations":["Automatiza\u00e7\u00f5es"],"Unlock Advanced Styling":["Desbloquear Estilo Avan\u00e7ado"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Tenha controle total sobre a apar\u00eancia do seu formul\u00e1rio com cores, fontes e layouts personalizados."],"Button Alignment":["Alinhamento do Bot\u00e3o"],"Add Custom CSS Class(es)":["Adicionar Classe(s) CSS Personalizada(s)"],"Set the total number of submissions allowed for this form.":["Defina o n\u00famero total de envios permitidos para este formul\u00e1rio."],"Save & Progress":["Salvar e Progredir"],"Allow users to save their progress and continue form completion later.":["Permitir que os usu\u00e1rios salvem seu progresso e continuem o preenchimento do formul\u00e1rio mais tarde."],"Save & Progress in SureForms":["Salvar & Progresso no SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["D\u00ea aos seus usu\u00e1rios a flexibilidade de preencher formul\u00e1rios no seu pr\u00f3prio ritmo, permitindo que salvem o progresso e retornem a qualquer momento."],"Let users pause long or multi-step forms and continue later.":["Permita que os usu\u00e1rios pausem formul\u00e1rios longos ou com v\u00e1rias etapas e continuem mais tarde."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Reduza o abandono de formul\u00e1rios com links de retomada convenientes e acesse o progresso de qualquer lugar."],"Improve user experience for lengthy, complex, or multi-page forms.":["Melhore a experi\u00eancia do usu\u00e1rio para formul\u00e1rios longos, complexos ou de v\u00e1rias p\u00e1ginas."],"This form is not yet available. Please check back after the scheduled start time.":["Este formul\u00e1rio ainda n\u00e3o est\u00e1 dispon\u00edvel. Por favor, verifique novamente ap\u00f3s o hor\u00e1rio de in\u00edcio programado."],"This form is no longer accepting submissions. The submission period has ended.":["Este formul\u00e1rio n\u00e3o est\u00e1 mais aceitando envios. O per\u00edodo de envio terminou."],"The start date and time must be before the end date and time.":["A data e hora de in\u00edcio devem ser anteriores \u00e0 data e hora de t\u00e9rmino."],"Form Scheduling":["Agendamento de Formul\u00e1rio"],"Enable Form Scheduling":["Ativar agendamento de formul\u00e1rios"],"Set a time period during which this form will be available for submissions.":["Defina um per\u00edodo de tempo durante o qual este formul\u00e1rio estar\u00e1 dispon\u00edvel para submiss\u00f5es."],"Start Date & Time":["Data e Hora de In\u00edcio"],"End Date & Time":["Data e Hora de T\u00e9rmino"],"Response Description Before Start Date":["Descri\u00e7\u00e3o da Resposta Antes da Data de In\u00edcio"],"Response Description After End Date":["Descri\u00e7\u00e3o da Resposta Ap\u00f3s a Data Final"],"Conditional Confirmations":["Confirma\u00e7\u00f5es Condicionais"],"Set up the message or redirect users will see after submitting the form.":["Configure a mensagem ou redirecione os usu\u00e1rios que ver\u00e3o ap\u00f3s enviar o formul\u00e1rio."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Mostre a mensagem certa para o usu\u00e1rio certo com base em como eles respondem. Personalize confirma\u00e7\u00f5es com condi\u00e7\u00f5es inteligentes e guie os usu\u00e1rios para o pr\u00f3ximo melhor passo automaticamente."],"Display different confirmation messages based on form responses.":["Exibir diferentes mensagens de confirma\u00e7\u00e3o com base nas respostas do formul\u00e1rio."],"Redirect users to specific pages or URLs conditionally.":["Redirecione os usu\u00e1rios para p\u00e1ginas ou URLs espec\u00edficos condicionalmente."],"Create personalized thank-you messages without extra forms.":["Crie mensagens de agradecimento personalizadas sem formul\u00e1rios extras."],"Lost Password":["Senha Perdida"],"Reset Password":["Redefinir Senha"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["Selecione um servi\u00e7o de prote\u00e7\u00e3o contra spam. Configure as chaves de API nas Configura\u00e7\u00f5es Globais antes de ativar."],"Send as Raw HTML":["Enviar como HTML bruto"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Quando ativado, o corpo do e-mail em HTML ser\u00e1 preservado exatamente como escrito e envolvido em um modelo de e-mail profissional."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["As tags inteligentes que fazem refer\u00eancia a campos enviados pelo usu\u00e1rio n\u00e3o ser\u00e3o escapadas no modo HTML bruto. Evite inserir valores de campos n\u00e3o confi\u00e1veis diretamente no corpo do e-mail."],"Please provide a recipient email address and subject line.":["Por favor, forne\u00e7a um endere\u00e7o de e-mail do destinat\u00e1rio e a linha de assunto."],"Email notification duplicated!":["Notifica\u00e7\u00e3o de e-mail duplicada!"],"Are you sure you want to delete this email notification?":["Tem certeza de que deseja excluir esta notifica\u00e7\u00e3o por e-mail?"],"Email notification deleted!":["Notifica\u00e7\u00e3o de email exclu\u00edda!"],"URL is missing Top Level Domain (TLD).":["O URL est\u00e1 sem o dom\u00ednio de n\u00edvel superior (TLD)."],"This form is now closed as the maximum number of entries has been received.":["Este formul\u00e1rio est\u00e1 agora fechado, pois o n\u00famero m\u00e1ximo de inscri\u00e7\u00f5es foi atingido."],"Publish Your Form":["Publique seu formul\u00e1rio"],"Enable This to Instantly Publish the Form":["Ative isto para publicar o formul\u00e1rio instantaneamente"],"Style Your Instant Form Page Here":["Estilize sua p\u00e1gina de formul\u00e1rio instant\u00e2neo aqui"],"Quizzes":["Question\u00e1rios"],"%s - Description":["%s - Descri\u00e7\u00e3o"],"Send entries to 100+ popular apps.":["Envie entradas para mais de 100 aplicativos populares."],"Build automated workflows that run instantly.":["Crie fluxos de trabalho automatizados que sejam executados instantaneamente."],"Create custom app integrations using our Custom App feature.":["Crie integra\u00e7\u00f5es de aplicativos personalizadas usando nosso recurso de Aplicativo Personalizado."],"Keep your tools in sync automatically.":["Mantenha suas ferramentas sincronizadas automaticamente."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["Isso instalar\u00e1 e ativar\u00e1 o OttoKit no seu site WordPress para habilitar recursos de automa\u00e7\u00e3o."],"Automate Your Forms with OttoKit":["Automatize Seus Formul\u00e1rios com OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Cada envio de formul\u00e1rio deve acionar algo \u2014 um alerta no Slack, um lead no CRM, um e-mail de acompanhamento ou uma nova linha no Google Sheets."],"Create interactive quizzes to engage your audience and gather insights.":["Crie question\u00e1rios interativos para envolver seu p\u00fablico e coletar insights."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Crie question\u00e1rios envolventes com v\u00e1rios tipos de perguntas, feedback personalizado e pontua\u00e7\u00e3o automatizada para cativar seu p\u00fablico e obter insights valiosos."],"Create interactive quizzes with multiple question types.":["Crie question\u00e1rios interativos com m\u00faltiplos tipos de perguntas."],"Provide personalized feedback based on user responses.":["Forne\u00e7a feedback personalizado com base nas respostas do usu\u00e1rio."],"Automate scoring and lead segmentation for better insights.":["Automatize a pontua\u00e7\u00e3o e a segmenta\u00e7\u00e3o de leads para obter melhores insights."],"Heading 1":["T\u00edtulo 1"],"Heading 2":["T\u00edtulo 2"],"Heading 3":["T\u00edtulo 3"],"Heading 4":["T\u00edtulo 4"],"Heading 5":["T\u00edtulo 5"],"Heading 6":["T\u00edtulo 6"],"Form settings saved.":["Configura\u00e7\u00f5es do formul\u00e1rio salvas."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["O limite de entrada depende de entradas armazenadas para contar as submiss\u00f5es. Enquanto as Configura\u00e7\u00f5es de Conformidade tiverem \"Nunca armazenar dados de entrada ap\u00f3s o envio do formul\u00e1rio\" ativado, esse limite n\u00e3o ser\u00e1 aplicado. Desative essa op\u00e7\u00e3o ou remova o limite de entrada para usar este recurso."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Configura\u00e7\u00f5es salvas, mas os atributos da postagem (senha \/ t\u00edtulo \/ conte\u00fado) n\u00e3o foram atualizados. Tente novamente para persistir."],"Failed to save form settings.":["Falha ao salvar as configura\u00e7\u00f5es do formul\u00e1rio."],"Saving\u2026":["Salvando\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Quando ativado, este formul\u00e1rio n\u00e3o armazenar\u00e1 o IP do usu\u00e1rio, o nome do navegador e o nome do dispositivo nas entradas."],"Failed to save. Please try again.":["Falha ao salvar. Por favor, tente novamente."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["As chaves da API reCAPTCHA para a vers\u00e3o selecionada n\u00e3o est\u00e3o configuradas. Defina-as nas Configura\u00e7\u00f5es Globais."],"Please select a reCAPTCHA version.":["Por favor, selecione uma vers\u00e3o do reCAPTCHA."],"hCaptcha API keys are not configured. Set them in Global Settings.":["As chaves da API do hCaptcha n\u00e3o est\u00e3o configuradas. Defina-as nas Configura\u00e7\u00f5es Globais."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["As chaves da API do Cloudflare Turnstile n\u00e3o est\u00e3o configuradas. Defina-as nas Configura\u00e7\u00f5es Globais."],"Form data":["Dados do formul\u00e1rio"],"Some fields need attention":["Alguns campos precisam de aten\u00e7\u00e3o"],"Unsaved changes":["Altera\u00e7\u00f5es n\u00e3o salvas"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Um endere\u00e7o de e-mail do destinat\u00e1rio e uma linha de assunto s\u00e3o necess\u00e1rios antes que esta notifica\u00e7\u00e3o possa ser salva. Corrija os campos destacados ou descarte suas altera\u00e7\u00f5es para voltar."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Voc\u00ea tem altera\u00e7\u00f5es n\u00e3o salvas para esta notifica\u00e7\u00e3o. Descarte-as para voltar ou permane\u00e7a para salv\u00e1-las."],"Discard & go back":["Descartar e voltar"],"Stay & fix":["Fique e conserte"],"Keep editing":["Continue editando"],"Please provide a recipient email address.":["Por favor, forne\u00e7a um endere\u00e7o de e-mail do destinat\u00e1rio."],"Please provide a subject line.":["Por favor, forne\u00e7a uma linha de assunto."],"Please provide a custom URL.":["Por favor, forne\u00e7a um URL personalizado."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Voc\u00ea tem altera\u00e7\u00f5es n\u00e3o salvas. Descarte-as para continuar ou permane\u00e7a para salvar suas altera\u00e7\u00f5es."],"Discard & continue":["Descartar e continuar"],"Quill heading picker: default paragraph style\u0004Normal":["Normal"]}}} \ No newline at end of file +{"translation-revision-date":"2025-01-02T08:44:42+00:00","generator":"WP-CLI\/2.12.0","source":"assets\/build\/formEditor.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"SureForms":["SureForms"],"Status":["Status"],"Form":["Formul\u00e1rio"],"Fields":["Campos"],"Image":["Imagem"],"Activated":["Ativado"],"Activate":["Ativar"],"Submit":["Enviar"],"Global Settings":["Configura\u00e7\u00f5es Globais"],"Form Title":["T\u00edtulo do Formul\u00e1rio"],"Edit":["Editar"],"Please enter a valid URL.":["Por favor, insira um URL v\u00e1lido."],"Desktop":["\u00c1rea de trabalho"],"Medium":["M\u00e9dio"],"Mobile":["M\u00f3vel"],"Repeat":["Repetir"],"Scroll":["Rolar"],"Signature":["Assinatura"],"Tablet":["Tablet"],"Upload":["Carregar"],"Basic":["B\u00e1sico"],"Form Settings":["Configura\u00e7\u00f5es do Formul\u00e1rio"],"General":["Geral"],"Style":["Estilo"],"Advanced":["Avan\u00e7ado"],"No tags available":["Sem etiquetas dispon\u00edveis"],"Device":["Dispositivo"],"Select Shortcodes":["Selecionar C\u00f3digos Curtos"],"Page Break Label":["R\u00f3tulo de Quebra de P\u00e1gina"],"Next":["Pr\u00f3ximo"],"Back":["Voltar"],"Reset":["Redefinir"],"Generic tags":["Etiquetas gen\u00e9ricas"],"Pixel":["Pixel"],"Em":["Em"],"Select Units":["Selecionar Unidades"],"%s units":["%s unidades"],"Margin":["Margem"],"None":["Nenhum"],"Custom":["Personalizado"],"Please add a option props to MultiButtonsControl":["Por favor, adicione uma op\u00e7\u00e3o de propriedades ao MultiButtonsControl"],"Confirm":["Confirmar"],"Cancel":["Cancelar"],"Processing\u2026":["Processando\u2026"],"Select Video":["Selecionar V\u00eddeo"],"Change Video":["Alterar V\u00eddeo"],"Select Lottie Animation":["Selecionar Anima\u00e7\u00e3o Lottie"],"Change Lottie Animation":["Alterar Anima\u00e7\u00e3o Lottie"],"Upload SVG":["Carregar SVG"],"Change SVG":["Alterar SVG"],"Select Image":["Selecionar imagem"],"Change Image":["Alterar Imagem"],"Upload SVG?":["Carregar SVG?"],"Upload SVG can be potentially risky. Are you sure?":["Fazer upload de SVG pode ser potencialmente arriscado. Tem certeza?"],"Upload Anyway":["Carregar Mesmo Assim"],"Full Width":["Largura Total"],"Install":["Instalar"],"Plugin Installation failed, Please try again later.":["A instala\u00e7\u00e3o do plugin falhou, por favor, tente novamente mais tarde."],"Plugin activation failed, Please try again later.":["A ativa\u00e7\u00e3o do plugin falhou, por favor, tente novamente mais tarde."],"Integrations":["Integra\u00e7\u00f5es"],"%s Removed from Quick Action Bar.":["%s removido da Barra de A\u00e7\u00e3o R\u00e1pida."],"Add to Quick Action Bar":["Adicionar \u00e0 Barra de A\u00e7\u00f5es R\u00e1pidas"],"%s Added to Quick Action Bar.":["%s adicionado \u00e0 barra de a\u00e7\u00f5es r\u00e1pidas."],"Already Present in Quick Action Bar":["J\u00e1 presente na barra de a\u00e7\u00f5es r\u00e1pidas"],"No results found.":["Nenhum resultado encontrado."],"data object is empty":["o objeto de dados est\u00e1 vazio"],"Add blocks to Quick Action Bar":["Adicionar blocos \u00e0 Barra de A\u00e7\u00e3o R\u00e1pida"],"Re-arrange block inside Quick Action Bar":["Reorganizar bloco dentro da Barra de A\u00e7\u00e3o R\u00e1pida"],"Upgrade":["Atualizar"],"Connecting\u2026":["Conectando\u2026"],"Install & Activate":["Instalar e Ativar"],"Compliance Settings":["Configura\u00e7\u00f5es de Conformidade"],"Enable GDPR Compliance":["Ativar conformidade com o GDPR"],"Never store entry data after form submission":["Nunca armazene os dados de entrada ap\u00f3s o envio do formul\u00e1rio"],"When enabled this form will never store Entries.":["Quando ativado, este formul\u00e1rio nunca armazenar\u00e1 entradas."],"Automatically delete entries":["Excluir entradas automaticamente"],"When enabled this form will automatically delete entries after a certain period of time.":["Quando ativado, este formul\u00e1rio excluir\u00e1 automaticamente as entradas ap\u00f3s um determinado per\u00edodo de tempo."],"Entries older than the days set will be deleted automatically.":["As entradas mais antigas do que os dias definidos ser\u00e3o exclu\u00eddas automaticamente."],"Custom CSS":["CSS Personalizado"],"The following CSS styles added below will only apply to this form container.":["Os estilos CSS a seguir adicionados abaixo ser\u00e3o aplicados apenas a este cont\u00eainer de formul\u00e1rio."],"Visual":["Visual"],"HTML":["HTML"],"All Data":["Todos os dados"],"Add Shortcode":["Adicionar Shortcode"],"Form input tags":["Etiquetas de entrada de formul\u00e1rio"],"Comma separated values are also accepted.":["Valores separados por v\u00edrgula tamb\u00e9m s\u00e3o aceitos."],"Email Notification":["Notifica\u00e7\u00e3o por Email"],"Name":["Nome"],"Send Email To":["Enviar Email Para"],"Subject":["Assunto"],"CC":["CC"],"BCC":["Cco"],"Reply To":["Responder a"],"Add Notification":["Adicionar Notifica\u00e7\u00e3o"],"Add Key":["Adicionar Chave"],"Add Value":["Adicionar Valor"],"Add":["Adicionar"],"Confirmation Message":["Mensagem de Confirma\u00e7\u00e3o"],"After Form Submission":["Ap\u00f3s o Envio do Formul\u00e1rio"],"Hide Form":["Ocultar Formul\u00e1rio"],"Reset Form":["Redefinir Formul\u00e1rio"],"Custom URL":["URL personalizada"],"Add Query Parameters":["Adicionar Par\u00e2metros de Consulta"],"Select if you want to add key-value pairs for form fields to include in query parameters":["Selecione se deseja adicionar pares chave-valor para campos de formul\u00e1rio a serem inclu\u00eddos nos par\u00e2metros de consulta"],"Query Parameters":["Par\u00e2metros de Consulta"],"Please select a page.":["Por favor, selecione uma p\u00e1gina."],"Suggestion: URL should use HTTPS":["Sugest\u00e3o: a URL deve usar HTTPS"],"Success Message":["Mensagem de Sucesso"],"Redirect":["Redirecionar"],"Redirect to":["Redirecionar para"],"Page":["P\u00e1gina"],"Form Confirmation":["Confirma\u00e7\u00e3o de Formul\u00e1rio"],"Confirmation Type":["Tipo de Confirma\u00e7\u00e3o"],"Use Labels as Placeholders":["Use r\u00f3tulos como marcadores de posi\u00e7\u00e3o"],"Above setting will place the labels inside the fields as placeholders (where possible). This setting takes effect only on the live page, not in the editor preview.":["A configura\u00e7\u00e3o acima colocar\u00e1 os r\u00f3tulos dentro dos campos como marcadores de posi\u00e7\u00e3o (quando poss\u00edvel). Esta configura\u00e7\u00e3o s\u00f3 tem efeito na p\u00e1gina ao vivo, n\u00e3o na pr\u00e9-visualiza\u00e7\u00e3o do editor."],"Page Break":["Quebra de p\u00e1gina"],"Show Labels":["Mostrar R\u00f3tulos"],"First Page Label":["Etiqueta da Primeira P\u00e1gina"],"Progress Indicator":["Indicador de Progresso"],"Progress Bar":["Barra de Progresso"],"Connector":["Conector"],"Steps":["Passos"],"Next Button Text":["Texto do Bot\u00e3o Seguinte"],"Back Button Text":["Texto do Bot\u00e3o Voltar"],"Are you sure you want to close? Your unsaved changes will be lost as you have some validation errors.":["Tem certeza de que deseja fechar? Suas altera\u00e7\u00f5es n\u00e3o salvas ser\u00e3o perdidas, pois voc\u00ea tem alguns erros de valida\u00e7\u00e3o."],"There are few unsaved changes. Please save your changes to reflect the updates.":["H\u00e1 algumas altera\u00e7\u00f5es n\u00e3o salvas. Por favor, salve suas altera\u00e7\u00f5es para refletir as atualiza\u00e7\u00f5es."],"Form Behavior":["Comportamento do Formul\u00e1rio"],"Clear":["Limpar"],"Select Color":["Selecionar Cor"],"Primary Color":["Cor Prim\u00e1ria"],"Text Color":["Cor do Texto"],"Text Color on Primary":["Cor do Texto no Prim\u00e1rio"],"Field Spacing":["Espa\u00e7amento de Campo"],"Small":["Pequeno"],"Large":["Grande"],"Left":["Esquerda"],"Center":["Centro"],"Right":["Certo"],"Google reCAPTCHA":["Google reCAPTCHA"],"CloudFlare Turnstile":["CloudFlare Turnstile"],"hCaptcha":["hCaptcha"],"reCAPTCHA v2 Invisible":["reCAPTCHA v2 Invis\u00edvel"],"reCAPTCHA v3":["reCAPTCHA v3"],"Date Picker":["Seletor de Data"],"Time Picker":["Seletor de Hora"],"Hidden":["Oculto"],"Slider":["Controle deslizante"],"Rating":["Avalia\u00e7\u00e3o"],"Upgrade to Unlock These Fields":["Atualize para Desbloquear Estes Campos"],"Add Block":["Adicionar Bloco"],"Customize with SureForms":["Personalize com SureForms"],"Page break":["Quebra de p\u00e1gina"],"Previous":["Anterior"],"Thank you":["Obrigado"],"Form submitted successfully!":["Formul\u00e1rio enviado com sucesso!"],"Instant Form":["Formul\u00e1rio Instant\u00e2neo"],"Enable Instant Form":["Ativar Formul\u00e1rio Instant\u00e2neo"],"Enable Preview":["Ativar visualiza\u00e7\u00e3o"],"Show Title":["T\u00edtulo do Show"],"Site Logo":["Logotipo do Site"],"Banner Background":["Fundo do Banner"],"Color":["Cor"],"Upload Image":["Carregar Imagem"],"Background Color":["Cor de Fundo"],"Use banner as page background":["Usar banner como fundo de p\u00e1gina"],"Form Width":["Largura do Formul\u00e1rio"],"URL":["URL"],"URL Slug":["Slug de URL"],"The last part of the URL.":["A \u00faltima parte do URL."],"Learn more.":["Saiba mais."],"SureForms Description":["Descri\u00e7\u00e3o do SureForms"],"Form Options":["Op\u00e7\u00f5es de Formul\u00e1rio"],"Form Shortcode":["C\u00f3digo Curto do Formul\u00e1rio"],"Paste this shortcode on the page or post to render this form.":["Cole este shortcode na p\u00e1gina ou postagem para renderizar este formul\u00e1rio."],"Spam Protection":["Prote\u00e7\u00e3o contra Spam"],"Auto":["Carro"],"Normal":["Normal"],"%":["%"],"Top":["Topo"],"Bottom":["Inferior"],"Solid":["S\u00f3lido"],"Width":["Largura"],"Size":["Tamanho"],"EM":["EM"],"Padding":["Preenchimento"],"Color 1":["Cor 1"],"Color 2":["Cor 2"],"Type":["Digite"],"Linear":["Linear"],"Radial":["Radial"],"Location 1":["Localiza\u00e7\u00e3o 1"],"Location 2":["Localiza\u00e7\u00e3o 2"],"Angle":["\u00c2ngulo"],"Classic":["Cl\u00e1ssico"],"Gradient":["Gradiente"],"Background":["Fundo"],"Cover":["Cobertura"],"Contain":["Conter"],"Overlay":["Sobreposi\u00e7\u00e3o"],"No Repeat":["Sem repeti\u00e7\u00e3o"],"Overlay Opacity":["Opacidade da Sobreposi\u00e7\u00e3o"],"Class names should be separated by spaces. Each class name must not start with a digit, hyphen, or underscore. They can only include letters (including Unicode characters), numbers, hyphens, and underscores.":["Os nomes das classes devem ser separados por espa\u00e7os. Cada nome de classe n\u00e3o deve come\u00e7ar com um d\u00edgito, h\u00edfen ou sublinhado. Eles podem incluir apenas letras (incluindo caracteres Unicode), n\u00fameros, h\u00edfens e sublinhados."],"Conversational Layout":["Layout Conversacional"],"Unlock Conversational Forms":["Desbloquear Formul\u00e1rios Conversacionais"],"With the SureForms Pro Plan, you can transform your forms into engaging conversational layouts for a seamless user experience.":["Com o Plano SureForms Pro, voc\u00ea pode transformar seus formul\u00e1rios em layouts conversacionais envolventes para uma experi\u00eancia do usu\u00e1rio perfeita."],"Premium":["Premium"],"Overlay Type":["Tipo de Sobreposi\u00e7\u00e3o"],"Image Overlay Color":["Cor de Sobreposi\u00e7\u00e3o da Imagem"],"Image Position":["Posi\u00e7\u00e3o da Imagem"],"Attachment":["Anexo"],"Fixed":["Fixo"],"Blend Mode":["Modo de Mesclagem"],"Multiply":["Multiplicar"],"Screen":["Tela"],"Darken":["Escurecer"],"Lighten":["Iluminar"],"Color Dodge":["Esquiva de Cor"],"Saturation":["Satura\u00e7\u00e3o"],"Repeat-x":["Repetir-x"],"Repeat-y":["Repita-y"],"PX":["PX"],"Button":["Bot\u00e3o"],"OttoKit":["OttoKit"],"Connect with OttoKit":["Conectar com OttoKit"],"SUREFORMS PREMIUM FIELDS":["CAMPOS PREMIUM DO SUREFORMS"],"The current 'From Email' address does not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["O endere\u00e7o de 'Email de Remetente' atual n\u00e3o corresponde ao nome de dom\u00ednio do seu site (%1$s). Isso pode fazer com que seus e-mails de notifica\u00e7\u00e3o sejam bloqueados ou marcados como spam. Alternativamente, tente usar um Endere\u00e7o de Remetente que corresponda ao dom\u00ednio do seu site (admin@%2$s)."],"The current 'From Email' address does not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["O endere\u00e7o de 'Email de Remetente' atual n\u00e3o corresponde ao nome de dom\u00ednio do seu site (%s). Isso pode fazer com que seus e-mails de notifica\u00e7\u00e3o sejam bloqueados ou marcados como spam."],"We strongly recommend that you install the free ":["Recomendamos fortemente que voc\u00ea instale o gratuito"]," plugin! The Setup Wizard makes it easy to fix your emails. ":["plugin! O Assistente de Configura\u00e7\u00e3o facilita a corre\u00e7\u00e3o dos seus e-mails."]," Alternately, try using a From Address that matches your website domain (admin@%s).":["Alternativamente, tente usar um endere\u00e7o de remetente que corresponda ao dom\u00ednio do seu site (admin@%s)."],"Please enter a valid email address. Your notifications won't be sent if the field is not filled in correctly.":["Por favor, insira um endere\u00e7o de e-mail v\u00e1lido. Suas notifica\u00e7\u00f5es n\u00e3o ser\u00e3o enviadas se o campo n\u00e3o for preenchido corretamente."],"From Name":["De Nome"],"From Email":["De Email"],"The current 'From Email' address may not match your website domain name (%1$s). This can cause your notification emails to be blocked or marked as spam. Alternately, try using a From Address that matches your website domain (admin@%2$s).":["O endere\u00e7o de 'Email de Remetente' atual pode n\u00e3o corresponder ao nome de dom\u00ednio do seu site (%1$s). Isso pode fazer com que seus e-mails de notifica\u00e7\u00e3o sejam bloqueados ou marcados como spam. Alternativamente, tente usar um Endere\u00e7o de Remetente que corresponda ao dom\u00ednio do seu site (admin@%2$s)."],"The current 'From Email' address may not match your website domain name (%s). This can cause your notification emails to be blocked or marked as spam. ":["O endere\u00e7o de 'Email de Remetente' atual pode n\u00e3o corresponder ao nome de dom\u00ednio do seu site (%s). Isso pode fazer com que seus e-mails de notifica\u00e7\u00e3o sejam bloqueados ou marcados como spam."],"Border Radius":["Raio da Borda"],"Form Theme":["Tema do Formul\u00e1rio"],"Instant Form Padding":["Preenchimento Instant\u00e2neo de Formul\u00e1rio"],"Instant Form Border Radius":["Raio de Borda do Formul\u00e1rio Instant\u00e2neo"],"Select Gradient":["Selecionar Gradiente"],"Upgrade Now":["Atualize agora"],"SureTriggers":["SureTriggers"],"Entries older than the selected days will be deleted.":["As entradas mais antigas que os dias selecionados ser\u00e3o exclu\u00eddas."],"Entries Time Period":["Per\u00edodo de Tempo das Entradas"],"Custom CSS Panel":["Painel de CSS Personalizado"],"Notifications can use only one From Email so please enter a single address.":["As notifica\u00e7\u00f5es podem usar apenas um \u00fanico endere\u00e7o de e-mail de remetente, portanto, insira um \u00fanico endere\u00e7o."],"Email Notifications":["Notifica\u00e7\u00f5es de Email"],"Actions":["A\u00e7\u00f5es"],"Duplicate":["Duplicar"],"Delete":["Excluir"],"Select Page to redirect":["Selecione a p\u00e1gina para redirecionar"],"Search for a page":["Procurar uma p\u00e1gina"],"Select a page":["Selecione uma p\u00e1gina"],"reCAPTCHA v2":["reCAPTCHA v2"],"Save":["Salvar"],"Login":["Entrar"],"Register":["Registrar"],"Date":["Data"],"Advanced Settings":["Configura\u00e7\u00f5es Avan\u00e7adas"],"Form Restriction":["Restri\u00e7\u00e3o de Formul\u00e1rio"],"Maximum Number of Entries":["N\u00famero M\u00e1ximo de Entradas"],"Maximum Entries":["Entradas M\u00e1ximas"],"The Time Period setting works according to your WordPress site's time zone. Click here<\/a> to open your WordPress General Settings, where you can check and update it.":["A configura\u00e7\u00e3o do Per\u00edodo de Tempo funciona de acordo com o fuso hor\u00e1rio do seu site WordPress. Clique aqui<\/a> para abrir as Configura\u00e7\u00f5es Gerais do WordPress, onde voc\u00ea pode verificar e atualiz\u00e1-lo."],"Click here":["Clique aqui"],"Response Description After Maximum Entries":["Descri\u00e7\u00e3o da Resposta Ap\u00f3s o M\u00e1ximo de Entradas"],"All changes will be saved automatically when you press back.":["Todas as altera\u00e7\u00f5es ser\u00e3o salvas automaticamente quando voc\u00ea pressionar voltar."],"Repeater":["Repetidor"],"OttoKit Settings":["Configura\u00e7\u00f5es do OttoKit"],"Get Started":["Come\u00e7ar"],"Connect Native Integrations with SureForms":["Conecte Integra\u00e7\u00f5es Nativas com SureForms"],"Expected format for emails - email@sureforms.com or John Doe ":["Formato esperado para e-mails - email@sureforms.com ou John Doe "],"delete":["excluir"],"Please type \"%s\" in the input box":["Por favor, digite \"%s\" na caixa de entrada"],"To confirm, type \"%s\" in the box below:":["Para confirmar, digite \"%s\" na caixa abaixo:"],"Type \"%s\"":["Digite \"%s\""],"Payment":["Pagamento"],"%s - Order ID":["%s - ID do Pedido"],"%s - Amount":["%s - Quantidade"],"%s - Customer Email":["%s - Email do Cliente"],"%s - Customer Name":["%s - Nome do Cliente"],"%s - Status":["%s - Status"],"Add custom CSS rules to style this specific form independently of global styles.":["Adicione regras CSS personalizadas para estilizar este formul\u00e1rio espec\u00edfico independentemente dos estilos globais."],"Spam Protection Type":["Tipo de Prote\u00e7\u00e3o contra Spam"],"Select Security Type":["Selecione o Tipo de Seguran\u00e7a"],"Note: Using different reCAPTCHA versions (V2 checkbox and V3) on the same page will create conflicts between the versions. Kindly avoid using different versions on the same page.":["Nota: Usar diferentes vers\u00f5es do reCAPTCHA (V2 checkbox e V3) na mesma p\u00e1gina criar\u00e1 conflitos entre as vers\u00f5es. Por favor, evite usar diferentes vers\u00f5es na mesma p\u00e1gina."],"Select Version":["Selecionar Vers\u00e3o"],"Please configure the API keys correctly from the settings":["Por favor, configure as chaves da API corretamente nas configura\u00e7\u00f5es"],"Control email alerts sent to admins or users after a form submission.":["Controle os alertas de e-mail enviados aos administradores ou usu\u00e1rios ap\u00f3s o envio de um formul\u00e1rio."],"Customize the confirmation message or redirect the users after submitting the form.":["Personalize a mensagem de confirma\u00e7\u00e3o ou redirecione os usu\u00e1rios ap\u00f3s enviar o formul\u00e1rio."],"Set limits on how many times a form can be submitted and manage compliance options, including GDPR and data retention.":["Defina limites sobre quantas vezes um formul\u00e1rio pode ser enviado e gerencie op\u00e7\u00f5es de conformidade, incluindo GDPR e reten\u00e7\u00e3o de dados."],"Go to OttoKit Settings":["V\u00e1 para as Configura\u00e7\u00f5es do OttoKit"],"Connect SureForms with your favorite apps to automate tasks and sync data seamlessly.":["Conecte o SureForms com seus aplicativos favoritos para automatizar tarefas e sincronizar dados perfeitamente."],"Unlock powerful integrations in the Premimum plan to automate your workflows and connect SureForms directly with your favorite tools.":["Desbloqueie integra\u00e7\u00f5es poderosas no plano Premium para automatizar seus fluxos de trabalho e conectar o SureForms diretamente com suas ferramentas favoritas."],"Send form submissions straight to CRMs, email, and marketing platforms.":["Envie envios de formul\u00e1rios diretamente para CRMs, e-mail e plataformas de marketing."],"Automate repetitive tasks with seamless data syncing.":["Automatize tarefas repetitivas com sincroniza\u00e7\u00e3o de dados perfeita."],"Access exclusive native integrations for faster workflows.":["Acesse integra\u00e7\u00f5es nativas exclusivas para fluxos de trabalho mais r\u00e1pidos."],"PDF Generation":["Gera\u00e7\u00e3o de PDF"],"Generate and customize PDF copies of form submissions.":["Gerar e personalizar c\u00f3pias em PDF das submiss\u00f5es de formul\u00e1rios."],"Generate Submission PDFs":["Gerar PDFs de Submiss\u00e3o"],"Turn every form entry into a polished PDF file, making it perfect for reports, records, or sharing.":["Transforme cada entrada de formul\u00e1rio em um arquivo PDF refinado, tornando-o perfeito para relat\u00f3rios, registros ou compartilhamento."],"Automatically generate PDFs from your form submissions.":["Gerar automaticamente PDFs a partir das suas submiss\u00f5es de formul\u00e1rios."],"Customize PDF templates with your branding.":["Personalize modelos de PDF com sua marca."],"Download or email PDFs instantly.":["Baixe ou envie PDFs por e-mail instantaneamente."],"User Registration":["Registro de Usu\u00e1rio"],"Onboard new users or update existing accounts through beautiful looking forms.":["Integre novos usu\u00e1rios ou atualize contas existentes atrav\u00e9s de formul\u00e1rios visualmente atraentes."],"Register Users with SureForms":["Registrar Usu\u00e1rios com SureForms"],"Streamline the entire user onboarding process for your sites with seamless form-powered logins and registrations.":["Simplifique todo o processo de integra\u00e7\u00e3o de usu\u00e1rios para seus sites com logins e registros perfeitos alimentados por formul\u00e1rios."],"Register new users directly via your form submissions.":["Registre novos usu\u00e1rios diretamente atrav\u00e9s das suas submiss\u00f5es de formul\u00e1rio."],"Create or update existing accounts by mapping form data to user fields.":["Crie ou atualize contas existentes mapeando os dados do formul\u00e1rio para os campos do usu\u00e1rio."],"Assign roles and control access automatically.":["Atribua fun\u00e7\u00f5es e controle o acesso automaticamente."],"Post Feed":["Feed de Publica\u00e7\u00f5es"],"Transform your form submission into WordPress posts.":["Transforme o envio do seu formul\u00e1rio em publica\u00e7\u00f5es do WordPress."],"Automatically turn form submissions into WordPress posts, pages, or custom post types. Save big on time and let your forms publish content directly.":["Transforme automaticamente envios de formul\u00e1rios em posts, p\u00e1ginas ou tipos de post personalizados no WordPress. Economize tempo e deixe seus formul\u00e1rios publicarem conte\u00fado diretamente."],"Create posts, pages, or CPTs from your form entries.":["Crie posts, p\u00e1ginas ou CPTs a partir das suas entradas de formul\u00e1rio."],"Map form fields to your post fields easily.":["Mapeie os campos do formul\u00e1rio para os campos da sua postagem facilmente."],"Automate the content publishing flow with few simple steps.":["Automatize o fluxo de publica\u00e7\u00e3o de conte\u00fado com alguns passos simples."],"Automations":["Automatiza\u00e7\u00f5es"],"Unlock Advanced Styling":["Desbloquear Estilo Avan\u00e7ado"],"Get full control over your form's look with custom colors, fonts, and layouts.":["Tenha controle total sobre a apar\u00eancia do seu formul\u00e1rio com cores, fontes e layouts personalizados."],"Button Alignment":["Alinhamento do Bot\u00e3o"],"Add Custom CSS Class(es)":["Adicionar Classe(s) CSS Personalizada(s)"],"Set the total number of submissions allowed for this form.":["Defina o n\u00famero total de envios permitidos para este formul\u00e1rio."],"Save & Progress":["Salvar e Progredir"],"Allow users to save their progress and continue form completion later.":["Permitir que os usu\u00e1rios salvem seu progresso e continuem o preenchimento do formul\u00e1rio mais tarde."],"Save & Progress in SureForms":["Salvar & Progresso no SureForms"],"Give your users the flexibility to complete forms at their own pace by allowing them to save progress and return anytime.":["D\u00ea aos seus usu\u00e1rios a flexibilidade de preencher formul\u00e1rios no seu pr\u00f3prio ritmo, permitindo que salvem o progresso e retornem a qualquer momento."],"Let users pause long or multi-step forms and continue later.":["Permita que os usu\u00e1rios pausem formul\u00e1rios longos ou com v\u00e1rias etapas e continuem mais tarde."],"Reduce form abandonment with convenient resume links and access their progress from anywhere.":["Reduza o abandono de formul\u00e1rios com links de retomada convenientes e acesse o progresso de qualquer lugar."],"Improve user experience for lengthy, complex, or multi-page forms.":["Melhore a experi\u00eancia do usu\u00e1rio para formul\u00e1rios longos, complexos ou de v\u00e1rias p\u00e1ginas."],"This form is not yet available. Please check back after the scheduled start time.":["Este formul\u00e1rio ainda n\u00e3o est\u00e1 dispon\u00edvel. Por favor, verifique novamente ap\u00f3s o hor\u00e1rio de in\u00edcio programado."],"This form is no longer accepting submissions. The submission period has ended.":["Este formul\u00e1rio n\u00e3o est\u00e1 mais aceitando envios. O per\u00edodo de envio terminou."],"The start date and time must be before the end date and time.":["A data e hora de in\u00edcio devem ser anteriores \u00e0 data e hora de t\u00e9rmino."],"Form Scheduling":["Agendamento de Formul\u00e1rio"],"Enable Form Scheduling":["Ativar agendamento de formul\u00e1rios"],"Set a time period during which this form will be available for submissions.":["Defina um per\u00edodo de tempo durante o qual este formul\u00e1rio estar\u00e1 dispon\u00edvel para submiss\u00f5es."],"Start Date & Time":["Data e Hora de In\u00edcio"],"End Date & Time":["Data e Hora de T\u00e9rmino"],"Response Description Before Start Date":["Descri\u00e7\u00e3o da Resposta Antes da Data de In\u00edcio"],"Response Description After End Date":["Descri\u00e7\u00e3o da Resposta Ap\u00f3s a Data Final"],"Conditional Confirmations":["Confirma\u00e7\u00f5es Condicionais"],"Set up the message or redirect users will see after submitting the form.":["Configure a mensagem ou redirecione os usu\u00e1rios que ver\u00e3o ap\u00f3s enviar o formul\u00e1rio."],"Show the right message to the right user based on how they respond. Personalize confirmations with smart conditions and guide users to the next best step automatically.":["Mostre a mensagem certa para o usu\u00e1rio certo com base em como eles respondem. Personalize confirma\u00e7\u00f5es com condi\u00e7\u00f5es inteligentes e guie os usu\u00e1rios para o pr\u00f3ximo melhor passo automaticamente."],"Display different confirmation messages based on form responses.":["Exibir diferentes mensagens de confirma\u00e7\u00e3o com base nas respostas do formul\u00e1rio."],"Redirect users to specific pages or URLs conditionally.":["Redirecione os usu\u00e1rios para p\u00e1ginas ou URLs espec\u00edficos condicionalmente."],"Create personalized thank-you messages without extra forms.":["Crie mensagens de agradecimento personalizadas sem formul\u00e1rios extras."],"Lost Password":["Senha Perdida"],"Reset Password":["Redefinir Senha"],"Select a spam protection service. Configure API keys in Global Settings before enabling.":["Selecione um servi\u00e7o de prote\u00e7\u00e3o contra spam. Configure as chaves de API nas Configura\u00e7\u00f5es Globais antes de ativar."],"Send as Raw HTML":["Enviar como HTML bruto"],"When enabled, the email body HTML will be preserved exactly as written and wrapped in a professional email template.":["Quando ativado, o corpo do e-mail em HTML ser\u00e1 preservado exatamente como escrito e envolvido em um modelo de e-mail profissional."],"Smart tags that reference user-submitted fields will not be escaped in raw HTML mode. Avoid inserting untrusted field values directly into the email body.":["As tags inteligentes que fazem refer\u00eancia a campos enviados pelo usu\u00e1rio n\u00e3o ser\u00e3o escapadas no modo HTML bruto. Evite inserir valores de campos n\u00e3o confi\u00e1veis diretamente no corpo do e-mail."],"Please provide a recipient email address and subject line.":["Por favor, forne\u00e7a um endere\u00e7o de e-mail do destinat\u00e1rio e a linha de assunto."],"Email notification duplicated!":["Notifica\u00e7\u00e3o de e-mail duplicada!"],"Are you sure you want to delete this email notification?":["Tem certeza de que deseja excluir esta notifica\u00e7\u00e3o por e-mail?"],"Email notification deleted!":["Notifica\u00e7\u00e3o de email exclu\u00edda!"],"URL is missing Top Level Domain (TLD).":["O URL est\u00e1 sem o dom\u00ednio de n\u00edvel superior (TLD)."],"This form is now closed as the maximum number of entries has been received.":["Este formul\u00e1rio est\u00e1 agora fechado, pois o n\u00famero m\u00e1ximo de inscri\u00e7\u00f5es foi atingido."],"Publish Your Form":["Publique seu formul\u00e1rio"],"Enable This to Instantly Publish the Form":["Ative isto para publicar o formul\u00e1rio instantaneamente"],"Style Your Instant Form Page Here":["Estilize sua p\u00e1gina de formul\u00e1rio instant\u00e2neo aqui"],"Quizzes":["Question\u00e1rios"],"%s - Description":["%s - Descri\u00e7\u00e3o"],"Send entries to 100+ popular apps.":["Envie entradas para mais de 100 aplicativos populares."],"Build automated workflows that run instantly.":["Crie fluxos de trabalho automatizados que sejam executados instantaneamente."],"Create custom app integrations using our Custom App feature.":["Crie integra\u00e7\u00f5es de aplicativos personalizadas usando nosso recurso de Aplicativo Personalizado."],"Keep your tools in sync automatically.":["Mantenha suas ferramentas sincronizadas automaticamente."],"This will install and activate OttoKit on your WordPress site to enable automation features.":["Isso instalar\u00e1 e ativar\u00e1 o OttoKit no seu site WordPress para habilitar recursos de automa\u00e7\u00e3o."],"Automate Your Forms with OttoKit":["Automatize Seus Formul\u00e1rios com OttoKit"],"Every form submission should trigger something \u2014 a Slack alert, a CRM lead, a follow-up email, or a new row in Google Sheets.":["Cada envio de formul\u00e1rio deve acionar algo \u2014 um alerta no Slack, um lead no CRM, um e-mail de acompanhamento ou uma nova linha no Google Sheets."],"Create interactive quizzes to engage your audience and gather insights.":["Crie question\u00e1rios interativos para envolver seu p\u00fablico e coletar insights."],"Design engaging quizzes with various question types, personalized feedback, and automated scoring to captivate your audience and gain valuable insights.":["Crie question\u00e1rios envolventes com v\u00e1rios tipos de perguntas, feedback personalizado e pontua\u00e7\u00e3o automatizada para cativar seu p\u00fablico e obter insights valiosos."],"Create interactive quizzes with multiple question types.":["Crie question\u00e1rios interativos com m\u00faltiplos tipos de perguntas."],"Provide personalized feedback based on user responses.":["Forne\u00e7a feedback personalizado com base nas respostas do usu\u00e1rio."],"Automate scoring and lead segmentation for better insights.":["Automatize a pontua\u00e7\u00e3o e a segmenta\u00e7\u00e3o de leads para obter melhores insights."],"Heading 1":["T\u00edtulo 1"],"Heading 2":["T\u00edtulo 2"],"Heading 3":["T\u00edtulo 3"],"Heading 4":["T\u00edtulo 4"],"Heading 5":["T\u00edtulo 5"],"Heading 6":["T\u00edtulo 6"],"Form settings saved.":["Configura\u00e7\u00f5es do formul\u00e1rio salvas."],"The entry cap relies on stored entries to count submissions. While Compliance Settings has \"Never store entry data after form submission\" enabled, this limit will not be enforced. Disable that option, or remove the entry limit, to use this feature.":["O limite de entrada depende de entradas armazenadas para contar as submiss\u00f5es. Enquanto as Configura\u00e7\u00f5es de Conformidade tiverem \"Nunca armazenar dados de entrada ap\u00f3s o envio do formul\u00e1rio\" ativado, esse limite n\u00e3o ser\u00e1 aplicado. Desative essa op\u00e7\u00e3o ou remova o limite de entrada para usar este recurso."],"Settings saved, but post attributes (password \/ title \/ content) failed to update. Retry to persist them.":["Configura\u00e7\u00f5es salvas, mas os atributos da postagem (senha \/ t\u00edtulo \/ conte\u00fado) n\u00e3o foram atualizados. Tente novamente para persistir."],"Failed to save form settings.":["Falha ao salvar as configura\u00e7\u00f5es do formul\u00e1rio."],"Saving\u2026":["Salvando\u2026"],"When enabled this form will not store User IP, Browser Name and the Device Name in the Entries.":["Quando ativado, este formul\u00e1rio n\u00e3o armazenar\u00e1 o IP do usu\u00e1rio, o nome do navegador e o nome do dispositivo nas entradas."],"Failed to save. Please try again.":["Falha ao salvar. Por favor, tente novamente."],"reCAPTCHA API keys for the selected version are not configured. Set them in Global Settings.":["As chaves da API reCAPTCHA para a vers\u00e3o selecionada n\u00e3o est\u00e3o configuradas. Defina-as nas Configura\u00e7\u00f5es Globais."],"Please select a reCAPTCHA version.":["Por favor, selecione uma vers\u00e3o do reCAPTCHA."],"hCaptcha API keys are not configured. Set them in Global Settings.":["As chaves da API do hCaptcha n\u00e3o est\u00e3o configuradas. Defina-as nas Configura\u00e7\u00f5es Globais."],"Cloudflare Turnstile API keys are not configured. Set them in Global Settings.":["As chaves da API do Cloudflare Turnstile n\u00e3o est\u00e3o configuradas. Defina-as nas Configura\u00e7\u00f5es Globais."],"Form data":["Dados do formul\u00e1rio"],"Some fields need attention":["Alguns campos precisam de aten\u00e7\u00e3o"],"Unsaved changes":["Altera\u00e7\u00f5es n\u00e3o salvas"],"A recipient email address and subject line are required before this notification can be saved. Fix the highlighted fields, or discard your changes to go back.":["Um endere\u00e7o de e-mail do destinat\u00e1rio e uma linha de assunto s\u00e3o necess\u00e1rios antes que esta notifica\u00e7\u00e3o possa ser salva. Corrija os campos destacados ou descarte suas altera\u00e7\u00f5es para voltar."],"You have unsaved changes for this notification. Discard them to go back, or stay to save them.":["Voc\u00ea tem altera\u00e7\u00f5es n\u00e3o salvas para esta notifica\u00e7\u00e3o. Descarte-as para voltar ou permane\u00e7a para salv\u00e1-las."],"Discard & go back":["Descartar e voltar"],"Stay & fix":["Fique e conserte"],"Keep editing":["Continue editando"],"Please provide a recipient email address.":["Por favor, forne\u00e7a um endere\u00e7o de e-mail do destinat\u00e1rio."],"Please provide a subject line.":["Por favor, forne\u00e7a uma linha de assunto."],"Please provide a custom URL.":["Por favor, forne\u00e7a um URL personalizado."],"You have unsaved changes. Discard them to continue, or stay to save your changes.":["Voc\u00ea tem altera\u00e7\u00f5es n\u00e3o salvas. Descarte-as para continuar ou permane\u00e7a para salvar suas altera\u00e7\u00f5es."],"Discard & continue":["Descartar e continuar"],"Show Entries":["Mostrar Entradas"],"Display this form\u2019s entries in a table on the frontend.":["Exiba as entradas deste formul\u00e1rio em uma tabela no frontend."],"Publish this form\u2019s submissions as a sortable, paginated table on any page \u2014 perfect for public directories, listings, and team dashboards, with per-role view access.":["Publique as submiss\u00f5es deste formul\u00e1rio como uma tabela classific\u00e1vel e paginada em qualquer p\u00e1gina \u2014 perfeito para diret\u00f3rios p\u00fablicos, listagens e pain\u00e9is de equipe, com acesso de visualiza\u00e7\u00e3o por fun\u00e7\u00e3o."],"Embed a sortable, paginated entries table with a simple shortcode.":["Incorpore uma tabela de entradas classific\u00e1vel e paginada com um shortcode simples."],"Choose exactly which fields appear as columns.":["Escolha exatamente quais campos aparecem como colunas."],"Restrict who can view entries by user role.":["Restrinja quem pode ver as entradas por fun\u00e7\u00e3o de usu\u00e1rio."],"Quill heading picker: default paragraph style\u0004Normal":["Normal"]}}} \ No newline at end of file diff --git a/languages/sureforms-pt_PT.mo b/languages/sureforms-pt_PT.mo index 4ab0d975f3e1eff12969f188a30e53e94e471026..04f537b12381d7a4ded7c7d712adab6172bd0fe8 100644 GIT binary patch delta 72202 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" msgstr "Formato esperado para e-mails - email@sureforms.com ou John Doe " -#: admin/admin.php:710 -#: admin/admin.php:711 +#: admin/admin.php:725 +#: admin/admin.php:726 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -12322,8 +12325,8 @@ msgstr "Desconhecido" #: inc/payments/admin/admin-handler.php:1501 #: inc/payments/admin/admin-handler.php:1505 #: inc/payments/admin/admin-handler.php:1509 -#: inc/payments/front-end.php:865 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:866 +#: inc/payments/front-end.php:1275 msgid "Guest User" msgstr "Usuário Convidado" @@ -12336,7 +12339,7 @@ msgid "Valid customer email is required for payments." msgstr "É necessário um e-mail válido do cliente para pagamentos." #: inc/payments/front-end.php:115 -#: inc/payments/front-end.php:340 +#: inc/payments/front-end.php:341 #: inc/payments/stripe/admin-stripe-handler.php:861 #: inc/payments/stripe/payments-settings.php:349 #: inc/payments/stripe/payments-settings.php:474 @@ -12347,136 +12350,136 @@ msgid "Stripe is not connected." msgstr "O Stripe não está conectado." #: inc/payments/front-end.php:121 -#: inc/payments/front-end.php:346 -#: inc/payments/front-end.php:634 -#: inc/payments/front-end.php:1153 +#: inc/payments/front-end.php:347 +#: inc/payments/front-end.php:635 +#: inc/payments/front-end.php:1154 msgid "Stripe secret key not found." msgstr "Chave secreta do Stripe não encontrada." #. translators: %s: formatted payment amount -#: inc/payments/front-end.php:203 +#: inc/payments/front-end.php:204 #, php-format msgid "The payment amount (%s) is below the minimum allowed. Stripe only processes amounts above 50¢." msgstr "O valor do pagamento (%s) está abaixo do mínimo permitido. O Stripe só processa valores acima de 50¢." #. translators: %s: Field name -#: inc/payments/front-end.php:273 +#: inc/payments/front-end.php:274 #, php-format msgid "Missing required field: %s" msgstr "Campo obrigatório ausente: %s" -#: inc/payments/front-end.php:292 +#: inc/payments/front-end.php:293 msgid "Valid customer email is required for subscriptions." msgstr "É necessário um e-mail válido do cliente para assinaturas." -#: inc/payments/front-end.php:297 +#: inc/payments/front-end.php:298 msgid "Customer name is required for subscriptions." msgstr "O nome do cliente é necessário para assinaturas." -#: inc/payments/front-end.php:314 +#: inc/payments/front-end.php:315 msgid "Amount must be greater than 0" msgstr "O valor deve ser maior que 0" -#: inc/payments/front-end.php:322 +#: inc/payments/front-end.php:323 msgid "Invalid billing interval" msgstr "Intervalo de cobrança inválido" -#: inc/payments/front-end.php:357 +#: inc/payments/front-end.php:358 msgid "Failed to create customer for subscription." msgstr "Falha ao criar cliente para a assinatura." -#: inc/payments/front-end.php:390 -#: inc/payments/front-end.php:406 +#: inc/payments/front-end.php:391 +#: inc/payments/front-end.php:407 msgid "Failed to create subscription through middleware." msgstr "Falha ao criar a assinatura através do middleware." -#: inc/payments/front-end.php:411 +#: inc/payments/front-end.php:412 msgid "Empty response from subscription creation." msgstr "Resposta vazia da criação de assinatura." -#: inc/payments/front-end.php:416 +#: inc/payments/front-end.php:417 msgid "Invalid JSON response from subscription creation." msgstr "Resposta JSON inválida da criação de assinatura." -#: inc/payments/front-end.php:420 -#: inc/payments/front-end.php:424 +#: inc/payments/front-end.php:421 +#: inc/payments/front-end.php:425 msgid "Invalid subscription data." msgstr "Dados de assinatura inválidos." -#: inc/payments/front-end.php:431 +#: inc/payments/front-end.php:432 msgid "Failed to create subscription." msgstr "Falha ao criar assinatura." #. translators: %s: Error message -#: inc/payments/front-end.php:464 +#: inc/payments/front-end.php:465 #, php-format msgid "Unexpected error: %s" msgstr "Erro inesperado: %s" -#: inc/payments/front-end.php:604 +#: inc/payments/front-end.php:605 #: inc/payments/stripe/admin-stripe-handler.php:103 #: inc/payments/stripe/admin-stripe-handler.php:165 #: inc/payments/stripe/admin-stripe-handler.php:471 msgid "Subscription ID not found." msgstr "ID de assinatura não encontrado." -#: inc/payments/front-end.php:623 +#: inc/payments/front-end.php:624 msgid "Customer ID not found for the payment." msgstr "ID do cliente não encontrado para o pagamento." -#: inc/payments/front-end.php:651 +#: inc/payments/front-end.php:652 msgid "Failed to retrieve setup intent." msgstr "Falha ao recuperar a intenção de configuração." -#: inc/payments/front-end.php:699 +#: inc/payments/front-end.php:700 msgid "Failed to update subscription." msgstr "Falha ao atualizar a assinatura." -#: inc/payments/front-end.php:707 +#: inc/payments/front-end.php:708 msgid "Latest invoice not found on subscription." msgstr "Fatura mais recente não encontrada na assinatura." -#: inc/payments/front-end.php:720 +#: inc/payments/front-end.php:721 msgid "Failed to retrieve invoice." msgstr "Falha ao recuperar a fatura." -#: inc/payments/front-end.php:742 +#: inc/payments/front-end.php:743 msgid "Payment intent not found on invoice." msgstr "Intenção de pagamento não encontrada na fatura." -#: inc/payments/front-end.php:757 +#: inc/payments/front-end.php:758 msgid "Failed to confirm payment." msgstr "Falha ao confirmar o pagamento." -#: inc/payments/front-end.php:773 +#: inc/payments/front-end.php:774 msgid "Failed to retrieve subscription." msgstr "Falha ao recuperar a assinatura." -#: inc/payments/front-end.php:788 +#: inc/payments/front-end.php:789 msgid "Subscription not found for the payment." msgstr "Assinatura não encontrada para o pagamento." #. translators: %d: User ID -#: inc/payments/front-end.php:863 -#: inc/payments/front-end.php:1274 +#: inc/payments/front-end.php:864 +#: inc/payments/front-end.php:1275 #, php-format msgid "User ID: %d" msgstr "ID do usuário: %d" #. translators: %s: Invoice status -#: inc/payments/front-end.php:871 +#: inc/payments/front-end.php:872 #, php-format msgid "Invoice Status: %s" msgstr "Status da Fatura: %s" #. translators: Title for subscription verification log -#: inc/payments/front-end.php:878 +#: inc/payments/front-end.php:879 msgid "Subscription Verification" msgstr "Verificação de Assinatura" #. translators: %s: Subscription ID #. translators: %s: Stripe subscription ID -#: inc/payments/front-end.php:882 +#: inc/payments/front-end.php:883 #: inc/payments/stripe/admin-stripe-handler.php:185 #: inc/payments/stripe/admin-stripe-handler.php:487 #: inc/payments/stripe/stripe-webhook.php:572 @@ -12491,8 +12494,8 @@ msgstr "ID de Assinatura: %s" #. translators: %s: payment gateway name (e.g., Stripe) #. translators: %s: payment gateway #. translators: %s: Payment gateway name (e.g., Stripe). -#: inc/payments/front-end.php:884 -#: inc/payments/front-end.php:1285 +#: inc/payments/front-end.php:885 +#: inc/payments/front-end.php:1286 #: inc/payments/stripe/admin-stripe-handler.php:190 #: inc/payments/stripe/admin-stripe-handler.php:492 #: inc/payments/stripe/admin-stripe-handler.php:667 @@ -12506,13 +12509,13 @@ msgid "Payment Gateway: %s" msgstr "Gateway de Pagamento: %s" #. translators: %s: Payment Intent ID -#: inc/payments/front-end.php:886 +#: inc/payments/front-end.php:887 #, php-format msgid "Payment Intent ID: %s" msgstr "ID da Intenção de Pagamento: %s" #. translators: %s: Charge ID -#: inc/payments/front-end.php:888 +#: inc/payments/front-end.php:889 #: inc/payments/stripe/stripe-webhook.php:1084 #, php-format msgid "Charge ID: %s" @@ -12520,7 +12523,7 @@ msgstr "ID de Cobrança: %s" #. translators: %s: Subscription Status #. translators: %s: subscription status -#: inc/payments/front-end.php:890 +#: inc/payments/front-end.php:891 #: inc/payments/stripe/admin-stripe-handler.php:195 #: inc/payments/stripe/admin-stripe-handler.php:497 #, php-format @@ -12528,7 +12531,7 @@ msgid "Subscription Status: %s" msgstr "Status da Assinatura: %s" #. translators: %s: Customer ID -#: inc/payments/front-end.php:892 +#: inc/payments/front-end.php:893 #: inc/payments/stripe/stripe-webhook.php:1174 #, php-format msgid "Customer ID: %s" @@ -12536,8 +12539,8 @@ msgstr "ID do Cliente: %s" #. translators: 1: Amount, 2: Currency #. translators: %1$s: amount, %2$s: currency. -#: inc/payments/front-end.php:894 -#: inc/payments/front-end.php:1287 +#: inc/payments/front-end.php:895 +#: inc/payments/front-end.php:1288 #: inc/payments/stripe/stripe-webhook.php:1089 #: inc/payments/stripe/stripe-webhook.php:1166 #, php-format @@ -12546,34 +12549,34 @@ msgstr "Quantia: %1$s %2$s" #. translators: %s: Payment mode (e.g. Live or Test) #. translators: %s: payment mode -#: inc/payments/front-end.php:897 -#: inc/payments/front-end.php:1292 +#: inc/payments/front-end.php:898 +#: inc/payments/front-end.php:1293 #, php-format msgid "Mode: %s" msgstr "Modo: %s" -#: inc/payments/front-end.php:931 +#: inc/payments/front-end.php:932 msgid "Failed to verify subscription." msgstr "Falha ao verificar a assinatura." -#: inc/payments/front-end.php:1186 -#: inc/payments/front-end.php:1204 -#: inc/payments/front-end.php:1212 -#: inc/payments/front-end.php:1219 +#: inc/payments/front-end.php:1187 +#: inc/payments/front-end.php:1205 +#: inc/payments/front-end.php:1213 +#: inc/payments/front-end.php:1220 msgid "Failed to retrieve payment intent." msgstr "Falha ao recuperar a intenção de pagamento." -#: inc/payments/front-end.php:1227 +#: inc/payments/front-end.php:1228 msgid "Payment was not confirmed successfully." msgstr "O pagamento não foi confirmado com sucesso." -#: inc/payments/front-end.php:1279 +#: inc/payments/front-end.php:1280 msgid "Payment Verification" msgstr "Verificação de Pagamento" #. translators: %s: Stripe transaction ID #. translators: %s: Charge ID -#: inc/payments/front-end.php:1283 +#: inc/payments/front-end.php:1284 #: inc/payments/stripe/stripe-webhook.php:1162 #, php-format msgid "Transaction ID: %s" @@ -12581,18 +12584,18 @@ msgstr "ID da Transação: %s" #. translators: %s: payment status #. translators: %s: Status -#: inc/payments/front-end.php:1289 +#: inc/payments/front-end.php:1290 #: inc/payments/stripe/stripe-webhook.php:576 #: inc/payments/stripe/stripe-webhook.php:1168 #, php-format msgid "Status: %s" msgstr "Status: %s" -#: inc/payments/front-end.php:1395 +#: inc/payments/front-end.php:1396 msgid "Failed to create Stripe customer." msgstr "Falha ao criar cliente Stripe." -#: inc/payments/front-end.php:1459 +#: inc/payments/front-end.php:1460 msgid "Failed to create Stripe guest customer." msgstr "Falha ao criar cliente convidado do Stripe." @@ -13457,7 +13460,7 @@ msgstr "Criado através do ciclo de faturamento por assinatura" msgid "Edit this form" msgstr "Edite este formulário" -#: inc/global-settings/global-settings.php:529 +#: inc/global-settings/global-settings.php:531 #: inc/migrator/base-migrator.php:549 #: inc/post-types.php:1081 msgid "Your form has been submitted successfully. We'll review your details and get back to you soon." @@ -15080,7 +15083,7 @@ msgid "Failed to create duplicate form." msgstr "Falha ao criar formulário duplicado." #: inc/payments/front-end.php:98 -#: inc/payments/front-end.php:303 +#: inc/payments/front-end.php:304 #: inc/payments/payment-helper.php:596 msgid "Invalid form configuration." msgstr "Configuração de formulário inválida." @@ -15156,7 +15159,7 @@ msgstr "É necessário o valor do campo de quantidade variável." msgid "Payment amount below minimum. Minimum: %1$s, received %2$s." msgstr "Valor do pagamento abaixo do mínimo. Mínimo: %1$s, recebido %2$s." -#: inc/rest-api.php:1839 +#: inc/rest-api.php:1851 msgid " (Copy)" msgstr "(Cópia)" @@ -15355,12 +15358,12 @@ msgid "Stripe Settings" msgstr "Configurações do Stripe" #. translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version. -#: admin/admin.php:1514 +#: admin/admin.php:1531 #, php-format msgid "SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version." msgstr "O SureForms %1$s requer o mínimo %2$s %3$s para funcionar corretamente. Por favor, atualize para a versão mais recente." -#: admin/admin.php:1527 +#: admin/admin.php:1544 msgid "Update Now" msgstr "Atualizar agora" @@ -15408,8 +15411,8 @@ msgstr "Posição do Sinal de Moeda" msgid "Select the position of the currency symbol relative to the amount." msgstr "Selecione a posição do símbolo da moeda em relação ao valor." -#: admin/admin.php:653 -#: admin/admin.php:654 +#: admin/admin.php:668 +#: admin/admin.php:669 #: assets/build/dashboard.js:172 #: assets/build/entries.js:172 #: assets/build/forms.js:172 @@ -15423,40 +15426,40 @@ msgstr "Selecione a posição do símbolo da moeda em relação ao valor." msgid "Learn" msgstr "Aprender" -#: admin/admin.php:1640 +#: admin/admin.php:1657 msgid "Amazing! SureForms is powering your forms and submissions - let's keep growing together!" msgstr "Incrível! SureForms está alimentando seus formulários e submissões - vamos continuar crescendo juntos!" -#: admin/admin.php:1641 +#: admin/admin.php:1658 msgid "If SureForms has been helpful, would you mind taking a moment to leave a 5-star review on WordPress.org?" msgstr "Se o SureForms foi útil, você se importaria de deixar um comentário de 5 estrelas no WordPress.org?" -#: admin/admin.php:1644 -#: admin/admin.php:1688 +#: admin/admin.php:1661 +#: admin/admin.php:1705 msgid "Maybe later" msgstr "Talvez mais tarde" -#: admin/admin.php:1645 +#: admin/admin.php:1662 msgid "I already did" msgstr "Eu já fiz" -#: admin/admin.php:1684 +#: admin/admin.php:1701 msgid "SureForms is ready to power your forms — explore what's possible!" msgstr "SureForms está pronto para potencializar seus formulários — explore o que é possível!" -#: admin/admin.php:1685 +#: admin/admin.php:1702 msgid "Manage your forms, track submissions, and discover features like AI Form Builder, payment integrations, and more from the SureForms dashboard." msgstr "Gerencie seus formulários, acompanhe as submissões e descubra recursos como o Construtor de Formulários com IA, integrações de pagamento e muito mais no painel do SureForms." -#: admin/admin.php:1687 +#: admin/admin.php:1704 msgid "Go to Dashboard" msgstr "Ir para o Painel" -#: admin/admin.php:1689 +#: admin/admin.php:1706 msgid "I already know" msgstr "Eu já sei" -#: admin/admin.php:1769 +#: admin/admin.php:1786 msgid "Invalid parameters." msgstr "Parâmetros inválidos." @@ -15850,7 +15853,7 @@ msgstr "Este formulário ainda não está disponível. Verifique novamente após #: inc/form-submit.php:1015 #: inc/migrator/bootstrap.php:357 #: inc/payments/front-end.php:78 -#: inc/payments/front-end.php:263 +#: inc/payments/front-end.php:264 #: inc/rest-api.php:124 #: inc/rest-api.php:183 #: inc/rest-api.php:349 @@ -15863,8 +15866,8 @@ msgstr "Este formulário ainda não está disponível. Verifique novamente após #: inc/rest-api.php:742 #: inc/rest-api.php:775 #: inc/rest-api.php:914 -#: inc/rest-api.php:987 -#: inc/rest-api.php:1047 +#: inc/rest-api.php:999 +#: inc/rest-api.php:1059 #: inc/single-form-settings/form-settings-api.php:89 msgid "Security verification failed. Please refresh the page and try again." msgstr "A verificação de segurança falhou. Por favor, atualize a página e tente novamente." @@ -16163,28 +16166,28 @@ msgstr "Ação inválida. Use \"ler\" ou \"não lida\"." msgid "Invalid action. Use \"trash\" or \"restore\"." msgstr "Ação inválida. Use \"trash\" ou \"restore\"." -#: inc/rest-api.php:1061 +#: inc/rest-api.php:1073 msgid "Select at least one form and specify an action." msgstr "Selecione pelo menos um formulário e especifique uma ação." -#: inc/rest-api.php:1076 +#: inc/rest-api.php:1088 msgid "Form not found or is not a valid form type." msgstr "Formulário não encontrado ou não é um tipo de formulário válido." -#: inc/rest-api.php:1088 +#: inc/rest-api.php:1100 msgid "This form is already in the trash." msgstr "Este formulário já está na lixeira." -#: inc/rest-api.php:1099 +#: inc/rest-api.php:1111 msgid "This form is not in the trash." msgstr "Este formulário não está na lixeira." -#: inc/rest-api.php:1135 +#: inc/rest-api.php:1147 msgid "Invalid action." msgstr "Ação inválida." #. translators: %s: action name -#: inc/rest-api.php:1150 +#: inc/rest-api.php:1162 #, php-format msgid "Failed to %s this form. Please try again." msgstr "Falha ao %s este formulário. Por favor, tente novamente." @@ -16202,32 +16205,32 @@ msgid "Enter a valid email address." msgstr "Insira um endereço de e-mail válido." #. translators: %s represents the minimum acceptable value -#: inc/translatable.php:97 +#: inc/translatable.php:103 #, php-format msgid "Minimum value is %s." msgstr "O valor mínimo é %s." #. translators: %s represents the maximum acceptable value -#: inc/translatable.php:100 +#: inc/translatable.php:106 #, php-format msgid "Maximum value is %s." msgstr "O valor máximo é %s." #. translators: %s represents the minimum number of options to select -#: inc/translatable.php:103 #: inc/translatable.php:109 +#: inc/translatable.php:115 #, php-format msgid "Select at least %s options." msgstr "Selecione pelo menos %s opções." #. translators: %s represents the maximum number of options that can be selected -#: inc/translatable.php:106 #: inc/translatable.php:112 +#: inc/translatable.php:118 #, php-format msgid "You can select up to %s options." msgstr "Você pode selecionar até %s opções." -#: inc/translatable.php:151 +#: inc/translatable.php:159 msgid "This form is now closed as we have reached the maximum number of entries." msgstr "Este formulário está agora fechado, pois atingimos o número máximo de inscrições." @@ -16423,7 +16426,7 @@ msgid "Duplicate form?" msgstr "Duplicar formulário?" #: assets/build/formSubmit.js:2 -#: assets/js/unminified/form-submit.js:487 +#: assets/js/unminified/form-submit.js:495 msgid "An error occurred while submitting your form. Please try again." msgstr "Ocorreu um erro ao enviar o seu formulário. Por favor, tente novamente." @@ -16750,35 +16753,35 @@ msgstr "Recuperar as configurações globais do SureForms. Opcionalmente, filtre msgid "Setting categories to retrieve. Omit for all categories." msgstr "Definindo categorias para recuperar. Omitir para todas as categorias." -#: inc/abilities/settings/update-global-settings.php:106 +#: inc/abilities/settings/update-global-settings.php:108 msgid "Update Global Settings" msgstr "Atualizar Configurações Globais" -#: inc/abilities/settings/update-global-settings.php:107 +#: inc/abilities/settings/update-global-settings.php:109 msgid "Update SureForms global settings for a specific category: general, validation-messages, email-summary, or security." msgstr "Atualize as configurações globais do SureForms para uma categoria específica: geral, mensagens de validação, resumo de e-mail ou segurança." -#: inc/abilities/settings/update-global-settings.php:140 +#: inc/abilities/settings/update-global-settings.php:142 msgid "The settings category to update." msgstr "A categoria de configurações a ser atualizada." -#: inc/abilities/settings/update-global-settings.php:145 +#: inc/abilities/settings/update-global-settings.php:147 msgid "Key-value pairs of settings to update." msgstr "Pares chave-valor de configurações para atualizar." -#: inc/abilities/settings/update-global-settings.php:181 +#: inc/abilities/settings/update-global-settings.php:183 msgid "Settings category is required." msgstr "A categoria de configurações é obrigatória." -#: inc/abilities/settings/update-global-settings.php:189 +#: inc/abilities/settings/update-global-settings.php:191 msgid "Settings data is required." msgstr "É necessário fornecer os dados de configurações." -#: inc/abilities/settings/update-global-settings.php:200 +#: inc/abilities/settings/update-global-settings.php:202 msgid "No valid settings keys provided for this category." msgstr "Nenhuma chave de configuração válida fornecida para esta categoria." -#: inc/abilities/settings/update-global-settings.php:226 +#: inc/abilities/settings/update-global-settings.php:228 msgid "Invalid settings category." msgstr "Categoria de configurações inválida." @@ -16794,19 +16797,19 @@ msgstr "Segredo do webhook não configurado." msgid "Invalid webhook signature." msgstr "Assinatura de webhook inválida." -#: admin/admin.php:492 -#: admin/admin.php:1085 +#: admin/admin.php:507 +#: admin/admin.php:1101 #: assets/build/formEditor.js:172 msgid "Quizzes" msgstr "Questionários" -#: admin/admin.php:491 +#: admin/admin.php:506 msgid "Quiz Entries" msgstr "Entradas do Quiz" -#: admin/admin.php:494 -#: admin/admin.php:527 -#: admin/admin.php:560 +#: admin/admin.php:509 +#: admin/admin.php:542 +#: admin/admin.php:575 #: assets/build/settings.js:172 msgid "New" msgstr "Novo" @@ -17324,43 +17327,43 @@ msgstr "Perfeito para educação, treinamento e curiosidades divertidas" msgid "Select this to create a quiz with scored questions and graded results." msgstr "Selecione isto para criar um questionário com perguntas pontuadas e resultados avaliados." -#: admin/admin.php:524 -#: admin/admin.php:525 -#: admin/admin.php:1090 +#: admin/admin.php:539 +#: admin/admin.php:540 +#: admin/admin.php:1106 msgid "Survey Reports" msgstr "Relatórios de Pesquisa" -#: inc/frontend-assets.php:301 +#: inc/frontend-assets.php:304 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 1" msgstr "Título 1" -#: inc/frontend-assets.php:302 +#: inc/frontend-assets.php:305 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 2" msgstr "Título 2" -#: inc/frontend-assets.php:303 +#: inc/frontend-assets.php:306 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 3" msgstr "Título 3" -#: inc/frontend-assets.php:304 +#: inc/frontend-assets.php:307 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 4" msgstr "Título 4" -#: inc/frontend-assets.php:305 +#: inc/frontend-assets.php:308 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 5" msgstr "Título 5" -#: inc/frontend-assets.php:306 +#: inc/frontend-assets.php:309 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgid "Heading 6" @@ -17694,15 +17697,15 @@ msgstr "Publicação inválida." msgid "You cannot edit this post." msgstr "Você não pode editar esta postagem." -#: inc/ai-form-builder/ai-form-builder.php:96 +#: inc/ai-form-builder/ai-form-builder.php:108 msgid "The AI did not return a form. Please refine your prompt and try again." msgstr "A IA não retornou um formulário. Por favor, refine seu prompt e tente novamente." -#: inc/ai-form-builder/ai-form-builder.php:104 +#: inc/ai-form-builder/ai-form-builder.php:116 msgid "The AI response is missing a form title. Please try again." msgstr "A resposta da IA está faltando um título de formulário. Por favor, tente novamente." -#: inc/ai-form-builder/ai-form-builder.php:115 +#: inc/ai-form-builder/ai-form-builder.php:127 #: inc/ai-form-builder/field-mapping.php:69 msgid "The AI was unable to generate form fields. Please try again." msgstr "A IA não conseguiu gerar os campos do formulário. Por favor, tente novamente." @@ -17743,7 +17746,7 @@ msgstr "Pagamento Único" msgid "Choose payment type" msgstr "Escolha o tipo de pagamento" -#: inc/payments/front-end.php:333 +#: inc/payments/front-end.php:334 msgid "Billing interval does not match the form configuration." msgstr "O intervalo de faturamento não corresponde à configuração do formulário." @@ -17887,15 +17890,15 @@ msgstr "A resposta da IA estava vazia. Por favor, refine seu prompt e tente nova msgid "Unable to build form fields from the AI response." msgstr "Não foi possível criar campos de formulário a partir da resposta da IA." -#: admin/admin.php:557 -#: admin/admin.php:558 -#: admin/admin.php:1095 +#: admin/admin.php:572 +#: admin/admin.php:573 +#: admin/admin.php:1111 msgid "Partial Entries" msgstr "Entradas Parciais" #: inc/global-settings/global-settings-defaults.php:260 -#: inc/global-settings/global-settings.php:425 -#: inc/global-settings/global-settings.php:613 +#: inc/global-settings/global-settings.php:427 +#: inc/global-settings/global-settings.php:615 #: assets/build/settings.js:172 msgid "This form is now closed as we've received all the entries." msgstr "Este formulário está agora fechado, pois recebemos todas as inscrições." @@ -17904,16 +17907,16 @@ msgstr "Este formulário está agora fechado, pois recebemos todas as inscriçõ msgid "Invalid settings tab." msgstr "Aba de configurações inválida." -#: inc/global-settings/global-settings.php:481 +#: inc/global-settings/global-settings.php:483 #: assets/build/settings.js:172 msgid "Thank you for contacting us! We will be in touch with you shortly." msgstr "Obrigado por nos contatar! Entraremos em contato com você em breve." -#: inc/rest-api.php:1115 +#: inc/rest-api.php:1127 msgid "Use the restore action to recover a trashed form before switching it to draft." msgstr "Use a ação de restauração para recuperar um formulário descartado antes de alterá-lo para rascunho." -#: inc/rest-api.php:1120 +#: inc/rest-api.php:1132 msgid "This form is already a draft." msgstr "Este formulário já é um rascunho." @@ -18760,6 +18763,72 @@ msgstr "O valor do pagamento não pôde ser verificado para este formulário. Po msgid "Cancellation is not supported for this payment gateway." msgstr "O cancelamento não é suportado para este gateway de pagamento." +#: admin/admin.php:1988 +msgid "SureForms AI Quick Draft" +msgstr "SureForms AI Rascunho Rápido" + +#: admin/admin.php:2007 +msgid "Describe the form and let SureForms AI generate it for you." +msgstr "Descreva o formulário e deixe o SureForms AI gerá-lo para você." + +#: admin/admin.php:2010 +msgid "Describe your form" +msgstr "Descreva seu formulário" + +#: admin/admin.php:2017 +msgid "Example: Create a contact form with name, email, phone, and message fields." +msgstr "Exemplo: Crie um formulário de contato com campos de nome, email, telefone e mensagem." + +#: admin/admin.php:2058 +msgid "Redirecting..." +msgstr "Redirecionando..." + +#: inc/ai-form-builder/ai-form-builder.php:57 +msgid "No prompt was supplied." +msgstr "Nenhum prompt foi fornecido." + +#: inc/ai-form-builder/ai-form-builder.php:64 +msgid "The prompt is too long. Please shorten it and try again." +msgstr "O prompt é muito longo. Por favor, encurte-o e tente novamente." + +#. translators: %s: maximum characters allowed before the @ symbol. +#: inc/field-validation.php:316 +#: inc/translatable.php:97 +#, php-format +msgid "The part before @ may not exceed %s characters." +msgstr "A parte antes do @ não pode exceder %s caracteres." + +#. translators: %s: maximum characters allowed after the @ symbol. +#: inc/field-validation.php:322 +#: inc/translatable.php:100 +#, php-format +msgid "The part after @ may not exceed %s characters." +msgstr "A parte após o @ não pode exceder %s caracteres." + +#: assets/build/formEditor.js:172 +msgid "Show Entries" +msgstr "Mostrar Entradas" + +#: assets/build/formEditor.js:172 +msgid "Display this form’s entries in a table on the frontend." +msgstr "Exiba as entradas deste formulário em uma tabela no frontend." + +#: assets/build/formEditor.js:172 +msgid "Publish this form’s submissions as a sortable, paginated table on any page — perfect for public directories, listings, and team dashboards, with per-role view access." +msgstr "Publique as submissões deste formulário como uma tabela classificável e paginada em qualquer página — perfeito para diretórios públicos, listagens e painéis de equipe, com acesso de visualização por função." + +#: assets/build/formEditor.js:172 +msgid "Embed a sortable, paginated entries table with a simple shortcode." +msgstr "Incorpore uma tabela de entradas classificável e paginada com um shortcode simples." + +#: assets/build/formEditor.js:172 +msgid "Choose exactly which fields appear as columns." +msgstr "Escolha exatamente quais campos aparecem como colunas." + +#: assets/build/formEditor.js:172 +msgid "Restrict who can view entries by user role." +msgstr "Restrinja quem pode ver as entradas por função de usuário." + #: inc/post-types.php:226 msgctxt "post type general name" msgid "Forms" @@ -19184,34 +19253,34 @@ msgctxt "block keyword" msgid "color" msgstr "cor" -#: inc/frontend-assets.php:300 +#: inc/frontend-assets.php:303 #: assets/build/formEditor.js:172 #: assets/build/settings.js:172 msgctxt "Quill heading picker: default paragraph style" msgid "Normal" msgstr "Normal" -#: inc/frontend-assets.php:307 +#: inc/frontend-assets.php:310 msgctxt "Quill link tooltip label" msgid "Visit URL:" msgstr "Visitar URL:" -#: inc/frontend-assets.php:308 +#: inc/frontend-assets.php:311 msgctxt "Quill link tooltip label" msgid "Enter link:" msgstr "Insira o link:" -#: inc/frontend-assets.php:309 +#: inc/frontend-assets.php:312 msgctxt "Quill link tooltip action" msgid "Edit" msgstr "Editar" -#: inc/frontend-assets.php:310 +#: inc/frontend-assets.php:313 msgctxt "Quill link tooltip action" msgid "Save" msgstr "Salvar" -#: inc/frontend-assets.php:311 +#: inc/frontend-assets.php:314 msgctxt "Quill link tooltip action" msgid "Remove" msgstr "Remover" From d817ef098d15f66203e1bd40c9c8c924120d0e28 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Wed, 8 Jul 2026 13:29:24 +0530 Subject: [PATCH 33/35] ci: use npm ci instead of npm install --force in deploy/asset workflows Replaces npm install --force with npm ci in push-to-deploy.yml and push-asset-readme-update.yml, matching the workflows that already use npm ci (playwright, code-analysis, release-tag-draft, update-translations). npm ci installs from the committed lockfile without re-resolving peer deps, so the --force ERESOLVE workaround is no longer needed. Non-breaking: neither workflow touches package.json before install, and npm ci installs the devDependencies that npm run build needs. --- .github/workflows/push-asset-readme-update.yml | 2 +- .github/workflows/push-to-deploy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push-asset-readme-update.yml b/.github/workflows/push-asset-readme-update.yml index 8e5de8169..fed4455c2 100644 --- a/.github/workflows/push-asset-readme-update.yml +++ b/.github/workflows/push-asset-readme-update.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@master - name: Build run: | - npm install --force + npm ci npm run build - name: WordPress.org plugin asset/readme update uses: 10up/action-wordpress-plugin-asset-update@stable diff --git a/.github/workflows/push-to-deploy.yml b/.github/workflows/push-to-deploy.yml index 87c43d058..8e518fe41 100644 --- a/.github/workflows/push-to-deploy.yml +++ b/.github/workflows/push-to-deploy.yml @@ -18,7 +18,7 @@ jobs: fi - name: Build run: | - npm install --force + npm ci npm run build - name: WordPress Plugin Deploy uses: 10up/action-wordpress-plugin-deploy@master From 70b4593cbcbdd71750ddfcf6b50e7404757a050f Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Wed, 8 Jul 2026 14:47:08 +0530 Subject: [PATCH 34/35] ci: pin WP-CLI to v2.12.0 in update-translations workflow --- .github/workflows/update-translations.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-translations.yml b/.github/workflows/update-translations.yml index 03fdc066f..38ca26957 100644 --- a/.github/workflows/update-translations.yml +++ b/.github/workflows/update-translations.yml @@ -83,9 +83,13 @@ jobs: node-version: '24.16.0' cache: 'npm' + # Pin WP-CLI to a fixed release. `wp i18n make-json` stamps + # "generator":"WP-CLI/" into every languages/*.json, so + # installing "latest" rewrites all JSON files whenever WP-CLI + # ships a new version. Bump this pin deliberately. - name: Install WP-CLI run: | - curl -sSfL -o wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar + curl -sSfL -o wp-cli.phar https://github.com/wp-cli/wp-cli/releases/download/v2.12.0/wp-cli-2.12.0.phar chmod +x wp-cli.phar sudo mv wp-cli.phar /usr/local/bin/wp wp --info From 0c2ef094739a456e0ab4a5d2f7efd82980648d95 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Thu, 9 Jul 2026 14:06:37 +0530 Subject: [PATCH 35/35] chore: strip internal-only paths from public mirror sync --- .claude/agents/code-simplifier.md | 48 - .claude/agents/srfm-block-checker.md | 60 - .claude/commands/sureforms:investigate.md | 67 - .claude/commands/sureforms:sync-public.md | 299 ---- .claude/commands/sureforms:update-readme.md | 327 ----- .claude/commands/sureforms:version-bump.md | 204 --- .claude/commands/sync-release-branches.md | 96 -- .claude/settings.json | 73 - .../workflows/push-asset-readme-update.yml | 24 - .github/workflows/push-to-deploy.yml | 27 - .github/workflows/release-pr-template.yml | 41 - .github/workflows/release-tag-draft.yml | 83 -- .github/workflows/update-translations.yml | 205 --- .scripts/git-hooks/pre-commit | 106 -- ARCHITECTURE.md | 355 ----- CLAUDE.md | 165 --- COMPREHENSIVE_ANALYSIS.md | 304 ----- PRODUCT_ANALYSIS.md | 251 ---- TECHNICAL_OVERVIEW.md | 393 ------ bin/build-zip.sh | 47 - bin/checkout-and-build | 21 - bin/i18n.sh | 75 -- docs/01-getting-started/GETTING-STARTED.md | 647 --------- docs/04-backend/DATABASE-SCHEMA.md | 962 ------------- docs/04-backend/HOOKS-REFERENCE.md | 1055 --------------- docs/05-frontend/FRONTEND-GUIDE.md | 1197 ----------------- docs/FORM_MIGRATION_OVERVIEW.md | 77 -- docs/FORM_MIGRATION_PLAN.md | 458 ------- docs/wiki/AI-Form-Builder.md | 103 -- docs/wiki/Admin-Dashboard.md | 121 -- docs/wiki/Architecture-Overview.md | 294 ---- docs/wiki/Block-Editor-Controls.md | 109 -- docs/wiki/Changelog.md | 39 - docs/wiki/Contributing-Guide.md | 205 --- docs/wiki/Data-Export-Import.md | 120 -- docs/wiki/Database-Schema.md | 173 --- docs/wiki/Deployment-Guide.md | 109 -- docs/wiki/Email-Notifications.md | 86 -- docs/wiki/Environment-Configuration.md | 197 --- docs/wiki/Form-Fields-Architecture.md | 137 -- docs/wiki/Form-Submission-Flow.md | 191 --- docs/wiki/Frontend-Assets.md | 122 -- docs/wiki/Getting-Started.md | 149 -- docs/wiki/Gutenberg-Blocks.md | 129 -- docs/wiki/Home.md | 58 - docs/wiki/Payment-Integration.md | 127 -- docs/wiki/REST-API-Reference.md | 114 -- docs/wiki/State-Management.md | 109 -- docs/wiki/Testing-Guide.md | 172 --- docs/wiki/Troubleshooting-FAQ.md | 232 ---- docs/wiki/Using-WPML-With-SureForms.md | 243 ---- docs/wiki/WordPress-Hooks-Reference.md | 152 --- docs/wiki/_Footer.md | 3 - docs/wiki/_Sidebar.md | 36 - inc/abilities/CLAUDE.md | 66 - internal-docs/README.md | 160 --- internal-docs/ai-agent-guide.md | 459 ------- internal-docs/apis.md | 418 ------ internal-docs/architecture.md | 329 ----- internal-docs/codebase-map.md | 303 ----- internal-docs/coding-standards.md | 312 ----- internal-docs/faq.md | 1003 -------------- internal-docs/glossary.md | 723 ---------- internal-docs/maintenance.md | 842 ------------ internal-docs/onboarding.md | 1086 --------------- internal-docs/product-vision.md | 688 ---------- internal-docs/troubleshooting.md | 1031 -------------- internal-docs/ui-and-copy.md | 1077 --------------- tests/play/specs/CLAUDE.md | 324 ----- tests/play/specs/TODO.md | 244 ---- 70 files changed, 20262 deletions(-) delete mode 100644 .claude/agents/code-simplifier.md delete mode 100644 .claude/agents/srfm-block-checker.md delete mode 100644 .claude/commands/sureforms:investigate.md delete mode 100644 .claude/commands/sureforms:sync-public.md delete mode 100644 .claude/commands/sureforms:update-readme.md delete mode 100644 .claude/commands/sureforms:version-bump.md delete mode 100644 .claude/commands/sync-release-branches.md delete mode 100644 .claude/settings.json delete mode 100644 .github/workflows/push-asset-readme-update.yml delete mode 100644 .github/workflows/push-to-deploy.yml delete mode 100644 .github/workflows/release-pr-template.yml delete mode 100644 .github/workflows/release-tag-draft.yml delete mode 100644 .github/workflows/update-translations.yml delete mode 100644 .scripts/git-hooks/pre-commit delete mode 100644 ARCHITECTURE.md delete mode 100644 CLAUDE.md delete mode 100644 COMPREHENSIVE_ANALYSIS.md delete mode 100644 PRODUCT_ANALYSIS.md delete mode 100644 TECHNICAL_OVERVIEW.md delete mode 100644 bin/build-zip.sh delete mode 100755 bin/checkout-and-build delete mode 100644 bin/i18n.sh delete mode 100644 docs/01-getting-started/GETTING-STARTED.md delete mode 100644 docs/04-backend/DATABASE-SCHEMA.md delete mode 100644 docs/04-backend/HOOKS-REFERENCE.md delete mode 100644 docs/05-frontend/FRONTEND-GUIDE.md delete mode 100644 docs/FORM_MIGRATION_OVERVIEW.md delete mode 100644 docs/FORM_MIGRATION_PLAN.md delete mode 100644 docs/wiki/AI-Form-Builder.md delete mode 100644 docs/wiki/Admin-Dashboard.md delete mode 100644 docs/wiki/Architecture-Overview.md delete mode 100644 docs/wiki/Block-Editor-Controls.md delete mode 100644 docs/wiki/Changelog.md delete mode 100644 docs/wiki/Contributing-Guide.md delete mode 100644 docs/wiki/Data-Export-Import.md delete mode 100644 docs/wiki/Database-Schema.md delete mode 100644 docs/wiki/Deployment-Guide.md delete mode 100644 docs/wiki/Email-Notifications.md delete mode 100644 docs/wiki/Environment-Configuration.md delete mode 100644 docs/wiki/Form-Fields-Architecture.md delete mode 100644 docs/wiki/Form-Submission-Flow.md delete mode 100644 docs/wiki/Frontend-Assets.md delete mode 100644 docs/wiki/Getting-Started.md delete mode 100644 docs/wiki/Gutenberg-Blocks.md delete mode 100644 docs/wiki/Home.md delete mode 100644 docs/wiki/Payment-Integration.md delete mode 100644 docs/wiki/REST-API-Reference.md delete mode 100644 docs/wiki/State-Management.md delete mode 100644 docs/wiki/Testing-Guide.md delete mode 100644 docs/wiki/Troubleshooting-FAQ.md delete mode 100644 docs/wiki/Using-WPML-With-SureForms.md delete mode 100644 docs/wiki/WordPress-Hooks-Reference.md delete mode 100644 docs/wiki/_Footer.md delete mode 100644 docs/wiki/_Sidebar.md delete mode 100644 inc/abilities/CLAUDE.md delete mode 100644 internal-docs/README.md delete mode 100644 internal-docs/ai-agent-guide.md delete mode 100644 internal-docs/apis.md delete mode 100644 internal-docs/architecture.md delete mode 100644 internal-docs/codebase-map.md delete mode 100644 internal-docs/coding-standards.md delete mode 100644 internal-docs/faq.md delete mode 100644 internal-docs/glossary.md delete mode 100644 internal-docs/maintenance.md delete mode 100644 internal-docs/onboarding.md delete mode 100644 internal-docs/product-vision.md delete mode 100644 internal-docs/troubleshooting.md delete mode 100644 internal-docs/ui-and-copy.md delete mode 100644 tests/play/specs/CLAUDE.md delete mode 100644 tests/play/specs/TODO.md diff --git a/.claude/agents/code-simplifier.md b/.claude/agents/code-simplifier.md deleted file mode 100644 index 2dfa2039d..000000000 --- a/.claude/agents/code-simplifier.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: code-simplifier -description: Reviews recent code changes and suggests simplifications — reduces complexity, removes dead code, flattens nesting, applies DRY. -tools: Read, Glob, Grep -model: sonnet ---- - -You are a code simplification expert for a WordPress/React plugin. - -## Your Task - -Review the recently changed files and suggest concrete simplifications. Do NOT make changes — only report findings. - -## What to Look For - -### PHP -- Duplicated logic that can be extracted into a shared method -- Deeply nested conditionals that can be flattened with early returns -- Dead code (unused variables, unreachable branches, commented-out code) -- Verbose array/string operations that WordPress has helpers for -- Overly complex conditionals that can be simplified - -### JavaScript/React -- Components doing too much — can they be split? -- Duplicated JSX or logic across components -- State that could be derived instead of stored -- useEffect with dependencies that could be simplified -- Unnecessary re-renders from inline objects/functions in props - -## Output Format - -For each finding: - -``` -### file-path:line-number -**Type:** DRY | Complexity | Dead Code | Optimization -**Current:** Brief description of current code -**Suggestion:** How to simplify (1-2 sentences) -**Impact:** Low | Medium | High -``` - -End with a summary: total findings by type and priority. - -## Rules -- Only report findings with clear, concrete improvements -- No subjective style preferences — only measurable simplifications -- Skip third-party code (`inc/lib/`, `node_modules/`, `vendor/`) -- Be precise with line numbers diff --git a/.claude/agents/srfm-block-checker.md b/.claude/agents/srfm-block-checker.md deleted file mode 100644 index ec7bbf8b9..000000000 --- a/.claude/agents/srfm-block-checker.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -name: srfm-block-checker -description: Audits SureForms block render files for security issues — checks output escaping, input sanitization, ABSPATH guards, and WPCS compliance. -tools: Read, Glob, Grep -model: sonnet ---- - -You are a WordPress security reviewer specialized in Gutenberg block rendering. - -## Your Task - -Audit SureForms block PHP files for common security and quality issues. - -## Files to Check - -1. All block render files: `inc/blocks/*/block.php` -2. All field markup files: `inc/fields/*.php` -3. The base block class: `inc/blocks/base.php` - -## Checklist for Each File - -For every file, check and report: - -### Security -- [ ] **ABSPATH guard** — File starts with `if ( ! defined( 'ABSPATH' ) ) { exit; }` -- [ ] **Output escaping** — All `echo` statements use `esc_html()`, `esc_attr()`, `wp_kses_post()`, or `wp_kses()`. Flag any raw `echo $var` without escaping (note: `// phpcs:ignore` comments may indicate intentional bypass — flag these for review) -- [ ] **Attribute sanitization** — Block attributes accessed from `$attributes` are sanitized before use -- [ ] **No direct `$_GET`/`$_POST`/`$_REQUEST`** — Block render should never read superglobals directly - -### Quality -- [ ] **`@since` tag present** — Class and methods have `@since` docblock tags -- [ ] **Namespace follows convention** — Uses `SRFM\Inc\Blocks\{BlockName}` pattern -- [ ] **Extends Base** — Block class extends `SRFM\Inc\Blocks\Base` -- [ ] **Return type** — `render()` method returns `string|bool` - -## Output Format - -For each file, output: - -``` -### block-name/block.php -- PASS: ABSPATH guard present -- WARN: Line 33 — raw echo with phpcs:ignore, verify markup() output is pre-escaped -- PASS: No superglobals used -- FAIL: Missing @since tag on render() method -``` - -End with a summary table: - -| Block | Security | Quality | Issues | -|-------|----------|---------|--------| -| input | WARN | FAIL | 2 | -| email | PASS | PASS | 0 | - -## Important - -- Be precise — include line numbers for every issue -- Distinguish FAIL (must fix) from WARN (review needed) -- Do NOT suggest code changes — only report findings -- Check ALL block files, not just a sample diff --git a/.claude/commands/sureforms:investigate.md b/.claude/commands/sureforms:investigate.md deleted file mode 100644 index d171756ec..000000000 --- a/.claude/commands/sureforms:investigate.md +++ /dev/null @@ -1,67 +0,0 @@ -# Investigate Issue - -Investigate a reported issue using parallel subagents for speed. - -## Input - -$ARGUMENTS — GitHub issue URL, Jira ticket ID, or a description of the issue. - -If no arguments provided, stop and ask: "Please provide an issue URL, Jira ticket ID, or describe the issue." - -## Step 1 — Read the Issue - -- If a GitHub URL: use `gh issue view` to read the issue -- If a Jira ticket: use the Jira MCP tools to read the ticket -- If a description: use the provided text directly - -Extract: title, description, steps to reproduce, expected vs actual behavior, screenshots, labels. - -## Step 2 — Classify - -Determine the type: -- **Bug** — something is broken, unexpected behavior, error, regression -- **Feature** — new functionality requested -- **Improvement** — enhancement to existing functionality - -Tell the user: "This looks like a **[type]**. Proceeding with [investigation/planning]." - -## Step 3 — Investigate with Subagents - -Use subagents to parallelize the investigation: - -### For Bugs: -Launch these subagents in parallel: -1. **Code Search Agent** — Search the codebase for files related to the bug (grep for keywords, function names, error messages, related hooks/filters) -2. **Test Coverage Agent** — Check if there are existing tests covering this area, identify gaps -3. **Impact Analysis Agent** — Trace all usages of the affected code to understand blast radius - -Once all agents report back, synthesize findings: -- Present the probable root cause with file path and line number -- Show which other areas might be affected -- Suggest a fix with a clear explanation -- Ask the user: "Should I apply this fix?" -- If yes — apply the fix, run linters, and suggest running relevant tests - -### For Features: -1. Enter plan mode -2. Launch subagents in parallel: - - **Pattern Discovery Agent** — Find similar existing features in the codebase to follow the same patterns - - **Impact Analysis Agent** — Identify all files that will need changes -3. Use findings to outline implementation steps -4. Present the plan for approval - -### For Improvements: -1. Launch subagents in parallel: - - **Code Search Agent** — Read the relevant existing code - - **Impact Analysis Agent** — Trace dependencies to understand what might break -2. Suggest the minimal changes needed -3. Ask the user: "Should I implement these changes?" -4. If yes — apply changes and run linters - -## Rules -- Always use subagents for investigation to maximize speed -- Show your reasoning — which files you checked and why -- Be precise with file paths and line numbers -- For bugs, prioritize finding the root cause over applying a quick patch -- Never make changes without asking first -- After any code change, run the verification checklist from CLAUDE.md diff --git a/.claude/commands/sureforms:sync-public.md b/.claude/commands/sureforms:sync-public.md deleted file mode 100644 index 08f2bba4c..000000000 --- a/.claude/commands/sureforms:sync-public.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -allowed-tools: Bash(git:*), Bash(gh:*), Bash(rm:*), Bash(rmdir:*), Bash(mktemp:*), Bash(echo:*), Bash(cd:*), Bash(test:*) -description: Sync private master to the sureforms-public mirror, stripping internal-only paths in one commit ---- - -# SureForms — Sync to Public Mirror - -Sync `brainstormforce/sureforms@master` (private, this repo) to `brainstormforce/sureforms-public@master` (public WordPress.org-facing mirror), stripping internal-only paths. The sync branch is bulk re-signed (verified signatures) and capped with a merge commit against the mirror so the public PR diff stays clean. - -This skill is the **only sanctioned way** to sync the public mirror. Running raw `git push mirror …` skips the strip and re-leaks internal artifacts. - -## Stripped paths (single source of truth) - -Edit this list when the leak surface changes — there is no other place to update. - -``` -.claude -.scripts/git-hooks -internal-docs -docs -CLAUDE.md # ALL CLAUDE.md at ANY depth (root + nested, e.g. inc/abilities/, tests/play/specs/) — stripped via `git ls-files '*CLAUDE.md'` (matches the root file too), NOT just the root path -ARCHITECTURE.md -COMPREHENSIVE_ANALYSIS.md -PRODUCT_ANALYSIS.md -TECHNICAL_OVERVIEW.md -tests/play/specs/TODO.md -.github/workflows/push-to-deploy.yml -.github/workflows/push-asset-readme-update.yml -.github/workflows/release-tag-draft.yml -.github/workflows/update-translations.yml -.github/workflows/release-pr-template.yml -bin/build-zip.sh -bin/checkout-and-build -bin/i18n.sh -``` - -These paths must NOT appear on the public mirror. They legitimately exist on private `master` and stay there (internal release CI, AI tooling, internal team wiki, internal dev docs under `docs/`). - -> **README.md is the ONLY markdown doc that should appear in the public PR diff.** After stripping, sanity-check with `git ls-files '*.md'` — anything beyond `README.md`, third-party `inc/lib/**` readmes, `modules/gutenberg/readme.md`, and `tests/play/README.md` is a likely leak. (A prior sync leaked `inc/abilities/CLAUDE.md`, `tests/play/specs/CLAUDE.md`, and the whole `docs/` tree because the strip matched only the root `CLAUDE.md`.) - -## Preconditions - -Working directory: any worktree of this repo. - -Required remotes: -- `origin` → `https://github.com/brainstormforce/sureforms` (private) -- public-mirror remote → `https://github.com/brainstormforce/sureforms-public` (public). May be named `mirror` **or** `public` — detect with `git remote -v` and use whichever points at `sureforms-public`. Examples below use `$MIRROR`. - -Verify with `git remote -v`. Abort if neither a `mirror` nor `public` remote points at `sureforms-public`. - -### ⚠️ `sureforms-public` ruleset constraints (READ FIRST) - -The public repo enforces a branch ruleset (since 2026-06-01) that the previous version of this skill violated. This flow is built around them: - -1. **Every commit needs a *verified* signature.** Signing is not enough — GitHub must mark it **Verified**, which requires BOTH the signing key registered on the pushing account as a **Signing Key** (an *Authentication* key does NOT count) AND the **committer email** verified on that account. **The skill uses the identity of whoever runs it** — your local `git config user.name` / `user.email` / `user.signingkey`. Before running, confirm: your `user.signingkey` points at an SSH public-key file (`*.pub`, since `gpg.format=ssh`) that is registered as a **Signing Key** on *your* GitHub account, and your `user.email` is a **verified** email on that same account. Hardware-backed keys (e.g. Secretive) verify fine but prompt for a tap **per commit**, so they cannot bulk-sign ~150 commits — use an on-disk passphrase-less key for the bulk re-sign. -2. **`sync/master` is force-push protected.** Push each sync to a **fresh** branch `sync/master-` (`-v2`, `-v3`… if re-pushing the same day — reusing a branch makes the ruleset re-evaluate the mirror's own unsigned ancestors and reject). -3. **Claude's auto-mode guardrail blocks the agent from pushing to the public mirror.** The agent prepares everything and hands the user the exact `git push` to run themselves. `gh pr` / `gh api` ARE agent-allowed. - -## Instructions - -Follow steps sequentially. If any step fails, jump to **Error Recovery**. - -### Step 1: Record current state - -```bash -ORIGINAL_BRANCH=$(git rev-parse --abbrev-ref HEAD) -HAD_STASH=false -if [ -n "$(git status --porcelain)" ]; then - git stash push -m "sureforms-sync-public auto-stash" - HAD_STASH=true -fi -``` - -### Step 2: Detect the mirror remote + signing identity, then fetch - -Detect the public-mirror remote (named `mirror` or `public`) once, and capture the running user's signing identity. These vars are used throughout: - -```bash -MIRROR=$(git remote -v | awk '/sureforms-public(\.git)?[[:space:]]/{print $1; exit}') -[ -n "$MIRROR" ] || { echo "No remote points at sureforms-public — add one and retry"; exit 1; } - -# Identity of whoever runs the skill (must satisfy the ruleset — see Preconditions) -SIGN_NAME=$(git config user.name) -SIGN_EMAIL=$(git config user.email) -SIGN_KEY=$(git config user.signingkey) # expected: path to an SSH *.pub registered as a Signing Key -[ -n "$SIGN_NAME" ] && [ -n "$SIGN_EMAIL" ] && [ -n "$SIGN_KEY" ] || { echo "git user.name/user.email/user.signingkey must all be set"; exit 1; } - -git fetch origin master -git fetch "$MIRROR" master sync/master 2>/dev/null || git fetch "$MIRROR" master -``` - -### Step 3: Detect no-op - -```bash -PRIVATE_TIP=$(git rev-parse origin/master) -PUBLIC_TIP=$(git rev-parse "$MIRROR/master") -``` - -If `PRIVATE_TIP` == `PUBLIC_TIP`, report "Public mirror is already up to date with private master — nothing to sync." Run **Step 8 cleanup** and exit. - -Capture the upstream commit range and count for reporting: - -```bash -COMMIT_COUNT=$(git rev-list --count "$PUBLIC_TIP..$PRIVATE_TIP") -``` - -### Step 4: Stage cleaned tree on a temp worktree - -Use a temp worktree so the developer's current working tree is never modified. - -```bash -WORKTREE=$(mktemp -d)/srfm-sync-public -git worktree add "$WORKTREE" "$PRIVATE_TIP" -cd "$WORKTREE" -``` - -### Step 5: Strip internal paths idempotently - -For each path in the **Stripped paths** list above, only act if it exists: - -```bash -for p in \ - .claude \ - .scripts/git-hooks \ - internal-docs \ - docs \ - ARCHITECTURE.md \ - COMPREHENSIVE_ANALYSIS.md \ - PRODUCT_ANALYSIS.md \ - TECHNICAL_OVERVIEW.md \ - tests/play/specs/TODO.md \ - .github/workflows/push-to-deploy.yml \ - .github/workflows/push-asset-readme-update.yml \ - .github/workflows/release-tag-draft.yml \ - .github/workflows/update-translations.yml \ - .github/workflows/release-pr-template.yml \ - bin/build-zip.sh \ - bin/checkout-and-build \ - bin/i18n.sh \ -; do - if [ -e "$p" ]; then - git rm -r --quiet "$p" - fi -done - -# Strip ALL CLAUDE.md at any depth (root + nested) — NOT just the root path. -# No pipe-to-while: a subshell would swallow a failing `git rm`. These paths never -# contain spaces, so word-splitting the list is safe. -CLAUDE_FILES=$(git ls-files '*CLAUDE.md') -if [ -n "$CLAUDE_FILES" ]; then - git rm --quiet $CLAUDE_FILES -fi - -# Remove now-empty .scripts directory if applicable -if [ -d .scripts ] && [ -z "$(ls -A .scripts 2>/dev/null)" ]; then - rmdir .scripts -fi - -# Sanity check (FAIL-CLOSED): README.md must be the ONLY non-third-party markdown -# doc left. This control already leaked once — a detected anomaly must HALT the -# sync, not just warn. The check runs in the temp worktree before any push, so -# aborting here means nothing internal reaches the mirror. -LEAKS=$(git ls-files '*.md' | grep -vE '^(README\.md|inc/lib/.*|modules/gutenberg/readme\.md|tests/play/README\.md|inc/abilities/ABILITIES\.md)$') || true -if [ -n "$LEAKS" ]; then - printf 'ABORT: unexpected markdown would be published:\n%s\n' "$LEAKS" - exit 1 -fi -echo "OK: only README.md + known readmes" -``` - -> If the sanity check aborts (`exit 1`), treat it as a failed step: jump to **Error Recovery** to tear down the temp worktree and restore the developer's branch/stash before investigating which path needs adding to the **Stripped paths** list. - -### Step 6: Commit the strip, then bulk re-sign ALL commits - -First commit the strip (committer is the running user — must be the verified email, see ruleset constraints): - -```bash -STRIPPED=false -if ! git diff --cached --quiet; then - STRIPPED=true - STRIPPED_PATHS=$(git diff --cached --name-only | tr '\n' ' ') - git commit -m "chore: strip internal-only paths from public mirror sync" -fi -``` - -Then re-sign **every** commit new to the mirror so they all carry verified signatures. Use `filter-branch` (it reuses each commit's tree + parents and only adds a signature → zero conflicts; `rebase --rebase-merges` re-merges and hits conflicts): - -> **Note:** this rewrites the **committer** of all re-signed commits to `$SIGN_EMAIL` (the original **author** of each commit is preserved). That committer/attribution change on the mirror is intentional — it's what lets the running user's registered key produce verified signatures. - -```bash -BASE=$(git merge-base "$MIRROR/master" "$PRIVATE_TIP") -FILTER_BRANCH_SQUELCH_WARNING=1 git \ - -c gpg.format=ssh -c user.signingkey="$SIGN_KEY" \ - filter-branch -f \ - --env-filter "export GIT_COMMITTER_NAME=\"$SIGN_NAME\"; export GIT_COMMITTER_EMAIL=\"$SIGN_EMAIL\"" \ - --commit-filter "git -c gpg.format=ssh -c user.signingkey=\"$SIGN_KEY\" commit-tree -S \"\$@\"" \ - -- "$BASE..HEAD" -``` - -### Step 7: Cap with a merge commit on the mirror, push to a fresh branch - -Cap the branch with a merge commit whose **first parent is `$MIRROR/master`**. This makes GitHub diff the PR against `$MIRROR/master` (which has no internal files), so the public PR shows **only real upstream changes — no internal-file deletions**. - -```bash -SIGNED_TIP=$(git rev-parse HEAD) -MERGE=$(git -c gpg.format=ssh -c user.signingkey="$SIGN_KEY" \ - -c user.name="$SIGN_NAME" -c user.email="$SIGN_EMAIL" \ - commit-tree -S "$(git rev-parse HEAD^{tree})" -p "$MIRROR/master" -p "$SIGNED_TIP" \ - -m "Sync master from upstream") -BRANCH="sync/master-$(date +%Y%m%d)" -git branch -f "$BRANCH" "$MERGE" -``` - -Then **the user runs the push** (the agent's push to the public mirror is blocked by Claude's guardrail). Push to a **fresh** branch — never `sync/master` (force-protected), and never reuse an existing branch (its prior value makes the ruleset re-evaluate the mirror's unsigned ancestors): - -```bash -git push "$MIRROR" "$BRANCH:refs/heads/$BRANCH" -``` - -If the push is rejected for "verified signatures", isolate before re-signing ~150 commits: push a single test commit signed the same way to a throwaway branch and confirm it's accepted (and `gh api .../commits/ -q .commit.verification.verified` is `true`). - -### Step 8: Open or update the sync PR - -Check whether an open PR already exists for the current sync branch → `master` on `brainstormforce/sureforms-public`: - -```bash -EXISTING_PR=$(gh pr list -R brainstormforce/sureforms-public \ - --base master --head "$BRANCH" --state open \ - --json number -q '.[0].number') -``` - -- **If `EXISTING_PR` is non-empty:** comment a refresh note: - - ```bash - gh pr comment "$EXISTING_PR" -R brainstormforce/sureforms-public \ - --body "Refreshed: synced \`$COMMIT_COUNT\` commit(s) from private master ($PUBLIC_TIP..$PRIVATE_TIP). Strip applied: $STRIPPED." - ``` - -- **If `EXISTING_PR` is empty:** open a new PR. - - Title: `Sync master from upstream` - - Body: upstream commit range, count, strip status, and a "## Highlights" section (`git log --oneline "$PUBLIC_TIP..$PRIVATE_TIP"`). Note that the diff is computed against `public/master` (merge-cap) so no internal files appear. - - ```bash - gh pr create -R brainstormforce/sureforms-public \ - --base master --head "$BRANCH" \ - --title "Sync master from upstream" \ - --body "$PR_BODY" - ``` - - Close/delete any superseded sync branch + PR: - - ```bash - gh pr close -R brainstormforce/sureforms-public - gh api -X DELETE repos/brainstormforce/sureforms-public/git/refs/heads/ - ``` - -### Step 9: Tear down the temp worktree - -```bash -cd - -git worktree remove --force "$WORKTREE" -``` - -### Step 10: Restore developer state - -```bash -git checkout "$ORIGINAL_BRANCH" -if [ "$HAD_STASH" = "true" ]; then - git stash pop -fi -``` - -## Output to user - -Report at the end: - -- Upstream range: `..` (`$COMMIT_COUNT` commits) -- Whether the strip commit was created (`$STRIPPED`); if true, list the stripped paths -- PR URL — new or existing - -## Error Recovery - -If any step fails: - -1. **`cd` back to the original repo dir** if currently inside `$WORKTREE`. -2. **Remove temp worktree** if it was created: `git worktree remove --force "$WORKTREE"` (ignore errors). -3. **Restore branch and stash** as in Step 10. -4. Surface the failing command and its error to the user — do not retry blindly. - -Common failure modes: - -- **Push rejected, "Commits must have verified signatures"** — a commit is signed by an unregistered key or committed under an unverified email. Confirm your `git config user.signingkey` (the `*.pub`) is registered as a **Signing Key** (not Authentication) on your GitHub account and your `git config user.email` is a verified email there. Test with a single throwaway commit before re-signing ~150. -- **Push rejected, "Cannot force-push to this branch"** — you targeted `sync/master`; use a fresh `sync/master-` branch instead. -- **Push rejected naming a few commits already on `master`** — you reused/updated an existing branch; push to a brand-new branch name so the ruleset only evaluates the new commits. -- **Agent push "denied by auto mode classifier"** — expected; the user must run the `git push` to the mirror themselves. -- **`gh pr create` fails with "PR already exists"** — Step 8 detection missed a draft PR. Re-run Step 8 with `--state all` and reuse / reopen the existing PR. -- **No mirror remote** — add it and re-run: `git remote add public https://github.com/brainstormforce/sureforms-public`. diff --git a/.claude/commands/sureforms:update-readme.md b/.claude/commands/sureforms:update-readme.md deleted file mode 100644 index b3d9d8a6f..000000000 --- a/.claude/commands/sureforms:update-readme.md +++ /dev/null @@ -1,327 +0,0 @@ ---- -description: Audit and update the WordPress.org readme.txt — analyze new features, research competitors, draft changes, and create a PR ---- - -# SureForms readme.txt Update - -Comprehensive audit and update of the `readme.txt` (and `README.md`) for the WordPress.org listing. - -## Arguments - -Parse from: $ARGUMENTS - -Expected format: `[version] [--full]` - -- `version` — optional. Baseline version to analyze changes since (e.g., `2.5.0`). If not provided, auto-detect from the most recent `= X.X.X` changelog header in `readme.txt`. -- `--full` — optional flag. Perform a full rewrite of all sections instead of an incremental update. - -If no arguments are given, auto-detect the baseline and proceed with incremental mode. - -## Working Directory - -All commands and file paths are relative to the sureforms plugin root — the directory containing `sureforms.php`. - ---- - -## Phase 1: Analyze What's New - -### Step 1.1 — Determine Baseline Version - -- If `version` argument was provided, use that as the baseline. -- Otherwise, read the `== Changelog ==` section of `readme.txt`, extract the most recent version header (`= X.X.X - ... =`), and use that version as the baseline. -- Resolve the git tag: `v` (e.g., `v2.5.0`). Verify it exists with `git tag -l "v"`. -- If the tag does not exist, list available tags (`git tag --sort=-version:refname | head -10`) and ask the user to pick one. - -### Step 1.2 — Gather Changes Since Baseline - -Launch **3 Explore subagents in parallel** (single message, 3 Agent tool calls): - -**Agent 1 — Git History Analysis:** -- Run `git log v..HEAD --oneline` to get all commits since the baseline. -- Categorize commits: new features, improvements, bug fixes, refactors. -- Focus on commits touching: `inc/blocks/`, `inc/fields/`, `inc/payments/`, `inc/ai-form-builder/`, `modules/`, `src/blocks/`, `inc/page-builders/`. -- Report: list of significant user-facing changes with commit hashes and affected areas. - -**Agent 2 — Free Codebase Feature Scan:** -- List all blocks in `inc/blocks/` — compare against the readme's "Input Fields" and feature bullets. -- Check `inc/payments/` for payment providers. -- Check `inc/ai-form-builder/` for AI feature updates. -- Check `modules/` for any new modules. -- Check `inc/global-settings/` for new settings sections. -- Check `inc/rest-api.php` for new REST endpoints. -- Check `inc/page-builders/` for page builder compatibility additions. -- Report: features present in code but missing from readme. - -**Agent 3 — Pro Codebase Feature Scan:** -- Check `../sureforms-pro/inc/pro/native-integrations/integrations/` — list ALL integration JSON files and compare against the readme's integration list. -- Check `../sureforms-pro/inc/blocks/` for pro-only blocks. -- Check `../sureforms-pro/inc/pro/` subdirectories for pro features (conversational-form, save-resume, signature, zapier, etc.). -- Check `../sureforms-pro/inc/extensions/` for conditional logic, field-restrictions, etc. -- If `../sureforms-pro/` does not exist, skip this agent and warn: "Pro plugin not found. Only free features will be analyzed." -- Report: pro features/integrations present in code but missing from readme. - -### Step 1.3 — Present Gap Analysis - -Merge all agent results and present a table: - -``` -┌─────────────────────────────────────┬────────┬──────────────────────────────┐ -│ Area │ Status │ Details │ -├─────────────────────────────────────┼────────┼──────────────────────────────┤ -│ Free field types match readme │ ✅/❌ │ Code: N, Readme: M │ -│ Pro blocks mentioned │ ✅/❌ │ Missing: ... │ -│ Integration list up to date │ ✅/❌ │ Missing: ... │ -│ Pro features all listed │ ✅/❌ │ Missing: ... │ -│ Payment providers accurate │ ✅/❌ │ │ -│ Page builder compat listed │ ✅/❌ │ │ -│ "Tested up to" WP version │ ✅/❌ │ Readme: X.X, Current: Y.Y │ -│ New features since v │ ℹ️ │ N features found │ -└─────────────────────────────────────┴────────┴──────────────────────────────┘ -``` - -Tell the user: "Phase 1 complete. Found **N gaps** and **M new features** to incorporate. Proceeding to competitor research." - ---- - -## Phase 2: Competitor & Market Research - -### Step 2.1 — Research Competitor READMEs - -Use **WebSearch** to run these searches in parallel: - -1. `"WPForms" site:wordpress.org/plugins description features` -2. `"Gravity Forms" site:wordpress.org/plugins description features` -3. `"Fluent Forms" site:wordpress.org/plugins fluentform features` -4. `"Formidable Forms" site:wordpress.org/plugins description features` -5. `"Contact Form 7" site:wordpress.org/plugins description` -6. `"WordPress.org readme.txt best practices 2025 2026"` - -For each competitor, use **WebFetch** on the top result URL if needed to extract: -- How they structure feature lists -- Marketing language and benefit-focused copy -- How they distinguish free vs pro features -- FAQ structure and common questions -- Trust signals (install counts, star ratings, testimonials) - -### Step 2.2 — Present Research Summary - -Show a comparison table: - -``` -Feature Highlighted by Competitors │ SureForms Has It? │ In Readme? -──────────────────────────────────────┼───────────────────┼────────── -Conditional Logic │ ✅ (Pro) │ ✅/❌ -File Upload │ ✅ (Pro) │ ✅/❌ -Drag & Drop Builder │ ✅ (Free) │ ✅/❌ -... │ │ -``` - -Also note: -- SEO-relevant keywords competitors use that SureForms is missing. -- Structural patterns that work well (comparison tables, benefit-focused headers). -- Any WordPress.org formatting trends. - -Tell the user: "Phase 2 complete. Ready to draft the update. Shall I proceed?" - -**⏸️ WAIT for user confirmation before proceeding to Phase 3.** - ---- - -## Phase 3: Draft the Update - -### Step 3.1 — Determine Scope - -- If `--full` flag was passed: draft ALL sections from scratch. -- Otherwise: draft only sections that need changes based on Phase 1 and Phase 2 findings. - -### Step 3.2 — Draft Each Section - -For each section that needs updating, present in this format: - -``` -━━━ SECTION: [Section Name] ━━━ - -── CURRENT ── -[Current text from readme.txt, abbreviated if very long] - -── PROPOSED ── -[New text] - -── CHANGES ── -• Added: [what was added] -• Removed: [what was removed, if any] -• Reworded: [what was reworded and why] -``` - -Sections to evaluate (in order): - -1. **Plugin header** — Stable tag, Tested up to, Tags (optimize for WordPress.org search) -2. **Short description** — Must be under 150 characters. Make it compelling and keyword-rich. -3. **Long description** — Feature bullets, AI section, payment section, field types list -4. **Premium features section** — Integration list, pro-only features, pro field types -5. **FAQ** — Add questions for new features, update outdated answers -6. **3rd Party Services** — Add any new external services used -7. **Compatibility lists** — Themes and plugins - -### Step 3.3 — WordPress.org Compliance Check - -Verify the draft against WordPress.org readme standards: -- ✅ Short description is under 150 characters -- ✅ No disallowed HTML in description -- ✅ `== Changelog ==` section exists and is properly formatted -- ✅ `== Frequently Asked Questions ==` uses correct `= Question =` format -- ✅ No broken markdown that WordPress.org would render incorrectly -- ✅ Stable tag matches the version in the main plugin file - -### Step 3.4 — Present Full Draft - -Present all drafted sections together with a summary: - -``` -📋 Readme Update Summary -───────────────────────────────────── -Sections modified: N -Lines added: +X -Lines removed: -Y -New features listed: Z -New integrations: W -New FAQ entries: Q -WP.org compliance: ✅ All checks pass -``` - -Tell the user: "Please review the draft above. You can: -1. **Approve** — proceed to apply changes and create PR -2. **Request edits** — tell me what to change -3. **Abort** — cancel the update" - -**⏸️ WAIT for explicit user approval. Do NOT proceed without it.** - ---- - -## Phase 4: User Review & Iteration - -This phase is a loop: - -1. If user **requests edits** → apply them to the draft and re-present the affected sections. Ask again for approval. -2. If user **approves** → proceed to Phase 5. -3. If user **aborts** → stop and report "Readme update cancelled. No changes were made." - ---- - -## Phase 5: Apply & Create PR - -### Step 5.1 — Create Branch - -```bash -git fetch origin -git checkout dev -git pull origin dev -git checkout -b chore/update-readme -``` - -If `chore/update-readme` already exists, append a date suffix: `chore/update-readme-YYYYMMDD`. - -### Step 5.2 — Apply Changes - -Edit `readme.txt` with the approved draft changes. Apply section by section — do NOT rewrite sections that weren't modified. - -### Step 5.3 — Regenerate README.md - -```bash -npx grunt readme -``` - -Verify `README.md` was updated. If `grunt readme` fails, check that `npm install` has been run and `grunt-wp-readme-to-markdown` is in `devDependencies`. - -### Step 5.4 — Verify - -Read the updated `readme.txt` and confirm: -- Stable tag matches current plugin version -- Tested up to is correct -- Short description is under 150 characters -- All sections are properly formatted -- No stray draft markers or debug text - -### Step 5.5 — Commit and Push - -```bash -git add readme.txt README.md -git commit -m "chore: update readme.txt for WordPress.org listing - -Update plugin description, feature lists, integrations, FAQ, -and compatibility information to reflect current capabilities. - -Co-Authored-By: Claude Opus 4.6 (1M context) " -git push -u origin chore/update-readme -``` - -### Step 5.6 — Create PR - -```bash -gh pr create \ - --title "chore: Update readme.txt for WordPress.org" \ - --base dev \ - --body "$(cat <<'EOF' -## Summary -- Updated readme.txt to reflect current plugin capabilities -- Regenerated README.md via `grunt readme` - -## What Changed -[List each section that was modified and what changed] - -## Research Notes -- Competitor READMEs reviewed: WPForms, Gravity Forms, Fluent Forms, Formidable, CF7 -- Gaps identified and addressed: [count] - -## Test Plan -- [ ] Preview on WordPress.org readme validator -- [ ] Verify all links in the readme are working -- [ ] Confirm feature claims match actual plugin capabilities -- [ ] Verify short description is under 150 characters - -🤖 Generated with [Claude Code](https://claude.com/claude-code) -EOF -)" -``` - -### Step 5.7 — Final Report - -``` -✅ Readme Update Complete -───────────────────────────────────────── -✅ Phase 1: Gap analysis complete -✅ Phase 2: Competitor research complete -✅ Phase 3: Draft created -✅ Phase 4: User review passed -✅ Phase 5: Changes applied -✅ Branch: chore/update-readme (from dev) -✅ readme.txt updated -✅ README.md regenerated -✅ PR: - -Sections updated: - • [List each modified section] -``` - ---- - -## Rules - -- **NEVER modify readme.txt without user approval.** Always present the full draft first. -- **NEVER invent features.** Only document what actually exists in the codebase. -- **NEVER modify changelog entries.** The changelog is managed by `/sureforms:version-bump`. -- **ALWAYS branch from `dev`**, never from `master` or `next-release`. -- **ALWAYS run `npx grunt readme`** after modifying `readme.txt` to regenerate `README.md`. -- **ALWAYS verify feature claims** by checking the actual codebase, not just commit messages. -- **Keep the 150-character limit** for the WordPress.org short description. -- **Maintain the existing voice and tone** — professional, benefit-focused, not overly salesy. - -## Error Recovery - -| Symptom | Fix | -|---------|-----| -| `grunt readme` fails | Run `npm install` first; verify `grunt-wp-readme-to-markdown` is in `devDependencies` | -| Branch already exists | Append date suffix: `chore/update-readme-YYYYMMDD` | -| Pro plugin not found at `../sureforms-pro/` | Skip pro analysis, warn user, document only free features | -| Git tag not found | List available tags and ask user to pick one | -| WebSearch unavailable | Skip Phase 2, proceed with codebase analysis only, note the limitation | diff --git a/.claude/commands/sureforms:version-bump.md b/.claude/commands/sureforms:version-bump.md deleted file mode 100644 index 5f48a2e1f..000000000 --- a/.claude/commands/sureforms:version-bump.md +++ /dev/null @@ -1,204 +0,0 @@ -# SureForms Release Version Bump - -Automates the SureForms version bump release checklist end-to-end. - -## Arguments - -Parse from: $ARGUMENTS - -Expected format: ` [pro-version] [branch]` - -- `version` — **required**. Version number to release (e.g., `2.5.2`). If not provided, stop immediately and tell the user: `"Version number is required. Usage: /sureforms:version-bump [pro-version] [branch]"` -- `pro-version` — optional. Version to set for `SRFM_PRO_RECOMMENDED_VER`. If not provided, ask: `"What should SRFM_PRO_RECOMMENDED_VER be set to? (default: )"` and default to the core version if the user presses Enter or skips. -- `branch` — optional. Source branch to cut from — must be `dev` or `next-release`. Default: `next-release`. - -## Working Directory - -All commands and file paths are relative to the sureforms plugin root. Detect it as the directory containing `sureforms.php` — typically the current working directory or a `sureforms/` subdirectory of it. - ---- - -## Steps - -### Step 1 — Validate Arguments - -- Extract `version`, `pro-version`, and `branch` from `$ARGUMENTS`. -- Abort with usage message if `version` is missing. -- If `branch` is provided but not `dev` or `next-release`, abort: `"Invalid branch. Must be 'dev' or 'next-release'."` -- If `pro-version` was not passed as an argument, prompt the user for it (default: same as `version`). - ---- - -### Step 2 — Create Version Bump Branch - -Run from the sureforms plugin root: - -```bash -git fetch origin -git checkout -git pull origin -git checkout -b version-bump- -``` - -Confirm the new branch was created successfully before continuing. - ---- - -### Step 3 — Run Grunt Version Bump - -```bash -npx grunt version-bump --ver= -``` - -This runs `bumpup` (updates `package.json`) and `replace` (updates version strings in `readme.txt` and `sureforms.php`). - ---- - -### Step 4 — Verify package.json and package-lock.json - -- Read `package.json` → confirm `"version"` equals ``. -- Read `package-lock.json` → confirm the top-level `"version"` and `packages[""].version` both equal ``. -- If `package-lock.json` is out of sync, patch **only** the two version string values — do not alter any whitespace, indentation, line endings, or surrounding characters: - - Top-level: replace only the value in `"version": ""` → `"version": ""` - - Inside `"packages": { "": { ... } }`: replace only the value in `"version": ""` → `"version": ""` - - The resulting diff must show **exactly 2 lines changed** — only the version string on each line. No other bytes in the file should change. - - Do **not** run `npm install` — dependency conflicts can cause it to fail and it is not needed for this change. - ---- - -### Step 5 — Verify readme.txt — Stable tag - -- Read `readme.txt` and confirm `Stable tag: `. -- If incorrect, edit the line to the correct value. - ---- - -### Step 6 — Verify readme.txt — Tested up to - -- Fetch the latest WordPress version by running: - ```bash - curl -s "https://api.wordpress.org/core/version-check/1.7/" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4 - ``` -- Store the result as ``. -- Read `readme.txt` and check the current `Tested up to:` value. -- If it does **not** match ``, update the line to `Tested up to: `. -- Confirm the final value in the file after any edit. - ---- - -### Step 7 — Verify sureforms.php — Plugin Header Version - -- Read `sureforms.php` lines 1–15. -- Confirm `Version: ` in the plugin header comment. -- If incorrect, edit to the correct value. - ---- - -### Step 8 — Verify SRFM_VER - -- Confirm `define( 'SRFM_VER', '' );` in `sureforms.php`. -- If incorrect, edit to the correct value. - ---- - -### Step 9 — Verify SRFM_PRO_RECOMMENDED_VER - -- Confirm `define( 'SRFM_PRO_RECOMMENDED_VER', '' );` in `sureforms.php`. -- If incorrect, edit to the correct value. - ---- - -### Step 10 — Verify Changelog Entry in readme.txt - -- Read the `== Changelog ==` section of `readme.txt`. -- **Check for a placeholder entry** matching `= x.x.x =` (case-insensitive): - - If **found**: ask the user: `"What is the release date for ? (e.g. 19th February 2026)"`. Replace the placeholder header with `= - =`, matching the exact formatting of existing changelog entries (spaces around the `=`, date style matching what is already present). -- **Check for an entry** matching `= -`: - - If **missing** after the above: warn the user and **pause** — they must add a changelog entry for `` before the process can continue. The expected format is: - ``` - = - DD Month YYYY = - * New: ... - * Improvement: ... - * Fix: ... - ``` - - If **found**: confirm the date is present and the format matches SureForms standards. -- **Polish each bullet line** of the `` entry: - - Fix any grammatical or spelling errors. - - Rewrite passive or vague phrasing into clear, active, benefit-led language (e.g. "Fixed bug where X" → "Fix: Resolved an issue where X to ensure Y"). - - Keep the improvements **subtle and factual** — do not invent features or exaggerate. The prefix (`New:`, `Improvement:`, `Fix:`) must stay unchanged. - - Show the user a before/after diff of any lines you changed and ask for confirmation before writing. -- Then sort the bullet lines of the `` entry so they follow this group order: - 1. `* New:` lines - 2. `* Improvement:` lines - 3. `* Fix:` lines - 4. Any other prefixes come last -- Within each group, sort lines **alphabetically** (case-insensitive) by the text that follows the prefix. -- Write the re-ordered lines back to `readme.txt` if any reordering was needed, and report whether changes were made. - ---- - -### Step 11 — Trim Changelog to 3 Entries - -- Parse the `== Changelog ==` section of `readme.txt` to identify all version entries (lines matching `= X.X.X - ...=`). -- Keep only the **3 most recent** entries (the new `` entry plus the 2 entries immediately before it), including all their bullet lines. -- Remove all older entries that follow the 3rd entry, up to but not including the next `==` section header. -- Write the trimmed content back to `readme.txt`. -- Report how many entries were removed (e.g., "Removed 4 old changelog entries"). - ---- - -### Step 12 — Generate README.md - -```bash -npx grunt readme -``` - ---- - -### Step 13 — Generate POT File - -```bash -npm run makepot -``` - ---- - -### Step 14 — Commit and Open PR - -```bash -git add -A -git commit -m "Version Bump " -git push origin version-bump- -gh pr create \ - --repo brainstormforce/sureforms \ - --title "Version Bump " \ - --base \ - --label "Release PR,skip-title-check" -``` - ---- - -### Step 15 — Final Report - -Print a status summary for every step, then the PR URL: - -``` -Release Bump: -───────────────────────────────────────── -✅ Branch created: version-bump- (from ) -✅ grunt version-bump: completed -✅ package.json: -✅ package-lock.json: -✅ readme.txt Stable tag: -✅ readme.txt Tested up to: -✅ sureforms.php Version: -✅ SRFM_VER: -✅ SRFM_PRO_RECOMMENDED_VER: -✅ Changelog entry polished, confirmed, and sorted (New → Improvement → Fix, alphabetical within groups) -✅ Changelog trimmed to 3 entries (N removed) -✅ README.md generated -✅ POT file generated -✅ PR opened: -``` - -If any step failed (❌) or needs attention (⚠️), list the required actions at the bottom. diff --git a/.claude/commands/sync-release-branches.md b/.claude/commands/sync-release-branches.md deleted file mode 100644 index 210f07139..000000000 --- a/.claude/commands/sync-release-branches.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -allowed-tools: Bash(git:*) -auto-approve: true -description: Sync master into dev and dev into next-release after a release ---- - -# Sync Release Branches - -After a release, sync the three main branches: master → dev → next-release. - -## Instructions - -Follow these steps sequentially. If any step fails, jump to the Error Recovery section. - -### Step 1: Record current state - -1. Record the current branch: -```bash -git rev-parse --abbrev-ref HEAD -``` -Store this as ORIGINAL_BRANCH. - -2. Check for uncommitted changes: -```bash -git status --porcelain -``` -If there is ANY output, stash the changes: -```bash -git stash -``` -Store HAD_STASH=true. Otherwise HAD_STASH=false. - -### Step 2: Fetch latest from remote - -```bash -git fetch origin -``` - -### Step 3: Update master - -```bash -git checkout master -git pull origin master -``` - -### Step 4: Merge master into dev - -```bash -git checkout dev -git pull origin dev -git pull origin master -``` - -If there are merge conflicts, STOP. Tell the user: "Merge conflicts while merging master into dev. Please resolve them manually." List the conflicting files. - -If merge succeeds: -```bash -git push origin dev -``` - -### Step 5: Merge dev into next-release - -```bash -git checkout next-release -git pull origin next-release -git pull origin dev -``` - -If there are merge conflicts, STOP. Tell the user: "Merge conflicts while merging dev into next-release. Please resolve them manually." List the conflicting files. - -If merge succeeds: -```bash -git push origin next-release -``` - -### Step 6: Return to original branch - -```bash -git checkout ${ORIGINAL_BRANCH} -``` - -If HAD_STASH=true: -```bash -git stash pop -``` - -Report success: "Branch sync complete. master → dev → next-release are now in sync." - -## Error Recovery - -If any step fails after switching branches: -1. Switch back to original branch: `git checkout ${ORIGINAL_BRANCH}` -2. Restore stash if needed: `git stash pop` (only if HAD_STASH=true) -3. Report the error to the user. - -NEVER force-push. If a merge has conflicts, stop and let the user resolve them. diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index 02b620f27..000000000 --- a/.claude/settings.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "hooks": { - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "if echo \"$CLAUDE_BASH_COMMAND\" | grep -q '^git commit'; then echo '--- PreCommit: Running checks ---' && composer lint 2>&1 | tail -5 && composer phpstan 2>&1 | tail -5 && composer insights 2>&1 | tail -5; fi" - } - ] - } - ], - "PostToolUse": [ - { - "matcher": "Write|Edit", - "hooks": [ - { - "type": "command", - "command": "case \"$CLAUDE_FILE_PATH\" in *.php) vendor/bin/phpcbf \"$CLAUDE_FILE_PATH\" 2>/dev/null || true;; *.js|*.jsx) npx wp-scripts lint-js --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null || true;; *.scss|*.css) npx wp-scripts lint-style --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null || true;; esac" - } - ] - } - ] - }, - "permissions": { - "allow": [ - "Bash(npm run lint-js*)", - "Bash(npm run lint-css*)", - "Bash(npm run pretty*)", - "Bash(npm run build*)", - "Bash(npm run start)", - "Bash(npm run test:unit)", - "Bash(npm run test:e2e)", - "Bash(npm run play:*)", - "Bash(npm run makepot)", - "Bash(npm run i18n:*)", - "Bash(composer lint*)", - "Bash(composer format*)", - "Bash(composer test*)", - "Bash(composer phpstan*)", - "Bash(composer insights*)", - "Bash(npx playwright test*)", - "Bash(npx grunt *)", - "Bash(composer install*)", - "Bash(npm install*)", - "Bash(curl *)", - "Bash(git status*)", - "Bash(git diff*)", - "Bash(git log*)", - "Bash(git branch*)", - "Bash(git show*)", - "Bash(git stash*)", - "Bash(git checkout*)", - "Bash(git add*)", - "Bash(git commit*)", - "Bash(git merge*)", - "Bash(git rebase*)", - "Bash(git fetch*)", - "Bash(git pull*)", - "Bash(gh pr *)", - "Bash(gh issue *)", - "Bash(gh api *)", - "Bash(ls *)", - "Bash(wc *)", - "Bash(which *)", - "Bash(node -e *)", - "Bash(php -r *)", - "Bash(wp *)" - ], - "deny": [] - } -} diff --git a/.github/workflows/push-asset-readme-update.yml b/.github/workflows/push-asset-readme-update.yml deleted file mode 100644 index fed4455c2..000000000 --- a/.github/workflows/push-asset-readme-update.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Plugin asset/readme update -on: - push: - branches: - - master - paths: - - 'readme.txt' - - '.wordpress-org/**' -jobs: - master: - name: Push to main - runs-on: ubuntu-22.04 - environment: production - steps: - - uses: actions/checkout@master - - name: Build - run: | - npm ci - npm run build - - name: WordPress.org plugin asset/readme update - uses: 10up/action-wordpress-plugin-asset-update@stable - env: - SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} - SVN_USERNAME: ${{ secrets.SVN_USERNAME }} diff --git a/.github/workflows/push-to-deploy.yml b/.github/workflows/push-to-deploy.yml deleted file mode 100644 index 8e518fe41..000000000 --- a/.github/workflows/push-to-deploy.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Deploy to WordPress.org -on: - release: - types: [published] -jobs: - tag: - name: New tag - runs-on: ubuntu-22.04 - environment: production - steps: - - uses: actions/checkout@master - - name: Verify release tag is reachable from master - run: | - git fetch --no-tags --depth=50 origin master:refs/remotes/origin/master - if ! git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/master; then - echo "::error::Tag $GITHUB_REF_NAME ($GITHUB_SHA) is not reachable from master. Refusing to deploy." - exit 1 - fi - - name: Build - run: | - npm ci - npm run build - - name: WordPress Plugin Deploy - uses: 10up/action-wordpress-plugin-deploy@master - env: - SVN_USERNAME: ${{ secrets.SVN_USERNAME }} - SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} diff --git a/.github/workflows/release-pr-template.yml b/.github/workflows/release-pr-template.yml deleted file mode 100644 index b296fcd31..000000000 --- a/.github/workflows/release-pr-template.yml +++ /dev/null @@ -1,41 +0,0 @@ - -name: Update checklist for Release PR - -on: - pull_request: - types: [ labeled ] - -jobs: - update_pr: - name: Update checklist for Release PR - if: ${{ github.event.label.name == 'Release PR' }} - runs-on: ubuntu-latest - steps: - - name: Check Branch - id: check-branch - run: | - if [[ ${{ github.base_ref }} == master || ${{ github.base_ref }} == dev || ${{ github.base_ref }} == next-release ]]; then - echo ::set-output name=match::true - fi - - uses: tzkhan/pr-update-action@v2 - if: steps.check-branch.outputs.match == 'true' - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" - base-branch-regex: '[A-Za-z\d-_.\\/]+' - head-branch-regex: '[A-Za-z\d-_.\\/]+' - body-template: | - --- - ### Release Checklist: - - [ ] Run to update version number : `grunt version-bump --ver=` - - [ ] Verify the version number in `package.json` and `package-lock.json` - - [ ] Verify `Stable tag` is `` in readme.txt - - [ ] Verify `Tested upto` is set to latest tested version of WordPress - - [ ] Update version in `sureforms.php` in plugin description - - [ ] Verify constant `SRFM_VER` in `sureforms.php` with latest version of SureForms - - [ ] Verify constant `SRFM_PRO_RECOMMENDED_VER` in `sureforms.php` with compatible version of SureForms Pro - - [ ] Verify changelog `date` and `content` as per SureForms standards - - [ ] Generate README.md : `grunt readme` - - [ ] Generate POT file : `npm run makepot` - --- - body-update-action: 'suffix' - body-uppercase-base-match: false diff --git a/.github/workflows/release-tag-draft.yml b/.github/workflows/release-tag-draft.yml deleted file mode 100644 index 55a6449bf..000000000 --- a/.github/workflows/release-tag-draft.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Create a release tag - -on: - push: - branches: - - master - paths: - # Release happens only if the version number in main file is updated. - - 'sureforms.php' - -jobs: - build-artifact: - name: Build Release Artifact - runs-on: ubuntu-22.04 - outputs: - current_version: ${{ steps.get_version.outputs.current_version }} - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - ref: master - - - name: Initialize mandatory git config - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Compute current version - id: get_version - run: | - CURRENT_VERSION=$(jq --raw-output '.version' package.json) - echo "current_version=${CURRENT_VERSION}" >> $GITHUB_OUTPUT - - - name: Install Node.js and npm - uses: actions/setup-node@v6 - with: - node-version: '24.16.0' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Build plugin ZIP file - run: bash ./bin/build-zip.sh - - - name: Upload artifact - uses: actions/upload-artifact@v7 - with: - name: sureforms-plugin - path: ./artifact/sureforms.zip - - create-release: - name: Create Release Draft and Attach Asset - needs: [build-artifact] - runs-on: ubuntu-latest - steps: - - name: Set Release Version - id: get_release_version - env: - VERSION: ${{ needs.build-artifact.outputs.current_version }} - run: echo "version=$(echo $VERSION | cut -d / -f 3 | sed 's/-rc./ RC/' )" >> $GITHUB_OUTPUT - - - name: Download Plugin Zip Artifact - uses: actions/download-artifact@v8 - with: - name: sureforms-plugin - - - name: Create Release Draft - id: create_release - uses: softprops/action-gh-release@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: Version ${{ steps.get_release_version.outputs.version }} - tag_name: "v${{ steps.get_release_version.outputs.version }}" - target_commitish: master - draft: true - prerelease: false - body: 'Replace changelog here' - files: ./sureforms.zip diff --git a/.github/workflows/update-translations.yml b/.github/workflows/update-translations.yml deleted file mode 100644 index 38ca26957..000000000 --- a/.github/workflows/update-translations.yml +++ /dev/null @@ -1,205 +0,0 @@ -name: Update Translations - -on: - issue_comment: - types: [created] - -# Only allow one i18n job per PR at a time. -concurrency: - group: i18n-${{ github.event.issue.number }} - cancel-in-progress: true - -permissions: - contents: write - pull-requests: write - issues: write - -jobs: - i18n: - # Only trusted users may trigger a run — this job executes branch build - # scripts with secrets, so gate on the commenter's association. - if: | - github.event.comment.body == '/i18n' && - github.event.issue.pull_request != null && - contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) - runs-on: ubuntu-latest - - steps: - # Acknowledge the command with a reaction - - name: Add reaction to comment - uses: peter-evans/create-or-update-comment@v5 - with: - comment-id: ${{ github.event.comment.id }} - reactions: rocket - - # Get PR metadata (head branch, fork detection) - - name: Get PR details - id: pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - PR_JSON=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}) - HEAD_REF=$(echo "$PR_JSON" | jq -r '.head.ref') - HEAD_REPO=$(echo "$PR_JSON" | jq -r '.head.repo.full_name') - IS_FORK=$(echo "$PR_JSON" | jq -r '.head.repo.full_name != .base.repo.full_name') - - echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT" - echo "head_repo=$HEAD_REPO" >> "$GITHUB_OUTPUT" - echo "is_fork=$IS_FORK" >> "$GITHUB_OUTPUT" - - # Block fork PRs — secrets are not available - - name: Block fork PRs - if: steps.pr.outputs.is_fork == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr comment "${{ github.event.issue.number }}" \ - --repo "${{ github.repository }}" \ - --body "**i18n update skipped**: This PR is from a fork. Translation updates require repository secrets and cannot run on fork PRs." - exit 1 - - # Notify that the workflow has started - - name: Comment starting - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr comment "${{ github.event.issue.number }}" \ - --repo "${{ github.repository }}" \ - --body "**Translations update started** :hourglass_flowing_sand: - Building and generating translations for 7 languages (nl, fr, de, es, it, pt, pl)... - [View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" - - # Checkout the actual PR branch (not default branch) - - name: Checkout PR branch - uses: actions/checkout@v6 - with: - ref: ${{ steps.pr.outputs.head_ref }} - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '24.16.0' - cache: 'npm' - - # Pin WP-CLI to a fixed release. `wp i18n make-json` stamps - # "generator":"WP-CLI/" into every languages/*.json, so - # installing "latest" rewrites all JSON files whenever WP-CLI - # ships a new version. Bump this pin deliberately. - - name: Install WP-CLI - run: | - curl -sSfL -o wp-cli.phar https://github.com/wp-cli/wp-cli/releases/download/v2.12.0/wp-cli-2.12.0.phar - chmod +x wp-cli.phar - sudo mv wp-cli.phar /usr/local/bin/wp - wp --info - - - name: Install dependencies - run: npm ci - - # Build first so makepot picks up all the latest strings - - name: Build - run: npm run build - - # Run the full i18n pipeline (makepot -> po -> gpt-translate -> mo -> json) - - name: Run i18n script - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - run: | - set -euo pipefail - bash bin/i18n.sh - - # Check if any files changed - - name: Check for changes - id: changes - run: | - if git diff --quiet; then - echo "changed=false" >> "$GITHUB_OUTPUT" - else - echo "changed=true" >> "$GITHUB_OUTPUT" - CHANGED_FILES=$(git diff --name-only | wc -l | tr -d ' ') - echo "file_count=$CHANGED_FILES" >> "$GITHUB_OUTPUT" - fi - - # Push the i18n branch at the current PR-head tip. This only creates a - # branch ref pointing at an existing commit — no new (unverified) commit - # object is introduced here. - - name: Create i18n branch - if: steps.changes.outputs.changed == 'true' - env: - # Pass via env so the branch name can't inject shell commands. - HEAD_REF: ${{ steps.pr.outputs.head_ref }} - run: | - BRANCH="i18n/$HEAD_REF" - echo "BRANCH=$BRANCH" >> "$GITHUB_ENV" - git push --force origin "HEAD:refs/heads/$BRANCH" - - # Commit the generated translation files through the GitHub GraphQL API - # so the commit is signed by GitHub's GPG key and shows as "Verified" - # (a plain `git push` over HTTPS is never signed). Pinned to the v0.2.20 - # commit SHA for supply-chain safety. - - name: Commit translations (verified) - if: steps.changes.outputs.changed == 'true' - uses: planetscale/ghcommit-action@25309d8005ac7c3bcd61d3fe19b69e0fe47dbdde # v0.2.20 - with: - commit_message: | - chore: update i18n translations - - Auto-generated by /i18n command on PR #${{ github.event.issue.number }} - repo: ${{ github.repository }} - branch: ${{ env.BRANCH }} - file_pattern: '.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Open the translations PR against the original PR's head branch. - - name: Create PR - if: steps.changes.outputs.changed == 'true' - id: create_pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Pass via env so the base branch name can't inject shell commands. - HEAD_REF: ${{ steps.pr.outputs.head_ref }} - run: | - PR_URL=$(gh pr create \ - --title "chore: update i18n translations" \ - --body "This PR updates translation files for 7 languages (nl, fr, de, es, it, pt, pl). - - Triggered by \`/i18n\` command on #${{ github.event.issue.number }}." \ - --base "$HEAD_REF" \ - --head "$BRANCH") - - echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" - - # Comment on the original PR with a link to the translations PR - - name: Comment success - if: steps.changes.outputs.changed == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr comment "${{ github.event.issue.number }}" \ - --repo "${{ github.repository }}" \ - --body "**Translations PR created** :white_check_mark: - ${{ steps.changes.outputs.file_count }} files changed across 7 languages. - Review and merge: ${{ steps.create_pr.outputs.pr_url }}" - - - name: Comment no changes - if: steps.changes.outputs.changed == 'false' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr comment "${{ github.event.issue.number }}" \ - --repo "${{ github.repository }}" \ - --body "**No translation changes** :white_check_mark: - All translation files are already up to date." - - # On any failure, comment with a link to the logs - - name: Comment failure - if: failure() - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr comment "${{ github.event.issue.number }}" \ - --repo "${{ github.repository }}" \ - --body "**Translation update failed** :x: - [View workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details." diff --git a/.scripts/git-hooks/pre-commit b/.scripts/git-hooks/pre-commit deleted file mode 100644 index ce0ba23ea..000000000 --- a/.scripts/git-hooks/pre-commit +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/sh - -# Check PHPCS before commit -# Ref - -# https://github.com/smgladkovskiy/phpcs-git-pre-commit/blob/master/src/pre-commit -# https://dev.to/akdevcraft/git-pre-hook-pre-commit-hook-with-npm-project-44p2 - -echo "***** Running pre-commit ******" - -PROJECT=$(php -r "echo dirname(dirname(dirname(realpath('$0'))));") -STAGED_FILES_CMD=$(git diff --cached --name-only --diff-filter=ACMR HEAD | grep \\.php) -UNSTAGED_FILES_CMD=$(git diff --name-only --diff-filter=ACMR | grep \\.php) - -# Determine if a file list is passed -if [ "$#" -eq 1 ] -then - oIFS=$IFS - IFS=' - ' - SFILES="$1" - IFS=$oIFS -fi - -SFILES=${SFILES:-$STAGED_FILES_CMD} - -STAGED_BUT_MODIFIED_FILES=$(php -r "\$sfiles=(explode(\"\\n\", '$SFILES'));\$usfiles=(explode(\"\\n\", '$UNSTAGED_FILES_CMD'));echo implode(\"\\n\",array_intersect(\$usfiles,\$sfiles));") - -if [ -z "$STAGED_BUT_MODIFIED_FILES" ]; then - echo "OK" -else - echo "Files staged but then modified:\n" - echo "${STAGED_BUT_MODIFIED_FILES}" - exit 1 -fi - -# echo $PROJECT -# echo $SFILES - -echo "Checking files for PHP Lint..." -for FILE in $SFILES -do - # echo "$PROJECT/$FILE" - php -l -d display_errors=0 "./$FILE" - if [ $? != 0 ] - then - echo "Fix the error before commit." - exit 1 - fi - FILES="$FILES ./$FILE" -done - -if [ "$FILES" = "" ] -then - echo "All good..." - exit 0 -fi - -if [ "$FILES" != "" ] -then - echo "Running PHPCS (Code Sniffer) Check..." - composer lint -n $FILES - if [ $? != 0 ] - then - echo "Please fix the PHPCS errors before commit!" - echo " => Run this command for automatic fixes." - echo " composer format-- $FILES" - exit 1 - fi -fi - -if [ "$FILES" != "" ] -then - echo "Running PHPStan Check..." - composer phpstan - if [ $? != 0 ] - then - echo "Please fix the PHPStan errors before commit!" - echo " => Run this command to check PHPStan errors." - echo " composer phpstan" - exit 1 - fi -fi - -if [ "$FILES" != "" ] -then - echo "Running CSS linting issues..." - npm run lint-css - if [ $? != 0 ] - then - echo "Please fix the CSS issues before commit!" - exit 1 - fi -fi - -if [ "$FILES" != "" ] -then - echo "Running JS linting issues..." - npm run lint-js - if [ $? != 0 ] - then - echo "Please fix the JS issues before commit!" - exit 1 - fi -fi - -exit $? diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 2afeea1d5..000000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,355 +0,0 @@ -# SureForms Architecture Documentation - -## Table of Contents - -1. [Introduction](#introduction) -2. [System Architecture](#system-architecture) -3. [Core Components](#core-components) -4. [Data Flow](#data-flow) -5. [Form Building Process](#form-building-process) -6. [AI Integration](#ai-integration) -7. [Form Submission and Processing](#form-submission-and-processing) -8. [Security Features](#security-features) -9. [Extension Points](#extension-points) -10. [Technical Debt and Improvement Areas](#technical-debt-and-improvement-areas) -11. [Development Guidelines](#development-guidelines) - -## Introduction - -SureForms is a modern WordPress form builder plugin designed to provide an intuitive, no-code form building experience. It leverages WordPress's native block editor (Gutenberg) to create a seamless form building experience that integrates naturally with the WordPress ecosystem. - -### Key Features - -- **Native WordPress Integration**: Built on top of the WordPress block editor -- **AI-Powered Form Generation**: Create forms quickly using AI suggestions -- **No-Code Flexibility**: Advanced features without requiring coding knowledge -- **Mobile-First Design**: Responsive forms that work across all devices -- **Modern UI/UX**: Clean, intuitive interface for both admins and users -- **Security Features**: Anti-spam protection, GDPR compliance, and data security -- **Extensibility**: Developer-friendly architecture with hooks and filters - -## System Architecture - -SureForms follows a modular architecture pattern, separating concerns into distinct components that interact through well-defined interfaces. - -```mermaid -graph TD - A[WordPress Core] --> B[SureForms Plugin] - B --> C[Admin Interface] - B --> D[Form Builder] - B --> E[Form Renderer] - B --> F[Form Processor] - B --> G[Database Layer] - B --> H[AI Integration] - - C --> I[Settings Management] - C --> J[Form Management] - C --> K[Entry Management] - - D --> L[Block Editor Integration] - D --> M[Form Templates] - D --> N[Field Components] - - E --> O[Frontend Rendering] - E --> P[Validation] - - F --> Q[Submission Handler] - F --> R[Email Notifications] - F --> S[Confirmation Handler] - - G --> T[Custom Post Types] - G --> U[Custom Database Tables] - G --> V[Metadata Storage] - - H --> W[AI Form Generator] - H --> X[AI Middleware] -``` - -## Core Components - -### Plugin Loader (`Plugin_Loader`) - -The central initialization class that bootstraps the plugin, registers hooks, and loads core components. It follows a singleton pattern to ensure only one instance exists. - -### Post Types (`Post_Types`) - -Registers and manages custom post types for forms and entries. Handles the admin UI customizations for these post types. - -### Form Submit (`Form_Submit`) - -Processes form submissions through REST API endpoints, validates input data, handles file uploads, processes anti-spam measures, and triggers email notifications. - -### AI Form Builder (`AI_Form_Builder`) - -Integrates with AI services to generate form structures based on user prompts. Communicates with middleware services to process AI requests. - -### Database Layer - -Custom database tables for storing form entries and related data, separate from the WordPress post tables for better performance and data organization. - -### Frontend Assets (`Frontend_Assets`) - -Manages the loading of CSS and JavaScript assets for the frontend rendering of forms. - -### Email Notifications - -Handles the sending of email notifications based on form submissions, with support for multiple notification templates and recipients. - -## Data Flow - -### Form Creation Flow - -```mermaid -sequenceDiagram - participant User - participant WordPress - participant FormBuilder - participant Database - - User->>WordPress: Access Form Builder - WordPress->>FormBuilder: Initialize Builder Interface - FormBuilder->>User: Display Form Builder UI - - alt Manual Form Creation - User->>FormBuilder: Add/Configure Form Fields - FormBuilder->>User: Update UI Preview - else AI-Assisted Creation - User->>FormBuilder: Enter Form Description - FormBuilder->>AI: Send Form Description - AI->>FormBuilder: Return Suggested Form Structure - FormBuilder->>User: Display AI-Generated Form - User->>FormBuilder: Modify/Approve Form - end - - User->>FormBuilder: Save Form - FormBuilder->>Database: Store Form Configuration - Database->>WordPress: Return Form ID - WordPress->>User: Confirm Form Creation -``` - -### Form Submission Flow - -```mermaid -sequenceDiagram - participant User - participant Frontend - participant REST_API - participant Validation - participant AntiSpam - participant Database - participant EmailSystem - - User->>Frontend: Fill Form - Frontend->>User: Validate Client-Side - User->>Frontend: Submit Form - Frontend->>REST_API: POST Form Data - - REST_API->>Validation: Validate Data - Validation->>REST_API: Validation Result - - REST_API->>AntiSpam: Check for Spam - AntiSpam->>REST_API: Spam Check Result - - alt Valid Submission - REST_API->>Database: Store Entry - Database->>REST_API: Entry ID - REST_API->>EmailSystem: Trigger Notifications - EmailSystem->>REST_API: Email Status - REST_API->>Frontend: Success Response - Frontend->>User: Show Confirmation - else Invalid Submission - REST_API->>Frontend: Error Response - Frontend->>User: Show Error Message - end -``` - -## Form Building Process - -SureForms uses WordPress's block editor as the foundation for its form builder. This approach provides several advantages: - -1. **Familiar Interface**: Users already familiar with WordPress's editor can easily adapt -2. **Native Integration**: Seamless integration with WordPress core functionality -3. **Extensibility**: Ability to leverage the block ecosystem - -### Block Structure - -Forms are composed of various block types: - -- **Container Blocks**: Group and organize form fields -- **Field Blocks**: Individual form inputs (text, email, checkbox, etc.) -- **Layout Blocks**: Control the visual arrangement of fields -- **Special Blocks**: Submit buttons, GDPR notices, etc. - -Each block has its own edit and save components, following the WordPress block API pattern. - -### Form Data Storage - -Form configurations are stored as custom post types with: - -- Post content: Stores the block structure (serialized blocks) -- Post meta: Stores form settings, styling options, and other configuration data - -## AI Integration - -SureForms features an AI-powered form generation system that allows users to create forms by describing them in natural language. - -```mermaid -graph TD - A[User Input] --> B[AI Middleware] - B --> C[AI Service] - C --> D[Form Structure Generation] - D --> E[Field Mapping] - E --> F[Block Generation] - F --> G[Form Preview] - G --> H[User Edits] - H --> I[Final Form] -``` - -### AI Form Generation Process - -1. User provides a description of the desired form -2. The description is sent to the AI middleware -3. AI analyzes the description and generates a structured form definition -4. The form definition is mapped to SureForms field types -5. Blocks are generated and inserted into the editor -6. User can review and modify the generated form - -## Form Submission and Processing - -### Submission Handling - -Form submissions are processed through a custom REST API endpoint that: - -1. Validates the form data against defined rules -2. Checks for spam using various methods (honeypot, reCAPTCHA, etc.) -3. Processes file uploads if present -4. Stores the submission in the database -5. Triggers email notifications -6. Returns appropriate success/error responses - -### Email Notifications - -The email notification system supports: - -- Multiple notification templates per form -- Dynamic content using smart tags -- HTML email formatting -- Conditional sending based on form data -- Custom headers (CC, BCC, Reply-To) - -```mermaid -graph TD - A[Form Submission] --> B[Email Notification System] - B --> C{Multiple Recipients?} - C -->|Yes| D[Process Each Recipient] - C -->|No| E[Single Email] - D --> F[Parse Email Template] - E --> F - F --> G[Replace Smart Tags] - G --> H[Apply Email Template] - H --> I[Send Email] - I --> J{Success?} - J -->|Yes| K[Log Success] - J -->|No| L[Log Failure] -``` - -## Security Features - -SureForms implements multiple security measures to protect against common vulnerabilities: - -### Anti-Spam Protection - -- **Honeypot Fields**: Hidden fields to catch automated submissions -- **reCAPTCHA Integration**: Multiple versions of Google reCAPTCHA -- **hCaptcha Support**: Alternative to reCAPTCHA -- **Cloudflare Turnstile**: Modern CAPTCHA alternative - -### Data Protection - -- **GDPR Compliance**: Options to enable GDPR-compliant data handling -- **Data Encryption**: Sensitive data can be encrypted in storage -- **Auto-Delete Entries**: Automatic deletion of entries after a specified period - -### Input Validation - -- **Client-Side Validation**: Immediate feedback to users -- **Server-Side Validation**: Thorough validation of all submitted data -- **File Upload Security**: Strict file type and size validation - -## Extension Points - -SureForms provides several extension points for developers: - -### WordPress Hooks - -```php -// Example of filter hook for email notification -add_filter('srfm_email_notification', function($parsed, $submission_data, $item, $form_data) { - // Modify email content or recipients - return $parsed; -}, 10, 4); - -// Example of action hook before form submission -add_action('srfm_before_submission', function($form_data) { - // Perform custom actions before form processing -}); -``` - -### JavaScript API - -```javascript -// Example of extending the form validation -window.sureFormsHooks.addFilter( - 'srfm.validation.rules', - 'my-plugin/custom-validation', - function(rules, fieldData) { - // Add custom validation rules - return rules; - } -); -``` - -## Technical Debt and Improvement Areas - -1. **Performance Optimization**: - - Lazy loading of form assets - - Improved caching strategies - -2. **Code Refactoring**: - - Further modularization of components - - Consistent naming conventions - -3. **Testing Coverage**: - - Increase unit and integration test coverage - - Automated UI testing - -## Development Guidelines - -### Coding Standards - -SureForms follows WordPress coding standards with some additional guidelines: - -- PHP: PSR-12 with WordPress-specific adaptations -- JavaScript: ESLint with WordPress configuration -- CSS: Follows WordPress CSS coding standards - -### Development Workflow - -1. **Feature Planning**: Document requirements and design -2. **Development**: Implement features with appropriate tests -3. **Code Review**: Peer review for quality assurance -4. **Testing**: Automated and manual testing -5. **Documentation**: Update technical and user documentation -6. **Release**: Version tagging and deployment - -### Version Control - -- Feature branches for new development -- Pull request workflow for code review -- Semantic versioning for releases - -## Conclusion - -SureForms represents a modern approach to WordPress form building, combining the power of the block editor with advanced features like AI form generation. Its modular architecture and extensive extension points make it both user-friendly and developer-friendly, while its focus on security and performance ensures a reliable experience for all users. - -The plugin continues to evolve with a focus on improving user experience, expanding integration capabilities, and optimizing performance across all devices and platforms. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 40ad8c0e7..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,165 +0,0 @@ -# SureForms - CLAUDE.md - -## Project Overview -SureForms is a WordPress form builder plugin (v2.5.1) by Brainstorm Force. AI-powered drag-and-drop form building using the Gutenberg block editor. - -- **Plugin slug:** `sureforms` | **Text domain:** `sureforms` -- **Prefix:** `SRFM_` (constants), `srfm` (slug), `sureforms_` (post types/DB) -- **Minimum PHP:** 7.4 | **Minimum WP:** 6.4 - -## Tech Stack -- **Backend:** PHP 7.4+ / WordPress Plugin API / Custom REST endpoints -- **Frontend:** React 18 + WordPress Gutenberg blocks -- **Styling:** TailwindCSS 3.4 + SASS + PostCSS -- **Build:** WordPress Scripts (Webpack 5) + Grunt -- **Testing:** PHPUnit 9 (unit) + Playwright (E2E) -- **Static Analysis:** PHPStan level 9 + PHPCS (WordPress-Extra) -- **Node:** 24.16.0 (Volta) | **UI Library:** @bsf/force-ui - -## Commands - -### Build -```bash -npm run start # Dev server with watch -npm run build # Full production build -npm run build:script # Webpack only -npm run build:sass # SASS only -``` - -### Lint & Format -```bash -npm run lint-js # ESLint -npm run lint-js:fix # ESLint auto-fix -npm run lint-css # Stylelint -composer lint # PHPCS -composer format # PHPCBF auto-fix -composer phpstan # PHPStan level 9 -composer insights # PHP Insights -``` - -### Test -```bash -composer test # PHPUnit -composer test:coverage # PHPUnit with coverage -npm run test:unit # JS unit tests -npm run play:up # Start WP test env -npm run play:run # Playwright E2E -npm run play:stop # Stop WP test env -``` - -### i18n -```bash -npm run makepot # Generate .pot -npm run i18n:po # Update .po from .pot -npm run i18n:mo # Compile .mo -npm run i18n:json # JSON translations -``` - -## Architecture - -### Directory Structure -``` -sureforms/ -├── sureforms.php # Entry point, constants -├── plugin-loader.php # Bootstrap -├── inc/ # PHP backend -│ ├── helper.php # Central utility (large — minimize changes) -│ ├── rest-api.php # REST endpoints -│ ├── form-submit.php # Submission handler -│ ├── entries.php # Entry management -│ ├── post-types.php # CPT registration -│ ├── blocks/ # PHP block rendering -│ ├── fields/ # Field type handlers -│ ├── database/ # Custom DB tables -│ ├── payments/ # Stripe integration -│ ├── ai-form-builder/ # AI form generation -│ ├── global-settings/ # Plugin settings -│ ├── page-builders/ # Elementor/Bricks compat -│ └── lib/ # Third-party (DO NOT modify) -├── src/ # React/JS source -│ ├── admin/ # Admin UI -│ ├── blocks/ # Gutenberg block JS -│ ├── components/ # Shared React components -│ ├── store/ # WordPress data store -│ ├── utils/ # JS utilities -│ └── srfm-controls/ # Custom block controls -├── assets/ # Compiled output (gitignored) -├── sass/ # SASS source -├── modules/ # Feature modules -├── templates/ # PHP templates -├── tests/ # PHPUnit + Playwright + Docker -└── languages/ # Translation files -``` - -### Webpack Aliases -``` -@Admin → src/admin/ @Blocks → src/blocks/ @Controls → src/srfm-controls/ -@Components → src/components/ @Utils → src/utils/ @Svg → assets/svg/ -@Attributes → src/blocks-attributes/ @Image → images/ @IncBlocks → inc/blocks/ -``` - -### Key Data Structures -- **Post Type:** `sureforms_form` — Forms as CPT with block content -- **Custom Tables:** `sureforms_entries`, `sureforms_payments` -- **REST Namespace:** Custom endpoints in `inc/rest-api.php` - -## Code Rules - -### PHP -- Follow WPCS. Use `sureforms` text domain for all translatable strings -- `@since x.x.x` on all new functions/classes (updated in release PRs) -- NEVER `echo` without escaping — use `esc_html()`, `esc_attr()`, `wp_kses_post()` -- NEVER use `$_GET/$_POST/$_REQUEST` directly — use `sanitize_text_field( wp_unslash( ... ) )` -- NEVER skip security verification on endpoints/AJAX handlers - - **Public form submission endpoints** use HMAC tokens via `Submit_Token::verify()` (`inc/submit-token.php`) — NOT nonces. These are cache-safe and session-independent. The `// phpcs:ignore WordPress.Security.NonceVerification.Missing` comment is expected on these handlers. - - **Admin/authenticated endpoints** still use traditional nonces (`wp_verify_nonce()` / `check_ajax_referer()`) -- NEVER skip capability checks — use `current_user_can()` before privileged operations -- NEVER use `wp_die()` in REST callbacks — use `WP_Error` with proper response codes -- NEVER hardcode table names — use `$wpdb->prefix . 'sureforms_entries'` -- ALWAYS use `wp_json_encode()`, `wp_remote_get/post()`, `gmdate()`, `wp_safe_redirect()` - -### JavaScript/React -- Follow WordPress Scripts ESLint config -- Use WordPress data stores (`useSelect`/`useDispatch`) — never raw fetch in blocks -- Import from `@wordpress/*` packages — never import React directly -- Use `__()` from `@wordpress/i18n` — never hardcode user-facing strings -- Use TailwindCSS utility classes; use `@bsf/force-ui` for admin UI -- NEVER use `dangerouslySetInnerHTML` — use `RawHTML` from `@wordpress/element` - -### General -- NEVER create files unless absolutely necessary — prefer editing existing files -- NEVER proactively create documentation files unless explicitly requested - -## Gotchas -- **Custom DB tables** introduced in v0.0.13 — migration is irreversible -- **PHPStan level 9** is strict; check baseline before adding new ignores -- **Grunt** still used alongside Webpack for CSS minification and release packaging -- **`inc/lib/`** is third-party code — do not lint or modify -- **Node 24.16.0** pinned via Volta — other versions may cause build issues - -## Verification Before Done -After code changes, verify before reporting done: - -**JS:** `npm run lint-js` → `npm run build:script` -**SASS:** `npm run build:sass` (if touched) - -Checklist: -- Re-read the diff for obvious issues -- No debug code left (`console.log`, `error_log`, `var_dump`) -- All new functions have `@since x.x.x` -- All user-facing strings use `__()` / `_e()` -- Security checks on new endpoints/handlers (HMAC token for public, nonce for admin) -- For significant changes, suggest `npm run play:run` - -## Self-Improvement Loop -- When corrected, add a rule to **Learned Rules** so the mistake never repeats -- When a pattern causes a bug or CI failure, document it immediately -- Periodically prune outdated rules - -## Learned Rules - -- NEVER flag `phpcs:ignore WordPress.Security.NonceVerification.Missing` as a security issue on public form submission handlers (form submit, field validation, Stripe/PayPal payment intents) — these use HMAC token verification (`Submit_Token::verify()`) instead of nonces for page-cache compatibility. The HMAC token is embedded in the form at render time and verified server-side without any session dependency. -- ALWAYS use `Submit_Token::generate($form_id)` / `Submit_Token::verify($token, $form_id)` for public-facing form security — nonces (`wp_verify_nonce`) are only for admin/authenticated endpoints. - -## Current Focus - diff --git a/COMPREHENSIVE_ANALYSIS.md b/COMPREHENSIVE_ANALYSIS.md deleted file mode 100644 index aed786d38..000000000 --- a/COMPREHENSIVE_ANALYSIS.md +++ /dev/null @@ -1,304 +0,0 @@ -# SureForms: Comprehensive Analysis - -This document provides a multi-perspective analysis of the SureForms WordPress plugin, examining it from software architecture, development, and product management viewpoints. - -## Table of Contents - -1. [Software Architecture Analysis](#software-architecture-analysis) -2. [Development Perspective](#development-perspective) -3. [Product Management Analysis](#product-management-analysis) -4. [Integration Capabilities](#integration-capabilities) -5. [Future Development Roadmap](#future-development-roadmap) - -## Software Architecture Analysis - -### Core Architecture - -SureForms follows a modular architecture pattern with clear separation of concerns. The plugin is built around several key components: - -```mermaid -graph TD - A[WordPress Core] --> B[SureForms Plugin] - B --> C[Plugin_Loader] - C --> D[Core Components] - D --> E[Post_Types] - D --> F[Form_Submit] - D --> G[Block_Patterns] - D --> H[Frontend_Assets] - D --> I[AI_Form_Builder] - D --> J[Rest_Api] - D --> K[Database] - K --> L[Custom Tables] - K --> M[Post Meta] -``` - -The architecture leverages WordPress's plugin system and follows object-oriented principles with extensive use of the singleton pattern for core components. - -### Data Flow Architecture - -The data flow within SureForms follows a clear pattern: - -```mermaid -sequenceDiagram - participant User - participant Frontend - participant BlockEditor - participant REST_API - participant Database - - User->>BlockEditor: Create Form - BlockEditor->>Database: Store Form Configuration - User->>Frontend: View & Submit Form - Frontend->>REST_API: Submit Form Data - REST_API->>Database: Store Submission - REST_API->>Frontend: Return Response -``` - -This separation ensures clean data handling and allows for extensibility at various points in the process. - -### Component Analysis - -#### Plugin Loader - -The `Plugin_Loader` class serves as the entry point for the plugin, handling initialization, autoloading, and hook registration. It follows the singleton pattern to ensure only one instance exists. - -#### Post Types - -The `Post_Types` class manages custom post types for forms and entries, including admin UI customizations, shortcode functionality, and metadata registration. - -#### Form Submit - -The `Form_Submit` class processes form submissions through REST API endpoints, handling validation, file uploads, anti-spam measures, and email notifications. - -#### Database Layer - -SureForms uses both WordPress custom post types and custom database tables: -- Custom post type `sureforms_form` for form configurations -- Custom table `{prefix}_srfm_entries` for form submissions - -This hybrid approach leverages WordPress's content management for forms while optimizing data storage for submissions. - -#### AI Integration - -The AI form builder component connects with external AI services through a middleware layer, translating natural language descriptions into structured form definitions. - -### Technical Debt Assessment - -Current areas of technical debt include: - -1. **Performance Optimization** - - Opportunity for more aggressive asset loading strategies - - Potential for improved caching mechanisms - -2. **Code Organization** - - Some components have grown large and could benefit from further modularization - - Inconsistent naming conventions in some areas - -3. **Testing Coverage** - - Limited automated testing for JavaScript components - - Opportunity for more comprehensive integration tests - -## Development Perspective - -### Code Quality - -The codebase demonstrates several positive qualities: - -- **Consistent Structure**: Well-organized directory structure -- **Modern Practices**: Use of namespaces, autoloading, and OOP principles -- **Documentation**: Comprehensive inline documentation -- **Separation of Concerns**: Clear boundaries between components - -Areas for improvement include: - -- **Testing Coverage**: Expanding unit and integration tests -- **Consistent Naming**: Standardizing naming conventions across all components -- **Performance Profiling**: Identifying and addressing performance bottlenecks - -### Development Workflow - -The development process appears to follow modern practices: - -- **Version Control**: Git-based workflow with feature branches -- **Code Review**: Evidence of peer review processes -- **Continuous Integration**: GitHub Actions for automated testing -- **Semantic Versioning**: Clear versioning strategy - -### Extension Points - -SureForms provides several extension mechanisms: - -1. **WordPress Hooks** - - Actions for process intervention - - Filters for data modification - -2. **JavaScript API** - - Client-side hooks for extending functionality - - Custom events for integration - -3. **Block API** - - Ability to register custom form field blocks - - Block filters for modifying existing blocks - -4. **Template System** - - Customizable templates for form rendering - - Theme override capabilities - -### Technology Stack - -The plugin leverages a modern technology stack: - -- **PHP**: Core server-side logic -- **JavaScript/React**: Block editor integration and frontend functionality -- **CSS/SCSS**: Styling with preprocessor support -- **MySQL**: Database storage -- **REST API**: Data communication -- **AI Services**: Form generation capabilities - -## Product Management Analysis - -### User Segments - -SureForms targets several distinct user segments: - -1. **Website Owners** - - Need: Simple form creation without technical knowledge - - Value: Intuitive interface, pre-designed templates - -2. **Designers** - - Need: Visually appealing forms that match site design - - Value: Design customization, aesthetic controls - -3. **Developers** - - Need: Extensible platform with clean APIs - - Value: Hooks, filters, and documentation - -4. **No-Code Professionals** - - Need: Advanced functionality without coding - - Value: Conditional logic, multi-step forms - -### Feature Analysis - -The feature set is well-aligned with user needs: - -| Feature | User Segment | Value Proposition | -|---------|--------------|-------------------| -| Block Editor Integration | All | Familiar, native WordPress experience | -| AI Form Generation | All | Rapid form creation with minimal effort | -| Mobile-First Design | All | Forms work well on all devices | -| Advanced Field Types | No-Code Professionals | Complex form capabilities without coding | -| Developer Hooks | Developers | Extensibility for custom needs | -| Visual Customization | Designers | Forms match site aesthetics | -| Anti-Spam Features | All | Protection from spam submissions | -| GDPR Compliance | All | Legal compliance and data protection | - -### Competitive Positioning - -SureForms differentiates itself through: - -1. **Native WordPress Integration**: Using the block editor rather than a custom interface -2. **AI-Powered Creation**: Faster form building through AI assistance -3. **Design Focus**: Emphasis on visual appeal and user experience -4. **Developer-Friendly**: Clean architecture and extension points - -### Market Fit - -The product demonstrates strong market fit by addressing several key pain points: - -1. **Complex Form Building**: Simplifying the process through AI and intuitive interface -2. **Design Limitations**: Providing modern styling out-of-the-box -3. **Mobile Responsiveness**: Ensuring forms work well on all devices -4. **Technical Barriers**: Enabling advanced features without coding requirements - -## Integration Capabilities - -### WordPress Ecosystem - -SureForms integrates well with the WordPress ecosystem: - -- **Themes**: Compatible with major themes including Astra, Kadence, GeneratePress -- **Page Builders**: Works with Elementor, Beaver Builder, Divi -- **Plugins**: Compatible with popular plugins like WooCommerce, Easy Digital Downloads - -### External Services - -The plugin offers integration with several external services: - -- **reCAPTCHA**: Anti-spam protection -- **hCaptcha**: Alternative CAPTCHA solution -- **Cloudflare Turnstile**: Modern CAPTCHA alternative -- **AI Services**: Form generation capabilities - -### API Architecture - -The API architecture facilitates integration: - -```mermaid -graph LR - A[WordPress] --> B[SureForms Core] - B --> C[REST API] - C --> D[External Integrations] - D --> E[CRM Systems] - D --> F[Email Marketing] - D --> G[Payment Processors] - D --> H[AI Services] -``` - -This architecture allows for flexible integration with various third-party services. - -## Future Development Roadmap - -Based on the analysis, several development priorities emerge: - -### Short-Term Priorities - -1. **Performance Optimization** - - Implement lazy loading for form assets - - Optimize database queries for large form submissions - -2. **Code Quality Improvements** - - Increase test coverage - - Standardize naming conventions - - Refactor larger components - -3. **Integration Expansion** - - Add more third-party service integrations - - Enhance existing integrations with deeper functionality - -### Medium-Term Priorities - -1. **Advanced AI Capabilities** - - Improve form generation accuracy - - Add form analytics and insights - -2. **Enhanced User Experience** - - Streamline the form building process - - Improve form submission handling - -3. **Developer Tools** - - Create comprehensive API documentation - - Develop example extensions - -### Long-Term Vision - -1. **Form Ecosystem** - - Develop a marketplace for extensions - - Create specialized form solutions for different industries - -2. **Enterprise Features** - - Advanced workflow automation - - Enhanced security and compliance features - -3. **SaaS Potential** - - Explore hosted form solution alongside WordPress plugin - - Develop cloud-based form management - -## Conclusion - -SureForms represents a modern approach to WordPress form building, combining native block editor integration with AI-powered form generation. Its architecture demonstrates good separation of concerns and extensibility, while its feature set addresses key user needs across multiple segments. - -The product is well-positioned in the competitive form builder market, with clear differentiation through its WordPress integration, AI capabilities, and design focus. Future development should focus on expanding integrations, enhancing AI capabilities, and improving performance to maintain this competitive advantage. - -From a technical perspective, the codebase is well-structured but would benefit from increased test coverage and some refactoring of larger components. The extension system provides good flexibility for developers, though more comprehensive documentation would enhance this further. - -Overall, SureForms shows strong potential for continued growth and adoption, particularly as the WordPress ecosystem continues to embrace the block editor and AI-powered tools become increasingly important in the website building process. diff --git a/PRODUCT_ANALYSIS.md b/PRODUCT_ANALYSIS.md deleted file mode 100644 index a6c4e595e..000000000 --- a/PRODUCT_ANALYSIS.md +++ /dev/null @@ -1,251 +0,0 @@ -# SureForms Product Analysis - -## Executive Summary - -SureForms is a modern WordPress form builder plugin that differentiates itself through its native WordPress integration, AI-powered form generation, and focus on user experience. This analysis examines the product from multiple perspectives to provide a comprehensive understanding of its strengths, challenges, and opportunities for growth. - -## Table of Contents - -1. [Product Overview](#product-overview) -2. [Market Analysis](#market-analysis) -3. [User Experience Analysis](#user-experience-analysis) -4. [Technical Analysis](#technical-analysis) -5. [Competitive Analysis](#competitive-analysis) -6. [SWOT Analysis](#swot-analysis) -7. [Growth Opportunities](#growth-opportunities) -8. [Recommendations](#recommendations) - -## Product Overview - -SureForms is a WordPress form builder plugin that leverages the native WordPress block editor (Gutenberg) to provide an intuitive form building experience. Key features include: - -- **Native WordPress Integration**: Built on top of the WordPress block editor -- **AI-Powered Form Generation**: Create forms quickly using AI suggestions -- **No-Code Flexibility**: Advanced features without requiring coding knowledge -- **Mobile-First Design**: Responsive forms that work across all devices -- **Modern UI/UX**: Clean, intuitive interface for both admins and users -- **Security Features**: Anti-spam protection, GDPR compliance, and data security -- **Extensibility**: Developer-friendly architecture with hooks and filters - -The plugin is currently at version 1.7.0 and requires WordPress 6.4+ and PHP 7.4+. - -## Market Analysis - -### Target Audience - -SureForms targets several key user segments: - -1. **Website Owners**: Individuals and businesses who manage their own WordPress websites and need to collect information from visitors -2. **Designers**: Professionals who value good design and want to create visually appealing forms -3. **Developers**: Technical users who need a developer-friendly solution with clean APIs and hooks -4. **No-Code Professionals**: Users who need advanced form features without coding skills - -### Market Positioning - -SureForms positions itself as a modern alternative to traditional form builders, emphasizing: - -1. **Seamless WordPress Integration**: Using the native block editor rather than a separate interface -2. **Design-First Approach**: Focusing on aesthetics and user experience -3. **AI-Powered Efficiency**: Leveraging AI to speed up form creation -4. **Simplicity Without Compromise**: Providing advanced features in an accessible way - -### Market Trends - -Several trends in the WordPress form builder market are relevant to SureForms: - -1. **Shift to Block Editor**: Growing adoption of Gutenberg and block-based themes -2. **AI Integration**: Increasing use of AI for content and design generation -3. **Mobile-First Design**: Continued emphasis on mobile responsiveness -4. **Privacy Compliance**: Growing importance of GDPR and other privacy regulations -5. **No-Code Movement**: Rising demand for powerful tools that don't require coding skills - -## User Experience Analysis - -### Form Creation Experience - -SureForms provides a streamlined form creation experience: - -- **Block-Based Building**: Drag-and-drop interface using familiar WordPress blocks -- **AI Form Generation**: Natural language prompts to quickly create form structures -- **Visual Editing**: Real-time preview of forms as they're built -- **Templates**: Pre-designed form templates for common use cases - -### Form Submission Experience - -The end-user experience for form submission is optimized for: - -- **Responsive Design**: Forms adapt to different screen sizes -- **Inline Validation**: Real-time feedback on form inputs -- **Accessibility**: Support for screen readers and keyboard navigation -- **Confirmation Messages**: Customizable success messages and redirects - -### Admin Experience - -The admin interface focuses on: - -- **Intuitive Management**: Easy access to forms, entries, and settings -- **Data Visualization**: Visual reporting of form submission data -- **Entry Management**: Tools for viewing, filtering, and exporting submissions -- **Email Notifications**: Customizable email alerts for new submissions - -## Technical Analysis - -### Architecture - -SureForms follows a modular architecture with clear separation of concerns: - -- **Core Plugin**: Handles initialization, hooks, and basic functionality -- **Block System**: Manages form field blocks and their behavior -- **Form Processing**: Handles form submissions and data storage -- **AI Integration**: Connects with AI services for form generation -- **Extension System**: Provides hooks and filters for customization - -### Performance - -The plugin is designed with performance in mind: - -- **Optimized Asset Loading**: CSS and JS loaded only when needed -- **Efficient Database Usage**: Custom tables with appropriate indexes -- **Caching Strategies**: Caching of frequently accessed data -- **Minimal Dependencies**: Limited reliance on external libraries - -### Security - -Security features include: - -- **Input Validation**: Thorough validation of all user inputs -- **Anti-Spam Measures**: Multiple options for spam prevention -- **Data Protection**: GDPR compliance features -- **File Upload Security**: Strict validation of uploaded files - -### Extensibility - -The plugin provides several extension points: - -- **WordPress Hooks**: Actions and filters for modifying behavior -- **JavaScript API**: Client-side hooks for extending functionality -- **Block API**: Ability to create custom form field blocks -- **Template System**: Customizable templates for form rendering - -## Competitive Analysis - -### Key Competitors - -1. **Gravity Forms**: Established premium form builder with extensive features -2. **WPForms**: User-friendly form builder with a focus on simplicity -3. **Formidable Forms**: Advanced form builder with data management features -4. **Ninja Forms**: Modular form builder with add-on marketplace -5. **Contact Form 7**: Simple, free form builder with large user base - -### Competitive Advantages - -SureForms differentiates itself through: - -1. **Native Block Editor Integration**: More seamless WordPress experience -2. **AI-Powered Form Creation**: Faster form building process -3. **Modern Design Focus**: Better out-of-the-box aesthetics -4. **Developer-Friendly Architecture**: Clean APIs and extension points -5. **Mobile-First Approach**: Better experience on mobile devices - -### Competitive Challenges - -Areas where competitors may have advantages: - -1. **Ecosystem Maturity**: Established competitors have larger add-on ecosystems -2. **Market Recognition**: Lower brand awareness as a newer entrant -3. **Feature Depth**: Some competitors have more specialized features -4. **Integration Breadth**: Fewer third-party service integrations - -## SWOT Analysis - -### Strengths - -- Native integration with WordPress block editor -- AI-powered form generation capabilities -- Modern, clean design aesthetic -- Mobile-first responsive approach -- Developer-friendly architecture -- Focus on user experience - -### Weaknesses - -- Newer entrant in a mature market -- Smaller ecosystem of extensions and add-ons -- Limited third-party integrations compared to competitors -- Requires newer versions of WordPress and PHP - -### Opportunities - -- Growing adoption of the WordPress block editor -- Increasing demand for AI-powered tools -- Rising importance of mobile-friendly forms -- Expanding market for no-code solutions -- Potential for vertical-specific form solutions - -### Threats - -- Established competitors with loyal user bases -- WordPress core changes affecting block editor functionality -- Potential AI regulation affecting AI form generation -- Market commoditization driving price competition -- Alternative form solutions outside WordPress ecosystem - -## Growth Opportunities - -### Product Development - -1. **Expanded AI Capabilities**: More sophisticated form generation and analysis -2. **Vertical-Specific Templates**: Forms tailored to specific industries or use cases -3. **Advanced Conditional Logic**: More powerful dynamic form behavior -4. **Enhanced Data Analysis**: Better visualization and reporting of form data -5. **Multi-Step Form Improvements**: More sophisticated multi-page form experiences - -### Market Expansion - -1. **Agency Partner Program**: Dedicated resources for agencies and developers -2. **Vertical Marketing**: Targeted solutions for specific industries -3. **Educational Content**: Tutorials and resources to drive adoption -4. **Integration Partnerships**: Collaborations with complementary services -5. **International Expansion**: Localization for key markets - -### Business Model - -1. **Freemium Model**: Free core plugin with premium add-ons or features -2. **Subscription Tiers**: Different service levels for different user needs -3. **White-Label Solutions**: Customizable versions for agencies and resellers -4. **Enterprise Offerings**: Custom solutions for larger organizations -5. **Marketplace**: Platform for third-party extensions - -## Recommendations - -### Short-Term (0-6 months) - -1. **Expand Integration Ecosystem**: Add connections to popular CRM and email marketing platforms -2. **Enhance AI Form Builder**: Improve accuracy and capabilities of AI form generation -3. **Develop Educational Content**: Create tutorials and documentation to drive adoption -4. **Optimize Performance**: Further improve loading speed and resource usage -5. **Implement User Feedback System**: Gather structured feedback from early adopters - -### Medium-Term (6-12 months) - -1. **Launch Partner Program**: Develop resources for agencies and developers -2. **Create Vertical Solutions**: Develop specialized templates for key industries -3. **Expand Block Library**: Add more specialized form field types -4. **Improve Analytics**: Enhance data visualization and reporting capabilities -5. **Develop Extension Marketplace**: Create platform for third-party add-ons - -### Long-Term (12+ months) - -1. **Develop Enterprise Features**: Add capabilities for larger organizations -2. **Explore SaaS Model**: Consider hosted form solution alongside WordPress plugin -3. **Implement Advanced AI**: Add predictive analytics and personalization -4. **Create Form Ecosystem**: Develop complementary products and services -5. **Explore International Markets**: Localize for key global markets - -## Conclusion - -SureForms represents a modern approach to WordPress form building, combining the power of the native block editor with AI-powered form generation. Its focus on user experience, design, and developer-friendly architecture positions it well in the competitive form builder market. - -By leveraging its strengths in WordPress integration and AI capabilities while addressing its challenges in market recognition and ecosystem development, SureForms has significant potential for growth. The recommended strategies focus on expanding integrations, enhancing AI capabilities, developing educational resources, and creating specialized solutions for key market segments. - -With continued development and strategic focus, SureForms can establish itself as a leading WordPress form solution for the modern web. diff --git a/TECHNICAL_OVERVIEW.md b/TECHNICAL_OVERVIEW.md deleted file mode 100644 index 9d2d44140..000000000 --- a/TECHNICAL_OVERVIEW.md +++ /dev/null @@ -1,393 +0,0 @@ -# SureForms Technical Overview - -## Introduction - -SureForms is a modern WordPress form builder plugin that leverages the native WordPress block editor (Gutenberg) to provide an intuitive, no-code form building experience. This document provides a comprehensive technical overview of the plugin's architecture, components, and functionality. - -## Table of Contents - -1. [Plugin Architecture](#plugin-architecture) -2. [Core Components](#core-components) -3. [Database Structure](#database-structure) -4. [Form Building System](#form-building-system) -5. [Form Submission Process](#form-submission-process) -6. [AI Integration](#ai-integration) -7. [Security Features](#security-features) -8. [Extension Points](#extension-points) -9. [Performance Considerations](#performance-considerations) -10. [Future Development](#future-development) - -## Plugin Architecture - -SureForms follows a modular architecture pattern with clear separation of concerns. The plugin is structured around several key components that interact through well-defined interfaces. - -### Directory Structure - -``` -sureforms/ -├── admin/ # Admin-specific functionality -├── api/ # REST API endpoints -├── assets/ # CSS, JS, and other assets -├── inc/ # Core functionality -│ ├── ai-form-builder/ # AI form generation -│ ├── blocks/ # Block registration -│ ├── compatibility/ # Theme/plugin compatibility -│ ├── database/ # Custom database tables -│ ├── email/ # Email notification system -│ ├── fields/ # Form field definitions -│ ├── global-settings/ # Global plugin settings -│ ├── lib/ # Third-party libraries -│ ├── page-builders/ # Page builder integrations -│ ├── single-form-settings/ # Form-specific settings -│ └── traits/ # Shared PHP traits -├── languages/ # Internationalization files -├── modules/ # Feature modules -│ ├── gutenberg/ # Gutenberg integration -│ └── quick-action-sidebar/ # Quick action UI -├── src/ # JavaScript source files -│ ├── admin/ # Admin JS -│ ├── blocks/ # Block JS -│ ├── components/ # Reusable React components -│ ├── lib/ # JS libraries -│ ├── store/ # Redux store -│ ├── styles/ # SCSS files -│ └── utils/ # Utility functions -└── templates/ # Template files -``` - -### Initialization Flow - -The plugin follows a structured initialization process: - -1. `sureforms.php` - Main plugin file that defines constants and includes the plugin loader -2. `plugin-loader.php` - Initializes the plugin and registers core components -3. Core components are loaded through the `Plugin_Loader` class using autoloading -4. WordPress hooks are registered to integrate with the WordPress lifecycle - -```mermaid -graph TD - A[sureforms.php] --> B[plugin-loader.php] - B --> C[Plugin_Loader class] - C --> D[Register hooks] - C --> E[Autoload classes] - C --> F[Load textdomain] - D --> G[Init core components] - G --> H[Post_Types] - G --> I[Form_Submit] - G --> J[Block_Patterns] - G --> K[Frontend_Assets] - G --> L[AI_Form_Builder] - G --> M[Rest_Api] -``` - -## Core Components - -### Plugin_Loader - -The central initialization class that bootstraps the plugin, registers hooks, and loads core components. It follows a singleton pattern to ensure only one instance exists. - -```php -class Plugin_Loader { - private static $instance = null; - - public static function get_instance() { - if (null === self::$instance) { - self::$instance = new self(); - do_action('srfm_core_loaded'); - } - return self::$instance; - } - - // Initialization methods... -} -``` - -### Post_Types - -Registers and manages custom post types for forms and entries. Handles the admin UI customizations for these post types. - -Key features: -- Registers `sureforms_form` post type for storing form configurations -- Adds custom columns to the admin list view -- Implements shortcode functionality -- Manages form metadata registration - -### Form_Submit - -Processes form submissions through REST API endpoints, validates input data, handles file uploads, processes anti-spam measures, and triggers email notifications. - -Key features: -- Registers `/sureforms/v1/submit-form` REST API endpoint -- Validates form submissions against defined rules -- Processes file uploads with security checks -- Handles anti-spam measures (honeypot, reCAPTCHA, hCaptcha, Cloudflare Turnstile) -- Stores submissions in the database -- Triggers email notifications - -### AI_Form_Builder - -Integrates with AI services to generate form structures based on user prompts. Communicates with middleware services to process AI requests. - -Key features: -- Processes natural language descriptions into form structures -- Maps AI-generated fields to SureForms field types -- Generates block structures for the editor - -## Database Structure - -SureForms uses both WordPress custom post types and custom database tables to store data efficiently. - -### Custom Post Types - -- **sureforms_form**: Stores form configurations - - Post content: Serialized blocks representing the form structure - - Post meta: Form settings, styling options, and other configuration data - -### Custom Database Tables - -- **{prefix}_srfm_entries**: Stores form submissions - - ID: Primary key - - form_id: Associated form ID - - user_id: Submitter's user ID (if logged in) - - form_data: JSON-encoded submission data - - submission_info: Browser, IP, and device information - - status: Entry status (read, unread, trash) - - logs: Activity logs for the entry - - created_at: Submission timestamp - -```mermaid -erDiagram - FORMS ||--o{ ENTRIES : "has" - FORMS { - int ID - string post_title - text post_content - string post_status - datetime post_date - } - ENTRIES { - int ID - int form_id - int user_id - json form_data - json submission_info - string status - json logs - datetime created_at - } - FORMS ||--o{ FORM_META : "has" - FORM_META { - int meta_id - int post_id - string meta_key - mixed meta_value - } -``` - -## Form Building System - -SureForms uses WordPress's block editor as the foundation for its form builder. - -### Block Structure - -Forms are composed of various block types: - -- **Container Blocks**: Group and organize form fields -- **Field Blocks**: Individual form inputs (text, email, checkbox, etc.) -- **Layout Blocks**: Control the visual arrangement of fields -- **Special Blocks**: Submit buttons, GDPR notices, etc. - -Each block has its own edit and save components, following the WordPress block API pattern. - -### Block Registration - -Blocks are registered using the WordPress block registration API: - -```javascript -registerBlockType('sureforms/input', { - title: __('Text Field', 'sureforms'), - icon: 'text', - category: 'sureforms', - attributes: { - // Block attributes - }, - edit: Edit, - save: Save -}); -``` - -### Form Rendering - -Forms are rendered on the frontend using a combination of server-side rendering and client-side JavaScript: - -1. The form shortcode or block triggers the `Generate_Form_Markup::get_form_markup()` method -2. The method retrieves the form configuration and generates the HTML markup -3. Frontend JavaScript initializes the form functionality (validation, submission, etc.) - -## Form Submission Process - -### Client-Side Flow - -1. User fills out the form -2. Client-side validation checks for errors -3. Form data is collected and serialized -4. AJAX request is sent to the REST API endpoint -5. Response is processed and appropriate feedback is shown to the user - -### Server-Side Flow - -1. REST API endpoint receives the form submission -2. Data is validated and sanitized -3. Anti-spam checks are performed -4. If GDPR compliance is enabled, appropriate data handling is applied -5. Submission is stored in the database (unless configured not to) -6. Email notifications are triggered -7. Response is sent back to the client - -```mermaid -sequenceDiagram - participant User - participant Browser - participant REST_API - participant Database - participant Email - - User->>Browser: Fill form - Browser->>Browser: Validate input - User->>Browser: Submit form - Browser->>REST_API: POST /sureforms/v1/submit-form - REST_API->>REST_API: Validate data - REST_API->>REST_API: Anti-spam check - REST_API->>Database: Store submission - REST_API->>Email: Send notifications - REST_API->>Browser: Return response - Browser->>User: Show confirmation -``` - -## AI Integration - -SureForms features an AI-powered form generation system that allows users to create forms by describing them in natural language. - -### AI Form Generation Process - -1. User provides a description of the desired form -2. The description is sent to the AI middleware -3. AI analyzes the description and generates a structured form definition -4. The form definition is mapped to SureForms field types -5. Blocks are generated and inserted into the editor -6. User can review and modify the generated form - -### AI Middleware - -The AI middleware acts as a bridge between SureForms and the AI service: - -1. Receives the form description from SureForms -2. Processes the description using AI models -3. Returns a structured form definition -4. Handles authentication and rate limiting - -## Security Features - -SureForms implements multiple security measures to protect against common vulnerabilities: - -### Anti-Spam Protection - -- **Honeypot Fields**: Hidden fields to catch automated submissions -- **reCAPTCHA Integration**: Multiple versions of Google reCAPTCHA -- **hCaptcha Support**: Alternative to reCAPTCHA -- **Cloudflare Turnstile**: Modern CAPTCHA alternative - -### Data Protection - -- **GDPR Compliance**: Options to enable GDPR-compliant data handling -- **Data Encryption**: Sensitive data can be encrypted in storage -- **Auto-Delete Entries**: Automatic deletion of entries after a specified period - -### Input Validation - -- **Client-Side Validation**: Immediate feedback to users -- **Server-Side Validation**: Thorough validation of all submitted data -- **File Upload Security**: Strict file type and size validation - -## Extension Points - -SureForms provides several extension points for developers: - -### WordPress Hooks - -```php -// Example of filter hook for email notification -add_filter('srfm_email_notification', function($parsed, $submission_data, $item, $form_data) { - // Modify email content or recipients - return $parsed; -}, 10, 4); - -// Example of action hook before form submission -add_action('srfm_before_submission', function($form_data) { - // Perform custom actions before form processing -}); -``` - -### JavaScript API - -```javascript -// Example of extending the form validation -window.sureFormsHooks.addFilter( - 'srfm.validation.rules', - 'my-plugin/custom-validation', - function(rules, fieldData) { - // Add custom validation rules - return rules; - } -); -``` - -## Performance Considerations - -SureForms is designed with performance in mind: - -### Asset Loading - -- CSS and JavaScript assets are loaded only when needed -- Assets are minified and optimized for production -- Critical CSS is inlined for faster rendering - -### Database Optimization - -- Custom database tables with appropriate indexes -- Efficient queries with proper WHERE clauses -- Caching of frequently accessed data - -### Form Rendering - -- Server-side rendering for initial form display -- Progressive enhancement for JavaScript features -- Lazy loading of heavy components - -## Future Development - -Planned enhancements for future versions: - -1. **Enhanced AI Capabilities** - - More sophisticated form generation - - AI-powered form analytics - -2. **Advanced Integrations** - - More third-party service integrations - - Improved CRM connections - -3. **Performance Optimizations** - - Further asset optimization - - Enhanced caching strategies - -4. **Accessibility Improvements** - - Better screen reader support - - Keyboard navigation enhancements - -5. **Developer Tools** - - More comprehensive API documentation - - Additional extension points - -## Conclusion - -SureForms represents a modern approach to WordPress form building, combining the power of the block editor with advanced features like AI form generation. Its modular architecture and extensive extension points make it both user-friendly and developer-friendly, while its focus on security and performance ensures a reliable experience for all users. diff --git a/bin/build-zip.sh b/bin/build-zip.sh deleted file mode 100644 index 0bf38a51d..000000000 --- a/bin/build-zip.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash - -# Exit if any command fails. -set -e - -# Enable nicer messaging for build status. -BLUE_BOLD='\033[1;34m'; -GREEN_BOLD='\033[1;32m'; -RED_BOLD='\033[1;31m'; -YELLOW_BOLD='\033[1;33m'; -COLOR_RESET='\033[0m'; -error () { - echo -e "\n${RED_BOLD}$1${COLOR_RESET}\n" -} -status () { - echo -e "\n${BLUE_BOLD}$1${COLOR_RESET}\n" -} -success () { - echo -e "\n${GREEN_BOLD}$1${COLOR_RESET}\n" -} -warning () { - echo -e "\n${YELLOW_BOLD}$1${COLOR_RESET}\n" -} - - -status "💃 Time to build the plugin ZIP file 🕺" - -if [ ! -d "artifact" ]; then - mkdir "artifact" -fi - - -# Copy files for zip. -rsync -rc --delete --exclude-from ".distignore" "./" "artifact/sureforms" - -# Go to directory -cd artifact - -# Create a zip copied files. -zip -r sureforms.zip "./sureforms" - -if [ "no-clean" != "$1" ]; then - # Removed copied files folder. - rm -rf sureforms -fi - -success "Done. Your sureforms zip is ready..! 🎉" diff --git a/bin/checkout-and-build b/bin/checkout-and-build deleted file mode 100755 index c7529e42e..000000000 --- a/bin/checkout-and-build +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# Script to checkout a branch and build the project - -# Check if branch name is provided as an argument -if [ -z "$1" ]; then - echo "Error: No branch name provided." - echo "Usage: ./checkout-and-build.sh " - exit 1 -fi - -# Assign the first argument to a variable -branch_name=$1 - -# Checkout the specified branch -git checkout "$branch_name" - -# pull the latest version -git pull origin "$branch_name" - -# Run the build command -npm run build diff --git a/bin/i18n.sh b/bin/i18n.sh deleted file mode 100644 index bd71a862a..000000000 --- a/bin/i18n.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash - -# Run release script -#bash bin/i18n.sh -# -# Generates/updates the .pot, .po, .mo and JS .json translation files for the -# BSF Top 20 languages standard. Run at release time. New locales are created -# automatically via msginit, so the .po files do not need to be committed. - -# Always run from the plugin root so relative paths resolve. -cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit 1 - -# Target locales (BSF Top 20). Format: : -LANGS=( - "de_DE:de_DE" "es_ES:es_ES" "fr_FR:fr_FR" "it_IT:it_IT" "nl_NL:nl_NL" - "pl_PL:pl_PL" "pt_PT:pt_PT" "id_ID:id_ID" "pt_BR:pt_BR" "ru_RU:ru_RU" - "tr_TR:tr_TR" "ja:ja_JP" "zh_CN:zh_CN" "ar:ar" "sv_SE:sv_SE" - "vi:vi_VN" "he_IL:he_IL" "th:th_TH" "el:el_GR" "cs_CZ:cs_CZ" -) - -# Run textdomain updates and generate the POT file -echo "Running textdomain update and POT file generation..." -npm run makepot - -# Create a .po for any target locale that does not exist yet. -echo "Ensuring .po files exist for all target locales..." -for entry in "${LANGS[@]}"; do - file="${entry%%:*}"; loc="${entry##*:}" - po="languages/sureforms-${file}.po" - if [ ! -f "$po" ]; then - echo " creating $po ($loc)" - msginit --input=languages/sureforms.pot --output="$po" --locale="$loc" --no-translator - fi -done - -# Update PO files -echo "Updating PO files..." -npm run i18n:po - -# Translate using GPT-PO (only empty msgstr entries are filled). -echo "Translating PO files using GPT-PO..." -npm run i18n:gptpo:nl -npm run i18n:gptpo:fr -npm run i18n:gptpo:de -npm run i18n:gptpo:es -npm run i18n:gptpo:it -npm run i18n:gptpo:pt -npm run i18n:gptpo:pl -npm run i18n:gptpo:id -npm run i18n:gptpo:ptbr -npm run i18n:gptpo:ru -npm run i18n:gptpo:tr -npm run i18n:gptpo:ja -npm run i18n:gptpo:zh -npm run i18n:gptpo:ar -npm run i18n:gptpo:sv -npm run i18n:gptpo:vi -npm run i18n:gptpo:he -npm run i18n:gptpo:th -npm run i18n:gptpo:el -npm run i18n:gptpo:cs - -# Update PO files again after translation -echo "Updating PO files again..." -npm run i18n:po - -# Generate MO files -echo "Generating MO files..." -npm run i18n:mo - -# Generate JSON translation files -echo "Generating JSON translation files..." -npm run i18n:json - -echo "All commands executed successfully." diff --git a/docs/01-getting-started/GETTING-STARTED.md b/docs/01-getting-started/GETTING-STARTED.md deleted file mode 100644 index a9c0b0c34..000000000 --- a/docs/01-getting-started/GETTING-STARTED.md +++ /dev/null @@ -1,647 +0,0 @@ -# Getting Started with SureForms Development - -> **Goal:** Get productive in less than 30 minutes -> **Audience:** New developers, contractors, open-source contributors -> **Prerequisites:** Basic WordPress, PHP, and React knowledge - -## Prerequisites - -Before you begin, ensure you have: - -- **PHP 7.4+** with extensions: mysqli, mbstring, curl, json -- **Composer 2.3+** for PHP dependencies -- **Node.js 18.15+** (check with `node --version`, use nvm: `nvm use`) -- **npm** (comes with Node.js) -- **Git** for version control -- **Code editor** (VS Code recommended with extensions: PHP Intelephense, ESLint, Prettier) -- **Docker** (optional, for wp-env local environment) - ---- - -## Quick Start (5 Minutes) - -### 1. Clone Repository -```bash -git clone https://github.com/brainstormforce/sureforms.git -cd sureforms -``` - -### 2. Install Dependencies -```bash -# PHP dependencies -composer install - -# JavaScript dependencies -npm install -``` - -This installs: -- **PHP:** Action Scheduler, NPS Survey, Analytics libraries -- **JavaScript:** React 18, WordPress scripts, TailwindCSS, Webpack, testing tools - -### 3. Build Assets -```bash -npm run build -``` - -This compiles: -- JavaScript bundles (Webpack → assets/build/) -- CSS from SASS (sass/ → assets/css/) -- Minified production files - -**Time:** ~2-3 minutes - -### 4. Start Local Environment - -**Option A: Using wp-env (Recommended)** -```bash -npm run play:up -``` - -This creates a Docker-based WordPress installation: -- **URL:** http://localhost:8888 -- **Admin:** http://localhost:8888/wp-admin -- **Username:** admin -- **Password:** password - -**Option B: Manual WordPress Setup** -1. Install WordPress locally (MAMP, Local by Flywheel, or XAMPP) -2. Symlink or copy plugin to `wp-content/plugins/sureforms` -3. Activate plugin in WordPress admin - -### 5. Activate Plugin - -Visit http://localhost:8888/wp-admin/plugins.php and activate "SureForms". - -**You're now ready to develop!** 🎉 - ---- - -## Project Structure Overview - -``` -sureforms/ -├── sureforms.php # Main plugin file (entry point) -├── plugin-loader.php # Bootstrap and autoloader -│ -├── inc/ # PHP backend logic (176+ files) -│ ├── form-submit.php # Form submission handler -│ ├── post-types.php # Custom post type registration -│ ├── rest-api.php # REST API endpoints -│ ├── helper.php # Utility functions -│ ├── blocks/ # Gutenberg block PHP classes -│ │ ├── base.php # Abstract base for blocks -│ │ ├── register.php # Block registration -│ │ ├── input/ # Text input block -│ │ └── [14+ more blocks] -│ ├── database/ # Custom database layer -│ │ ├── base.php # Abstract CRUD operations -│ │ ├── register.php # Table initialization -│ │ └── tables/ -│ │ ├── entries.php # Form submissions table -│ │ └── payments.php # Payment transactions table -│ └── [many more directories] -│ -├── src/ # JavaScript/React source -│ ├── blocks/ # Gutenberg block components -│ │ ├── blocks.js # Block registration entry -│ │ ├── input/ # Input block React component -│ │ │ ├── edit.js # Editor interface -│ │ │ ├── save.js # Frontend save (usually null) -│ │ │ └── block.json # Block metadata -│ │ └── [14+ more blocks] -│ ├── admin/ # Admin dashboard React apps -│ │ ├── dashboard/ # Forms listing -│ │ ├── settings/ # Settings page -│ │ ├── entries/ # Entry management -│ │ └── single-form-settings/ # Form editor -│ ├── components/ # Reusable React components -│ ├── store/ # WordPress Data Store (Redux) -│ └── utils/ # Helper functions -│ -├── assets/ # Compiled build output -│ ├── build/ # Webpack bundles -│ ├── css/ # Compiled CSS -│ └── js/ # Compiled JavaScript -│ -├── sass/ # SCSS source files -├── tests/ # PHPUnit and Playwright tests -├── webpack.config.js # Build configuration -├── package.json # npm dependencies and scripts -└── composer.json # PHP dependencies -``` - -### Key Entry Points - -**PHP Initialization:** -``` -sureforms.php (defines constants) - → plugin-loader.php (Plugin_Loader class) - → Autoloader registration - → WordPress hooks: plugins_loaded, init, admin_init -``` - -**JavaScript Entry:** -- **Blocks:** [src/blocks/index.js](../../src/blocks/index.js) -- **Admin Pages:** [src/admin/{page}/index.js](../../src/admin/) - ---- - -## Development Workflow - -### Daily Development - -**1. Start Development Server** -```bash -npm start -``` - -This starts Webpack in watch mode: -- Automatically rebuilds on file changes -- Hot reload for React components -- Source maps for debugging - -**2. Make Changes** -- Edit PHP files in `inc/` or `admin/` -- Edit React components in `src/` -- Edit styles in `sass/` - -**3. See Changes** -- **Admin:** Refresh browser (Cmd/Ctrl + R) -- **Frontend:** Hard refresh (Cmd/Ctrl + Shift + R) to clear cache - -### Before Committing - -**CRITICAL: Always run these commands before committing** - -```bash -# 1. Fix JavaScript linting issues -npm run lint-js:fix - -# 2. Fix CSS linting issues -npm run lint-css:fix - -# 3. Production build (REQUIRED) -npm run build - -# 4. Check PHP coding standards -composer phpcs - -# 5. Run static analysis -composer phpstan - -# 6. Run tests (optional but recommended) -composer test -npm run test:e2e -``` - -**If all pass, commit:** -```bash -git add . -git commit -m "feat: your change description" -git push -``` - -### Commit Message Format - -Follow [Conventional Commits](https://www.conventionalcommits.org/): - -``` -feat: add date picker field block -fix: resolve form submission nonce error -docs: update API endpoint documentation -style: format code with PHPCS -refactor: simplify validation logic -test: add unit tests for Helper class -chore: update dependencies -``` - ---- - -## Common Development Tasks - -### Task 1: Create a Test Form - -**Time:** 5 minutes - -1. Navigate to **SureForms → Add New** in WordPress admin -2. Block editor opens with empty form -3. Add fields by clicking "+" button: - - Add "Input" block → Configure as "Name" - - Add "Email" block → Configure as "Email Address" - - Add "Textarea" block → Configure as "Message" -4. Click "Publish" -5. View form on frontend or use shortcode `[sureforms id="{ID}"]` - -### Task 2: Submit a Test Form - -**Time:** 2 minutes - -1. Visit form on frontend -2. Fill out fields -3. Click "Submit" -4. Check **SureForms → Entries** to see submission - -### Task 3: Add a New Setting to Existing Block - -**Time:** 30-45 minutes -**Difficulty:** Easy -**Files Modified:** 2 - -**Example: Add "Max Length" setting to Input block** - -**Step 1:** Update [src/blocks/input/edit.js](../../src/blocks/input/edit.js) - -Find the `` section and add: -```javascript -import { TextControl } from '@wordpress/components'; - -// Inside InspectorControls > PanelBody - setAttributes({ maxLength: parseInt(value) })} - help={__('Maximum number of characters allowed', 'sureforms')} -/> -``` - -**Step 2:** Update [src/blocks/input/block.json](../../src/blocks/input/block.json) - -Add to `attributes` object: -```json -"maxLength": { - "type": "number", - "default": 0 -} -``` - -**Step 3:** Build and test -```bash -npm run build -# Refresh block editor -# Test setting in Inspector panel -``` - -**Step 4:** Update PHP rendering (if needed) - -Edit [inc/blocks/input/class-input.php](../../inc/blocks/input/class-input.php) to use the attribute: -```php -$max_length = $attributes['maxLength'] ?? 0; - -// In HTML output - 0): ?> - maxlength="" - -/> -``` - -### Task 4: Debug a Form Submission Issue - -**Time:** 10-20 minutes - -**Symptoms:** Form submission fails silently, no error message - -**Debug Steps:** - -1. **Check Browser Console** (F12 → Console tab) - - Look for JavaScript errors - - Check Network tab for failed AJAX requests - - Look for 403 (nonce error) or 500 (server error) - -2. **Check PHP Error Logs** -```bash -# If using wp-env -npm run play:logs - -# Or tail WordPress debug log -tail -f wp-content/debug.log -``` - -3. **Enable WordPress Debug Mode** (wp-config.php) -```php -define('WP_DEBUG', true); -define('WP_DEBUG_LOG', true); -define('WP_DEBUG_DISPLAY', false); -define('SCRIPT_DEBUG', true); -``` - -4. **Test REST API Directly** -```bash -# Get nonce from browser (inspect form, look for data-nonce attribute) -curl -X POST "http://localhost:8888/wp-json/sureforms/v1/submit-form" \ - -H "X-WP-Nonce: YOUR_NONCE_HERE" \ - -H "Content-Type: application/json" \ - -d '{"form_id":123,"field_data":{"email":{"value":"test@example.com"}}}' -``` - -5. **Common Fixes:** - - **Nonce expired:** Refresh page - - **Spam protection:** Disable reCAPTCHA temporarily - - **PHP error:** Check debug.log for syntax errors - - **REST API disabled:** Check .htaccess or security plugins - ---- - -## Testing Your Changes - -### Unit Tests (PHP) - -```bash -# Run all PHPUnit tests -composer test - -# Run specific test file -./vendor/bin/phpunit tests/unit/test-helper.php - -# Generate coverage report -composer test:coverage -``` - -### E2E Tests (Playwright) - -```bash -# Run all E2E tests -npm run test:e2e - -# Run specific test -npx playwright test tests/play/specs/basic-form-test-submit-text-field.spec.js - -# Run in headed mode (see browser) -npx playwright test --headed - -# Debug mode -npx playwright test --debug -``` - -### Manual Testing Checklist - -After making changes, test: -- [ ] Form appears in block editor -- [ ] Settings panel works -- [ ] Form renders on frontend -- [ ] Form submission works -- [ ] Entry appears in admin -- [ ] Email notification sent (if configured) -- [ ] No console errors -- [ ] No PHP errors in debug.log - ---- - -## Debugging Tips - -### Enable Debugging - -**WordPress Debug Mode** (wp-config.php): -```php -define('WP_DEBUG', true); // Enable debug mode -define('WP_DEBUG_LOG', true); // Log to wp-content/debug.log -define('WP_DEBUG_DISPLAY', false); // Don't display errors on page -define('SCRIPT_DEBUG', true); // Load unminified JS/CSS -define('SAVEQUERIES', true); // Log database queries -``` - -**Plugin Debug Mode** (wp-config.php): -```php -define('SRFM_DEBUG', true); // Load unminified assets -``` - -### View Logs - -**PHP Errors:** -```bash -tail -f wp-content/debug.log -``` - -**JavaScript Console:** -- Open browser DevTools (F12) -- Console tab shows errors and logs -- Network tab shows AJAX requests - -### Common Issues - -#### Issue: "Class not found" Error -**Cause:** Namespace doesn't match file path -**Fix:** Ensure `SRFM\Inc\Blocks\Input` maps to `inc/blocks/input/class-input.php` - -#### Issue: Blocks Don't Appear in Editor -**Cause:** JavaScript not rebuilt -**Fix:** -```bash -npm run build -# Hard refresh browser (Cmd/Ctrl + Shift + R) -``` - -#### Issue: Form Submission Fails -**Cause:** Nonce expired or invalid -**Fix:** Refresh page to get new nonce - -#### Issue: Styling Not Applied -**Cause:** CSS not compiled or cached -**Fix:** -```bash -npm run build:sass -# Hard refresh browser -``` - -#### Issue: PHP Syntax Error After Edit -**Cause:** Syntax mistake in PHP file -**Fix:** Check debug.log, fix syntax, refresh - ---- - -## Next Steps - -Now that you're set up, here's how to continue learning: - -### 1. Read Core Documentation (2-3 hours) - -**Essential:** -- [CLAUDE.md](../../CLAUDE.md) - AI agent guide with constraints and patterns -- [ARCHITECTURE.md](../02-architecture/ARCHITECTURE.md) - System design overview - -**Important:** -- [CODING-STANDARDS.md](../03-development/CODING-STANDARDS.md) - Code style rules -- [DATABASE-SCHEMA.md](../04-backend/DATABASE-SCHEMA.md) - Data model -- [FRONTEND-GUIDE.md](../05-frontend/FRONTEND-GUIDE.md) - React/Gutenberg patterns - -### 2. Explore the Codebase (2-3 hours) - -**Follow the Request Flow:** - -**Admin Page Load:** -``` -1. WordPress loads: admin.php?page=sureforms_forms -2. admin/admin.php → render_forms_page() -3. Outputs:

    -4. React app mounts from assets/build/forms.js -5. Fetches data from REST API -``` - -**Form Submission:** -``` -1. User fills form → formSubmit.js validates -2. AJAX POST → /wp-json/sureforms/v1/submit-form -3. inc/form-submit.php → handle_form_submission() -4. Validates → Saves to wp_srfm_entries table -5. Sends email notifications -6. Returns success response -``` - -### 3. Study Example Implementations - -**Reference Block:** [inc/blocks/input/](../../inc/blocks/input/) -- See complete block implementation -- PHP class extends Base -- React component structure -- Attribute schema in block.json - -**Reference React App:** [src/admin/dashboard/](../../src/admin/dashboard/) -- React Query for data fetching -- React Router for navigation -- Component structure - -### 4. Make Your First Contribution - -**Choose from "Good First Issue" labels:** -- Add documentation -- Fix typos -- Add unit tests -- Improve error messages -- Add new field setting - -**Contribution Process:** -1. Fork repository -2. Create feature branch: `git checkout -b feat/your-feature` -3. Make changes -4. Run tests and linters -5. Commit with conventional commit message -6. Push and create pull request -7. Address review feedback - ---- - -## Getting Help - -### Documentation -- [CLAUDE.md](../../CLAUDE.md) - AI agent guide -- [docs/](../) - Full documentation -- [README.md](../../README.md) - Plugin overview - -### Community -- **GitHub Issues:** https://github.com/brainstormforce/sureforms/issues -- **Pull Requests:** https://github.com/brainstormforce/sureforms/pulls - -### Code Questions - -**When asking for help, include:** -1. What you're trying to do -2. What you've tried -3. Error messages (browser console + PHP debug.log) -4. Code snippets -5. Environment (WordPress version, PHP version, plugin version) - ---- - -## Development Tools - -### Recommended VS Code Extensions - -- **PHP Intelephense** - PHP intelligence and autocomplete -- **ESLint** - JavaScript linting -- **Prettier** - Code formatting -- **EditorConfig** - Consistent editor config -- **WordPress Snippets** - WordPress function snippets -- **GitLens** - Git integration -- **Auto Rename Tag** - HTML tag renaming -- **Path Intellisense** - File path autocomplete - -### Browser Extensions - -- **React Developer Tools** - Inspect React components -- **Redux DevTools** - Inspect WordPress Data Store state - -### Command Line Tools - -```bash -# Install WP-CLI globally (optional but helpful) -curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -chmod +x wp-cli.phar -sudo mv wp-cli.phar /usr/local/bin/wp - -# Useful WP-CLI commands -wp plugin list -wp post list --post_type=sureforms_form -wp cache flush -``` - ---- - -## Keyboard Shortcuts (Block Editor) - -| Action | Mac | Windows | -|--------|-----|---------| -| Save | Cmd + S | Ctrl + S | -| Add block | / | / | -| Duplicate block | Cmd + Shift + D | Ctrl + Shift + D | -| Delete block | Shift + Alt + Z | Shift + Alt + Z | -| Block settings | Cmd + Shift + , | Ctrl + Shift + , | -| Undo | Cmd + Z | Ctrl + Z | -| Redo | Cmd + Shift + Z | Ctrl + Shift + Z | - ---- - -## Quick Reference - -### Important npm Scripts - -```bash -npm start # Watch mode with hot reload -npm run build # Production build -npm run build:script # JavaScript only -npm run build:sass # CSS only -npm run lint-js # Check JavaScript -npm run lint-js:fix # Fix JavaScript -npm run lint-css # Check CSS -npm run lint-css:fix # Fix CSS -npm test:unit # JavaScript unit tests -npm run test:e2e # Playwright E2E tests -npm run play:up # Start wp-env -npm run play:stop # Stop wp-env -npm run play:down # Destroy wp-env -npm run makepot # Generate translation template -``` - -### Important Composer Scripts - -```bash -composer install # Install dependencies -composer test # Run PHPUnit tests -composer test:coverage # Generate coverage report -composer phpcs # Check coding standards -composer phpcbf # Fix coding standards -composer phpstan # Static analysis -composer phpinsights # Code quality metrics -``` - -### Important File Paths - -| Purpose | Path | -|---------|------| -| Main plugin file | sureforms.php | -| Plugin loader | plugin-loader.php | -| Form submission | inc/form-submit.php | -| REST API | inc/rest-api.php | -| Helper functions | inc/helper.php | -| Block base class | inc/blocks/base.php | -| Database entries | inc/database/tables/entries.php | -| Webpack config | webpack.config.js | -| TailwindCSS config | tailwind.config.js | - ---- - -**Congratulations!** You're now ready to contribute to SureForms. Happy coding! 🚀 - -**Questions?** Check [CLAUDE.md](../../CLAUDE.md) or create an issue on GitHub. diff --git a/docs/04-backend/DATABASE-SCHEMA.md b/docs/04-backend/DATABASE-SCHEMA.md deleted file mode 100644 index 68f5de98f..000000000 --- a/docs/04-backend/DATABASE-SCHEMA.md +++ /dev/null @@ -1,962 +0,0 @@ -# SureForms Database Schema - -> **Purpose:** Document custom database tables, WordPress post types, and data migration strategies -> **Audience:** Backend developers, database administrators -> **Last Updated:** 2026-02-06 -> **Plugin Version:** 2.5.0 - -## Overview - -SureForms uses a **hybrid data storage approach** combining WordPress native tables with custom optimized tables: - -1. **WordPress Custom Post Type (`sureforms_form`)** - Form configurations and structure -2. **Custom Table (`wp_srfm_entries`)** - Form submission data (performance-optimized) -3. **Custom Table (`wp_srfm_payments`)** - Payment transaction tracking - -**Why Custom Tables?** -- **Performance:** Separate from WordPress posts/postmeta tables (millions of rows) -- **Query Optimization:** Custom indexes for form-specific queries -- **Data Integrity:** Structured schema with foreign keys -- **Scalability:** Handle large volumes of form submissions efficiently - ---- - -## Custom Tables - -### Table: `wp_srfm_entries` - -**Purpose:** Store form submission data separate from WordPress posts for optimized query performance. - -**Schema Definition:** [inc/database/tables/entries.php](../../inc/database/tables/entries.php) -**Table Version:** 1 -**Character Set:** utf8mb4_unicode_ci - -#### SQL Schema - -```sql -CREATE TABLE wp_srfm_entries ( - ID BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY, - form_id BIGINT(20) UNSIGNED, - user_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, - form_data LONGTEXT, - logs LONGTEXT, - notes LONGTEXT, - submission_info LONGTEXT, - status VARCHAR(10), - type VARCHAR(20), - extras LONGTEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - - INDEX idx_form_id (form_id), - INDEX idx_user_id (user_id), - INDEX idx_form_id_created_at_status (form_id, created_at, status) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -``` - -#### Column Descriptions - -| Column | Type | Nullable | Default | Description | -|--------|------|----------|---------|-------------| -| `ID` | BIGINT(20) UNSIGNED | No | AUTO_INCREMENT | Primary key, unique entry identifier | -| `form_id` | BIGINT(20) UNSIGNED | No | - | Foreign key to wp_posts.ID (sureforms_form post type) | -| `user_id` | BIGINT(20) UNSIGNED | No | 0 | Foreign key to wp_users.ID (0 for guest submissions) | -| `form_data` | LONGTEXT | Yes | NULL | JSON-encoded submission data (field values) | -| `logs` | LONGTEXT | Yes | NULL | JSON-encoded activity logs array | -| `notes` | LONGTEXT | Yes | NULL | JSON-encoded admin notes array | -| `submission_info` | LONGTEXT | Yes | NULL | JSON-encoded metadata (IP, browser, device, referrer) | -| `status` | VARCHAR(10) | Yes | 'unread' | Entry status: 'unread', 'read', 'trash' | -| `type` | VARCHAR(20) | Yes | NULL | Form type: 'quiz', 'standard', NULL (standard default) | -| `extras` | LONGTEXT | Yes | NULL | JSON-encoded additional data | -| `created_at` | TIMESTAMP | No | CURRENT_TIMESTAMP | Entry creation timestamp | -| `updated_at` | TIMESTAMP | No | CURRENT_TIMESTAMP ON UPDATE | Last modification timestamp | - -#### Indexes - -**Purpose:** Optimize query performance for common access patterns - -| Index Name | Columns | Type | Purpose | -|------------|---------|------|---------| -| PRIMARY | ID | PRIMARY KEY | Unique entry lookup | -| idx_form_id | form_id | INDEX | Filter entries by form | -| idx_user_id | user_id | INDEX | Filter entries by user | -| idx_form_id_created_at_status | form_id, created_at, status | COMPOSITE INDEX | Optimize form dashboard queries with date/status filters | - -**Index Usage Examples:** -```sql --- Uses idx_form_id index -SELECT * FROM wp_srfm_entries WHERE form_id = 123; - --- Uses idx_form_id_created_at_status composite index -SELECT * FROM wp_srfm_entries -WHERE form_id = 123 - AND status = 'unread' - AND created_at > '2026-01-01' -ORDER BY created_at DESC; -``` - -#### JSON Column Structures - -**form_data** (LONGTEXT, JSON): -```json -{ - "field_slug": { - "value": "user input", - "label": "Field Label", - "type": "text" - }, - "email": { - "value": "user@example.com", - "label": "Email Address", - "type": "email" - }, - "phone": { - "value": "+1234567890", - "label": "Phone Number", - "type": "phone" - } -} -``` - -**submission_info** (LONGTEXT, JSON): -```json -{ - "ip_address": "192.168.1.1", - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...", - "browser": "Chrome", - "browser_version": "120.0.0.0", - "device": "Desktop", - "os": "Mac OS X", - "referrer": "https://example.com/landing-page", - "timestamp": 1706800000 -} -``` - -**logs** (LONGTEXT, JSON): -```json -[ - { - "title": "Entry Created", - "messages": ["Entry submitted successfully"], - "timestamp": "2026-02-06 10:30:00" - }, - { - "title": "Email Sent", - "messages": [ - "Notification sent to admin@example.com", - "User confirmation sent to user@example.com" - ], - "timestamp": "2026-02-06 10:30:05" - }, - { - "title": "Integration Triggered", - "messages": ["Data sent to MailChimp", "Subscriber added successfully"], - "timestamp": "2026-02-06 10:30:10" - } -] -``` - -**notes** (LONGTEXT, JSON): -```json -[ - { - "note": "Follow up required", - "author": "admin", - "timestamp": "2026-02-06 12:00:00" - } -] -``` - -**extras** (LONGTEXT, JSON): -```json -{ - "quiz_score": 85, - "quiz_result": "passed", - "conversational_form": true, - "utm_source": "google", - "utm_campaign": "summer_2026" -} -``` - -#### Status Values - -| Status | Description | -|--------|-------------| -| `unread` | New submission, not yet viewed by admin (default) | -| `read` | Admin has viewed the submission | -| `trash` | Soft-deleted, can be restored or permanently deleted | - -#### Type Values - -| Type | Description | -|------|-------------| -| NULL or `standard` | Regular form submission (default) | -| `quiz` | Quiz form submission with score/results | -| Other custom types | Extensible for future form types | - -#### Migration History - -**Version 1:** -- **Renamed Column:** `user_data` → `form_data` (v0.0.13) - - Reason: More descriptive name - - Migration: ALTER TABLE RENAME COLUMN - - Backward compatibility: Old queries updated - -- **Added Columns:** - - `type` VARCHAR(20) (v0.0.13) - - `extras` LONGTEXT (v0.0.13) - - `user_id` BIGINT(20) (v0.0.13) - ---- - -### Table: `wp_srfm_payments` - -**Purpose:** Store payment transaction records for Stripe integration and payment tracking. - -**Schema Definition:** [inc/database/tables/payments.php](../../inc/database/tables/payments.php) -**Table Version:** 1 -**Character Set:** utf8mb4_unicode_ci - -#### SQL Schema - -```sql -CREATE TABLE wp_srfm_payments ( - id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY, - form_id BIGINT(20) UNSIGNED, - block_id VARCHAR(255) NOT NULL, - status VARCHAR(50) NOT NULL, - total_amount DECIMAL(26,8) NOT NULL, - refunded_amount DECIMAL(26,8) NOT NULL, - currency VARCHAR(10) NOT NULL, - entry_id BIGINT(20) UNSIGNED NOT NULL, - gateway VARCHAR(20) NOT NULL, - type VARCHAR(30) NOT NULL, - mode VARCHAR(20) NOT NULL, - transaction_id VARCHAR(50) NOT NULL, - customer_id VARCHAR(50) NOT NULL, - subscription_id VARCHAR(50) NOT NULL, - subscription_status VARCHAR(20) NOT NULL, - parent_subscription_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, - payment_data LONGTEXT, - extra LONGTEXT, - log LONGTEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - srfm_txn_id VARCHAR(100) NOT NULL, - customer_email VARCHAR(255) NOT NULL, - customer_name VARCHAR(255) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -``` - -#### Column Descriptions - -| Column | Type | Nullable | Default | Description | -|--------|------|----------|---------|-------------| -| `id` | BIGINT(20) UNSIGNED | No | AUTO_INCREMENT | Primary key, unique payment identifier | -| `form_id` | BIGINT(20) UNSIGNED | No | - | Foreign key to wp_posts.ID (form) | -| `block_id` | VARCHAR(255) | No | '' | Block client ID (unique identifier for payment field) | -| `status` | VARCHAR(50) | No | 'pending' | Payment status (Stripe-specific) | -| `total_amount` | DECIMAL(26,8) | No | 0.00000000 | Total amount after discount (8 decimal precision) | -| `refunded_amount` | DECIMAL(26,8) | No | 0.00000000 | Total refunded amount | -| `currency` | VARCHAR(10) | No | '' | ISO 4217 currency code (USD, EUR, GBP, etc.) | -| `entry_id` | BIGINT(20) UNSIGNED | No | 0 | Foreign key to wp_srfm_entries.ID | -| `gateway` | VARCHAR(20) | No | '' | Payment gateway ('stripe') | -| `type` | VARCHAR(30) | No | '' | Payment type: 'payment', 'subscription', 'renewal' | -| `mode` | VARCHAR(20) | No | '' | Payment mode: 'test' or 'live' | -| `transaction_id` | VARCHAR(50) | No | '' | Gateway transaction ID (e.g., Stripe pi_xxx) | -| `customer_id` | VARCHAR(50) | No | '' | Gateway customer ID (e.g., Stripe cus_xxx) | -| `subscription_id` | VARCHAR(50) | No | '' | Gateway subscription ID (e.g., Stripe sub_xxx) | -| `subscription_status` | VARCHAR(20) | No | '' | Subscription status ('active', 'canceled', etc.) | -| `parent_subscription_id` | BIGINT(20) UNSIGNED | No | 0 | Parent subscription payment ID (for renewal payments) | -| `payment_data` | LONGTEXT | Yes | NULL | JSON-encoded gateway-specific data | -| `extra` | LONGTEXT | Yes | NULL | JSON-encoded additional metadata | -| `log` | LONGTEXT | Yes | NULL | JSON-encoded payment activity logs | -| `created_at` | TIMESTAMP | No | CURRENT_TIMESTAMP | Payment creation timestamp | -| `updated_at` | TIMESTAMP | No | CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | -| `srfm_txn_id` | VARCHAR(100) | No | '' | SureForms transaction ID (custom format) | -| `customer_email` | VARCHAR(255) | No | '' | Customer email address | -| `customer_name` | VARCHAR(255) | No | '' | Customer full name | - -#### Valid Enum Values - -**Payment Status** (Stripe-specific): -- `pending` - Payment initiated but not completed -- `succeeded` - Payment completed successfully -- `failed` - Payment failed -- `canceled` - Payment canceled by user or admin -- `requires_action` - Requires additional authentication (3D Secure) -- `requires_payment_method` - Payment method needs to be provided -- `processing` - Payment is being processed -- `refunded` - Full refund processed -- `partially_refunded` - Partial refund processed - -**Currency Codes** (ISO 4217): -``` -USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, SEK, NZD, -MXN, SGD, HKD, NOK, TRY, RUB, INR, BRL, ZAR, KRW -``` - -**Payment Gateway:** -- `stripe` (currently only Stripe supported) - -**Payment Mode:** -- `test` - Test/sandbox mode -- `live` - Production mode - -**Subscription Status** (Stripe-specific): -- `active` - Subscription is active -- `canceled` - Subscription canceled -- `past_due` - Payment failed, retrying -- `unpaid` - Payment failed, no retry -- `trialing` - In trial period -- `incomplete` - Initial payment not completed -- `incomplete_expired` - Initial payment window expired -- `paused` - Subscription paused - -#### JSON Column Structures - -**payment_data** (LONGTEXT, JSON): -```json -{ - "stripe_payment_intent": { - "id": "pi_1234567890", - "amount": 4999, - "currency": "usd", - "status": "succeeded", - "created": 1706800000 - }, - "refunds": { - "re_1234567890": { - "refund_id": "re_1234567890", - "amount": 1000, - "status": "succeeded", - "created": 1706900000, - "reason": "requested_by_customer" - } - } -} -``` - -**extra** (LONGTEXT, JSON): -```json -{ - "discount_code": "SUMMER2026", - "discount_amount": 10.00, - "original_amount": 59.99, - "tax_amount": 5.00, - "metadata": { - "utm_source": "google", - "utm_campaign": "summer_sale" - } -} -``` - -**log** (LONGTEXT, JSON): -```json -[ - { - "title": "Payment Initiated", - "message": "Stripe Payment Intent created", - "timestamp": "2026-02-06 10:30:00" - }, - { - "title": "Payment Succeeded", - "message": "Payment completed successfully", - "timestamp": "2026-02-06 10:30:15" - } -] -``` - ---- - -## WordPress Custom Post Type - -### Post Type: `sureforms_form` - -**Purpose:** Store form configurations and structure using WordPress's content management system. - -**Registration:** [inc/post-types.php](../../inc/post-types.php) -**Supports:** title, editor, custom-fields -**Public:** false (admin-only) -**Has Archive:** false -**Rewrite:** false - -#### Post Fields - -| Field | Type | Description | -|-------|------|-------------| -| `post_title` | VARCHAR(200) | Form name (user-editable) | -| `post_content` | LONGTEXT | Serialized Gutenberg blocks (form structure) | -| `post_status` | VARCHAR(20) | 'draft', 'publish', 'trash' | -| `post_type` | VARCHAR(20) | 'sureforms_form' (constant) | -| `post_author` | BIGINT(20) | WordPress user ID who created the form | -| `post_date` | DATETIME | Form creation date | -| `post_modified` | DATETIME | Last modification date | - -#### Post Meta Keys - -Form-specific settings stored in `wp_postmeta`: - -| Meta Key | Description | Data Type | Example | -|----------|-------------|-----------|---------| -| `_srfm_form_settings` | General form settings | Serialized array | [complex settings object] | -| `_srfm_email_notifications` | Email notification rules | Serialized array | Multiple notification configs | -| `_srfm_confirmation_settings` | Post-submission behavior | Serialized array | Message or redirect URL | -| `_srfm_spam_protection` | Anti-spam settings | Serialized array | reCAPTCHA, honeypot config | -| `_srfm_gdpr_settings` | GDPR compliance options | Serialized array | Consent, auto-delete rules | -| `_srfm_styling_settings` | Form visual customization | Serialized array | Colors, fonts, spacing | -| `_srfm_conditional_logic` | Conditional field display | Serialized array | Field visibility rules | -| `_srfm_integrations_webhooks` | Third-party integrations | Serialized array | MailChimp, webhooks | -| `_srfm_instant_form_settings` | Instant form configuration | Serialized array | AI-generated form data | -| `_srfm_conversational_form` | Conversational mode settings | Serialized array | Step-by-step form config | -| `_srfm_premium_common` | Pro features settings | Serialized array | Pro-only configurations | -| `_srfm_user_registration_settings` | User registration integration | Serialized array | Create user on submission | - -#### Block Structure (post_content) - -Forms stored as serialized Gutenberg blocks (HTML comments): - -```html - - - - - - - - - -``` - ---- - -## Database Schema Migrations - -### Version Control System - -**Implementation:** Each table class maintains a `$table_version` property. - -**Version Storage:** WordPress options table -``` -Option Name: srfm_entries_version -Option Value: 1 (integer) - -Option Name: srfm_payments_version -Option Value: 1 (integer) -``` - -### Migration Flow - -``` -1. Plugin loads → Database\Register::init() -2. For each table: - a. Get stored version from wp_options - b. Compare with code version ($table_version) - c. If mismatch: run upgrade() method - d. Execute schema changes - e. Update version in wp_options -``` - -### Migration Methods - -**Base Class Methods** ([inc/database/base.php](../../inc/database/base.php)): - -| Method | Purpose | -|--------|---------| -| `maybe_create_table()` | Create table if not exists | -| `maybe_add_new_columns()` | Add new columns to existing table | -| `maybe_rename_columns()` | Rename columns (e.g., user_data → form_data) | -| `upgrade($old_version)` | Custom migration logic per version | - -### Adding a New Column (Example) - -**Step 1:** Increment table version in table class -```php -// In inc/database/tables/entries.php -protected $table_version = 2; // Increment from 1 to 2 -``` - -**Step 2:** Add column to schema -```php -public function get_schema() { - return [ - // ... existing columns - 'new_column' => [ - 'type' => 'string', - 'default' => '', - ], - ]; -} -``` - -**Step 3:** Add column definition -```php -public function get_new_columns_definition() { - return [ - 'new_column VARCHAR(255) DEFAULT "" AFTER status', - ]; -} -``` - -**Step 4:** Implement upgrade logic (if needed) -```php -public function upgrade($old_version) { - global $wpdb; - - if ($old_version < 2) { - // Additional migration logic beyond column addition - $wpdb->query("UPDATE {$this->table_name} SET new_column = 'default_value'"); - } -} -``` - -### Migration Best Practices - -1. **Never Delete Columns** - Mark as deprecated instead - ```php - // DON'T: ALTER TABLE DROP COLUMN old_column - // DO: Leave column, document as deprecated - ``` - -2. **Always Provide Defaults** - Avoid NULL for new columns - ```php - 'new_column VARCHAR(255) DEFAULT ""' // Good - 'new_column VARCHAR(255)' // Bad (no default) - ``` - -3. **Test with Production Data** - Use staging environment first -4. **Log All Migrations** - Use error_log() for debugging -5. **Backwards Compatible** - Old code must work during migration -6. **Atomic Operations** - Use transactions where possible - ---- - -## Common Query Patterns - -### Get All Entries for a Form - -```php -use SRFM\Inc\Database\Tables\Entries; - -$entries = Entries::get_all([ - 'where' => [ - [ - ['key' => 'form_id', 'compare' => '=', 'value' => 123], - ['key' => 'status', 'compare' => '!=', 'value' => 'trash'], - ] - ], - 'orderby' => 'created_at', - 'order' => 'DESC', - 'limit' => 20, - 'offset' => 0 -]); -``` - -**Generated SQL:** -```sql -SELECT * FROM wp_srfm_entries -WHERE form_id = 123 - AND status != 'trash' -ORDER BY created_at DESC -LIMIT 0, 20 -``` - -### Get Entry Count by Status - -```php -$unread_count = Entries::get_total_entries_by_status('unread', $form_id); -$all_count = Entries::get_total_entries_by_status('all', $form_id); -``` - -**Generated SQL:** -```sql --- Unread count -SELECT COUNT(*) FROM wp_srfm_entries -WHERE status = 'unread' AND form_id = 123 - --- All count (excluding trash) -SELECT COUNT(*) FROM wp_srfm_entries -WHERE status != 'trash' AND form_id = 123 -``` - -### Get Entries with Payments - -```php -global $wpdb; - -$entries_with_payments = $wpdb->get_results($wpdb->prepare( - "SELECT e.*, p.total_amount, p.status as payment_status, p.currency - FROM {$wpdb->prefix}srfm_entries e - INNER JOIN {$wpdb->prefix}srfm_payments p ON e.ID = p.entry_id - WHERE e.form_id = %d - AND p.status = 'succeeded' - ORDER BY e.created_at DESC", - $form_id -)); -``` - -### Get Payments by Status - -```php -use SRFM\Inc\Database\Tables\Payments; - -$succeeded_payments = Payments::get_all([ - 'where' => [ - [ - ['key' => 'form_id', 'compare' => '=', 'value' => 123], - ['key' => 'status', 'compare' => '=', 'value' => 'succeeded'], - ] - ], - 'orderby' => 'created_at', - 'order' => 'DESC' -]); -``` - ---- - -## Performance Optimization - -### Indexing Strategy - -**Why Indexes Matter:** -- 10,000+ entries: ~50ms query time with indexes vs ~500ms without -- Composite indexes optimize multi-column WHERE clauses -- Index order matters: (form_id, created_at, status) ≠ (status, form_id, created_at) - -**Index Usage Guidelines:** - -1. **Use Indexed Columns in WHERE Clauses** -```php -// GOOD - Uses idx_form_id index -$entries = Entries::get_all(['where' => [[['key' => 'form_id', 'compare' => '=', 'value' => 123]]]]); - -// BAD - No index on 'type' column (full table scan) -$entries = Entries::get_all(['where' => [[['key' => 'type', 'compare' => '=', 'value' => 'quiz']]]]); -``` - -2. **Leverage Composite Indexes** -```php -// GOOD - Uses idx_form_id_created_at_status composite index -$entries = Entries::get_all([ - 'where' => [ - [ - ['key' => 'form_id', 'compare' => '=', 'value' => 123], - ['key' => 'created_at', 'compare' => '>', 'value' => '2026-01-01'], - ['key' => 'status', 'compare' => '=', 'value' => 'unread'], - ] - ] -]); -``` - -3. **Avoid SELECT *** -```php -// GOOD - Specify columns -$entries = Entries::get_instance()->get_results( - ['form_id' => 123], - 'ID, form_data, created_at' -); - -// BAD - Selects all columns including large LONGTEXT -$entries = Entries::get_all(['where' => [[['key' => 'form_id', 'compare' => '=', 'value' => 123]]]]); -``` - -### Pagination Best Practices - -```php -// Paginate large result sets -$per_page = 20; -$page = 2; -$offset = ($page - 1) * $per_page; - -$entries = Entries::get_all([ - 'where' => [[['key' => 'form_id', 'compare' => '=', 'value' => 123]]], - 'limit' => $per_page, - 'offset' => $offset, - 'orderby' => 'created_at', - 'order' => 'DESC' -]); - -// Cache total count separately -$total = Entries::get_total_entries_by_status('all', 123); -$total_pages = ceil($total / $per_page); -``` - -### JSON Column Optimization - -**MySQL 5.7+ JSON Functions:** -```sql --- Extract specific field from form_data JSON -SELECT - ID, - JSON_EXTRACT(form_data, '$.email.value') as email -FROM wp_srfm_entries -WHERE form_id = 123; - --- Search within JSON -SELECT * FROM wp_srfm_entries -WHERE JSON_EXTRACT(form_data, '$.email.value') = 'user@example.com'; -``` - -**Performance Warning:** -- JSON queries are slower than indexed columns -- Avoid wildcard searches in JSON (`LIKE '%value%'`) -- Consider extracting frequently-queried JSON fields to dedicated columns - ---- - -## Data Retention & GDPR - -### Auto-Delete Configuration - -**Setting Location:** [inc/single-form-settings/compliance-settings.php](../../inc/single-form-settings/compliance-settings.php) - -**Per-Form Settings:** -```php -$retention_days = get_post_meta($form_id, '_srfm_entry_retention_days', true); -// Values: 0 (never delete), 7, 30, 60, 90, 365 (days) -``` - -**Implementation:** WordPress Cron (Action Scheduler) - -```php -// Scheduled task runs daily at 2 AM -add_action('srfm_daily_scheduled_action', 'srfm_auto_delete_entries'); - -function srfm_auto_delete_entries() { - global $wpdb; - - // Get forms with auto-delete enabled - $forms = $wpdb->get_results(" - SELECT post_id, meta_value as retention_days - FROM {$wpdb->postmeta} - WHERE meta_key = '_srfm_entry_retention_days' - AND meta_value > 0 - "); - - foreach ($forms as $form) { - $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$form->retention_days} days")); - - // Delete entries older than cutoff - $wpdb->delete( - $wpdb->prefix . 'srfm_entries', - [ - 'form_id' => $form->post_id, - 'created_at < ' => $cutoff_date - ] - ); - } -} -``` - -### Manual Entry Management - -**Soft Delete (Trash):** -```php -Entries::update($entry_id, ['status' => 'trash']); -``` - -**Permanent Delete:** -```php -// Fires srfm_before_delete_entry hook -Entries::delete($entry_id); -``` - -**Restore from Trash:** -```php -Entries::update($entry_id, ['status' => 'unread']); -``` - ---- - -## Backup & Recovery - -### Backup Recommendations - -**1. Full Database Backup** (before plugin updates) -```bash -mysqldump -u username -p database_name > sureforms_backup.sql -``` - -**2. Table-Specific Dumps** -```bash -# Entries table -mysqldump -u username -p database_name wp_srfm_entries > entries_backup.sql - -# Payments table -mysqldump -u username -p database_name wp_srfm_payments > payments_backup.sql - -# Form configurations (WordPress tables) -mysqldump -u username -p database_name wp_posts wp_postmeta \ - --where="post_type='sureforms_form'" > forms_backup.sql -``` - -**3. Export Entries via Admin UI** -- Navigate to SureForms → Entries -- Select entries → Export CSV -- Includes all form data and metadata - -### Restore Process - -```bash -# Restore from backup -mysql -u username -p database_name < entries_backup.sql - -# Restore specific table -mysql -u username -p database_name < wp_srfm_entries.sql -``` - ---- - -## Troubleshooting - -### Issue: Missing Tables - -**Symptom:** "Table doesn't exist" errors in PHP logs -**Cause:** Tables not created during activation -**Fix:** -1. Deactivate plugin -2. Delete option: `srfm_entries_version`, `srfm_payments_version` -3. Reactivate plugin (triggers table creation) - -### Issue: Charset/Collation Errors - -**Symptom:** Garbled text in submissions (emoji, special characters) -**Cause:** Incorrect charset (not utf8mb4) -**Fix:** -```sql -ALTER TABLE wp_srfm_entries -CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - -ALTER TABLE wp_srfm_payments -CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -``` - -### Issue: JSON Parsing Errors - -**Symptom:** "Invalid JSON" errors when retrieving form_data -**Cause:** Unescaped quotes in field values -**Fix:** -```php -// Always use wp_json_encode() for JSON storage -$form_data_json = wp_json_encode($form_data); - -// Always use json_decode() with error handling -$form_data = json_decode($form_data_json, true); -if (json_last_error() !== JSON_ERROR_NONE) { - error_log('JSON decode error: ' . json_last_error_msg()); -} -``` - -### Issue: Slow Queries - -**Symptom:** Dashboard loads slowly with many entries -**Cause:** Missing indexes or inefficient queries -**Debug:** -```sql --- Check query execution plan -EXPLAIN SELECT * FROM wp_srfm_entries -WHERE form_id = 123 - AND status = 'unread' -ORDER BY created_at DESC -LIMIT 20; - --- Should show "Using index" in Extra column -``` - -**Fix:** -1. Ensure indexes exist (check `SHOW INDEX FROM wp_srfm_entries`) -2. Use indexed columns in WHERE clauses -3. Add pagination (LIMIT/OFFSET) - ---- - -## Entity Relationship Diagram - -```mermaid -erDiagram - wp_posts ||--o{ wp_srfm_entries : "has many" - wp_posts ||--o{ wp_srfm_payments : "has many" - wp_srfm_entries ||--o| wp_srfm_payments : "may have" - wp_users ||--o{ wp_srfm_entries : "submits" - - wp_posts { - bigint ID PK - varchar post_type "sureforms_form" - longtext post_content "Gutenberg blocks" - varchar post_status "draft|publish|trash" - } - - wp_srfm_entries { - bigint ID PK - bigint form_id FK - bigint user_id FK - longtext form_data "JSON" - varchar status "unread|read|trash" - timestamp created_at - } - - wp_srfm_payments { - bigint id PK - bigint form_id FK - bigint entry_id FK - varchar status "pending|succeeded|failed" - decimal total_amount - varchar currency - varchar transaction_id - timestamp created_at - } - - wp_users { - bigint ID PK - varchar user_email - } -``` - ---- - -## Summary - -**Storage Strategy:** -- **Forms:** WordPress custom post type (native CMS benefits) -- **Entries:** Custom table (performance optimization) -- **Payments:** Custom table (financial data tracking) - -**Key Design Decisions:** -- Custom tables for scalability (handle millions of entries) -- JSON columns for flexible schema (form fields vary per form) -- Composite indexes for query optimization -- Version-controlled migrations for safe schema updates - -**Maintenance:** -- Regular backups before plugin updates -- Monitor table sizes (OPTIMIZE TABLE if needed) -- Review slow query logs -- Test migrations on staging first - ---- - -**Related Documentation:** -- [CLAUDE.md](../../CLAUDE.md) - AI agent guide -- [ARCHITECTURE.md](../02-architecture/ARCHITECTURE.md) - System design -- [CODING-STANDARDS.md](../03-development/CODING-STANDARDS.md) - Code style -- [SECURITY.md](../07-security/SECURITY.md) - Security practices diff --git a/docs/04-backend/HOOKS-REFERENCE.md b/docs/04-backend/HOOKS-REFERENCE.md deleted file mode 100644 index 35b5c4c12..000000000 --- a/docs/04-backend/HOOKS-REFERENCE.md +++ /dev/null @@ -1,1055 +0,0 @@ -# SureForms Hooks Reference - -> **Purpose:** Complete reference of all WordPress actions and filters for extending SureForms -> **Audience:** Plugin developers, theme developers, integrators -> **Last Updated:** 2026-02-06 -> **Plugin Version:** 2.5.0 - -## Overview - -SureForms provides **450+ hooks** (actions and filters) for extending functionality without modifying core files. This document catalogs the most commonly used hooks with usage examples. - -**Hook Naming Convention:** -- **Prefix:** All hooks start with `srfm_` -- **Format:** `srfm_{context}_{action}` or `srfm_{data_type}_{modification}` -- **Examples:** `srfm_before_submission`, `srfm_field_validation`, `srfm_email_content` - ---- - -## Table of Contents - -- [Plugin Lifecycle](#plugin-lifecycle) -- [Form Submission](#form-submission) -- [Field Validation](#field-validation) -- [Email Notifications](#email-notifications) -- [Form Rendering](#form-rendering) -- [Data Modification](#data-modification) -- [Database Operations](#database-operations) -- [Payment Processing](#payment-processing) -- [Block Registration](#block-registration) -- [Admin Operations](#admin-operations) -- [Integration Points](#integration-points) - ---- - -## Plugin Lifecycle - -### `srfm_core_loaded` - -Fires after the plugin core is fully loaded and initialized. - -**Location:** [plugin-loader.php](../../plugin-loader.php) -**Since:** 0.0.1 -**Parameters:** None - -**Usage:** -```php -add_action('srfm_core_loaded', function() { - // Plugin is ready, initialize custom extensions - error_log('SureForms plugin loaded'); - - // Register custom integrations - my_custom_sureforms_integration(); -}); -``` - -### `srfm_init` - -Fires during WordPress `init` hook after SureForms initialization. - -**Location:** [plugin-loader.php](../../plugin-loader.php) -**Since:** 0.0.1 -**Parameters:** None - -**Usage:** -```php -add_action('srfm_init', function() { - // Register custom post types, taxonomies, or other WordPress entities - register_post_type('custom_form_data', [ - 'public' => false, - 'supports' => ['title'] - ]); -}); -``` - ---- - -## Form Submission - -### `srfm_before_submission` - -Fires immediately before form data is processed and saved to database. Use to validate or modify data before processing. - -**Location:** [inc/form-submit.php:525](../../inc/form-submit.php) -**Since:** 0.0.1 -**Parameters:** -- `array $form_data` - Submitted form data including form_id, fields, and metadata - -**Usage:** -```php -add_action('srfm_before_submission', function($form_data) { - $form_id = $form_data['form_id'] ?? 0; - - // Log submission attempt - error_log(sprintf('Form %d submission started', $form_id)); - - // Perform custom validation - if ($form_id === 123) { - $email = $form_data['email']['value'] ?? ''; - if (strpos($email, '@company.com') === false) { - // Throw exception to halt submission - throw new Exception('Only company emails allowed'); - } - } -}, 10, 1); -``` - -### `srfm_after_submission_process` - -Fires after form entry is saved to database but before email notifications are sent. - -**Location:** [inc/form-submit.php:556](../../inc/form-submit.php) -**Since:** 0.0.1 -**Parameters:** -- `array $form_data` - Complete form data including entry_id - -**Usage:** -```php -add_action('srfm_after_submission_process', function($form_data) { - $entry_id = $form_data['entry_id'] ?? 0; - $form_id = $form_data['form_id'] ?? 0; - - // Trigger third-party integration - if ($form_id === 123) { - $email = $form_data['email']['value'] ?? ''; - $name = $form_data['name']['value'] ?? ''; - - // Add to MailChimp - mailchimp_add_subscriber($email, $name); - - // Send to CRM - crm_create_lead([ - 'email' => $email, - 'name' => $name, - 'source' => 'sureforms' - ]); - } -}, 10, 1); -``` - -### `srfm_form_submit` - -Fires after complete form submission process (entry saved, emails sent). - -**Location:** [inc/form-submit.php:548,644](../../inc/form-submit.php) -**Since:** 0.0.1 -**Parameters:** -- `array $response` - Submission response with entry_id, success status, message - -**Usage:** -```php -add_action('srfm_form_submit', function($response) { - if ($response['success']) { - $entry_id = $response['entry_id']; - - // Custom logging - error_log(sprintf('Form submission %d completed', $entry_id)); - - // Update custom database - global $wpdb; - $wpdb->insert('wp_custom_leads', [ - 'sureforms_entry_id' => $entry_id, - 'created_at' => current_time('mysql') - ]); - } -}, 10, 1); -``` - ---- - -## Field Validation - -### `srfm_field_validation` - -Filters field validation result. Return `WP_Error` to fail validation. - -**Location:** [inc/field-validation.php](../../inc/field-validation.php) -**Since:** 0.0.1 -**Parameters:** -- `bool|WP_Error $is_valid` - Current validation status -- `mixed $field_value` - Field value to validate -- `array $field_data` - Field metadata (type, label, required, etc.) -- `int $form_id` - Form post ID - -**Return:** `bool|WP_Error` - True if valid, WP_Error if invalid - -**Usage:** -```php -add_filter('srfm_field_validation', function($is_valid, $field_value, $field_data, $form_id) { - // Custom validation for specific field type - if ($field_data['type'] === 'text' && $field_data['slug'] === 'phone') { - // Validate phone format - if (!preg_match('/^\+?[1-9]\d{1,14}$/', $field_value)) { - return new WP_Error( - 'invalid_phone', - __('Please enter a valid phone number', 'your-plugin') - ); - } - } - - // Custom validation for specific form - if ($form_id === 123 && $field_data['slug'] === 'age') { - if (intval($field_value) < 18) { - return new WP_Error( - 'age_restriction', - __('You must be 18 or older', 'your-plugin') - ); - } - } - - return $is_valid; -}, 10, 4); -``` - -### `srfm_validate_{field_type}` - -Filters validation for specific field type. - -**Location:** [inc/field-validation.php](../../inc/field-validation.php) -**Since:** Various -**Available Types:** `email`, `phone`, `url`, `number`, `text`, etc. - -**Usage:** -```php -// Custom email validation -add_filter('srfm_validate_email', function($is_valid, $value, $field_data) { - // Block disposable email domains - $disposable_domains = ['tempmail.com', 'throwaway.email']; - $domain = substr(strrchr($value, '@'), 1); - - if (in_array($domain, $disposable_domains)) { - return new WP_Error( - 'disposable_email', - __('Disposable email addresses are not allowed', 'your-plugin') - ); - } - - return $is_valid; -}, 10, 3); -``` - ---- - -## Email Notifications - -### `srfm_email_notification_should_send` - -Filters whether email notification should be sent. - -**Location:** [inc/form-submit.php:796](../../inc/form-submit.php) -**Since:** Various -**Parameters:** -- `array $email_notification` - Email configuration -- `array $submission_data` - Submitted form data -- `array $form_data` - Complete form metadata - -**Return:** `array|false` - Email config to send, false to skip - -**Usage:** -```php -add_filter('srfm_email_notification_should_send', function($email_notification, $submission_data, $form_data) { - $form_id = $form_data['form_id'] ?? 0; - - // Only send email for specific form if user opted in - if ($form_id === 123) { - $opted_in = $submission_data['email_optin']['value'] ?? false; - if (!$opted_in) { - return false; // Skip email - } - } - - return $email_notification; -}, 10, 3); -``` - -### `srfm_email_notification` - -Filters parsed email data before sending. - -**Location:** [inc/form-submit.php:809](../../inc/form-submit.php) -**Since:** Various -**Parameters:** -- `array $parsed` - Parsed email data (to, subject, message, headers) -- `array $submission_data` - Submitted form data -- `array $item` - Email notification configuration -- `array $form_data` - Form metadata - -**Return:** `array` - Modified email data - -**Usage:** -```php -add_filter('srfm_email_notification', function($parsed, $submission_data, $item, $form_data) { - // Add custom header - $parsed['headers'][] = 'X-Form-ID: ' . ($form_data['form_id'] ?? 0); - - // Modify subject based on submission data - if (isset($submission_data['priority']['value']) && $submission_data['priority']['value'] === 'high') { - $parsed['subject'] = '[URGENT] ' . $parsed['subject']; - } - - // Add custom footer to message - $parsed['message'] .= "\n\n---\nSubmitted via SureForms"; - - return $parsed; -}, 10, 4); -``` - -### `srfm_before_email_send` - -Action fired immediately before email is sent via `wp_mail()`. - -**Location:** [inc/form-submit.php:812](../../inc/form-submit.php) -**Since:** Various -**Parameters:** -- `array $parsed` - Email data (to, subject, message, headers) -- `array $submission_data` - Submitted form data -- `array $item` - Email notification configuration -- `array $form_data` - Form metadata - -**Usage:** -```php -add_action('srfm_before_email_send', function($parsed, $submission_data, $item, $form_data) { - // Log email send attempt - error_log(sprintf( - 'Sending email to %s for form %d', - $parsed['to'], - $form_data['form_id'] ?? 0 - )); - - // Save email to custom table for tracking - global $wpdb; - $wpdb->insert('wp_email_log', [ - 'recipient' => $parsed['to'], - 'subject' => $parsed['subject'], - 'sent_at' => current_time('mysql') - ]); -}, 10, 4); -``` - -### `srfm_after_email_send` - -Action fired after email is sent. - -**Location:** [inc/form-submit.php:904-905](../../inc/form-submit.php) -**Since:** Various -**Parameters:** -- `array $parsed` - Email data -- `array $submission_data` - Form data -- `array $item` - Email config -- `array $form_data` - Form metadata -- `bool $sent` - Whether email was sent successfully - -**Usage:** -```php -add_action('srfm_after_email_send', function($parsed, $submission_data, $item, $form_data, $sent) { - if (!$sent) { - // Email failed to send - error_log(sprintf( - 'Email failed for form %d to %s', - $form_data['form_id'] ?? 0, - $parsed['to'] - )); - - // Send alert to admin - wp_mail( - get_option('admin_email'), - 'Form email notification failed', - sprintf('Failed to send notification for form %d', $form_data['form_id']) - ); - } -}, 10, 5); -``` - ---- - -## Data Modification - -### `srfm_form_submit_data` - -Filters complete form submission data before processing. - -**Location:** [inc/form-submit.php:474](../../inc/form-submit.php) -**Since:** 0.0.1 -**Parameters:** -- `array $form_data` - Complete form data - -**Return:** `array` - Modified form data - -**Usage:** -```php -add_filter('srfm_form_submit_data', function($form_data) { - // Add custom calculated field - if (isset($form_data['price'], $form_data['quantity'])) { - $form_data['total'] = [ - 'value' => $form_data['price']['value'] * $form_data['quantity']['value'], - 'label' => 'Total Amount', - 'type' => 'calculated' - ]; - } - - // Add timestamp - $form_data['submitted_timestamp'] = [ - 'value' => current_time('mysql'), - 'label' => 'Submission Time', - 'type' => 'hidden' - ]; - - return $form_data; -}, 10, 1); -``` - -### `srfm_before_entry_data` - -Filters entry data before saving to database. - -**Location:** [inc/form-submit.php:605-606](../../inc/form-submit.php) -**Since:** 0.0.1 -**Parameters:** -- `array $entries_data` - Entry data to be saved -- `int $form_id` - Form post ID - -**Return:** `array` - Modified entry data - -**Usage:** -```php -add_filter('srfm_before_entry_data', function($entries_data, $form_id) { - // Add custom extras data - $entries_data['extras'] = [ - 'ip_country' => get_user_ip_country(), - 'utm_source' => $_GET['utm_source'] ?? '', - 'utm_campaign' => $_GET['utm_campaign'] ?? '', - 'referrer' => wp_get_referer() - ]; - - // Modify status based on conditions - if ($form_id === 123) { - $entries_data['status'] = 'pending_review'; // Custom status - } - - return $entries_data; -}, 10, 2); -``` - -### `srfm_form_submission_response` - -Filters final submission response sent to frontend. - -**Location:** [inc/form-submit.php:660](../../inc/form-submit.php) -**Since:** 2.4.0 -**Parameters:** -- `array $response` - Response data (success, message, entry_id) -- `array $form_data` - Form data -- `array $submission_data` - Submission data - -**Return:** `array` - Modified response - -**Usage:** -```php -add_filter('srfm_form_submission_response', function($response, $form_data, $submission_data) { - // Add custom data to response - if ($response['success']) { - $response['custom_redirect_url'] = 'https://example.com/thank-you'; - $response['tracking_code'] = 'FB-' . $response['entry_id']; - - // Add custom message - $response['custom_message'] = sprintf( - 'Thank you! Your reference number is %d', - $response['entry_id'] - ); - } - - return $response; -}, 10, 3); -``` - -### `srfm_process_field_value` - -Filters individual field value during processing. - -**Location:** [inc/form-submit.php:1211-1212](../../inc/form-submit.php) -**Since:** 0.0.1 -**Parameters:** -- `mixed $value` - Field value -- `array $field` - Field data -- `array $form_data` - Complete form data - -**Return:** `mixed` - Modified field value - -**Usage:** -```php -add_filter('srfm_process_field_value', function($value, $field, $form_data) { - // Normalize phone numbers - if ($field['type'] === 'phone') { - $value = preg_replace('/[^0-9+]/', '', $value); - } - - // Convert text to uppercase for specific field - if ($field['slug'] === 'reference_code') { - $value = strtoupper($value); - } - - // Encrypt sensitive data - if ($field['slug'] === 'ssn') { - $value = encrypt_data($value); - } - - return $value; -}, 10, 3); -``` - ---- - -## Form Rendering - -### `srfm_form_markup` - -Filters complete form HTML before rendering. - -**Location:** [inc/generate-form-markup.php](../../inc/generate-form-markup.php) -**Since:** 0.0.1 -**Parameters:** -- `string $markup` - Generated HTML -- `int $form_id` - Form post ID -- `array $blocks` - Gutenberg blocks array - -**Return:** `string` - Modified HTML - -**Usage:** -```php -add_filter('srfm_form_markup', function($markup, $form_id, $blocks) { - // Add custom wrapper - $markup = '
    ' . $markup . '
    '; - - // Add custom analytics tracking - $markup .= sprintf( - '', - $form_id - ); - - return $markup; -}, 10, 3); -``` - -### `srfm_field_markup` - -Filters individual field HTML. - -**Location:** [inc/fields/base.php](../../inc/fields/base.php) -**Since:** 0.0.1 -**Parameters:** -- `string $markup` - Field HTML -- `array $attributes` - Block attributes -- `string $field_type` - Field type (input, email, etc.) - -**Return:** `string` - Modified HTML - -**Usage:** -```php -add_filter('srfm_field_markup', function($markup, $attributes, $field_type) { - // Add custom class to all email fields - if ($field_type === 'email') { - $markup = str_replace( - 'class="srfm-field', - 'class="srfm-field custom-email-field', - $markup - ); - } - - // Add tooltips - if (!empty($attributes['help_text'])) { - $markup .= sprintf( - '%s', - esc_html($attributes['help_text']) - ); - } - - return $markup; -}, 10, 3); -``` - -### `srfm_form_class` - -Filters CSS classes applied to form element. - -**Location:** [inc/generate-form-markup.php](../../inc/generate-form-markup.php) -**Since:** 0.0.1 -**Parameters:** -- `array $classes` - CSS class array -- `int $form_id` - Form post ID - -**Return:** `array` - Modified classes - -**Usage:** -```php -add_filter('srfm_form_class', function($classes, $form_id) { - // Add custom class - $classes[] = 'custom-form'; - - // Add class based on form ID - $classes[] = 'form-' . $form_id; - - // Add class based on form type - $form_type = get_post_meta($form_id, '_srfm_form_type', true); - if ($form_type === 'contact') { - $classes[] = 'contact-form'; - } - - return $classes; -}, 10, 2); -``` - ---- - -## Database Operations - -### `srfm_before_delete_entry` - -Action fired before entry is permanently deleted from database. - -**Location:** [inc/database/tables/entries.php:297](../../inc/database/tables/entries.php) -**Since:** 0.0.13 -**Parameters:** -- `int $entry_id` - Entry ID being deleted - -**Usage:** -```php -add_action('srfm_before_delete_entry', function($entry_id) { - // Log deletion - error_log(sprintf('Deleting entry %d', $entry_id)); - - // Clean up related data - global $wpdb; - $wpdb->delete('wp_custom_entry_meta', ['entry_id' => $entry_id]); - - // Archive entry before deletion - $entry = SRFM\Inc\Database\Tables\Entries::get($entry_id); - if ($entry) { - $wpdb->insert('wp_deleted_entries_archive', [ - 'entry_id' => $entry_id, - 'form_data' => wp_json_encode($entry), - 'deleted_at' => current_time('mysql') - ]); - } -}, 10, 1); -``` - ---- - -## Payment Processing - -### `srfm_before_payment_process` - -Action fired before payment processing starts. - -**Location:** [inc/payments/front-end.php](../../inc/payments/front-end.php) -**Since:** 2.0.0 -**Parameters:** -- `array $payment_data` - Payment data -- `int $form_id` - Form post ID - -**Usage:** -```php -add_action('srfm_before_payment_process', function($payment_data, $form_id) { - // Log payment attempt - error_log(sprintf( - 'Processing payment of %s %s for form %d', - $payment_data['amount'], - $payment_data['currency'], - $form_id - )); - - // Verify inventory before charging - if ($form_id === 123) { - $product_id = $payment_data['product_id'] ?? 0; - if (!check_inventory($product_id)) { - throw new Exception('Product out of stock'); - } - } -}, 10, 2); -``` - -### `srfm_after_payment_success` - -Action fired after successful payment. - -**Location:** [inc/payments/front-end.php](../../inc/payments/front-end.php) -**Since:** 2.0.0 -**Parameters:** -- `int $payment_id` - Payment record ID -- `array $payment_data` - Payment data -- `int $entry_id` - Entry ID - -**Usage:** -```php -add_action('srfm_after_payment_success', function($payment_id, $payment_data, $entry_id) { - // Send receipt - $customer_email = $payment_data['customer_email']; - wp_mail( - $customer_email, - 'Payment Receipt', - sprintf('Payment of %s received', $payment_data['total_amount']) - ); - - // Update inventory - update_inventory($payment_data['product_id'], -1); - - // Trigger fulfillment - trigger_order_fulfillment($payment_id); -}, 10, 3); -``` - ---- - -## Block Registration - -### `srfm_block_registration_args` - -Filters block registration arguments. - -**Location:** [inc/blocks/register.php](../../inc/blocks/register.php) -**Since:** 0.0.1 -**Parameters:** -- `array $args` - Block registration args -- `string $block_name` - Block name (sureforms/input, etc.) - -**Return:** `array` - Modified args - -**Usage:** -```php -add_filter('srfm_block_registration_args', function($args, $block_name) { - // Modify render callback - if ($block_name === 'sureforms/input') { - $original_callback = $args['render_callback']; - $args['render_callback'] = function($attributes, $content) use ($original_callback) { - // Wrap output in custom div - $output = call_user_func($original_callback, $attributes, $content); - return '
    ' . $output . '
    '; - }; - } - - return $args; -}, 10, 2); -``` - ---- - -## Admin Operations - -### `srfm_after_form_created` - -Action fired after new form is created. - -**Location:** [inc/create-new-form.php](../../inc/create-new-form.php) -**Since:** 0.0.1 -**Parameters:** -- `int $form_id` - Created form post ID - -**Usage:** -```php -add_action('srfm_after_form_created', function($form_id) { - // Set default form settings - update_post_meta($form_id, '_srfm_custom_setting', 'default_value'); - - // Log form creation - error_log(sprintf('New form created: %d', $form_id)); - - // Notify admin - wp_mail( - get_option('admin_email'), - 'New form created', - sprintf('Form ID %d was created', $form_id) - ); -}, 10, 1); -``` - ---- - -## Form Restrictions - -### `srfm_form_restriction_message` - -Filters restriction message shown to users. - -**Location:** [inc/form-submit.php:267](../../inc/form-submit.php) -**Since:** Various -**Parameters:** -- `string $message` - Default restriction message -- `int $form_id` - Form post ID -- `array $form_restriction` - Restriction settings - -**Return:** `string` - Modified message - -**Usage:** -```php -add_filter('srfm_form_restriction_message', function($message, $form_id, $form_restriction) { - // Custom message based on restriction type - if ($form_restriction['type'] === 'schedule' && $form_restriction['state'] === 'not_started') { - $message = sprintf( - 'This form will open on %s', - date('F j, Y', strtotime($form_restriction['start_date'])) - ); - } - - return $message; -}, 10, 3); -``` - -### `srfm_additional_restriction_check` - -Filters whether form has additional restrictions beyond scheduling. - -**Location:** [inc/form-submit.php:276](../../inc/form-submit.php) -**Since:** Various -**Parameters:** -- `bool $is_restricted` - Whether form is restricted -- `int $form_id` - Form post ID -- `array $form_data` - Form data - -**Return:** `bool` - True to restrict, false to allow - -**Usage:** -```php -add_filter('srfm_additional_restriction_check', function($is_restricted, $form_id, $form_data) { - // Restrict based on user role - if ($form_id === 123 && !current_user_can('subscriber')) { - return true; // Restrict - } - - // Restrict based on IP - $user_ip = $_SERVER['REMOTE_ADDR']; - if (is_ip_blocked($user_ip)) { - return true; - } - - // Restrict based on previous submissions - $user_email = $form_data['email']['value'] ?? ''; - if ($user_email && has_recent_submission($form_id, $user_email)) { - return true; // Already submitted recently - } - - return $is_restricted; -}, 10, 3); -``` - ---- - -## Integration Points - -### Third-Party Service Integration Example - -```php -/** - * Complete integration example: Send form data to CRM after submission - */ -class SureForms_CRM_Integration { - public function __construct() { - add_action('srfm_after_submission_process', [$this, 'send_to_crm'], 10, 1); - add_action('srfm_before_delete_entry', [$this, 'delete_from_crm'], 10, 1); - } - - public function send_to_crm($form_data) { - // Only for contact forms - if ($form_data['form_id'] !== 123) { - return; - } - - $crm_data = [ - 'email' => $form_data['email']['value'] ?? '', - 'name' => $form_data['name']['value'] ?? '', - 'phone' => $form_data['phone']['value'] ?? '', - 'company' => $form_data['company']['value'] ?? '', - 'source' => 'sureforms', - 'entry_id' => $form_data['entry_id'] ?? 0 - ]; - - $response = wp_remote_post('https://crm.example.com/api/leads', [ - 'body' => json_encode($crm_data), - 'headers' => [ - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . get_option('crm_api_key') - ] - ]); - - if (is_wp_error($response)) { - error_log('CRM sync failed: ' . $response->get_error_message()); - } else { - $crm_id = json_decode(wp_remote_retrieve_body($response))->id; - // Store CRM ID with entry - update_post_meta($form_data['entry_id'], '_crm_lead_id', $crm_id); - } - } - - public function delete_from_crm($entry_id) { - $crm_id = get_post_meta($entry_id, '_crm_lead_id', true); - if ($crm_id) { - wp_remote_request('https://crm.example.com/api/leads/' . $crm_id, [ - 'method' => 'DELETE', - 'headers' => [ - 'Authorization' => 'Bearer ' . get_option('crm_api_key') - ] - ]); - } - } -} - -new SureForms_CRM_Integration(); -``` - ---- - -## Hook Priority Guidelines - -**Priority Values:** -- **5:** Execute before default behavior -- **10:** Default priority (most hooks) -- **15:** Execute after default behavior -- **20+:** Late execution -- **100+:** Very late (cleanup, logging) - -**Example:** -```php -// Execute before SureForms processes submission -add_action('srfm_before_submission', 'my_function', 5); - -// Execute at default time -add_action('srfm_before_submission', 'my_function', 10); - -// Execute after SureForms processes submission -add_action('srfm_before_submission', 'my_function', 15); -``` - ---- - -## Debugging Hooks - -### Enable Hook Debugging - -```php -// Add to wp-config.php -define('SRFM_DEBUG_HOOKS', true); - -// Log all SureForms hooks -add_action('all', function($hook) { - if (strpos($hook, 'srfm_') === 0) { - error_log('Hook fired: ' . $hook); - } -}); -``` - -### View Hook Execution Order - -```php -// Log hook priority and callback -add_action('all', function($hook) { - if (strpos($hook, 'srfm_') === 0) { - global $wp_filter; - if (isset($wp_filter[$hook])) { - foreach ($wp_filter[$hook]->callbacks as $priority => $callbacks) { - foreach ($callbacks as $callback) { - error_log(sprintf( - 'Hook: %s, Priority: %d, Callback: %s', - $hook, - $priority, - is_array($callback['function']) ? - get_class($callback['function'][0]) . '::' . $callback['function'][1] : - $callback['function'] - )); - } - } - } - } -}); -``` - ---- - -## Best Practices - -### 1. Always Check Hook Existence - -```php -if (function_exists('add_action')) { - add_action('srfm_after_submission_process', 'my_function'); -} -``` - -### 2. Use Proper Nonces for Custom Forms - -```php -add_filter('srfm_before_entry_data', function($data, $form_id) { - // Verify custom nonce if adding custom fields - if (isset($_POST['custom_nonce'])) { - if (!wp_verify_nonce($_POST['custom_nonce'], 'custom_action')) { - return new WP_Error('nonce_failed', 'Security check failed'); - } - } - return $data; -}, 10, 2); -``` - -### 3. Return Correct Data Types - -```php -// Filter must return array -add_filter('srfm_form_submit_data', function($data) { - // ✅ GOOD - returns array - return array_merge($data, ['custom' => 'value']); - - // ❌ BAD - returns nothing - // (void return breaks functionality) -}, 10, 1); -``` - -### 4. Handle Errors Gracefully - -```php -add_action('srfm_after_submission_process', function($form_data) { - try { - // Risky operation - send_to_external_api($form_data); - } catch (Exception $e) { - error_log('External API failed: ' . $e->getMessage()); - // Don't throw - allow submission to continue - } -}, 10, 1); -``` - ---- - -## Summary - -**Hook Categories:** -- **Lifecycle:** 2 hooks (core_loaded, init) -- **Submission:** 10+ hooks (before, during, after) -- **Validation:** 5+ hooks (field-specific and general) -- **Email:** 5+ hooks (before, during, after send) -- **Rendering:** 10+ hooks (markup, classes, attributes) -- **Data:** 8+ hooks (modify, filter, transform) -- **Database:** 3+ hooks (before/after operations) -- **Payment:** 6+ hooks (process, success, failure) -- **Admin:** 5+ hooks (form creation, updates) - -**Total:** 450+ hooks across the plugin - -**Auto-Generation Note:** This document contains the most commonly used hooks. For a complete list, run the hook extraction script: -```bash -php scripts/extract-hooks.php > docs/COMPLETE-HOOKS-LIST.md -``` - ---- - -**Related Documentation:** -- [CLAUDE.md](../../CLAUDE.md) - AI agent guide -- [ARCHITECTURE.md](../02-architecture/ARCHITECTURE.md) - System design -- [REST-API.md](REST-API.md) - REST API endpoints -- [DATABASE-SCHEMA.md](DATABASE-SCHEMA.md) - Database tables diff --git a/docs/05-frontend/FRONTEND-GUIDE.md b/docs/05-frontend/FRONTEND-GUIDE.md deleted file mode 100644 index d26f09ee8..000000000 --- a/docs/05-frontend/FRONTEND-GUIDE.md +++ /dev/null @@ -1,1197 +0,0 @@ -# SureForms Frontend Development Guide - -> **Purpose:** Comprehensive guide for React/Gutenberg block development -> **Audience:** Frontend developers working with React, Gutenberg, and WordPress -> **Last Updated:** 2026-02-06 -> **Plugin Version:** 2.5.0 - -## Overview - -SureForms frontend is built with modern JavaScript technologies integrated deeply with WordPress's Gutenberg block editor: - -- **React 18.2:** UI components and interactive features -- **Gutenberg Blocks:** WordPress block editor integration for form building -- **TailwindCSS 3.4:** Utility-first CSS framework -- **React Query 5.x:** Server state management and caching -- **@wordpress/scripts 26.2:** Build tooling (Webpack 5 wrapper) - ---- - -## Table of Contents - -- [Architecture Overview](#architecture-overview) -- [Directory Structure](#directory-structure) -- [Creating Gutenberg Blocks](#creating-gutenberg-blocks) -- [State Management](#state-management) -- [Component Patterns](#component-patterns) -- [Styling](#styling) -- [Form Validation](#form-validation) -- [Internationalization](#internationalization) -- [Performance Optimization](#performance-optimization) -- [Testing](#testing) - ---- - -## Architecture Overview - -### Technology Stack - -``` -┌─────────────────────────────────────────┐ -│ Admin UI (React Apps) │ -│ Dashboard, Settings, Entries, Editor │ -└──────────────┬──────────────────────────┘ - │ - ┌──────────┴──────────┐ - │ │ -┌───▼────────┐ ┌──────▼──────┐ -│ Gutenberg │ │ Admin │ -│ Blocks │ │ Pages │ -│ (Editor) │ │ (Router) │ -└───┬────────┘ └──────┬──────┘ - │ │ - └──────────┬─────────┘ - │ - ┌──────────▼──────────┐ - │ State Management │ - │ Data Store + Hooks │ - └──────────┬──────────┘ - │ - ┌──────────▼──────────┐ - │ REST API │ - │ /sureforms/v1/* │ - └─────────────────────┘ -``` - -### Component Layers - -1. **Gutenberg Block Layer** - Form builder blocks (src/blocks/) -2. **Admin UI Layer** - React SPA pages (src/admin/) -3. **Shared Components** - Reusable UI (src/components/) -4. **State Layer** - Data stores (src/store/) -5. **API Layer** - WordPress REST API - ---- - -## Directory Structure - -``` -src/ -├── blocks/ # Gutenberg block components -│ ├── blocks.js # Block registration entry -│ ├── register-block.js # Block utilities -│ │ -│ ├── sform/ # Form container block -│ │ ├── index.js # Block registration -│ │ ├── edit.js # Editor component -│ │ ├── save.js # Frontend save (returns null) -│ │ ├── block.json # Block metadata -│ │ ├── components/ # Block-specific components -│ │ └── style.scss # Block styles -│ │ -│ ├── input/ # Text input field block -│ │ ├── edit.js # Editor with InspectorControls -│ │ ├── save.js # Server-side rendering -│ │ └── block.json # Attributes schema -│ │ -│ └── [14+ more blocks] # Email, textarea, checkbox, etc. -│ -├── admin/ # Admin dashboard React apps -│ ├── dashboard/ # Forms listing page -│ │ ├── index.js # Entry point -│ │ ├── FormsTable.js # Table component -│ │ ├── Analytics.js # Analytics widgets -│ │ └── styles.scss -│ │ -│ ├── single-form-settings/ # Form editor -│ │ ├── Editor.js # Main editor wrapper -│ │ ├── InstantForm.js # Live preview -│ │ ├── tabs/ # Settings tabs -│ │ │ ├── GeneralSettings.js -│ │ │ └── StyleSettings.js -│ │ └── components/ # Editor components -│ │ -│ ├── entries/ # Entry management -│ ├── settings/ # Global settings -│ ├── payment/ # Payment settings -│ └── onboarding/ # Setup wizard -│ -├── components/ # Shared React components -│ ├── ui/ # UI primitives -│ │ ├── Button/ -│ │ ├── Input/ -│ │ ├── Modal/ -│ │ └── Tooltip/ -│ │ -│ ├── form/ # Form-specific -│ │ ├── FieldWrapper/ -│ │ ├── ValidationMessage/ -│ │ └── FormContainer/ -│ │ -│ ├── inspector-tabs/ # Block editor tabs -│ ├── color-switch-control/ # Color picker -│ ├── spacing-control/ # Responsive spacing -│ └── [40+ component dirs] -│ -├── srfm-controls/ # Gutenberg block controls -│ ├── index.js # Control registry -│ ├── getPreviewType.js # Responsive preview -│ └── [18+ control definitions] -│ -├── store/ # WordPress Data Store (Redux) -│ ├── store.js # Store creation -│ ├── actions.js # Action creators -│ ├── reducer.js # Reducer function -│ ├── selectors.js # Selectors -│ └── constants.js # Action types -│ -├── utils/ # Utility functions -│ ├── Helpers.js # Common helpers -│ ├── datePickerHelper.js -│ └── addressFieldHelper.js -│ -└── styles/ # Global styles - ├── typography/ - └── breakpoints/ -``` - -**Build Output:** -``` -assets/build/ -├── formEditor.js # Form editor bundle (~1.9MB) -├── blocks.js # Gutenberg blocks (~1.3MB) -├── dashboard.js # Dashboard page (~1.3MB) -├── entries.js # Entries page (~728KB) -├── forms.js # Forms listing (~727KB) -├── settings.js # Settings page (~1.1MB) -├── formSubmit.js # Frontend submission (~60KB) -└── *.asset.php # Webpack dependency manifests -``` - ---- - -## Creating Gutenberg Blocks - -### Block Anatomy - -Every form field block consists of: -1. **block.json** - Metadata and attributes schema -2. **edit.js** - Editor component (React) -3. **save.js** - Frontend rendering (usually null, PHP renders) -4. **style.scss** - Block-specific styles -5. **PHP class** - Server-side registration and rendering - -### Step-by-Step: Create a New Block - -#### Step 1: Create Block Directory - -```bash -mkdir -p src/blocks/custom-field -cd src/blocks/custom-field -``` - -#### Step 2: Create block.json - -**File:** [src/blocks/custom-field/block.json](../../src/blocks/custom-field/block.json) - -```json -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 2, - "name": "sureforms/custom-field", - "title": "Custom Field", - "category": "sureforms", - "icon": "text", - "description": "A custom form field", - "keywords": ["form", "custom", "field"], - "supports": { - "html": false, - "anchor": false, - "customClassName": false - }, - "attributes": { - "label": { - "type": "string", - "default": "Custom Field" - }, - "slug": { - "type": "string", - "default": "custom_field" - }, - "placeholder": { - "type": "string", - "default": "" - }, - "required": { - "type": "boolean", - "default": false - }, - "helpText": { - "type": "string", - "default": "" - } - }, - "editorScript": "file:./index.js", - "editorStyle": "file:./editor.scss", - "style": "file:./style.scss" -} -``` - -#### Step 3: Create edit.js (Editor Component) - -**File:** [src/blocks/custom-field/edit.js](../../src/blocks/custom-field/edit.js) - -```javascript -import { __ } from '@wordpress/i18n'; -import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; -import { PanelBody, TextControl, ToggleControl, TextareaControl } from '@wordpress/components'; - -export default function Edit({ attributes, setAttributes, clientId }) { - const { label, slug, placeholder, required, helpText } = attributes; - - const blockProps = useBlockProps({ - className: 'srfm-block srfm-custom-field-block', - }); - - return ( - <> - {/* Inspector Panel (Right Sidebar) */} - - - setAttributes({ label: value })} - help={__('Field label shown to users', 'sureforms')} - /> - - setAttributes({ slug: value })} - help={__('Unique identifier for this field', 'sureforms')} - /> - - setAttributes({ placeholder: value })} - /> - - setAttributes({ required: value })} - /> - - setAttributes({ helpText: value })} - help={__('Additional instructions for users', 'sureforms')} - /> - - - - {/* Block Preview in Editor */} -
    -
    - - - - - {helpText && ( - - {helpText} - - )} -
    -
    - - ); -} -``` - -#### Step 4: Create save.js - -**File:** [src/blocks/custom-field/save.js](../../src/blocks/custom-field/save.js) - -```javascript -/** - * Save function - return null for dynamic blocks (rendered server-side). - * - * SureForms blocks are rendered server-side via PHP for: - * - Better security (no client-side data exposure) - * - Consistent rendering across contexts - * - Easier form submission handling - */ -export default function save() { - return null; -} -``` - -#### Step 5: Create index.js (Registration) - -**File:** [src/blocks/custom-field/index.js](../../src/blocks/custom-field/index.js) - -```javascript -import { registerBlockType } from '@wordpress/blocks'; -import Edit from './edit'; -import save from './save'; -import metadata from './block.json'; - -// Register block -registerBlockType(metadata.name, { - ...metadata, - edit: Edit, - save, -}); -``` - -#### Step 6: Add to blocks.js - -**File:** [src/blocks/blocks.js](../../src/blocks/blocks.js) - -```javascript -// ... other imports -import './custom-field'; -``` - -#### Step 7: Create PHP Server-Side Rendering - -**File:** [inc/blocks/custom-field/class-custom-field.php](../../inc/blocks/custom-field/class-custom-field.php) - -```php - -
    - - - - - /> - - - - - - -
    - { - setValue(e.target.value); - - // Validate - if (e.target.value.length < 3) { - setError('Minimum 3 characters'); - } else { - setError(''); - } - }; - - return ( -
    - - {error && {error}} -
    - ); -} -``` - -### 2. WordPress Data Store (Redux-based) - -For block editor state and form metadata. - -**Define Store** ([src/store/store.js](../../src/store/store.js)): - -```javascript -import { createReduxStore, register } from '@wordpress/data'; -import apiFetch from '@wordpress/api-fetch'; - -const DEFAULT_STATE = { - formSettings: {}, - blockAttributes: {}, - isLoading: false, -}; - -const actions = { - setFormSettings(settings) { - return { - type: 'SET_FORM_SETTINGS', - settings, - }; - }, - - updateBlockAttribute(blockId, attribute, value) { - return { - type: 'UPDATE_BLOCK_ATTRIBUTE', - blockId, - attribute, - value, - }; - }, - - *fetchFormSettings(formId) { - yield { type: 'SET_LOADING', isLoading: true }; - - try { - const settings = yield apiFetch({ - path: `/sureforms/v1/forms/${formId}/settings`, - }); - - return actions.setFormSettings(settings); - } catch (error) { - console.error('Failed to fetch settings:', error); - return { type: 'FETCH_ERROR', error }; - } - }, -}; - -const selectors = { - getFormSettings(state) { - return state.formSettings; - }, - - getBlockAttribute(state, blockId, attribute) { - return state.blockAttributes[blockId]?.[attribute]; - }, - - isLoading(state) { - return state.isLoading; - }, -}; - -const reducer = (state = DEFAULT_STATE, action) => { - switch (action.type) { - case 'SET_FORM_SETTINGS': - return { - ...state, - formSettings: action.settings, - isLoading: false, - }; - - case 'UPDATE_BLOCK_ATTRIBUTE': - return { - ...state, - blockAttributes: { - ...state.blockAttributes, - [action.blockId]: { - ...state.blockAttributes[action.blockId], - [action.attribute]: action.value, - }, - }, - }; - - case 'SET_LOADING': - return { - ...state, - isLoading: action.isLoading, - }; - - default: - return state; - } -}; - -export const store = createReduxStore('srfm/forms', { - reducer, - actions, - selectors, -}); - -register(store); -``` - -**Use Store in Components:** - -```javascript -import { useSelect, useDispatch } from '@wordpress/data'; - -function FormEditor({ formId }) { - // Select data from store - const { formSettings, isLoading } = useSelect((select) => ({ - formSettings: select('srfm/forms').getFormSettings(), - isLoading: select('srfm/forms').isLoading(), - })); - - // Dispatch actions to store - const { fetchFormSettings, setFormSettings } = useDispatch('srfm/forms'); - - useEffect(() => { - fetchFormSettings(formId); - }, [formId]); - - if (isLoading) { - return ; - } - - return ( -
    -

    {formSettings.title}

    - {/* Editor UI */} -
    - ); -} -``` - -### 3. React Query (Server State) - -For API responses and caching. - -**Setup Query Client** ([src/admin/dashboard/index.js](../../src/admin/dashboard/index.js)): - -```javascript -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 5 * 60 * 1000, // 5 minutes - retry: 1, - }, - }, -}); - -function App() { - return ( - - - - ); -} -``` - -**Create Custom Hooks** ([src/hooks/useFormData.js](../../src/hooks/)): - -```javascript -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import apiFetch from '@wordpress/api-fetch'; - -// Fetch form data -export function useFormData(formId) { - return useQuery({ - queryKey: ['form', formId], - queryFn: async () => { - const response = await apiFetch({ - path: `/sureforms/v1/forms/${formId}`, - }); - return response; - }, - enabled: !!formId, - }); -} - -// Update form data -export function useUpdateForm() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ formId, data }) => { - return await apiFetch({ - path: `/sureforms/v1/forms/${formId}`, - method: 'PUT', - data, - }); - }, - onSuccess: (data, variables) => { - // Invalidate and refetch - queryClient.invalidateQueries(['form', variables.formId]); - }, - }); -} - -// Fetch entries -export function useEntries(formId, page = 1) { - return useQuery({ - queryKey: ['entries', formId, page], - queryFn: async () => { - return await apiFetch({ - path: `/sureforms/v1/forms/${formId}/entries?page=${page}`, - }); - }, - keepPreviousData: true, // For pagination - }); -} -``` - -**Use in Components:** - -```javascript -function FormEditor({ formId }) { - const { data: form, isLoading, error } = useFormData(formId); - const updateForm = useUpdateForm(); - - const handleSave = async () => { - try { - await updateForm.mutateAsync({ - formId, - data: { title: 'Updated Title' }, - }); - // Success toast - toast.success('Form saved!'); - } catch (error) { - // Error toast - toast.error('Failed to save form'); - } - }; - - if (isLoading) return ; - if (error) return
    Error: {error.message}
    ; - - return ( -
    -

    {form.title}

    - -
    - ); -} -``` - ---- - -## Component Patterns - -### Functional Components with Hooks - -**Always use functional components** (not class components): - -```javascript -// ✅ GOOD - Functional component -function MyComponent({ prop1, prop2 }) { - const [state, setState] = useState(null); - - useEffect(() => { - // Side effects - }, []); - - return
    {/* JSX */}
    ; -} - -// ❌ BAD - Class component (deprecated pattern) -class MyComponent extends React.Component { - // Don't use class components in SureForms -} -``` - -### Props Destructuring - -```javascript -// ✅ GOOD - Destructure in signature -function Button({ label, onClick, disabled = false, variant = 'primary' }) { - return ( - - ); -} - -// ❌ BAD - Access via props object -function Button(props) { - return ; -} -``` - -### Conditional Rendering - -```javascript -function FormField({ label, required, error }) { - return ( -
    - - - {/* Ternary for if/else */} - {error ? ( - {error} - ) : ( - Optional - )} - - {/* Conditional with && */} - {!error && Valid} -
    - ); -} -``` - -### Event Handlers - -```javascript -function FormContainer() { - // useCallback for stable reference - const handleSubmit = useCallback((event) => { - event.preventDefault(); - // Handle submit - }, []); - - // Arrow function for inline with parameters - const handleFieldChange = useCallback((fieldId, value) => { - setFormData((prev) => ({ - ...prev, - [fieldId]: value, - })); - }, []); - - return ( -
    - handleFieldChange('name', e.target.value)} - /> -
    - ); -} -``` - ---- - -## Styling - -### TailwindCSS (Preferred) - -Use Tailwind utility classes for rapid development: - -```javascript -function Card({ title, children }) { - return ( -
    -

    - {title} -

    -
    - {children} -
    -
    - ); -} -``` - -**Responsive Design:** - -```javascript -
    - {/* Full width mobile, half on tablet, third on desktop */} -
    - -
    - {/* Responsive text size */} -
    -``` - -### Custom SCSS (When Needed) - -**Use BEM methodology** with `.srfm-` prefix: - -```scss -// style.scss -.srfm-custom-field { - // Block - &__label { - // Element - @apply block text-sm font-medium text-gray-700 mb-2; - - .required { - // Nested element - @apply text-red-500 ml-1; - } - } - - &__input { - // Element - @apply w-full px-4 py-2 border border-gray-300 rounded-md; - @apply focus:outline-none focus:ring-2 focus:ring-blue-500; - - &:disabled { - // Modifier via pseudo-class - @apply bg-gray-100 cursor-not-allowed; - } - } - - &--error { - // Block modifier - .srfm-custom-field__input { - @apply border-red-500; - } - } -} -``` - -### Conditional Classes (classnames) - -```javascript -import classnames from 'classnames'; - -function Button({ variant, size, disabled, className }) { - const classes = classnames( - 'srfm-button', - `srfm-button--${variant}`, - `srfm-button--${size}`, - { - 'srfm-button--disabled': disabled, - }, - className // Allow override - ); - - return ; -} -``` - ---- - -## Form Validation - -### Client-Side Validation (Frontend) - -**Inline validation:** - -```javascript -import { useState } from 'react'; - -function EmailField({ value, onChange, required }) { - const [error, setError] = useState(''); - - const validateEmail = (email) => { - const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return regex.test(email); - }; - - const handleBlur = () => { - if (required && !value) { - setError(__('Email is required', 'sureforms')); - } else if (value && !validateEmail(value)) { - setError(__('Please enter a valid email', 'sureforms')); - } else { - setError(''); - } - }; - - return ( -
    - - {error && {error}} -
    - ); -} -``` - -**Form-level validation:** - -```javascript -function FormContainer() { - const [formData, setFormData] = useState({}); - const [errors, setErrors] = useState({}); - - const validate = () => { - const newErrors = {}; - - if (!formData.name) { - newErrors.name = 'Name is required'; - } - - if (!formData.email || !isValidEmail(formData.email)) { - newErrors.email = 'Valid email is required'; - } - - if (formData.age && formData.age < 18) { - newErrors.age = 'Must be 18 or older'; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validate()) { - return; - } - - // Submit form - try { - await submitForm(formData); - toast.success('Form submitted!'); - } catch (error) { - toast.error('Submission failed'); - } - }; - - return ( -
    - {/* Form fields */} - -
    - ); -} -``` - ---- - -## Internationalization (i18n) - -### Translation Functions - -```javascript -import { __, _n, sprintf } from '@wordpress/i18n'; - -// Simple translation -const label = __('Submit Form', 'sureforms'); - -// Plural forms -const message = _n( - 'One field is required', - '%d fields are required', - count, - 'sureforms' -); - -// String interpolation -const greeting = sprintf( - __('Hello, %s!', 'sureforms'), - userName -); -``` - -### Best Practices - -**✅ DO:** -```javascript -// Use sprintf for variables -const msg = sprintf(__('Form %s saved', 'sureforms'), formName); - -// Separate strings for different contexts -const saveButton = __('Save', 'sureforms'); -const saveForm = __('Save Form', 'sureforms'); -``` - -**❌ DON'T:** -```javascript -// Never use template literals in translations -const msg = __(`Form ${formName} saved`, 'sureforms'); // ❌ WRONG - -// Never concatenate -const msg = __('Form ', 'sureforms') + formName + __(' saved', 'sureforms'); // ❌ WRONG -``` - ---- - -## Performance Optimization - -### Code Splitting - -```javascript -import { lazy, Suspense } from 'react'; - -const HeavyComponent = lazy(() => import('./HeavyComponent')); - -function App() { - return ( - }> - - - ); -} -``` - -### Memoization - -```javascript -import { useMemo, useCallback } from 'react'; - -function DataTable({ data, onRowClick }) { - // Memoize expensive calculations - const sortedData = useMemo(() => { - console.log('Sorting data...'); - return data.sort((a, b) => a.name.localeCompare(b.name)); - }, [data]); // Only recompute when data changes - - // Memoize callbacks - const handleRowClick = useCallback( - (row) => { - onRowClick(row); - }, - [onRowClick] - ); - - return {/* Render sortedData */}
    ; -} -``` - -### React.memo - -```javascript -import { memo } from 'react'; - -// Prevent re-renders if props haven't changed -const ExpensiveComponent = memo(function ExpensiveComponent({ data }) { - console.log('Rendering...'); - return
    {/* Complex rendering */}
    ; -}); -``` - ---- - -## Testing - -### Component Testing (Jest + React Testing Library) - -```javascript -import { render, screen, fireEvent } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import FormField from '../FormField'; - -describe('FormField', () => { - it('renders with label', () => { - render(); - expect(screen.getByText('Name')).toBeInTheDocument(); - }); - - it('shows required indicator', () => { - render(); - expect(screen.getByText('*')).toBeInTheDocument(); - }); - - it('calls onChange when value changes', () => { - const handleChange = jest.fn(); - render(); - - const input = screen.getByRole('textbox'); - fireEvent.change(input, { target: { value: 'test' } }); - - expect(handleChange).toHaveBeenCalledWith('test'); - }); - - it('displays error message', () => { - render(); - expect(screen.getByText('This field is required')).toBeInTheDocument(); - }); -}); -``` - ---- - -## Summary - -**Key Takeaways:** - -1. **Use React 18 functional components** with hooks -2. **Gutenberg blocks** are registered in JavaScript, rendered in PHP -3. **State management** uses three layers: local, WordPress Data Store, React Query -4. **Styling** prefers TailwindCSS utilities, custom SCSS for complex needs -5. **Validation** happens client-side (UX) and server-side (security) -6. **i18n** uses `@wordpress/i18n` functions, no template literals -7. **Performance** optimized with memoization, code splitting, virtualization - ---- - -**Related Documentation:** -- [CLAUDE.md](../../CLAUDE.md) - AI agent guide -- [GETTING-STARTED.md](../01-getting-started/GETTING-STARTED.md) - Setup -- [CODING-STANDARDS.md](../03-development/CODING-STANDARDS.md) - Code style -- [TESTING.md](../08-testing/TESTING.md) - Testing guide diff --git a/docs/FORM_MIGRATION_OVERVIEW.md b/docs/FORM_MIGRATION_OVERVIEW.md deleted file mode 100644 index dd7fac4d6..000000000 --- a/docs/FORM_MIGRATION_OVERVIEW.md +++ /dev/null @@ -1,77 +0,0 @@ -# SureForms — Form Migration (Overview) - -> Looking for the full technical plan? See `FORM_MIGRATION_PLAN.md` in this folder. - -## What this feature does - -Lets users move their existing forms into SureForms from the other popular form-builder plugins, in one click — no copy-pasting, no rebuilding. - -## Which plugins we'll support - -| Source plugin | Approx. user base | Status | -|---|---|---| -| Contact Form 7 | 10M+ sites | **Available in v1** | -| WPForms | 6M+ sites | Coming next | -| Gravity Forms | ~650K sites | Coming next | -| Ninja Forms | 600K+ sites | Later release | -| Caldera Forms | ~50K sites (unmaintained plugin) | Later release | - -Combined audience: roughly **17 million sites** that could move to SureForms. - -## What an import gives you - -When a user clicks "Import", we bring across: - -1. **The form's fields** — text, email, phone, dropdown, checkbox, radio, file URL, number, textarea, consent checkboxes, etc. They show up as native SureForms blocks, ready to edit in the Gutenberg editor. -2. **The form's title** — same as the original. -3. **A working admin notification email** — recipient is the site admin, subject mentions the form name, body uses `{all_data}` so submissions go straight to your inbox. -4. **A success message** — a clean "Thank you" confirmation shown after submission. - -The imported form works on day one. Users can polish it in SureForms' editor. - -## What doesn't transfer yet (and why) - -| Thing | Why not | -|---|---| -| File-upload fields | SureForms doesn't have a file-upload block yet — coming separately. | -| Quiz / signature / rating fields | No SureForms equivalent yet. | -| The exact email-body text from CF7 | CF7 uses a different placeholder syntax (`[your-name]` vs SureForms' smart tags). Trying to translate it gets unreliable, so we ship a clean default instead. WPForms and Gravity emails will come through fully when those are added. | -| Multi-step / page breaks | Waiting on a SureForms multi-step block. | - -When a field can't be migrated, the import screen lists it under **"Some fields were skipped"** so the user knows what to add by hand. - -## How users will find it - -A new **Migration** tab inside **SureForms → Settings**, at the bottom of the sidebar. The workflow is four short steps: - -1. **Choose a source** — tiles show each detected plugin and how many forms it has. -2. **Pick which forms** — checkbox list; "Select all" if you want everything. -3. **Preview** — see what the migrated form will look like before anything is saved. -4. **Confirm & import** — done. The result screen has direct "Edit in SureForms" links. - -If a user re-runs the import for the same source form, we **update** the existing SureForms version instead of creating a duplicate. - -## What's already built - -- The Migration tab is live in **SureForms → Settings → Migration** -- Contact Form 7 imports work end to end (fields + notification + confirmation) -- "Preview before importing" works — nothing gets saved until the user confirms -- Skipped fields are surfaced clearly to the user - -## What's next - -1. **WPForms importer** — including its full email-notification and confirmation rules -2. **Gravity Forms importer** — including notifications, scheduling, entry limits -3. **Ninja Forms + Caldera Forms importers** -4. **Entry migration** — bringing across historical form submissions (capped at 1,000 per form to keep imports fast) -5. **Filling in field gaps** — once SureForms ships file-upload, multi-step, and signature blocks, the migrator will start carrying those over too - -## Why we chose this approach - -We modelled the migrator on **Fluent Forms**' approach, which has been running in production on 700K+ sites for years. Same flow (source → forms → preview → import), same safety net (re-imports update rather than duplicate), and the same honest stance on edge cases like CF7 email templates. - -## Where to find things (for engineers) - -- Full technical plan with code structure, REST endpoints, field-mapping tables: `FORM_MIGRATION_PLAN.md` -- PHP code: `inc/migrator/` -- Admin tab UI: `src/admin/settings/migration/` diff --git a/docs/FORM_MIGRATION_PLAN.md b/docs/FORM_MIGRATION_PLAN.md deleted file mode 100644 index 9d7c2a9a8..000000000 --- a/docs/FORM_MIGRATION_PLAN.md +++ /dev/null @@ -1,458 +0,0 @@ -# SureForms — Form Migration Implementation Plan - -## 1. Goal - -Build a one-click migrator that imports forms (and optionally entries) from the 5 major WordPress form plugins into SureForms, modelled on Fluent Forms' production-tested migrator architecture (~5,400 LOC across 5 sources, deployed on 700K+ sites). - -**Sources to support in v1**: Contact Form 7, WPForms (Lite + Pro), Gravity Forms, Ninja Forms, Caldera Forms. - -## 2. Why Fluent Forms' pattern - -Fluent's full migrator source was read end-to-end (`fluentform/app/Services/Migrator/`). Their architecture is: - -- **One abstract base class** with the import loop, idempotency map, and a ~1,200-line canonical field dictionary. -- **Thin per-source subclasses** (500–900 LOC each) that only do (a) `source_type → fluent_id` mapping and (b) source-specific args formatting. -- **4 AJAX endpoints** for: list sources, list forms in source, import forms, import entries. -- **Template Method pattern** — base class drives the flow, subclasses fill in 5 abstract methods (`exist`, `get_source_forms`, `get_source_form_id`, `get_source_form_name`, `build_form_content`, `get_form_metas`). - -It's clean, proven, and easy to extend. SureForms will adopt the same shape, adapted for: - -1. **REST instead of AJAX** — SureForms already uses `/sureforms/v1/` namespace (`inc/rest-api.php`); AJAX would be inconsistent. -2. **Block markup output instead of JSON** — SureForms forms are Gutenberg block strings in `post_content` of the `sureforms_form` CPT, not JSON in a custom table. So the importer's final stage is to serialize block markup, not assemble a flat field array. -3. **15 SureForms blocks as targets** — instead of Fluent's ~25 field types. - -## 3. Sources to import — confirmed field type maps - -These are lifted directly from Fluent's `fieldTypes()` methods in each migrator class. Total source field types we need to handle: **110** (with overlap). - -### 3.1 Contact Form 7 (13 form-tag types → SureForms blocks) -Source: `wpcf7_contact_form` post type, `_form` meta (shortcode template). - -| CF7 form-tag | SureForms block | Notes | -|---|---|---| -| `text`, `text*` | `srfm/input` | type=text | -| `email`, `email*` | `srfm/email` | | -| `url`, `url*` | `srfm/url` | | -| `tel`, `tel*` | `srfm/phone` | | -| `number`, `number*` | `srfm/number` | | -| `range` | `srfm/number` | slider mode | -| `date` | `srfm/input` | type=date (no native date block) | -| `textarea`, `textarea*` | `srfm/textarea` | | -| `select`, `select*` | `srfm/dropdown` | `multiple` → multi-select | -| `checkbox`, `checkbox*` | `srfm/checkbox` | | -| `radio` | `srfm/multi-choice` | | -| `acceptance` | `srfm/gdpr` | tnc_html → content | -| `file`, `file*` | unsupported in v1 | no native `srfm/file` — flag | -| `quiz`, `captchar`, `hidden` | skipped | legacy / not in v1 scope | - -Detection: `defined('WPCF7_PLUGIN')`. Entries: requires Flamingo plugin. - -### 3.2 WPForms (22 field types → SureForms blocks) -Source: CPT `wpforms`, field data as JSON in `post_content`. - -| WPForms type | SureForms block | Notes | -|---|---|---| -| `text` | `srfm/input` | | -| `email` | `srfm/email` | | -| `url` | `srfm/url` | | -| `phone` | `srfm/phone` | | -| `number`, `number-slider` | `srfm/number` | | -| `textarea`, `richtext` | `srfm/textarea` | rich → fallback | -| `select`, `multi_select` | `srfm/dropdown` | | -| `radio` | `srfm/multi-choice` | | -| `checkbox` | `srfm/checkbox` | | -| `name` | `srfm/input × 3` | split first/middle/last | -| `address` | `srfm/address` | structured fields | -| `date-time` | `srfm/input` | type=date until native block | -| `password` | `srfm/input` | type=password | -| `hidden` | `srfm/input` | type=hidden | -| `html`, `divider` | structural — core/html or section break | | -| `pagebreak` | unsupported in v1 | no native multi-step yet | -| `file-upload`, `rating`, `layout` | unsupported in v1 | flag | - -Detection: `function_exists('wpforms')`. Entries: WPForms Pro only. - -### 3.3 Gravity Forms (22 field types → SureForms blocks) -Source: custom tables `gf_form` + `gf_form_meta` (serialized PHP array). - -| Gravity type | SureForms block | Notes | -|---|---|---| -| `email` | `srfm/email` | | -| `text` | `srfm/input` | | -| `name` | `srfm/input × 3` | first/middle/last sub-inputs | -| `hidden` | `srfm/input` (type=hidden) | | -| `textarea` | `srfm/textarea` | | -| `website` | `srfm/url` | | -| `phone` | `srfm/phone` | | -| `select`, `multiselect` | `srfm/dropdown` | | -| `checkbox` | `srfm/checkbox` | | -| `radio` | `srfm/multi-choice` | | -| `date`, `time` | `srfm/input` (type=date/time) | | -| `number` | `srfm/number` | | -| `address` | `srfm/address` | | -| `consent` | `srfm/gdpr` | | -| `captcha` | unsupported | external service | -| `html` | core/html (or skip) | | -| `section` | section break / divider | | -| `page` | unsupported in v1 | multi-step | -| `list` | unsupported in v1 | repeater not in srfm | -| `fileupload` | unsupported in v1 | no srfm/file | - -Detection: `class_exists('GFForms')`. Entries: `gf_entry`, `gf_entry_meta` tables. - -### 3.4 Ninja Forms (27 field type aliases → SureForms blocks) -Source: custom tables `nf3_forms`, `nf3_fields`, `nf3_field_meta` (JOIN-based). - -| Ninja type | SureForms block | Notes | -|---|---|---| -| `email` | `srfm/email` | | -| `textbox`, `firstname`, `lastname`, `address`, `city`, `zip` | `srfm/input` | (Ninja splits address into individual text fields) | -| `liststate`, `listcountry` | `srfm/dropdown` | | -| `textarea` | `srfm/textarea` | | -| `phone` | `srfm/phone` | | -| `select`, `listselect` | `srfm/dropdown` | | -| `listmultiselect` | `srfm/dropdown` (multi) | | -| `radio`, `listradio`, `listimage`, `toggle_switch` | `srfm/multi-choice` | | -| `checkbox`, `listcheckbox` | `srfm/checkbox` | | -| `date` | `srfm/input` (type=date) | | -| `number` | `srfm/number` | | -| `hidden` | `srfm/input` (type=hidden) | | -| `html` | core/html | | -| `hr` | section divider | | -| `starrating` | unsupported in v1 | no srfm/rating | -| `repeater` | unsupported in v1 | | -| `recaptcha` | spam protection — emit as form-level setting | | -| `submit` | handled by `srfm/inline-button` | | - -Detection: `defined('NF_SERVER_URL')`. Entries: `nf_sub` CPT. - -### 3.5 Caldera Forms (26 field types → SureForms blocks) -Source: `Caldera_Forms_Forms::get_forms()` static API. Caldera is unmaintained — migration is a one-shot rescue. - -| Caldera type | SureForms block | Notes | -|---|---|---| -| `email` | `srfm/email` | | -| `text` | `srfm/input` | | -| `hidden` | `srfm/input` (type=hidden) | | -| `textarea`, `paragraph`, `wysiwyg` | `srfm/textarea` | | -| `url` | `srfm/url` | | -| `phone`, `phone_better` | `srfm/phone` | | -| `select`, `dropdown` | `srfm/dropdown` | | -| `filtered_select2` | `srfm/dropdown` (multi) | | -| `radio`, `toggle_switch` | `srfm/multi-choice` | | -| `checkbox` | `srfm/checkbox` | | -| `date_picker`, `date` | `srfm/input` (type=date) | | -| `number`, `range`, `calculation` | `srfm/number` | | -| `range_slider` | `srfm/number` (slider) | | -| `gdpr` | `srfm/gdpr` | | -| `section_break`, `html` | structural | | -| `color_picker`, `star_rating`, `file`, `cf2_file`, `advanced_file` | unsupported in v1 | flag | -| `button` | handled by `srfm/inline-button` | | - -Detection: `defined('CFCORE_VER')`. Caldera abandoned in 2022 — entries critical. - -## 4. SureForms target blocks (the destination schema) - -From `find sureforms/inc/blocks/*/block.json` — **15 blocks** total: - -| Block | Type | Used for | -|---|---|---| -| `srfm/form` | wrapper | Outer form container | -| `srfm/input` | field | Text/email/url/phone/password/date/hidden (HTML type via attr) | -| `srfm/email` | field | Email input | -| `srfm/textarea` | field | Paragraph text | -| `srfm/url` | field | URL input | -| `srfm/number` | field | Numeric input + slider mode | -| `srfm/phone` | field | International phone | -| `srfm/dropdown` | field | Select / multi-select | -| `srfm/multi-choice` | field | Radio / multiple choice | -| `srfm/checkbox` | field | Checkbox group | -| `srfm/address` | field | Structured address (line1/2/city/state/zip/country) | -| `srfm/gdpr` | field | Consent checkbox | -| `srfm/payment` | field | Stripe payment | -| `srfm/payment-history` | display | Past payments table | -| `srfm/inline-button` | structural | Submit / action button | - -**Gaps acknowledged**: no native rich-text, file upload, signature, rating, date-picker, multi-step (page break), or repeater blocks. Fields mapped to these in source plugins are flagged as `unsupported_fields[]` in the v1 response and surfaced in the import UI as warnings. They will be added in subsequent releases. - -## 5. Code layout (mirrors Fluent's, adapted to SureForms conventions) - -``` -sureforms/inc/migrator/ -├── class-migrator-bootstrap.php # ~200 LOC — REST routes, capability checks, factory -├── class-base-migrator.php # ~800 LOC — abstract import loop + idempotency + helpers -├── class-block-templates.php # ~600 LOC — canonical srfm/* block markup templates -├── importers/ -│ ├── class-cf7-importer.php # ~600 LOC — shortcode regex parser -│ ├── class-wpforms-importer.php # ~700 LOC — JSON decode -│ ├── class-gravity-importer.php # ~800 LOC — serialized array parser -│ ├── class-ninja-importer.php # ~600 LOC — JOIN across nf3_* tables -│ └── class-caldera-importer.php # ~500 LOC — Caldera static API -└── views/admin-page.php # ~150 LOC — admin page mount point - -sureforms/src/admin/migrator/ # React UI (~800 LOC) -├── MigratorApp.jsx # Top-level component -├── SourceList.jsx # Tile per detected source -├── FormSelector.jsx # Pick forms to import -├── ImportPreview.jsx # Dry-run rendered output -├── ImportResult.jsx # Success + unsupported warnings -└── EntryImporter.jsx # Per-form entry import -``` - -**Total estimate: ~5,750 LOC** for all 5 sources (close to Fluent's ~5,400 LOC, with the extra slack accounting for SureForms' block-markup serialization). - -## 6. REST API design - -Namespace: `/wp-json/sureforms/v1/migrator/` (extending the existing `/sureforms/v1/` namespace from `inc/rest-api.php`). - -| Method | Route | Body / Query | Returns | -|---|---|---|---| -| GET | `/migrator/sources` | — | `[{key, title, installed, form_count, supports_entries}]` | -| GET | `/migrator/sources/{key}/forms` | — | `[{id, name, field_count, imported_srfm_id, last_imported_at}]` | -| POST | `/migrator/sources/{key}/import` | `{form_ids: [], dry_run: bool, behavior: {}, post_status?: 'draft'\|'publish'}` | `{imported: [{srfm_id, edit_url, source_id}], failed: [], skipped: [], unsupported_fields: [], preview?: {}}` | -| POST | `/migrator/sources/{key}/forms/{id}/entries` _(planned — P3, not yet registered)_ | `{limit?: 1000}` | `{count_imported, count_skipped, count_total}` | - -**Security model** (per `sureforms/CLAUDE.md`): -- Every route's `permission_callback` requires `current_user_can('manage_options')`. -- REST cookie nonce (`X-WP-Nonce`) for all routes — these are admin-only, no public access. -- Sanitize `form_ids` via `array_map('absint', ...)`, `key` via allowlist `['cf7','wpforms','gravity','ninja','caldera']`. - -## 7. Canonical block template dictionary - -Analogue of Fluent's 1,200-line `defaultFieldConfig()` array, but emitting Gutenberg block markup strings instead of field-config arrays. - -One method per target block in `class-block-templates.php`: - -```php -final class SRFM_Block_Templates { - - public static function input( array $args ): string { - $defaults = [ - 'label' => '', - 'placeholder' => '', - 'required' => false, - 'field_type' => 'text', // text|email|number|password|date|hidden - 'help_text' => '', - 'default_val' => '', - 'min_length' => '', - 'max_length' => '', - 'css_class' => '', - ]; - $attrs = wp_parse_args( $args, $defaults ); - // Match the schema in src/blocks/input/block.json - $json = wp_json_encode( self::strip_empty( [ - 'label' => $attrs['label'], - 'placeholder' => $attrs['placeholder'], - 'required' => $attrs['required'], - 'fieldType' => $attrs['field_type'], - 'help' => $attrs['help_text'], - 'defaultValue' => $attrs['default_val'], - 'minLength' => $attrs['min_length'], - 'maxLength' => $attrs['max_length'], - 'className' => $attrs['css_class'], - ] ) ); - return "\n"; - } - - public static function email( array $args ): string { /* ... */ } - public static function dropdown( array $args ): string { /* ... */ } - public static function checkbox( array $args ): string { /* ... */ } - // ... one per srfm/* block - - public static function form_wrapper( string $inner_content ): string { - return "\n{$inner_content}\n"; - } -} -``` - -The base migrator's import loop concatenates the per-field strings, wraps them in `form_wrapper()`, and persists via `wp_insert_post(['post_type' => 'sureforms_form', 'post_content' => $markup])`. - -## 8. Base migrator contract (abstract methods) - -```php -abstract class SRFM_Base_Migrator { - - protected string $key; // 'cf7' | 'wpforms' | 'gravity' | 'ninja' | 'caldera' - protected string $title; // Display name - protected array $unsupported_fields = []; - - // Per-source plumbing (abstract — subclasses implement) - abstract public function exist(): bool; - abstract protected function get_source_forms(): array; - abstract protected function get_source_form_id( array $form ); - abstract protected function get_source_form_name( array $form ): string; - abstract protected function build_form_content( array $form ): string; // concatenated block markup - abstract protected function get_form_metas( array $form ): array; // notifications, confirmations - // NOTE: get_entries() is planned for the entries-migration phase (P3); not shipped yet. - - // Shared (in base): - public function list_forms(): array; - public function count_source_forms(): int; - public function import_forms( array $selected_ids = [], bool $dry_run = false, array $behavior = [], string $post_status = 'publish' ): array; - protected function find_existing_srfm_id( $source_id ): int; - protected function record_import_mapping( int $srfm_id, $source_id ): void; -} -``` - -## 9. Idempotent re-imports - -Same pattern as Fluent: a single `wp_option('srfm_imported_forms_map')` storing: - -```php -[ - 123 => [ 'source_id' => 'cf7-45', 'source_key' => 'cf7', 'last_imported' => '2026-05-22 10:00:00' ], - 124 => [ 'source_id' => '8', 'source_key' => 'wpforms', 'last_imported' => '2026-05-22 10:01:00' ], -] -``` - -`is_already_imported()`: -1. Look up `source_key` + `source_id` in the map. -2. If found and the matching `sureforms_form` post still exists → **update** (not insert). -3. If the SureForms form was deleted → prune the stale entry, fresh insert. - -## 10. Unsupported field tracking - -Same pattern as Fluent: -- `$this->unsupported_fields[]` is pushed each time a source field's type isn't in the source's `field_type_map`, capturing the field's user-facing label (not internal id). -- Returned in the REST response as `unsupported_fields: []`. -- Admin UI renders these as a yellow callout: "These fields couldn't be migrated to SureForms — add them manually after import: Field A, Field B, Field C." - -## 11. Entry migration - -- **Form-by-form, not bulk** — same as Fluent. -- **Hard cap of 1,000 entries per form**, override via the `srfm/entry_migration_max_limit` filter. -- **Idempotent**: import deletes existing SureForms entries for that form first (via the same `resetEntries()` pattern Fluent uses) before inserting. -- **Files**: copy via `WP_Filesystem_Direct::copy()` into `wp-content/uploads/sureforms/migrated/`. - -## 12. Admin UI flow - -Lives at **WordPress → SureForms → Tools → Migrator** (new sub-tab next to existing export/import). - -``` -[ Step 1: Select source ] Detected plugins shown as tiles with logo + install count - ▼ click "Migrate from Contact Form 7" - -[ Step 2: Select forms ] Table of forms in source (name | fields | already-imported badge) - ▼ bulk select + "Preview" - -[ Step 3: Dry-run preview ] Server returns block markup; UI renders in a code pane - + warning callout for unsupported fields - ▼ "Confirm & Import" - -[ Step 4: Result ] "Imported X forms." Links: [Edit in SureForms] [Import entries] - Unsupported-fields list (if any) -``` - -The dry-run preview is the **one improvement over Fluent's UX** — it answers the user's "is this going to break anything?" anxiety before they commit. - -## 13. Implementation phasing - -Four mergeable PRs, each ~7–10 working days: - -| Phase | Deliverable | LOC | -|---|---|---| -| **P1: Foundation** | `Base_Migrator`, `Block_Templates`, REST routes, admin page shell, CF7 importer end-to-end | ~2,400 | -| **P2: WPForms + Gravity** | Two more importers + dry-run preview UI | ~1,700 | -| **P3: Ninja + Caldera** | Final two importers + entry import flow | ~1,300 | -| **P4: Polish** | Field-mapping overrides, entry import UI, docs, E2E tests | ~350 | - -Total: ~5,750 LOC delivered across ~4–5 weeks. - -## 14. Testing approach - -- **Unit tests** (PHPUnit, per `composer test`): per importer, fixture-driven — store sample source-form payloads in `tests/fixtures/migrator/{source}/*.json` and assert the produced block markup string matches a golden snapshot. -- **Integration tests**: a real CF7 / WPForms / Gravity form created via their PHP APIs in a `tests/Integration/Migrator/` test, imported via `Base_Migrator::import_forms()`, then assert a `sureforms_form` post exists with the right block content. -- **Playwright E2E** (per `npm run play:run`): full admin flow — open Migrator, pick CF7, select a form, dry-run, confirm import, verify the new form renders correctly on the front-end. -- **Manual exploratory**: all 5 source plugins are already installed locally, so the test environment is real, not mocked. - -## 15. Verification (when is this done) - -1. All 5 importers exist under `sureforms/inc/migrator/importers/` and pass their PHPUnit fixtures. -2. REST endpoints respond correctly: `GET /sureforms/v1/migrator/sources` lists the 5 sources with `installed: true` for whichever plugins are active. -3. Admin page at SureForms → Tools → Migrator renders the tile-based source picker. -4. Dry-run for a non-trivial CF7 form (text + email + textarea + radio + acceptance) produces valid Gutenberg block markup that renders correctly in the SureForms editor. -5. Re-importing the same source form updates the existing SureForms form (not a duplicate). -6. Entry migration imports up to 1,000 entries for a WPForms Pro form and the entries appear in SureForms' Entries admin. -7. Unsupported-field warnings appear in the UI for source fields without a SureForms target (e.g. CF7 `[file]`, WPForms `rating`). -8. PHPStan level 9 passes (`composer phpstan`) and PHPCS passes (`composer lint`). - -## 16. Reference source files (Fluent Forms — production reference) - -| Fluent file | Our equivalent | Why useful | -|---|---|---| -| `app/Services/Migrator/Bootstrap.php` | `class-migrator-bootstrap.php` | AJAX → REST route mapping | -| `app/Services/Migrator/Classes/BaseMigrator.php` | `class-base-migrator.php` | Import loop, idempotency, file migration helpers | -| `app/Services/Migrator/Classes/ContactForm7Migrator.php` | `class-cf7-importer.php` | Battle-tested shortcode regex parser (`formatAsFluentField()`) | -| `app/Services/Migrator/Classes/WpFormsMigrator.php` | `class-wpforms-importer.php` | JSON-decode + 22-entry field map | -| `app/Services/Migrator/Classes/GravityFormsMigrator.php` | `class-gravity-importer.php` | Serialized-array parsing + Name/Address sub-input handling | -| `app/Services/Migrator/Classes/NinjaFormsMigrator.php` | `class-ninja-importer.php` | JOIN across `nf3_*` tables via Ninja's PHP API | -| `app/Services/Migrator/Classes/CalderaMigrator.php` | `class-caldera-importer.php` | `Caldera_Forms_Forms` static API | - -Fluent Forms is GPL-2.0+ — we can lift regexes and parsing patterns directly with attribution comments. The `defaultFieldConfig()` array structure should be re-written for SureForms blocks, not copied (different output format). - -## 16a. Metadata migration - -Form metadata (email notifications, confirmation messages, compliance flags, integrations) lives **outside** the block-markup pipeline — it's persisted as `sureforms_form` post meta. The migrator handles it via the abstract `Base_Migrator::get_form_metas()` method which returns a `meta_key => meta_value` array passed to `wp_insert_post()` / `wp_update_post()` via `meta_input`. - -### What every importer must produce - -A SureForms-shaped payload. The two keys every importer should populate are: - -| SureForms meta key | Schema (registered in `inc/post-types.php`) | -|---|---| -| `_srfm_email_notification` | Array of objects: `{id, status, is_raw_format, name, email_to, email_reply_to, from_name, from_email, email_cc, email_bcc, subject, email_body}` | -| `_srfm_form_confirmation` | Array of objects: `{id, confirmation_type, page_url, custom_url, message, submission_action, enable_query_params, query_params}` | - -Other meta keys exist (`_srfm_compliance`, `_srfm_conditional_logic`, `_srfm_forms_styling`, `_srfm_integrations_webhooks`) but are not required for a usable imported form — they default to SureForms' built-in `register_post_meta` defaults if not provided. - -### What each source can give us (and what we ship in v1) - -| Source | Notifications | Confirmations | Other meta | Status in v1 | -|---|---|---|---|---| -| **CF7** | `_mail`, `_mail_2` use `[field-name]` shortcodes — incompatible with SureForms smart tags | `_messages` array (success/error text) | None | **Stub only** — admin notification to `{admin_email}` with `{all_data}` body, generic success message. Form is usable; users can refine in Single Form Settings. (Matches Fluent's CF7 approach for the same reason.) | -| **WPForms** | `settings.notifications[]` — fully structured (subject/body/recipient/CC/BCC/replyTo/fromName) | `settings.confirmations[]` — multi-confirmation with conditional logic | Webhooks | **Full migration planned in P2** | -| **Gravity** | Notifications, conditional notifications, routing | Confirmations, scheduling, login-required, entry limits | Advanced validation | **Full migration planned in P2** | -| **Ninja** | `Actions` API — type `email`, `successmessage`, `redirect`, `save` | Same Actions API | — | **Full migration planned in P3** | -| **Caldera** | `mailer` config in form settings | `successMessage` field | — | **Full migration planned in P3** | - -### Why CF7 is special - -CF7's email body uses `[your-name]`, `[your-email]` style shortcodes that reference the form-tag `name` attribute. Translating these to SureForms' `{slug_field_id}` smart-tag system requires either (a) maintaining a per-form name→slug mapping during import, or (b) rewriting the body string with regex substitutions that match the field tokens we emitted. Both approaches add ~400 LOC and break easily on edge cases (multi-line bodies, mixed shortcodes/text, CF7 special mail tags like `[_remote_ip]`). - -Fluent Forms — with 700K+ installs and 5+ years of migrator iteration — landed on the same stub-only conclusion for CF7. v1 matches that decision; if user demand surfaces, CF7 mail-template translation is a separate scoped feature. - -### Code pointers (live) - -- **Abstract method**: `Base_Migrator::get_form_metas()` — `inc/migrator/base-migrator.php` -- **Wire-up**: `Base_Migrator::insert_form_post()` / `update_form_post()` both accept a `$metas` arg and pass it to `wp_insert_post()` via `meta_input` -- **CF7 implementation**: `Cf7_Importer::get_form_metas()` — `inc/migrator/importers/cf7-importer.php` -- **SureForms meta schemas** (the contract we have to match): `inc/post-types.php` lines 911-1010 (notification), 1086-1182 (confirmation) - -### Verification - -After importing a CF7 form via the Migration tab, open the resulting SureForms form's settings: -- **Email Notifications** tab → should show one enabled notification named "Admin Notification Email" with subject "New submission: {form title}" and body `{all_data}`. -- **Form Confirmation** tab → should show a centered "Thank you" success message, "Same page" confirmation type. - -If those panels are empty, the meta payload didn't persist — check `wp_options.srfm_imported_forms_map` and `wp_postmeta` for the form id. - -## 17. Out of scope for v1 - -- Polls / Quizzes (Forminator, Gravity surveys) — no SureForms equivalent. -- Formidable Views — front-end data display is its own feature. -- WS Form — niche, defer. -- Multi-step / page break preservation — wait for native `srfm/page-break` block. -- File-upload, signature, rating, repeater fields — wait for native blocks (flagged as unsupported until then). -- Bulk source-selection ("import everything from everywhere") — one source at a time in v1. - -## 18. Sources verified live (May 2026) - -All wordpress.org plugin pages were fetched live during research: - -- [Contact Form 7](https://wordpress.org/plugins/contact-form-7/) — 10M+ installs, v6.1.6 -- [WPForms Lite](https://wordpress.org/plugins/wpforms-lite/) — 6M+ installs, v1.10.0.5 -- [Gravity Forms docs](https://docs.gravityforms.com/form-fields/) — ~655K live sites, commercial -- [Ninja Forms](https://wordpress.org/plugins/ninja-forms/) — 600K+ installs, v3.14.4 -- Caldera Forms — unmaintained since 2022, ~50K legacy sites - -**Combined migration pool: ~17.3M active sites** — the addressable target for v1. diff --git a/docs/wiki/AI-Form-Builder.md b/docs/wiki/AI-Form-Builder.md deleted file mode 100644 index a35a46a4e..000000000 --- a/docs/wiki/AI-Form-Builder.md +++ /dev/null @@ -1,103 +0,0 @@ -# AI Form Builder - -SureForms includes an AI-powered form generator that creates complete forms from natural language descriptions. - -## Architecture - -``` -inc/ai-form-builder/ -|-- ai-form-builder.php # Main AI builder class -|-- ai-auth.php # Authentication and billing -|-- ai-helper.php # AI utility functions -|-- field-mapping.php # Maps AI response to Gutenberg blocks -``` - -## Authentication Flow - -The `AI_Auth` class handles authentication with the SureForms middleware API: - -1. User initiates authentication from admin settings -2. `GET /initiate-auth` REST endpoint generates an auth URL -3. User is redirected to the billing portal (`SRFM_BILLING_PORTAL`) -4. After authentication, an encrypted access key is returned -5. `POST /handle-access-key` decrypts and stores the key in WordPress options -6. Subsequent AI requests use this key for authorization - -### Constants - -| Constant | URL | Purpose | -|----------|-----|---------| -| `SRFM_AI_MIDDLEWARE` | `https://credits.startertemplates.com/sureforms/` | AI credits API | -| `SRFM_MIDDLEWARE_BASE_URL` | `https://api.sureforms.com/` | API middleware | -| `SRFM_BILLING_PORTAL` | `https://billing.sureforms.com/` | Billing portal | - -## Form Generation Process - -### Step 1: User Input - -The user provides a natural language description of the form they want (e.g., "Create a contact form with name, email, phone, and message fields"). - -### Step 2: AI Generation - -`POST /generate-form` sends the description to the AI middleware: - -``` -User prompt -> REST API -> AI_Form_Builder::generate_ai_form() - -> External AI middleware API call - -> AI generates form structure as JSON - -> Returns field definitions -``` - -### Step 3: Field Mapping - -`POST /map-fields` converts the AI response to SureForms Gutenberg blocks: - -``` -AI JSON response -> Field_Mapping::generate_gutenberg_fields_from_questions() - -> Maps each AI field to a SureForms block type - -> Generates block attributes - -> Creates Gutenberg block markup - -> Returns blocks ready for the editor -``` - -### Field Type Mapping - -The `Field_Mapping` class maps AI-suggested field types to SureForms blocks: - -| AI Field Type | SureForms Block | -|---------------|----------------| -| text / name | `srfm/input` | -| email | `srfm/email` | -| phone / tel | `srfm/phone` | -| textarea / message | `srfm/textarea` | -| number | `srfm/number` | -| url / website | `srfm/url` | -| select / dropdown | `srfm/dropdown` | -| checkbox | `srfm/checkbox` | -| radio / multiple choice | `srfm/multichoice` | -| address | `srfm/address` | - -## REST API Endpoints - -| Method | Endpoint | Handler | Description | -|--------|----------|---------|-------------| -| POST | `/generate-form` | `AI_Form_Builder::generate_ai_form` | Send prompt to AI, get form structure | -| POST | `/map-fields` | `Field_Mapping::generate_gutenberg_fields_from_questions` | Convert AI fields to blocks | -| GET | `/initiate-auth` | `AI_Auth::get_auth_url` | Get billing portal auth URL | -| POST | `/handle-access-key` | `AI_Auth::handle_access_key` | Save decrypted access key | - -All endpoints require `manage_options` capability. - -## AI Helper - -The `AI_Helper` class provides utility functions for: -- API key management -- Credit balance checking -- Request formatting -- Error handling for AI middleware responses - -## Related Pages - -- [REST API Reference](REST-API-Reference) -- All API endpoints -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block types the AI maps to -- [Architecture Overview](Architecture-Overview) -- AI classes in bootstrap flow diff --git a/docs/wiki/Admin-Dashboard.md b/docs/wiki/Admin-Dashboard.md deleted file mode 100644 index 9c16aea85..000000000 --- a/docs/wiki/Admin-Dashboard.md +++ /dev/null @@ -1,121 +0,0 @@ -# Admin Dashboard - -The SureForms admin dashboard is a React single-page application (SPA) integrated into the WordPress admin area. - -## Architecture - -### Entry Point - -The admin React app is bootstrapped in `src/admin/editor-scripts.js`, which serves as the Webpack entry point. It mounts the React app to a DOM container registered by the PHP `Admin` class. - -### Tech Stack - -| Technology | Purpose | -|------------|---------| -| React 18 | UI rendering | -| react-router-dom 6 | Client-side routing | -| @tanstack/react-query 5 | Server state management and data fetching | -| @wordpress/api-fetch | REST API client with nonce handling | -| @wordpress/data | WordPress data store integration | -| @bsf/force-ui | BSF component library (buttons, modals, inputs, etc.) | -| Tailwind CSS 3 | Utility-first styling | -| react-hot-toast | Toast notifications | - -### Admin Pages - -The React app includes the following pages/routes: - -| Route | Component | Description | -|-------|-----------|-------------| -| Dashboard | `src/admin/dashboard/` | Overview with charts and stats | -| Forms | `src/admin/forms/` | Forms listing with search, filter, bulk actions | -| Entries | `src/admin/entries/` | Entry listing and management | -| Settings | `src/admin/settings/` | Global plugin settings | -| Payments | `src/admin/payment/` | Payment transactions listing | -| Single Form Settings | `src/admin/single-form-settings/` | Per-form configuration | - -### Shared Components - -Common admin components are in `src/admin/components/`: - -- `PageHeader` -- Consistent page header with title and actions -- `TemplatePicker` -- Form template selection UI -- Loading skeletons (via `react-loading-skeleton`) -- Modal dialogs and confirmation prompts - -## WordPress Integration - -### Menu Registration - -The PHP `Admin` class (`admin/admin.php`) registers the WordPress admin menu: - -```php -add_menu_page( - 'SureForms', - 'SureForms', - 'manage_options', - 'sureforms_menu', - [ $this, 'render_admin_page' ], - 'dashicons-feedback', - 30 -); -``` - -### Asset Enqueuing - -Admin assets are enqueued only on SureForms admin pages: - -- React app bundle (from `assets/build/`) -- Tailwind CSS styles -- Localized data via `wp_localize_script()` providing: - - REST API URL and nonce - - Current user data - - Plugin settings - - Form data - -## Data Fetching - -### @tanstack/react-query - -Used for server state management with automatic caching, refetching, and invalidation: - -```javascript -const { data, isLoading } = useQuery({ - queryKey: ['entries', filters], - queryFn: () => apiFetch({ path: '/sureforms/v1/entries/list', ... }), -}); -``` - -### @wordpress/api-fetch - -WordPress API client with automatic nonce injection: - -```javascript -import apiFetch from '@wordpress/api-fetch'; - -apiFetch({ path: '/sureforms/v1/form-data' }) - .then(data => { /* handle response */ }); -``` - -## Force UI Component Library - -`@bsf/force-ui` is BSF's internal component library providing: - -- Buttons, inputs, selects, toggles -- Modals and dialogs -- Tables and data display -- Navigation components -- Consistent design system - -Components are imported directly: - -```javascript -import { Button, Modal, Input } from '@bsf/force-ui'; -``` - -## Related Pages - -- [State Management](State-Management) -- WordPress data store -- [Frontend Assets](Frontend-Assets) -- Asset loading pipeline -- [REST API Reference](REST-API-Reference) -- API endpoints used by dashboard -- [Environment Configuration](Environment-Configuration) -- Build setup diff --git a/docs/wiki/Architecture-Overview.md b/docs/wiki/Architecture-Overview.md deleted file mode 100644 index 523f18cc8..000000000 --- a/docs/wiki/Architecture-Overview.md +++ /dev/null @@ -1,294 +0,0 @@ -# Architecture Overview - -This page describes the high-level architecture of the SureForms WordPress plugin (v2.5.1), covering the bootstrap flow, namespace conventions, directory layout, design patterns, and data flow. - -## High-Level Architecture Diagram - -``` -+------------------------------------------------------+ -| WordPress Core | -| (hooks: plugins_loaded, init, rest_api_init, etc.) | -+---------------------------+--------------------------+ - | - +--------v---------+ - | sureforms.php | - | (entry point, | - | constants) | - +--------+---------+ - | - +--------v---------+ - | plugin-loader.php| - | (Plugin_Loader) | - | - autoloader | - | - hook setup | - | - class init | - +--------+---------+ - | - +------------------+------------------+ - | | | - plugins_loaded(99) init admin_init - load_plugin() load_classes() activation_redirect() - | | - +------v------+ +------v------+ - | Core Classes| | Admin/Block | - | Post_Types | | Register | - | Form_Submit | | Admin | - | Rest_Api | | Payments | - | Helper | | Duplicate | - | ...20+ more | +-------------+ - +------+------+ - | - +------v------+ +-------------+ +-------------+ - | Database | | REST API | | Frontend | - | Register | | /sureforms/ | | Assets | - | Entries tbl | | v1/... | | Form Markup | - | Payments tbl| +-------------+ +-------------+ - +-------------+ -``` - -## Plugin Bootstrap Flow - -### 1. Entry Point: sureforms.php - -The main plugin file defines all global constants and then requires `plugin-loader.php`: - -```php -define( 'SRFM_FILE', __FILE__ ); -define( 'SRFM_DIR', plugin_dir_path( SRFM_FILE ) ); -define( 'SRFM_URL', plugins_url( '/', SRFM_FILE ) ); -define( 'SRFM_VER', '2.5.1' ); -define( 'SRFM_FORMS_POST_TYPE', 'sureforms_form' ); -define( 'SRFM_ENTRIES', 'sureforms_entries' ); -define( 'SRFM_PAYMENTS', 'sureforms_payments' ); -require_once 'plugin-loader.php'; -``` - -### 2. Plugin Loader: plugin-loader.php - -The `SRFM\Plugin_Loader` class is the central bootstrap orchestrator: - -1. Loads the Action Scheduler library from `inc/lib/action-scheduler/action-scheduler.php` -2. Registers the PSR-4-style autoloader via `spl_autoload_register()` -3. Hooks into WordPress lifecycle events: - - `plugins_loaded` (default priority) -- `load_textdomain()` - - `plugins_loaded` (priority 99) -- `load_plugin()` (main class initialization) - - `init` -- `load_classes()` (block registration, admin, payments) - - `admin_init` -- `activation_redirect()` -4. Instantiates `Analytics` singleton immediately -5. Registers activation hook (`Activator::activate()`) -6. Registers deactivation hook (clears scheduled events) - -The `Plugin_Loader` fires the `srfm_core_loaded` action after first instantiation. - -### 3. Class Initialization Order - -**On `plugins_loaded` (priority 99) via `load_plugin()`:** - -| Order | Class | Responsibility | -|-------|-------|---------------| -| 1 | `Post_Types` | Registers `sureforms_form` CPT and all post meta | -| 2 | `Form_Submit` | Registers `/submit-form` and `/refresh-nonces` REST endpoints | -| 3 | `Gutenberg_Hooks` | Block editor integration hooks | -| 4 | `Frontend_Assets` | Frontend CSS/JS enqueue | -| 5 | `Helper` | Utility/helper functions | -| 6 | `Activator` | First-run setup and migrations | -| 7 | `Admin_Ajax` | WordPress AJAX handlers | -| 8 | `Forms_Data` | Form data queries and listing | -| 9 | `Export` | Import/export functionality | -| 10 | `Smart_Tags` | Template tag processing | -| 11 | `Generate_Form_Markup` | Form HTML rendering | -| 12 | `Create_New_Form` | New form creation flow with default meta | -| 13 | `Global_Settings` | Plugin-wide settings management | -| 14 | `Email_Summary` | Scheduled email digest reports | -| 15 | `Compliance_Settings` | GDPR/privacy per-form settings | -| 16 | `Events_Scheduler` | Cron-based scheduled events | -| 17 | `AI_Form_Builder` | AI-powered form generation | -| 18 | `Field_Mapping` | AI response to Gutenberg block mapping | -| 19 | `Background_Process` | Background task processing via Action Scheduler | -| 20 | `Page_Builders` | Third-party page builder integrations | -| 21 | `Rest_Api` | Admin REST endpoint registration (20+ routes) | -| 22 | `AI_Helper` | AI utility functions | -| 23 | `AI_Auth` | AI authentication and billing portal | -| 24 | `Updater` | Plugin update logic | -| 25 | `Onboarding` | First-run onboarding wizard | -| 26 | `DatabaseRegister` | Custom table creation/migration | -| 27 | `Form_Restriction` | Submission limits, scheduling, and entry caps | -| 28 | `Astra` | Astra theme compatibility layer | - -**On `init` via `load_classes()`:** - -| Class | Condition | Responsibility | -|-------|-----------|---------------| -| `Blocks\Register` | Always | Gutenberg block type registration | -| `Admin` | `is_admin()` only | Admin menu pages, assets, and screens | -| `Notice_Manager` | `is_admin()` only | Admin notice display and management | -| `Payments` | Always | Payment gateway setup (Stripe) | -| `Duplicate_Form` | Always | Form duplication handler | - -## Namespace and Autoloading - -### Namespace Structure - -The root namespace is `SRFM`. All classes follow `SRFM\{directory}\{ClassName}`: - -``` -SRFM\ -> plugin-loader.php -SRFM\Inc\Helper -> inc/helper.php -SRFM\Inc\Rest_Api -> inc/rest-api.php -SRFM\Inc\Form_Submit -> inc/form-submit.php -SRFM\Inc\Database\Base -> inc/database/base.php -SRFM\Inc\Database\Tables\Entries -> inc/database/tables/entries.php -SRFM\Inc\AI_Form_Builder\* -> inc/ai-form-builder/ -SRFM\Inc\Traits\Get_Instance -> inc/traits/get-instance.php -SRFM\Admin\Admin -> admin/admin.php -``` - -### Autoloader Logic - -The autoloader in `Plugin_Loader::autoload()`: - -1. Strips the root `SRFM\` namespace prefix -2. Converts `CamelCase` to `kebab-case` -3. Replaces underscores with hyphens -4. Replaces namespace separators with directory separators -5. Converts to lowercase, prepends `SRFM_DIR`, appends `.php` -6. Rejects any path containing `..` (directory traversal protection) - -## Directory Layout - -``` -sureforms/ -|-- sureforms.php # Plugin entry point, all SRFM_* constants -|-- plugin-loader.php # Autoloader, bootstrap, hook registration -|-- inc/ # Core PHP classes (SRFM\Inc namespace) -| |-- helper.php # Static utility methods -| |-- rest-api.php # REST API endpoint registration -| |-- form-submit.php # Public form submission handler -| |-- post-types.php # sureforms_form CPT and post meta -| |-- entries.php # Entry CRUD operations -| |-- blocks/ # Gutenberg block server-side rendering -| |-- database/ # Custom DB table management -| |-- fields/ # Form field type definitions and validation -| |-- payments/ # Payment gateway integrations (Stripe) -| |-- email/ # Email template rendering -| |-- ai-form-builder/ # AI-powered form generation -| |-- global-settings/ # Plugin-wide settings -| |-- single-form-settings/ # Per-form configuration -| |-- compatibility/ # Theme/plugin compatibility -| |-- page-builders/ # Page builder integrations -| |-- traits/ # Shared PHP traits (Get_Instance) -| |-- lib/ # Bundled third-party libraries (do not modify) -|-- src/ # JS/React source (compiled by wp-scripts) -| |-- admin/ # Admin dashboard React app -| |-- blocks/ # Gutenberg block editor components -| |-- components/ # Shared React components -| |-- store/ # WordPress @wordpress/data store -| |-- utils/ # JS utilities -|-- modules/ # Feature modules (Gutenberg, Quick Action Sidebar) -|-- admin/ # Admin PHP classes and assets -|-- sass/ # SASS source files (compiled to assets/css/) -|-- assets/ # Compiled and static assets -|-- templates/ # PHP templates (BladeOne engine) -|-- tests/ # PHPUnit, Playwright, and Docker setup -``` - -## Design Patterns - -### Singleton via Get_Instance Trait - -Nearly every class uses the `Get_Instance` trait from `inc/traits/get-instance.php`: - -```php -trait Get_Instance { - private static $instance = null; - public static function get_instance() { - if ( null === self::$instance ) { - self::$instance = new self(); - } - return self::$instance; - } -} -``` - -### Abstract Base Class (Database) - -The database layer uses `SRFM\Inc\Database\Base` providing: - -- Table creation via `dbDelta()` -- Schema versioning with integer versions stored in options -- Column migration (`maybe_add_new_columns()`, `maybe_rename_columns()`) -- CRUD wrappers (`use_insert()`, `use_update()`, `use_delete()`) -- Query builder with parameterized WHERE supporting `=`, `!=`, `>`, `<`, `LIKE`, `IN` -- In-memory result caching keyed by MD5 of SQL query -- Schema-driven JSON encoding/decoding for array columns - -### Hook-Based Initialization - -Classes register WordPress hooks in their constructors, invoked during singleton instantiation in `load_plugin()` or `load_classes()`. - -### Filter-Driven Extensibility - -| Filter | Purpose | -|--------|---------| -| `srfm_rest_api_endpoints` | Extend REST API routes | -| `srfm_register_post_meta` | Register additional form post meta | -| `srfm_form_submit_data` | Modify form data before processing | -| `srfm_entry_value` | Transform entry field values | -| `srfm_is_special_block` | Declare custom blocks as special | -| `srfm_handle_special_block` | Custom processing for special blocks | -| `srfm_form_confirmation_params` | Extend form confirmation settings | -| `srfm_normalize_csv_field_value` | Custom CSV export formatting | - -## Data Flow - -``` -1. FORM CREATION - Admin creates form in Gutenberg block editor - -> Saved as sureforms_form CPT (wp_posts) - -> Settings stored as post meta (_srfm_* keys) - -> Block content in post_content column - -2. FORM DISPLAY - Form embedded via shortcode, Gutenberg block, or Instant Form URL - -> Generate_Form_Markup::get_form_markup() renders HTML - -> Frontend_Assets enqueues CSS/JS - -> Submission nonce generated - -3. FORM SUBMISSION - User submits via JavaScript fetch() - -> POST /wp-json/sureforms/v1/submit-form - -> Nonce verification, field validation - -> CAPTCHA validation (reCAPTCHA, hCaptcha, Turnstile) - -> Honeypot spam check - -4. ENTRY STORAGE - -> JSON-encoded form data inserted into wp_srfm_entries - -> Email notifications sent - -> Payment processed if applicable (wp_srfm_payments) - -> Confirmation response returned - -5. POST-SUBMISSION - -> srfm_form_submit and srfm_after_submission_process actions fired -``` - -## Key Constants - -| Constant | Value | Purpose | -|----------|-------|---------| -| `SRFM_FILE` | `__FILE__` | Main plugin file path | -| `SRFM_DIR` | Plugin dir path | File system operations | -| `SRFM_URL` | Plugin URL | Asset URL generation | -| `SRFM_VER` | `2.5.1` | Version for cache busting | -| `SRFM_SLUG` | `srfm` | Short identifier slug | -| `SRFM_FORMS_POST_TYPE` | `sureforms_form` | Custom post type slug | -| `SRFM_ENTRIES` | `sureforms_entries` | Entries identifier | -| `SRFM_PAYMENTS` | `sureforms_payments` | Payments identifier | -| `SRFM_PRO_RECOMMENDED_VER` | `2.5.1` | Minimum Pro version | - -## Related Pages - -- [Database Schema](Database-Schema) -- Custom tables, post types, and meta keys -- [Environment Configuration](Environment-Configuration) -- Development setup, build tools, and linting -- [REST API Reference](REST-API-Reference) -- Full endpoint documentation -- [Form Submission Flow](Form-Submission-Flow) -- Detailed submission pipeline -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- All actions and filters diff --git a/docs/wiki/Block-Editor-Controls.md b/docs/wiki/Block-Editor-Controls.md deleted file mode 100644 index 7d5cd1e33..000000000 --- a/docs/wiki/Block-Editor-Controls.md +++ /dev/null @@ -1,109 +0,0 @@ -# Block Editor Controls - -SureForms provides custom inspector controls and utilities for the Gutenberg block editor experience. - -## Directory Structure - -``` -src/srfm-controls/ -|-- addCommonData.js # Inject common data into blocks -|-- generateCSS.js # Dynamic CSS generation for blocks -|-- block-icons.js # Block icon definitions -|-- fonts.js # Font management utilities -|-- maybeGetColorForVariable.js # CSS variable color resolution -|-- parseIcon.js # SVG icon parser -|-- renderIcon.js # Icon rendering component - -src/blocks-attributes/ -|-- getMetakeys.js # Form meta key retrieval -|-- getFormId.js # Get current form ID -|-- getBlocksDefaultAttributes.js # Block attribute defaults -``` - -## CSS Generation System - -`generateCSS.js` dynamically generates CSS for block styling: - -- Converts block attributes to CSS properties -- Supports responsive breakpoints (desktop, tablet, mobile) -- Generates inline styles injected into the page -- Handles CSS variable references - -This system allows each block instance to have unique styling without class conflicts. - -## Block Icons - -`block-icons.js` provides SVG icon components for all SureForms blocks, used in: - -- Block inserter panel -- Block toolbar -- Block settings sidebar - -Additional icon sets from Spectra are available via `spectra-icons-v*.php` files. - -Icon utilities: -- `parseIcon.js` -- Parses SVG string into React-renderable format -- `renderIcon.js` -- React component for rendering parsed icons - -## Common Data Injection - -`addCommonData.js` injects shared data into block components: - -- Form-level settings accessible to all child blocks -- Common attributes and defaults -- Shared configuration values - -## Meta Key Integration - -`getMetakeys.js` retrieves form post meta keys for use in block controls: - -- Provides meta key names for form settings -- Used by inspector controls to read/write form meta -- Integrates with `@wordpress/data` store - -## Block Attribute Defaults - -`getBlocksDefaultAttributes.js` provides default attribute values for all block types: - -- Ensures consistent defaults across block instances -- Used during block registration -- Prevents undefined attribute errors - -## Form ID Resolution - -`getFormId.js` resolves the current form's post ID: - -- Used by blocks to identify their parent form -- Required for meta key operations -- Supports nested block contexts - -## Field Preview - -`src/blocks/FieldsPreview.jsx` renders a preview of form fields in the block editor: - -- Shows a visual representation of the field in editor mode -- Responds to attribute changes in real-time -- Handles edit vs preview states - -## Font Management - -`fonts.js` manages font loading for form styling: - -- Font family selection in block settings -- Google Fonts integration -- Font weight and style options - -## Color Variable Resolution - -`maybeGetColorForVariable.js` resolves WordPress CSS custom properties: - -- Maps color slugs to CSS variable references -- Supports theme color palette integration -- Falls back to raw color values when variables are not available - -## Related Pages - -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block types and registration -- [State Management](State-Management) -- WordPress data store -- [Frontend Assets](Frontend-Assets) -- Editor asset loading -- [Form Fields Architecture](Form-Fields-Architecture) -- Field types diff --git a/docs/wiki/Changelog.md b/docs/wiki/Changelog.md deleted file mode 100644 index 058c89f45..000000000 --- a/docs/wiki/Changelog.md +++ /dev/null @@ -1,39 +0,0 @@ -# Changelog - -Recent changes to SureForms. For the full changelog, see [sureforms.com/whats-new](https://sureforms.com/whats-new/). - -## 2.5.1 - 16th February 2026 - -- **New:** Added option to change currency position (e.g., $100, 100$, $ 100, 100 $) -- **Fix:** Fixed the payment block amount not updating correctly with conditional logic - -## 2.5.0 - 2nd February 2026 - -- **Improvement:** Added translation support for the country list in the phone number field -- **Fix:** Fixed dropdown placeholder and GDPR label translation on the frontend - -## 2.4.0 - 20th January 2026 - -- **New:** Added form scheduling restrictions with start and end date/time -- **New:** Added Previous/Next navigation for single entry page -- **Improvement:** Improved multi-choice block settings UI -- **Improvement:** Updated Syria flag in the Phone field -- **Fix:** Fixed HTML block content corruption when duplicating forms -- **Fix:** Fixed multi-choice options being translated in entries -- **Fix:** Fixed reCAPTCHA v3 validation triggering multiple times on the same page -- **Fix:** Fixed Spectra button styles being affected by SureForms CSS - -## Version Bumping - -When preparing a release, update the version in: - -1. `sureforms.php` -- Plugin header `Version:` and `SRFM_VER` constant -2. `readme.txt` -- `Stable tag:` and changelog section -3. `package.json` -- `version` field - -See [Deployment Guide](Deployment-Guide) for the full release process. - -## Related Pages - -- [Deployment Guide](Deployment-Guide) -- Release workflow and CI/CD -- [Contributing Guide](Contributing-Guide) -- Development workflow diff --git a/docs/wiki/Contributing-Guide.md b/docs/wiki/Contributing-Guide.md deleted file mode 100644 index 441736c7e..000000000 --- a/docs/wiki/Contributing-Guide.md +++ /dev/null @@ -1,205 +0,0 @@ -# Contributing Guide - -Thank you for your interest in contributing to SureForms! This guide covers the development workflow, coding standards, and submission process. - -## Prerequisites - -- **PHP** 7.4+ -- **Node.js** 24.16.0 (managed via [Volta](https://volta.sh/)) -- **Composer** for PHP dependencies -- **Git** for version control -- **Docker** (optional, for wp-env test environment) - -## Getting Started - -1. Fork the repository on GitHub -2. Clone your fork: - ```bash - git clone https://github.com/YOUR_USERNAME/sureforms.git - cd sureforms - ``` -3. Install dependencies: - ```bash - composer install - npm install - ``` -4. Create a feature branch from `next-release`: - ```bash - git checkout next-release - git pull origin next-release - git checkout -b feature/your-feature-name - ``` - -## Git Workflow - -``` -feature/branch --> next-release --> master --> tag/release - (development) (production) -``` - -- **Feature branches** are created from `next-release` -- **Pull requests** target `next-release` -- **Hotfixes** may target `master` directly -- **Release merges** go from `next-release` into `master` - -## Development Workflow - -### Running the Dev Server - -```bash -npm start # Start wp-scripts dev server (hot reload) -``` - -### Building - -```bash -npm run build # Full production build (JS + SASS + minify) -npm run build:script # Build JS only -npm run build:sass # Build SASS only -``` - -### Linting - -Run linters before submitting a PR: - -```bash -# JavaScript -npm run lint-js # ESLint check -npm run lint-js:fix # Auto-fix JS issues - -# CSS -npm run lint-css # Stylelint check -npm run lint-css:fix # Auto-fix CSS issues - -# Formatting -npm run pretty # Prettier check -npm run pretty:fix # Auto-format - -# PHP -vendor/bin/phpcs # PHP CodeSniffer (WPCS) -vendor/bin/phpcbf # Auto-fix PHPCS issues -``` - -### Testing - -All tests should pass before submitting a PR: - -```bash -# PHP unit tests -vendor/bin/phpunit - -# JavaScript unit tests -npm run test:unit - -# E2E tests (requires wp-env) -npm run play:up # Start environment -npm run play:run # Run Playwright tests -``` - -See [Testing Guide](Testing-Guide) for detailed test setup. - -## Coding Standards - -### PHP Standards - -SureForms follows **WordPress Coding Standards (WPCS)** enforced via `phpcs.xml`: - -- **Escaping**: Use `esc_html()`, `esc_attr()`, `esc_url()`, `wp_kses_post()` for all output -- **Nonces**: Use `wp_nonce_field()` / `check_ajax_referer()` for form submissions and AJAX -- **Capabilities**: Always check `current_user_can()` before privileged operations -- **Sanitization**: Use `sanitize_text_field()`, `absint()`, `wp_kses()` for all input -- **Prefixes**: `SRFM_` for constants, `srfm_` for function names and handles -- **Text domain**: `sureforms` for all translatable strings -- **PHP compatibility**: 7.4+ (enforced via `PHPCompatibilityWP`) - -### JavaScript Standards - -- Follow `@wordpress/scripts` ESLint configuration -- Use `@wordpress/data` store for state management -- Use `@wordpress/api-fetch` for REST API calls -- Import WordPress dependencies from `@wordpress/*` packages -- Use Force UI (`@bsf/force-ui`) components in admin dashboard pages - -### CSS Standards - -- **Admin dashboard**: Tailwind CSS 3.x utility classes -- **Block frontend**: SASS files in `sass/` directory -- **Class prefix**: `srfm-` for all frontend CSS classes -- Do not edit generated CSS in `assets/css/` -- edit SASS source files instead - -## Pull Request Process - -### PR Checklist - -Before submitting your PR, verify: - -- [ ] Code follows the coding standards (PHP, JS, CSS) -- [ ] All linters pass without errors -- [ ] Tests pass (PHPUnit, Jest) -- [ ] New features include tests -- [ ] Translatable strings use the `sureforms` text domain -- [ ] No debug code, `console.log`, or `var_dump` statements -- [ ] No secrets or credentials committed -- [ ] Build completes without errors (`npm run build`) - -### PR Title Format - -PR titles are validated by CI. Use a clear, descriptive title that summarizes the change. - -### CI Checks - -Pull requests automatically run: - -1. **PHPUnit** -- PHP unit tests -2. **PHPStan** -- Static analysis -3. **PHP Insights** -- Code quality -4. **PR Title Validation** -- Title format check -5. **E2E Tests** -- When the PR is labeled with `e2e` - -All checks must pass before merge. - -### Code Review - -- PRs require at least one approving review -- Address all review comments -- Keep PRs focused on a single feature or fix -- Large changes should be discussed in an issue first - -## Directory Guide for Contributors - -| Directory | Purpose | Edit? | -|-----------|---------|-------| -| `src/` | JS/React source code | Yes | -| `sass/` | SASS stylesheets | Yes | -| `inc/` | PHP classes and logic | Yes | -| `tests/` | Test files | Yes | -| `build/` | Compiled JS output | No (generated) | -| `assets/css/` | Compiled CSS output | No (generated) | -| `assets/js/minified/` | Minified JS | No (generated) | -| `inc/lib/` | Bundled third-party libraries | No | -| `node_modules/` | NPM packages | No | -| `vendor/` | Composer packages | No | - -## Adding a New Gutenberg Block - -See [Gutenberg Blocks](Gutenberg-Blocks) for the full guide on adding blocks, including: - -- Block registration (server-side and client-side) -- `block.json` schema -- Editor and frontend rendering - -## Adding a New REST Endpoint - -See [REST API Reference](REST-API-Reference) for endpoint patterns and the registration system. - -## Reporting Issues - -- Use [GitHub Issues](https://github.com/brainstormforce/sureforms/issues) for bug reports and feature requests -- Security vulnerabilities should be reported through the [Bug Bounty Program](https://brainstormforce.com/bug-bounty-program/) - -## Related Pages - -- [Environment Configuration](Environment-Configuration) -- Development setup details -- [Architecture Overview](Architecture-Overview) -- Codebase structure -- [Testing Guide](Testing-Guide) -- Test framework details -- [Deployment Guide](Deployment-Guide) -- Release process diff --git a/docs/wiki/Data-Export-Import.md b/docs/wiki/Data-Export-Import.md deleted file mode 100644 index fc07271ae..000000000 --- a/docs/wiki/Data-Export-Import.md +++ /dev/null @@ -1,120 +0,0 @@ -# Data Export & Import - -SureForms supports exporting and importing forms and entries for backup, migration, and data analysis. - -## Form Export - -### REST Endpoint - -`POST /wp-json/sureforms/v1/forms/export` - -**Parameters:** -- `form_ids` (required) -- Array of form IDs to export - -**Response:** JSON file containing form data including: -- Form post data (title, content, status) -- All form post meta (`_srfm_*` keys) -- Block content (Gutenberg blocks) - -### Export Format - -Forms are exported as a JSON structure containing all data needed to recreate the form on another site. - -## Form Import - -### REST Endpoint - -`POST /wp-json/sureforms/v1/forms/import` - -**Parameters:** -- File upload containing exported JSON data - -**Process:** -1. Validates the uploaded JSON structure -2. Creates new `sureforms_form` posts -3. Restores all post meta keys -4. Assigns new post IDs (no ID conflicts) -5. Returns count of imported forms - -## Form Duplication - -### REST Endpoint - -`POST /wp-json/sureforms/v1/forms/duplicate` - -**Parameters:** -- `form_id` (required) -- ID of the form to duplicate - -**Process:** - -Handled by `Duplicate_Form` class (`inc/duplicate-form.php`): - -1. Reads the original form post and all meta -2. Creates a new post with "(Copy)" appended to the title -3. Copies all `_srfm_*` post meta to the new form -4. Returns the new form ID - -## Entry Export - -### REST Endpoint - -`POST /wp-json/sureforms/v1/entries/export` - -**Parameters:** -- `entry_ids` -- Specific entry IDs to export (optional) -- `form_id` -- Export all entries for a specific form -- `status` -- Filter by status (all, read, unread) -- `search` -- Search filter -- `date_from` / `date_to` -- Date range filter - -**Response:** CSV file (or ZIP for large exports) - -### Export Format - -The `Export` class (`inc/export.php`) generates CSV with: -- One row per entry -- Columns for each form field -- Submission metadata (date, status, IP) -- Field values normalized via `srfm_normalize_csv_field_value` filter - -### CSV Field Value Normalization - -The `srfm_normalize_csv_field_value` filter allows customizing how field values appear in CSV exports: - -```php -add_filter( 'srfm_normalize_csv_field_value', function( $value, $field ) { - // Custom formatting for specific field types - return $value; -}, 10, 2 ); -``` - -## Form Lifecycle Management - -### REST Endpoint - -`POST /wp-json/sureforms/v1/forms/manage` - -**Parameters:** -- `form_ids` (required) -- Array of form IDs -- `action` (required) -- `trash`, `restore`, or `delete` - -| Action | Behavior | -|--------|----------| -| `trash` | Move forms to trash (recoverable) | -| `restore` | Restore trashed forms | -| `delete` | Permanently delete forms | - -## Create New Form - -The `Create_New_Form` class (`inc/create-new-form.php`) handles new form creation with default meta values: - -- Sets default styling options -- Configures default submit button text -- Initializes email notification defaults -- Applies default form confirmation settings - -## Related Pages - -- [REST API Reference](REST-API-Reference) -- All export/import endpoints -- [Database Schema](Database-Schema) -- Entry and form data structure -- [Admin Dashboard](Admin-Dashboard) -- UI for export/import operations diff --git a/docs/wiki/Database-Schema.md b/docs/wiki/Database-Schema.md deleted file mode 100644 index c0a47044b..000000000 --- a/docs/wiki/Database-Schema.md +++ /dev/null @@ -1,173 +0,0 @@ -# Database Schema - -SureForms uses two custom database tables alongside the WordPress `sureforms_form` custom post type. All custom tables use the `wp_srfm_` prefix. - -## Table Relationships - -``` -+-------------------+ +-------------------+ -| wp_posts | | wp_srfm_entries | -| (sureforms_form) |<---------| form_id | -| ID (PK) | | | ID (PK) | -| post_content | | | user_id | -| post_meta (_srfm)| | | form_data (JSON) | -+-------------------+ | | status | - | +--------+-----------+ - | | - | +--------v-----------+ - | | wp_srfm_payments | - +----| form_id | - | id (PK) | - | entry_id (FK) | - | transaction_id | - | total_amount | - +--------------------+ -``` - -## Custom Tables - -### wp_srfm_entries - -Stores all form submissions. Table version: 1. - -| Column | Type | Default | Description | -|--------|------|---------|-------------| -| `ID` | BIGINT(20) UNSIGNED | AUTO_INCREMENT | Primary key | -| `form_id` | BIGINT(20) UNSIGNED | -- | Reference to sureforms_form post ID | -| `user_id` | BIGINT(20) UNSIGNED | 0 | WordPress user ID (0 = anonymous) | -| `status` | VARCHAR(10) | `unread` | Entry status: `unread`, `read`, `trash` | -| `type` | VARCHAR(20) | -- | Form type: `quiz`, `standard`, etc. | -| `form_data` | LONGTEXT | `[]` | JSON-encoded submitted field values | -| `submission_info` | LONGTEXT | `[]` | JSON-encoded browser/device/IP metadata | -| `notes` | LONGTEXT | `[]` | JSON-encoded admin notes | -| `logs` | LONGTEXT | `[]` | JSON-encoded activity log entries | -| `extras` | LONGTEXT | `[]` | JSON-encoded misc additional data | -| `created_at` | TIMESTAMP | CURRENT_TIMESTAMP | Submission timestamp | -| `updated_at` | TIMESTAMP | CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes:** -- `PRIMARY KEY (ID)` -- `INDEX idx_form_id (form_id)` -- `INDEX idx_user_id (user_id)` -- `INDEX idx_form_id_created_at_status (form_id, created_at, status)` -- composite index for list queries - -**Log Entry Structure:** -```json -{ - "title": "Form Submitted", - "messages": ["Email notification sent", "Entry stored"], - "timestamp": "2024-01-15 10:30:00" -} -``` - -### wp_srfm_payments - -Stores payment transaction records linked to form entries. Table version: 1. Added in v2.0.0. - -| Column | Type | Default | Description | -|--------|------|---------|-------------| -| `id` | BIGINT(20) UNSIGNED | AUTO_INCREMENT | Primary key | -| `form_id` | BIGINT(20) UNSIGNED | -- | Reference to sureforms_form post | -| `block_id` | VARCHAR(255) | `''` | Payment block identifier | -| `status` | VARCHAR(50) | `pending` | Payment status (see below) | -| `total_amount` | DECIMAL(26,8) | `0.00000000` | Total amount after discount | -| `refunded_amount` | DECIMAL(26,8) | `0.00000000` | Total refunded amount | -| `currency` | VARCHAR(10) | `''` | ISO 4217 currency code | -| `entry_id` | BIGINT(20) UNSIGNED | 0 | Reference to entries table | -| `gateway` | VARCHAR(20) | `''` | Payment gateway: `stripe` | -| `type` | VARCHAR(30) | `''` | Payment type: `payment`, `subscription`, `renewal` | -| `mode` | VARCHAR(20) | `''` | Mode: `test` or `live` | -| `transaction_id` | VARCHAR(50) | `''` | Gateway transaction ID | -| `customer_id` | VARCHAR(50) | `''` | Gateway customer ID | -| `subscription_id` | VARCHAR(50) | `''` | Subscription ID (if recurring) | -| `subscription_status` | VARCHAR(20) | `''` | Subscription status | -| `parent_subscription_id` | BIGINT(20) UNSIGNED | 0 | Parent subscription payment ID | -| `payment_data` | LONGTEXT | `[]` | JSON-encoded payment details (includes refunds) | -| `extra` | LONGTEXT | `[]` | JSON-encoded additional data | -| `log` | LONGTEXT | `[]` | JSON-encoded payment log | -| `created_at` | TIMESTAMP | CURRENT_TIMESTAMP | Creation timestamp | -| `updated_at` | TIMESTAMP | CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | -| `srfm_txn_id` | VARCHAR(100) | `''` | Custom transaction ID format | -| `customer_email` | VARCHAR(255) | `''` | Customer email | -| `customer_name` | VARCHAR(255) | `''` | Customer name | - -**Valid Payment Statuses:** -`pending`, `succeeded`, `failed`, `canceled`, `requires_action`, `requires_payment_method`, `processing`, `refunded`, `partially_refunded` - -**Valid Subscription Statuses:** -`active`, `canceled`, `past_due`, `unpaid`, `trialing`, `incomplete`, `incomplete_expired`, `paused` - -**Supported Currencies:** -USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, SEK, NZD, MXN, SGD, HKD, NOK, TRY, RUB, INR, BRL, ZAR, KRW - -## WordPress Post Type: sureforms_form - -Forms are stored as a custom post type (`sureforms_form`) in the standard `wp_posts` table. Block content is in `post_content`, and form configuration is stored as post meta with `_srfm_` prefixed keys. - -### Key Post Meta Keys - -| Meta Key | Type | Description | -|----------|------|-------------| -| `_srfm_forms_styling` | string | Form styling mode | -| `_srfm_color` | string | Primary form color | -| `_srfm_bg_color` | string | Background color | -| `_srfm_fontsize` | string | Font size | -| `_srfm_label_color` | string | Label text color | -| `_srfm_input_color` | string | Input text color | -| `_srfm_submit_button_text` | string | Submit button text | -| `_srfm_submit_alignment` | string | Button alignment | -| `_srfm_submit_width` | string | Button width | -| `_srfm_submit_width_backend` | string | Backend button width | -| `_srfm_is_inline_button` | boolean | Inline button mode | -| `_srfm_email_notification` | array | Email notification settings | -| `_srfm_form_confirmation` | array | Confirmation settings (message/redirect) | -| `_srfm_captcha` | string | CAPTCHA type (recaptcha_v2, v3, hcaptcha, turnstile) | -| `_srfm_honeypot` | boolean | Honeypot spam protection | -| `_srfm_is_page_break` | boolean | Multi-step form enabled | -| `_srfm_single_page_form_title` | boolean | Show title on instant form | -| `_srfm_instant_form_settings` | array | Instant form configuration | -| `_srfm_page_form_logo` | string | Form logo URL | -| `_srfm_submit_type` | string | Submission type | - -Post meta is extensible via the `srfm_register_post_meta` filter. - -## Database Base Class - -All custom tables extend `SRFM\Inc\Database\Base`, which provides: - -### Schema Versioning -- Each table has a `$table_version` integer -- Versions stored in `srfm_database_table_versions` option -- On version mismatch, `start_db_upgrade()` runs migrations (new columns, renames) - -### Query Builder -```php -// Simple equality -$results = $instance->get_results(['form_id' => 123]); - -// Complex conditions with operators -$results = $instance->get_results([ - [ - ['key' => 'status', 'compare' => '!=', 'value' => 'trash'], - ['key' => 'created_at', 'compare' => '>', 'value' => '2024-01-01'], - ] -]); - -// IN operator -$results = $instance->get_results([ - [['key' => 'ID', 'compare' => 'IN', 'value' => [1, 2, 3]]] -]); -``` - -### Caching -Results are cached in-memory using MD5 of the SQL query as the cache key. Cache is reset on insert, update, or delete operations. - -### Data Encoding -Array-type columns (defined in `get_schema()`) are automatically JSON-encoded on write and decoded on read. Number types are cast to integers. - -## Related Pages - -- [Architecture Overview](Architecture-Overview) -- Plugin bootstrap and class initialization -- [Payment Integration](Payment-Integration) -- Stripe payment flow -- [Form Submission Flow](Form-Submission-Flow) -- Entry creation pipeline -- [REST API Reference](REST-API-Reference) -- API endpoints for entries and payments diff --git a/docs/wiki/Deployment-Guide.md b/docs/wiki/Deployment-Guide.md deleted file mode 100644 index 1511856ec..000000000 --- a/docs/wiki/Deployment-Guide.md +++ /dev/null @@ -1,109 +0,0 @@ -# Deployment Guide - -## Release Process Overview - -SureForms follows a branch-based release workflow with automated CI/CD via GitHub Actions. - -## Git Workflow - -``` -feature/branch --> next-release --> master --> tag/release - (development) (production) -``` - -1. Feature branches are created from `next-release` -2. PRs target `next-release` for review and merge -3. `next-release` is merged to `master` for releases -4. Tags trigger release artifacts - -## Build Steps - -### Production Build - -```bash -# Full production build (JS + SASS + minify) -npm run build - -# Individual steps: -npm run build:script # Build JS via wp-scripts -npm run build:sass # Compile SASS via Grunt -``` - -The build process: -1. Webpack bundles JS/React source from `src/` to `assets/build/` -2. Grunt compiles SASS from `sass/` to `assets/css/` -3. Grunt minifies CSS to `assets/css/minified/` -4. Grunt minifies JS to `assets/js/minified/` -5. PostCSS applies Autoprefixer - -### i18n - -```bash -npm run makepot # Generate .pot translation file -``` - -## CI/CD Pipelines - -### GitHub Actions Workflows - -| Workflow | Trigger | Purpose | -|----------|---------|---------| -| `phpunit.yml` | Pull requests | Run PHP unit tests | -| `playwright.yml` | PR labeled `e2e` | Run E2E browser tests | -| `code-analysis.yml` | Pull requests | PHPStan static analysis | -| `insights.yml` | Pull requests | PHP Insights code quality | -| `pr-title.yml` | Pull requests | Validate PR title format | -| `push-to-deploy.yml` | Push to master | Deploy to production | -| `push-asset-readme-update.yml` | Push to master | Update readme/assets | -| `release-tag-draft.yml` | Tag push | Create draft GitHub release | -| `release-pr-template.yml` | Release PRs | Apply release PR template | -| `claude-code-review.yml` | Pull requests | AI-powered code review | - -### Test Pipeline - -Pull requests automatically run: -1. PHPUnit tests (PHP 8.2, WordPress trunk) -2. PHPStan static analysis -3. PHP Insights code quality -4. PR title validation -5. E2E tests (when labeled `e2e`) - -### Deployment Pipeline - -On push to `master`: -1. `push-to-deploy.yml` triggers deployment -2. `push-asset-readme-update.yml` updates WordPress.org assets - -### Release Pipeline - -1. Merge `next-release` into `master` -2. Create a version tag (e.g., `v2.5.1`) -3. `release-tag-draft.yml` creates a draft GitHub release -4. Review and publish the release - -## Pre-Deployment Checklist - -- [ ] All tests pass (PHPUnit, Playwright, Jest) -- [ ] PHPStan analysis clean -- [ ] PHPCS standards pass -- [ ] Production build completes without errors -- [ ] Version bumped in `sureforms.php` and `readme.txt` -- [ ] `SRFM_VER` constant updated -- [ ] `SRFM_PRO_RECOMMENDED_VER` checked -- [ ] Changelog updated in `readme.txt` -- [ ] Translation .pot file regenerated -- [ ] No debug code or console.log statements -- [ ] Database migrations tested (if any) - -## Version Bumping - -Update version in these files: -1. `sureforms.php` -- Plugin header `Version:` and `SRFM_VER` constant -2. `readme.txt` -- `Stable tag:` and changelog section -3. `package.json` -- `version` field - -## Related Pages - -- [Testing Guide](Testing-Guide) -- Test setup and execution -- [Contributing Guide](Contributing-Guide) -- Development workflow -- [Environment Configuration](Environment-Configuration) -- Build tools diff --git a/docs/wiki/Email-Notifications.md b/docs/wiki/Email-Notifications.md deleted file mode 100644 index 642751bcb..000000000 --- a/docs/wiki/Email-Notifications.md +++ /dev/null @@ -1,86 +0,0 @@ -# Email Notifications - -SureForms sends email notifications on form submission and supports scheduled email summary digests. - -## Email System Overview - -``` -Form Submission - -> Form_Submit::handle_form_submission() - -> Email_Template (inc/email/email-template.php) - -> Smart_Tags resolution - -> wp_mail() dispatch -``` - -## Email Template - -The `Email_Template` class (`inc/email/email-template.php`) handles: - -- HTML email template rendering -- Dynamic content insertion via smart tags -- Multiple notification recipients -- Customizable subject lines and body content -- Reply-to address configuration - -### Per-Form Email Settings - -Email notifications are configured per-form via the `_srfm_email_notification` post meta key. Each form can have: - -- **To** -- Recipient email addresses (supports multiple, comma-separated) -- **Subject** -- Email subject line (supports smart tags) -- **Body** -- Email content (supports smart tags and HTML) -- **Reply-To** -- Reply-to email address -- **CC/BCC** -- Additional recipients - -## Smart Tags - -The `Smart_Tags` class (`inc/smart-tags.php`) provides template variables that are resolved at email send time: - -| Smart Tag | Description | -|-----------|-------------| -| `{admin_email}` | WordPress admin email | -| `{site_title}` | WordPress site title | -| `{site_url}` | WordPress site URL | -| `{form_title}` | Name of the submitted form | -| `{form_id}` | ID of the submitted form | -| `{entry_id}` | ID of the created entry | -| `{all_fields}` | All submitted field values formatted | -| `{field:field_name}` | Specific field value by field name | -| `{user_ip}` | Submitter's IP address | -| `{user_agent}` | Submitter's browser user agent | -| `{submission_date}` | Date of submission | - -Smart tags are processed by replacing the tag placeholders with actual values from the form submission data. - -## Email Summary - -The `Email_Summary` class (`inc/global-settings/email-summary.php`) provides scheduled digest reports: - -- Configurable frequency (daily, weekly, monthly) -- Summary of form submissions over the period -- Entry counts per form -- Sent to configured admin email addresses -- Uses WordPress cron (`Events_Scheduler`) for scheduling - -## Customization - -### Modifying Email Content - -Email content is filtered through smart tag processing, allowing dynamic content: - -```php -// In per-form settings, the email body supports: -// - Smart tags: {form_title}, {all_fields}, etc. -// - HTML formatting -// - Custom text -``` - -### Extending Smart Tags - -Smart tags can be extended by hooking into the tag resolution process to add custom replacements. - -## Related Pages - -- [Form Submission Flow](Form-Submission-Flow) -- When emails are triggered -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- Email-related hooks -- [Architecture Overview](Architecture-Overview) -- Email_Template initialization diff --git a/docs/wiki/Environment-Configuration.md b/docs/wiki/Environment-Configuration.md deleted file mode 100644 index b4a89da2f..000000000 --- a/docs/wiki/Environment-Configuration.md +++ /dev/null @@ -1,197 +0,0 @@ -# Environment Configuration - -## Prerequisites - -| Requirement | Version | Notes | -|-------------|---------|-------| -| PHP | 7.4+ | WordPress minimum requirement | -| WordPress | 6.4+ | Gutenberg block editor required | -| Node.js | 24.16.0 | Managed via Volta (see `package.json`) | -| Composer | 2.x | PHP dependency management | -| npm | 11.x+ | Comes with Node 24 | - -## Plugin Constants - -Defined in `sureforms.php`: - -| Constant | Value | Purpose | -|----------|-------|---------| -| `SRFM_FILE` | `__FILE__` | Absolute path to main plugin file | -| `SRFM_BASENAME` | `sureforms/sureforms.php` | Plugin basename for hooks | -| `SRFM_DIR` | Plugin directory path | File system path operations | -| `SRFM_URL` | Plugin URL | Asset URL generation | -| `SRFM_VER` | `2.5.1` | Version for cache busting | -| `SRFM_SLUG` | `srfm` | Short identifier slug | -| `SRFM_FORMS_POST_TYPE` | `sureforms_form` | Custom post type slug | -| `SRFM_ENTRIES` | `sureforms_entries` | Entries identifier | -| `SRFM_PAYMENTS` | `sureforms_payments` | Payments identifier | -| `SRFM_WEBSITE` | `https://sureforms.com/` | Official website | -| `SRFM_AI_MIDDLEWARE` | `https://credits.startertemplates.com/sureforms/` | AI credits API | -| `SRFM_MIDDLEWARE_BASE_URL` | `https://api.sureforms.com/` | API middleware | -| `SRFM_BILLING_PORTAL` | `https://billing.sureforms.com/` | Billing portal | -| `SRFM_PRO_RECOMMENDED_VER` | `2.5.1` | Minimum recommended Pro version | -| `SRFM_SURETRIGGERS_INTEGRATION_BASE_URL` | `https://app.ottokit.com/` | OttoKit integration | - -## Development Environment Setup - -### Option 1: wp-env (Docker) - -```bash -# Start the WordPress environment -npm run play:up - -# Stop the environment -npm run play:stop - -# Clean environment data -npm run env:clean -``` - -Configuration is in `.wp-env.json`. Requires Docker to be installed. - -### Option 2: Local by Flywheel / MAMP / XAMPP - -1. Set up a local WordPress installation -2. Clone the repo into `wp-content/plugins/sureforms` -3. Run `composer install` for PHP dependencies -4. Run `npm install` for JS dependencies -5. Run `npm start` for development with hot reload - -## Build Tools - -### wp-scripts (JavaScript) - -Primary build tool for JS/React compilation via Webpack: - -```bash -npm start # Dev server with hot reload -npm run build:script # Production JS build -``` - -Entry points are defined in `webpack.config.js` and output to `assets/build/`. - -### Grunt (SASS / Minification) - -```bash -npm run build:sass # Compile SASS to CSS -npm run build # Full build (JS + SASS + minify) -``` - -The `Gruntfile.js` handles: -- SASS compilation (`sass/` -> `assets/css/`) -- CSS minification -- JS minification -- PostCSS with Autoprefixer - -### Tailwind CSS - -Used for admin dashboard styles. Configuration in `tailwind.config.js`: -- Content scanning paths: `src/admin/**/*.{js,jsx}` -- Plugin: `@tailwindcss/forms` -- Custom theme extensions for Force UI integration - -### PostCSS - -Configuration in `postcss.config.js`: -- Autoprefixer for browser compatibility -- cssnano for production minification - -## NPM Scripts Reference - -| Script | Command | Description | -|--------|---------|-------------| -| `start` | `wp-scripts start` | Dev server with hot reload | -| `build` | Full pipeline | JS + SASS + minify | -| `build:script` | `wp-scripts build` | Build JS only | -| `build:sass` | Grunt sass tasks | Build SASS only | -| `lint-js` | `wp-scripts lint-js` | ESLint check | -| `lint-js:fix` | `wp-scripts lint-js --fix` | Auto-fix JS lint | -| `lint-css` | `wp-scripts lint-style` | Stylelint check | -| `lint-css:fix` | `wp-scripts lint-style --fix` | Auto-fix CSS lint | -| `pretty` | `prettier --check` | Prettier check | -| `pretty:fix` | `prettier --write` | Prettier format | -| `test:unit` | `wp-scripts test-unit-js` | Jest unit tests | -| `play:up` | `wp-env start` | Start Docker env | -| `play:stop` | `wp-env stop` | Stop Docker env | -| `play:run` | `playwright test` | Run E2E tests | -| `play:run:interactive` | `playwright test --headed` | E2E tests (headed) | -| `env:clean` | `wp-env clean` | Clean Docker data | -| `makepot` | `wp i18n make-pot` | Generate .pot file | - -## Composer Scripts - -| Script | Description | -|--------|-------------| -| `vendor/bin/phpunit` | Run PHPUnit tests | -| `vendor/bin/phpcs` | PHP CodeSniffer (WPCS) | -| `vendor/bin/phpcbf` | Auto-fix PHPCS issues | -| `vendor/bin/phpstan` | PHPStan static analysis | - -## Linting Configuration - -### PHP (PHPCS) - -Configured via `phpcs.xml`: -- Ruleset: WordPress Coding Standards (WPCS) -- PHPCompatibility for PHP 7.4+ -- Text domain: `sureforms` -- Prefixes: `srfm`, `SRFM_` - -### JavaScript (ESLint) - -Uses `@wordpress/scripts` ESLint configuration which extends WordPress defaults. - -### CSS (Stylelint) - -WordPress Stylelint configuration via `@wordpress/scripts`. - -### Prettier - -Code formatting for JS/CSS files. - -## Volta Configuration - -Node version is pinned in `package.json`: - -```json -{ - "volta": { - "node": "24.16.0" - } -} -``` - -Install Volta to automatically use the correct Node version when entering the project directory. - -## Key Dependencies - -### PHP (Composer) - -| Package | Purpose | -|---------|---------| -| `yoast/phpunit-polyfills` | PHPUnit compatibility | -| `squizlabs/php_codesniffer` | Code style checking | -| `wp-coding-standards/wpcs` | WordPress standards | -| `phpcompatibility/php-compatibility` | PHP version checks | -| `phpstan/phpstan` | Static type analysis | -| `nunomaduro/phpinsights` | Code quality insights | - -### JavaScript (npm) - -| Package | Purpose | -|---------|---------| -| `@bsf/force-ui` | BSF internal UI component library | -| `@wordpress/scripts` | Build tooling | -| `@wordpress/api-fetch` | REST API client | -| `@tanstack/react-query` | Data fetching/caching | -| `react-router-dom` | Client-side routing | -| `tailwindcss` | Utility-first CSS | -| `@playwright/test` | E2E testing | -| `@wordpress/env` | Docker test environment | - -## Related Pages - -- [Architecture Overview](Architecture-Overview) -- Plugin structure and bootstrap -- [Testing Guide](Testing-Guide) -- Test setup and execution -- [Contributing Guide](Contributing-Guide) -- Coding standards and workflow -- [Frontend Assets](Frontend-Assets) -- Asset pipeline details diff --git a/docs/wiki/Form-Fields-Architecture.md b/docs/wiki/Form-Fields-Architecture.md deleted file mode 100644 index 285f4793c..000000000 --- a/docs/wiki/Form-Fields-Architecture.md +++ /dev/null @@ -1,137 +0,0 @@ -# Form Fields Architecture - -SureForms uses a layered architecture for form fields: Gutenberg blocks define the editor experience, field markup classes generate the frontend HTML, and the validation system ensures data integrity on submission. - -## Field Type Hierarchy - -``` -Fields\Base (inc/fields/base.php) - |-- Input_Markup (text, hidden) - |-- Email_Markup (email with validation) - |-- Textarea_Markup (multi-line text) - |-- Number_Markup (numeric with min/max) - |-- Phone_Markup (with country selector) - |-- URL_Markup (URL with validation) - |-- Dropdown_Markup (select options) - |-- Checkbox_Markup (single/multiple) - |-- Multichoice_Markup (radio/checkbox groups) - |-- Address_Markup (composite address field) - |-- GDPR_Markup (consent checkbox) - |-- Payment_Markup (Stripe payment field) - |-- Inlinebutton_Markup (inline buttons) -``` - -## Base Field Class - -`inc/fields/base.php` provides the foundation: - -- Common HTML wrapper generation -- Label rendering with required indicator -- Help text / description output -- Error message container -- CSS class assembly with `srfm-` prefix -- Conditional logic attribute output -- Accessibility attributes (aria-label, aria-required) - -## Markup Generation Pattern - -Each field markup class follows a consistent pattern: - -1. Receive block attributes from the Gutenberg block -2. Process and sanitize attribute values -3. Generate field-specific HTML using WordPress escaping functions -4. Wrap in the standard field container with label and error placeholder -5. Return the complete HTML string - -## Available Field Types - -### Input (input-markup.php) -Standard text input supporting `text`, `hidden`, and other HTML5 input types. Supports placeholder, default value, min/max length. - -### Email (email-markup.php) -Email input with built-in email format validation. Supports confirmation email field option. - -### Textarea (textarea-markup.php) -Multi-line text area with configurable rows and max length. - -### Number (number-markup.php) -Numeric input with min/max value constraints, step size, and format validation. - -### Phone (phone-markup.php) -Phone number input integrated with `intl-tel-input` library for country code selection. Uses `countries.json` for country data. - -### URL (url-markup.php) -URL input with format validation. - -### Dropdown (dropdown-markup.php) -Select dropdown with configurable options. Supports default selection and placeholder text. - -### Checkbox (checkbox-markup.php) -Single or multiple checkbox inputs with label positioning options. - -### Multi Choice (multichoice-markup.php) -Radio button or checkbox groups for multiple choice questions. Supports image choices and custom layouts. - -### Address (address-markup.php) -Composite field with subfields: street address, city, state/province, zip/postal code, and country. Each subfield is independently configurable. - -### GDPR (gdpr-markup.php) -GDPR consent checkbox with customizable compliance text. Integrates with form-level compliance settings. - -### Payment (payment-markup.php) -Payment collection field for Stripe integration. Renders the payment form elements and connects to the payment processing flow. - -## Validation System - -### Server-Side Validation (field-validation.php) - -`inc/field-validation.php` handles validation during form submission: - -- **Required field check** -- Ensures required fields have values -- **Type validation** -- Validates data types (email format, URL format, number ranges) -- **Unique value check** -- Optional unique validation against existing entries -- **Custom validation** -- Extensible via filters - -Common error messages are defined in `Helper::get_common_err_msg()`: -- `required`: "This field is required." -- `unique`: "Value needs to be unique." - -### Client-Side Validation - -Frontend JavaScript provides instant validation feedback before submission, matching server-side rules. - -## Field Rendering Pipeline - -``` -1. Block Editor (Gutenberg) - -> Block attributes saved in post_content - -2. Frontend Request - -> Generate_Form_Markup::get_form_markup() - -> Parses post_content blocks - -> For each block, calls its render callback - -3. Block Render Callback - -> Block class (inc/blocks/{type}/block.php) - -> Instantiates field markup class - -> Passes block attributes - -4. Field Markup Generation - -> Field markup class (inc/fields/{type}-markup.php) - -> Base class wraps with container, label, error placeholder - -> Field-specific HTML generated - -> Returns escaped HTML string - -5. Form Assembly - -> All field HTML concatenated inside
    wrapper - -> Submit button appended - -> Nonce field added - -> Final form HTML returned -``` - -## Related Pages - -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block registration and editor components -- [Form Submission Flow](Form-Submission-Flow) -- How field data is validated and processed -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- Field-related filters -- [Payment Integration](Payment-Integration) -- Payment field details diff --git a/docs/wiki/Form-Submission-Flow.md b/docs/wiki/Form-Submission-Flow.md deleted file mode 100644 index 81a9e3806..000000000 --- a/docs/wiki/Form-Submission-Flow.md +++ /dev/null @@ -1,191 +0,0 @@ -# Form Submission Flow - -This page documents the end-to-end form submission process from frontend to entry storage. - -## Flow Diagram - -``` -+------------------+ +-------------------+ +------------------+ -| User fills form |---->| JavaScript fetch() |---->| REST API | -| in browser | | POST /submit-form | | Form_Submit | -+------------------+ +-------------------+ +--------+---------+ - | - +--------------------------------+ - | - +----------v-----------+ - | Permission Check | - | - X-WP-Submit-Nonce | - | - Form data exists | - +----------+-----------+ - | - +----------v-----------+ - | Form Restrictions | - | - Max entries check | - | - Date scheduling | - | - Login required | - +----------+-----------+ - | - +----------v-----------+ - | Field Validation | - | - Required fields | - | - Type validation | - | - Unique values | - +----------+-----------+ - | - +----------v-----------+ - | Security Checks | - | - CAPTCHA verify | - | - Honeypot check | - +----------+-----------+ - | - +----------v-----------+ - | Data Processing | - | - Sanitize fields | - | - Smart tag resolve | - | - Collect metadata | - +----------+-----------+ - | - +----------------+----------------+ - | | -+--------v--------+ +--------v--------+ -| Email Notify | | Entry Storage | -| Email_Template | | wp_srfm_entries | -+-----------------+ +--------+--------+ - | - +--------v--------+ - | Payment Process | - | (if applicable) | - | wp_srfm_payments| - +--------+--------+ - | - +--------v--------+ - | Confirmation | - | Response | - +-----------------+ -``` - -## Step-by-Step Process - -### 1. Frontend Submission - -The form is submitted via JavaScript `fetch()` to `POST /wp-json/sureforms/v1/submit-form` with: - -- Form field data as POST body -- `X-WP-Submit-Nonce` header for security (does not require logged-in user) -- Form ID to identify which form was submitted - -Nonces can be refreshed via `GET /refresh-nonces` (public, no auth required). - -### 2. Permission Check - -`Form_Submit::submit_form_permissions_check()`: - -- Verifies the `X-WP-Submit-Nonce` header -- Validates that form data and form ID are present -- Returns `WP_Error` on failure - -### 3. Form Restrictions - -`Form_Restriction` class checks: - -- **Maximum entries** -- Has the form reached its submission limit? -- **Date scheduling** -- Is the form within its active date range? -- **Login required** -- Does the form require a logged-in user? -- **Entry caps** -- Per-user submission limits - -### 4. Field Validation - -`Field_Validation` (`inc/field-validation.php`): - -- **Required fields** -- Ensures all required fields have non-empty values -- **Type validation** -- Email format, URL format, number ranges, phone format -- **Unique values** -- Checks existing entries for uniqueness if configured -- Returns validation errors that prevent submission - -### 5. Security Verification - -- **CAPTCHA** -- Validates reCAPTCHA v2/v3, hCaptcha, or Cloudflare Turnstile token -- **Honeypot** -- Checks the hidden honeypot field is empty (bots fill it) - -### 6. Data Processing - -- `apply_filters('srfm_form_submit_data', $data, $form_id)` -- allows modification -- Sanitizes each field value -- Resolves smart tags for email templates -- Collects submission metadata (IP, browser via Browser class, device info) -- `do_action('srfm_before_submission', $data, $form_id)` - -### 7. Email Notifications - -`Email_Template` sends configured email notifications: - -- Processes smart tags in subject and body -- Sends to configured recipients -- Handles CC/BCC -- See [Email Notifications](Email-Notifications) for details - -### 8. Entry Storage - -Checks GDPR compliance (`do_not_store_entries` setting). If storage is allowed: - -```php -Entries::add([ - 'form_id' => $form_id, - 'user_id' => get_current_user_id(), - 'form_data' => $sanitized_data, // JSON-encoded - 'submission_info' => $metadata, // JSON-encoded - 'logs' => $activity_logs, // JSON-encoded - 'status' => 'unread', - 'type' => $form_type, // 'standard', 'quiz', etc. -]); -``` - -### 9. Payment Processing - -If the form includes a payment block: - -- Stripe payment is processed -- Payment record created in `wp_srfm_payments` -- Payment ID linked to the entry -- See [Payment Integration](Payment-Integration) for details - -### 10. Post-Submission - -- `do_action('srfm_form_submit', $entry_id, $form_id, $form_data)` -- `do_action('srfm_after_submission_process', $entry_id, $form_id)` -- Returns confirmation response: - - **Message** -- Display a success message - - **Redirect** -- Redirect to a specified URL - -## Form Confirmation Types - -Configured via `_srfm_form_confirmation` post meta: - -| Type | Behavior | -|------|----------| -| Message | Display a success message on the same page | -| Redirect | Redirect to a specified URL after submission | - -Confirmation parameters are filterable via `srfm_form_confirmation_params`. - -## Error Handling - -Errors at any stage return a JSON error response to the frontend: - -```json -{ - "success": false, - "data": "Validation error message" -} -``` - -The frontend JavaScript displays errors inline next to the relevant fields. - -## Related Pages - -- [REST API Reference](REST-API-Reference) -- Submit endpoint details -- [Database Schema](Database-Schema) -- Entry and payment table structure -- [Email Notifications](Email-Notifications) -- Email dispatch details -- [Payment Integration](Payment-Integration) -- Payment processing -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- Submission hooks -- [Form Fields Architecture](Form-Fields-Architecture) -- Field validation diff --git a/docs/wiki/Frontend-Assets.md b/docs/wiki/Frontend-Assets.md deleted file mode 100644 index 99b4cca91..000000000 --- a/docs/wiki/Frontend-Assets.md +++ /dev/null @@ -1,122 +0,0 @@ -# Frontend Assets - -SureForms manages assets across three contexts: frontend (form display), admin (dashboard), and block editor. - -## Asset Loading Strategy - -### Frontend (Form Display) - -`inc/frontend-assets.php` (`Frontend_Assets` class) handles loading CSS/JS on pages containing SureForms forms: - -- Detects form blocks in page content -- Enqueues only necessary field-specific CSS/JS -- Loads Stripe JS SDK only when payment fields are present -- Enqueues `intl-tel-input` only when phone fields exist - -### Admin Dashboard - -Admin assets are enqueued only on SureForms admin pages: - -- React app bundle from `assets/build/` -- Tailwind CSS base styles from `src/admin/tw-base.scss` -- Localized data via `wp_localize_script()` - -### Block Editor - -`inc/gutenberg-hooks.php` (`Gutenberg_Hooks` class) loads editor assets: - -- Block editor JS/CSS from `assets/build/` -- Editor-specific stylesheets -- Block preview styles - -## Build Pipeline - -### wp-scripts (Webpack) - -Primary JS build tool. Configuration extends `@wordpress/scripts` defaults: - -```bash -npm start # Development with hot reload -npm run build:script # Production build -``` - -**Entry Points:** -- `src/blocks/blocks.js` -- Gutenberg block registration -- `src/admin/editor-scripts.js` -- Admin dashboard React app -- Additional entries per block type - -**Output:** `assets/build/` directory - -**Webpack Features:** -- Code splitting per entry point -- WordPress dependency extraction (`@wordpress/*` packages) -- Source maps in development -- Asset hashing for cache busting - -### Grunt (SASS / Minification) - -```bash -npm run build:sass # Compile SASS only -npm run build # Full build (JS + SASS + minify) -``` - -The `Gruntfile.js` tasks include: - -| Task | Description | -|------|-------------| -| `sass` | Compile SASS files from `sass/` to `assets/css/` | -| `postcss` | Run PostCSS with Autoprefixer | -| `cssmin` | Minify CSS to `assets/css/minified/` | -| `uglify` | Minify JS to `assets/js/minified/` | -| `copy` | Copy assets to distribution directories | - -### SASS Directory Structure - -``` -sass/ -|-- frontend block styles -|-- form field styles -|-- layout and grid styles -``` - -Compiled output: `assets/css/` (unminified) and `assets/css/minified/` - -### Tailwind CSS - -Used exclusively for admin dashboard pages: - -**Configuration (`tailwind.config.js`):** -- Content paths: `src/admin/**/*.{js,jsx}` -- Plugins: `@tailwindcss/forms` -- Custom theme extensions for Force UI compatibility -- `tailwind-merge` used for dynamic class composition - -**Base styles:** `src/admin/tw-base.scss` - -## CSS Naming Conventions - -| Context | Prefix | Example | -|---------|--------|---------| -| Frontend form styles | `srfm-` | `srfm-input-field`, `srfm-form-container` | -| Admin dashboard | Tailwind utilities | `flex items-center gap-2` | -| Block editor | `srfm-editor-` | `srfm-editor-preview` | - -## Asset Versioning - -All enqueued assets use `SRFM_VER` (currently `2.5.1`) for cache busting: - -```php -wp_enqueue_style( - 'srfm-frontend', - SRFM_URL . 'assets/css/minified/frontend.min.css', - [], - SRFM_VER -); -``` - -## Related Pages - -- [Admin Dashboard](Admin-Dashboard) -- React admin asset details -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block editor assets -- [Environment Configuration](Environment-Configuration) -- Build tool setup -- [Contributing Guide](Contributing-Guide) -- CSS/JS conventions diff --git a/docs/wiki/Getting-Started.md b/docs/wiki/Getting-Started.md deleted file mode 100644 index a0f2ea908..000000000 --- a/docs/wiki/Getting-Started.md +++ /dev/null @@ -1,149 +0,0 @@ -# Getting Started - -This guide walks you through setting up a SureForms development environment from scratch. - -## Prerequisites - -| Tool | Version | Notes | -|------|---------|-------| -| PHP | 7.4+ | Required for plugin runtime | -| WordPress | 6.4+ | Required for block editor features | -| Node.js | 24.16.0 | Managed via Volta (auto-switches) | -| Composer | 2.x | PHP dependency manager | -| Git | 2.x | Version control | -| Docker | Latest | Optional, for wp-env test environment | - -### Installing Volta (Recommended) - -[Volta](https://volta.sh/) ensures you use the correct Node.js version automatically: - -```bash -curl https://get.volta.sh | bash -``` - -The `package.json` pins Node 24.16.0 via Volta config -- it activates automatically when you enter the project directory. - -## Initial Setup - -### 1. Clone the Repository - -```bash -git clone https://github.com/brainstormforce/sureforms.git -cd sureforms -``` - -### 2. Install Dependencies - -```bash -composer install # PHP dependencies -npm install # Node dependencies -``` - -### 3. Build the Plugin - -```bash -npm run build # Full production build (JS + SASS + minify) -``` - -This runs three steps: -1. `wp-scripts build` -- Bundles JS/React from `src/` to `build/` -2. SASS compilation -- Compiles `sass/` to `assets/css/` -3. `grunt minify` -- Minifies CSS and JS to `assets/css/minified/` and `assets/js/minified/` - -### 4. Start Development Server - -```bash -npm start # wp-scripts dev server with hot reload -``` - -This watches `src/` for changes and rebuilds JS automatically. For SASS changes, run `npm run build:sass` separately. - -## WordPress Environment - -### Option A: Local WordPress Installation - -1. Set up a local WordPress site (e.g., using Local, MAMP, or Docker) -2. Symlink or copy the plugin into `wp-content/plugins/sureforms/` -3. Activate the plugin from the WordPress admin - -### Option B: wp-env (Docker-Based) - -The project includes a `.wp-env.json` configuration for a Docker-based WordPress environment: - -```bash -npm run play:up # Start WordPress + MySQL containers -``` - -This provides: -- WordPress at `http://localhost:8888` (admin: `admin` / `password`) -- Test WordPress at `http://localhost:8889` -- Plugin auto-mounted and activated -- Consistent environment across developers - -Additional commands: -```bash -npm run play:stop # Stop containers -npm run env:clean # Reset all data -``` - -## Verifying Your Setup - -### Run Linters - -```bash -vendor/bin/phpcs # PHP CodeSniffer -npm run lint-js # ESLint -npm run lint-css # Stylelint -npm run pretty # Prettier -``` - -### Run Tests - -```bash -vendor/bin/phpunit # PHP unit tests -npm run test:unit # JavaScript unit tests -``` - -### Build Check - -```bash -npm run build # Should complete without errors -``` - -## Project Structure Overview - -``` -sureforms/ -├── sureforms.php # Plugin entry point -├── plugin-loader.php # Autoloader and bootstrap -├── inc/ # PHP classes (core logic) -├── src/ # JS/React source (compiled by wp-scripts) -│ ├── admin/ # Admin dashboard React app -│ ├── blocks/ # Gutenberg block components -│ ├── components/ # Shared React components -│ └── store/ # WordPress data store -├── sass/ # SASS source files -├── assets/ # Compiled assets (CSS, JS, images) -├── templates/ # PHP templates (BladeOne) -├── tests/ # Test files -├── modules/ # Feature modules -└── admin/ # Admin PHP + assets -``` - -See [Architecture Overview](Architecture-Overview) for a detailed breakdown. - -## Key Concepts - -- **Custom Post Type**: Forms are stored as `sureforms_form` posts with block content -- **Custom Database Tables**: Entries in `wp_srfm_entries`, payments in `wp_srfm_payments` -- **REST API**: All data operations use the `sureforms/v1` REST namespace -- **Block Editor**: Form fields are Gutenberg blocks registered via `block.json` -- **Admin SPA**: The dashboard is a React single-page application using Force UI components - -## Next Steps - -- [Architecture Overview](Architecture-Overview) -- Understand the codebase -- [Contributing Guide](Contributing-Guide) -- Development workflow and standards -- [Gutenberg Blocks](Gutenberg-Blocks) -- How form blocks work -- [REST API Reference](REST-API-Reference) -- Available API endpoints -- [Testing Guide](Testing-Guide) -- Running and writing tests diff --git a/docs/wiki/Gutenberg-Blocks.md b/docs/wiki/Gutenberg-Blocks.md deleted file mode 100644 index ed38cd9b3..000000000 --- a/docs/wiki/Gutenberg-Blocks.md +++ /dev/null @@ -1,129 +0,0 @@ -# Gutenberg Blocks - -SureForms provides 14+ Gutenberg block types for building forms in the WordPress block editor. - -## Block Types - -### Field Blocks - -| Block | Slug | Description | -|-------|------|-------------| -| Input | `srfm/input` | Text input (text, hidden, etc.) | -| Email | `srfm/email` | Email address input with validation | -| Textarea | `srfm/textarea` | Multi-line text area | -| Number | `srfm/number` | Numeric input with min/max | -| Phone | `srfm/phone` | Phone number with country selector (intl-tel-input) | -| URL | `srfm/url` | URL input with validation | -| Dropdown | `srfm/dropdown` | Select dropdown with options | -| Checkbox | `srfm/checkbox` | Single/multiple checkboxes | -| Multi Choice | `srfm/multichoice` | Radio/checkbox group | -| Address | `srfm/address` | Address field with subfields (street, city, state, zip, country) | -| GDPR | `srfm/gdpr` | GDPR consent checkbox | -| Payment | `srfm/payment` | Payment collection (Stripe integration) | -| Inline Button | `srfm/inlinebutton` | Inline form button | - -### Container Block - -| Block | Slug | Description | -|-------|------|-------------| -| SForm | `srfm/sform` | Main form wrapper block (required container for all field blocks) | - -## Block Registration - -### Server-Side (PHP) - -Blocks are registered in `inc/blocks/register.php` via the `Blocks\Register` class, initialized on the `init` hook: - -```php -// inc/blocks/register.php -register_block_type_from_metadata( - SRFM_DIR . 'inc/blocks/{block-type}/', - [ - 'render_callback' => [ $block_instance, 'render' ], - ] -); -``` - -Each block type has: -- `block.json` -- Block metadata, attributes, and category -- `block.php` -- Server-side rendering class extending `Blocks\Base` - -### Client-Side (JavaScript) - -Block registration happens in `src/blocks/blocks.js`: - -```javascript -// src/blocks/blocks.js -import { registerBlockType } from '@wordpress/blocks'; -// Block imports and registrations... -``` - -The `src/blocks/register-block.js` utility handles individual block registration. - -## Block Architecture - -### Base Class: `Blocks\Base` - -All block PHP classes extend `SRFM\Inc\Blocks\Base`, which provides: - -- `render()` -- Main render callback invoked by WordPress -- Access to block attributes from `block.json` -- Common rendering logic shared across field types -- Integration with field markup classes - -### Block to Field Mapping - -Each block delegates HTML generation to its corresponding field markup class in `inc/fields/`: - -``` -Block (inc/blocks/input/block.php) - -> Field Markup (inc/fields/input-markup.php) - -> Base Field (inc/fields/base.php) - -> HTML output -``` - -### block.json Structure - -Each block directory contains a `block.json` file defining: - -```json -{ - "apiVersion": 3, - "name": "srfm/input", - "title": "Input", - "category": "sureforms", - "attributes": { - "label": { "type": "string", "default": "Input" }, - "required": { "type": "boolean", "default": false }, - "placeholder": { "type": "string", "default": "" } - }, - "supports": { "html": false } -} -``` - -## Adding a New Block Type - -1. **Create block directory:** `inc/blocks/{new-type}/` -2. **Add `block.json`** with attributes, name, and category -3. **Create `block.php`** extending `Blocks\Base` with a `render()` method -4. **Create field markup:** `inc/fields/{new-type}-markup.php` extending `Fields\Base` -5. **Register in `Blocks\Register`** -- add to the blocks array -6. **Create editor component:** `src/blocks/{new-type}/` with `edit.js` and `index.js` -7. **Add to `src/blocks/blocks.js`** -- import and register the new block - -## The sform Container Block - -The `srfm/sform` block is the required wrapper for all form fields. It: - -- Renders the `` HTML element -- Handles form-level settings (styling, confirmation, CAPTCHA) -- Manages submission nonce generation -- Controls form layout and submit button -- Integrates with frontend JavaScript for submission handling - -## Related Pages - -- [Form Fields Architecture](Form-Fields-Architecture) -- Field markup and validation -- [Block Editor Controls](Block-Editor-Controls) -- Custom inspector controls -- [Architecture Overview](Architecture-Overview) -- Block registration in bootstrap -- [Frontend Assets](Frontend-Assets) -- Block editor asset loading diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md deleted file mode 100644 index 365d8af63..000000000 --- a/docs/wiki/Home.md +++ /dev/null @@ -1,58 +0,0 @@ -# SureForms Wiki - -Welcome to the SureForms developer wiki. This documentation covers the architecture, APIs, development workflows, and internals of the SureForms WordPress form builder plugin. - -**Version:** 2.5.1 | **WordPress:** 6.4+ | **PHP:** 7.4+ | **Node:** 24.16.0 - -## Quick Links - -- [Getting Started](Getting-Started) -- Set up your development environment -- [Architecture Overview](Architecture-Overview) -- Understand the codebase structure -- [Contributing Guide](Contributing-Guide) -- How to contribute - -## Architecture & Core - -| Page | Description | -|------|-------------| -| [Architecture Overview](Architecture-Overview) | Plugin bootstrap, namespaces, directory layout, design patterns | -| [Database Schema](Database-Schema) | Custom tables (`wp_srfm_entries`, `wp_srfm_payments`), post meta keys | -| [Environment Configuration](Environment-Configuration) | Constants, build tools, linting, development setup | -| [Form Submission Flow](Form-Submission-Flow) | End-to-end submission pipeline from frontend to storage | - -## WordPress / Backend - -| Page | Description | -|------|-------------| -| [Gutenberg Blocks](Gutenberg-Blocks) | Block types, registration, `block.json` structure | -| [Form Fields Architecture](Form-Fields-Architecture) | Field type hierarchy, validation, rendering pipeline | -| [WordPress Hooks Reference](WordPress-Hooks-Reference) | Custom actions, filters, and execution order | -| [Payment Integration](Payment-Integration) | Stripe setup, payment flow, webhooks, refunds | -| [Email Notifications](Email-Notifications) | Email templates, smart tags, notification settings | -| [AI Form Builder](AI-Form-Builder) | AI-powered form generation process | -| [Data Export & Import](Data-Export-Import) | Form/entry export, import, duplication | - -## Frontend / React - -| Page | Description | -|------|-------------| -| [Admin Dashboard](Admin-Dashboard) | React SPA architecture, routing, data fetching | -| [State Management](State-Management) | WordPress data store, actions, selectors | -| [Frontend Assets](Frontend-Assets) | Asset loading, build pipeline, SASS, Tailwind | -| [Block Editor Controls](Block-Editor-Controls) | Custom inspector controls, CSS generation, icons | - -## API - -| Page | Description | -|------|-------------| -| [REST API Reference](REST-API-Reference) | All endpoints with methods, parameters, authentication | - -## Development Workflow - -| Page | Description | -|------|-------------| -| [Getting Started](Getting-Started) | Prerequisites, installation, first build | -| [Contributing Guide](Contributing-Guide) | Git workflow, coding standards, PR process | -| [Testing Guide](Testing-Guide) | PHPUnit, Playwright, Jest setup and execution | -| [Deployment Guide](Deployment-Guide) | CI/CD pipelines, release process, version bumping | -| [Troubleshooting & FAQ](Troubleshooting-FAQ) | Common issues and debugging tips | -| [Changelog](Changelog) | Recent version history | diff --git a/docs/wiki/Payment-Integration.md b/docs/wiki/Payment-Integration.md deleted file mode 100644 index 28d985e6e..000000000 --- a/docs/wiki/Payment-Integration.md +++ /dev/null @@ -1,127 +0,0 @@ -# Payment Integration - -SureForms supports Stripe payment processing with one-time payments and recurring subscriptions. - -## Architecture Overview - -``` -inc/payments/ -|-- payments.php # Main payments orchestrator -|-- front-end.php # Frontend payment handling -|-- payment-helper.php # Shared payment utilities -|-- admin/ -| |-- admin-handler.php # Admin payment management UI -|-- stripe/ - |-- payments-settings.php # Stripe settings + REST routes - |-- stripe-helper.php # Stripe API utilities - |-- stripe-webhook.php # Webhook event handler - |-- admin-stripe-handler.php # Admin Stripe config -``` - -## Stripe Setup - -### Configuration - -Stripe settings are managed via the admin settings page and stored as WordPress options: - -1. Navigate to SureForms > Settings > Payments -2. Connect your Stripe account via OAuth flow -3. Configure test/live mode -4. Set default currency - -The `Payments_Settings` class registers REST routes for settings management and handles the Stripe OAuth callback via `admin_init`. - -### OAuth Flow - -1. Admin clicks "Connect Stripe" in settings -2. `intercept_stripe_callback()` on `admin_init` handles the OAuth redirect -3. Stripe credentials are securely stored in WordPress options - -## Payment Flow - -``` -1. FORM DISPLAY - - Payment block renders Stripe Elements - - Frontend_Assets enqueues Stripe JS SDK - - Payment form elements injected into the form - -2. FORM SUBMISSION - - User fills form + enters payment details - - Frontend JavaScript creates Stripe PaymentIntent - - Form data + payment token submitted to /submit-form - -3. SERVER PROCESSING - - Form_Submit validates form data - - Payment processing triggered - - Stripe API called to confirm payment - - Payment record created in wp_srfm_payments table - - Entry created in wp_srfm_entries table - - Payment ID linked to entry - -4. WEBHOOK HANDLING - - Stripe sends webhook events to /wp-json endpoint - - Stripe_Webhook validates signature - - Payment status updated in database - - Subscription events handled (renewal, cancellation, etc.) -``` - -## Payment Database Schema - -Payments are stored in the `wp_srfm_payments` custom table. See [Database Schema](Database-Schema) for full column definitions. - -### Payment Types - -| Type | Description | -|------|-------------| -| `payment` | One-time payment | -| `subscription` | Recurring subscription record | -| `renewal` | Subscription renewal payment | - -### Payment Statuses - -`pending`, `succeeded`, `failed`, `canceled`, `requires_action`, `requires_payment_method`, `processing`, `refunded`, `partially_refunded` - -### Subscription Statuses - -`active`, `canceled`, `past_due`, `unpaid`, `trialing`, `incomplete`, `incomplete_expired`, `paused` - -## Webhook Handling - -The `Stripe_Webhook` class (`inc/payments/stripe/stripe-webhook.php`) processes Stripe webhook events: - -- Validates webhook signature using Stripe signing secret -- Handles payment status updates (succeeded, failed, etc.) -- Processes subscription lifecycle events (renewal, cancellation) -- Creates renewal payment records for subscription billing cycles -- Updates payment and subscription statuses in the database - -## Refund Management - -The `Payments` table class provides refund tracking: - -- `add_refund_to_payment_data()` -- Record refund details in payment_data JSON -- `add_refund_amount()` -- Increment the refunded_amount column -- `get_refundable_amount()` -- Calculate remaining refundable balance -- `is_fully_refunded()` / `is_partially_refunded()` -- Status checks - -## Supported Currencies - -USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, SEK, NZD, MXN, SGD, HKD, NOK, TRY, RUB, INR, BRL, ZAR, KRW - -## Payment Modes - -| Mode | Description | -|------|-------------| -| `test` | Stripe test mode (test API keys) | -| `live` | Stripe live mode (production) | - -## Admin Payment Management - -The `Admin_Handler` class provides the admin interface for viewing and managing payments. Payment IDs in entry views are rendered as clickable links via the `srfm_entry_value` filter. - -## Related Pages - -- [Database Schema](Database-Schema) -- Payment table columns and relationships -- [Form Submission Flow](Form-Submission-Flow) -- Payment processing in submission pipeline -- [REST API Reference](REST-API-Reference) -- Payment-related endpoints -- [Gutenberg Blocks](Gutenberg-Blocks) -- Payment block type diff --git a/docs/wiki/REST-API-Reference.md b/docs/wiki/REST-API-Reference.md deleted file mode 100644 index d366470e5..000000000 --- a/docs/wiki/REST-API-Reference.md +++ /dev/null @@ -1,114 +0,0 @@ -# REST API Reference - -All endpoints are registered under the namespace `sureforms/v1` and accessible at `/wp-json/sureforms/v1/`. - -## Authentication - -### Admin Endpoints -All admin endpoints require the `manage_options` capability. Authentication is via WordPress REST API nonce (`X-WP-Nonce` header) or cookie-based auth. - -Permission check: `Helper::get_items_permissions_check()` verifies `current_user_can('manage_options')`. - -### Public Endpoints -The form submission endpoint uses a custom nonce (`X-WP-Submit-Nonce` header) that does not require a logged-in user. - -## Endpoints - -### AI Form Builder - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/generate-form` | Generate a form using AI | -| POST | `/map-fields` | Map AI response to SureForms Gutenberg blocks | -| GET | `/initiate-auth` | Get auth URL for AI billing portal | -| POST | `/handle-access-key` | Decrypt and save AI access key | - -### Dashboard - -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/entries-chart-data` | Get entries chart data for analytics | -| GET | `/form-data` | Get all forms data for dashboard | -| GET | `/plugin-status` | Check integration plugin install status | - -### Onboarding - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/onboarding/set-status` | Mark onboarding as complete | -| GET | `/onboarding/get-status` | Get current onboarding status | - -### Entries - -| Method | Endpoint | Description | Parameters | -|--------|----------|-------------|------------| -| GET | `/entries/list` | Paginated entries listing | `form_id`, `status`, `search`, `date_from`, `date_to`, `orderby`, `order`, `per_page`, `page` | -| POST | `/entries/read-status` | Bulk mark entries read/unread | `entry_ids` (required), `action` (required: read/unread) | -| POST | `/entries/trash` | Bulk trash/restore entries | `entry_ids` (required), `action` (required: trash/restore) | -| POST | `/entries/delete` | Permanently delete entries | `entry_ids` (required) | -| POST | `/entries/export` | Export entries to CSV/ZIP | `entry_ids`, `form_id`, `status`, `search`, `date_from`, `date_to` | -| GET | `/entry/{id}/details` | Single entry with form data | `id` (required) | -| GET | `/entry/{id}/logs` | Entry activity logs | `id` (required), `page`, `per_page` | - -### Forms - -| Method | Endpoint | Description | Parameters | -|--------|----------|-------------|------------| -| GET | `/forms` | Paginated forms listing | `page`, `per_page`, `search`, `status`, `orderby`, `order`, `date_from`, `date_to` | -| POST | `/forms/export` | Export forms as JSON | `form_ids` (required) | -| POST | `/forms/import` | Import forms from JSON | File upload | -| POST | `/forms/manage` | Manage form lifecycle | `form_ids` (required), `action` (trash/restore/delete) | -| POST | `/forms/duplicate` | Duplicate a form | `form_id` (required) | - -### Form Submission (Public) - -| Method | Endpoint | Auth | Description | -|--------|----------|------|-------------| -| POST | `/submit-form` | Custom nonce | Public form submission | -| GET | `/refresh-nonces` | None | Refresh frontend nonces | - -## Extending the API - -Endpoints are extensible via the `srfm_rest_api_endpoints` filter: - -```php -add_filter( 'srfm_rest_api_endpoints', function( $endpoints ) { - $endpoints['custom/endpoint'] = [ - 'methods' => 'GET', - 'callback' => [ $this, 'handle_custom' ], - 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], - ]; - return $endpoints; -} ); -``` - -## Error Responses - -Errors are returned via `wp_send_json_error()`: - -```json -{ - "success": false, - "data": "Error message string" -} -``` - -## Pagination - -List endpoints support pagination with consistent parameters: - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `page` | 1 | Current page number | -| `per_page` | 20 | Items per page | -| `orderby` | `created_at` | Sort column | -| `order` | `DESC` | Sort direction (ASC/DESC) | - -Response includes total counts for pagination UI. - -## Related Pages - -- [Architecture Overview](Architecture-Overview) -- API registration flow -- [Form Submission Flow](Form-Submission-Flow) -- Submit endpoint details -- [Data Export Import](Data-Export-Import) -- Export/import endpoints -- [Payment Integration](Payment-Integration) -- Payment API endpoints diff --git a/docs/wiki/State-Management.md b/docs/wiki/State-Management.md deleted file mode 100644 index 6cb4b072a..000000000 --- a/docs/wiki/State-Management.md +++ /dev/null @@ -1,109 +0,0 @@ -# State Management - -SureForms uses the `@wordpress/data` package to create a Redux-like data store for managing application state in the block editor. - -## Store Structure - -Located in `src/store/`: - -``` -src/store/ -|-- store.js # Store registration -|-- reducer.js # State reducers -|-- actions.js # Action creators -|-- selectors.js # State selectors -|-- constants.js # Store constants -|-- setInitialState.js # Initial state configuration -``` - -## Store Registration - -The store is registered with `@wordpress/data` in `store.js`: - -```javascript -import { createReduxStore, register } from '@wordpress/data'; -import reducer from './reducer'; -import * as actions from './actions'; -import * as selectors from './selectors'; - -const store = createReduxStore('sureforms', { - reducer, - actions, - selectors, -}); - -register(store); -``` - -The store namespace is `sureforms`. - -## Initial State - -`setInitialState.js` configures the initial state from server-side localized data (`wp_localize_script`), providing: - -- Form settings and metadata -- Block defaults -- User preferences -- Plugin configuration - -## Actions - -Actions are defined in `actions.js` and dispatched to modify the store state. They follow the WordPress data store pattern: - -```javascript -// Dispatching an action -import { dispatch } from '@wordpress/data'; -dispatch('sureforms').updateFormSettings({ key: 'value' }); -``` - -## Selectors - -Selectors in `selectors.js` extract specific data from the store state: - -```javascript -// Using a selector -import { select } from '@wordpress/data'; -const settings = select('sureforms').getFormSettings(); -``` - -## Component Integration - -Components connect to the store using WordPress data hooks: - -```javascript -import { useSelect, useDispatch } from '@wordpress/data'; - -function MyComponent() { - const formData = useSelect((select) => { - return select('sureforms').getFormSettings(); - }, []); - - const { updateFormSettings } = useDispatch('sureforms'); - - return ( - - ); -} -``` - -## Constants - -`constants.js` defines store-wide constants used across actions, reducers, and selectors to ensure consistency. - -## Usage Context - -The WordPress data store is primarily used in: - -- **Block editor** -- Form-level settings and block state -- **Inspector controls** -- Sidebar settings panels -- **Block attributes** -- Synchronized state between blocks - -For the admin dashboard pages, `@tanstack/react-query` is used instead for server state management. See [Admin Dashboard](Admin-Dashboard) for details. - -## Related Pages - -- [Admin Dashboard](Admin-Dashboard) -- React admin app architecture -- [Block Editor Controls](Block-Editor-Controls) -- Inspector controls using the store -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block registration and attributes diff --git a/docs/wiki/Testing-Guide.md b/docs/wiki/Testing-Guide.md deleted file mode 100644 index ea129c084..000000000 --- a/docs/wiki/Testing-Guide.md +++ /dev/null @@ -1,172 +0,0 @@ -# Testing Guide - -SureForms uses three testing frameworks: PHPUnit for PHP unit tests, Playwright for end-to-end tests, and Jest for JavaScript unit tests. - -## Testing Strategy - -| Level | Framework | Location | Purpose | -|-------|-----------|----------|---------| -| Unit (PHP) | PHPUnit 9.x | `tests/unit/` | PHP class and function testing | -| Unit (JS) | Jest | Via `wp-scripts` | JavaScript utility testing | -| E2E | Playwright 1.48+ | `tests/play/` | Full browser-based workflow testing | - -## PHPUnit (PHP Unit Tests) - -### Setup - -1. Install PHP dependencies: - ```bash - composer install - ``` - -2. WordPress test environment must be available (via wp-env or local setup) - -### Running Tests - -```bash -# Run all PHP unit tests -vendor/bin/phpunit - -# Run a specific test file -vendor/bin/phpunit tests/unit/test-specific.php - -# Run tests with verbose output -vendor/bin/phpunit --verbose -``` - -### Test File Conventions - -- Location: `tests/unit/` -- File prefix: `test-` (e.g., `test-helper.php`) -- Class naming: `Test_ClassName` extending `WP_UnitTestCase` -- Method naming: `test_` prefix (e.g., `test_form_submission()`) - -### CI Configuration - -PHPUnit runs in GitHub Actions (`.github/workflows/phpunit.yml`): -- PHP: 8.2 -- WordPress: trunk -- MySQL: 5.7 -- Triggered on pull requests - -## Playwright (E2E Tests) - -### Prerequisites - -- Node.js 24.16.0 (Volta) -- Docker (for wp-env) - -### Setup - -```bash -# Install dependencies -npm install - -# Start the WordPress environment -npm run play:up - -# Build the plugin -npm run build - -# Install Playwright browsers -npx playwright install -``` - -### Running Tests - -```bash -# Run all E2E tests (headless) -npm run play:run - -# Run tests in headed mode (visible browser) -npm run play:run:interactive - -# Run a specific test file -npx playwright test tests/play/specific-test.spec.js -``` - -### Test File Conventions - -- Location: `tests/play/` -- File suffix: `.spec.js` -- Uses Playwright Test runner with `expect` assertions -- Page Object Model pattern for reusable interactions - -### CI Configuration - -Playwright runs in GitHub Actions (`.github/workflows/playwright.yml`): -- Triggered when PR is labeled with `e2e` -- Node: 18.15 -- Builds plugin and starts wp-env -- Generates HTML test report (30-day artifact retention) - -## Jest (JavaScript Unit Tests) - -### Running Tests - -```bash -npm run test:unit -``` - -Uses `@wordpress/scripts` Jest configuration with WordPress-specific transforms and module resolution. - -## Test Environment (wp-env) - -The Docker-based test environment is configured in `.wp-env.json`: - -```bash -# Start environment -npm run play:up - -# Stop environment -npm run play:stop - -# Clean all data -npm run env:clean -``` - -wp-env provides: -- WordPress installation with the plugin active -- MySQL database -- Consistent environment across developers -- Used by both Playwright E2E tests and local development - -## Writing New Tests - -### PHP Unit Test - -```php -class Test_My_Feature extends WP_UnitTestCase { - public function test_feature_works() { - // Arrange - $form_id = $this->factory->post->create([ - 'post_type' => 'sureforms_form', - ]); - - // Act - $result = some_function($form_id); - - // Assert - $this->assertEquals('expected', $result); - } -} -``` - -### Playwright E2E Test - -```javascript -const { test, expect } = require('@playwright/test'); - -test('form submission works', async ({ page }) => { - await page.goto('/form-page/'); - await page.fill('[name="email"]', 'test@example.com'); - await page.click('button[type="submit"]'); - await expect(page.locator('.success-message')).toBeVisible(); -}); -``` - -## Related Pages - -- [Environment Configuration](Environment-Configuration) -- Development setup -- [Contributing Guide](Contributing-Guide) -- Test requirements for PRs -- [Deployment Guide](Deployment-Guide) -- CI/CD test pipeline diff --git a/docs/wiki/Troubleshooting-FAQ.md b/docs/wiki/Troubleshooting-FAQ.md deleted file mode 100644 index 7afa4619a..000000000 --- a/docs/wiki/Troubleshooting-FAQ.md +++ /dev/null @@ -1,232 +0,0 @@ -# Troubleshooting & FAQ - -Common issues, debugging tips, and frequently asked questions for SureForms development. - -## Common Development Issues - -### Build Failures - -**Problem:** `npm run build` fails with errors. - -**Solutions:** -1. Ensure Node.js 24.16.0 is active (Volta manages this automatically via `package.json`): - ```bash - node -v # Should output v24.16.0 - ``` -2. Clear and reinstall dependencies: - ```bash - rm -rf node_modules - npm install - ``` -3. If SASS compilation fails, check that `sass/` source files have valid syntax -4. If Grunt minification fails, ensure Grunt CLI is available: - ```bash - npx grunt minify - ``` - -### wp-env Issues - -**Problem:** `npm run play:up` fails to start the Docker environment. - -**Solutions:** -1. Ensure Docker is installed and running -2. Clean the environment: - ```bash - npm run env:clean - ``` -3. Restart the environment: - ```bash - npm run play:stop - npm run play:up - ``` -4. If port conflicts occur, check for other services on ports 8888/8889 - -### PHPCS Errors - -**Problem:** `vendor/bin/phpcs` reports violations. - -**Solutions:** -1. Auto-fix what's possible: - ```bash - vendor/bin/phpcbf - ``` -2. Common issues: - - Missing text domain: Add `'sureforms'` to all translatable strings - - Unescaped output: Wrap output in `esc_html()`, `esc_attr()`, etc. - - Missing nonce verification: Add nonce checks for form/AJAX handlers - - Short array syntax: Use `[]` instead of `array()` (enforced by config) - -### PHPUnit Test Failures - -**Problem:** `vendor/bin/phpunit` tests fail. - -**Solutions:** -1. Ensure WordPress test environment is available (wp-env or local setup) -2. Check database connectivity for the test suite -3. Run a specific test to isolate the issue: - ```bash - vendor/bin/phpunit tests/unit/test-specific.php - ``` - -### JavaScript Lint Errors - -**Problem:** `npm run lint-js` reports errors. - -**Solutions:** -1. Auto-fix most issues: - ```bash - npm run lint-js:fix - ``` -2. Format code with Prettier: - ```bash - npm run pretty:fix - ``` -3. Common issues: - - Import ordering: WordPress dependencies should use `@wordpress/*` - - Unused variables: Remove or prefix with underscore - - Missing dependencies in `useEffect`: Add to dependency array - -## Form Submission Issues - -### Nonce Verification Fails - -**Cause:** The `X-WP-Submit-Nonce` has expired (default: 24-hour lifetime). - -**Debug:** -- Nonces can be refreshed via `GET /wp-json/sureforms/v1/refresh-nonces` -- Check that the nonce is being sent in the request header -- Caching plugins may cache the nonce -- exclude form pages from page cache - -### CAPTCHA Validation Fails - -**Cause:** CAPTCHA keys are misconfigured or the token is expired. - -**Debug:** -1. Verify CAPTCHA keys in SureForms global settings -2. Check browser console for CAPTCHA loading errors -3. Ensure the CAPTCHA provider (reCAPTCHA, hCaptcha, Turnstile) is correctly configured -4. reCAPTCHA v3 can trigger multiple times on pages with multiple forms - -### File Upload Errors - -**Debug:** -1. Check PHP `upload_max_filesize` and `post_max_size` in `php.ini` -2. Verify allowed file types in form field settings -3. Check file size against the configured maximum -4. Ensure the `wp-content/uploads/` directory is writable - -## Database Issues - -### Custom Tables Missing - -**Cause:** The `wp_srfm_entries` or `wp_srfm_payments` table was not created. - -**Debug:** -1. Deactivate and reactivate the plugin to trigger table creation -2. Check the `srfm_db_version` option in `wp_options` -3. Verify database user has `CREATE TABLE` permissions -4. Check for errors in `debug.log` during activation - -### Entry Data Not Saving - -**Debug:** -1. Check the `do_not_store_entries` form setting (GDPR compliance) -2. Verify the form submission response for error messages -3. Check `debug.log` for database insert errors -4. Ensure the entries table schema is up to date - -## REST API Issues - -### 401 Unauthorized on Admin Endpoints - -**Cause:** User is not authenticated or lacks required capabilities. - -**Debug:** -1. Admin endpoints require `manage_options` capability -2. Ensure the user is logged in with a valid session -3. Check that the REST API nonce is being sent (via `X-WP-Nonce` header) -4. WordPress cookie authentication must be active - -### 403 on Form Submission - -**Cause:** The submit nonce is invalid or form restrictions are blocking submission. - -**Debug:** -1. Check the `X-WP-Submit-Nonce` header value -2. Review form restriction settings (max entries, date scheduling, login required) -3. Check the `srfm_form_submit_permissions_check` filter - -## Performance Issues - -### Slow Admin Dashboard - -**Causes & Solutions:** -1. **Large number of entries:** Entries are paginated -- default page size is managed by REST API -2. **Unoptimized queries:** The entries table has indexes on `form_id`, `user_id`, and a composite index on `(form_id, created_at, status)` -3. **Asset loading:** Admin assets are conditionally loaded only on SureForms admin pages - -### Slow Form Rendering - -**Causes & Solutions:** -1. **Too many blocks:** Complex forms with many fields may benefit from multi-step layout -2. **External scripts:** CAPTCHA and payment scripts load conditionally based on form settings -3. **CSS generation:** Each block generates dynamic CSS -- review `generateCSS.js` for optimization - -## Debugging Tips - -### Enable WordPress Debug Mode - -Add to `wp-config.php`: -```php -define( 'WP_DEBUG', true ); -define( 'WP_DEBUG_LOG', true ); -define( 'WP_DEBUG_DISPLAY', false ); -define( 'SCRIPT_DEBUG', true ); -``` - -### Check REST API Responses - -Use the browser developer tools Network tab to inspect REST API calls: -- Filter by `sureforms/v1` to see plugin API requests -- Check response status codes and error messages - -### Inspect Form Data Store - -In the browser console on a form editor page: -```javascript -wp.data.select('starter-starter/starter-starter-forms-data').getFormSettings(); -``` - -## FAQ - -### What WordPress version is required? - -WordPress 6.4 or higher is required. - -### What PHP version is required? - -PHP 7.4 or higher. The `phpcs.xml` enforces PHP 7.4+ compatibility. - -### Can I modify bundled libraries? - -Files in `inc/lib/` are third-party libraries and should not be modified directly. If changes are needed, they should be managed upstream. - -### Where do I put new PHP classes? - -New PHP classes go in the `inc/` directory following the existing namespace structure (`SRFM\Inc\*`). The autoloader in `plugin-loader.php` handles class loading. - -### How do I add a new admin page? - -Admin pages are rendered by the React SPA in `src/admin/`. Add new routes using `react-router-dom` and register the corresponding menu items in PHP. See [Admin Dashboard](Admin-Dashboard) for details. - -### How do I add a new form field type? - -See [Form Fields Architecture](Form-Fields-Architecture) for the field type hierarchy and the process for adding new field types. - -## Related Pages - -- [Environment Configuration](Environment-Configuration) -- Development setup -- [Testing Guide](Testing-Guide) -- Running tests -- [Architecture Overview](Architecture-Overview) -- Codebase structure -- [REST API Reference](REST-API-Reference) -- API endpoints -- [Database Schema](Database-Schema) -- Table structure diff --git a/docs/wiki/Using-WPML-With-SureForms.md b/docs/wiki/Using-WPML-With-SureForms.md deleted file mode 100644 index 540b74aef..000000000 --- a/docs/wiki/Using-WPML-With-SureForms.md +++ /dev/null @@ -1,243 +0,0 @@ -# Using WPML With SureForms - -SureForms ships with native compatibility for **WPML**, the leading WordPress multilingual plugin. With WPML active, you can offer your forms in any number of languages without duplicating the form itself — one form serves every language, every submission is tagged with the visitor's language, and translators work from a single place. - -This guide walks you through everything you need to know as a site owner, content editor, or translator. - ---- - -## What you can do with WPML + SureForms - -Once both plugins are active and configured, SureForms automatically: - -- Translates **field labels, placeholders, help text, error messages** and **dropdown / multi-choice options** based on the visitor's language. -- Translates the **submit button** label. -- Translates the **success message** shown after a submission. -- Translates the **confirmation redirect content** (when "show same page" confirmation is used). -- Translates the **form-restriction notice** (e.g., "This form is closed"). -- Records the **submission language** alongside every entry, so you can see at a glance which language a lead came from. - -You keep one form. You collect entries from every language into the same list. Reports, integrations, and exports stay unified. - ---- - -## Before you start - -You will need: - -| Item | Notes | -|---|---| -| **WordPress 6.4 or higher** | SureForms minimum requirement. | -| **WPML Multilingual CMS** (4.5+) | Paid plugin from [wpml.org](https://wpml.org). | -| **WPML String Translation** add-on | Required to translate form labels, options, and messages. Bundled with most WPML licences. | -| **SureForms 2.5.1 or higher** | Native WPML support shipped in this release. | - -> **Polylang and other multilingual plugins:** SureForms is architected with a generic provider interface so other plugins can be supported in the future. WPML is the only multilingual plugin currently supported out of the box. - ---- - -## Step 1 — Install and configure WPML - -1. Install and activate the following plugins from your WordPress dashboard: - - **WPML Multilingual CMS** (`sitepress-multilingual-cms`) - - **WPML String Translation** - -2. Open **WPML → Languages** and choose at least two languages (for example, English as the default, plus German and French). - -3. Open **WPML → Settings → Translation Options** and confirm that **Posts** are set to *"Translatable — use translation"*. - -That's the only WPML setup that is required. SureForms takes care of the rest automatically. - ---- - -## Step 2 — Create your form (just once) - -Create a SureForms form exactly the way you always have: - -1. Go to **SureForms → Add New Form**. -2. Pick a template or start blank. -3. Add fields (text, email, dropdown, multi-choice, etc.) and configure their labels, placeholders, help text, options, and error messages in your default language. -4. Configure the submit button text, success message, and any other form settings. -5. Save the form. - -> **Important:** Do **not** try to duplicate the form for each language. SureForms intentionally registers the form post type as *non-translatable* in WPML. There is one canonical form post; WPML translates the strings on it. This keeps entries unified and avoids the maintenance nightmare of keeping multiple form copies in sync. - -When you save the form, SureForms automatically registers every translatable string with WPML under the `sureforms` text domain. - ---- - -## Step 3 — Translate your form strings - -1. Go to **WPML → String Translation**. -2. In the **"Select strings within domain"** dropdown at the top, choose `sureforms`. -3. You will see entries for every translatable piece of the form, grouped by form ID for easy scanning. For example, a form with ID `42` will show strings such as: - - - `form_42_submit_button` - - `form_42_confirmation_0_message` - - `form_42_notification_0_subject` - - `form_42_restriction_message` - - `form_42_block_{block-id}_label` - - `form_42_block_{block-id}_placeholder` - - `form_42_block_{block-id}_helpText` - - `form_42_block_{block-id}_errorMsg` - - `form_42_block_{block-id}_option_0_label` *(for dropdown / multi-choice options)* - -4. Click **"translations"** next to any string, enter the translated text for each language, and save. - -You can also use WPML's built-in **Translation Editor** workflow or send strings to a translation service from this screen — the standard WPML String Translation UI applies. - -> **Tip:** The per-form naming scheme (`form_{id}_...`) keeps every form's strings neatly grouped, so translators don't have to hunt across the entire site for a particular form's labels. - ---- - -## Step 4 — Display your form on translated pages - -SureForms forms are added to pages exactly the way they always have been — via the SureForms block or the `[sureforms id="..."]` shortcode. - -For the translated version of a form to render correctly, the **host page must also be translated in WPML**: - -1. Create the original page and embed your form on it. -2. In WPML, translate that page into each language. The form block / shortcode is carried over automatically. -3. Visit the translated URL (e.g., `/de/contact/`) — the form will render with all the labels, placeholders, options, and the submit button in German. - -If you instead use SureForms' **instant form** URL (`/form/{id}/`), append the language code to switch languages: - -``` -https://example.com/form/42/?lang=de -https://example.com/form/42/?lang=fr -``` - ---- - -## Step 5 — Verify everything is translating - -Open the translated form in the browser and confirm that the following are all in the target language: - -- ✅ Field labels -- ✅ Field placeholders -- ✅ Help text -- ✅ Inline error messages (e.g., "This field is required") -- ✅ Dropdown / multi-choice / checkbox option labels -- ✅ Submit button text -- ✅ Success / confirmation message after submission -- ✅ Form restriction notice (if the form is closed or capped) - -If anything appears in the original language: - -1. Check that the host page is translated in WPML. -2. Check that the specific string has a translation in **WPML → String Translation** (filter by domain `sureforms`). -3. Re-save the form once — this regenerates the string registry. - ---- - -## Step 6 — Collect entries and review them by language - -Submissions made on the German version of the page are stored against the **same form** as English submissions, with the submission language recorded on the entry row. - -1. Go to **SureForms → Entries**. -2. You will see a new **Language** column between *Date & Time* and *Actions*. -3. Each entry displays an uppercase language code: `EN`, `DE`, `FR`, etc. -4. Click the **Language** column header to sort entries by language. - -Older entries (and entries submitted on sites where WPML is not active) display `—`. - -> **Tip:** All your usual entry features — search, view details, delete, export — work as normal. Language is just an extra column for filtering and reporting. - ---- - -## Step 7 — Form restriction messages - -If you have configured form restrictions (a closing date or maximum entry count), the **restriction notice** shown to visitors is automatically translated: - -1. Open the form and enable restrictions (e.g., set a close date in the past, or set a max-entry limit). -2. Customise the restriction message in the form settings. -3. Translate `form_{id}_restriction_message` in **WPML → String Translation**. -4. Trigger the restriction (visit the form in a translated URL once it's closed) — visitors see the translated restriction notice. - ---- - -## What about email notifications? - -> **Note:** In the current release, email notifications (the message sent to your admin or to the visitor after a successful submission) are sent in the site's default language. Multilingual email notifications — where each email is composed in the visitor's submission language — are planned for a future SureForms release. - -The submission language is already recorded on the entry, so any custom integration that hooks into the SureForms submission flow can use it. - ---- - -## Graceful fallback when WPML is not active - -You can safely install and uninstall WPML at any time: - -- **WPML active**: forms translate based on the visitor's language. Entries are tagged. -- **WPML deactivated**: forms render in their default language as if WPML had never been installed. New entries record an empty language string. No errors, no broken pages. - -The plugin is designed so that the multilingual layer is completely silent when WPML is not active. - ---- - -## Troubleshooting - -**Translated strings don't appear in WPML String Translation** - -- Confirm that *WPML String Translation* is active (not just WPML core). -- Re-save your form once. The string registry runs on form save. -- Filter by domain `sureforms` in the WPML String Translation UI — strings from other plugins live in different domains. - -**Translated labels still render in the original language on `/de/...`** - -- Make sure the host page is translated in WPML and you are visiting the translated URL. -- Open WPML → String Translation, filter by `sureforms`, and confirm the specific strings actually have translations saved. -- Check that the page language switcher confirms you are on the German (or other target) version of the page. - -**The success message is in English even though I submitted in German** - -- Confirm `form_{id}_confirmation_0_message` has a German translation in WPML String Translation. -- Confirm the form is being submitted from a German page (`/de/...`), not from a query-string variant in some WPML modes. - -**Entries show `—` in the Language column** - -- Older entries submitted before WPML support shipped will not have a language stored. -- Entries created from the admin (e.g., test submissions, or imports) may not carry a language code. -- New front-end submissions on translated pages with WPML active will always carry a language code. - -**An entry's language code is wrong** - -- The visitor's language is determined by WPML based on the URL of the page they submitted from. If your WPML configuration uses query-string mode (`?lang=de`), make sure the parameter is on the page URL when the form is submitted. - ---- - -## Frequently asked questions - -**Do I need to duplicate the form for each language?** - -No. There is one canonical form post regardless of how many languages you support. WPML translates the strings on it. - -**Will entries from different languages mix in the same list?** - -Yes — by design. All entries from a given form, regardless of the visitor's language, belong to that one form. The Language column lets you filter and sort. - -**Can I export entries with the language?** - -The submission language is stored alongside each entry and is available to any integration that reads SureForms entries. A future SureForms release will include the language column in the standard CSV export header. - -**What languages are supported?** - -Any language WPML supports. SureForms records the standard WPML language code (`en`, `de`, `hi`, `zh-cn`, `pt-br`, etc.) — up to 20 characters, which covers the full WPML and BCP-47 locale-code range. - -**Does this work with Polylang or other multilingual plugins?** - -SureForms is built on a generic multilingual provider interface, so support for Polylang and other plugins is straightforward to add. WPML is the only multilingual plugin currently supported in the box. - ---- - -## Need help? - -If you run into anything not covered here, contact the SureForms support team at [sureforms.com/support](https://sureforms.com/support) with: - -1. Your SureForms version (Plugins → Installed Plugins → SureForms). -2. Your WPML version, including whether *WPML String Translation* is active. -3. Your form ID. -4. The specific string or behaviour that isn't translating. -5. A screenshot if possible. - -We're happy to help. diff --git a/docs/wiki/WordPress-Hooks-Reference.md b/docs/wiki/WordPress-Hooks-Reference.md deleted file mode 100644 index 1db16e372..000000000 --- a/docs/wiki/WordPress-Hooks-Reference.md +++ /dev/null @@ -1,152 +0,0 @@ -# WordPress Hooks Reference - -SureForms exposes custom actions and filters for extensibility, and hooks into WordPress core lifecycle events. - -## Custom Actions - -| Action | Parameters | Description | -|--------|-----------|-------------| -| `srfm_core_loaded` | -- | Fired after Plugin_Loader singleton is created | -| `srfm_before_submission` | `$form_data`, `$form_id` | Before form submission is processed | -| `srfm_form_submit` | `$entry_id`, `$form_id`, `$form_data` | After form submission is complete | -| `srfm_after_submission_process` | `$entry_id`, `$form_id` | After all post-submission processing | -| `srfm_before_delete_entry` | `$entry_id` | Before an entry is permanently deleted | - -## Custom Filters - -### REST API - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_rest_api_endpoints` | `$endpoints` | Add/modify REST API endpoints | - -### Form Data - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_form_submit_data` | `$form_data`, `$form_id` | Modify form data before processing | -| `srfm_entry_value` | `$value`, `$args` | Transform entry field values in responses | -| `srfm_normalize_csv_field_value` | `$value`, `$field` | Custom CSV export value formatting | -| `srfm_form_confirmation_params` | `$params`, `$form_id` | Modify confirmation settings | - -### Post Meta - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_register_post_meta` | `$meta_keys` | Register additional form post meta keys | - -### Blocks - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_is_special_block` | `$is_special`, `$block_name` | Declare a block as special (nested) | -| `srfm_handle_special_block` | `$result`, `$block` | Custom processing for special blocks | -| `srfm_extract_form_fields_field_name` | `$field_name`, `$block` | Modify generated field names | - -### Settings - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_global_settings_data` | `$settings` | Extend global settings data | -| `srfm_disable_nps_survey` | `$disable` | Disable NPS survey module | -| `srfm_enable_redirect_activation` | `$enable` | Control activation redirect behavior | - -## WordPress Core Hooks Used - -### plugins_loaded - -| Priority | Callback | Class | -|----------|----------|-------| -| default | `load_textdomain()` | Plugin_Loader | -| 99 | `load_plugin()` | Plugin_Loader | - -### init - -| Priority | Callback | Purpose | -|----------|----------|---------| -| 10 | `load_classes()` | Block registration, admin, payments | - -### rest_api_init - -| Callback | Class | Purpose | -|----------|-------|---------| -| `register_endpoints()` | Rest_Api | Register all admin REST endpoints | -| `register_routes()` | Form_Submit | Register public submission endpoints | -| `register_rest_routes()` | Payments_Settings | Register payment REST routes | - -### admin_init - -| Callback | Purpose | -|----------|---------| -| `activation_redirect()` | Redirect to onboarding after activation | -| `intercept_stripe_callback()` | Handle Stripe OAuth callback | - -### wp_enqueue_scripts - -| Callback | Class | Purpose | -|----------|-------|---------| -| Asset enqueue | Frontend_Assets | Load form CSS/JS on pages with forms | - -### enqueue_block_editor_assets - -| Callback | Class | Purpose | -|----------|-------|---------| -| Editor asset enqueue | Gutenberg_Hooks | Load block editor JS/CSS | - -## Hook Execution Order (Form Submission) - -``` -1. REST API receives POST /submit-form -2. Form_Submit::submit_form_permissions_check() - -> Nonce verification -3. Form_Submit::handle_form_submission() - -> apply_filters('srfm_form_submit_data', $data, $form_id) - -> Field validation - -> CAPTCHA verification - -> Honeypot check -4. do_action('srfm_before_submission', $data, $form_id) -5. Email notification sent -6. Entry stored in database -7. do_action('srfm_form_submit', $entry_id, $form_id, $data) -8. do_action('srfm_after_submission_process', $entry_id, $form_id) -9. Confirmation response returned -``` - -## Usage Examples - -### Adding a Custom REST Endpoint - -```php -add_filter( 'srfm_rest_api_endpoints', function( $endpoints ) { - $endpoints['my-custom/data'] = [ - 'methods' => 'GET', - 'callback' => 'my_custom_callback', - 'permission_callback' => [ \SRFM\Inc\Helper::class, 'get_items_permissions_check' ], - ]; - return $endpoints; -} ); -``` - -### Modifying Form Data Before Processing - -```php -add_filter( 'srfm_form_submit_data', function( $data, $form_id ) { - // Add custom processing - $data['custom_field'] = 'custom_value'; - return $data; -}, 10, 2 ); -``` - -### Running Code After Submission - -```php -add_action( 'srfm_form_submit', function( $entry_id, $form_id, $form_data ) { - // Send to external service, trigger automation, etc. -}, 10, 3 ); -``` - -## Related Pages - -- [Architecture Overview](Architecture-Overview) -- Plugin bootstrap and hook registration -- [REST API Reference](REST-API-Reference) -- Full endpoint documentation -- [Form Submission Flow](Form-Submission-Flow) -- Submission hook execution order diff --git a/docs/wiki/_Footer.md b/docs/wiki/_Footer.md deleted file mode 100644 index 0a54d2e24..000000000 --- a/docs/wiki/_Footer.md +++ /dev/null @@ -1,3 +0,0 @@ ---- - -**SureForms** by [Brainstorm Force](https://brainstormforce.com/) | [GitHub](https://github.com/brainstormforce/sureforms) | [Website](https://sureforms.com/) | [Support](https://support.brainstormforce.com/) diff --git a/docs/wiki/_Sidebar.md b/docs/wiki/_Sidebar.md deleted file mode 100644 index 4fe3b09d1..000000000 --- a/docs/wiki/_Sidebar.md +++ /dev/null @@ -1,36 +0,0 @@ -**[Home](Home)** - -**Getting Started** -- [Getting Started](Getting-Started) -- [Environment Configuration](Environment-Configuration) - -**Architecture** -- [Architecture Overview](Architecture-Overview) -- [Database Schema](Database-Schema) -- [Form Submission Flow](Form-Submission-Flow) - -**WordPress / Backend** -- [Gutenberg Blocks](Gutenberg-Blocks) -- [Form Fields Architecture](Form-Fields-Architecture) -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- [Payment Integration](Payment-Integration) -- [Email Notifications](Email-Notifications) -- [AI Form Builder](AI-Form-Builder) -- [Data Export & Import](Data-Export-Import) -- [Using WPML With SureForms](Using-WPML-With-SureForms) - -**Frontend / React** -- [Admin Dashboard](Admin-Dashboard) -- [State Management](State-Management) -- [Frontend Assets](Frontend-Assets) -- [Block Editor Controls](Block-Editor-Controls) - -**API** -- [REST API Reference](REST-API-Reference) - -**Development** -- [Contributing Guide](Contributing-Guide) -- [Testing Guide](Testing-Guide) -- [Deployment Guide](Deployment-Guide) -- [Troubleshooting & FAQ](Troubleshooting-FAQ) -- [Changelog](Changelog) diff --git a/inc/abilities/CLAUDE.md b/inc/abilities/CLAUDE.md deleted file mode 100644 index 0a7af5e04..000000000 --- a/inc/abilities/CLAUDE.md +++ /dev/null @@ -1,66 +0,0 @@ -# Abilities — Development Guide - -## Adding a New Ability - -1. Create a class extending `Abstract_Ability` in the appropriate subdirectory: - - `forms/` — form CRUD operations - - `entries/` — entry/submission operations - - `embedding/` — shortcodes, block markup, rendering - - `settings/` — global settings read/write - - `analytics/` — form analytics and reporting - - `export/` — form and entry import/export -2. Register it in `Abilities_Registrar::register_abilities()` - -## Abstract_Ability Contract - -Every ability must implement: -- **Constructor** — set `$id`, `$label`, `$description`, `$capability` -- **`get_input_schema()`** — JSON Schema for input validation -- **`get_output_schema()`** — JSON Schema for output contract -- **`execute( $input )`** — business logic, return array or `WP_Error` -- **`get_annotations()`** — override if not default (`readonly`, `destructive`, `idempotent`) - -## Conventions - -- ID format: `sureforms/-` (e.g. `sureforms/list-forms`) -- Namespace: `SRFM\Inc\Abilities\\` -- Return `WP_Error` for failures, never throw exceptions -- Default capability: `edit_posts` — override per-ability where needed -- Destructive abilities must set `destructive: true` in annotations -- All classes need `@since x.x.x` tags -- Add PHPUnit tests in `tests/unit/inc/abilities/` for every new ability - -## Architecture - -``` -inc/abilities/ -├── abstract-ability.php # Base class — do not modify without updating all abilities -├── abilities-registrar.php # Singleton — registers category + all abilities -├── forms/ -│ ├── list-forms.php # readonly, idempotent -│ ├── create-form.php # uses Field_Mapping engine -│ ├── get-form.php # readonly, idempotent — parses Gutenberg blocks -│ ├── delete-form.php # destructive -│ ├── duplicate-form.php # delegates to \SRFM\Inc\Duplicate_Form -│ ├── update-form.php # write, idempotent — title/status/metadata -│ └── get-form-stats.php # readonly, idempotent — entry counts -├── entries/ -│ ├── list-entries.php # readonly, idempotent — paginated listing -│ ├── get-entry.php # readonly, idempotent — decrypted form_data -│ ├── update-entry-status.php # write, idempotent — read/unread/trash/restore -│ └── delete-entry.php # destructive — permanent delete -├── embedding/ -│ └── get-shortcode.php # readonly, idempotent -├── settings/ -│ ├── get-global-settings.php # readonly, idempotent — all setting categories -│ └── update-global-settings.php # write, idempotent — delegates to Global_Settings -└── analytics/ - └── get-form-analytics.php # readonly, idempotent — submission date queries -``` - -## Extensibility - -- **Filter:** `srfm_register_abilities` — third-party plugins can add abilities (must extend `Abstract_Ability`) -- **Hooks:** `srfm_before_ability_execute` / `srfm_after_ability_execute` — fire around every execution -- **Collision guard:** Registrar checks `wp_has_ability()` before registering to avoid duplicates with zipwp-mcp -- **WP < 6.9:** Registrar bails in constructor — zero overhead when Abilities API is unavailable diff --git a/internal-docs/README.md b/internal-docs/README.md deleted file mode 100644 index 9a933cae0..000000000 --- a/internal-docs/README.md +++ /dev/null @@ -1,160 +0,0 @@ -# SureForms Internal Documentation - -**Version:** 2.5.0 -**Last Updated:** February 2026 -**Target Audience:** Developers, AI Agents, Technical Team - ---- - -## Welcome - -This is the internal documentation for **SureForms Free** and **SureForms Pro** - WordPress form builder plugins used by 300,000+ websites. - -**SureForms Free:** AI-powered form builder with payments (Stripe), native WordPress blocks, 16+ fields -**SureForms Pro:** Advanced features - conversational forms, multi-step, conditional logic, PayPal, 24+ integrations - ---- - -## Quick Start - -### For New Developers (1 Hour) - -1. **Install locally:** - ```bash - cd /path/to/wordpress/wp-content/plugins - git clone https://github.com/brainstormforce/sureforms.git - git clone https://github.com/brainstormforce/sureforms-pro.git - cd sureforms && npm install && npm run build - cd ../sureforms-pro && npm install && npm run build - ``` - -2. **Activate plugins:** - - SureForms Free (required) - - SureForms Pro (optional, requires Free) - -3. **Create first form:** - - Dashboard → SureForms → Add New - - Try AI form builder or blank form - - Add fields from block inserter - - Publish and test submission - -4. **Read these docs:** - - [Architecture](architecture.md) - System design - - [Codebase Map](codebase-map.md) - Where is what - - [APIs](apis.md) - REST, AJAX, hooks - -### For AI Agents - -Start with [ai-agent-guide.md](ai-agent-guide.md) for codebase conventions, patterns, and agent-specific guidance. - ---- - -## Key Files & Directories - -| Path | Purpose | -|------|---------| -| `sureforms/inc/` | Core PHP logic | -| `sureforms/inc/blocks/` | Gutenberg block PHP | -| `sureforms/src/` | React components (admin UI) | -| `sureforms/inc/payments/` | Stripe payment processing | -| `sureforms/inc/ai-form-builder/` | AI form generation | -| `sureforms-pro/inc/business/` | Pro features (PayPal, registration, PDF) | -| `sureforms-pro/inc/pro/native-integrations/` | 24+ service integrations | - ---- - -## Build System - -**SureForms Free:** -```bash -npm run build # Full build (webpack + sass + grunt) -npm run start # Dev mode (watch) -npm run lint-js:fix # Fix JS linting -npm run makepot # Generate translation files -``` - -**SureForms Pro:** -```bash -npm run build # Full build -npm run package # Create distributable zip -``` - -Both use: -- **@wordpress/scripts** (webpack 5, Babel, ESLint) -- **Grunt** (minification, compression) -- **Sass** for CSS compilation - ---- - -## Database Tables - -Custom tables (via `inc/database/`): - -| Table | Purpose | -|-------|---------| -| `wp_sureforms_entries` | Form submissions | -| `wp_sureforms_payments` | Payment transactions (Stripe/PayPal) | -| `wp_sureforms_integrations` | Native integration credentials (encrypted) | -| `wp_sureforms_save_resume` | Draft form state (Pro) | - ---- - -## Testing - -**Unit Tests:** -```bash -composer test # PHPUnit -npm run test:unit # Jest (JS) -``` - -**E2E Tests:** -```bash -npm run play:up # Start wp-env -npm run play:run # Run Playwright tests -``` - -**Code Quality:** -```bash -composer lint # PHP_CodeSniffer -composer phpstan # Static analysis -npm run lint-js # ESLint -``` - ---- - -## Release Process - -**Free (WordPress.org):** -1. Update version in `sureforms.php` and `package.json` -2. Run `npm run build && grunt release` -3. Files excluded via `.distignore` -4. SVN commit to WordPress.org repo - -**Pro (Custom Distribution):** -1. Update version in `sureforms-pro.php` -2. Run `npm run build && npm run package` -3. Generates `sureforms-pro.zip` -4. Deploy to licensing server - ---- - -## Support & Resources - -- **GitHub (Free):** https://github.com/brainstormforce/sureforms -- **GitHub (Pro):** https://github.com/brainstormforce/sureforms-pro -- **Documentation:** https://sureforms.com/docs/ -- **Bug Bounty:** https://brainstormforce.com/bug-bounty-program/ -- **Support:** support@brainstormforce.com - ---- - -## Next Steps - -- **Product Vision:** [product-vision.md](product-vision.md) - Understand goals and personas -- **Architecture:** [architecture.md](architecture.md) - System design and data flow -- **Onboarding:** [onboarding.md](onboarding.md) - 1-hour, 1-day, 1-week learning paths - ---- - -**Maintained by:** SureForms Engineering Team -**Contact:** engineering@brainstormforce.com diff --git a/internal-docs/ai-agent-guide.md b/internal-docs/ai-agent-guide.md deleted file mode 100644 index 867f3c7ea..000000000 --- a/internal-docs/ai-agent-guide.md +++ /dev/null @@ -1,459 +0,0 @@ -# AI Agent Guide - -**Version:** 2.5.0 -**For:** AI Coding Agents & LLMs - ---- - -## Overview - -This guide helps AI agents understand SureForms codebase conventions, make safe changes, and avoid common pitfalls. - ---- - -## Core Principles - -1. **Security First:** Sanitize inputs, escape outputs, use prepared statements -2. **WordPress Standards:** Follow WP coding standards, use WP functions -3. **Backward Compatibility:** Don't break existing forms/entries -4. **Performance:** Avoid N+1 queries, cache when possible -5. **Testing:** Write tests, run existing tests before committing - ---- - -## File Modification Guidelines - -### ✅ Safe to Modify - -**Add features (low risk):** -- New blocks: `inc/blocks/new-block/` -- New integrations (Pro): `inc/pro/native-integrations/integrations/new-service/` -- New validation rules: Add to `inc/field-validation.php` -- New hooks: Add filters/actions (don't remove existing) - -**Extend existing:** -- Add sanitization functions to `inc/helper.php` -- Add validation methods to `inc/field-validation.php` -- Add utility functions to helper classes - -### ⚠️ Modify with Caution - -**Database schema changes:** -- Requires migration script -- Must handle existing data -- Version increments in `inc/database/tables/*.php` - -**Payment processing:** -- `inc/payments/stripe/stripe-webhook.php` -- `inc/business/payments/pay-pal/webhook-listener.php` -- **Risk:** Financial impact if broken - -**Form submission pipeline:** -- `inc/form-submit.php` -- **Risk:** Could break all forms - -### 🚫 High Risk - Avoid Unless Necessary - -**Core WordPress integration:** -- `plugin-loader.php` -- `sureforms.php` / `sureforms-pro.php` - -**Database base class:** -- `inc/database/base.php` (inherited by all tables) - -**Critical security:** -- Nonce verification logic -- Capability checks -- Encryption (Pro): `inc/pro/native-integrations/encryption.php` - ---- - -## Common Patterns - -### Adding a New Field Type - -1. **Create block:** - ``` - inc/blocks/my-field/block.php - src/blocks/my-field/ - ``` - -2. **Register block:** - ```php - // inc/blocks/my-field/block.php - namespace SRFM\Inc\Blocks\My_Field; - - class Block extends \SRFM\Inc\Blocks\Base { - public static function register() { - register_block_type( - 'sureforms/my-field', - [ - 'render_callback' => [__CLASS__, 'render'], - 'attributes' => [/* schema */], - ] - ); - } - - public static function render($attributes) { - // Return HTML - } - } - ``` - -3. **Add to block registry:** - ```php - // inc/blocks/register.php - My_Field\Block::register(); - ``` - -4. **Add sanitization:** - ```php - // inc/helper.php - public static function sanitize_by_field_type($type, $value) { - switch ($type) { - case 'my-field': - return sanitize_text_field($value); - } - } - ``` - -### Adding a REST Endpoint - -```php -// inc/rest-api.php or new file -public function register_routes() { - register_rest_route( - 'sureforms/v1', - '/my-endpoint', - [ - 'methods' => 'POST', - 'callback' => [$this, 'handle_request'], - 'permission_callback' => [$this, 'check_permissions'], - ] - ); -} - -public function check_permissions() { - // CRITICAL: Always verify permissions - return current_user_can('manage_options'); -} - -public function handle_request($request) { - // CRITICAL: Always verify nonce - $nonce = $request->get_header('X-WP-Nonce'); - if (!wp_verify_nonce($nonce, 'wp_rest')) { - return new \WP_Error('invalid_nonce', 'Invalid nonce', ['status' => 403]); - } - - // Sanitize inputs - $data = $request->get_params(); - $clean_data = Helper::sanitize_array_recursively($data); - - // Process... - - return ['success' => true]; -} -``` - -### Adding an Integration (Pro) - -1. **Create integration folder:** - ``` - inc/pro/native-integrations/integrations/myservice/ - ├── config.json - └── actions/ - └── send-data.php - ``` - -2. **config.json:** - ```json - { - "name": "MyService", - "slug": "myservice", - "auth_method": "api_key", - "api_endpoint": "https://api.myservice.com/v1/", - "actions": [ - { - "name": "Send Data", - "slug": "send-data", - "endpoint": "contacts" - } - ] - } - ``` - -3. **actions/send-data.php:** - ```php - namespace SRFM_PRO\Inc\Pro\Native_Integrations\Integrations\Myservice\Actions; - - class Send_Data { - public function execute($integration_data, $form_data) { - $api_key = $integration_data['api_key']; - $response = wp_remote_post( - 'https://api.myservice.com/v1/contacts', - [ - 'headers' => ['Authorization' => 'Bearer ' . $api_key], - 'body' => json_encode($form_data), - ] - ); - - if (is_wp_error($response)) { - return ['success' => false, 'error' => $response->get_error_message()]; - } - - return ['success' => true, 'response' => wp_remote_retrieve_body($response)]; - } - } - ``` - ---- - -## Security Patterns - -### Input Sanitization - -```php -// Text -$name = sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) ); - -// Email -$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); - -// Number -$id = absint( $_POST['id'] ?? 0 ); - -// Array (recursive) -$data = Helper::sanitize_array_recursively( $_POST['data'] ?? [] ); - -// Textarea -$message = Helper::sanitize_textarea( wp_unslash( $_POST['message'] ?? '' ) ); -``` - -### Output Escaping - -```php -// HTML content -echo esc_html( $user_name ); - -// Attributes -echo '
    '; - -// URLs -echo ''; - -// Rich text (allows safe HTML) -echo wp_kses_post( $content ); -``` - -### Database Queries - -```php -global $wpdb; - -// ✅ Good - Prepared statement -$results = $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$wpdb->prefix}sureforms_entries WHERE form_id = %d AND status = %s", - $form_id, - $status - ) -); - -// ❌ Bad - SQL injection risk -$results = $wpdb->query("DELETE FROM {$wpdb->prefix}sureforms_entries WHERE id = $_POST[id]"); -``` - ---- - -## Testing Your Changes - -### Before Committing - -```bash -# 1. Lint PHP -composer lint - -# 2. Run PHP tests -composer test - -# 3. Lint JavaScript -npm run lint-js - -# 4. Run JS tests -npm run test:unit - -# 5. Build assets -npm run build - -# 6. Test in browser -# - Create form -# - Submit form -# - Check entries -# - Verify no console errors -``` - -### E2E Testing - -```bash -# Start environment -npm run play:up - -# Run tests -npm run play:run - -# Interactive mode (with browser) -npm run play:run:interactive -``` - ---- - -## Database Queries - Common Patterns - -### Get Entries - -```php -use SRFM\Inc\Database\Tables\Entries; - -// Get all for form -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='] - ], - 'orderby' => 'created_at', - 'order' => 'DESC', -]); - -// With pagination -$entries = Entries::get_all([ - 'where' => [['key' => 'form_id', 'value' => 123, 'compare' => '=']], - 'limit' => 20, - 'offset' => 40 // Page 3 (20 per page) -]); - -// Count entries -$count = Entries::get_count([ - 'where' => [['key' => 'status', 'value' => 'published', 'compare' => '=']] -]); -``` - -### Get Payments - -```php -use SRFM\Inc\Database\Tables\Payments; - -// Get completed payments -$payments = Payments::get_all([ - 'where' => [ - ['key' => 'status', 'value' => 'completed', 'compare' => '='] - ] -]); - -// Get payment by transaction ID -$payment = Payments::get_by_transaction_id('ch_xxxxx', 'stripe'); -``` - ---- - -## Hooks - When to Use - -### Modify Data Before Save - -```php -// Filter entry data before save -add_filter('sureforms_submit_form_data', function($data, $form_id) { - // Add custom field - $data['custom_timestamp'] = current_time('mysql'); - return $data; -}, 10, 2); -``` - -### Trigger External Action - -```php -// Action after entry save -add_action('sureforms_after_entry_save', function($entry_id, $form_id, $data) { - // Send to external CRM - send_to_crm($data); -}, 10, 3); -``` - -### Modify Email - -```php -// Filter email template -add_filter('sureforms_email_template', function($html, $entry_id) { - // Add custom header - return '
    ...
    ' . $html; -}, 10, 2); -``` - ---- - -## Debugging Tips - -### Enable Debug Mode - -```php -// wp-config.php -define('WP_DEBUG', true); -define('WP_DEBUG_LOG', true); -define('WP_DEBUG_DISPLAY', false); -``` - -### Check Debug Log - -```bash -tail -f wp-content/debug.log -``` - -### JavaScript Console - -```javascript -// Check for errors -// Open browser DevTools → Console - -// SureForms global -console.log(window.sureforms); -``` - -### Database Queries - -```php -// Install Query Monitor plugin -// Shows all queries, slow queries, errors -``` - ---- - -## Common Pitfalls to Avoid - -1. **Don't bypass sanitization:** "It's internal, it's safe" → Still sanitize -2. **Don't skip nonce checks:** Every AJAX/REST endpoint needs verification -3. **Don't use `$_POST` directly:** Always sanitize first -4. **Don't concatenate SQL:** Use `$wpdb->prepare()` -5. **Don't echo user data raw:** Use `esc_html()`, `esc_attr()`, etc. -6. **Don't modify database schema without migration:** Breaks existing sites -7. **Don't remove existing hooks:** Other plugins may depend on them -8. **Don't hardcode `wp_` prefix:** Use `$wpdb->prefix` - ---- - -## Getting Help - -**Documentation:** -- This folder: All internal docs -- WordPress Codex: https://codex.wordpress.org/ -- React Docs: https://react.dev/ - -**Code Search:** -- Use Glob/Grep tools to find patterns -- Example: `grep -r "sanitize_text_field" inc/` → See how sanitization is used - -**Ask Before:** -- Breaking changes -- Database schema changes -- Removing existing functionality - ---- - -**Next:** [Troubleshooting](troubleshooting.md) - Common problems and solutions diff --git a/internal-docs/apis.md b/internal-docs/apis.md deleted file mode 100644 index 0a7a16ec7..000000000 --- a/internal-docs/apis.md +++ /dev/null @@ -1,418 +0,0 @@ -# APIs - -Complete API reference for SureForms Free & Pro. - ---- - -## REST API Endpoints - -### Free (`/sureforms/v1/`) - -**Form Management:** -```http -POST /sureforms/v1/generate-form -GET /sureforms/v1/forms -GET /sureforms/v1/forms/{id} -DELETE /sureforms/v1/forms/{id} -``` - -**Form Submission:** -```http -POST /sureforms/v1/submit-form -``` -**Body:** `{ form_id, nonce, field_data... }` -**Response:** `{ success, message, entry_id }` - -**Entries:** -```http -GET /sureforms/v1/entries/list -GET /sureforms/v1/entries/{id} -DELETE /sureforms/v1/entries/{id} -POST /sureforms/v1/entries/export -``` - -**Payments:** -```http -GET /sureforms/v1/payments/list -GET /sureforms/v1/payments/{id} -``` - -**Stripe Webhooks:** -```http -POST /sureforms/webhook_test # Stripe webhooks -POST /sureforms/webhook_live -``` - -**Settings:** -```http -GET /sureforms/v1/settings -POST /sureforms/v1/settings/update -``` - -### Pro (`/sureforms-pro/v1/`) - -**OAuth:** -```http -POST /sureforms-pro/v1/oauth/authorize -POST /sureforms-pro/v1/oauth/callback -``` - -**PayPal Webhooks:** -```http -POST /sureforms-pro/paypal-test-webhook -POST /sureforms-pro/paypal-live-webhook -``` - -**User Registration:** -```http -POST /sureforms-pro/v1/login -POST /sureforms-pro/v1/lost-password -POST /sureforms-pro/v1/reset-password -``` - ---- - -## AJAX Handlers - -### Free (30 handlers) - -**Admin:** -```php -'srfm_admin_action' # Generic admin handler -'srfm_get_forms' # Fetch forms list -'srfm_delete_form' # Delete form -'srfm_duplicate_form' # Duplicate form -'srfm_export_entries' # Export CSV -``` - -**Payments (Public):** -```php -'wp_ajax_srfm_create_payment_intent' # Stripe payment -'wp_ajax_nopriv_srfm_create_payment_intent' -'wp_ajax_srfm_create_subscription_intent' # Stripe subscription -'wp_ajax_nopriv_srfm_create_subscription_intent' -``` - -**Admin Stripe:** -```php -'srfm_stripe_cancel_subscription' # Cancel subscription -'srfm_stripe_pause_subscription' # Pause subscription -``` - -**Admin Payments:** -```php -'srfm_create_payment_refund' # Create refund -``` - -**Form Validation (Public):** -```php -'wp_ajax_srfm_field_unique_validation' # Check unique fields -'wp_ajax_nopriv_srfm_field_unique_validation' -``` - -### Pro (22 handlers) - -**PayPal (Public):** -```php -'wp_ajax_srfm_pro_create_paypal_order' -'wp_ajax_nopriv_srfm_pro_create_paypal_order' -'wp_ajax_srfm_pro_create_paypal_subscription' -'wp_ajax_nopriv_srfm_pro_create_paypal_subscription' -``` - -**PayPal Admin:** -```php -'srfm_pro_paypal_cancel_subscription' -'srfm_pro_paypal_pause_subscription' -``` - -**PDF:** -```php -'srfm_pro_generate_pdf' # Generate PDF from entry -'srfm_pro_download_pdf' # Download PDF -``` - -**Licensing:** -```php -'srfm_pro_activate_license' -'srfm_pro_deactivate_license' -``` - -**Entries:** -```php -'sureforms_pro_entry_delete_file' # Delete uploaded file -``` - ---- - -## WordPress Hooks - -### Actions (Form Submission) - -```php -// Before entry save -do_action('sureforms_before_entry_save', $form_id, $entry_data); - -// After entry save -do_action('sureforms_after_entry_save', $entry_id, $form_id, $entry_data); - -// After payment completed -do_action('sureforms_payment_completed', $payment_id, $entry_id, $gateway); - -// After integration success -do_action('sureforms_integration_success', $integration_type, $response); - -// Before email send -do_action('sureforms_before_email_send', $to, $subject, $message); - -// After form submission (success) -do_action('sureforms_form_submitted', $entry_id, $form_id); -``` - -### Filters (Data Modification) - -```php -// Modify form data before save -$entry_data = apply_filters('sureforms_submit_form_data', $entry_data, $form_id); - -// Modify payment amount -$amount = apply_filters('sureforms_payment_amount', $amount, $form_id, $entry_data); - -// Modify email template -$html = apply_filters('sureforms_email_template', $html, $entry_id, $form_id); - -// Modify email subject -$subject = apply_filters('sureforms_email_subject', $subject, $form_id); - -// Modify email recipients -$to = apply_filters('sureforms_email_recipients', $to, $entry_id, $form_id); - -// Modify confirmation message -$message = apply_filters('sureforms_confirmation_message', $message, $entry_id); - -// Modify field validation -$is_valid = apply_filters('sureforms_validate_field', $is_valid, $field, $value); - -// Modify sanitized value -$value = apply_filters("sureforms_sanitize_{$field_type}", $value, $field); -``` - -### Integration Hooks (Pro) - -```php -// Before integration request -do_action('sureforms_pro_before_integration', $integration, $data); - -// After integration request -do_action('sureforms_pro_after_integration', $integration, $response); - -// Integration error -do_action('sureforms_pro_integration_error', $integration, $error); -``` - ---- - -## JavaScript APIs - -### Global Objects - -```javascript -// Form settings -window.sureforms = { - ajaxUrl: '/wp-admin/admin-ajax.php', - nonce: 'xxx', - formId: 123, - settings: {...} -}; - -// Block editor -window.sureformsBlocks = { - registerBlock: (name, settings) => {...}, - getBlock: (name) => {...} -}; -``` - -### Custom Events - -```javascript -// Form submitted -document.addEventListener('sureforms-form-submitted', (e) => { - console.log('Entry ID:', e.detail.entryId); -}); - -// Payment completed -document.addEventListener('sureforms-payment-completed', (e) => { - console.log('Payment ID:', e.detail.paymentId); -}); - -// Validation error -document.addEventListener('sureforms-validation-error', (e) => { - console.log('Field:', e.detail.field, 'Error:', e.detail.message); -}); -``` - ---- - -## Database APIs - -### Entry Operations - -```php -use SRFM\Inc\Database\Tables\Entries; - -// Get all entries for a form -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='] - ], - 'orderby' => 'created_at', - 'order' => 'DESC', - 'limit' => 20, - 'offset' => 0 -]); - -// Get single entry -$entry = Entries::get_by_id(456); - -// Insert entry -$entry_id = Entries::insert([ - 'form_id' => 123, - 'entry_data' => json_encode($data), - 'status' => 'published' -]); - -// Update entry -Entries::update(456, ['status' => 'trash']); - -// Delete entry -Entries::delete(456); -``` - -### Payment Operations - -```php -use SRFM\Inc\Database\Tables\Payments; - -// Get payments -$payments = Payments::get_all_main_payments([ - 'where' => [ - ['key' => 'status', 'value' => 'completed', 'compare' => '='] - ] -]); - -// Create payment -$payment_id = Payments::insert([ - 'entry_id' => 456, - 'transaction_id' => 'ch_xxx', - 'gateway' => 'stripe', - 'status' => 'pending', - 'total_amount' => 99.99, - 'currency' => 'USD' -]); - -// Update payment status -Payments::update($payment_id, ['status' => 'completed']); -``` - ---- - -## Helper APIs - -### Sanitization - -```php -use SRFM\Inc\Helper; - -// Sanitize by field type -$clean = Helper::sanitize_by_field_type('email', $value); -$clean = Helper::sanitize_by_field_type('textarea', $value); -$clean = Helper::sanitize_by_field_type('number', $value); - -// Email-specific -$email = Helper::sanitize_email_header($raw_email); - -// Recursive sanitization -$clean_array = Helper::sanitize_array_recursively($array); -``` - -### Validation - -```php -use SRFM\Inc\Field_Validation; - -// Validate field -$result = Field_Validation::validate_field($field_config, $value); -// Returns: ['valid' => true/false, 'message' => 'Error message'] - -// Validate entire form -$result = Field_Validation::validate_form_data($form_id, $data); -``` - -### Payment Helpers - -```php -use SRFM\Inc\Payments\Payment_Helper; - -// Validate amount against form config -$result = Payment_Helper::validate_payment_amount($amount, $currency, $form_id, $block_id); - -// Get currency -$currency = Payment_Helper::get_currency(); -``` - ---- - -## Encryption API (Pro) - -```php -use SRFM_PRO\Inc\Pro\Native_Integrations\Encryption; - -$enc = new Encryption(); - -// Encrypt sensitive data -$encrypted = $enc->encrypt('oauth_token_12345'); - -// Decrypt -$decrypted = $enc->decrypt($encrypted); - -// Storage: wp_sureforms_integrations.data column -``` - -**Algorithm:** AES-256-CTR -**Key:** `SRFM_ENCRYPTION_KEY` constant or `LOGGED_IN_KEY` - ---- - -## Integration Workflow API (Pro) - -```php -use SRFM_PRO\Inc\Pro\Native_Integrations\Services\Workflow_Processor; - -// Execute integration workflow -$processor = new Workflow_Processor(); -$result = $processor->execute_workflow($integration_config, $entry_data); - -// Returns: ['success' => true/false, 'response' => API response] -``` - ---- - -## Rate Limiting - -**Note:** No built-in rate limiting. Implementations should add: - -```php -// Example pattern (not in core) -$key = 'form_submit_' . $user_ip; -$attempts = get_transient($key) ?: 0; - -if ($attempts >= 5) { - wp_send_json_error('Too many submissions', 429); -} - -set_transient($key, $attempts + 1, 15 * MINUTE_IN_SECONDS); -``` - ---- - -**Next:** [Coding Standards](coding-standards.md) - PHP/JS conventions diff --git a/internal-docs/architecture.md b/internal-docs/architecture.md deleted file mode 100644 index 9e59e0b81..000000000 --- a/internal-docs/architecture.md +++ /dev/null @@ -1,329 +0,0 @@ -# Architecture - -**Version:** 2.5.0 - ---- - -## System Overview - -SureForms is a WordPress form builder with a **Free + Pro** plugin architecture: - -``` -WordPress Site -├── SureForms Free (Required) -│ ├── Core form functionality -│ ├── Stripe payments -│ ├── AI form builder -│ └── Entry management -└── SureForms Pro (Optional Extension) - ├── Advanced features (conditional logic, multi-step) - ├── PayPal payments - ├── User registration/login - └── 24+ native integrations -``` - ---- - -## High-Level Architecture - -``` -┌─────────────────────────────────────────────────────────┐ -│ WordPress Admin │ -│ ┌──────────────────────────────────────────────────┐ │ -│ │ React Admin UI (src/) │ │ -│ │ - Form Builder (Gutenberg Blocks) │ │ -│ │ - Entry Dashboard │ │ -│ │ - Settings Panels │ │ -│ └──────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ↕ -┌─────────────────────────────────────────────────────────┐ -│ REST API Layer │ -│ - /sureforms/v1/* (Free) │ -│ - /sureforms-pro/v1/* (Pro) │ -│ - AJAX handlers (wp_ajax_*) │ -└─────────────────────────────────────────────────────────┘ - ↕ -┌─────────────────────────────────────────────────────────┐ -│ Business Logic (inc/) │ -│ ┌──────────┬──────────┬──────────┬──────────────────┐ │ -│ │ Forms │ Payments │ AI │ Integrations │ │ -│ │ Submit │ Stripe │ Builder │ (Pro: 24+) │ │ -│ │ Validate │ PayPal │ Field │ Mailchimp, etc │ │ -│ │ Store │ Webhooks │ Mapping │ │ │ -│ └──────────┴──────────┴──────────┴──────────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ↕ -┌─────────────────────────────────────────────────────────┐ -│ Data Layer │ -│ ┌──────────────┬────────────────┬──────────────────┐ │ -│ │ WP Options │ Custom Tables │ Encrypted Data │ │ -│ │ Form configs │ Entries │ OAuth tokens │ │ -│ │ Settings │ Payments │ API keys │ │ -│ └──────────────┴────────────────┴──────────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ↕ -┌─────────────────────────────────────────────────────────┐ -│ Frontend Rendering │ -│ - Gutenberg Blocks (PHP + JS) │ -│ - Form submission (AJAX) │ -│ - reCAPTCHA/Turnstile │ -│ - Real-time validation │ -└─────────────────────────────────────────────────────────┘ -``` - ---- - -## Component Breakdown - -### 1. Block System (Gutenberg) - -**Free Blocks (16):** -- Input types: Text, Email, Number, Phone, URL, Textarea, Dropdown -- Special: Address, Checkbox, Multiple Choice, GDPR, Payment -- Layout: Separator, Heading, Image, Icon, Custom Button - -**Pro Blocks (8):** -- Input: Date Picker, Time Picker, Rating, Slider, Hidden, Password -- Advanced: File Upload, HTML, Page Break (multi-step) -- User Registration: Login, Register, Lost Password, Reset Password - -**Location:** `inc/blocks/*/block.php` and `src/blocks/*/` - -### 2. Form Submission Pipeline - -``` -User Submits Form - ↓ -1. Nonce Verification (inc/form-submit.php:91-95) - ↓ -2. Field Validation (inc/field-validation.php) - - Type validation (email, URL, number) - - Required fields - - Min/max constraints - - Custom regex - ↓ -3. Sanitization (inc/helper.php:258-274) - - Per-field type sanitization - - XSS prevention (wp_kses_post) - - SQL injection prevention - ↓ -4. CAPTCHA Validation (inc/form-submit.php:317-386) - - reCAPTCHA v2/v3 - - Cloudflare Turnstile - - hCaptcha - ↓ -5. Business Logic (Pro: Conditional Logic) - ↓ -6. Payment Processing (if enabled) - - Stripe: Create PaymentIntent - - PayPal: Create Order - ↓ -7. Entry Storage (inc/database/tables/entries.php) - ↓ -8. Integrations (Pro: Send to external services) - ↓ -9. Email Notifications (inc/email/email-template.php) - ↓ -10. Confirmation/Redirect -``` - -### 3. Payment Processing - -**Stripe (Free):** -- Location: `inc/payments/stripe/` -- Flow: - 1. Frontend creates PaymentIntent (AJAX) - 2. User completes payment on Stripe - 3. Webhook updates payment status - 4. Entry marked as paid - -**PayPal (Pro):** -- Location: `inc/business/payments/pay-pal/` -- Flow: - 1. Frontend creates Order via API - 2. User redirected to PayPal - 3. Webhook confirms payment - 4. Entry status updated - -**Database:** `wp_sureforms_payments` table - -### 4. AI Form Builder - -**Location:** `inc/ai-form-builder/` - -**Flow:** -``` -User Prompt ("Create a contact form") - ↓ -AI Middleware (SRFM_AI_MIDDLEWARE) - ↓ -Field Mapping (field-mapping.php) - ↓ -Gutenberg Block Generation - ↓ -Form Preview -``` - -**API:** `https://credits.startertemplates.com/sureforms/` - -### 5. Native Integrations (Pro) - -**Location:** `inc/pro/native-integrations/` - -**Supported Services (24+):** -- Email: Mailchimp, Brevo, Constant Contact -- CRM: HubSpot, Salesforce, Zoho -- Messaging: Telegram, Slack, Discord -- Marketing: FluentCRM, MailerPress -- Booking: LatePoint -- Automation: Zapier, OttoKit - -**Architecture:** -``` -Generic Provider Base Class - ↓ -Integration-Specific Classes -(inc/pro/native-integrations/integrations/*/actions/) - ↓ -OAuth Handler (oauth-handler.php) - ↓ -Encrypted Storage (encryption.php - AES-256-CTR) - ↓ -Workflow Processor (services/workflow-processor.php) -``` - -### 6. Database Schema - -**Custom Tables:** - -```sql --- Entries -wp_sureforms_entries -- id (INT, PK) -- form_id (INT) -- entry_data (LONGTEXT, JSON) -- created_at (DATETIME) -- updated_at (DATETIME) -- status (VARCHAR - 'published', 'trash') -- user_agent (TEXT) -- ip_address (VARCHAR) - --- Payments -wp_sureforms_payments -- id (INT, PK) -- entry_id (INT, FK) -- transaction_id (VARCHAR) -- gateway (ENUM 'stripe', 'paypal') -- status (ENUM 'pending', 'completed', 'failed', 'refunded') -- total_amount (DECIMAL) -- refunded_amount (DECIMAL) -- currency (VARCHAR) - --- Integrations (Pro) -wp_sureforms_integrations -- id (INT, PK) -- type (VARCHAR - 'mailchimp', 'hubspot', etc.) -- data (LONGTEXT, encrypted JSON) -- status (ENUM 'active', 'inactive') - --- Save & Resume (Pro) -wp_sureforms_save_resume -- id (INT, PK) -- form_id (INT) -- token (VARCHAR, unique) -- form_data (LONGTEXT, JSON) -- expires_at (DATETIME) -``` - ---- - -## Security Architecture - -**Input Validation:** -- Nonce verification on all AJAX/REST requests -- Sanitization via `sanitize_*` functions -- Type casting (absint, floatval) - -**Output Escaping:** -- `esc_html()`, `esc_attr()`, `esc_url()` throughout -- `wp_kses_post()` for rich text - -**SQL Security:** -- `$wpdb->prepare()` for all queries -- No direct SQL concatenation - -**Payment Security:** -- Webhook signature verification (Stripe, PayPal) -- Server-side amount validation -- PCI-DSS compliant (payments handled by Stripe/PayPal) - -**Encryption (Pro):** -- AES-256-CTR for OAuth tokens -- Key derivation: `SRFM_ENCRYPTION_KEY` constant or `LOGGED_IN_KEY` -- Storage: `wp_sureforms_integrations.data` (encrypted JSON) - ---- - -## Performance Optimizations - -1. **Lazy Loading:** React components loaded on-demand -2. **Asset Minification:** Grunt uglifies JS/CSS -3. **Database Indexing:** Indexes on `form_id`, `entry_id`, `status` -4. **Caching:** Transients for integration API calls -5. **Query Optimization:** `SELECT` only needed columns - ---- - -## Extensibility - -**Hooks (Filters):** -- `sureforms_submit_form_data` - Modify data before save -- `sureforms_email_template` - Customize email HTML -- `sureforms_payment_amount` - Modify payment amount - -**Hooks (Actions):** -- `sureforms_after_entry_save` - Trigger after entry created -- `sureforms_payment_completed` - After successful payment -- `sureforms_integration_success` - After integration sends data - -**Developer APIs:** -- REST: `/sureforms/v1/*` -- PHP: `SRFM\Inc\Helper` class -- JavaScript: `window.sureforms.*` globals - ---- - -## Technology Stack - -| Layer | Technologies | -|-------|-------------| -| Frontend (Admin) | React 18, @wordpress/components, TanStack Query | -| Frontend (Public) | Vanilla JS, DOMPurify, intl-tel-input | -| Backend | PHP 7.4+, WordPress 6.4+ | -| Build | webpack 5, Babel, Sass, Grunt | -| Testing | PHPUnit, Jest, Playwright | -| Database | MySQL 5.7+, MariaDB 10.3+ | - ---- - -## Deployment Architecture - -``` -Developer Machine - ↓ -Git Commit → GitHub Actions CI - ↓ -Automated Tests (PHPUnit, Playwright) - ↓ -Build Assets (npm run build) - ↓ -Free: WordPress.org SVN -Pro: Licensing Server (ZIP) - ↓ -End User WordPress Sites (300,000+) -``` - ---- - -**Next:** [Codebase Map](codebase-map.md) for detailed folder structure diff --git a/internal-docs/codebase-map.md b/internal-docs/codebase-map.md deleted file mode 100644 index 9d85000c2..000000000 --- a/internal-docs/codebase-map.md +++ /dev/null @@ -1,303 +0,0 @@ -# Codebase Map - -**Version:** 2.5.0 - -Complete folder-by-folder guide to both plugins. - ---- - -## SureForms Free (`sureforms/`) - -### Root Files - -| File | Purpose | -|------|---------| -| `sureforms.php` | Main plugin file, defines constants, loads `plugin-loader.php` | -| `plugin-loader.php` | Bootstraps plugin, registers hooks, loads dependencies | -| `composer.json` | PHP dependencies (nps-survey, bsf-analytics, astra-notices) | -| `package.json` | JS dependencies, build scripts | -| `Gruntfile.js` | Grunt tasks (minify, compress, release) | - -### `admin/` - WordPress Admin UI - -| File | Purpose | -|------|---------| -| `admin.php` | Admin page registration, AJAX handlers for admin actions | -| `analytics.php` | Usage analytics, event tracking | -| `notice-manager.php` | Admin notices, dismissible alerts | - -### `inc/` - Core PHP Logic - -#### `inc/blocks/` - Gutenberg Block Definitions (PHP) - -Each block has a `block.php` with: -- Block registration (`register_block_type`) -- Server-side rendering callback -- Attribute schema -- Enqueue scripts/styles - -**Blocks (16):** -- `address/`, `checkbox/`, `dropdown/`, `email/`, `gdpr/`, `inlinebutton/` -- `input/`, `multichoice/`, `number/`, `payment/`, `phone/`, `sform/`, `textarea/`, `url/` -- `base.php` - Base block class - -#### `inc/fields/` - Field Markup Generation - -Mirrors `blocks/` - generates HTML markup for each field type. - -**Pattern:** -```php -*-markup.php files: -- render_field() method -- Outputs escaped HTML -- Handles field attributes, validation states -``` - -#### `inc/payments/` - Payment Processing - -**Core Files:** -| File | Purpose | -|------|---------| -| `front-end.php` | Public AJAX handlers (`srfm_create_payment_intent`, `srfm_create_subscription_intent`) | -| `payment-helper.php` | Utility functions, amount validation, currency formatting | - -**`payments/stripe/`:** -| File | Purpose | -|------|---------| -| `stripe-webhook.php` | Webhook handler (charge, refund, subscription events) | -| `stripe-helper.php` | Stripe API wrapper, PaymentIntent creation | -| `admin-stripe-handler.php` | Admin AJAX for subscription management | -| `payments-settings.php` | Stripe connection settings, API keys | - -**`payments/admin/`:** -| File | Purpose | -|------|---------| -| `admin-handler.php` | Admin AJAX for refunds, payment management | - -#### `inc/ai-form-builder/` - AI Form Generation - -| File | Purpose | -|------|---------| -| `ai-form-builder.php` | Main AI form builder class, API integration | -| `ai-auth.php` | Middleware authentication | -| `ai-helper.php` | Helper functions, prompt processing | -| `field-mapping.php` | Maps AI response to Gutenberg blocks | - -**API:** `SRFM_AI_MIDDLEWARE` - `https://credits.startertemplates.com/sureforms/` - -#### `inc/database/` - Custom Database Tables - -| File | Purpose | -|------|---------| -| `base.php` | Abstract base class for all table classes (CRUD operations) | -| `register.php` | Table registration, schema creation | -| `tables/entries.php` | `wp_sureforms_entries` table (1,300+ lines) | -| `tables/payments.php` | `wp_sureforms_payments` table (1,200+ lines) | - -**Key Methods:** -- `get_all()`, `get_by_id()`, `insert()`, `update()`, `delete()` -- `get_all_main_payments()` - Payment queries with filtering - -#### Other `inc/` Files - -| File | Purpose | Lines | -|------|---------|-------| -| `form-submit.php` | Form submission handler (AJAX + REST) | 1,280 | -| `helper.php` | Utility functions (sanitization, escaping, validation) | 1,500+ | -| `field-validation.php` | Server-side field validation | 800+ | -| `rest-api.php` | REST API registration (`/sureforms/v1/*`) | 800 | -| `entries.php` | Entry management (list, view, export) | 1,000+ | -| `email/email-template.php` | Email notification templates | 400 | -| `frontend-assets.php` | Enqueue public scripts/styles | 300 | - -### `src/` - React Admin UI - -**Structure:** -``` -src/ -├── admin/ # Main admin app -│ ├── components/ # Reusable React components -│ ├── pages/ # Page-level components -│ └── index.js # Entry point -├── blocks/ # Block editor components -│ ├── address/ # Block-specific React -│ ├── email/ -│ └── ... -└── common/ # Shared utilities - ├── api.js # API client (wp.apiFetch) - └── utils.js # Helper functions -``` - -**Build:** webpack via `@wordpress/scripts` - ---- - -## SureForms Pro (`sureforms-pro/`) - -### Root Files - -| File | Purpose | -|------|---------| -| `sureforms-pro.php` | Main plugin file, requires Free plugin | -| `plugin-loader.php` | Bootstraps Pro features | - -### `inc/business/` - Pro Business Features - -#### `inc/business/payments/pay-pal/` - PayPal Integration - -| File | Purpose | Lines | -|------|---------|-------| -| `webhook-listener.php` | PayPal webhook handler (orders, refunds, subscriptions) | 1,173 | -| `frontend.php` | Public AJAX for PayPal orders, subscriptions | 1,100 | -| `api-payments.php` | PayPal API wrapper (create order, capture, refund) | 178 | -| `helper.php` | PayPal utilities, amount formatting | 520 | -| `settings.php` | PayPal connection settings | AJAX | - -#### `inc/business/user-registration/` - Login & Registration - -| File | Purpose | Lines | -|------|---------|-------| -| `init.php` | REST API registration, login/password endpoints | 600+ | -| `processor.php` | Registration processing, `wp_insert_user()` | 1,000+ | -| `login/block.php` | Login block definition | | -| `register/block.php` | Registration block | | -| `lost-password/block.php` | Lost password block | | -| `reset-password/block.php` | Reset password block | | - -**⚠️ Security Note:** Registration processor creates WordPress users from form submissions. - -#### `inc/business/pdf/` - PDF Generation - -| File | Purpose | -|------|---------| -| `pdf.php` | PDF generation API, AJAX handlers | -| `document.php` | PDF document builder (uses TCPDF or similar) | -| `utils.php` | PDF utilities | - -#### `inc/business/custom-app/` - Custom Application Builder - -| File | Purpose | -|------|---------| -| `load.php` | Custom post type forms, standalone applications | -| `utils.php` | App utilities | - -#### `inc/business/custom-post-type/` - Custom Post Type Integration - -Allows forms to create/update custom post types. - -### `inc/pro/native-integrations/` - 24+ Service Integrations - -**Core:** -| File | Purpose | Lines | -|------|---------|-------| -| `native-integrations.php` | Main integration manager | | -| `encryption.php` | AES-256-CTR encryption for OAuth tokens | 200 | -| `oauth-handler.php` | OAuth authorization flow | 400+ | -| `generic-provider.php` | Base provider class | | -| `integration-provider.php` | Provider interface | | - -**`inc/pro/native-integrations/integrations/`:** - -Each integration has its own folder: -``` -mailchimp/ -├── config.json # API endpoints, auth method -└── actions/ - ├── add-contact.php # Action: Add subscriber - └── remove-contact.php - -hubspot/ -├── config.json -└── actions/ - └── create-contact.php - -...24+ integrations -``` - -**Supported:** -- Email: Mailchimp, Brevo, Constant Contact -- CRM: HubSpot, Salesforce, Zoho -- Messaging: Telegram, Slack, Discord -- WP: FluentCRM, MailerPress, MailPoet -- Booking: LatePoint -- Automation: Zapier, OttoKit - -**Encryption:** OAuth tokens encrypted via `encryption.php` before storage in `wp_sureforms_integrations`. - -### `inc/pro/database/tables/` - Pro Database Tables - -| File | Purpose | -|------|---------| -| `integrations.php` | `wp_sureforms_integrations` table | -| `save-resume.php` | `wp_sureforms_save_resume` table (draft forms) | - -### `inc/extensions/` - Pro Extensions - -| File | Purpose | -|------|---------| -| `conditional-logic.php` | Show/hide fields based on conditions | -| `conditional-emails.php` | Conditional email notifications | -| `conditional-confirmations.php` | Conditional redirects | -| `field-validation.php` | Extended validation (file uploads, MIME types) | -| `entries-management.php` | Enhanced entry features (edit, delete files) | -| `page-break.php` | Multi-step form logic | - -### `inc/blocks/` - Pro Blocks (8) - -- `date-picker/`, `time-picker/`, `rating/`, `slider/` -- `upload/`, `hidden/`, `html/`, `page-break/` - ---- - -## Key Patterns & Conventions - -### Naming Conventions - -**Functions:** -- Public: `srfm_function_name()` -- Internal: `_srfm_internal_function()` - -**Classes:** -- Namespaced: `SRFM\Inc\ClassName` -- Pro: `SRFM_PRO\Inc\ClassName` - -**Hooks:** -- Actions: `sureforms_action_name` -- Filters: `sureforms_filter_name` - -**Database:** -- Tables: `wp_sureforms_table_name` -- Options: `sureforms_option_name` - -### File Organization - -``` -feature/ -├── block.php # Block registration -├── feature-markup.php # HTML generation -├── api-handler.php # AJAX/REST handlers -└── helper.php # Utilities -``` - -### REST API Convention - -**Free:** `/sureforms/v1/endpoint` -**Pro:** `/sureforms-pro/v1/endpoint` - ---- - -## Where to Find Things - -**Need to:** -- **Add a new field type?** → `inc/blocks/` + `inc/fields/` + `src/blocks/` -- **Modify form submission?** → `inc/form-submit.php` -- **Change email template?** → `inc/email/email-template.php` -- **Add payment gateway?** → `inc/payments/` (copy Stripe/PayPal pattern) -- **Add integration?** → `inc/pro/native-integrations/integrations/new-service/` -- **Debug entries?** → `inc/database/tables/entries.php` -- **Fix validation?** → `inc/field-validation.php` -- **Customize admin UI?** → `src/admin/` - ---- - -**Next:** [APIs](apis.md) - REST endpoints, AJAX handlers, hooks diff --git a/internal-docs/coding-standards.md b/internal-docs/coding-standards.md deleted file mode 100644 index 0b746674c..000000000 --- a/internal-docs/coding-standards.md +++ /dev/null @@ -1,312 +0,0 @@ -# Coding Standards - -**Version:** 2.5.0 - ---- - -## PHP Standards - -**WordPress Coding Standards:** https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/ - -### Code Style - -- **PSR-12 inspired** but follows WordPress conventions -- **Tabs for indentation** (not spaces) -- **Yoda conditions:** `if ( 'value' === $variable )` -- **Braces:** Required for all control structures - -```php -// ✅ Good -if ( $condition ) { - do_something(); -} - -// ❌ Bad -if ($condition) do_something(); -``` - -### Naming Conventions - -**Functions:** -```php -srfm_public_function() // Public functions -_srfm_private_function() // Internal (leading underscore) -``` - -**Classes:** -```php -namespace SRFM\Inc; -class Form_Submit { } // Underscores in class names - -namespace SRFM_PRO\Inc\Business; -class PayPal_Helper { } -``` - -**Variables:** -```php -$snake_case_variable = 'value'; // Lowercase with underscores -``` - -**Constants:** -```php -define( 'SRFM_CONSTANT', 'value' ); -``` - -### Security - -**Always:** -- Sanitize input: `sanitize_text_field()`, `absint()`, `sanitize_email()` -- Validate: Check types, ranges, allowed values -- Escape output: `esc_html()`, `esc_attr()`, `esc_url()` -- Use nonces: `wp_verify_nonce()` for AJAX/forms -- Use `$wpdb->prepare()` for SQL queries -- Check capabilities: `current_user_can()` - -**Example:** -```php -// Input -$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); - -// Database -$results = $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$wpdb->prefix}sureforms_entries WHERE form_id = %d", - $form_id - ) -); - -// Output -echo '
    ' . esc_html( $user_name ) . '
    '; -``` - -### Documentation - -**PHPDoc blocks:** -```php -/** - * Short description. - * - * Longer description if needed. - * - * @since 2.5.0 - * - * @param int $form_id Form ID. - * @param array $data Entry data. - * @return int|false Entry ID on success, false on failure. - */ -function srfm_create_entry( $form_id, $data ) { - // Implementation -} -``` - ---- - -## JavaScript Standards - -**WordPress JavaScript Coding Standards:** https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/ - -### Code Style (ESLint) - -**Config:** `@wordpress/eslint-plugin` - -```javascript -// ✅ Good - ES6+, const/let, arrow functions -const handleSubmit = (event) => { - event.preventDefault(); - const formData = new FormData(event.target); - // ... -}; - -// ❌ Bad - var, function expressions -var handleSubmit = function(event) { - event.preventDefault(); -}; -``` - -### React Conventions - -**Functional components with hooks:** -```jsx -import { useState, useEffect } from '@wordpress/element'; - -function FormBuilder({ formId }) { - const [fields, setFields] = useState([]); - - useEffect(() => { - fetchFields(formId).then(setFields); - }, [formId]); - - return ( -
    - {fields.map(field => )} -
    - ); -} -``` - -**Naming:** -- Components: `PascalCase` -- Hooks: `useCamelCase` -- Functions: `camelCase` -- Constants: `UPPER_SNAKE_CASE` - ---- - -## CSS/SCSS Standards - -**BEM-inspired naming:** -```scss -.srfm-form { - &__field { - // Field styles - } - - &__field--error { - // Error state - } -} -``` - -**Prefix:** All classes start with `srfm-` to avoid conflicts. - ---- - -## Database Standards - -### Table Names - -```php -$wpdb->prefix . 'sureforms_entries' -$wpdb->prefix . 'sureforms_payments' -``` - -**Always use `$wpdb->prefix`**, never hardcode `wp_`. - -### Queries - -**Always use prepared statements:** -```php -// ✅ Good -$wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$wpdb->prefix}sureforms_entries WHERE id = %d", - $entry_id - ) -); - -// ❌ Bad -$wpdb->query( "DELETE FROM wp_sureforms_entries WHERE id = $entry_id" ); -``` - ---- - -## Git Commit Messages - -**Format:** -``` -type(scope): brief description - -Longer explanation if needed. - -Co-Authored-By: Name -``` - -**Types:** -- `feat`: New feature -- `fix`: Bug fix -- `docs`: Documentation -- `style`: Formatting, no code change -- `refactor`: Code restructuring -- `test`: Adding tests -- `chore`: Build/tooling - -**Example:** -``` -feat(payments): add PayPal subscription support - -- Implement PayPal billing agreement API -- Add webhook handler for subscription events -- Update admin UI for PayPal subscriptions - -Co-Authored-By: Claude Sonnet 4.5 -``` - ---- - -## File Organization - -**One class per file:** -``` -inc/payments/stripe/stripe-helper.php → class Stripe_Helper -``` - -**Group related functionality:** -``` -inc/payments/ -├── payment-helper.php # Shared utilities -├── front-end.php # Public AJAX handlers -├── stripe/ -│ ├── stripe-helper.php -│ ├── stripe-webhook.php -│ └── admin-stripe-handler.php -└── admin/ - └── admin-handler.php -``` - ---- - -## Testing Standards - -**PHPUnit:** -```php -class Test_Form_Submit extends WP_UnitTestCase { - public function test_form_submission_saves_entry() { - $entry_id = srfm_submit_form( $data ); - $this->assertIsInt( $entry_id ); - } -} -``` - -**Playwright:** -```javascript -test('form submission works', async ({ page }) => { - await page.goto('/test-form/'); - await page.fill('[name="email"]', 'test@example.com'); - await page.click('button[type="submit"]'); - await expect(page.locator('.success-message')).toBeVisible(); -}); -``` - ---- - -## Code Review Checklist - -Before submitting PR: - -- [ ] Code follows WordPress/project standards -- [ ] All inputs sanitized -- [ ] All outputs escaped -- [ ] SQL queries use `prepare()` -- [ ] Nonces verified for AJAX/forms -- [ ] Capabilities checked for admin actions -- [ ] PHPDoc blocks added -- [ ] No PHP/JS errors in console -- [ ] Tested in latest WordPress version -- [ ] Tests pass: `composer test && npm run test:unit` -- [ ] Linting passes: `composer lint && npm run lint-js` - ---- - -**Automated Checks:** - -```bash -# PHP linting -composer lint # PHP_CodeSniffer - -# JS linting -npm run lint-js # ESLint - -# Auto-fix -composer format # phpcbf -npm run lint-js:fix # ESLint --fix -``` diff --git a/internal-docs/faq.md b/internal-docs/faq.md deleted file mode 100644 index 8e174c45f..000000000 --- a/internal-docs/faq.md +++ /dev/null @@ -1,1003 +0,0 @@ -# Frequently Asked Questions - -**Version:** 2.5.0 - ---- - -## General Questions - -### What is SureForms? - -SureForms is an AI-powered WordPress form builder that uses Gutenberg blocks. - -**Key features:** -- AI form generation (describe in plain language, get complete form) -- Built-in payments (Stripe in Free, PayPal in Pro) -- Mobile-first design -- Native WordPress (no proprietary builder) -- Custom database tables (not post meta) - -**Target users:** Website owners, designers, developers who need forms without complexity. - ---- - -### What's the difference between Free and Pro? - -**Free (sureforms):** -- Unlimited forms & submissions -- All 15+ field types -- Stripe payments (one-time & subscriptions) -- Email notifications -- Spam protection (reCAPTCHA, Honeypot) -- Form analytics -- CSV export - -**Pro (sureforms-pro):** -- **All Free features, plus:** -- PayPal payments -- 24+ native integrations (Mailchimp, HubSpot, Salesforce, etc.) -- Conditional logic -- Multi-step & conversational forms -- User registration & login -- PDF generation -- File upload fields -- Priority support (< 24hr response) - -**Pricing:** -- Free: $0 (WordPress.org) -- Pro: $99-$299/year (3 sites to unlimited) - ---- - -### Can I use SureForms on multiple sites? - -**Free:** Yes, unlimited sites. - -**Pro:** Depends on license: -- Essential ($99/year): 3 sites -- Plus ($199/year): 20 sites -- Agency ($299/year): Unlimited sites - -**License activation:** -- Enter license key in SureForms → Settings → License -- Deactivate from one site to move to another -- All sites must be owned by you (not for client sites unless Agency plan) - ---- - -### Is SureForms GDPR compliant? - -**Yes.** SureForms provides tools for GDPR compliance: - -**Features:** -- GDPR checkbox field (explicit consent) -- Data export (users can request their data) -- Data deletion (users can request deletion) -- No third-party data sharing (unless you enable integrations) -- Privacy policy link support - -**What you must do:** -1. Add GDPR checkbox to forms collecting personal data -2. Link to your privacy policy -3. Honor data export/deletion requests -4. Configure data retention policies - -**Data storage:** -- Submissions stored in your WordPress database -- No data sent to SureForms servers (AI calls go through middleware but don't store data) -- Payment data stored encrypted (PCI-compliant) - ---- - -### Does SureForms work with my theme? - -**Yes.** SureForms is designed to work with any properly-coded WordPress theme. - -**Tested with:** -- Astra -- GeneratePress -- Kadence -- Neve -- Blocksy -- Twenty Twenty-Three/Four -- Page builder themes (Elementor, Divi, Beaver Builder) - -**If you experience styling conflicts:** -1. Check [troubleshooting guide](troubleshooting.md#form-breaks-after-theme-update) -2. Use SureForms → Settings → Custom CSS to override -3. Contact support with theme name - ---- - -### Can I use SureForms with page builders? - -**Yes.** SureForms works with: -- **Gutenberg** (native, best experience) -- **Elementor** (via shortcode or WordPress widget) -- **Divi** (via shortcode or code module) -- **Beaver Builder** (via WordPress widget) -- **Bricks** (via shortcode or WordPress element) - -**How to add:** - -**Elementor:** -1. Add "Shortcode" widget -2. Paste: `[sureforms id="123"]` -3. Replace 123 with your form ID - -**Divi:** -1. Add "Code" module -2. Paste shortcode -3. Save - -**Beaver Builder:** -1. Add "WordPress Widget" module -2. Select "SureForms" widget -3. Choose form - ---- - -## Technical Questions - -### What are the system requirements? - -**Minimum:** -- WordPress 6.4+ -- PHP 7.4+ -- MySQL 5.6+ or MariaDB 10.1+ -- HTTPS (for payments) - -**Recommended:** -- WordPress 6.6+ -- PHP 8.1+ -- MySQL 8.0+ or MariaDB 10.6+ -- 256MB PHP memory limit -- Object caching (Redis or Memcached) - -**Browser support:** -- Chrome 90+ -- Firefox 88+ -- Safari 14+ -- Edge 90+ - ---- - -### Does SureForms slow down my site? - -**No.** SureForms is optimized for performance: - -**Impact on page load:** -- Additional CSS: ~15KB (minified) -- Additional JS: ~25KB (minified, deferred) -- Total: ~40KB additional assets - -**Database queries:** -- Form render: 2-3 queries (cached) -- Form submission: 4-5 queries (optimized with indexes) -- No N+1 query issues - -**Performance features:** -- Lazy loading for non-critical assets -- Custom database tables (not post meta) -- Query optimization (indexed columns) -- Minified & concatenated assets -- CDN-friendly (versioned static files) - -**Benchmarks:** -- Lighthouse score: 95+ (with proper hosting) -- Time to Interactive: < 2 seconds on 3G -- Form submission latency: < 500ms - ---- - -### Can I export my data? - -**Yes.** Multiple export options: - -**Entries export:** -1. Go to SureForms → Entries -2. Select entries (or "Select All") -3. Click "Export to CSV" -4. Download file - -**Format:** CSV with all fields as columns - -**Form export:** -- Export form as JSON (Settings → Export) -- Import on another site (Settings → Import) - -**Database export:** -```bash -# Export all SureForms data -wp db export sureforms-backup.sql --tables=wp_sureforms_entries,wp_sureforms_payments,wp_sureforms_integrations -``` - ---- - -### How do I migrate forms to another site? - -**Method 1: Export/Import (Recommended)** - -**On old site:** -1. Go to SureForms → Forms -2. Hover over form → "Export" -3. Download JSON file - -**On new site:** -1. Install SureForms (same version) -2. Go to SureForms → Import -3. Upload JSON file -4. Map any dependencies (integrations, etc.) - -**Method 2: Database migration** - -```bash -# On old site -wp db export sureforms-data.sql --tables=wp_sureforms_entries,wp_sureforms_payments - -# Transfer file to new site - -# On new site -wp db import sureforms-data.sql - -# Update URLs in entries (if domain changed) -wp search-replace 'https://oldsite.com' 'https://newsite.com' wp_sureforms_entries -``` - ---- - -### Can I use SureForms offline / localhost? - -**Yes, with limitations:** - -**Works offline:** -- Form creation -- Form editing -- Local submissions (saved to database) - -**Requires internet:** -- AI form generation (external API) -- Payment processing (Stripe/PayPal API) -- Native integrations (Mailchimp, HubSpot, etc.) -- Google Fonts (if used) -- reCAPTCHA (if enabled) - -**Development setup:** -```bash -# Use Local by Flywheel or similar -# Or manual setup: -cd ~/Sites/my-wordpress-site -wp core download -wp core config --dbname=sureforms_dev --dbuser=root -wp core install --url=http://localhost:8080 --title="Dev Site" -wp plugin install sureforms --activate -``` - ---- - -## Payment Questions - -### Which payment gateways are supported? - -**Free plugin:** -- Stripe (one-time payments & subscriptions) - -**Pro plugin:** -- Stripe (one-time payments & subscriptions) -- PayPal (one-time payments & subscriptions) - -**Coming soon:** -- Razorpay (India) -- Mollie (Europe) - -**Not planned:** -- WooCommerce (use WooCommerce for full e-commerce) -- Square (low demand) -- Bitcoin/crypto (regulatory complexity) - ---- - -### How do I test payments before going live? - -**Stripe test mode:** - -1. Get test API keys: - - Go to https://dashboard.stripe.com/test/apikeys - - Copy "Publishable key" (pk_test_...) - - Copy "Secret key" (sk_test_...) - -2. In SureForms: - - Go to Settings → Payments → Stripe - - Enable "Test Mode" - - Paste test keys - - Save - -3. Test with card: - - Card number: `4242 4242 4242 4242` - - Expiry: Any future date (e.g., 12/28) - - CVC: Any 3 digits (e.g., 123) - -4. Verify in Stripe dashboard: - - Go to Payments → Test Mode - - See your test payment - -**PayPal sandbox (Pro):** - -1. Create sandbox account: - - Go to https://developer.paypal.com/ - - Sign in with PayPal account - - Create sandbox business & buyer accounts - -2. Get sandbox credentials: - - Apps & Credentials → Sandbox - - Copy Client ID & Secret - -3. In SureForms: - - Settings → Payments → PayPal - - Enable "Sandbox Mode" - - Paste credentials - - Save - -4. Test with sandbox buyer account - ---- - -### How are payment fees handled? - -**SureForms fees:** $0 (we charge nothing) - -**Gateway fees:** - -**Stripe:** -- Standard: 2.9% + $0.30 per transaction -- International cards: +1.5% -- Currency conversion: +1% -- (Varies by country, see Stripe pricing) - -**PayPal:** -- Standard: 2.9% + $0.30 per transaction -- International: +1.5% -- (Varies by country, see PayPal pricing) - -**Who pays fees:** -- Fees deducted from amount received -- Example: $100 charge → You receive ~$97 - -**Passing fees to customer:** -- Not built-in (would require payment gateway approval) -- Workaround: Add fee as separate amount field - ---- - -### Can I accept subscriptions? - -**Yes** (both Stripe and PayPal). - -**Setup:** - -1. Create form with Payment block -2. Payment block settings: - - Payment Type: ● Subscription - - Interval: Monthly/Yearly/Weekly - - Amount: [price] - -3. Customer pays once, charged automatically recurring - -**Management:** - -**Stripe:** -- View subscriptions: Stripe Dashboard → Subscriptions -- Cancel: Click subscription → "Cancel subscription" -- Refund: Not automatic, manual refund in Stripe - -**PayPal (Pro):** -- View subscriptions: PayPal Dashboard → Subscriptions -- Cancel: Click subscription → "Cancel" - -**Customer cancellation:** -- Customers can cancel in Stripe/PayPal customer portal -- You can provide link in confirmation email - ---- - -## AI & Form Generation Questions - -### How does AI form generation work? - -**Technical flow:** - -``` -User enters prompt ("job application form") - ↓ -SureForms sends to AI middleware - ↓ -Middleware calls GPT-4 API - ↓ -GPT-4 returns structured JSON (field types, labels, validations) - ↓ -Middleware sends back to SureForms - ↓ -SureForms creates Gutenberg blocks from JSON - ↓ -Form appears in editor, ready to customize -``` - -**What data is sent:** -- Your prompt ("create a contact form") -- Form context (if modifying existing form) -- No user data, no submissions, no PII - -**What data is stored:** -- None. AI responses are not logged. -- Form configuration stored in WordPress database only - -**Privacy:** -- AI calls routed through SureForms middleware (not direct to OpenAI) -- No data retention -- No training on your data - ---- - -### Can I use AI without internet? - -**No.** AI requires external API call to GPT-4. - -**Offline alternatives:** -- Create forms manually (no AI) -- Use templates (no AI needed) -- Import pre-made forms - ---- - -### What languages does AI support? - -**Prompts:** Any language GPT-4 supports (100+ languages) - -**Field labels:** Generated in same language as prompt - -**Example:** -- Prompt (Spanish): "formulario de contacto" -- Result: Fields with Spanish labels ("Nombre", "Correo electrónico", etc.) - -**Limitations:** -- Some languages better than others (English, Spanish, French, German excellent) -- Less common languages may have lower quality - ---- - -## Integration Questions (Pro) - -### What integrations are available? - -**24+ native integrations:** - -**Email Marketing:** -- Mailchimp -- Brevo (Sendinblue) -- ActiveCampaign -- ConvertKit -- MailerLite - -**CRM:** -- HubSpot -- Salesforce -- Zoho CRM -- Pipedrive - -**Communication:** -- Slack -- Telegram -- Discord (coming soon) - -**Productivity:** -- Google Sheets -- Airtable (coming soon) -- Notion (coming soon) - -**WordPress Plugins:** -- FluentCRM (native) -- OttoKit (SureTriggers) - -**Webhooks:** -- Custom webhooks (POST to any URL) - -**Not included:** -- Zapier (use webhooks instead) -- Make/Integromat (use webhooks) - ---- - -### How do I connect Mailchimp? - -**Setup (OAuth):** - -1. Go to SureForms → Settings → Integrations -2. Click "Mailchimp" → "Connect" -3. Redirected to Mailchimp → "Allow access" -4. Redirected back, connection confirmed - -**Use in form:** - -1. Edit form -2. Settings → Actions → "Add Action" -3. Select "Mailchimp" -4. Choose audience (list) -5. Map fields: - - Email field → Mailchimp EMAIL - - Name field → Mailchimp FNAME -6. Save - -**On form submission:** -- Contact added to Mailchimp list automatically -- Tags applied (if configured) -- Double opt-in email sent (if enabled in Mailchimp) - ---- - -### Can I send form data to custom webhook? - -**Yes** (Pro feature). - -**Setup:** - -1. Form settings → Actions → "Add Action" -2. Select "Webhook" -3. Configure: - - URL: `https://yourapi.com/endpoint` - - Method: POST (or GET, PUT) - - Headers: (optional, e.g., `Authorization: Bearer token`) -4. Save - -**Payload format:** - -```json -{ - "form_id": 123, - "entry_id": 456, - "fields": { - "email": "user@example.com", - "name": "John Doe", - "message": "Hello!" - }, - "meta": { - "submitted_at": "2026-02-12 10:30:00", - "user_ip": "192.168.1.1", - "user_agent": "Mozilla/5.0..." - } -} -``` - -**Retry logic:** -- Failed webhooks retried 3 times -- Exponential backoff (1s, 5s, 25s) -- After 3 failures, logged as error - ---- - -## Security & Privacy Questions - -### Is SureForms secure? - -**Yes.** Security is a core priority. - -**Security measures:** - -**Input sanitization:** -- All user input sanitized before storage -- WordPress functions: `sanitize_text_field()`, `sanitize_email()`, etc. -- Custom sanitization for complex fields - -**Output escaping:** -- All output escaped before rendering -- `esc_html()`, `esc_attr()`, `esc_url()` used throughout -- Prevents XSS attacks - -**SQL injection prevention:** -- All database queries use `$wpdb->prepare()` -- No direct SQL concatenation -- Custom database class with built-in protection - -**CSRF protection:** -- Nonce verification on all AJAX/REST endpoints -- `wp_verify_nonce()` checked before state-changing operations - -**Authorization:** -- Capability checks: `current_user_can()` -- Admin actions require `manage_options` capability -- User-specific data access controlled - -**Payment security:** -- PCI DSS compliant (no card storage) -- Stripe/PayPal handle sensitive data -- API credentials encrypted at rest (AES-256) - -**File uploads (Pro):** -- MIME type validation (server-side) -- File size limits enforced -- Path traversal prevention -- Disallowed file types: `.php`, `.js`, `.exe`, etc. - ---- - -### Where is my data stored? - -**Form submissions:** -- Your WordPress database -- Table: `wp_sureforms_entries` -- No data sent to SureForms servers - -**Payment data:** -- Stripe: Stored in Stripe (not in WordPress) -- PayPal: Stored in PayPal (not in WordPress) -- WordPress stores: Transaction ID, amount, status only - -**Integration credentials (Pro):** -- WordPress database -- Table: `wp_sureforms_integrations` -- Encrypted with AES-256 -- Encryption key in `wp-config.php` (recommended) - -**AI prompts:** -- Sent to AI middleware → OpenAI GPT-4 -- Not stored or logged -- Not used for training - ---- - -### Can users delete their data? - -**Yes** (GDPR requirement). - -**Manual deletion:** - -1. User requests deletion via email -2. Admin finds submissions by email -3. Delete from SureForms → Entries - -**Automatic (Pro):** -- Data retention policy: Auto-delete entries after X days -- Settings → Privacy → Data Retention -- Configure per form or globally - -**What gets deleted:** -- Entry data (all fields) -- Entry metadata (IP, user agent) -- File uploads (if any) - -**What's preserved:** -- Form configuration (for re-use) -- Analytics (anonymized counts only) - ---- - -## Troubleshooting Questions - -### My forms aren't showing on the frontend. Why? - -**Common causes:** - -**1. Form not published:** -- Check form status in SureForms → Forms -- Must be "Published" not "Draft" - -**2. Form not embedded:** -- Add SureForms block to page -- Select your form from dropdown -- Or use shortcode: `[sureforms id="123"]` - -**3. Theme conflict:** -- Try default theme: `wp theme activate twentytwentythree` -- If works → Theme issue, contact theme developer - -**4. JavaScript disabled:** -- Check browser console for errors -- Disable other plugins to find conflict - -**Debug:** -```bash -# Enable debugging -wp config set WP_DEBUG true --raw -wp config set WP_DEBUG_LOG true --raw - -# Check debug log -tail -f wp-content/debug.log -``` - -See [troubleshooting guide](troubleshooting.md) for more. - ---- - -### Form submissions aren't saving. What's wrong? - -**Check:** - -**1. Database tables exist:** -```bash -wp db query "SHOW TABLES LIKE '%sureforms%';" -``` - -Should show `wp_sureforms_entries` table. - -**2. Database permissions:** -```bash -wp db query "SHOW GRANTS;" -``` - -User must have INSERT privilege. - -**3. Nonce verification:** -- If using caching, exclude AJAX endpoints -- WP Super Cache: Exclude `/wp-admin/admin-ajax.php` - -**4. Debug log:** -```bash -tail -f wp-content/debug.log -# Submit form, watch for errors -``` - -See [troubleshooting: submissions not saving](troubleshooting.md#form-submissions-not-saving) - ---- - -### Email notifications aren't sending. Help! - -**Diagnose:** - -```bash -# Test WordPress mail function -wp eval "wp_mail('your@email.com', 'Test', 'Testing');" -``` - -If no email received → Server mail() disabled. - -**Fix:** - -**Install SMTP plugin:** -```bash -wp plugin install wp-mail-smtp --activate -``` - -**Configure with:** -- Gmail -- SendGrid -- Mailgun -- Amazon SES - -**Check spam folder:** -- Default WordPress sender: `wordpress@yourdomain.com` -- Often flagged as spam -- SMTP fixes this - -See [troubleshooting: emails not sending](troubleshooting.md#email-notifications-not-sending) - ---- - -## Customization Questions - -### Can I customize form styling? - -**Yes.** Multiple options: - -**Option 1: Settings panel (no code):** -1. Edit form -2. Settings sidebar → Design -3. Customize: - - Colors (background, text, buttons) - - Typography (fonts, sizes) - - Spacing (padding, margins) - - Border radius - -**Option 2: Custom CSS:** -1. SureForms → Settings → Custom CSS -2. Add your styles: -```css -.srfm-form { - background: #f9f9f9; - padding: 30px; - border-radius: 8px; -} - -.srfm-form input { - border: 2px solid #333; -} -``` - -**Option 3: Theme stylesheet:** - -Add to `style.css`: -```css -.srfm-form { /* Your styles */ } -``` - -**Option 4: Page builder:** - -Use page builder's CSS editor (if using Elementor/Divi). - ---- - -### Can I add custom fields? - -**Yes** (requires development). - -**Process:** - -1. Create block folder: `inc/blocks/my-field/` -2. Register block: `block.php` -3. Create React component: `src/blocks/my-field/edit.js` -4. Add sanitization: `inc/helper.php` -5. Add validation: `inc/field-validation.php` - -**Example:** See [onboarding guide](onboarding.md#step-2-create-block-structure) - -**Pre-built custom fields (Pro):** -- Star rating -- Signature -- File upload -- Date/time picker - ---- - -### Can I change the submit button text? - -**Yes.** - -**Per form:** -1. Edit form -2. Click submit button block -3. Settings sidebar → Button Text -4. Change text -5. Update form - -**Globally:** - -Add to `functions.php`: -```php -add_filter('sureforms_submit_button_text', function($text) { - return __('Send Message', 'sureforms'); -}); -``` - ---- - -## Business & Licensing Questions - -### Can I use SureForms on client sites? - -**Free:** Yes, unlimited sites. - -**Pro:** -- **Agency license required** ($299/year, unlimited sites) -- Essential ($99/year, 3 sites) - only for your own sites -- Plus ($199/year, 20 sites) - only for your own sites - -**License terms:** -- You must own the sites (not client-owned sites with Essential/Plus) -- Agency license allows client sites -- Each site needs separate activation - ---- - -### Can I white-label SureForms? - -**Not officially.** - -**What you can do:** -- Remove branding from admin (CSS) -- Custom email footer (replace "Powered by SureForms") -- Custom confirmation messages - -**What you can't do:** -- Remove copyright notices from code -- Rebrand and resell as your own product -- Remove attribution from free plugin - -**Enterprise white-label:** -- Contact sales for custom licensing -- Available for high-volume agencies - ---- - -### What's your refund policy? - -**14-day money-back guarantee** (Pro licenses) - -**Refund eligibility:** -- Purchased within last 14 days -- Tried to use but encountered issues -- Support unable to resolve - -**Not eligible:** -- After 14 days -- Changed mind (not technical issue) -- Purchased wrong license (can upgrade instead) - -**Process:** -1. Contact support: support@sureforms.com -2. Describe issue (we'll try to help first) -3. If unresolved, refund processed within 3-5 business days - ---- - -### How long do I get updates? - -**Free:** Lifetime updates. - -**Pro:** -- Updates: For duration of license (1 year) -- Renewals: Discounted renewal price after year 1 -- If license expires: Plugin continues working, no new updates - -**Version compatibility:** -- Major updates: Included -- Security updates: Prioritized -- Feature updates: Based on roadmap - ---- - -## Developer Questions - -### Is the code open source? - -**Free plugin:** Yes. -- License: GPL v2 or later -- GitHub: https://github.com/brainstormforce/sureforms - -**Pro plugin:** No. -- Proprietary license -- Source code visible but not redistributable - ---- - -### Can I contribute to SureForms? - -**Yes!** We welcome contributions. - -**How to contribute:** - -1. Fork repo: https://github.com/brainstormforce/sureforms -2. Create branch: `git checkout -b feat/my-feature` -3. Make changes -4. Run tests: `composer test && npm run test:unit` -5. Submit PR - -**Contribution guidelines:** -- Follow [coding standards](coding-standards.md) -- Add tests for new features -- Update docs if needed -- Sign off commits: `Co-Authored-By: Your Name ` - ---- - -### Where can I find the API documentation? - -**API docs:** [apis.md](apis.md) - -**Key resources:** -- REST endpoints: [apis.md#rest-api](apis.md#rest-api) -- Hooks & filters: [apis.md#hooks--filters](apis.md#hooks--filters) -- JavaScript APIs: [apis.md#javascript-apis](apis.md#javascript-apis) -- Database APIs: [apis.md#database-apis](apis.md#database-apis) - -**Code examples:** [ai-agent-guide.md](ai-agent-guide.md) - ---- - -## Need More Help? - -**Documentation:** -- [README.md](README.md) - Quick start -- [Troubleshooting](troubleshooting.md) - Common problems -- [Onboarding](onboarding.md) - Developer guide -- [Glossary](glossary.md) - Technical terms - -**Support:** -- **Free users:** WordPress.org forum, GitHub issues -- **Pro users:** https://support.brainstormforce.com/ (< 24hr response) - -**Community:** -- Facebook: https://www.facebook.com/groups/surecart -- GitHub Discussions: https://github.com/brainstormforce/sureforms/discussions - ---- - -**Next:** [Glossary](glossary.md) diff --git a/internal-docs/glossary.md b/internal-docs/glossary.md deleted file mode 100644 index 440e34b70..000000000 --- a/internal-docs/glossary.md +++ /dev/null @@ -1,723 +0,0 @@ -# Glossary - -**Version:** 2.5.0 - ---- - -## A - -### AJAX (Asynchronous JavaScript and XML) -Technique for updating parts of a page without full reload. Used in SureForms for form submissions, payment processing, and file uploads. - -**Example:** -```javascript -// AJAX request to submit form -wp.ajax.post('srfm_submit_form', { data: formData }); -``` - -### API (Application Programming Interface) -Set of functions and methods for interacting with software. SureForms provides REST API, database API, and JavaScript API. - -**Types in SureForms:** -- **REST API:** HTTP endpoints for external access -- **Database API:** Methods to query entries/payments -- **JavaScript API:** Frontend form manipulation - -### Attribute (Gutenberg) -Data stored with a block. In SureForms, attributes define field configuration (label, required, placeholder, etc.). - -**Example:** -```json -{ - "label": "Email", - "required": true, - "placeholder": "name@example.com" -} -``` - ---- - -## B - -### Block (Gutenberg) -Reusable component in WordPress editor. SureForms forms are composed of blocks (Email block, Textarea block, Submit button block, etc.). - -**Types:** -- **Static blocks:** Rendered client-side (React) -- **Dynamic blocks:** Rendered server-side (PHP) - -### Block Editor -WordPress's editing interface (formerly called "Gutenberg"). Native to WordPress 5.0+. - -**Why SureForms uses it:** -- Users already familiar -- No proprietary builder to learn -- Future-proof (WordPress core) - ---- - -## C - -### Capability -WordPress permission level. SureForms checks capabilities before allowing admin actions. - -**Common capabilities:** -- `manage_options` - Admin settings access -- `edit_posts` - Create forms (default) -- `read` - View forms (logged-in users) - -**Example:** -```php -if (current_user_can('manage_options')) { - // Allow admin action -} -``` - -### Conditional Logic (Pro) -Show or hide form fields based on user answers. Example: Show "Dietary restrictions" only if user selects "Yes" to "Will you attend?" - -**Types:** -- **Show when:** Display field if condition matches -- **Hide when:** Hide field if condition matches -- **AND/OR logic:** Multiple conditions combined - -### Conversational Form (Pro) -Form that displays one question at a time, like a chat conversation. Increases engagement and completion rates. - -**Benefits:** -- Less overwhelming than long forms -- Higher completion rate (3x vs single-page) -- Better mobile experience - -### CSRF (Cross-Site Request Forgery) -Attack where malicious site tricks user into submitting request to your site. SureForms prevents with nonce verification. - -**Example attack (prevented):** -```html - - - - - - -``` - -### CSV (Comma-Separated Values) -File format for spreadsheet data. SureForms can export form entries as CSV. - -**Example:** -``` -Name,Email,Message -John Doe,john@example.com,Hello! -Jane Smith,jane@example.com,Question about... -``` - ---- - -## D - -### Database Table -MySQL table storing structured data. SureForms uses 4 custom tables: -- `wp_sureforms_entries` - Form submissions -- `wp_sureforms_payments` - Payment records -- `wp_sureforms_integrations` - OAuth credentials -- `wp_sureforms_save_resume` - Draft submissions (Pro) - -**Why custom tables?** -- Faster queries (indexed columns) -- More efficient than post meta -- Easier to export - -### Dynamic Block -Gutenberg block rendered server-side (PHP). Most SureForms field blocks are dynamic for security (server-side validation). - -**Example:** -```php -register_block_type('sureforms/email', [ - 'render_callback' => 'render_email_field' -]); -``` - ---- - -## E - -### Encryption -Converting data to unreadable format. SureForms encrypts OAuth credentials and API keys. - -**Algorithm:** AES-256-CTR - -**Example:** -```php -$encrypted = Encryption::encrypt($api_key); -// Stored in database: "base64encodedIV.encryptedData" -``` - -### Entry -Single form submission. Stored in `wp_sureforms_entries` table. - -**Contains:** -- Form ID -- User ID (if logged in) -- Field values (JSON) -- Metadata (IP, user agent, timestamp) -- Status (published, spam, trash) - -### Escaping (Output Escaping) -Converting special characters to prevent XSS. SureForms escapes all user data before display. - -**Functions:** -- `esc_html()` - For HTML content -- `esc_attr()` - For HTML attributes -- `esc_url()` - For URLs -- `esc_js()` - For JavaScript strings - -**Example:** -```php -echo '
    ' . esc_html($user_input) . '
    '; -``` - ---- - -## F - -### Field Type -Type of input field (email, text, number, etc.). SureForms has 15+ field types. - -**Common types:** -- Text, Email, URL, Textarea -- Number, Phone -- Multiple Choice, Checkbox, Dropdown -- Address, Upload (Pro) - -### Filter (WordPress Hook) -Function that modifies data. SureForms provides 30+ filters for customization. - -**Example:** -```php -add_filter('sureforms_email_subject', function($subject, $form_id) { - return "[Form $form_id] $subject"; -}, 10, 2); -``` - ---- - -## G - -### GDPR (General Data Protection Regulation) -European privacy law. SureForms provides GDPR-compliance features (consent checkbox, data export/deletion). - -**Requirements:** -- Explicit consent for data collection -- Right to access data (export) -- Right to deletion -- Privacy policy link - -### Gutenberg -WordPress's block editor (officially called "Block Editor"). Named after Johannes Gutenberg, inventor of printing press. - -**Why "Gutenberg"?** -- Revolutionized content creation (like printing press) -- Blocks are reusable components - ---- - -## H - -### Honeypot -Anti-spam technique using hidden field. Bots fill all fields (including hidden), revealing themselves. - -**How it works:** -```html - - -``` - -**SureForms logic:** -```php -if (!empty($_POST['srfm-honeypot'])) { - // Spam detected -} -``` - -### Hook (WordPress) -Point where developers can inject custom code. Two types: actions and filters. - -**SureForms hooks:** -- 50+ action hooks (e.g., `sureforms_after_entry_save`) -- 30+ filter hooks (e.g., `sureforms_email_template`) - ---- - -## I - -### Integration (Native Integration) -Connection to third-party service. SureForms Pro has 24+ native integrations (Mailchimp, HubSpot, etc.). - -**Types:** -- **OAuth:** User authorizes connection (Mailchimp, Google) -- **API Key:** User provides key from service -- **Webhook:** SureForms sends POST request to URL - -### Instant Form -SureForms feature allowing forms to be published with unique URL (no embedding needed). - -**Example URL:** -``` -https://yoursite.com/?srfm_form=abc123 -``` - -**Use cases:** -- Email signatures -- Social media links -- QR codes - ---- - -## J - -### JSON (JavaScript Object Notation) -Data format for structured information. SureForms stores entry data and block attributes as JSON. - -**Example entry data:** -```json -{ - "email": "john@example.com", - "name": "John Doe", - "message": "Hello!" -} -``` - ---- - -## L - -### Localization (l10n) -Adapting software for specific language/region. SureForms supports translation. - -**Files:** -- `languages/sureforms-{locale}.po` - Translation strings -- `languages/sureforms-{locale}.mo` - Compiled translations - -**Functions:** -```php -__('Email', 'sureforms'); // Translate string -_e('Submit', 'sureforms'); // Translate and echo -``` - ---- - -## M - -### Middleware -Intermediate server between client and API. SureForms uses middleware for AI form generation (client → middleware → OpenAI). - -**Why middleware?** -- Hides API keys from client -- Rate limiting -- Request logging -- Caching - -### Multi-Step Form (Pro) -Form split into multiple pages/steps. Improves completion rate by reducing cognitive load. - -**Example:** -``` -Step 1: Personal Information (name, email) -Step 2: Address Details -Step 3: Payment Information -``` - ---- - -## N - -### Namespace -PHP feature to organize code and prevent name conflicts. SureForms uses namespaces. - -**Example:** -```php -namespace SRFM\Inc\Database\Tables; - -class Entries { - // ... -} -``` - -### Native Integration -Integration built into SureForms (not requiring Zapier or external connector). - -**Examples:** -- Mailchimp (OAuth) -- HubSpot (OAuth) -- Slack (Webhook URL) - -### Nonce (Number Used Once) -Security token to verify request came from your site. SureForms verifies nonces on all AJAX/REST requests. - -**Example:** -```php -// Generate nonce -$nonce = wp_create_nonce('srfm_submit_form'); - -// Verify nonce -if (!wp_verify_nonce($_POST['nonce'], 'srfm_submit_form')) { - die('Invalid nonce'); -} -``` - ---- - -## O - -### OAuth (Open Authorization) -Protocol for secure authorization. Used by SureForms integrations (Mailchimp, HubSpot, etc.). - -**Flow:** -1. User clicks "Connect to Mailchimp" -2. Redirected to Mailchimp login -3. User approves access -4. Mailchimp redirects back with token -5. SureForms stores encrypted token - ---- - -## P - -### Payment Gateway -Service that processes credit card payments. SureForms supports Stripe and PayPal. - -**Comparison:** -- **Stripe:** 2.9% + $0.30, better for US/Europe -- **PayPal:** 2.9% + $0.30, more global recognition - -### Payment Intent (Stripe) -Stripe object representing payment attempt. Created before customer enters card details. - -**States:** -- `requires_payment_method` - Awaiting card -- `requires_confirmation` - Card entered, needs confirm -- `succeeded` - Payment successful -- `canceled` - Payment canceled - -### PCI DSS (Payment Card Industry Data Security Standard) -Security standard for handling credit card data. SureForms is PCI-compliant (never stores card data). - -**Compliance:** -- Card data sent directly to Stripe/PayPal -- SureForms only stores transaction ID -- No PAN (Primary Account Number) stored - -### Placeholder -Example text shown in empty input field. Disappears when user types. - -**Example:** -```html - -``` - -**Accessibility note:** Never use placeholder as label (screen readers may miss it). - -### Post Meta -WordPress method for storing additional data with posts. SureForms **doesn't** use post meta (uses custom tables instead). - -**Why not post meta?** -- Slow queries (no indexes) -- Hard to search -- Not optimized for large datasets - -### Post Type (Custom Post Type) -WordPress content type. SureForms forms are stored as custom post type `sureforms_form`. - -**Advantages:** -- Uses WordPress admin UI -- Revision history -- Trash/restore functionality - ---- - -## R - -### reCAPTCHA -Google anti-spam service. SureForms supports reCAPTCHA v2 and v3. - -**Versions:** -- **v2:** "I'm not a robot" checkbox -- **v3:** Invisible, scores user behavior (0-1) - -**Setup:** -1. Get API keys from Google reCAPTCHA admin -2. Add to SureForms → Settings → Security -3. Enable on forms - -### REST API -HTTP API for accessing SureForms data. Used by frontend, mobile apps, integrations. - -**Base URL:** `/wp-json/sureforms/v1/` - -**Example endpoints:** -- `POST /submit-form` - Submit form -- `GET /forms/{id}` - Get form data -- `POST /generate-form` - AI form generation - -### RTL (Right-to-Left) -Text direction for languages like Arabic, Hebrew. SureForms supports RTL. - -**CSS:** -```css -.srfm-field { - margin-inline-start: 10px; /* Not margin-left */ -} -``` - ---- - -## S - -### Sanitization (Input Sanitization) -Cleaning user input to prevent attacks. SureForms sanitizes all input before storage. - -**Functions:** -- `sanitize_text_field()` - Remove HTML/PHP -- `sanitize_email()` - Validate email format -- `sanitize_url()` - Validate URL format -- `absint()` - Force integer - -**Example:** -```php -$email = sanitize_email($_POST['email']); -$id = absint($_POST['id']); -``` - -### Schema (Database Schema) -Structure of database table (columns, types, indexes). - -**Example (wp_sureforms_entries):** -```sql -CREATE TABLE wp_sureforms_entries ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - form_id BIGINT UNSIGNED NOT NULL, - user_id BIGINT UNSIGNED, - entry_data LONGTEXT NOT NULL, - status VARCHAR(20) DEFAULT 'published', - created_at DATETIME NOT NULL, - INDEX idx_form_id (form_id), - INDEX idx_created_at (created_at) -); -``` - -### Shortcode -WordPress feature for embedding content. SureForms forms can be embedded via shortcode. - -**Syntax:** -``` -[sureforms id="123"] -``` - -**Parameters:** -- `id` - Form ID (required) -- `title` - Show title (true/false) -- `description` - Show description (true/false) - -### SQL Injection -Attack injecting malicious SQL into queries. SureForms prevents with `$wpdb->prepare()`. - -**Vulnerable code (DON'T DO THIS):** -```php -$id = $_GET['id']; -$wpdb->query("DELETE FROM table WHERE id = $id"); -// Attack: ?id=1 OR 1=1 (deletes all records!) -``` - -**Safe code:** -```php -$id = absint($_GET['id']); -$wpdb->query($wpdb->prepare("DELETE FROM table WHERE id = %d", $id)); -``` - -### Subscription -Recurring payment. SureForms supports subscriptions via Stripe and PayPal. - -**Intervals:** -- Daily -- Weekly -- Monthly -- Yearly - ---- - -## T - -### Template (Form Template) -Pre-built form configuration. SureForms provides 100+ templates (contact, registration, survey, etc.). - -**Structure:** -```json -{ - "name": "Contact Form", - "fields": [ - { "type": "text", "label": "Name" }, - { "type": "email", "label": "Email" }, - { "type": "textarea", "label": "Message" } - ] -} -``` - -### Transient -WordPress temporary cache. SureForms uses transients for AI rate limiting and API responses. - -**Example:** -```php -// Store for 1 hour -set_transient('srfm_ai_rate_limit_' . $user_id, true, HOUR_IN_SECONDS); - -// Check -if (get_transient('srfm_ai_rate_limit_' . $user_id)) { - // Rate limited -} -``` - ---- - -## U - -### User Agent -String identifying browser/device. SureForms logs user agent for spam detection. - -**Example:** -``` -Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 -``` - ---- - -## V - -### Validation (Form Validation) -Checking if user input meets requirements. SureForms validates both client-side (JavaScript) and server-side (PHP). - -**Types:** -- **Required:** Field must not be empty -- **Format:** Email/URL must be valid -- **Range:** Number within min/max -- **Length:** String length constraints - -**Example:** -```php -if (empty($email) || !is_email($email)) { - $errors[] = 'Please enter a valid email'; -} -``` - ---- - -## W - -### Webhook -HTTP callback sent when event occurs. SureForms can send webhooks on form submission (Pro). - -**Flow:** -``` -User submits form - ↓ -SureForms processes submission - ↓ -Sends POST to webhook URL - ↓ -External service receives data -``` - -**Example payload:** -```json -{ - "event": "form_submit", - "form_id": 123, - "entry_id": 456, - "fields": { ... } -} -``` - -### WordPress Coding Standards (WPCS) -PHP coding style guide for WordPress. SureForms follows WPCS. - -**Key rules:** -- Tabs for indentation (not spaces) -- Yoda conditions: `if ( 'value' === $variable )` -- Braces required for all control structures -- Space after control keywords: `if (` not `if(` - -**Check compliance:** -```bash -composer lint -``` - ---- - -## X - -### XSS (Cross-Site Scripting) -Attack injecting malicious JavaScript into pages. SureForms prevents with output escaping. - -**Vulnerable code (DON'T DO THIS):** -```php -echo '
    ' . $_POST['name'] . '
    '; -// Attack: -``` - -**Safe code:** -```php -echo '
    ' . esc_html($_POST['name']) . '
    '; -// Output: <script>steal(document.cookie)</script> -``` - ---- - -## Z - -### Zapier -Third-party automation service. SureForms doesn't have native Zapier integration but supports webhooks (which Zapier can consume). - -**Alternative:** Use SureForms webhooks to trigger Zapier zaps. - ---- - -## Acronyms Quick Reference - -| Acronym | Full Term | Meaning | -|---------|-----------|---------| -| AJAX | Asynchronous JavaScript and XML | Update page without reload | -| API | Application Programming Interface | Methods to interact with software | -| CSRF | Cross-Site Request Forgery | Attack tricking users into unwanted actions | -| CSV | Comma-Separated Values | Spreadsheet file format | -| GDPR | General Data Protection Regulation | EU privacy law | -| JSON | JavaScript Object Notation | Data format | -| OAuth | Open Authorization | Secure authorization protocol | -| PCI DSS | Payment Card Industry Data Security Standard | Credit card security | -| REST | Representational State Transfer | Web API architecture | -| RTL | Right-to-Left | Text direction (Arabic, Hebrew) | -| SQL | Structured Query Language | Database query language | -| URL | Uniform Resource Locator | Web address | -| WCAG | Web Content Accessibility Guidelines | Accessibility standards | -| WPCS | WordPress Coding Standards | PHP coding style | -| XSS | Cross-Site Scripting | JavaScript injection attack | - ---- - -## Common Abbreviations - -| Abbr | Full Term | -|------|-----------| -| Pro | SureForms Pro (premium plugin) | -| CRM | Customer Relationship Management | -| UI | User Interface | -| UX | User Experience | -| CPT | Custom Post Type | -| DB | Database | -| WP | WordPress | -| PHP | PHP: Hypertext Preprocessor | -| JS | JavaScript | -| CSS | Cascading Style Sheets | - ---- - -**Next:** [Maintenance](maintenance.md) diff --git a/internal-docs/maintenance.md b/internal-docs/maintenance.md deleted file mode 100644 index 71c2f2604..000000000 --- a/internal-docs/maintenance.md +++ /dev/null @@ -1,842 +0,0 @@ -# Documentation Maintenance Guide - -**Version:** 2.5.0 - ---- - -## Purpose - -This guide explains how to keep SureForms internal documentation accurate and useful. - -**Goal:** Documentation should always reflect current reality. - -**Principle:** Update docs when code changes, not as afterthought. - ---- - -## When to Update Documentation - -### Always Update - -**1. New Feature Added** -- Update: `architecture.md`, `apis.md`, `product-vision.md` -- Add: Code examples to `ai-agent-guide.md` -- Update: Onboarding guide if workflow changes - -**2. API Changes** -- Update: `apis.md` (new endpoints, hooks, parameters) -- Update: Code examples in all docs referencing changed API -- Mark deprecated APIs in `apis.md` - -**3. Database Schema Changes** -- Update: `architecture.md` (database section) -- Update: `codebase-map.md` (new files) -- Update: Migration guide if schema change requires migration - -**4. Security Fix** -- Update: `ai-agent-guide.md` (add to common pitfalls) -- Update: `troubleshooting.md` if fix addresses known issue -- Add example of vulnerable pattern to avoid - -**5. Breaking Changes** -- Update: `README.md` (migration guide) -- Update: `onboarding.md` (new workflows) -- Update: `faq.md` (address upgrade questions) - ---- - -### Update if Needed - -**6. Bug Fixes** -- Update: `troubleshooting.md` if common issue -- Add: To FAQ if frequently asked - -**7. Performance Improvements** -- Update: `architecture.md` if architecture changed -- Update: Benchmarks in `README.md` - -**8. UI/UX Changes** -- Update: `ui-and-copy.md` (new patterns, copy) -- Update: `onboarding.md` (user journeys) - ---- - -### Don't Update - -**9. Code Refactoring (No Behavior Change)** -- No doc update unless internal architecture significantly changed - -**10. Minor Copy Changes** -- No doc update unless it changes UX patterns - -**11. Version Bumps** -- Update version number at top of each doc -- That's it (unless other changes) - ---- - -## How to Update Documentation - -### Step 1: Identify Affected Docs - -**Use this decision tree:** - -``` -Did you change... - -┌─ Database schema? -│ └─> Update: architecture.md, codebase-map.md -│ -┌─ REST API? -│ └─> Update: apis.md, ai-agent-guide.md (examples) -│ -┌─ AJAX handlers? -│ └─> Update: apis.md -│ -┌─ Hooks/filters? -│ └─> Update: apis.md, ai-agent-guide.md (patterns) -│ -┌─ User-facing UI? -│ └─> Update: ui-and-copy.md, onboarding.md -│ -┌─ Security patterns? -│ └─> Update: ai-agent-guide.md, coding-standards.md -│ -┌─ Build process? -│ └─> Update: README.md, onboarding.md -│ -└─ New file/folder? - └─> Update: codebase-map.md -``` - ---- - -### Step 2: Read Existing Doc - -**Before editing:** -1. Read entire document -2. Identify outdated sections -3. Check for consistency with your changes - -**Don't:** -- Blindly add new section without reading context -- Create duplicate information -- Use different terminology than existing docs - ---- - -### Step 3: Make Minimal Changes - -**Best practices:** - -**Do:** -- Update only what changed -- Keep existing structure -- Match existing tone and style -- Add code examples if helpful - -**Don't:** -- Rewrite entire document (unless necessary) -- Change unrelated sections -- Reorganize without discussion -- Remove information (mark deprecated instead) - ---- - -### Step 4: Verify Examples Still Work - -**All code examples must be tested:** - -**PHP examples:** -```bash -# Create test file -cat > /tmp/test-example.php << 'EOF' - [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='] - ], - 'limit' => 20 -]); -``` - -**❌ Bad:** -```php -// Vague, generic -$data = SomeClass::get_stuff($args); -``` - ---- - -### Cross-Referencing - -**Internal links:** -```markdown -See [Architecture Guide](architecture.md) for details. -See [Database Schema](architecture.md#database-schema) for table structure. -``` - -**External links:** -```markdown -Read [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/). -``` - -**Code references:** -```markdown -See `inc/form-submit.php:88-119` for nonce verification. -``` - ---- - -### Code Block Guidelines - -**Always specify language:** - -````markdown -```php -// PHP code here -``` - -```javascript -// JavaScript here -``` - -```bash -# Shell commands here -``` - -```sql --- SQL queries here -``` -```` - -**Include comments:** -```php -// Good: Explain what code does -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'status', 'value' => 'published', 'compare' => '='] - ] -]); -``` - -**Show output if helpful:** -```bash -wp plugin list --status=active -# Output: -# sureforms active -# sureforms-pro active -``` - ---- - -## Deprecation Process - -**When removing features:** - -### Step 1: Mark as Deprecated - -**In code:** -```php -/** - * Old function. - * - * @deprecated 2.5.0 Use new_function() instead. - */ -function old_function() { - _deprecated_function(__FUNCTION__, '2.5.0', 'new_function'); - return new_function(); -} -``` - -**In docs:** -```markdown -## ~~Old Feature~~ (Deprecated) - -**Deprecated in:** 2.5.0 -**Removed in:** 3.0.0 -**Replacement:** [New Feature](#new-feature) - -This feature is deprecated and will be removed in version 3.0.0. -Use [New Feature](#new-feature) instead. - -~~Old documentation here...~~ -``` - ---- - -### Step 2: Update Migration Guide - -**In README.md:** - -```markdown -## Upgrading from 2.4.x to 2.5.0 - -### Breaking Changes - -**Old Feature Deprecated:** -- **What changed:** `old_function()` is now deprecated -- **Action required:** Replace with `new_function()` -- **Code example:** - ```php - // Old (deprecated) - old_function($data); - - // New (recommended) - new_function($data); - ``` -``` - ---- - -### Step 3: Remove After Major Version - -**On next major version (3.0.0):** -1. Remove deprecated code -2. Remove ~~strikethrough~~ docs -3. Update changelog - ---- - -## Changelog Management - -**Keep CHANGELOG.md updated:** - -### Format - -```markdown -# Changelog - -## [2.5.1] - 2026-02-15 - -### Added -- New REST endpoint: `/sureforms/v1/forms/{id}/duplicate` -- Support for custom date formats in email notifications - -### Changed -- Improved performance of entry queries (20% faster) -- Updated Stripe API to v2024-01-01 - -### Deprecated -- `old_function()` - Use `new_function()` instead - -### Fixed -- Bug where conditional logic didn't work with checkboxes -- Memory leak in AI form generation - -### Security -- Fixed XSS vulnerability in admin settings (CVE-2026-0001) - -## [2.5.0] - 2026-02-01 - -... -``` - -**Categories:** -- **Added:** New features -- **Changed:** Changes to existing features -- **Deprecated:** Features marked for removal -- **Removed:** Features removed -- **Fixed:** Bug fixes -- **Security:** Security fixes - ---- - -## Documentation Review Checklist - -**Before committing doc changes:** - -### Content -- [ ] Information is accurate (tested code examples) -- [ ] No outdated information (double-checked) -- [ ] Cross-references updated (links work) -- [ ] Version number updated -- [ ] Terminology consistent with other docs - -### Style -- [ ] Clear and concise (no fluff) -- [ ] Code examples formatted correctly -- [ ] Spelling and grammar correct -- [ ] Tone matches existing docs - -### Technical -- [ ] All code examples tested -- [ ] All commands verified (on dev environment) -- [ ] All links work (no 404s) -- [ ] File paths correct (checked in codebase) - -### Accessibility -- [ ] Headings in logical order (H1 → H2 → H3) -- [ ] Code blocks have language specified -- [ ] Alt text for images (if any) -- [ ] Tables have headers - ---- - -## Automation & Tools - -### Linting Documentation - -**Check Markdown syntax:** - -```bash -# Install markdownlint -npm install -g markdownlint-cli - -# Lint all docs -cd internal-docs/ -markdownlint *.md -``` - -**Common issues caught:** -- Inconsistent heading levels -- Trailing whitespace -- Missing blank lines around code blocks - ---- - -### Link Checking - -**Verify all links work:** - -```bash -# Install markdown-link-check -npm install -g markdown-link-check - -# Check links -markdown-link-check internal-docs/*.md -``` - -**Fix broken links:** -- Update to correct URL -- Remove if resource no longer exists -- Use Internet Archive if critical resource - ---- - -### Spell Checking - -**Use spell checker:** - -```bash -# Install aspell -brew install aspell # macOS -apt install aspell # Linux - -# Check spelling -aspell check internal-docs/README.md -``` - -**Custom dictionary:** - -Create `.aspell.en.pws`: -``` -personal_ws-1.1 en 50 -SureForms -WordPress -Gutenberg -Mailchimp -PayPal -``` - ---- - -## Documentation Templates - -### New Feature Documentation Template - -**When adding new feature:** - -```markdown -## [Feature Name] - -**Since:** [version] -**Type:** [Free/Pro] - -### Overview - -[2-3 sentence description of what feature does and why it exists] - -### Use Cases - -- [Use case 1] -- [Use case 2] -- [Use case 3] - -### How to Use - -**Step 1: [Action]** - -[Description] - -```php -// Code example -``` - -**Step 2: [Action]** - -[Description] - -### Configuration - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `option_name` | string | 'default' | What this does | - -### API Reference - -**REST Endpoint:** -``` -POST /sureforms/v1/feature -``` - -**Parameters:** -- `param1` (string, required) - Description -- `param2` (integer, optional) - Description - -**Response:** -```json -{ - "success": true, - "data": { ... } -} -``` - -### Examples - -**Example 1: [Use case]** -```php -// Code here -``` - -**Example 2: [Use case]** -```php -// Code here -``` - -### Troubleshooting - -**Issue:** [Common problem] -**Solution:** [How to fix] - -### Related - -- [Related feature 1](link) -- [Related feature 2](link) -``` - ---- - -### Bug Fix Documentation Template - -**When fixing bug:** - -**In troubleshooting.md:** - -```markdown -### [Issue Description] - -**Symptom:** [What user sees] - -**Diagnosis:** - -[How to identify the issue] - -```bash -# Commands to diagnose -``` - -**Fixes:** - -**Option 1: [Fix description]** -```php -// Code or command -``` - -**Option 2: [Alternative fix]** -```php -// Alternative code -``` - -**Prevents:** [What this prevents] - -**See also:** [Related issues] -``` - ---- - -## Review & Approval Process - -### Before Merging Docs - -**Self-review:** -1. Read your changes out loud -2. Test all code examples -3. Run linters -4. Check cross-references - -**Peer review:** -1. Request review from team member -2. Address feedback -3. Update based on comments - -**Final checks:** -1. Rebase on main branch -2. Verify no conflicts -3. One last proofread - ---- - -### Documentation Pull Request Template - -**PR description:** - -```markdown -## Documentation Update - -**Plugin Version:** 2.5.1 -**Docs Changed:** [List files] - -### Changes Made - -- [ ] Added documentation for [feature] -- [ ] Updated [section] in [file] -- [ ] Fixed typos in [file] - -### Verification - -- [x] All code examples tested -- [x] All links checked -- [x] Spelling checked -- [x] Cross-references updated -- [x] Version numbers updated - -### Related - -- Related PR: #123 -- Related Issue: #456 -``` - ---- - -## Documentation Metrics - -**Track documentation quality:** - -### Metrics to Monitor - -**Coverage:** -- % of features documented -- % of APIs documented -- % of code examples tested - -**Freshness:** -- Days since last update -- Number of outdated sections -- Deprecated content count - -**Quality:** -- Broken link count -- Spelling error count -- User-reported doc issues - -**Engagement:** -- Doc views (if analytics enabled) -- Time spent reading -- Bounce rate (if high, docs unclear) - ---- - -## Long-Term Maintenance - -### Quarterly Review - -**Every 3 months:** - -1. **Audit all docs:** - - Read each document start to finish - - Test all code examples - - Verify all links - - Update outdated info - -2. **User feedback:** - - Review GitHub issues tagged "documentation" - - Survey users about doc clarity - - Identify gaps in coverage - -3. **Reorganize if needed:** - - Move sections to better fit structure - - Split large docs if too long - - Merge duplicate information - ---- - -### Annual Review - -**Every year:** - -1. **Major cleanup:** - - Remove deprecated content - - Archive old versions - - Rewrite outdated sections - -2. **Structure evaluation:** - - Does structure still make sense? - - Should we add/remove files? - - Are cross-references clear? - -3. **Voice & tone:** - - Is tone consistent? - - Is it still beginner-friendly? - - Does it match product evolution? - ---- - -## Questions? - -**For documentation questions:** -- Create GitHub issue: `[Docs] Your question` -- Tag: `documentation` -- Assign: Documentation maintainer - -**For urgent doc bugs:** -- Ping in Slack: `#sureforms-docs` -- Include: File name, line number, issue description - ---- - -## Conclusion - -**Documentation is code.** - -Treat it with same care: -- Test before committing -- Review changes -- Keep it DRY (Don't Repeat Yourself) -- Refactor when needed - -**Good documentation:** -- Saves support time -- Accelerates onboarding -- Prevents bugs -- Shows we care - -**Thank you for maintaining SureForms docs!** 📚 - ---- - -**Version:** 2.5.0 -**Last Updated:** 2026-02-12 -**Maintainer:** SureForms Team diff --git a/internal-docs/onboarding.md b/internal-docs/onboarding.md deleted file mode 100644 index ab5b83ad3..000000000 --- a/internal-docs/onboarding.md +++ /dev/null @@ -1,1086 +0,0 @@ -# Developer Onboarding - -**Version:** 2.5.0 - ---- - -## Welcome to SureForms - -This guide helps you become productive quickly, whether you have 1 hour, 1 day, or 1 week. - -**What you're working on:** -- **SureForms Free:** AI-powered WordPress form builder (219 PHP files, 50+ blocks) -- **SureForms Pro:** Premium extension (206 PHP files, payments, integrations, advanced features) - -**Architecture:** WordPress + Gutenberg blocks + React 18 + Custom database - ---- - -## Prerequisites - -Before starting: -- [ ] WordPress 6.4+ installed locally -- [ ] Node.js 18+ and npm 8+ -- [ ] PHP 7.4+ with Composer -- [ ] Git configured -- [ ] IDE with PHP/JavaScript support -- [ ] Both plugins cloned from GitHub - -**Test environment:** -```bash -# If using Local by Flywheel or similar -http://localhost:10003/wp-admin/ -Username: admin -Password: admin -``` - ---- - -## 1-Hour Quick Start - -**Goal:** Make your first successful change - -### Step 1: Get Code Running (15 min) - -```bash -# Clone repositories (if not already) -cd /path/to/wp-content/plugins/ -git clone https://github.com/brainstormforce/sureforms.git -git clone https://github.com/brainstormforce/sureforms-pro.git - -# Install dependencies - SureForms Free -cd sureforms/ -npm install -composer install - -# Build assets -npm run build - -# Install dependencies - SureForms Pro -cd ../sureforms-pro/ -npm install -composer install -npm run build -``` - -**Verify installation:** -```bash -# In WordPress admin -wp plugin activate sureforms sureforms-pro -wp plugin list | grep sureforms -``` - -### Step 2: Create Your First Form (15 min) - -**Via WordPress admin:** -1. Go to **SureForms → Add New** -2. Click **Create with AI** or **Start from Blank** -3. Add fields: Email, Name, Message -4. Click **Publish** -5. Add to a page with the SureForms block - -**Via WP-CLI:** -```bash -# Create form programmatically -wp post create \ - --post_type=sureforms_form \ - --post_title="Test Contact Form" \ - --post_status=publish \ - --post_content='' -``` - -### Step 3: Make a Simple Change (20 min) - -**Task:** Change the submit button text - -**Files to modify:** -``` -src/blocks/form/edit.js ← React component -inc/blocks/form/block.php ← Server-side rendering -``` - -**Change 1: Edit the React component** - -Open `src/blocks/form/edit.js`: - -```javascript -// Find around line 45-50 -const { submitButtonText } = attributes; - -// Change default value -const defaultSubmitText = __('Send Message', 'sureforms'); // ← Change this -``` - -**Change 2: Rebuild** - -```bash -npm run build -``` - -**Verify:** Create new form, check if default button text changed. - -### Step 4: Debug Your Change (10 min) - -**Enable debugging:** - -Edit `wp-config.php`: -```php -define('WP_DEBUG', true); -define('WP_DEBUG_LOG', true); -define('WP_DEBUG_DISPLAY', false); -define('SCRIPT_DEBUG', true); // Loads unminified JS -``` - -**Check debug log:** -```bash -tail -f wp-content/debug.log -``` - -**Browser console:** -- Open DevTools → Console -- Look for JavaScript errors -- Check Network tab for AJAX failures - ---- - -## 1-Day Deep Dive - -**Goal:** Understand architecture and make meaningful contributions - -### Morning: Core Concepts (4 hours) - -#### Hour 1: Architecture Overview - -**Read these docs first:** -1. [README.md](README.md) - Project overview -2. [architecture.md](architecture.md) - System design -3. [codebase-map.md](codebase-map.md) - File structure - -**Key concepts to understand:** -- **Gutenberg blocks:** React components that render forms -- **Custom post type:** `sureforms_form` stores form configurations -- **Custom database tables:** `wp_sureforms_entries`, `wp_sureforms_payments` -- **REST API:** Handles form submissions, AI generation -- **AJAX handlers:** Payment processing, file uploads - -**Test your understanding:** -- Where is form submission data saved? (Answer: `wp_sureforms_entries` table) -- How does AI form generation work? (Answer: REST endpoint → external middleware → GPT API) -- What's the difference between Free and Pro? (Answer: Pro adds payments, integrations, conditional logic) - -#### Hour 2: Data Flow - -**Trace a form submission from start to finish:** - -``` -User fills form → Frontend validation → AJAX request - ↓ -inc/form-submit.php (Line 88-119: Nonce verification) - ↓ -Field validation (inc/field-validation.php) - ↓ -Sanitization (inc/helper.php::sanitize_by_field_type()) - ↓ -Database insert (inc/database/tables/entries.php::insert()) - ↓ -Email notification (inc/email/email-handler.php) - ↓ -Confirmation message (or redirect) -``` - -**Hands-on exercise:** -1. Enable MySQL query logging -2. Submit a test form -3. Check `wp_sureforms_entries` table -4. Verify entry data matches submission - -```bash -# View recent entries -wp db query "SELECT * FROM wp_sureforms_entries ORDER BY id DESC LIMIT 5;" -``` - -#### Hour 3: Payment Processing - -**Understand Stripe payment flow:** - -``` -User clicks "Pay" → Frontend creates payment intent - ↓ -AJAX: wp_ajax_nopriv_srfm_create_payment_intent - ↓ -inc/payments/front-end.php (Line 77-83: Create Stripe PaymentIntent) - ↓ -Stripe API processes payment - ↓ -Webhook received: sureforms/webhook_test - ↓ -inc/payments/stripe/stripe-webhook.php (Line 220-637: Handle events) - ↓ -Update wp_sureforms_payments table - ↓ -Send confirmation email -``` - -**Hands-on exercise:** -1. Set up Stripe test keys -2. Create a payment form -3. Use Stripe test card: `4242 4242 4242 4242` -4. Check `wp_sureforms_payments` table for payment record - -#### Hour 4: Pro Features - -**Explore key Pro additions:** - -**1. User Registration:** -- File: `inc/business/user-registration/processor.php` -- Creates WordPress user accounts from form submissions -- Handles login/password reset - -**2. Native Integrations:** -- Folder: `inc/pro/native-integrations/integrations/` -- 24+ services: Mailchimp, Brevo, HubSpot, Salesforce, etc. -- OAuth authentication with encrypted token storage - -**3. Conditional Logic:** -- Frontend: `src/pro/conditional-logic/` -- Show/hide fields based on user selections -- Real-time UI updates - -**Hands-on exercise:** -1. Create form with registration block -2. Submit form, verify user created: `wp user list` -3. Enable Mailchimp integration (test mode) -4. Submit form, check integration logs - -### Afternoon: Build Your First Feature (4 hours) - -**Challenge:** Add a new field type - -**Example: "Star Rating" field** - -#### Step 1: Plan (30 min) - -**Requirements:** -- Display 5 stars (clickable) -- Save selected rating (1-5) -- Validate: Required if configured -- Display in entries table - -**Files to create/modify:** -``` -src/blocks/star-rating/ ← New React component -inc/blocks/star-rating/block.php ← Server-side rendering -inc/helper.php ← Add sanitization -inc/field-validation.php ← Add validation -``` - -#### Step 2: Create Block Structure (1 hour) - -**Create block files:** - -```bash -# Create directories -mkdir -p src/blocks/star-rating -mkdir -p inc/blocks/star-rating - -# Copy from existing field (Phone is a good template) -cp -r src/blocks/phone/* src/blocks/star-rating/ -cp inc/blocks/phone/block.php inc/blocks/star-rating/ -``` - -**Edit `src/blocks/star-rating/block.json`:** - -```json -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "sureforms/star-rating", - "title": "Star Rating", - "category": "sureforms", - "icon": "star-filled", - "description": "Display a star rating input", - "attributes": { - "label": { - "type": "string", - "default": "Rate your experience" - }, - "required": { - "type": "boolean", - "default": false - }, - "maxRating": { - "type": "number", - "default": 5 - } - }, - "supports": { - "html": false - } -} -``` - -**Edit `src/blocks/star-rating/edit.js`:** - -```javascript -import { __ } from '@wordpress/i18n'; -import { useBlockProps } from '@wordpress/block-editor'; -import { TextControl, ToggleControl, RangeControl } from '@wordpress/components'; - -export default function Edit({ attributes, setAttributes }) { - const { label, required, maxRating } = attributes; - - return ( -
    - setAttributes({ label: value })} - /> - setAttributes({ required: value })} - /> - setAttributes({ maxRating: value })} - min={1} - max={10} - /> -
    - {[...Array(maxRating)].map((_, i) => ( - - ))} -
    -
    - ); -} -``` - -#### Step 3: Server-Side Rendering (45 min) - -**Edit `inc/blocks/star-rating/block.php`:** - -```php - [__CLASS__, 'render'], - ] - ); - } - - public static function render($attributes) { - $label = esc_html($attributes['label'] ?? 'Rate your experience'); - $required = !empty($attributes['required']); - $max_rating = absint($attributes['maxRating'] ?? 5); - $field_name = 'srfm-star-rating-' . uniqid(); - - ob_start(); - ?> -
    - -
    - - - ★ - - -
    - > -
    - { - const stars = container.querySelectorAll('.srfm-star'); - const input = container.nextElementSibling; - const maxRating = parseInt(container.dataset.max); - - stars.forEach(star => { - star.addEventListener('click', function() { - const value = parseInt(this.dataset.value); - input.value = value; - - // Visual update - stars.forEach((s, index) => { - if (index < value) { - s.classList.add('selected'); - } else { - s.classList.remove('selected'); - } - }); - }); - - // Hover effect - star.addEventListener('mouseenter', function() { - const value = parseInt(this.dataset.value); - stars.forEach((s, index) => { - if (index < value) { - s.classList.add('hover'); - } else { - s.classList.remove('hover'); - } - }); - }); - }); - - container.addEventListener('mouseleave', function() { - stars.forEach(s => s.classList.remove('hover')); - }); - }); -}); -``` - -#### Step 5: Register Block (15 min) - -**Edit `inc/blocks/register.php`:** - -```php -// Add to existing blocks registration -Star_Rating\Block::register(); -``` - -**Edit `src/blocks/star-rating/index.js`:** - -```javascript -import { registerBlockType } from '@wordpress/blocks'; -import edit from './edit'; -import metadata from './block.json'; - -registerBlockType(metadata.name, { - edit, - save: () => null, // Server-side rendering -}); -``` - -#### Step 6: Add Sanitization & Validation (30 min) - -**Edit `inc/helper.php`:** - -Find `sanitize_by_field_type()` method and add: - -```php -case 'star-rating': - $value = absint($value); - // Ensure value is between 1 and max rating - return ($value >= 1 && $value <= 10) ? $value : 0; -``` - -**Edit `inc/field-validation.php`:** - -Find validation logic and add: - -```php -if ($field_type === 'star-rating' && $is_required && empty($value)) { - $errors[] = __('Please select a rating', 'sureforms'); -} -``` - -#### Step 7: Build & Test (30 min) - -```bash -# Build assets -npm run build - -# Test in browser -# 1. Create new form -# 2. Add "Star Rating" block -# 3. Configure settings (label, required, max stars) -# 4. Publish form -# 5. Submit test entry -# 6. Verify data saved correctly - -# Check database -wp db query "SELECT * FROM wp_sureforms_entries ORDER BY id DESC LIMIT 1;" -``` - ---- - -## 1-Week Mastery - -**Goal:** Expert-level understanding and complex feature development - -### Day 1: Architecture Deep Dive - -**Morning:** -- Complete 1-day onboarding -- Read all internal docs thoroughly -- Map data flow for all major features - -**Afternoon:** -- Trace payment processing end-to-end -- Understand webhook verification -- Study integration architecture - -### Day 2: Security & Best Practices - -**Focus areas:** -1. **Input sanitization:** Understand all 15+ sanitization methods -2. **Output escaping:** When to use `esc_html()`, `esc_attr()`, `esc_url()` -3. **SQL injection prevention:** Always use `$wpdb->prepare()` -4. **CSRF protection:** Nonce verification patterns -5. **Authorization:** `current_user_can()` checks - -**Hands-on exercises:** -- Review `inc/form-submit.php` (Lines 88-119: Security checks) -- Study `inc/helper.php` sanitization methods -- Find and fix intentional security bugs in test code - -**Read:** -- [coding-standards.md](coding-standards.md) -- [ai-agent-guide.md](ai-agent-guide.md) - Security section - -### Day 3: React & Gutenberg Blocks - -**Topics:** -1. **Block architecture:** How Gutenberg blocks work -2. **Attributes:** Data storage and retrieval -3. **Inspector controls:** Settings sidebar -4. **Block variations:** Different field configurations -5. **Dynamic blocks:** Server-side rendering - -**Build complex block:** -- **Multi-step form block** (if not using Pro feature) -- Add step navigation -- Save progress between steps -- Implement validation per step - -**Resources:** -- Official Gutenberg docs: https://developer.wordpress.org/block-editor/ -- Example: Study `src/blocks/phone/` for complex field - -### Day 4: Database & REST API - -**Morning: Database operations** - -**Study these classes:** -- `inc/database/base.php` - Base table class -- `inc/database/tables/entries.php` - Entries CRUD -- `inc/database/tables/payments.php` - Payment records - -**Practice:** -```php -// Create custom query -use SRFM\Inc\Database\Tables\Entries; - -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='], - ['key' => 'status', 'value' => 'published', 'compare' => '='] - ], - 'orderby' => 'created_at', - 'order' => 'DESC', - 'limit' => 20 -]); -``` - -**Afternoon: REST API development** - -**Create custom endpoint:** - -```php -// In new file: inc/rest-api-custom.php -namespace SRFM\Inc; - -class Rest_API_Custom { - public function register_routes() { - register_rest_route('sureforms/v1', '/custom-stats', [ - 'methods' => 'GET', - 'callback' => [$this, 'get_stats'], - 'permission_callback' => [$this, 'check_permissions'] - ]); - } - - public function check_permissions() { - return current_user_can('manage_options'); - } - - public function get_stats($request) { - // Your logic here - return ['success' => true, 'data' => []]; - } -} -``` - -### Day 5: Pro Features & Integrations - -**Morning: Payment processing** - -**Study both gateways:** -- Stripe: `inc/payments/stripe/` -- PayPal (Pro): `inc/business/payments/pay-pal/` - -**Understand webhook handling:** -- Signature verification -- Event processing -- Database updates -- Error handling - -**Afternoon: Build custom integration** - -**Example: Slack integration** - -1. Create integration folder: -``` -inc/pro/native-integrations/integrations/slack/ -├── config.json -└── actions/ - └── send-message.php -``` - -2. Define config: -```json -{ - "name": "Slack", - "slug": "slack", - "auth_method": "webhook_url", - "actions": [ - { - "name": "Send Message", - "slug": "send-message", - "endpoint": "chat.postMessage" - } - ] -} -``` - -3. Implement action: -```php -format_message($form_data); - - $response = wp_remote_post($webhook_url, [ - 'body' => json_encode(['text' => $message]), - 'headers' => ['Content-Type' => 'application/json'] - ]); - - if (is_wp_error($response)) { - return ['success' => false, 'error' => $response->get_error_message()]; - } - - return ['success' => true]; - } - - private function format_message($form_data) { - // Format form data as Slack message - return "New form submission:\n" . print_r($form_data, true); - } -} -``` - ---- - -## Testing Your Changes - -### Local Testing - -**PHP Unit Tests:** -```bash -# Run all tests -composer test - -# Run specific test file -vendor/bin/phpunit tests/unit/test-helper.php - -# Run with coverage -composer test-coverage -``` - -**JavaScript Tests:** -```bash -# Run all JS tests -npm run test:unit - -# Watch mode -npm run test:unit:watch - -# Coverage -npm run test:unit:coverage -``` - -### E2E Testing (Playwright) - -```bash -# Start test environment -npm run play:up - -# Run E2E tests -npm run play:run - -# Interactive mode (see browser) -npm run play:run:interactive - -# Stop environment -npm run play:down -``` - -### Manual Testing Checklist - -Before submitting PR: -- [ ] Form creation works -- [ ] Form submission saves entry -- [ ] Email notifications sent -- [ ] Payment processing works (if touched) -- [ ] No JavaScript console errors -- [ ] No PHP errors in debug.log -- [ ] Works in latest WordPress version -- [ ] Works in Firefox, Chrome, Safari -- [ ] Mobile responsive -- [ ] Accessibility: keyboard navigation works - ---- - -## Code Review Process - -### Before Creating PR - -**Self-review:** -1. Run linters: -```bash -composer lint # PHP_CodeSniffer -npm run lint-js # ESLint -``` - -2. Auto-fix minor issues: -```bash -composer format # phpcbf -npm run lint-js:fix # ESLint --fix -``` - -3. Check for common issues: -- [ ] All inputs sanitized -- [ ] All outputs escaped -- [ ] SQL queries use `prepare()` -- [ ] Nonces verified for AJAX/forms -- [ ] Capabilities checked for admin actions -- [ ] No hardcoded strings (use `__()` for i18n) - -### Creating PR - -**Branch naming:** -```bash -git checkout -b feat/star-rating-field -git checkout -b fix/payment-webhook-error -git checkout -b refactor/database-queries -``` - -**Commit message format:** -``` -type(scope): brief description - -Longer explanation if needed. - -Co-Authored-By: Your Name -``` - -**Types:** feat, fix, docs, style, refactor, test, chore - -**Example:** -``` -feat(blocks): add star rating field - -- Add new star-rating block -- Implement frontend JavaScript -- Add sanitization and validation -- Update docs with new field type - -Co-Authored-By: Claude Sonnet 4.5 -``` - -### PR Checklist - -- [ ] Tests pass: `composer test && npm run test:unit` -- [ ] Linting passes: `composer lint && npm run lint-js` -- [ ] No console errors -- [ ] Database migrations documented (if any) -- [ ] Breaking changes documented -- [ ] Changelog updated -- [ ] Screenshots attached (for UI changes) - ---- - -## Common Tasks & Patterns - -### Add New AJAX Handler - -**Pattern:** -```php -// In controller file -public function register_ajax_actions() { - add_action('wp_ajax_srfm_my_action', [$this, 'handle_my_action']); - add_action('wp_ajax_nopriv_srfm_my_action', [$this, 'handle_my_action']); -} - -public function handle_my_action() { - // CRITICAL: Always verify nonce - check_ajax_referer('srfm_my_action_nonce', 'security'); - - // Check permissions if needed - if (!current_user_can('manage_options')) { - wp_send_json_error(['message' => 'Insufficient permissions']); - } - - // Sanitize inputs - $data = Helper::sanitize_array_recursively($_POST['data'] ?? []); - - // Process... - - wp_send_json_success(['result' => $result]); -} -``` - -### Add New REST Endpoint - -**Pattern:** -```php -// In inc/rest-api.php -public function register_routes() { - register_rest_route('sureforms/v1', '/my-endpoint', [ - 'methods' => 'POST', - 'callback' => [$this, 'handle_request'], - 'permission_callback' => [$this, 'check_permissions'] - ]); -} - -public function check_permissions() { - return current_user_can('manage_options'); -} - -public function handle_request($request) { - // Always verify nonce - $nonce = $request->get_header('X-WP-Nonce'); - if (!wp_verify_nonce($nonce, 'wp_rest')) { - return new \WP_Error('invalid_nonce', 'Invalid nonce', ['status' => 403]); - } - - // Get and sanitize parameters - $params = $request->get_params(); - $clean_params = Helper::sanitize_array_recursively($params); - - // Process... - - return ['success' => true, 'data' => $result]; -} -``` - -### Add WordPress Hook - -**Action hook (notify about event):** -```php -// Trigger action after entry save -do_action('sureforms_after_entry_save', $entry_id, $form_id, $entry_data); - -// Other plugins/themes can listen: -add_action('sureforms_after_entry_save', function($entry_id, $form_id, $data) { - // Custom logic -}, 10, 3); -``` - -**Filter hook (modify data):** -```php -// Allow filtering of email subject -$subject = apply_filters('sureforms_email_subject', $subject, $form_id, $entry_data); - -// Other plugins/themes can modify: -add_filter('sureforms_email_subject', function($subject, $form_id, $data) { - return "[Form $form_id] $subject"; -}, 10, 3); -``` - -### Database Operations - -**Insert entry:** -```php -use SRFM\Inc\Database\Tables\Entries; - -$entry_id = Entries::insert([ - 'form_id' => 123, - 'user_id' => get_current_user_id(), - 'status' => 'published', - 'entry_data' => wp_json_encode($form_data) -]); -``` - -**Update entry:** -```php -Entries::update($entry_id, [ - 'status' => 'spam' -]); -``` - -**Delete entry:** -```php -Entries::delete($entry_id); -``` - -**Query entries:** -```php -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='] - ], - 'limit' => 20, - 'offset' => 0 -]); -``` - ---- - -## Debugging Tips - -### Enable Debug Mode - -**wp-config.php:** -```php -define('WP_DEBUG', true); -define('WP_DEBUG_LOG', true); -define('WP_DEBUG_DISPLAY', false); -define('SCRIPT_DEBUG', true); -define('SAVEQUERIES', true); -``` - -### Check Debug Log - -```bash -# Real-time log monitoring -tail -f wp-content/debug.log - -# Search for errors -grep "SureForms" wp-content/debug.log -``` - -### JavaScript Debugging - -**Browser console:** -```javascript -// Check global object -console.log(window.sureforms); - -// Check block attributes -wp.data.select('core/block-editor').getBlocks(); - -// Check form data -document.querySelector('form').addEventListener('submit', (e) => { - console.log('Form data:', new FormData(e.target)); -}); -``` - -### Database Queries - -**Install Query Monitor plugin:** -```bash -wp plugin install query-monitor --activate -``` - -**Check slow queries:** -- Visit any admin page -- Click "Query Monitor" in admin bar -- View "Queries" tab -- Sort by time -- Optimize slow queries - -**Manual query inspection:** -```php -// Add to code temporarily -global $wpdb; -$wpdb->show_errors(); -$wpdb->print_error(); -``` - -### AJAX Debugging - -**Check network tab:** -1. Open DevTools → Network -2. Filter: XHR -3. Submit form -4. Click request -5. Check Response tab for errors - -**Add debug output:** -```php -public function handle_ajax() { - error_log('AJAX data: ' . print_r($_POST, true)); - // ... rest of code -} -``` - ---- - -## Resources - -### Internal Documentation -- [README.md](README.md) - Quick start -- [architecture.md](architecture.md) - System design -- [codebase-map.md](codebase-map.md) - File structure -- [apis.md](apis.md) - API reference -- [coding-standards.md](coding-standards.md) - Code standards -- [ai-agent-guide.md](ai-agent-guide.md) - AI agent guidance -- [troubleshooting.md](troubleshooting.md) - Common problems - -### External Resources -- **WordPress Codex:** https://codex.wordpress.org/ -- **Block Editor Handbook:** https://developer.wordpress.org/block-editor/ -- **REST API Handbook:** https://developer.wordpress.org/rest-api/ -- **React Docs:** https://react.dev/ -- **WP-CLI:** https://wp-cli.org/ - -### Community -- **GitHub Issues:** Report bugs, request features -- **Facebook Group:** https://www.facebook.com/groups/surecart -- **Support:** https://support.brainstormforce.com/ - ---- - -## Next Steps - -After completing onboarding: - -1. **Pick a starter issue:** - - Look for "good first issue" label on GitHub - - Start with documentation or small bug fixes - - Work up to features - -2. **Read code:** - - Pick a feature you use - - Trace it from UI to database - - Understand every line - -3. **Write tests:** - - For code you touch - - Increases confidence - - Helps others understand your changes - -4. **Ask questions:** - - Don't hesitate to ask - - Better to ask than assume - - Document answers for others - -**Welcome to the team!** 🚀 diff --git a/internal-docs/product-vision.md b/internal-docs/product-vision.md deleted file mode 100644 index f41f1951c..000000000 --- a/internal-docs/product-vision.md +++ /dev/null @@ -1,688 +0,0 @@ -# Product Vision - -**Version:** 2.5.0 - ---- - -## Mission - -**Empower anyone to build beautiful, high-converting forms without code.** - -SureForms exists to solve a fundamental problem: creating forms on WordPress is unnecessarily complex, resulting in ugly forms that hurt conversion rates. - ---- - -## The Problem We Solve - -### Pain Points (Before SureForms) - -**1. Complexity** -- Learning curve: New interfaces, proprietary builders -- Time waste: Hours configuring simple contact forms -- Technical barriers: Requires developer for custom fields - -**2. Design Limitations** -- Generic templates that don't match site design -- Limited styling options without CSS knowledge -- Mobile responsiveness as afterthought - -**3. Low Engagement** -- Long, intimidating single-page forms -- No personalization or conditional logic -- High abandonment rates (avg 67% for long forms) - -**4. Spam & Security** -- Constant bot submissions -- Security vulnerabilities in popular plugins -- GDPR compliance challenges - -**5. Integration Friction** -- Payment processing requires multiple plugins -- Manual data export/import to CRMs -- Webhook setup requires developer knowledge - ---- - -## Our Solution - -### Core Differentiators - -**1. Native WordPress (Gutenberg)** -- **Why:** Users already know the interface -- **Benefit:** Zero learning curve, instant productivity -- **Technical:** React blocks, no proprietary builder - -**2. AI-Powered Form Building** -- **Innovation:** First AI form builder for WordPress -- **How:** Natural language → complete functional form in seconds -- **Examples:** - - "Create a job application form" → 12-field form with file upload - - "Simple contact form" → 3 fields, perfectly styled - - "Event RSVP with dietary restrictions" → Conditional logic auto-configured - -**3. Built-in Payments** -- **Why:** No WooCommerce, no add-ons required -- **Gateways:** Stripe (Free), PayPal (Pro) -- **Features:** One-time, subscriptions, custom amounts -- **Security:** PCI-compliant, encrypted credentials - -**4. Mobile-First Design** -- **Approach:** Responsive by default, not opt-in -- **Testing:** Every block tested on iOS/Android -- **Performance:** Fast load times on 3G networks - -**5. Engagement Features (Pro)** -- **Conversational Forms:** Chat-like, one question at a time -- **Multi-Step Forms:** Break long forms into digestible steps -- **Conditional Logic:** Show/hide based on answers -- **Result:** 3x higher completion rates vs single-page forms - ---- - -## Target Users - -### Primary Personas - -#### 1. Website Owner (40% of users) -**Profile:** -- Small business owner or solopreneur -- Limited technical skills -- DIY mentality -- Budget-conscious - -**Needs:** -- Contact forms, quote requests, bookings -- Easy setup (< 10 minutes) -- Professional appearance -- Spam protection - -**Pain Points:** -- Frustrated with complex form builders -- Can't afford developer -- Generic templates don't match brand - -**How SureForms Helps:** -- AI creates form in 30 seconds -- Instant Form feature (no embedding needed) -- Built-in anti-spam (reCAPTCHA, Honeypot) -- Modern, customizable design - ---- - -#### 2. WordPress Designer (30% of users) -**Profile:** -- Freelancer or agency designer -- Strong design skills, basic code knowledge -- Builds sites for clients -- Values aesthetics and UX - -**Needs:** -- Forms that match site design -- Custom styling without CSS -- Fast deployment -- Client-friendly interface - -**Pain Points:** -- Form plugins look "generic" -- CSS overrides are tedious -- Clients can't update forms themselves -- Other plugins don't use Gutenberg - -**How SureForms Helps:** -- Gutenberg-native (feels like page building) -- Extensive styling options in UI -- Client can edit without breaking design -- Block patterns for reusability - ---- - -#### 3. WordPress Developer (20% of users) -**Profile:** -- Full-stack developer -- Builds custom WordPress solutions -- Values clean code and extensibility -- Performance-conscious - -**Needs:** -- Developer-friendly APIs -- Hooks and filters -- Custom field types -- Database access -- Integration capabilities - -**Pain Points:** -- Other plugins have messy codebases -- Limited extensibility -- Poor documentation -- Performance issues (N+1 queries, bloat) - -**How SureForms Helps:** -- Clean, modern codebase (PSR-12-inspired) -- Extensive hooks: 50+ actions/filters -- Well-documented APIs -- Optimized queries (custom tables, not post meta) -- GitHub access for contributions - ---- - -#### 4. E-commerce Store Owner (10% of users) -**Profile:** -- Runs WooCommerce or custom store -- Needs payment forms (not full checkout) -- Sells services, memberships, donations -- Wants simplicity - -**Needs:** -- Accept payments without WooCommerce -- Subscription billing -- Custom pricing fields -- Receipt emails - -**Pain Points:** -- WooCommerce is overkill for simple payments -- Other payment forms require add-ons -- Can't customize payment flow -- Poor mobile checkout experience - -**How SureForms Helps:** -- Built-in Stripe/PayPal (no plugins) -- Subscription support included -- Custom amount fields (donations, tips) -- Mobile-optimized payment UI - ---- - -## Feature Philosophy - -### Design Principles - -**1. Simplicity Over Features** -- Don't add features just because competitors have them -- Every feature must solve real user problem -- Hide complexity behind smart defaults - -**Example:** -- ❌ Bad: 50 font options overwhelming users -- ✅ Good: 5 curated fonts + custom font option - -**2. Progressive Disclosure** -- Show basic options first -- Advanced settings collapsed by default -- Help text on hover, not always visible - -**Example:** -- Default form settings: 4 essential options -- Advanced panel: 15+ options (collapsed) - -**3. Convention Over Configuration** -- Smart defaults based on common use cases -- Zero config for 80% of users -- Power users can customize - -**Example:** -- Email notification auto-configured with sensible template -- User can override if needed - -**4. Performance First** -- Lazy load non-critical assets -- Minimize HTTP requests -- Database queries optimized (no N+1) -- CSS/JS minified and cached - -**Metrics:** -- Page load impact: < 50KB additional assets -- Time to Interactive: < 2 seconds on 3G -- Database queries: Max 5 per form render - -**5. Accessibility Built-In** -- WCAG 2.1 Level AA compliance -- Keyboard navigation -- Screen reader support -- Focus indicators - -**Testing:** -- Every block tested with NVDA/JAWS -- Keyboard-only testing required -- Color contrast checked - ---- - -## Free vs Pro Strategy - -### Free Plugin (Core Experience) - -**Philosophy:** Full-featured, not crippled trial - -**Included:** -- Unlimited forms -- Unlimited submissions -- All 15+ field types -- Email notifications -- Spam protection (reCAPTCHA, Honeypot) -- Stripe payments (one-time, subscriptions) -- Form analytics -- Export entries (CSV) -- GDPR compliance -- Instant Forms - -**Limitations:** -- No PayPal -- No native integrations (Mailchimp, etc.) -- No conditional logic -- No multi-step/conversational forms -- No user registration -- No PDF generation - -**Goal:** Provide genuine value, build trust, convert 5-10% to Pro - ---- - -### Pro Plugin (Power Features) - -**Philosophy:** Advanced features for serious users - -**Added Value:** -- **Payments:** PayPal (one-time, subscriptions) -- **Integrations:** 24+ native (Mailchimp, Brevo, HubSpot, Salesforce, etc.) -- **Logic:** Conditional show/hide, calculations -- **Forms:** Multi-step, conversational, save & resume -- **Users:** Registration, login, password reset -- **Output:** PDF generation from submissions -- **Advanced Fields:** Upload (images, files), signature, calculator -- **Priority Support:** < 24hr response time - -**Pricing Tiers:** -- **Essential:** $99/year (3 sites) -- **Plus:** $199/year (20 sites) -- **Agency:** $299/year (unlimited sites) - -**Conversion Strategy:** -- Free users see "Pro" badge on locked features -- No nag screens or popups -- Upgrade CTA in logical places (when user needs feature) -- 14-day money-back guarantee - ---- - -## Competitive Landscape - -### Direct Competitors - -**1. Gravity Forms** -- **Strengths:** Mature, extensive add-ons, trusted -- **Weaknesses:** Old UI, not Gutenberg-native, expensive -- **Our Advantage:** Modern interface, AI builder, lower cost - -**2. WPForms** -- **Strengths:** Beginner-friendly, drag-and-drop -- **Weaknesses:** Generic designs, limited styling -- **Our Advantage:** Better design flexibility, Gutenberg-native - -**3. Formidable Forms** -- **Strengths:** Advanced features, views/reporting -- **Weaknesses:** Steep learning curve, performance issues -- **Our Advantage:** Simpler, faster, AI-assisted - -**4. Fluent Forms** -- **Strengths:** Conversational forms, modern UI -- **Weaknesses:** Not Gutenberg-native, separate builder -- **Our Advantage:** Native Gutenberg, simpler UX - -**5. Contact Form 7** -- **Strengths:** Free, lightweight, popular -- **Weaknesses:** No UI, requires shortcodes, ugly default styles -- **Our Advantage:** Visual builder, beautiful defaults, AI - ---- - -### Market Positioning - -**SureForms:** The AI-powered Gutenberg form builder - -**Tagline:** "Beautiful forms without code" - -**Positioning Statement:** -> For WordPress users who want high-converting forms without complexity, SureForms is the AI-powered form builder that creates beautiful, mobile-first forms in seconds—unlike outdated plugins that require hours of configuration and result in generic-looking forms. - -**Why Users Choose SureForms:** -1. **Speed:** AI creates forms in 30 seconds vs 30 minutes -2. **Design:** Modern, mobile-first vs generic templates -3. **Ease:** Gutenberg-native vs proprietary builder -4. **Value:** Built-in payments vs add-ons required - ---- - -## Product Roadmap - -### Current Focus (2026 Q1) - -**Theme:** Stability & Performance - -**Priorities:** -1. Bug fixes from user reports -2. Performance optimization (lazy loading, query optimization) -3. Security hardening (code audit, penetration testing) -4. Documentation improvements -5. Accessibility compliance (WCAG 2.2 Level AA) - ---- - -### Near-Term (2026 Q2-Q3) - -**Theme:** Advanced Features & Integrations - -**Planned:** -1. **More Integrations:** - - Google Sheets (native, no Zapier) - - Airtable - - Notion - - Slack (native) - -2. **Field Types:** - - Star rating - - Matrix/grid (Pro) - - File upload enhancements (drag & drop, preview) - -3. **Conditional Logic Enhancements (Pro):** - - Show/hide blocks (not just fields) - - Calculation fields (price quotes, BMI calculators) - - Conditional email notifications - -4. **Analytics:** - - Conversion funnel visualization - - A/B testing (form variations) - - Heatmaps (where users drop off) - -5. **Templates:** - - 100+ pre-built form templates - - Industry-specific (real estate, healthcare, education) - - Import/export custom templates - ---- - -### Long-Term Vision (2027+) - -**Theme:** AI-Driven Personalization - -**Research Areas:** -1. **AI Form Optimization:** - - Auto-suggest field improvements based on completion rates - - Predictive text for common fields - - Smart field ordering (ML-driven) - -2. **Advanced Conversational Forms:** - - Voice input support - - Natural language processing for responses - - Branching logic based on sentiment - -3. **Global Expansion:** - - Multi-language support (WPML, Polylang) - - Currency localization - - Regional compliance (CCPA, PIPEDA, etc.) - -4. **Enterprise Features:** - - Team collaboration (roles, permissions) - - Advanced approval workflows - - Audit logs - - White-label options - -5. **Mobile App:** - - iOS/Android app for managing entries - - Push notifications for new submissions - - Offline form viewing - ---- - -## Success Metrics - -### North Star Metric -**Active Forms:** Number of forms receiving at least 1 submission per month - -**Why:** Indicates genuine usage, not just installs - -**Target:** 100,000 active forms by end of 2026 - ---- - -### Secondary Metrics - -**Growth:** -- New installations per month: 50,000+ -- Activation rate: 60% (user creates first form within 7 days) -- Retention: 80% still active after 30 days - -**Engagement:** -- Forms created per user: 3.5 average -- Submissions per form: 25/month average -- Feature adoption (Pro): 40% use conditional logic - -**Conversion:** -- Free → Pro conversion: 5-7% -- Trial → Paid: 25% -- Annual renewal rate: 85% - -**Quality:** -- Support ticket volume: < 2% of active users -- Bug reports: < 0.5% of installations -- 4.5+ star rating on WordPress.org - -**Performance:** -- Average page load impact: < 40KB -- Time to first form submission: < 5 minutes (from install) -- Support response time: < 12 hours - ---- - -## User Feedback Integration - -### How We Listen - -**1. Support Tickets** -- Every ticket tagged by topic -- Monthly review of common issues -- Feature requests tracked in GitHub - -**2. User Surveys** -- Annual user satisfaction survey -- Post-purchase survey (Pro users) -- Exit survey (churned Pro users) - -**3. Analytics** -- Feature usage tracking (opt-in) -- Error logging (anonymized) -- Performance metrics - -**4. Community** -- Facebook group discussions -- GitHub issues and discussions -- WordPress.org support forum - -**5. Direct Outreach** -- User interviews (quarterly) -- Beta tester program -- Power user advisory board - ---- - -### Decision Framework - -**Feature Requests Evaluation:** - -**Criteria:** -1. **Impact:** How many users need this? (1-10) -2. **Effort:** Development complexity? (1-10) -3. **Strategic Fit:** Aligns with vision? (Yes/No) -4. **Competitive:** Do competitors have it? (Yes/No) -5. **Revenue:** Drives conversions? (Yes/No) - -**Scoring:** -- Impact / Effort = Priority score -- Strategic Fit = multiplier (2x if yes) -- Build if score > 5 - -**Example:** -- Feature: "Drag & drop file upload" -- Impact: 8 (many requests) -- Effort: 4 (moderate complexity) -- Strategic Fit: Yes (better UX) -- Score: (8/4) × 2 = 4 → **Build later** - ---- - -## Brand & Voice - -### Brand Personality - -**Adjectives:** -- Approachable (not intimidating) -- Modern (not trendy) -- Reliable (not boring) -- Empowering (not condescending) - -**Tone:** -- Friendly but professional -- Clear over clever -- Helpful without being pushy -- Honest about limitations - ---- - -### Writing Guidelines - -**Do:** -- Use "you" (conversational) -- Short sentences -- Active voice -- Explain "why" not just "how" - -**Don't:** -- Jargon without explanation -- Marketing fluff ("revolutionary", "game-changing") -- Passive voice ("the form was created") -- Unnecessary exclamation marks!!!! - -**Examples:** - -❌ Bad: "SureForms revolutionizes form building with cutting-edge AI technology!" - -✅ Good: "SureForms uses AI to create forms in seconds. Describe what you need, and we'll build it." - ---- - -❌ Bad: "The form submission process has been optimized for maximum conversion potential." - -✅ Good: "We designed our forms to load fast and look great on mobile, so more people complete them." - ---- - -## Technical Vision - -### Architecture Goals - -**1. Performance** -- Custom database tables (not post meta) -- Lazy loading for non-critical assets -- Query optimization (no N+1) -- CDN-friendly (static assets versioned) - -**2. Scalability** -- Handle 100,000+ submissions per form -- Efficient database queries (indexed columns) -- Background processing for heavy tasks (webhooks, PDFs) -- Caching strategy (transients, object cache) - -**3. Security** -- Input sanitization (all user data) -- Output escaping (all rendered content) -- Nonce verification (all AJAX/REST) -- SQL injection prevention (prepared statements) -- Regular security audits - -**4. Extensibility** -- 50+ hooks (actions and filters) -- Clean, documented APIs -- Developer-friendly codebase -- Backward compatibility promise - -**5. Maintainability** -- WordPress coding standards (PHP_CodeSniffer) -- ESLint for JavaScript -- Automated testing (PHPUnit, Playwright) -- Clear documentation (inline and external) - ---- - -## Values & Principles - -**1. User Privacy** -- No tracking without consent -- GDPR compliance built-in -- Data portability (easy export) -- Clear privacy policy - -**2. Open Source (Free Plugin)** -- Public GitHub repository -- Accept community contributions -- Transparent roadmap -- Active maintenance - -**3. Quality Over Speed** -- Test thoroughly before release -- Fix bugs before adding features -- Code review required for all changes -- No "move fast and break things" - -**4. Accessibility** -- WCAG 2.1 Level AA minimum -- Keyboard navigation always -- Screen reader testing required -- Color contrast compliance - -**5. Sustainability** -- Reasonable pricing (not subscription trap) -- Long-term support commitment -- No vendor lock-in (data always exportable) -- Transparent upgrade policies - ---- - -## What We Won't Do - -**Out of Scope:** - -**1. Full CRM System** -- Why: Bloat, complexity, competing with specialists -- Alternative: Integrate with existing CRMs (HubSpot, Salesforce) - -**2. Email Marketing Platform** -- Why: Already solved by Mailchimp, Brevo, etc. -- Alternative: Native integrations with 24+ email platforms - -**3. Complete E-commerce Solution** -- Why: WooCommerce exists, does it well -- Alternative: Simple payment forms (our niche) - -**4. Website Builder** -- Why: Outside core competency (forms) -- Alternative: Work seamlessly with any page builder - -**5. Survey & Quiz Builder** -- Why: Different use case, different UX requirements -- Alternative: Forms work for simple surveys - ---- - -## Conclusion - -**Core Belief:** -Forms are critical touchpoints between businesses and customers. They should be beautiful, fast, and easy to create. - -**Our Promise:** -We'll keep building tools that make form creation accessible to everyone, without sacrificing power or flexibility. - -**For Developers:** -Your feedback shapes this product. Keep the issues and PRs coming. We're listening. - ---- - -**Next:** [UI & Copy Guidelines](ui-and-copy.md) diff --git a/internal-docs/troubleshooting.md b/internal-docs/troubleshooting.md deleted file mode 100644 index b7f256af5..000000000 --- a/internal-docs/troubleshooting.md +++ /dev/null @@ -1,1031 +0,0 @@ -# Troubleshooting Guide - -**Version:** 2.5.0 - ---- - -## Quick Diagnostic - -**Before diving deep, check these common issues:** - -```bash -# 1. WordPress & PHP versions -wp core version -php -v - -# 2. Plugin status -wp plugin list | grep sureforms - -# 3. Theme compatibility -wp theme list --status=active - -# 4. Recent errors -tail -50 wp-content/debug.log | grep -i "sureforms\|fatal\|error" - -# 5. Database tables exist -wp db query "SHOW TABLES LIKE '%sureforms%';" -``` - ---- - -## Installation & Activation Issues - -### Plugin Won't Activate - -**Symptom:** "Plugin activation failed" or white screen - -**Common Causes:** - -**1. PHP Version Too Old** -```bash -php -v -# Required: PHP 7.4+ -``` - -**Fix:** -```bash -# Update PHP (contact host if shared hosting) -# Or add to wp-config.php temporarily to see error: -define('WP_DEBUG', true); -define('WP_DEBUG_DISPLAY', true); -``` - -**2. WordPress Version Too Old** -```bash -wp core version -# Required: WordPress 6.4+ -``` - -**Fix:** -```bash -wp core update -``` - -**3. Conflicting Plugin** - -**Check for conflicts:** -```bash -# Deactivate all other plugins -wp plugin deactivate --all --exclude=sureforms,sureforms-pro - -# Try activating SureForms -wp plugin activate sureforms - -# Reactivate plugins one by one -wp plugin activate plugin-name -``` - -**Common conflicts:** -- Old caching plugins (W3 Total Cache < 2.0) -- Security plugins with aggressive rules -- Other form builders (namespace collisions) - -**4. Memory Limit Too Low** - -**Check current limit:** -```bash -wp eval 'echo WP_MEMORY_LIMIT;' -``` - -**Fix (wp-config.php):** -```php -define('WP_MEMORY_LIMIT', '256M'); -``` - ---- - -### Pro Plugin Shows "Base Plugin Required" - -**Symptom:** SureForms Pro won't activate - -**Diagnosis:** -```bash -wp plugin list | grep sureforms -# Ensure both sureforms AND sureforms-pro are installed -``` - -**Fix:** -1. Install SureForms Free first -2. Activate SureForms Free -3. Then activate SureForms Pro - -**Check version compatibility:** -```bash -# Free and Pro versions should match -# Both should be 2.5.0 (or same major.minor) -``` - ---- - -### Database Tables Not Created - -**Symptom:** Form submissions fail, entries not showing - -**Check tables exist:** -```bash -wp db query "SHOW TABLES LIKE '%sureforms%';" -``` - -**Expected output:** -``` -wp_sureforms_entries -wp_sureforms_payments -wp_sureforms_integrations -wp_sureforms_save_resume -``` - -**Fix: Force database creation** -```bash -# Deactivate plugin -wp plugin deactivate sureforms sureforms-pro - -# Delete plugin (backup first!) -wp plugin delete sureforms - -# Reinstall -wp plugin install sureforms --activate - -# Tables should be created on activation -``` - -**Manual creation (last resort):** - -Read schema from: -- `inc/database/tables/entries.php` -- `inc/database/tables/payments.php` - -Run CREATE TABLE statements manually. - ---- - -## Form Builder (Editor) Issues - -### Forms Won't Load in Editor - -**Symptom:** Blank screen or infinite loading spinner - -**Diagnosis:** - -**1. Check browser console:** -``` -Open DevTools → Console -Look for JavaScript errors -``` - -**Common errors:** - -**a) "wp.blockEditor is undefined"** -- **Cause:** Outdated WordPress version -- **Fix:** `wp core update` - -**b) "React version mismatch"** -- **Cause:** Another plugin using old React version -- **Fix:** Deactivate other plugins one by one to find conflict - -**c) "Uncaught SyntaxError"** -- **Cause:** JavaScript file corrupted or not minified correctly -- **Fix:** Rebuild assets: `npm run build` in plugin directory - -**2. Check network tab:** -``` -DevTools → Network -Filter: JS -Look for failed requests (red, 404, 500) -``` - -**Common failures:** -- `form-editor.js` (404) → Rebuild assets -- `chunk-vendors.js` (500) → Server misconfiguration - -**3. Enable SCRIPT_DEBUG:** - -In `wp-config.php`: -```php -define('SCRIPT_DEBUG', true); -``` - -This loads unminified JS, easier to debug. - ---- - -### Blocks Not Appearing in Inserter - -**Symptom:** SureForms blocks missing from block library - -**Diagnosis:** -```bash -# Check if blocks are registered -wp eval "print_r(get_option('srfm_blocks_registration'));" -``` - -**Fixes:** - -**1. Re-register blocks:** -```bash -wp plugin deactivate sureforms -wp plugin activate sureforms -``` - -**2. Clear block cache:** -```bash -wp cache flush -wp transient delete --all -``` - -**3. Check block.json files exist:** -```bash -find wp-content/plugins/sureforms/inc/blocks -name "block.json" -# Should return multiple files -``` - ---- - -### Form Preview Shows "Invalid Block" - -**Symptom:** Form renders as "This block contains unexpected or invalid content" - -**Cause:** Block HTML structure changed, saved form has old structure - -**Fix:** - -**Option 1: Update block (preserves data):** -1. Click "Attempt Block Recovery" -2. Verify content looks correct -3. Update form - -**Option 2: Clear and rebuild:** -1. Delete the invalid block -2. Add fresh block -3. Reconfigure settings - -**Prevention:** -- Always test after plugin updates -- Keep staging environment for testing - ---- - -## Form Submission Issues - -### Form Submissions Not Saving - -**Symptom:** User submits form, success message shows, but no entry in database - -**Diagnosis:** - -**1. Check debug log:** -```bash -tail -f wp-content/debug.log -# Submit form while watching log -``` - -**Common errors:** -- "Database insert failed" → Check table permissions -- "Nonce verification failed" → Caching issue (see below) -- "Call to undefined method" → PHP version or missing dependency - -**2. Check database directly:** -```bash -# Get latest entry -wp db query "SELECT * FROM wp_sureforms_entries ORDER BY id DESC LIMIT 1;" -``` - -**3. Test with minimal form:** -Create form with ONLY: -- Email field -- Submit button - -If this works, issue is with specific field type. - -**Fixes:** - -**Database Permissions:** -```bash -# Check MySQL user has INSERT privilege -wp db query "SHOW GRANTS;" -``` - -**Fix permissions (MySQL):** -```sql -GRANT INSERT, UPDATE, DELETE ON database_name.* TO 'wp_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -**Caching Interference:** - -Caching plugins cache nonce values, causing verification failures. - -**Fix:** -1. Exclude `/wp-admin/admin-ajax.php` from cache -2. Exclude REST API `/wp-json/` from cache -3. Or disable caching temporarily to test - ---- - -### Email Notifications Not Sending - -**Symptom:** Form submitted successfully, but no email received - -**Diagnosis:** - -**1. Check if emails are being sent at all:** -```bash -# Install WP Mail SMTP or similar -wp plugin install wp-mail-smtp --activate - -# Or test with simple command: -wp eval "wp_mail('your@email.com', 'Test', 'Testing SureForms');" -``` - -**2. Check SureForms email settings:** -```bash -# View form meta -wp post meta list | grep email -``` - -**3. Check spam folder** -- WordPress default `from` address: `wordpress@yourdomain.com` -- Often flagged as spam - -**Fixes:** - -**Configure SMTP:** - -Install WP Mail SMTP plugin: -```bash -wp plugin install wp-mail-smtp --activate -``` - -Configure with: -- Gmail -- SendGrid -- Mailgun -- Amazon SES - -**Check email template:** - -In form settings → Email Notification: -- Verify "To" address is correct -- Check "From" address is valid domain -- Test with simple subject/message - -**Server mail() function:** - -Some hosts disable PHP `mail()` function. - -Test: -```bash -php -r "mail('test@example.com', 'Test', 'Test message');" -``` - -If no email received, `mail()` is disabled. Use SMTP. - ---- - -### Form Validation Not Working - -**Symptom:** Form submits even with empty required fields - -**Causes:** - -**1. JavaScript disabled** (frontend validation skipped) -**2. Theme CSS hiding error messages** -**3. Custom JavaScript conflict** - -**Diagnosis:** - -**Check browser console:** -``` -Look for JavaScript errors -Check if srfm-validation.js loaded -``` - -**Test with default theme:** -```bash -wp theme activate twentytwentythree -# Submit form again -``` - -If works with default theme → Theme conflict. - -**Fixes:** - -**Theme conflict:** - -Add to theme's `functions.php`: -```php -add_action('wp_enqueue_scripts', function() { - // Ensure SureForms scripts load - wp_enqueue_script('srfm-frontend'); -}, 20); -``` - -**CSS hiding errors:** - -Check if theme has: -```css -.srfm-error { display: none !important; } -``` - -Remove or override. - ---- - -## Payment Processing Issues - -### Stripe Payments Failing - -**Symptom:** "Payment failed" error after entering card details - -**Diagnosis:** - -**1. Check Stripe API keys:** -```bash -# In WordPress admin: SureForms → Settings → Payments → Stripe -# Verify: -# - Using correct keys (test vs live) -# - Keys match Stripe dashboard -``` - -**2. Check Stripe webhook:** -```bash -# In Stripe Dashboard → Developers → Webhooks -# Verify webhook URL is: -https://yoursite.com/wp-json/sureforms/webhook_test - -# Check recent webhook deliveries for errors -``` - -**3. Check browser console:** -``` -DevTools → Console -Look for Stripe.js errors -``` - -**Common Errors:** - -**"Invalid API Key"** -- Using test key in live mode (or vice versa) -- API key revoked in Stripe dashboard -- Fix: Copy fresh keys from Stripe - -**"Payment Intent creation failed"** -- Amount is $0 or negative -- Currency mismatch -- Fix: Check form configuration, amount field - -**"Webhook signature verification failed"** -- Webhook secret incorrect -- Middleware issue -- Fix: Copy webhook signing secret from Stripe, update settings - -**Fixes:** - -**Test mode checklist:** -```bash -# 1. Use test API keys (starts with pk_test_ / sk_test_) -# 2. Test card: 4242 4242 4242 4242 -# 3. Any future expiry date -# 4. Any 3-digit CVC -``` - -**Live mode checklist:** -```bash -# 1. Use live API keys (starts with pk_live_ / sk_live_) -# 2. SSL certificate valid (https://) -# 3. Webhook verified in Stripe dashboard -# 4. Test with real card (refund immediately) -``` - ---- - -### PayPal Payments Failing (Pro) - -**Symptom:** Redirected to PayPal but payment doesn't process - -**Diagnosis:** - -**Check PayPal credentials:** -```bash -# SureForms → Settings → Payments → PayPal -# Verify: -# - Client ID matches PayPal dashboard -# - Secret matches -# - Using sandbox for testing, live for production -``` - -**Check webhook endpoint:** -```bash -# PayPal Dashboard → Apps & Credentials → Webhooks -# Webhook URL should be: -https://yoursite.com/wp-json/sureforms-pro/paypal-live-webhook -``` - -**Fixes:** - -**Test in sandbox mode first:** -1. Create PayPal sandbox account: https://developer.paypal.com/ -2. Use sandbox credentials in SureForms -3. Test with sandbox buyer account - -**Common issues:** -- **Wrong environment:** Using sandbox credentials in live mode -- **Webhook not subscribed:** Must subscribe to payment events in PayPal dashboard -- **SSL certificate:** PayPal requires valid HTTPS - ---- - -## Integration Issues (Pro) - -### Native Integration Not Connecting - -**Symptom:** "Connection failed" when adding integration (Mailchimp, HubSpot, etc.) - -**Diagnosis:** - -**1. Check OAuth redirect URL:** - -For OAuth integrations (Mailchimp, HubSpot, Salesforce): -``` -Redirect URL must be: -https://yoursite.com/wp-json/sureforms-pro/v1/oauth/callback -``` - -**2. Test API credentials:** - -For API key integrations (Brevo, etc.): -```bash -# Test API key directly -curl -X GET "https://api.brevo.com/v3/account" \ - -H "api-key: YOUR_API_KEY" -``` - -Should return account info, not error. - -**3. Check error logs:** -```bash -tail -f wp-content/debug.log | grep -i "integration\|oauth" -``` - -**Fixes:** - -**OAuth issues:** -- Ensure site uses HTTPS (required for OAuth) -- Whitelist redirect URL in service's developer console -- Check OAuth app has correct permissions/scopes - -**API key issues:** -- Regenerate key in service dashboard -- Copy entire key (no spaces, no quotes) -- Check key has required permissions (read/write) - ---- - -### Webhook Not Firing - -**Symptom:** Form submitted, but data not sent to integrated service - -**Diagnosis:** - -**Check webhook logs:** - -Install Query Monitor plugin: -```bash -wp plugin install query-monitor --activate -``` - -Submit form, check "HTTP API Calls" panel for webhook requests. - -**Manually trigger webhook:** -```bash -# Find integration ID -wp db query "SELECT * FROM wp_sureforms_integrations;" - -# Trigger webhook manually (developer test) -wp eval "do_action('sureforms_after_entry_save', 123, 456, []);" -``` - -**Fixes:** - -**Webhook URL validation:** - -In `inc/pro/integrations/webhooks.php`, ensure URL is valid: -- HTTPS only (no HTTP) -- No localhost (unless testing) -- Responds with 200 OK - -**Timeout issues:** - -Increase timeout in `wp-config.php`: -```php -define('WP_HTTP_BLOCK_EXTERNAL', false); -define('WP_ACCESSIBLE_HOSTS', 'api.mailchimp.com,api.hubspot.com'); -``` - ---- - -## Performance Issues - -### Form Editor Slow to Load - -**Symptom:** Takes 10+ seconds to load form in editor - -**Diagnosis:** - -**1. Check database size:** -```bash -wp db query "SELECT COUNT(*) FROM wp_sureforms_entries;" -# If > 100,000 entries, database may be slow -``` - -**2. Check server resources:** -```bash -# Memory usage -free -h - -# CPU usage -top - -# Disk I/O -iostat -``` - -**3. Profile with Query Monitor:** -```bash -wp plugin install query-monitor --activate -# Open form editor, check QM panel for slow queries -``` - -**Fixes:** - -**Optimize database:** -```bash -# Clean old entries (backup first!) -wp db query "DELETE FROM wp_sureforms_entries WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);" - -# Optimize tables -wp db optimize -``` - -**Increase PHP limits:** - -In `php.ini` or `.htaccess`: -``` -max_execution_time = 300 -memory_limit = 256M -``` - -**Enable object caching:** - -Install Redis or Memcached: -```bash -wp plugin install redis-cache --activate -wp redis enable -``` - ---- - -### Frontend Form Loads Slowly - -**Symptom:** Form takes 5+ seconds to appear on page - -**Diagnosis:** - -**Check asset loading:** -``` -DevTools → Network → Slow 3G simulation -Watch which assets are slow -``` - -**Common bottlenecks:** -- Google Fonts loading slowly -- Large CSS/JS files -- Unoptimized images in form - -**Fixes:** - -**Lazy load non-critical assets:** - -In `functions.php`: -```php -add_filter('script_loader_tag', function($tag, $handle) { - if ($handle === 'srfm-frontend') { - return str_replace(' src', ' defer src', $tag); - } - return $tag; -}, 10, 2); -``` - -**Use CDN for Google Fonts:** - -In form settings → Design → Typography: -- Limit to 1-2 font families -- Use system fonts for faster load (e.g., -apple-system) - -**Minify and combine assets:** -```bash -npm run build # Ensures assets are minified -``` - -**Enable caching:** - -Install caching plugin: -```bash -wp plugin install wp-super-cache --activate -``` - -Configure to cache pages with forms. - ---- - -## Block Compatibility Issues - -### Form Breaks After Theme Update - -**Symptom:** Form displays incorrectly or not at all after theme update - -**Diagnosis:** - -**Compare theme CSS:** - -Check if new theme has conflicting styles: -```css -/* Common conflicts */ -.srfm-form input { /* Theme override */ } -.srfm-field { /* Theme override */ } -``` - -**Test with default theme:** -```bash -wp theme activate twentytwentythree -``` - -If works → Theme issue. - -**Fixes:** - -**Add theme compatibility CSS:** - -In child theme `style.css`: -```css -/* Reset SureForms blocks */ -.srfm-form, -.srfm-field, -.srfm-form input, -.srfm-form textarea { - all: revert; -} - -/* Then apply minimal SureForms styles */ -``` - -**Use !important (last resort):** - -In SureForms settings → Custom CSS: -```css -.srfm-form input { - border: 1px solid #ccc !important; - padding: 10px !important; -} -``` - ---- - -### Conflicts with Page Builders - -**Symptom:** Forms don't work inside Elementor/Divi/Beaver Builder - -**Common Issues:** - -**1. JavaScript conflicts:** -- Page builder loads own jQuery version -- Conflicts with SureForms scripts - -**Fix:** -```php -// In functions.php -add_action('wp_enqueue_scripts', function() { - if (class_exists('Elementor\Plugin')) { - wp_dequeue_script('jquery'); - wp_enqueue_script('jquery'); - } -}, 100); -``` - -**2. CSS specificity:** -- Page builder CSS overrides SureForms - -**Fix:** - -Use SureForms settings → Custom CSS with higher specificity: -```css -.elementor-widget-container .srfm-form input { - /* Your styles */ -} -``` - ---- - -## Database & Query Issues - -### "Too many connections" Error - -**Symptom:** Site crashes during high form submission volume - -**Diagnosis:** -```bash -wp db query "SHOW STATUS LIKE 'max_used_connections';" -wp db query "SHOW VARIABLES LIKE 'max_connections';" -``` - -**Fixes:** - -**Increase max connections (MySQL):** - -In `my.cnf`: -``` -[mysqld] -max_connections = 500 -``` - -**Use persistent connections:** - -In `wp-config.php`: -```php -define('DB_CHARSET', 'utf8mb4'); -define('DB_COLLATE', ''); -define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_PERSISTENT); -``` - -**Add connection pooling:** - -Use ProxySQL or PgBouncer for connection pooling. - ---- - -### Slow Query: "SELECT * FROM wp_sureforms_entries" - -**Symptom:** Admin page very slow when viewing entries - -**Diagnosis:** -```bash -wp plugin install query-monitor --activate -# View entries page, check QM for slow queries -``` - -**Fixes:** - -**Add database indexes:** -```sql -ALTER TABLE wp_sureforms_entries -ADD INDEX idx_form_id (form_id), -ADD INDEX idx_created_at (created_at), -ADD INDEX idx_status (status); -``` - -**Limit entries displayed:** - -In admin, reduce entries per page from 100 to 20. - -**Paginate large result sets:** - -Ensure code uses `LIMIT` and `OFFSET`: -```php -$entries = Entries::get_all([ - 'limit' => 20, - 'offset' => ($page - 1) * 20 -]); -``` - ---- - -## WordPress.org Review Issues - -### Plugin Rejected for Security - -**Common reasons:** - -**1. Direct database calls without prepare():** -```php -// ❌ Bad -$wpdb->query("DELETE FROM table WHERE id = $id"); - -// ✅ Good -$wpdb->query($wpdb->prepare("DELETE FROM table WHERE id = %d", $id)); -``` - -**2. Unsanitized user input:** -```php -// ❌ Bad -echo $_POST['name']; - -// ✅ Good -echo esc_html(sanitize_text_field($_POST['name'] ?? '')); -``` - -**3. Missing nonce verification:** -```php -// ❌ Bad -if (isset($_POST['action'])) { do_action(); } - -// ✅ Good -if (isset($_POST['action']) && check_ajax_referer('my_action_nonce')) { - do_action(); -} -``` - -**Fix:** - -Run WordPress Coding Standards checker: -```bash -composer require --dev wp-coding-standards/wpcs -vendor/bin/phpcs --standard=WordPress inc/ -``` - -Fix all errors before resubmitting. - ---- - -## Getting Help - -### Before Asking for Help - -**Gather this information:** - -```bash -# 1. WordPress & PHP versions -wp core version -php -v - -# 2. Plugin version -wp plugin list | grep sureforms - -# 3. Active theme -wp theme list --status=active - -# 4. Other active plugins -wp plugin list --status=active - -# 5. Recent errors -tail -50 wp-content/debug.log - -# 6. Browser/OS (if frontend issue) -# Example: Chrome 120 on macOS 14 -``` - -### Where to Get Help - -**1. Documentation** (check first) -- This troubleshooting guide -- [FAQ](faq.md) -- [Architecture](architecture.md) - -**2. GitHub Issues** (bugs & feature requests) -- Search existing issues first -- Provide minimal reproduction steps -- Include system info from above - -**3. Support Portal** (Pro users) -- https://support.brainstormforce.com/ -- < 24hr response time for Pro users - -**4. Community** -- Facebook group: https://www.facebook.com/groups/surecart -- WordPress.org forum (Free only) - ---- - -## Still Stuck? - -If none of the above solutions work: - -**Create detailed bug report:** - -```markdown -**Environment:** -- WordPress: 6.4.2 -- PHP: 8.1 -- SureForms: 2.5.0 (Free/Pro) -- Theme: Astra 4.5.0 -- Browser: Chrome 120 - -**Steps to Reproduce:** -1. Create form with email field -2. Mark field as required -3. Submit form with empty email -4. [Describe unexpected behavior] - -**Expected:** Validation error shows -**Actual:** Form submits anyway - -**Debug Log:** -[Paste relevant errors from debug.log] - -**Screenshots:** -[Attach if visual issue] -``` - -Submit to: https://github.com/brainstormforce/sureforms/issues - ---- - -**Next:** [FAQ](faq.md) diff --git a/internal-docs/ui-and-copy.md b/internal-docs/ui-and-copy.md deleted file mode 100644 index e12a9a45a..000000000 --- a/internal-docs/ui-and-copy.md +++ /dev/null @@ -1,1077 +0,0 @@ -# UI & Copy Guidelines - -**Version:** 2.5.0 - ---- - -## User Experience Philosophy - -**Principle:** Every word and interaction should make the user feel capable, not confused. - -**User Mindset:** -- "I just want to create a form quickly" -- "I don't have time to read documentation" -- "I'm not a developer" - -**Our Responsibility:** -- Make obvious what to do next -- Explain why, not just how -- Never blame the user - ---- - -## User Journeys - -### Journey 1: First-Time User Creates Contact Form - -**Goal:** Create and publish first form in < 5 minutes - -**Steps:** - -**1. Plugin Activation (30 seconds)** - -``` -User activates plugin - ↓ -Redirect to welcome screen - ↓ -Show 2 options: - [Create with AI] [Start from Blank] -``` - -**Copy:** -``` -Welcome to SureForms! - -Let's create your first form. - -[Create with AI] ← Recommended for beginners - Let AI build your form from a simple description - -[Start from Blank] - Build your form block-by-block -``` - -**Design notes:** -- Large, friendly buttons -- "Recommended" badge on AI option -- No walls of text - ---- - -**2. AI Form Creation (2 minutes)** - -``` -User clicks "Create with AI" - ↓ -Modal appears with text input - ↓ -User types: "simple contact form" - ↓ -AI generates form (3-5 seconds) - ↓ -Form opens in editor, ready to publish -``` - -**Copy:** - -``` -Create Form with AI - -Describe the form you need: -┌─────────────────────────────────────┐ -│ Example: "job application with │ -│ file upload" or "event RSVP" │ -└─────────────────────────────────────┘ - -[Cancel] [Generate Form →] -``` - -**Loading state:** -``` -✨ Creating your form... - -AI is adding fields based on your description. -This usually takes 3-5 seconds. -``` - -**Success state:** -``` -✅ Your form is ready! - -We added: -• Name field -• Email field -• Message field -• Submit button - -You can add, remove, or rearrange fields below. - -[Publish Form] -``` - -**Design notes:** -- Loading spinner + encouraging message -- Success message lists what was created -- Clear next action (Publish) - ---- - -**3. Form Publishing (1 minute)** - -``` -User clicks "Publish" - ↓ -Gutenberg publish panel opens - ↓ -SureForms shows additional options -``` - -**Copy in publish panel:** - -``` -📋 Form Settings - -Where should submissions be sent? - -Email: [admin@yoursite.com ▼] - -After submission, show: -○ Confirmation message -○ Redirect to page -● Confirmation message (selected by default) - -Message: -┌─────────────────────────────────────┐ -│ Thank you! We'll respond within │ -│ 24 hours. │ -└─────────────────────────────────────┘ - -[Publish Form] -``` - -**Design notes:** -- Sensible defaults (confirmation message, admin email) -- Inline editing (no separate settings page) -- Jargon-free labels - ---- - -**4. Embedding Form (30 seconds)** - -``` -User wants to add form to page - ↓ -Edit page in Gutenberg - ↓ -Add SureForms block - ↓ -Select form from dropdown -``` - -**Copy in block inserter:** - -``` -SureForms - -Display a form on your page. - -📝 No form yet? Create one in SureForms > Add New -``` - -**Copy in block settings:** - -``` -Form - -Select a form: -[Contact Form ▼] [Create New] - -Display settings: -☑ Show form title -☑ Show form description -☐ Hide labels (show placeholders only) -``` - -**Design notes:** -- Help text guides to form creation if needed -- Checkbox labels explain what they do -- Preview updates in real-time - ---- - -### Journey 2: Pro User Sets Up Payment Form - -**Goal:** Create payment form and collect first payment - -**Complexity:** Higher (involves Stripe setup) - -**Steps:** - -**1. Payment Gateway Setup (5 minutes)** - -``` -User navigates to: -SureForms → Settings → Payments → Stripe -``` - -**Copy:** - -``` -Stripe Payments - -Accept credit card payments with Stripe. - -🔒 Secure: We never store card details. Payments are processed directly by Stripe. - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Test Mode (for testing) - -☑ Enable test mode - -Use these while testing: - Test card: 4242 4242 4242 4242 - Any future expiry, any CVC - -Publishable Key (starts with pk_test_) -┌─────────────────────────────────────┐ -│ │ -└─────────────────────────────────────┘ - -Secret Key (starts with sk_test_) -┌─────────────────────────────────────┐ -│ •••••••••••••••••••••••••••••••• │ -└─────────────────────────────────────┘ - -Where to get keys: -→ Stripe Dashboard > Developers > API Keys - https://dashboard.stripe.com/apikeys - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Live Mode (for real payments) - -⚠️ Only use after testing - -Publishable Key (starts with pk_live_) -┌─────────────────────────────────────┐ -│ │ -└─────────────────────────────────────┘ - -Secret Key (starts with sk_live_) -┌─────────────────────────────────────┐ -│ │ -└─────────────────────────────────────┘ - -[Save Settings] -``` - -**Design notes:** -- Test mode emphasized first -- Clear instructions where to get keys -- Security reassurance ("We never store") -- Direct link to Stripe dashboard - ---- - -**2. Creating Payment Form (3 minutes)** - -``` -User creates new form - ↓ -Adds "Payment" block - ↓ -Configures amount and currency -``` - -**Copy in Payment block settings:** - -``` -Payment Details - -Amount -○ Fixed amount - ┌──────┐ - │ 50 │ USD ▼ - └──────┘ - -● Let user choose amount - Min: [10] Max: [1000] USD ▼ - Default: [50] - -Payment Type -● One-time payment -○ Subscription - Interval: [Monthly ▼] - -Button Text -┌─────────────────────────────────────┐ -│ Pay Now │ -└─────────────────────────────────────┘ -``` - -**Design notes:** -- Radio buttons for mutually exclusive options -- Currency dropdown next to amount -- Clear labels (no "recurring" jargon, use "subscription") - ---- - -**3. Testing Payment (2 minutes)** - -**Copy on frontend form:** - -``` -Payment Information - -Amount: $50.00 - -Card Number -┌─────────────────────────────────────┐ -│ 1234 5678 9012 3456 │ [Card icon] -└─────────────────────────────────────┘ - -Expiry CVC -┌──────────┐ ┌─────┐ -│ MM / YY │ │ 123 │ -└──────────┘ └─────┘ - -🔒 Secure payment powered by Stripe - Your card details are encrypted and never stored. - -[Pay $50.00] -``` - -**Success message after payment:** - -``` -✅ Payment Successful! - -Receipt sent to: john@example.com - -Transaction ID: ch_1A2B3C4D5E6F - -[View Receipt] [Back to Home] -``` - -**Error message if payment fails:** - -``` -❌ Payment Failed - -Your card was declined. - -Common reasons: -• Insufficient funds -• Incorrect card number or expiry date -• Card requires 3D Secure verification - -Please try again or use a different card. - -[Try Again] -``` - -**Design notes:** -- Security reassurance prominent -- Success message includes receipt email and transaction ID -- Error message explains why and how to fix - ---- - -### Journey 3: Power User Builds Conditional Logic Form (Pro) - -**Goal:** Show/hide fields based on user selection - -**Example:** Event RSVP with dietary restrictions (only show if attending) - -**Steps:** - -**1. Creating Base Form (2 minutes)** - -``` -User adds fields: - - "Will you attend?" (Multiple Choice: Yes/No) - - "Dietary restrictions" (Dropdown) -``` - -**2. Adding Conditional Logic (3 minutes)** - -``` -User clicks "Dietary restrictions" block - ↓ -Opens block settings panel - ↓ -Enables conditional logic -``` - -**Copy in block settings:** - -``` -Conditional Logic - -Show this field only when certain conditions are met. - -☑ Enable conditional logic - -Show this field when: - -[Will you attend? ▼] [is ▼] [Yes ▼] - -[+ Add Condition] - -Logic: -● Show if ALL conditions match (AND) -○ Show if ANY condition matches (OR) -``` - -**Design notes:** -- Toggle to enable -- Dropdown-based condition builder (no code) -- Visual feedback: field grays out in editor preview when hidden - ---- - -**3. Testing Logic (1 minute)** - -**Copy in editor preview mode:** - -``` -💡 Preview Mode - -This is how your form will appear to users. - -Try selecting different options to see conditional logic in action. - -[Exit Preview] -``` - -**Behavior:** -- User selects "Yes" → Dietary field appears -- User selects "No" → Dietary field disappears - -**Design notes:** -- Clear indication of preview mode -- Real-time updates (no reload) - ---- - -## Microcopy Guidelines - -### Field Labels - -**Be concise and conversational** - -❌ Bad: "Please enter your electronic mail address" -✅ Good: "Email" - -❌ Bad: "Input telephone number (optional)" -✅ Good: "Phone (optional)" - -**Use sentence case, not title case** - -❌ Bad: "First Name" -✅ Good: "First name" - -❌ Bad: "Company Name (If Applicable)" -✅ Good: "Company name (if applicable)" - ---- - -### Help Text - -**Explain why or provide examples** - -**Email field:** -❌ Bad: "Enter email" -✅ Good: "We'll send a confirmation to this email" - -**Phone field:** -❌ Bad: "Phone number" -✅ Good: "We'll only call if there's an issue with your order" - -**File upload:** -❌ Bad: "Upload file" -✅ Good: "Upload your resume (PDF or DOCX, max 10MB)" - ---- - -### Error Messages - -**Be specific and actionable** - -**Email validation:** -❌ Bad: "Invalid email" -✅ Good: "Please enter a valid email (e.g., name@example.com)" - -**Required field:** -❌ Bad: "This field is required" -✅ Good: "Please enter your name" - -**File size:** -❌ Bad: "File too large" -✅ Good: "File must be under 10MB. Yours is 15MB. Try compressing it." - -**Payment failed:** -❌ Bad: "Transaction error" -✅ Good: "Your card was declined. Please check your card details or try a different card." - ---- - -### Success Messages - -**Be enthusiastic but not over-the-top** - -**Form submission:** -❌ Bad: "Form submitted" -✅ Good: "Thanks! We'll get back to you within 24 hours." - -**Payment successful:** -❌ Bad: "Payment processed successfully. Transaction ID: 1234." -✅ Good: "✅ Payment received! Receipt sent to your email." - -**Registration:** -❌ Bad: "Account created. Please log in." -✅ Good: "Welcome! Check your email to verify your account." - ---- - -### Button Labels - -**Use verbs that describe the action** - -❌ Bad: "Submit" -✅ Good: "Send Message" - -❌ Bad: "Click Here" -✅ Good: "Download Receipt" - -❌ Bad: "Proceed" -✅ Good: "Continue to Payment" - -**For payment buttons, include amount** - -❌ Bad: "Pay" -✅ Good: "Pay $50.00" - -❌ Bad: "Subscribe" -✅ Good: "Subscribe for $9.99/month" - ---- - -### Settings & Options - -**Explain consequences, not just features** - -**Email notification:** -❌ Bad: "Send email notification" -✅ Good: "Email me when someone submits this form" - -**Required field:** -❌ Bad: "Required" -✅ Good: "Make this field required" (checkbox label) - -**Conditional logic:** -❌ Bad: "Enable conditional logic" -✅ Good: "Show or hide this field based on other answers" - ---- - -## Visual Design Patterns - -### Empty States - -**When user has no forms yet:** - -``` -┌─────────────────────────────────────────┐ -│ │ -│ 📝 │ -│ │ -│ No forms yet │ -│ │ -│ Forms help you collect information │ -│ from your website visitors. │ -│ │ -│ [Create Your First Form] │ -│ │ -└─────────────────────────────────────────┘ -``` - -**Copy principles:** -- Icon relevant to context -- 1-sentence explanation -- Clear call-to-action button - ---- - -**When form has no submissions:** - -``` -┌─────────────────────────────────────────┐ -│ │ -│ 📭 │ -│ │ -│ No submissions yet │ -│ │ -│ Share this form to start collecting │ -│ responses. │ -│ │ -│ [Copy Form Link] [Embed on Page] │ -│ │ -└─────────────────────────────────────────┘ -``` - -**Design notes:** -- Actionable next steps -- Multiple options (link vs embed) - ---- - -### Loading States - -**Form submission in progress:** - -``` -┌─────────────────────────────────────┐ -│ ⏳ Sending... │ -│ │ -│ Please don't close this page. │ -└─────────────────────────────────────┘ -``` - -**AI form generation:** - -``` -┌─────────────────────────────────────┐ -│ ✨ Creating your form... │ -│ │ -│ [████████░░] 80% │ -│ │ -│ Adding email validation... │ -└─────────────────────────────────────┘ -``` - -**Design notes:** -- Spinner or progress indicator -- Explain what's happening -- Show progress if possible - ---- - -### Confirmation Dialogs - -**Deleting a form:** - -``` -┌─────────────────────────────────────┐ -│ Delete "Contact Form"? │ -│ │ -│ This will permanently delete: │ -│ • The form │ -│ • 47 submissions │ -│ • All settings │ -│ │ -│ This cannot be undone. │ -│ │ -│ [Cancel] [Delete Form] │ -└─────────────────────────────────────┘ -``` - -**Design notes:** -- Specific about what will be deleted -- Use red/destructive style for delete button -- Cancel button should be default (easier to accidentally click) - ---- - -### Tooltips & Hints - -**When to use:** -- Explaining technical terms -- Providing examples -- Showing keyboard shortcuts - -**Format:** - -``` -Field Name [?] - ↓ (on hover) -┌─────────────────────────────────────┐ -│ This is the internal name used in │ -│ the database. │ -│ │ -│ Example: "first_name" │ -└─────────────────────────────────────┘ -``` - -**Guidelines:** -- Keep under 2 sentences -- Provide example if helpful -- Don't repeat the label - ---- - -## Accessibility - -### Screen Reader Text - -**Form structure:** - -```html -
    -
    -

    Personal Information

    - -
    -
    -``` - -**Error announcements:** - -```html -
    - Please fix 2 errors before submitting. -
    - - - - Please enter a valid email address - -``` - ---- - -### Keyboard Navigation - -**Required interactions:** -- Tab through all fields -- Space to toggle checkboxes/radio buttons -- Enter to submit form -- Escape to close modals - -**Visual focus indicators:** - -```css -.srfm-field input:focus { - outline: 2px solid #0073aa; - outline-offset: 2px; -} -``` - -**Never:** -```css -:focus { - outline: none; /* ❌ Never remove focus outline */ -} -``` - ---- - -### Color Contrast - -**WCAG 2.1 Level AA Requirements:** -- Normal text: 4.5:1 minimum -- Large text (18pt+): 3:1 minimum -- UI components: 3:1 minimum - -**Test tools:** -- WebAIM Contrast Checker -- Chrome DevTools (Lighthouse) - -**Safe color combinations:** -- White text on dark backgrounds (#333 or darker) -- Dark text on light backgrounds (#F0F0F0 or lighter) -- Avoid gray text on gray backgrounds - ---- - -## Tone & Voice - -### Voice Characteristics - -**Friendly but professional** -- Use "we" and "you" -- Conversational, not formal -- Helpful, not condescending - -**Example:** -❌ Formal: "The system has detected an error in your input." -✅ Friendly: "Oops! We couldn't save that. Check for any errors above." - ---- - -**Clear over clever** -- Avoid puns and wordplay -- Be direct -- Technical accuracy over marketing fluff - -**Example:** -❌ Clever: "Houston, we have a problem!" -✅ Clear: "Something went wrong. Please try again." - ---- - -**Empowering, not blaming** -- Focus on solutions, not problems -- Use "we" for errors, "you" for successes - -**Example:** -❌ Blaming: "You entered an invalid email." -✅ Empowering: "Hmm, that email doesn't look quite right. Mind double-checking it?" - ---- - -### Writing for Different Contexts - -**First-time users:** -- More explanation -- Examples provided -- Encouraging tone - -**Example:** -``` -Welcome to SureForms! - -Creating your first form is easy. We'll walk you through each step. - -Let's start by choosing what kind of form you need. -``` - ---- - -**Power users:** -- Less explanation -- Assume knowledge -- Efficiency-focused - -**Example:** -``` -Advanced Settings - -Show field if: [condition builder] -Custom CSS class: [input] -``` - ---- - -**Error states:** -- Apologetic but not dramatic -- Specific about what went wrong -- Clear next steps - -**Example:** -``` -We couldn't save your changes. - -The connection to the server timed out. - -Please try again. If this keeps happening, check your internet connection. - -[Try Again] -``` - ---- - -**Success states:** -- Positive reinforcement -- What happens next -- Optional next action - -**Example:** -``` -✅ Form published! - -Your form is now live at: -https://yoursite.com/contact - -[View Form] [Create Another] -``` - ---- - -## Form Field Best Practices - -### Required vs Optional - -**Default:** Make fields optional - -**Require only when absolutely necessary:** -- Name (if sending personalized response) -- Email (if you need to contact them) -- Payment details (if collecting payment) - -**Label optional fields:** -``` -Phone (optional) -Company name (optional) -``` - -**Don't label required fields:** -❌ Bad: "Email (required)" -✅ Good: "Email" (with * indicator) - ---- - -### Field Order - -**Logical flow:** -1. Personal info (Name, Email) -2. Specific questions -3. Payment details (if applicable) -4. Submit button - -**Example contact form:** -``` -1. Name -2. Email -3. Subject -4. Message -5. [Submit] -``` - -**Example order form:** -``` -1. Name -2. Email -3. Product selection -4. Quantity -5. Payment details -6. [Complete Purchase] -``` - ---- - -### Placeholder Text - -**Use sparingly** - -Only for examples, not as labels: - -❌ Bad: -``` -Label: [empty] -Placeholder: "Enter your email" -``` - -✅ Good: -``` -Label: Email -Placeholder: "name@example.com" -``` - -**Never use placeholders as only label** (accessibility issue) - ---- - -### Multi-Step Forms (Pro) - -**Step indicators:** - -``` -Step 1 of 3: Your Information -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -●━━━━━━━━━━━━○━━━━━━━━━━━━━○ - -[Fields here] - -[Continue to Step 2 →] -``` - -**Navigation:** -- Show progress (Step 1 of 3) -- Visual progress bar -- Allow back navigation (not just forward) - -**Button labels:** -- Step 1: "Continue" (not "Next") -- Step 2: "Continue" -- Step 3: "Submit Form" (or "Complete Order") - ---- - -## Internationalization (i18n) - -### Text Translation - -**All user-facing strings must be translatable:** - -```php -// ✅ Good -__('Email', 'sureforms'); -_e('Submit Form', 'sureforms'); - -// ❌ Bad -echo 'Email'; // Hardcoded English -``` - -**Placeholders for dynamic content:** - -```php -// ✅ Good -sprintf(__('You have %d new submissions', 'sureforms'), $count); - -// ❌ Bad -echo "You have $count new submissions"; -``` - ---- - -### Date & Number Formatting - -**Use WordPress functions:** - -```php -// Dates -echo date_i18n(get_option('date_format'), $timestamp); - -// Numbers -echo number_format_i18n($number); - -// Currency -echo '$' . number_format_i18n($amount, 2); -``` - ---- - -### RTL Support - -**CSS for right-to-left languages:** - -```css -/* Use logical properties */ -.srfm-field { - margin-inline-start: 10px; /* Not margin-left */ - padding-inline-end: 20px; /* Not padding-right */ -} -``` - -**Test with RTL languages:** -- Arabic -- Hebrew -- Persian - ---- - -## Quality Checklist - -Before shipping UI copy: - -- [ ] Spell check passed -- [ ] Grammar correct -- [ ] Tone matches guidelines -- [ ] Actionable error messages -- [ ] Examples provided where helpful -- [ ] Accessibility: screen reader friendly -- [ ] Accessibility: color contrast ≥ 4.5:1 -- [ ] Keyboard navigation works -- [ ] i18n: All strings translatable -- [ ] RTL languages supported -- [ ] Tested with real users (if major UI) - ---- - -**Next:** [FAQ](faq.md) diff --git a/tests/play/specs/CLAUDE.md b/tests/play/specs/CLAUDE.md deleted file mode 100644 index 113b91abd..000000000 --- a/tests/play/specs/CLAUDE.md +++ /dev/null @@ -1,324 +0,0 @@ -# Playwright E2E Specs — Writing Guide - -Rules and patterns for writing reliable SureForms E2E tests. Each section is a lesson learned from real failures. - ---- - -## Auth: Never do a full form-login inside tests - -**Pattern:** `globalSetup` logs in once and saves `storageState.json`. All workers restore it. - -**Why:** Multiple workers calling the WordPress login form simultaneously causes nonce collisions — WordPress redirects to `?reauth=1`, breaking every test in those workers. - -**Correct:** Let `storageState` handle auth. The `loginAsAdmin` util now just navigates to `/wp-admin` and relies on the pre-loaded cookies. - -```js -// DO — storageState is already loaded by playwright.config.js -test.beforeEach(async ({ page }) => { - await loginAsAdmin(page); // navigates to /wp-admin, skips login form -}); - -// DON'T — never re-do a full form login inside specs -await page.goto('/wp-login.php'); -await page.fill('#user_login', 'admin'); -``` - ---- - -## Adding blocks: sidebar vs programmatic insertion - -**Only these blocks appear in the quick-action sidebar by default:** -`input`, `email`, `textarea`, `checkbox`, `number`, `inline-button`, `advanced-heading`, `payment` - -**All other blocks** (`phone`, `url`, `multi-choice`, `dropdown`, `gdpr`, `rating`, `date`, etc.) **must be inserted programmatically.** - -Use `addFieldBlock(page, 'slug')` from `formHelpers.js` — it automatically routes sidebar blocks via click and all other blocks via `insertBlock`. - -```js -// This handles routing automatically -await addFieldBlock(page, 'gdpr'); // programmatic -await addFieldBlock(page, 'input'); // sidebar click -await addFieldBlock(page, 'dropdown'); // programmatic -``` - -**Never** try to open the WP block inserter toolbar to add non-sidebar blocks — it is fragile and frequently breaks tests. - ---- - -## WP 6.9+ iframe editor: no `data-type`, use data store - -WP 6.9 renders the block editor inside an `iframe[name="editor-canvas"]` and **removed the `data-type` attribute** from block wrapper elements. - -**Never** use `page.locator('.wp-block[data-type="srfm/..."]')` — it will find nothing. - -To **select a block** (e.g. to open its settings panel), use `selectBlock(page, 'slug')` from `formHelpers.js`. It dispatches `selectBlock` via the Gutenberg data store: - -```js -const { selectBlock } = require('../utils/formHelpers'); - -await addFieldBlock(page, 'email'); -await selectBlock(page, 'email'); // selects the block via data store -await openBlockSettingsTab(page); // now the sidebar shows this block's settings -``` - -To **verify a block exists**, `addFieldBlock` already checks via `wp.data.select('core/block-editor').getBlocks()` — no need for a separate DOM assertion. - ---- - -## Field CSS selectors - -Always use the most specific class. Do not rely on element order (`.nth(N)`) unless you are certain only N elements exist. - -| Field | Frontend input selector | -|---|---| -| Single line input | `input.srfm-input-input` | -| Email | `input.srfm-input-email` | -| Email confirm | `input.srfm-input-email-confirm` — **NOT** `input.srfm-input-email` | -| Phone | `input.srfm-input-phone` | -| Number | `input.srfm-input-number` | -| URL | `input.srfm-input-url` | -| Textarea | `textarea.srfm-textarea-text` | -| Checkbox | `input[type="checkbox"]` inside `.srfm-checkbox-input-wrap` | -| Submit button | `#srfm-submit-btn` | -| Success box | `.srfm-success-box` | -| Error message | `.srfm-error-message` | - ---- - -## TomSelect dropdowns - -The SureForms dropdown field uses TomSelect. The native ``. Filling it simulates a bot: - -```js -await page.evaluate(() => { - const f = document.querySelector('[name="srfm-honeypot-field"]'); - if (f) f.value = 'spambot'; -}); -``` - -Server returns `wp_send_json_error` → `.srfm-success-box` must NOT appear. - -## Helpful utilities - -All utilities live in `tests/play/utils/`. - -| Utility | Purpose | -|---|---| -| `loginAsAdmin(page)` | Navigates to `/wp-admin`, restores auth from storageState | -| `createBlankForm(page)` | Creates a new blank SureForms form in the editor | -| `addFieldBlock(page, slug)` | Adds a field block (sidebar or programmatic) | -| `selectBlock(page, slug)` | Selects a block in the editor via the data store (iframe-safe) | -| `publishFormAndGetURL(page)` | Publishes the current form and returns its front-end URL | -| `openFormSettingsDialog(page, navItem)` | Opens Form Behavior dialog to a specific nav item | -| `setFormTitle(page, title)` | Sets the editor post title via `editPost` (avoids fragile UI title input) | -| `createWPPage(page, title, content)` | Creates a published WP page via REST API | - -When a new utility is needed, add it here rather than inlining complex selectors in spec files. diff --git a/tests/play/specs/TODO.md b/tests/play/specs/TODO.md deleted file mode 100644 index fd594df9a..000000000 --- a/tests/play/specs/TODO.md +++ /dev/null @@ -1,244 +0,0 @@ -# E2E Test Plan — SureForms Free Plugin - -Track progress for all Playwright test cases. -**Update this file every time a test is added or a test ID changes.** - -Last updated: 2026-03-16 -Branch convention: `test/e2e-p{N}-test-cases` - ---- - -## How to update this file - -- When you **add a test**, tick its checkbox and fill in the `Spec file` column. -- When you **add a new test ID** that wasn't listed here, add a row in the correct section. -- Fix the `Last updated` date above. - ---- - -## Coverage status key - -| Symbol | Meaning | -|---|---| -| ✅ | Implemented and passing | -| ⏭️ | Implemented but skipped (requires external keys / infra) | -| ❌ | Not yet implemented | -| 🔢 | Implemented but test ID needs renumbering | - ---- - -## Group 1 — Field types (basic submit) - -All tests live in `field-types.spec.js`. -Single-line input and email are covered in `form-creation-submission.spec.js` (not repeated here). - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 1.1 | Textarea — submit multi-line text | ✅ | field-types.spec.js | -| 1.2 | Number — submit a valid number | ✅ | field-types.spec.js | -| 1.3 | Phone — submit a valid phone number | ✅ | field-types.spec.js | -| 1.4 | URL — submit a valid URL | ✅ | field-types.spec.js | -| 1.5 | Checkbox — check and submit | ✅ | field-types.spec.js | -| 1.6 | Multi-choice (radio) — pick one option and submit | ✅ | field-types.spec.js | -| 1.7 | Multi-choice (multi-select) — pick multiple options and submit | ✅ | field-types.spec.js | -| 1.8 | Dropdown — select one option and submit | ✅ | field-types.spec.js | -| 1.9 | *(reserved — inline-button / advanced-heading behaviour TBD)* | ❌ | — | -| 1.10 | GDPR — check consent and submit | ✅ | field-types.spec.js | - ---- - -## Group 2 — Field validation - -All tests live in `field-validation.spec.js`. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 2.1 | Email — invalid format shows error | ✅ | field-validation.spec.js | -| 2.2 | Email confirmation — mismatch shows error | ✅ | field-validation.spec.js | -| 2.3 | Email confirmation — matching emails submit successfully | ✅ | field-validation.spec.js | -| 2.4 | URL — invalid format shows error | ✅ | field-validation.spec.js | -| 2.5 | Number — value below minimum shows error | ✅ | field-validation.spec.js | -| 2.6 | Number — value above maximum shows error | ✅ | field-validation.spec.js | -| 2.7 | Required single-line input — submit empty shows error | ✅ | field-validation.spec.js | -| 2.8 | Required email — submit empty shows error | ✅ | field-validation.spec.js | -| 2.9 | Required phone — submit empty shows error | ✅ | field-validation.spec.js | -| 2.10 | Required textarea — submit empty shows error | ✅ | field-validation.spec.js | -| 2.11 | Required checkbox — submit unchecked shows error | ✅ | field-validation.spec.js | -| 2.12 | Required GDPR — submit without consent shows error | ✅ | field-validation.spec.js | -| 2.13 | Required dropdown — submit without selection shows error | ✅ | field-validation.spec.js | -| 2.14 | Required multi-choice — submit without selection shows error | ✅ | field-validation.spec.js | -| 2.15 | Character limit — input/textarea rejects text over max-chars | ❌ | — | - ---- - -## Group 3 — Form settings (confirmation behaviour) - -All tests live in `form-settings.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 3.1 | Redirect on submission — user lands on configured URL | ✅ | form-settings.spec.js | -| 3.2 | Custom success message — configured text shown after submit | ✅ | form-settings.spec.js | -| 3.3 | Store entries disabled — submission succeeds but no entry stored | ✅ | form-settings.spec.js | -| 3.4 | Page confirmation type — redirect to a configured WordPress page | ✅ | form-settings.spec.js | -| 3.5 | Store entries enabled (default) — entry appears in admin after submit | ✅ | form-settings.spec.js | -| 3.6 | Per-form GDPR compliance: do-not-store-entries enabled → no entry created | ✅ | form-settings.spec.js (covered by 3.3) | -| 3.7 | Per-form compliance: auto-delete setting saves and is visible in editor | ❌ | — | - ---- - -## Group 4 — Entries management - -All tests live in `entries.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 4.1 | Submitted entry appears in the admin entries list | ✅ | entries.spec.js | -| 4.2 | Entry detail contains the correct submitted field values | ✅ | entries.spec.js | -| 4.3 | Bulk delete removes selected entries | ✅ | entries.spec.js | -| 4.4 | CSV export downloads a file | ✅ | entries.spec.js | -| 4.5 | Mark entry as read / unread — status updates in the admin list | ✅ | entries.spec.js | - ---- - -## Group 5 — Form embedding - -All tests live in `form-embedding.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 5.1 | Shortcode-embedded form renders on a page | ✅ | form-embedding.spec.js | -| 5.2 | Shortcode-embedded form submits successfully | ✅ | form-embedding.spec.js | -| 5.3 | Gutenberg block-embedded form renders on a page | ✅ | form-embedding.spec.js | -| 5.4 | Gutenberg block-embedded form submits successfully | ✅ | form-embedding.spec.js | - ---- - -## Group 6 — Email notifications - -All tests live in `email-notifications.spec.js`. -All tests are currently skipped — require MailHog wired into the wp-env Docker setup. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 6.1 | Admin notification email is sent after form submission | ⏭️ | email-notifications.spec.js | -| 6.2 | Notification email body contains submitted field values | ⏭️ | email-notifications.spec.js | -| 6.3 | User confirmation email is sent to submitter's address | ❌ | — | - ---- - -## Group 7 — CAPTCHA - -All tests live in `captcha.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 7.2 | reCAPTCHA v2 — submit without solving is blocked | ⏭️ | captcha.spec.js | -| 7.3 | Cloudflare Turnstile — submit without solving is blocked | ❌ | — | -| 7.4 | hCaptcha — submit without solving is blocked | ❌ | — | -| 7.5 | reCAPTCHA v3 — submit with low score is blocked | ❌ | — | -| 7.7 | CAPTCHA disabled — form submits normally without CAPTCHA widget | ✅ | captcha.spec.js | - -> **Note:** 7.3–7.5 require external API keys; add them with `test.skip` pattern matching 7.2. - ---- - -## Group 8 — Payments (Stripe) - -All tests live in `payments.spec.js`. -All tests are currently skipped — require Stripe test keys. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 8.1 | Payment field renders the Stripe card widget | ⏭️ | payments.spec.js | -| 8.2 | Valid Stripe test card — payment succeeds and success message shown | ⏭️ | payments.spec.js | -| 8.3 | Declined Stripe test card — error message shown | ⏭️ | payments.spec.js | -| 8.6 | Successful payment creates an entry in the admin entries list | ⏭️ | payments.spec.js | - ---- - -## Group 9 — Global settings - -All tests live in `global-settings.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 9.1 | Global settings page loads without a JS error | ✅ | global-settings.spec.js | -| 9.2 | All main settings tabs are clickable and stay on settings page | ✅ | global-settings.spec.js | - ---- - -## Group 10 — Spam protection - -All tests live in `spam-protection.spec.js`. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 10.1 | Honeypot — bot fills hidden field → submission is blocked | ✅ | spam-protection.spec.js | -| 10.2 | Double-submit prevention — submit button disabled after first click | ✅ | spam-protection.spec.js | - ---- - -## Group 11 — Form restrictions - -All tests live in `form-restrictions.spec.js`. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 11.1 | Entry limit — form shows restriction message after limit reached | ✅ | form-restrictions.spec.js | -| 11.2 | Scheduling — restriction message shown when schedule has ended | ✅ | form-restrictions.spec.js | -| 11.3 | Scheduling — restriction message shown when schedule has not started yet | ✅ | form-restrictions.spec.js | - ---- - -## Group 12 — Form lifecycle - -All tests live in `form-lifecycle.spec.js`. - -> **✅ Renaming done:** Comments in `form-lifecycle.spec.js` updated from `8.x` to `12.x`. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 12.1 | Duplicate form — copy appears in forms list with "(Copy)" suffix | ✅ | form-lifecycle.spec.js | -| 12.2 | Trash form — form disappears from All Forms view | ✅ | form-lifecycle.spec.js | -| 12.3 | Restore form — form reappears in All Forms view after restore from Trash | ✅ | form-lifecycle.spec.js | -| 12.4 | Delete permanently — form is fully removed and not restorable | ✅ | form-lifecycle.spec.js | - ---- - -## Group 13 — AI form generation - -All tests will live in `ai-form-builder.spec.js`. -Tests that require an API key should follow the `test.skip` pattern used in payments and CAPTCHA. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 13.1 | AI generation panel opens from the "Create Form" flow | ❌ | — | -| 13.2 | Generating a form with a prompt produces at least one block | ❌ | — | - ---- - -## Suggested implementation order - -Work these in priority order — top of each section first: - -### Next up (P1 — no external dependencies) - -- [x] **11.3** Scheduling not-started-yet *(done — form-restrictions.spec.js)* -- [x] **12.4** Delete permanently from trash *(done — form-lifecycle.spec.js)* -- [x] **2.7–2.10** Required validation for remaining field types *(done — field-validation.spec.js)* -- [x] **4.5** Entry read/unread status toggle *(done — entries.spec.js)* -- [x] **3.6** Per-form do-not-store-entries compliance *(already covered by test 3.3)* -- [ ] **2.15** Character limit — low E2E value (browser `maxlength` attribute, no server-side check); defer unless a custom error message is confirmed - -### Later (P2 — needs infra or API keys, use skip pattern) - -- [ ] **6.3** User confirmation email (needs MailHog) -- [ ] **7.3–7.5** Additional CAPTCHA types (need external keys) -- [ ] **13.1–13.2** AI form generation (needs AI API key) - -### Nice to have - -- [ ] **3.7** Auto-delete entries compliance setting -- [ ] **9.x** Global settings save (email From Name/From Email) -- [ ] **1.9** Inline button / advanced heading block behaviour