diff --git a/README.md b/README.md index 4e348ffe6..468a5a71e 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,40 @@ -# SureForms - Contact Form, Payment Form & Other Custom Form Builder # +# SureForms - Contact Form, Payment Form, Survey & Other Custom Form Builder # **Contributors:** [brainstormforce](https://profiles.wordpress.org/brainstormforce/) -**Tags:** forms, contact form, custom form, payment form, form builder +**Tags:** forms, contact form, form builder, payment form, WordPress form plugin **Requires at least:** 6.4 -**Tested up to:** 6.9.4 +**Tested up to:** 7.0 **Requires PHP:** 7.4 -**Stable tag:** 2.8.2 +**Stable tag:** 2.10.1 **License:** GPLv2 or later **License URI:** http://www.gnu.org/licenses/gpl-2.0.html -The most beginner-friendly AI Form Builder for WordPress. Create contact, payment, quiz & custom forms with advanced features in minutes. +The AI-powered WordPress form builder. Build contact, payment, quiz, survey & multi-step forms in minutes, no code needed. ## Description ## -#### SUREFORMS – THE BEST AI-ASSISTED FORM BUILDER +#### SUREFORMS – THE BEST AI-ASSISTED WORDPRESS FORM BUILDER ★★★★★ +SureForms is the most beginner-friendly **WordPress form builder**, designed to help anyone create contact forms, payment forms, surveys, quizzes, multi-step forms, and conversational forms without writing a single line of code. Powered by AI and built natively on the WordPress block editor, SureForms makes building any form on your WordPress site fast, intuitive, and beautiful. + +Trusted by **500,000+ websites**, SureForms is the modern alternative to traditional contact form plugins — purpose-built for speed, design, and conversions. + 👉 Try the live demo of SureForms [youtube https://www.youtube.com/watch?v=qLpnm4GdXks] ### CREATE BEAUTIFUL RESPONSIVE FORMS USING WORDPRESS BLOCK EDITOR (GUTENBERG EDITOR) WITHOUT CODING ⚡ -Our mission at SureForms is to empower you to build **beautiful forms without any code**. +Our mission at SureForms is to empower you to build **beautiful WordPress forms without any code**. -We're taking a fresh approach with Gutenberg, WordPress's native drag-and-drop builder, to make creating forms seamless and intuitive with no new interface to learn. +We're taking a fresh approach with Gutenberg, WordPress's native drag-and-drop builder, to make creating contact forms and custom forms seamless and intuitive — with no new interface to learn. -SureForms is a beginner-friendly form plugin that makes building contact forms and other high-converting custom forms fast and simple with advanced features like multi-step forms, conversational forms, payment forms, conditional logic, calculation, native integrations, etc. +SureForms is a beginner-friendly WordPress form plugin that makes building contact forms and other high-converting custom forms fast and simple, with advanced features like multi-step forms, conversational forms, payment forms, conditional logic, calculations, native integrations, and more. Good design and user experience are crucial for forms. Yet many website owners struggle to make their forms blend seamlessly with their site's look and feel, often leading to lower response rates. -In this regard, SureForms addresses several pain points commonly experienced by website owners: +In this regard, SureForms addresses several pain points commonly experienced by WordPress users: - Complex Process of Form Building - Design Limitations @@ -44,17 +48,17 @@ SureForms is packed with features to address these pain points of WordPress user Below are some of the top features SureForms offers: -#### Native WordPress +#### Native WordPress Form Builder (No New Editor to Learn) -SureForms uses WordPress's native block editor for a seamless drag-and-drop form building experience. No need to learn a new editor or a new style. +SureForms uses WordPress's native block editor for a seamless drag-and-drop form building experience. There's no separate interface, no proprietary builder, and no learning curve — just the WordPress editor you already know. It's fast, simple, and the most beginner-friendly form builder for WordPress. -#### Create Forms Effortlessly with AI +#### AI Form Builder for WordPress [youtube https://www.youtube.com/watch?v=uDLF4dk3YHI] -SureForms is the first AI-powered form builder for WordPress, which makes form creation faster and smarter. From simple contact forms to advanced calculators, AI handles the details, offers smart suggestions, and helps you build functional, user friendly forms in just a few clicks. +SureForms is the first AI-powered form builder for WordPress, making form creation faster and smarter. From simple contact forms to advanced calculators, the SureForms AI handles the details, offers smart field suggestions, and helps you build functional, user-friendly forms in just a few clicks. **Here are a few examples of forms you can create with SureForms AI:** @@ -69,35 +73,35 @@ SureForms is the first AI-powered form builder for WordPress, which makes form c - Calculators - Conversational Forms -#### No-Code Flexibility +#### No-Code Form Builder with Advanced Logic -SureForms makes advanced features like conditional logic, multi-step forms, query parameters and etc. simple to use through a no-code interface. This empowers anyone to create powerful, interactive forms without writing a single line of code. +SureForms makes advanced features like conditional logic, multi-step forms, query parameters, and more simple to use through a no-code interface. This empowers anyone to create powerful, interactive WordPress forms without writing a single line of code. -#### Payment Forms +#### WordPress Payment Forms with Stripe and PayPal -SureForms supports a built-in Payment Forms feature. With SureForms, anyone can easily create full-fledged donation forms, event registrations, product checkouts, or any kind of form that needs to accept payments, all without relying on any extra add-on or third-party plugins. This makes collecting payments seamless, secure, and effortless directly through your forms using Stripe. PayPal payments are available with SureForms Business. +SureForms supports a built-in **Payment Forms** feature. With SureForms, anyone can easily create full-fledged donation forms, event registrations, product checkouts, or any kind of form that needs to accept payments — all without relying on any extra add-on or third-party plugins. This makes collecting payments seamless, secure, and effortless directly through your WordPress forms using Stripe. PayPal payments are available with SureForms Business. -Here are a few examples of how the Payment Feature in SureForms can power your forms: +Here are a few examples of how the Payment Forms feature in SureForms can power your forms: - [Donation Form](https://sureforms.com/form/donation-form/) - [Photography Packages Booking Form](https://sureforms.com/form/photography-packages-payment-form/) - [Monthly Membership Plan Form](https://sureforms.com/form/monthly-membership-plan/) - [Web Design Packages Booking Form](https://sureforms.com/form/freelance-web-design-packages-payment/) -#### Instant Forms +#### Instant Forms (Shareable Form Links — No Embed Needed) [youtube https://www.youtube.com/watch?v=pMH129ZUxJ0] -With SureForms' own Instant Form feature, you can publish forms with a unique shareable URL. No embedding needed. Share them anywhere and make your forms instantly accessible. +With SureForms' own Instant Form feature, you can publish forms with a unique shareable URL. No page, no shortcode, no embedding needed. Share the link anywhere — email, social, SMS — and make your forms instantly accessible. -#### Mobile-First Approach +#### Mobile-Friendly Responsive Forms -SureForms recognizes the importance of mobile friendly forms. The forms created with SureForms are designed with a mobile-first approach, ensuring they look and function flawlessly on various devices. +SureForms recognizes the importance of mobile-friendly forms. Every form created with SureForms is designed with a mobile-first approach, ensuring it looks and functions flawlessly on phones, tablets, and desktops alike. -#### Multi-Column Layouts +#### Multi-Column Form Layouts SureForms makes it easy to arrange input fields into multiple columns for a clean, efficient form layout. This makes your forms look organized, visually appealing, and encourages higher completion rates. You can customize the columns to fit your design. -#### Input Fields +#### 15+ Form Input Fields **SureForms comes with 15+ fields to build your form:** @@ -118,35 +122,35 @@ SureForms makes it easy to arrange input fields into multiple columns for a clea - Image - Icon -#### Inline Field Validation +#### Inline Form Validation in Real-Time -Real-time validation for form fields provides instant feedback to users if they enter incorrect or incomplete information. This ensures data accuracy and enhances the user experience. +Real-time validation for form fields provides instant feedback to users if they enter incorrect or incomplete information. This ensures data accuracy and enhances the user experience on every form submission. -#### GDPR Compliance +#### GDPR-Compliant WordPress Forms Ensure your forms are GDPR compliant and protect user data by simply enabling the built-in GDPR setting. -#### Anti-Spam Forms +#### Anti-Spam Forms (reCAPTCHA + Honeypot) -Protect your forms from spam with built-in measures like Google reCAPTCHA and Honeypot fields. These ensure submissions are genuine, block bots, and keep your forms secure without bothering your users. +Protect your WordPress forms from spam with built-in measures like Google reCAPTCHA and Honeypot fields. These ensure submissions are genuine, block bots, and keep your forms secure without bothering your users. -#### Personalized Confirmation Messages +#### Personalized Confirmation Messages and Redirects SureForms provides options for setting up customized confirmation messages or redirections that are displayed to users after successful form submissions. Tailor these messages to provide personalized feedback or redirect respondents to specific pages. -#### Email Notifications +#### Email Notifications for Every Form Submission Configure customized email notifications triggered by form submissions. Specify recipients, email templates, and personalized messages to stay informed and automate communication. -#### Form Entries +#### Form Entries Management Inside WordPress -Stores form entries securely to access and manage form submission data within your WordPress dashboard. You can also review, export, or perform actions on form entries as needed. +Stores form entries securely so you can access and manage form submission data right inside your WordPress dashboard. You can review, export, or perform actions on form entries as needed — no external service required. -#### Visual Data Reporting +#### Visual Data Reporting and Form Analytics -Analyze and gain insights from form submissions through interactive charts, graphs, and data visualizations. Take data-driven decisions. +Analyze and gain insights from form submissions through interactive charts, graphs, and data visualizations. Take data-driven decisions from your WordPress dashboard. #### Developer-Friendly Customization @@ -159,10 +163,10 @@ SureForms Business unlocks advanced capabilities for power users, agencies, and #### Advanced Form Types [**Conversational Forms**](https://sureforms.com/features/conversational-form/): -Chat-like forms, one question at a time, for more conversions. +Chat-like forms, one question at a time, for higher conversions and lower drop-off rates. [**Multi-step Forms**](https://sureforms.com/features/multi-step-forms/): -Bite-sized forms with progress indicators, less form fatigue for your users. +Bite-sized forms with progress indicators, reducing form fatigue and boosting completion rates. [**Quiz Forms**](https://sureforms.com/features/quiz/): Create scored quizzes and personality tests with result output and grading. @@ -170,8 +174,8 @@ Create scored quizzes and personality tests with result output and grading. [**Conditional Logic Forms**](https://sureforms.com/features/conditional-logic/): Show or hide fields, customize email notifications, and display different confirmation messages based on users' answers. -[**Calculators**](https://sureforms.com/features/calculator-form/): -Provide instant quotes or results with interactive calculators in your site. Designed to bring more traffic to your site. +[**Calculator Forms**](https://sureforms.com/features/calculator-form/): +Provide instant quotes or results with interactive calculators on your site — designed to bring more traffic and conversions. #### Pro Field Types @@ -197,7 +201,7 @@ Instantly create downloadable PDFs of form submissions for records or sharing. Let users save their progress and come back to complete the form later. [**Custom Registration & Login Forms**](https://sureforms.com/features/login-and-registration/): -Fully functional, branded Registration & Login forms for your site using just a block. +Fully functional, branded Registration and Login forms for your WordPress site using just a block. **Custom Post Type Creation**: Create WordPress posts or custom post types directly from form submissions. @@ -217,7 +221,7 @@ Customize form appearance with advanced styling options when embedding forms via #### Integrations & Automation [**Advanced Native Integrations**](https://sureforms.com/integrations/): -Connect your forms natively with top apps and services to automate workflows and save time without any third-party add-ons or plugins. +Connect your forms natively with top apps and services to automate workflows and save time — no third-party add-ons or plugins required. SureForms connects natively with: - Google Sheets @@ -264,23 +268,23 @@ Customize form appearance directly within your favorite page builder: - Bricks Builder — styling options in the Bricks panel - Elementor — styling controls in the Elementor widget -With SureForms, we are here to fix some real issues users face with WordPress Forms. That's why **300,000+ websites** have embraced SureForms for all their form related needs. +With SureForms, we are here to fix the real issues users face with WordPress forms. That's why **500,000+ websites** have embraced SureForms for all their form-related needs. ### HOW IT WORKS ❓ From installation to creating your first form, SureForms is designed to be simple and intuitive. Here is how it works: -#### Step 1: Install WordPress Plugin 🔌 +#### Step 1: Install the SureForms WordPress Plugin 🔌 From your WordPress dashboard, navigate to the Plugins menu and click on the "Add New" button. Search for the SureForms plugin, then click "Install Now" and "Activate". -#### Step 2: Build your Form ⚙️ +#### Step 2: Build Your Form with Drag and Drop ⚙️ -Once the plugin is installed, you will be redirected to the SureForms dashboard page in your WordPress dashboard. There you will find a button to create a new form. Click it to build and customize your form using the SureForms interface to suit your requirements. +Once the plugin is installed, you'll be redirected to the SureForms dashboard page in your WordPress dashboard. There you will find a button to create a new form. Click it to build and customize your form using the SureForms drag-and-drop interface to suit your requirements. -#### Step 3: Embed/Publish your Form 📄 +#### Step 3: Embed or Publish Your Form 📄 -Once your form is ready, easily embed it on any page or post using the provided shortcode or block. Or, you can use the 'Instant Form' option to make it live instantly. +Once your form is ready, easily embed it on any page or post using the provided shortcode or block. Or, you can use the 'Instant Form' option to make it live instantly with a shareable link. ### WHO CAN BENEFIT FROM SUREFORMS? @@ -334,18 +338,6 @@ Don't think this is just it. There is no limit to the types of businesses and or - Divi Builder - And many more -### BRANDING GUIDELINE - -#### SureForms® is a registered trademark. Please use the following format when mentioning SureForms anywhere: - -- SureForms \[correct\] -- Sure Forms \[incorrect\] -- Sureform \[incorrect\] -- Sureforms \[incorrect\] -- Sure forms \[incorrect\] -- SureForm \[incorrect\] -- Sure Form \[incorrect\] - ### CONNECT WITH OUR TEAM AND COMMUNITY [Join our Facebook group community](https://www.facebook.com/groups/surecart): @@ -369,140 +361,146 @@ To improve the user experience, SureForms may use the following 3rd party servic - **Stripe** -- is used for processing payment forms. [TOS](https://stripe.com/legal/ssa) and [Privacy Policy](https://stripe.com/privacy) +### BRANDING GUIDELINE + +#### SureForms® is a registered trademark. Please use the following format when mentioning SureForms anywhere: + +- SureForms \[correct\] +- Sure Forms \[incorrect\] +- Sureform \[incorrect\] +- Sureforms \[incorrect\] +- Sure forms \[incorrect\] +- SureForm \[incorrect\] +- Sure Form \[incorrect\] + ## Blocks ## This plugin provides 16 blocks for all users: -- **Address:** Displays a SureForms Address Field +- **Address Field:** Add a full-address input to your WordPress contact form, with line 1, line 2, city, state, postal code, and country. -- **Phone Number:** Displays a SureForms Phone Field +- **Phone Number Field:** Collect international phone numbers in your forms with built-in country code detection and validation. -- **Email:** Displays a SureForms Email Field +- **Email Field:** Add an email input to your contact form, with real-time validation and built-in spam protection. -- **Textarea:** Displays a SureForms Textarea Field +- **Textarea Field:** Capture long-form responses, comments, or messages in your WordPress forms with a resizable text area. -- **Number:** Displays a SureForms Number Field +- **Number Field:** Collect numeric input such as age, quantity, or price in your form with min, max, and step controls. -- **URL:** Displays a SureForms URL Field +- **URL Field:** Let users submit website addresses with automatic URL validation in your forms. -- **Dropdown:** Displays a SureForms Dropdown Field +- **Dropdown Field:** Add a single-select dropdown to your WordPress form, with customizable options and dynamic values. -- **Custom Button:** Displays a SureForms Custom Button +- **Custom Button Block:** Add a styled submit or action button to your SureForms layout. -- **Text:** Displays a SureForms Text Field +- **Text Field:** Add a single-line text input for short answers like names, titles, or short responses. -- **Multiple Choice:** Displays a SureForms Multiple Choice Field +- **Multiple Choice Field:** Let users pick one option from a list of radio choices in your WordPress form. -- **Checkbox:** Displays a SureForms Check Box Field +- **Checkbox Field:** Add single or multiple checkbox options to capture preferences, agreements, or selections. -- **Separator:** Displays a SureForms Separator Field +- **Separator:** Add a visual divider to organize fields and sections inside your SureForms form. -- **Heading:** Displays a SureForms Heading Field +- **Heading:** Add section headings to give your form structure and improve readability. -- **Image:** Displays a SureForms Upload Image Field +- **Image:** Display an image inside your form to add visual context, branding, or instructions. -- **Icon:** Displays a SureForms Icon Field +- **Icon:** Add an icon to your form for visual polish and brand consistency. -- **GDPR Agreement:** Displays a SureForms GDPR Agreement Field +- **GDPR Agreement Field:** Add a GDPR consent checkbox to your WordPress contact form to stay compliant with data privacy laws. ## Frequently Asked Questions ## +### Is SureForms free? ### + +Yes! SureForms is 100% free to download and use on the WordPress.org plugin directory. The free version includes the AI form builder, contact forms, payment forms via Stripe, GDPR compliance, anti-spam protection, native integrations, and 15+ form fields. Advanced features like multi-step forms, conversational forms, quizzes, surveys, file uploads, and PayPal payments are available with SureForms Business. + +### How is SureForms different from other WordPress form plugins? ### + +SureForms is the first AI-powered form builder built natively on the WordPress block editor. Most form plugins ship with their own proprietary builder you have to learn from scratch. SureForms uses the Gutenberg interface you already know, adds AI form generation so you can build a form from a single prompt, and includes payment forms, conditional logic, and native integrations in a clean, modern design. It's the modern alternative to legacy contact form plugins on WordPress. + +### How is SureForms different from Contact Form 7? ### + +Contact Form 7 is a long-trusted plugin, but it relies on shortcodes and requires manual HTML for advanced layouts. SureForms is fully visual, drag-and-drop, mobile-first, and includes payment forms, AI form generation, conditional logic, and design controls out of the box — without needing add-ons. If you're upgrading from Contact Form 7, SureForms gives you a modern WordPress form-building experience without the technical setup. + +### Does SureForms work with Elementor, Bricks, and Divi? ### + +Yes. SureForms works seamlessly with Elementor, Bricks Builder, Divi Builder, Beaver Builder, and any other major WordPress page builder. SureForms Business also adds dedicated styling controls inside the Elementor widget and the Bricks element so you can match your form's appearance to your site's design without writing CSS. + ### Do I need coding skills to create forms using SureForms? ### **No coding needed!** -With SureForms' visual block builder, you can drag and drop Gutenberg -blocks to create beautiful, responsive forms. Perfect for small business -owners, freelancers, or anyone who wants forms without technical skills. +With SureForms' visual block builder, you can drag and drop Gutenberg blocks to create beautiful, responsive WordPress forms. Perfect for small business owners, freelancers, designers, or anyone who wants forms without technical skills. ### What are the requirements to use SureForms? ### -You just need the latest version of WordPress and a theme. SureForms -installs like any other plugin and is ready to use. +You just need the latest version of WordPress and any theme. SureForms installs like any other plugin and is ready to use within seconds. + +### Can I use SureForms along with a Page Builder? ### -### Can I use the SureForms along with a Page Builder? ### +Yes. SureForms only requires the latest WordPress version and works seamlessly with all major page builders — Elementor, Bricks, Divi, Beaver Builder, and more — without interference. -SureForms only requires the latest WordPress version and works -seamlessly with all major page builders without interference. +Should you need help, you can [get in touch with us.](https://support.brainstormforce.com/?utm_source=wp-repo&utm_medium=link&utm_campaign=readme) -Should you need help, you can [get in touch with -us.](https://support.brainstormforce.com/?utm_source=wp-repo&utm_medium=link&utm_campaign=readme) +### Will SureForms slow down my website? ### -### Will the SureForms slow down my website? ### +Absolutely not. SureForms is lightweight and fast, built with clean code and modular architecture to ensure it never slows down your WordPress site. -Absolutely not! SureForms is lightweight and fast, built with clean -code and modular architecture to ensure it never slows down your -website. +### Can I use SureForms on client websites? ### -### Can I use the SureForms on client websites? ### +Yes. You can use SureForms on your own sites as well as on your clients' websites — no licensing restrictions on the free version. -Yes! You can certainly use SureForms on your own as well as your -client's websites. +### What more do I get with SureForms? ### -### What more do I get with the SureForms? ### +When you use SureForms, you get a huge library of ready-to-use form templates that can be used to build your forms fast — contact forms, payment forms, lead generation forms, registration forms, and many more. -When you use SureForms, you get a huge library of ready-to-use form -templates that can be used to build your forms fast. +### Does SureForms support Multi-step Forms or Conversational Forms? ### -### Does SureForms support Multi-step Forms or Conversational Forms? +Yes. You can easily create Multi-step Forms as well as Conversational Forms to improve user experience and conversion rates with SureForms Business plans. -Yes. You can easily create Multi-step Forms as well as Conversational -Forms to improve user experience and conversion rates with SureForms Business -plans. -### ### Can I create quiz forms with SureForms? ### -Yes! SureForms Business includes a Quiz module. You can create scored quizzes with grading and personality tests with result output — all with the same drag-and-drop builder. +Yes. SureForms Business includes a Quiz module. You can create scored quizzes with grading and personality tests with result output — all with the same drag-and-drop builder. ### Does SureForms support file uploads? ### -Yes! With SureForms Business, you can add file upload fields to your forms. It supports multi-file uploads, file type restrictions, and role-based access control for uploaded files. +Yes. With SureForms Business, you can add file upload fields to your forms. It supports multi-file uploads, file type restrictions, and role-based access control for uploaded files. ### What integrations does SureForms support? ### -SureForms Business connects natively with services including Google Sheets, Mailchimp, HubSpot, ActiveCampaign, Klaviyo, Zoho CRM, Notion, and many more. You can also use Zapier to connect with 5,000+ additional apps, or use webhooks to send data to any API endpoint. +SureForms connects natively with services including Google Sheets, Mailchimp, HubSpot, ActiveCampaign, Klaviyo, FluentCRM, Zoho CRM, Notion, and many more. You can also use Zapier to connect with 5,000+ additional apps, or use webhooks to send data to any API endpoint. ### Does SureForms support Payment Forms? ### -Yes! SureForms has built-in Payment Form support. -You can collect payments securely through Stripe in the free version. PayPal payments are available with SureForms Business. No add-ons or extra plugins needed. +Yes. SureForms has built-in Payment Form support. You can collect payments securely through Stripe in the free version. PayPal payments are available with SureForms Business. No add-ons or extra plugins needed. ## Screenshot ## -1. SureForms Dashboard -2. Start building your forms within minutes using SureForms. You can build a form from scratch or choose AI to build one for you. -3. Build forms instantly with just a prompt. AI creates your forms with smart field suggestions. Then add, remove, or rearrange fields as you need. -4. Beautify your forms with Instant Form Styling and a wide range of Block and Form Settings to customize every detail. -5. Lastly, publish your forms instantly or easily embed forms in any pages with SureForms' native block. +1. SureForms dashboard — manage all your WordPress forms in one place with form analytics, entries, and quick actions. +2. Start building your forms within minutes using SureForms. Build a form from scratch with drag and drop, or let AI generate one for you from a simple prompt. +3. AI form builder for WordPress — describe the form you need and SureForms creates it instantly with smart field suggestions. Add, remove, or rearrange fields as you need. +4. Beautify your forms with Instant Form Styling and a wide range of Block and Form Settings — customize colors, typography, spacing, and every visual detail. +5. Publish your forms instantly with a shareable Instant Form link, or easily embed forms in any page or post using SureForms' native Gutenberg block. -You can report the issue 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. +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.8.2 - 5th May 2026 ### -* New: Added "Both" payment type so a single form can collect one-time and subscription payments together. -* New: Added Hidden field support for the payment block's dynamic amount. -* New: Added minimum character limit support for the Textarea field. -* Improvement: Added extension hooks for windowed entry caps in form restriction. -* Fix: Fixed AI Form Builder silent failures and replaced generic error messages with clearer feedback. -* Fix: Resolved an issue where the Confirm Email field could silently block form submission when the Email field was not marked as required. -### 2.8.1 - 27th April 2026 ### -* New: Added support for dynamic default values on Dropdown and Multi-Choice fields. -* New: Added {entry_id} smart tag for use in email templates, confirmations, and dynamic content. -* Improvement: Added PLN (Polish Złoty) to the currency dropdown. -* Fix: Fixed background color not applying correctly in non-iframe editor mode. -* Fix: Fixed MCP endpoint REST URL resolution so it uses the correct site URL. -* Fix: Fixed Phone field country flag unexpectedly changing during browser autocomplete. -* Fix: Fixed review/validation issues in Global Settings. -### 2.8.0 - 16th April 2026 ### -* New: Added My Account page allowing users to view and manage their payment history and subscriptions. -* Improvement: Phone number country detection moved to backend with caching, reducing multiple API calls. -* Fix: Fixed payment method radio button display issue when both Stripe and PayPal gateways are enabled. -* Fix: Fixed crash when saving a form containing the Register block. -* Fix: Fixed inability to deselect a preselected option in single-selection controls (radio, dropdown) within the editor. -* Fix: Fixed Quill rich text editor toolbar strings not being translatable. +### 2.10.1 - 1st June 2026 ### +* Improvement: Improved compatibility with older Block API versions so the SureForms editor loads reliably across WordPress environments. +* Fix: Resolved an issue where the Textarea character counter was misaligned. +* Fix: Resolved an issue where the {entry_id} smart tag failed to resolve in email notifications. +### 2.10.0 - 27th May 2026 ### +* New: Added a live character counter to Textarea fields. +* New: Added a one-click converter that turns raw HTML forms into native SureForms in gutenberg editor. +* Improvement: Added dynamic default value support for multi-select Dropdown and Multi-Choice fields. +* Improvement: Entry URL now records actual submission page. +* Fix: CSV export was stripping line breaks from Textarea submissions. +### 2.9.1 - 19th May 2026 ### +* Improvement: Added compatibility with WordPress 7.0. The full changelog is available [here](https://sureforms.com/whats-new/). ## Upgrade Notice ## ### 0.0.13 ### -From version 0.0.13 we're migrating to a custom database to enhance SureForms' performance and features. This step is necessary and irreversible and your current existing entries will be lost. Thank you for your understanding! +From version 0.0.13 we're migrating to a custom database to enhance SureForms' performance and features. This step is necessary and irreversible and your current existing entries will be lost. Thank you for your understanding! \ No newline at end of file diff --git a/admin/admin.php b/admin/admin.php index bc68cef59..c8e5efd78 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -10,10 +10,12 @@ use Astra_Notices; use SRFM\Inc\AI_Form_Builder\AI_Helper; use SRFM\Inc\Database\Tables\Entries; +use SRFM\Inc\Global_Settings\Global_Settings; use SRFM\Inc\Helper; use SRFM\Inc\Onboarding; use SRFM\Inc\Payments\Payment_Helper; use SRFM\Inc\Payments\Stripe\Stripe_Helper; +use SRFM\Inc\Smart_Tags; use SRFM\Inc\Traits\Get_Instance; if ( ! defined( 'ABSPATH' ) ) { @@ -78,6 +80,7 @@ public function __construct() { if ( ! Helper::has_pro() ) { add_action( 'admin_menu', [ $this, 'add_quiz_page' ] ); add_action( 'admin_menu', [ $this, 'add_survey_reports_page' ] ); + add_action( 'admin_menu', [ $this, 'add_partial_entries_page' ] ); add_action( 'admin_menu', [ $this, 'add_upgrade_to_pro' ] ); add_action( 'admin_footer', [ $this, 'add_upgrade_to_pro_target_attr' ] ); } @@ -230,12 +233,7 @@ public static function check_first_form_creation_threshold( $days = 3 ) { public function add_action_links( $links ) { if ( ! Helper::has_pro() ) { // Display upsell link if SureForms Pro is not installed. - $upsell_link = add_query_arg( - [ - 'utm_medium' => 'plugin-list', - ], - Helper::get_sureforms_website_url( 'pricing' ) - ); + $upsell_link = Helper::get_sureforms_website_url( 'pricing', [ 'utm_medium' => 'plugin-list' ] ); ob_start(); ?> @@ -397,12 +395,7 @@ public function add_upgrade_to_pro_target_attr() { public function add_upgrade_to_pro() { // The url used here is used as a selector for css to style the upgrade to pro submenu. // If you are changing this url, please make sure to update the css as well. - $upgrade_url = add_query_arg( - [ - 'utm_medium' => 'submenu_link_upgrade', - ], - Helper::get_sureforms_website_url( 'upgrade' ) - ); + $upgrade_url = Helper::get_sureforms_website_url( 'upgrade', [ 'utm_medium' => 'submenu_link_upgrade' ] ); add_submenu_page( 'sureforms_menu', @@ -479,6 +472,39 @@ public function render_survey_empty_state() { ' . + esc_html__( 'New', 'sureforms' ) . + '', + self::$sureforms_page_default_capability, + 'sureforms_partial_entries', + [ $this, 'render_partial_entries_empty_state' ], + 7 + ); + } + + /** + * Partial Entries empty state page callback. + * + * @return void + * @since 2.9.0 + */ + public function render_partial_entries_empty_state() { + ?> +
+ get_site_url(), - 'current_user_login' => $current_user->user_login ?? '', - 'website_lead_details' => [ + 'site_url' => get_site_url(), + 'current_user_login' => $current_user->user_login ?? '', + 'website_lead_details' => [ 'first_name' => $current_user->first_name ?? '', 'last_name' => $current_user->last_name ?? '', 'email' => $current_user->user_email ?? '', ], - 'breadcrumbs' => $this->get_breadcrumbs_for_current_page(), - 'sureforms_dashboard_url' => admin_url( '/admin.php?page=sureforms_menu' ), - 'plugin_version' => SRFM_VER, - 'global_settings_nonce' => Helper::current_user_can() ? wp_create_nonce( 'wp_rest' ) : '', - 'is_pro_active' => Helper::has_pro(), - 'is_first_form_created' => self::is_first_form_created(), - 'check_three_days_threshold' => self::check_first_form_creation_threshold(), - 'check_eight_days_threshold' => self::check_first_form_creation_threshold( 8 ), - 'pro_plugin_version' => Helper::has_pro() ? SRFM_PRO_VER : '', - 'pro_plugin_name' => Helper::has_pro() && defined( 'SRFM_PRO_PRODUCT' ) ? SRFM_PRO_PRODUCT : 'SureForms Pro', - 'sureforms_pricing_page' => Helper::get_sureforms_website_url( 'pricing' ), - 'field_spacing_vars' => Helper::get_css_vars(), - 'is_ver_lower_than_6_7' => version_compare( $wp_version, '6.6.2', '<=' ), - 'integrations' => Helper::sureforms_get_integration(), - 'rotating_plugin_banner' => Helper::get_rotating_plugin_banner(), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'sf_plugin_manager_nonce' => wp_create_nonce( 'sf_plugin_manager_nonce' ), - 'plugin_installer_nonce' => wp_create_nonce( 'updates' ), - 'plugin_activating_text' => __( 'Activating...', 'sureforms' ), - 'plugin_activated_text' => __( 'Activated', 'sureforms' ), - 'plugin_activate_text' => __( 'Activate', 'sureforms' ), - 'plugin_installing_text' => __( 'Installing...', 'sureforms' ), - 'plugin_installed_text' => __( 'Installed', 'sureforms' ), - 'privacy_policy_url' => Helper::get_sureforms_website_url( 'privacy-policy/' ), - 'is_rtl' => $is_rtl, - 'onboarding_completed' => method_exists( $onboarding_instance, 'get_onboarding_status' ) ? $onboarding_instance->get_onboarding_status() : false, - 'onboarding_redirect' => isset( $_GET['srfm-activation-redirect'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required for the activation redirection. - 'pointer_nonce' => wp_create_nonce( 'sureforms_pointer_action' ), - 'general_settings_url' => admin_url( '/options-general.php' ), - 'additional_header_nav_items' => [], - 'payments' => apply_filters( + 'breadcrumbs' => $this->get_breadcrumbs_for_current_page(), + 'sureforms_dashboard_url' => admin_url( '/admin.php?page=sureforms_menu' ), + 'plugin_version' => SRFM_VER, + 'global_settings_nonce' => Helper::current_user_can() ? wp_create_nonce( 'wp_rest' ) : '', + 'is_pro_active' => Helper::has_pro(), + 'is_first_form_created' => self::is_first_form_created(), + 'check_three_days_threshold' => self::check_first_form_creation_threshold(), + 'check_eight_days_threshold' => self::check_first_form_creation_threshold( 8 ), + 'pro_plugin_version' => Helper::has_pro() ? SRFM_PRO_VER : '', + 'pro_plugin_name' => Helper::has_pro() && defined( 'SRFM_PRO_PRODUCT' ) ? SRFM_PRO_PRODUCT : 'SureForms Pro', + 'sureforms_pricing_page' => Helper::get_sureforms_website_url( 'pricing' ), + 'field_spacing_vars' => Helper::get_css_vars(), + 'is_ver_lower_than_6_7' => version_compare( $wp_version, '6.6.2', '<=' ), + 'integrations' => Helper::sureforms_get_integration(), + 'rotating_plugin_banner' => Helper::get_rotating_plugin_banner(), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'sf_plugin_manager_nonce' => wp_create_nonce( 'sf_plugin_manager_nonce' ), + 'plugin_installer_nonce' => wp_create_nonce( 'updates' ), + 'plugin_activating_text' => __( 'Activating...', 'sureforms' ), + 'plugin_activated_text' => __( 'Activated', 'sureforms' ), + 'plugin_activate_text' => __( 'Activate', 'sureforms' ), + 'plugin_installing_text' => __( 'Installing...', 'sureforms' ), + 'plugin_installed_text' => __( 'Installed', 'sureforms' ), + 'privacy_policy_url' => Helper::get_sureforms_website_url( 'privacy-policy/' ), + 'is_rtl' => $is_rtl, + 'onboarding_completed' => method_exists( $onboarding_instance, 'get_onboarding_status' ) ? $onboarding_instance->get_onboarding_status() : false, + 'onboarding_redirect' => isset( $_GET['srfm-activation-redirect'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required for the activation redirection. + 'pointer_nonce' => wp_create_nonce( 'sureforms_pointer_action' ), + 'general_settings_url' => admin_url( '/options-general.php' ), + 'additional_header_nav_items' => [], + // Smart tags for the Global Defaults email notification fields. + // srfm_block_data is only available in the block editor; these keys + // make the same data accessible on the settings page via srfm_admin. + 'smart_tags_array' => Smart_Tags::smart_tag_list(), + 'smart_tags_array_email' => Smart_Tags::email_smart_tag_list(), + // Default confirmation message HTML (icon + heading + text) used as + // the initial React state before the settings API response arrives. + 'default_confirmation_message' => Global_Settings::get_default_confirmation_message(), + 'payments' => apply_filters( 'srfm_admin_localize_payments_data', [ 'stripe_connected' => Stripe_Helper::is_stripe_connected(), @@ -940,22 +974,23 @@ public function enqueue_scripts() { 'currency_sign_position' => Payment_Helper::get_currency_sign_position(), ] ), - 'mcp_adapter_status' => file_exists( WP_PLUGIN_DIR . '/mcp-adapter/mcp-adapter.php' ) + 'mcp_adapter_status' => file_exists( WP_PLUGIN_DIR . '/mcp-adapter/mcp-adapter.php' ) ? ( is_plugin_active( 'mcp-adapter/mcp-adapter.php' ) ? 'active' : 'installed' ) : 'not_installed', - 'mcp_endpoint_url' => esc_url_raw( rest_url( 'sureforms/v1/mcp' ) ), + 'mcp_endpoint_url' => esc_url_raw( rest_url( 'sureforms/v1/mcp' ) ), ]; - $is_screen_sureforms_menu = Helper::validate_request_context( 'sureforms_menu', 'page' ); - $is_screen_add_new_form = Helper::validate_request_context( 'add-new-form', 'page' ); - $is_screen_sureforms_forms = Helper::validate_request_context( 'sureforms_forms', 'page' ); - $is_screen_sureforms_form_settings = Helper::validate_request_context( 'sureforms_form_settings', 'page' ); - $is_screen_sureforms_payments = Helper::validate_request_context( 'sureforms_payments', 'page' ); - $is_screen_sureforms_entries = Helper::validate_request_context( SRFM_ENTRIES, 'page' ); - $is_screen_sureforms_learn = Helper::validate_request_context( 'sureforms_learn', 'page' ); - $is_screen_quiz_empty_state = Helper::validate_request_context( 'sureforms_quiz_entries', 'page' ); - $is_screen_survey_empty_state = Helper::validate_request_context( 'sureforms_survey_reports', 'page' ); - $is_post_type_sureforms_form = SRFM_FORMS_POST_TYPE === $current_screen->post_type; + $is_screen_sureforms_menu = Helper::validate_request_context( 'sureforms_menu', 'page' ); + $is_screen_add_new_form = Helper::validate_request_context( 'add-new-form', 'page' ); + $is_screen_sureforms_forms = Helper::validate_request_context( 'sureforms_forms', 'page' ); + $is_screen_sureforms_form_settings = Helper::validate_request_context( 'sureforms_form_settings', 'page' ); + $is_screen_sureforms_payments = Helper::validate_request_context( 'sureforms_payments', 'page' ); + $is_screen_sureforms_entries = Helper::validate_request_context( SRFM_ENTRIES, 'page' ); + $is_screen_sureforms_learn = Helper::validate_request_context( 'sureforms_learn', 'page' ); + $is_screen_quiz_empty_state = Helper::validate_request_context( 'sureforms_quiz_entries', 'page' ); + $is_screen_survey_empty_state = Helper::validate_request_context( 'sureforms_survey_reports', 'page' ); + $is_screen_partial_entries_empty_state = Helper::validate_request_context( 'sureforms_partial_entries', 'page' ); + $is_post_type_sureforms_form = SRFM_FORMS_POST_TYPE === $current_screen->post_type; /** * Check if the current screen is the SureForms Menu and AI Auth Email is present then we will add user type as registered. @@ -980,9 +1015,14 @@ public function enqueue_scripts() { 'text' => __( 'Survey Reports', 'sureforms' ), 'link' => admin_url( 'admin.php?page=sureforms_survey_reports' ), ]; + $localization_data['additional_header_nav_items'][] = [ + 'slug' => 'sureforms_partial_entries', + 'text' => __( 'Partial Entries', 'sureforms' ), + 'link' => admin_url( 'admin.php?page=sureforms_partial_entries' ), + ]; } - $is_sureforms_screen = $is_screen_sureforms_menu || $is_post_type_sureforms_form || $is_screen_add_new_form || $is_screen_sureforms_forms || $is_screen_sureforms_form_settings || $is_screen_sureforms_entries || $is_screen_sureforms_payments || $is_screen_sureforms_learn || $is_screen_quiz_empty_state || $is_screen_survey_empty_state; + $is_sureforms_screen = $is_screen_sureforms_menu || $is_post_type_sureforms_form || $is_screen_add_new_form || $is_screen_sureforms_forms || $is_screen_sureforms_form_settings || $is_screen_sureforms_entries || $is_screen_sureforms_payments || $is_screen_sureforms_learn || $is_screen_quiz_empty_state || $is_screen_survey_empty_state || $is_screen_partial_entries_empty_state; /** * Filter to allow extending the SureForms dashboard screen check. @@ -1174,6 +1214,24 @@ public function enqueue_scripts() { $script_translations_handlers[] = SRFM_SLUG . '-survey-empty-state'; } + // Enqueue scripts for the Partial Entries empty state page (free users only). + if ( $is_screen_partial_entries_empty_state && ! Helper::has_pro() ) { + $asset_handle = 'partialEntriesEmptyState'; + + $script_asset_path = SRFM_DIR . 'assets/build/' . $asset_handle . '.asset.php'; + $script_info = file_exists( $script_asset_path ) + ? include $script_asset_path + : [ + 'dependencies' => [], + 'version' => SRFM_VER, + ]; + + wp_enqueue_script( SRFM_SLUG . '-partial-entries-empty-state', SRFM_URL . 'assets/build/' . $asset_handle . '.js', $script_info['dependencies'], SRFM_VER, true ); + wp_enqueue_style( SRFM_SLUG . '-partial-entries-empty-state', SRFM_URL . 'assets/build/' . $asset_handle . '.css', [], SRFM_VER, 'all' ); + + $script_translations_handlers[] = SRFM_SLUG . '-partial-entries-empty-state'; + } + // Admin Submenu Styles. wp_enqueue_style( SRFM_SLUG . '-admin', $css_uri . 'backend/admin' . $file_prefix . $rtl . '.css', [], SRFM_VER ); @@ -1198,6 +1256,10 @@ 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 ); + $script_translations_handlers[] = SRFM_SLUG . '-settings'; } @@ -2011,12 +2073,7 @@ private function render_dashboard_widget_footer( $entries_data ) { get_random_premium_feature_text() ); ?> 'dashboard-widget', - ], - Helper::get_sureforms_website_url( 'pricing' ) - ); + $upgrade_url = Helper::get_sureforms_website_url( 'pricing', [ 'utm_medium' => 'dashboard-widget' ] ); ?> diff --git a/assets/js/unminified/frontend.js b/assets/js/unminified/frontend.js index 49cfab4cc..e383b5868 100644 --- a/assets/js/unminified/frontend.js +++ b/assets/js/unminified/frontend.js @@ -110,6 +110,69 @@ function srfmSprintfString( str, ...args ) { addGlobalSrfmObject( 'toggleErrorState', toggleErrorState ); addGlobalSrfmObject( 'srfmSprintfString', srfmSprintfString ); +// Character counter for textarea/input fields with min or max length configured. +const SRFM_CHAR_COUNTER_FIELD_SELECTOR = + 'textarea.srfm-input-textarea, input.srfm-input-input'; + +// Refresh a single counter's text and over/under-limit error state from its field's current value. +function updateCharCounter( counter ) { + const block = counter.closest( '.srfm-block' ); + if ( ! block ) { + return; + } + const field = block.querySelector( SRFM_CHAR_COUNTER_FIELD_SELECTOR ); + if ( ! field ) { + return; + } + + const limit = counter.getAttribute( 'data-counter-limit' ); + const min = counter.getAttribute( 'data-counter-min' ); + const len = field.value.length; + + counter.textContent = len + '/' + limit; + + const tooShort = min && len < parseInt( min, 10 ); + const tooLong = limit && len > parseInt( limit, 10 ); + counter.classList.toggle( + 'srfm-char-counter--error', + Boolean( tooShort || tooLong ) + ); +} + +// Initial paint for every counter already in the DOM (handles pre-filled default values). +function initCharCounters() { + document + .querySelectorAll( '.srfm-char-counter' ) + .forEach( updateCharCounter ); +} + +// Delegated input handler so counters keep working for fields added or revealed +// after initial paint — multi-step navigation, conditional logic, and AJAX-loaded forms. +document.addEventListener( 'input', ( e ) => { + const field = e.target; + if ( + ! field || + typeof field.matches !== 'function' || + ! field.matches( SRFM_CHAR_COUNTER_FIELD_SELECTOR ) + ) { + return; + } + const block = field.closest( '.srfm-block' ); + if ( ! block ) { + return; + } + const counter = block.querySelector( '.srfm-char-counter' ); + if ( counter ) { + updateCharCounter( counter ); + } +} ); + +if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', initCharCounters ); +} else { + initCharCounters(); +} + // Sender's Email. const emailElements = document.getElementsByClassName( diff --git a/inc/abilities/ABILITIES.md b/inc/abilities/ABILITIES.md index 158fe3bed..e4f2bd45e 100644 --- a/inc/abilities/ABILITIES.md +++ b/inc/abilities/ABILITIES.md @@ -346,7 +346,8 @@ Complete reference for all 15 abilities registered with the WordPress Abilities "submission_info": { "user_ip": "192.168.1.1", "browser_name": "Chrome", - "device_name": "Desktop" + "device_name": "Desktop", + "submission_url": "https://example.com/contact/" }, "user": { "id": 1, diff --git a/inc/abilities/entries/entry-parser.php b/inc/abilities/entries/entry-parser.php index e3b948dc3..cf40c33be 100644 --- a/inc/abilities/entries/entry-parser.php +++ b/inc/abilities/entries/entry-parser.php @@ -87,9 +87,12 @@ protected function parse_entry( array $entry ) { } $submission_info = [ - 'user_ip' => $ip, - 'browser_name' => (string) ( $submission_info_raw['browser_name'] ?? '' ), - 'device_name' => (string) ( $submission_info_raw['device_name'] ?? '' ), + 'user_ip' => $ip, + 'browser_name' => (string) ( $submission_info_raw['browser_name'] ?? '' ), + 'device_name' => (string) ( $submission_info_raw['device_name'] ?? '' ), + // Re-sanitize at the exposure boundary in case the stored value + // was written by a future code path that bypasses form-submit.php. + 'submission_url' => esc_url_raw( (string) ( $submission_info_raw['submission_url'] ?? '' ), [ 'http', 'https' ] ), ]; // Build user info. diff --git a/inc/abilities/forms/create-form.php b/inc/abilities/forms/create-form.php index a4323e686..b0944f2a7 100644 --- a/inc/abilities/forms/create-form.php +++ b/inc/abilities/forms/create-form.php @@ -331,7 +331,14 @@ public function execute( $input ) { // Get default post meta. $post_metas = Create_New_Form::get_default_meta_keys(); - // Add serialized meta defaults for metadata overrides. + // Pull complex-meta defaults from the registered schemas so created + // forms are identical to hand-created ones. Storing empty arrays/strings + // would override the register_post_meta 'default' values (WordPress only + // uses those defaults when no value is stored at all), causing confirmation + // messages, email notifications, styling, and restriction settings to be + // blank for any form created through this code path. + $registered = get_registered_meta_keys( 'post', SRFM_FORMS_POST_TYPE ); + $post_metas['_srfm_instant_form_settings'] = [ 'bg_type' => 'color', 'bg_color' => '#ffffff', @@ -345,11 +352,11 @@ public function execute( $input ) { 'single_page_form_title' => true, 'use_banner_as_page_background' => false, ]; - $post_metas['_srfm_form_confirmation'] = []; - $post_metas['_srfm_compliance'] = []; - $post_metas['_srfm_forms_styling'] = []; - $post_metas['_srfm_email_notification'] = []; - $post_metas['_srfm_form_restriction'] = ''; + $post_metas['_srfm_form_confirmation'] = $registered['_srfm_form_confirmation']['default'] ?? []; + $post_metas['_srfm_compliance'] = $registered['_srfm_compliance']['default'] ?? []; + $post_metas['_srfm_forms_styling'] = $registered['_srfm_forms_styling']['default'] ?? []; + $post_metas['_srfm_email_notification'] = $registered['_srfm_email_notification']['default'] ?? []; + $post_metas['_srfm_form_restriction'] = $registered['_srfm_form_restriction']['default'] ?? ''; $post_metas['_srfm_form_custom_css'] = ''; // Apply metadata overrides from input. diff --git a/inc/abilities/forms/form-field-schema.php b/inc/abilities/forms/form-field-schema.php index 339df8a84..7c09c8681 100644 --- a/inc/abilities/forms/form-field-schema.php +++ b/inc/abilities/forms/form-field-schema.php @@ -102,6 +102,11 @@ protected function get_form_field_schema() { 'type' => 'integer', 'description' => __( 'Maximum character length.', 'sureforms' ), ], + // `placeholder` — @since 2.10.0 (added alongside the HTML-form converter) + 'placeholder' => [ + 'type' => 'string', + 'description' => __( 'Placeholder text shown inside the empty input/textarea or as the empty first option in a dropdown.', 'sureforms' ), + ], ]; /** @@ -143,6 +148,9 @@ protected function sanitize_form_fields( array $fields ) { if ( isset( $field['defaultValue'] ) && is_string( $field['defaultValue'] ) ) { $fields[ $index ]['defaultValue'] = sanitize_text_field( $field['defaultValue'] ); } + if ( isset( $field['placeholder'] ) && is_string( $field['placeholder'] ) ) { + $fields[ $index ]['placeholder'] = sanitize_text_field( $field['placeholder'] ); + } if ( ! empty( $field['fieldOptions'] ) && is_array( $field['fieldOptions'] ) ) { // @phpstan-ignore-next-line -- $field['fieldOptions'] is validated as array above. $field_options = $field['fieldOptions']; diff --git a/inc/admin-ajax.php b/inc/admin-ajax.php index c8d59c244..272f490cf 100644 --- a/inc/admin-ajax.php +++ b/inc/admin-ajax.php @@ -426,7 +426,15 @@ public function download_export_file() { $content_type = 'text/csv'; } elseif ( 'zip' === $file_info['extension'] ) { $content_type = 'application/zip'; - $filename = 'SureForms Entries.zip'; + /** + * Filter the user-facing filename used when serving an exported ZIP archive. + * + * @since 2.9.0 + * + * @param string $filename Default ZIP filename. + * @param array $file_info pathinfo() result for the file being served. + */ + $filename = (string) apply_filters( 'srfm_export_zip_filename', 'SureForms Entries.zip', $file_info ); } } diff --git a/inc/admin/editor-nudge.php b/inc/admin/editor-nudge.php index 87ae268b8..20ed9c096 100644 --- a/inc/admin/editor-nudge.php +++ b/inc/admin/editor-nudge.php @@ -181,13 +181,34 @@ public function enqueue_scripts() { SRFM_VER ); + // Editor nudge UTM attribution — start. + // Tag the "Create Form" CTA with the same UTM scheme used by other + // SureForms nudge links so the destination flow can attribute the + // click. Only fills keys not already present in the URL. + $create_form_url = admin_url( 'admin.php?page=add-new-form' ); + $nudge_utm = [ + 'utm_source' => 'sureforms_plugin', + 'utm_medium' => 'editor_nudge', + 'utm_campaign' => 'core_plugin', + ]; + $existing_args = []; + $query_string = wp_parse_url( $create_form_url, PHP_URL_QUERY ); + if ( is_string( $query_string ) && '' !== $query_string ) { + parse_str( $query_string, $existing_args ); + } + $missing_utm = array_diff_key( $nudge_utm, $existing_args ); + if ( ! empty( $missing_utm ) ) { + $create_form_url = add_query_arg( $missing_utm, $create_form_url ); + } + // Editor nudge UTM attribution — end. + wp_localize_script( $handle, 'srfm_editor_nudge', [ 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE_ACTION ), - 'create_form_url' => admin_url( 'admin.php?page=add-new-form' ), + 'create_form_url' => $create_form_url, 'message' => __( 'Hey! It looks like you\'re creating a form. Build a ready-to-use form in under 30 seconds with SureForms AI, with no extra setup required.', 'sureforms' ), 'button_label' => __( 'Create Form', 'sureforms' ), 'logo_url' => SRFM_URL . 'admin/assets/sureforms-logo.png', diff --git a/inc/admin/html-form-detector.php b/inc/admin/html-form-detector.php new file mode 100644 index 000000000..5bff593ac --- /dev/null +++ b/inc/admin/html-form-detector.php @@ -0,0 +1,1012 @@ +` markup and offers a one-click conversion to a SureForms form. + * + * This is the free-plugin prototype: detection + UI only. The conversion + * callback in the JS layer currently parses locally and logs the result; + * the AI-assisted REST endpoint that actually creates the form lives in a + * follow-up patch so the detection wiring can be validated independently. + * + * Script is enqueued when: + * - User can manage SureForms forms (same `manage_options` gate as the + * rest of the form-admin surface — there is no point offering a CTA to + * users who cannot create forms). + * - Current screen is the block editor and not the SureForms form CPT + * (we never run on the form editor itself — the source `
` only + * appears on host posts/pages). + * + * @package sureforms. + */ + +namespace SRFM\Inc\Admin; + +use SRFM\Inc\Abilities\Forms\Create_Form; +use SRFM\Inc\AI_Form_Builder\AI_Helper; +use SRFM\Inc\Helper; +use SRFM\Inc\Traits\Get_Instance; +use WP_Error; +use WP_REST_Request; +use WP_REST_Server; + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + +/** + * HTML form detector handler. + * + * @since 2.10.0 + */ +class Html_Form_Detector { + use Get_Instance; + + /** + * Confidence level below which we route the raw HTML through the AI + * middleware instead of trusting the local parser output. + * + * @since 2.10.0 + */ + public const AI_FALLBACK_CONFIDENCE = 'low'; + + /** + * Hard cap on the size of raw HTML accepted by the conversion endpoint. + * + * Anything larger is almost certainly the entire page rather than a + * single `` and would waste an AI roundtrip on noise. Matches the + * upper bound the AI middleware tolerates for `query` payloads. + * + * @since 2.10.0 + */ + public const MAX_HTML_BYTES = 32768; + + /** + * Constructor. + * + * Registers the REST route only on admin / REST-dispatch requests + * — that is the only context where the route is reachable, and + * gating registration narrows the blast radius if the shared + * `Helper::get_items_permissions_check` is ever loosened by an + * unrelated change. The endpoint also re-checks `manage_options` + * inside the handler so authorization survives both contexts. + * + * @since 2.10.0 + */ + public function __construct() { + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); + + // Register the REST endpoint unconditionally. The constructor runs on + // the `init` hook (see plugin-loader.php), which fires *before* + // `parse_request` — the point at which WordPress defines + // `REST_REQUEST`. Gating on `REST_REQUEST` here meant the filter was + // never attached for the actual REST dispatch, and the endpoint 404'd. + // `apply_filters( 'srfm_rest_api_endpoints', ... )` is only invoked + // from `Rest_Api::register_endpoints()` on `rest_api_init`, so + // attaching this filter on non-REST requests has no runtime cost. + add_filter( 'srfm_rest_api_endpoints', [ $this, 'register_rest_endpoint' ] ); + } + + /** + * Decide whether the detector script should be loaded for the current request. + * + * @since 2.10.0 + * @return bool + */ + public function allow_load() { + if ( ! is_admin() ) { + return false; + } + + // Gate on the cap required to actually manage SureForms forms — same + // rationale as the Editor_Nudge: never surface a "Convert to + // SureForms" CTA to a user who cannot reach the form-creation flow. + if ( ! Helper::current_user_can( 'manage_options' ) ) { + return false; + } + + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + + if ( ! $screen || ! method_exists( $screen, 'is_block_editor' ) || ! $screen->is_block_editor() ) { + return false; + } + + // Skip on the SureForms form editor itself — the source `` + // markup we look for only appears on host posts/pages. + if ( SRFM_FORMS_POST_TYPE === $screen->post_type ) { + return false; + } + + return true; + } + + /** + * Enqueue the detector script when allowed. + * + * @since 2.10.0 + * @return void + */ + public function enqueue_scripts() { + if ( ! $this->allow_load() ) { + return; + } + + $handle = SRFM_SLUG . '-html-form-detector'; + $asset_path = SRFM_DIR . 'assets/build/htmlFormDetector.asset.php'; + $asset = file_exists( $asset_path ) + ? include $asset_path + : [ + 'dependencies' => [ 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n' ], + 'version' => SRFM_VER, + ]; + + wp_enqueue_script( + $handle, + SRFM_URL . 'assets/build/htmlFormDetector.js', + $asset['dependencies'], + $asset['version'], + true + ); + + wp_localize_script( + $handle, + 'srfm_html_form_detector', + [ + 'rest_nonce' => wp_create_nonce( 'wp_rest' ), + ] + ); + + Helper::register_script_translations( $handle ); + } + + /** + * Register the conversion REST endpoint on the existing SureForms route map. + * + * Hooked into `srfm_rest_api_endpoints` so we land alongside the other + * `sureforms/v1/*` routes without touching `Rest_Api::get_endpoints()` — + * keeping every concern of the detector co-located in this class. + * + * @since 2.10.0 + * @param array> $endpoints Existing endpoints map. + * @return array> + */ + public function register_rest_endpoint( $endpoints ) { + if ( ! is_array( $endpoints ) ) { + $endpoints = []; + } + + $endpoints['convert-html-form'] = [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'handle_convert_html_form' ], + 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], + 'args' => [ + 'parsed_fields' => [ + 'required' => false, + 'type' => 'array', + 'description' => __( 'Array of fields produced by the editor-side parser.', 'sureforms' ), + ], + 'submit_text' => [ + 'required' => false, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => '', + ], + 'confidence' => [ + 'required' => false, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => 'high', + ], + 'html' => [ + 'required' => false, + 'type' => 'string', + 'description' => __( 'Raw HTML of the source . Required when parser confidence is low so we can hand the markup to the AI middleware.', 'sureforms' ), + ], + 'form_title' => [ + 'required' => false, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => '', + ], + 'styling' => [ + 'required' => false, + 'type' => 'object', + 'description' => __( 'Best-effort styling descriptor (hex colors) extracted from inline styles on the source .', 'sureforms' ), + ], + ], + ]; + + return $endpoints; + } + + /** + * Convert a raw HTML form into a SureForms form. + * + * Flow: + * - If the editor-side parser returned `confidence === 'low'` AND raw + * HTML is supplied, send the HTML to the AI middleware and use the + * structured schema it returns (hybrid path — AI handles markup the + * deterministic parser could not confidently classify). + * - Otherwise trust the parsed fields and pass them straight to the + * existing `Create_Form` ability so the same code that creates AI- / + * MCP-generated forms also handles this conversion. Means a single + * code path produces the final `sureforms_form` CPT; no parallel + * insert logic to maintain. + * + * @since 2.10.0 + * @param WP_REST_Request $request REST request. + * @return array|\WP_Error + */ + public function handle_convert_html_form( $request ) { + // Capability check runs first — cheaper than `wp_verify_nonce`, + // and the REST framework's `permission_callback` already passed + // at this point, so a failure here means a `current_user_can` + // filter or capability-mapping shim was loosened between the + // permission callback and the handler. Bail early to keep + // the expensive AI middleware path off the table for users who + // could not legitimately complete the action. + if ( ! Helper::current_user_can( 'manage_options' ) ) { + return new WP_Error( + 'srfm_html_convert_forbidden', + __( 'You are not allowed to convert HTML forms.', 'sureforms' ), + [ 'status' => 403 ] + ); + } + + // Nonce check is CSRF defense for the legitimate + // `manage_options` user we just confirmed. Running it after the + // cap check means cap-less requests never pay the + // `hash_hmac`/session-lookup cost of nonce verification, and + // the error-code precedence is also more honest: a subscriber + // without `manage_options` gets `forbidden`, not the misleading + // `nonce_failed`. + $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ); + if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) { + return new WP_Error( + 'srfm_html_convert_nonce_failed', + __( 'Security verification failed. Please refresh the page and try again.', 'sureforms' ), + [ 'status' => 403 ] + ); + } + + $raw_fields = $request->get_param( 'parsed_fields' ); + $confidence = Helper::get_string_value( $request->get_param( 'confidence' ) ); + $raw_html = Helper::get_string_value( $request->get_param( 'html' ) ); + $form_title = Helper::get_string_value( $request->get_param( 'form_title' ) ); + $submit_text = Helper::get_string_value( $request->get_param( 'submit_text' ) ); + $styling_in = $request->get_param( 'styling' ); + $styling_in = is_array( $styling_in ) ? $styling_in : []; + $used_ai = false; + + if ( '' === $form_title ) { + $form_title = __( 'Converted form', 'sureforms' ); + } + + // AI fallback path. We only invoke the middleware when the local + // parser flagged the input as ambiguous AND the caller actually + // supplied raw HTML — otherwise there is nothing useful to send. + if ( self::AI_FALLBACK_CONFIDENCE === $confidence && '' !== $raw_html ) { + if ( strlen( $raw_html ) > self::MAX_HTML_BYTES ) { + return new WP_Error( + 'srfm_html_convert_too_large', + __( 'The HTML form is too large to convert. Please simplify the markup or build the form manually.', 'sureforms' ), + [ 'status' => 413 ] + ); + } + + $ai_fields = $this->extract_fields_via_ai( $raw_html ); + if ( is_wp_error( $ai_fields ) ) { + return $ai_fields; + } + $raw_fields = $ai_fields; + $used_ai = true; + } + + if ( ! is_array( $raw_fields ) || empty( $raw_fields ) ) { + return new WP_Error( + 'srfm_html_convert_no_fields', + __( 'No fields could be derived from the supplied form.', 'sureforms' ), + [ 'status' => 400 ] + ); + } + + // Strip the parser-internal hints (`_groupName`, `_optionValue`, + // `confidence`) before handing fields to Create_Form — the schema + // for that ability rejects unknown keys via additionalProperties. + $clean_fields = $this->strip_internal_hints( $raw_fields ); + + /** + * Filter the field list before handing it to `Create_Form`. + * + * Lets extensions (notably SureForms Pro) re-inspect the raw + * source HTML and refine the parsed fields — e.g. promote a + * `` from a plain `input` to a `date-picker` + * block when the pro field type is registered. The JS parser + * cannot do this on its own because the pro field types are + * only valid when the pro plugin is active; gating that on the + * server is simpler and avoids leaking pro-specific behavior + * into the public block-editor bundle. + * + * @since 2.10.0 + * @param array> $clean_fields Sanitized field list ready for Create_Form. + * @param string $raw_html Original HTML of the source `` block. + * @param string $confidence Parser confidence (`high`/`medium`/`low`). + * + * SECURITY CONTRACT: callbacks MUST return values already + * sanitized for storage as block attributes. `Create_Form` + * re-sanitizes a hardcoded list of properties (label, + * placeholder, helpText, defaultValue, fieldOptions), but any + * property a callback introduces beyond that set — a pro + * field's `allowedFormats`, `dateFormat`, `step`, etc. — is NOT + * covered by the downstream sanitization pass. Strings should + * pass through `sanitize_text_field` / `wp_kses_post`, scalars + * through `absint` / `floatval`, arrays should have each leaf + * sanitized. The defensive `strip_unsafe_html_in_fields` pass + * below catches obvious raw-tag injection but is not a + * substitute for proper per-property sanitization. + */ + $clean_fields = apply_filters( 'srfm_html_form_detector_refine_fields', $clean_fields, $raw_html, $confidence ); + + // Defensive post-filter sweep: strip raw HTML tags from every + // string leaf in every field property. The filter contract + // above documents that callbacks must sanitize, but a sloppy + // callback could re-introduce attacker markup in properties + // `Create_Form` does not know to clean — at which point a + // later block renderer that emits the attribute as inner HTML + // becomes a stored-XSS sink. Stripping tags here is a narrow + // safety net that loses no legitimate value: form-field + // attributes are not HTML containers. + $clean_fields = $this->strip_unsafe_html_in_fields( $clean_fields ); + + $create_form = new Create_Form(); + $result = $create_form->execute( + [ + 'formTitle' => $form_title, + 'formFields' => $clean_fields, + 'formStatus' => 'publish', + 'formMetaData' => $this->build_form_metadata( $submit_text, $styling_in ), + ] + ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + // Layer in the native form-card styling (background, padding, + // border radius). These live in `_srfm_forms_styling` and are + // exposed in the per-form Styling sidebar — the same UI users get + // when they build a form by hand — so populating them keeps the + // converted form fully editable post-creation instead of locking + // the look behind opaque custom CSS. + $form_id = isset( $result['form_id'] ) ? Helper::get_integer_value( $result['form_id'] ) : 0; + if ( $form_id > 0 ) { + $this->apply_native_card_styling( $form_id, $styling_in ); + + /** + * Fires after the converter writes its baseline form + * metadata, giving extensions a chance to layer in + * additional `_srfm_forms_styling` keys — e.g. a pro + * `form_theme` preset chosen from inline-style hints. + * + * @since 2.10.0 + * @param int $form_id Newly-created SureForms form ID. + * @param array $styling Parser styling descriptor (inline-style hints). + * @param string $raw_html Original HTML of the source `` block. + */ + do_action( 'srfm_html_form_detector_after_styling', $form_id, $styling_in, $raw_html ); + } + + // Compute the markup that survives once the source `` is + // removed from the original block — wrapping `
`s, a + // heading above the form, a post-submit paragraph below it, + // inline `' + . '' + . ''; + + $scrubbed = $this->call_protected( 'scrub_html_for_ai', [ $html ] ); + + // Hidden input gone. + $this->assertStringNotContainsString( 'type="hidden"', $scrubbed ); + $this->assertStringNotContainsString( 'secret-token-123', $scrubbed ); + + // Pre-filled value gone, but the structural element survives. + $this->assertStringNotContainsString( 'prefilled@example.com', $scrubbed ); + $this->assertStringContainsString( 'name="email"', $scrubbed ); + + // `required` boolean attribute is preserved (no `value=` to strip). + $this->assertStringContainsString( 'required', $scrubbed ); + + // `action` attribute is gone. + $this->assertStringNotContainsString( '/internal/submit', $scrubbed ); + + // HTML comments are stripped — they routinely host staging + // notes / build hashes / API keys that have no business going + // to the conversion middleware. + $this->assertStringNotContainsString( 'INTERNAL_API_KEY', $scrubbed ); + $this->assertStringNotContainsString( 'do-not-leak', $scrubbed ); + + // Textarea body is emptied so pre-filled drafts (often PII, + // CSRF tokens, or server-rendered defaults) do not leave the + // site. The opening/closing tags survive so the model still + // sees a `#', $scrubbed ); + + // `#', $scrubbed ); + + // Non-string input collapses to empty string defensively. + $this->assertSame( '', $this->call_protected( 'scrub_html_for_ai', [ null ] ) ); + } + + public function test_build_form_metadata() { + $meta = $this->call_protected( + 'build_form_metadata', + [ + 'Submit Now', + [ + 'textColor' => '#111111', + 'primaryColor' => '#c2410c', + 'textColorOnPrimary' => '#ffffff', + ], + ] + ); + + $this->assertSame( 'Submit Now', $meta['general']['submitText'] ); + $this->assertFalse( $meta['instantForm']['showTitle'] ); + $this->assertSame( '#111111', $meta['formStyling']['textColor'] ); + $this->assertSame( '#c2410c', $meta['formStyling']['primaryColor'] ); + $this->assertSame( 'medium', $meta['formStyling']['fieldSpacing'] ); + + // Empty submit text omits the general slice. + $meta = $this->call_protected( 'build_form_metadata', [ '', [] ] ); + $this->assertArrayNotHasKey( 'general', $meta ); + + // Falsy / invalid hex falls back to the safe default. + $meta = $this->call_protected( 'build_form_metadata', [ '', [ 'textColor' => 'not-a-hex' ] ] ); + $this->assertSame( '#1E1E1E', $meta['formStyling']['textColor'] ); + + // `formBackgroundColor` is intentionally NOT applied through + // `formMetaData` — the `Form_Metadata` trait only exposes the + // colors + field-spacing slice, so the background is routed + // through `apply_native_card_styling` to `_srfm_forms_styling` + // instead. Asserting it landed in `instantForm` would lock in + // a contract the method explicitly disclaims (see the comment + // at the bottom of `build_form_metadata`). That assertion is + // covered by `test_apply_native_card_styling` below. + $meta = $this->call_protected( 'build_form_metadata', [ '', [ 'formBackgroundColor' => '#abcdef' ] ] ); + $this->assertArrayNotHasKey( 'formBackgroundColor', $meta['instantForm'] ); + } + + public function test_pick_hex() { + $this->assertSame( '#abcdef', $this->call_protected( 'pick_hex', [ '#abcdef', '#000000' ] ) ); + $this->assertSame( '#000000', $this->call_protected( 'pick_hex', [ '', '#000000' ] ) ); + $this->assertSame( '#000000', $this->call_protected( 'pick_hex', [ 'not-a-hex', '#000000' ] ) ); + $this->assertSame( '#000000', $this->call_protected( 'pick_hex', [ null, '#000000' ] ) ); + } + + public function test_strip_internal_hints() { + $result = $this->call_protected( + 'strip_internal_hints', + [ + [ + [ + 'label' => 'Name', + 'fieldType' => 'input', + '_groupName' => 'group_a', + '_optionValue' => 'val', + '_groupLabel' => 'Group A', + 'confidence' => 'high', + ], + 'not-an-array-entry', + ], + ] + ); + + $this->assertCount( 1, $result ); + $this->assertSame( 'Name', $result[0]['label'] ); + $this->assertArrayNotHasKey( '_groupName', $result[0] ); + $this->assertArrayNotHasKey( '_optionValue', $result[0] ); + $this->assertArrayNotHasKey( '_groupLabel', $result[0] ); + $this->assertArrayNotHasKey( 'confidence', $result[0] ); + } + + public function test_strip_unsafe_html_in_fields() { + // Simulates a sloppy `srfm_html_form_detector_refine_fields` + // callback that re-introduces raw HTML in properties + // `Create_Form` does not know to sanitize. The defensive sweep + // must strip tags from every string leaf at any depth. + $result = $this->call_protected( + 'strip_unsafe_html_in_fields', + [ + [ + [ + 'label' => 'Name ', + 'fieldType' => 'input', + 'helpText' => 'Hint ', + 'fieldOptions' => [ + [ + 'label' => 'Yes