Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
223baf2
feat(entries): pass per-log retry metadata through the logs REST resp…
vanshk141999 Jun 23, 2026
2d02c2b
Merge pull request #2906 from brainstormforce/master-dev-2.12.0-post
vanshk141999 Jun 24, 2026
3c02453
Merge pull request #2907 from brainstormforce/dev-nr-2.12.0-post
vanshk141999 Jun 24, 2026
16429a6
Merge remote-tracking branch 'origin/next-release' into feat/entry-lo…
vanshk141999 Jun 25, 2026
d48fe21
fix(editor): import createRoot from @wordpress/element instead of rea…
vanshk141999 Jun 25, 2026
2d30f51
fix(quick-action-sidebar): import createRoot from @wordpress/element
vanshk141999 Jun 25, 2026
9f0c1ef
chore: expand sync-public strip surface (nested CLAUDE.md, docs/, TOD…
vanshk141999 Jun 26, 2026
1e767d2
feat(email): enforce RFC 5321 length limits with filter (#2904)
vanshk141999 Jun 26, 2026
ffa3ca0
fix(email): reveal the char-limit error message (was border-only)
vanshk141999 Jun 26, 2026
95af6e6
refactor(email): split the length error into part-specific messages
vanshk141999 Jun 26, 2026
6dbce65
chore: make sync-public sanity check fail-closed; address review
vanshk141999 Jun 26, 2026
e1ff28e
fix: dispatch srfm_form_reset event and reset Dropdown on form reset
avi1080p Jun 26, 2026
df055e5
fix: restore bullet point visibility in react-quill admin editor
avi1080p Jun 26, 2026
91ae043
fix: restore ordered list numbering in react-quill admin editor
avi1080p Jun 26, 2026
5dd4d8a
fix: restore bullet and number list visibility on the frontend
avi1080p Jun 26, 2026
e62abb3
fix: apply Quill editor formatting in the confirmation message display
avi1080p Jun 27, 2026
58757ac
fix: use inline ::before markers in confirmation message for correct …
avi1080p Jun 27, 2026
8215845
Add dashboard AI quick draft flow with analytics tracking
mohitsbsftester May 14, 2026
b1a54bc
test: cover AI quick draft dashboard widget functions
vanshk141999 Jun 29, 2026
371e6de
refactor: clarify dashboard widget registration comment
vanshk141999 Jun 29, 2026
99afb2e
fix(payments): pin one-time Stripe intent payload to card
vanshk141999 Jun 29, 2026
562b7e5
Merge pull request #2909 from brainstormforce/fix/react19-createroot-…
vanshk141999 Jun 29, 2026
6c4f6bc
Merge pull request #2910 from brainstormforce/chore/sync-public-strip…
vanshk141999 Jun 29, 2026
44db5c4
fix(payment): list fields nested in container blocks in Email/Name ma…
vanshk141999 Jun 29, 2026
15fda4c
fix: address adi review feedback on AI dashboard quick draft
vanshk141999 Jun 30, 2026
031cf70
fix(payment): exclude repeater children from field mappers, extract f…
vanshk141999 Jun 30, 2026
72a158e
test: add coverage for enqueue_ai_dashboard_widget_assets
vanshk141999 Jun 30, 2026
cda2a4e
fix(email): resolve osk review — shared limits source, blur message, …
vanshk141999 Jun 30, 2026
8bdc1c2
fix: address code review comments for Quill 1.x list marker CSS
avi1080p Jul 1, 2026
657c79d
Merge pull request #2915 from brainstormforce/feat/dashboard-ai-quick…
vanshk141999 Jul 2, 2026
0e64368
Merge pull request #2918 from brainstormforce/fix/payment-field-map-n…
vanshk141999 Jul 2, 2026
09dfcba
Merge pull request #2916 from brainstormforce/fix/stripe-one-time-car…
vanshk141999 Jul 2, 2026
afb2359
test(admin): add enqueue_styles coverage for Quill 1.x inline CSS
vanshk141999 Jul 4, 2026
b4663e6
Merge pull request #2914 from brainstormforce/fix/page-break-settings…
vanshk141999 Jul 4, 2026
04e4b75
Merge pull request #2912 from brainstormforce/fix/reset-form-custom-f…
vanshk141999 Jul 4, 2026
883f241
Merge pull request #2898 from brainstormforce/feat/entry-logs-retry-m…
vanshk141999 Jul 4, 2026
83adedf
Merge pull request #2911 from brainstormforce/feat/email-rfc5321-leng…
vanshk141999 Jul 5, 2026
958387b
Merge branch 'master' of https://github.com/brainstormforce/sureforms…
vanshk141999 Jul 5, 2026
6a5277b
Merge pull request #2920 from brainstormforce/master-dev-2.12.1
vanshk141999 Jul 6, 2026
007a6dd
Merge branch 'dev' of https://github.com/brainstormforce/sureforms in…
vanshk141999 Jul 6, 2026
e32097d
Merge pull request #2921 from brainstormforce/dev-nr-2.12.1
vanshk141999 Jul 6, 2026
df9507f
Version Bump 2.12.1
vanshk141999 Jul 6, 2026
a5cccf5
updated change log
vanshk141999 Jul 6, 2026
15e7523
updated change log
vanshk141999 Jul 6, 2026
40eaec0
Add changelog entry for email field RFC 5321 length limits (#2904)
vanshk141999 Jul 6, 2026
2db6e77
Merge pull request #2922 from brainstormforce/version-bump-2.12.1
vanshk141999 Jul 6, 2026
297c3a0
chore: update BSF Analytics library to 1.1.29
vanshk141999 Jul 7, 2026
94db7ee
Merge pull request #2924 from brainstormforce/update/bsf-analytics-1.…
adi3890 Jul 7, 2026
e869783
chore: update 2.12.1 release date to 8th July 2026
vanshk141999 Jul 8, 2026
498d56b
Merge pull request #2926 from brainstormforce/chore/2.12.1-release-date
vanshk141999 Jul 8, 2026
508bbfb
chore: update i18n translations
github-actions[bot] Jul 8, 2026
b97253e
Merge pull request #2927 from brainstormforce/i18n/next-release
vanshk141999 Jul 8, 2026
d3ba05a
Merge pull request #2923 from brainstormforce/next-release
vanshk141999 Jul 8, 2026
d817ef0
ci: use npm ci instead of npm install --force in deploy/asset workflows
vanshk141999 Jul 8, 2026
70b4593
ci: pin WP-CLI to v2.12.0 in update-translations workflow
vanshk141999 Jul 8, 2026
aaf64ea
Merge pull request #2929 from brainstormforce/ci/pin-wp-cli-2.12.0
vanshk141999 Jul 8, 2026
beb14c1
Merge pull request #2928 from brainstormforce/ci/npm-ci-deploy-workflows
vanshk141999 Jul 8, 2026
0c2ef09
chore: strip internal-only paths from public mirror sync
vanshk141999 Jul 9, 2026
6d53300
Sync master from upstream
vanshk141999 Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -458,6 +458,12 @@ 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 - 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.
* 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 ###
* 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.
Expand All @@ -468,9 +474,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 ##
Expand Down
192 changes: 192 additions & 0 deletions admin/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -120,10 +131,14 @@ 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' ] );

// 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' ] );
Expand Down Expand Up @@ -894,6 +909,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 );
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.
Expand Down Expand Up @@ -1334,6 +1350,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 );
wp_add_inline_style( SRFM_SLUG . '-reactQuill', self::QUILL_1X_INLINE_CSS );

$script_translations_handlers[] = SRFM_SLUG . '-settings';
}
Expand Down Expand Up @@ -1921,6 +1938,9 @@ public function maybe_register_dashboard_widget() {
return;
}

// 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.
$seven_days_ago = strtotime( '-7 days' );
$total_entries = Entries::get_entries_count_after( $seven_days_ago );
Expand Down Expand Up @@ -1956,6 +1976,178 @@ public function register_dashboard_widget() {
);
}

/**
* Register the AI quick draft dashboard widget.
*
* @return void
* @since 2.12.1
*/
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.12.1
*/
public function render_ai_dashboard_widget() {
?>
<div class="srfm-ai-dashboard-widget">
<p>
<?php esc_html_e( 'Describe the form and let SureForms AI generate it for you.', 'sureforms' ); ?>
</p>
<label for="srfm-ai-dashboard-prompt" class="screen-reader-text">
<?php esc_html_e( 'Describe your form', 'sureforms' ); ?>
</label>
<textarea
id="srfm-ai-dashboard-prompt"
class="widefat"
rows="5"
maxlength="2000"
placeholder="<?php esc_attr_e( 'Example: Create a contact form with name, email, phone, and message fields.', 'sureforms' ); ?>"
></textarea>
<p style="margin-top:10px;margin-bottom:0;display:flex;align-items:center;gap:10px;">
<button type="button" class="button button-primary" id="srfm-ai-dashboard-generate" disabled>
<?php esc_html_e( 'Create New Form', 'sureforms' ); ?>
</button>
<span id="srfm-ai-dashboard-char-count" style="color:#646970;">0/2000</span>
</p>
</div>
<?php
}

/**
* Enqueue the AI quick draft dashboard widget script on the dashboard screen.
*
* The widget's behavior lives here (attached via wp_add_inline_script) rather than as an
* inline <script> 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 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).
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.12.1
*/
public function track_ai_widget_usage() {
if ( ! check_ajax_referer( 'srfm_ai_widget_usage', 'nonce', false ) ) {
wp_send_json_error( [ 'message' => __( '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 ) + 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();
}

/**
* Render the dashboard widget content.
*
Expand Down
25 changes: 25 additions & 0 deletions assets/js/unminified/blocks/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } ) );
}
} );
} );
8 changes: 8 additions & 0 deletions assets/js/unminified/form-submit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
Expand Down
Loading