Skip to content

Sync master from upstream - #144

Merged
vanshk141999 merged 213 commits into
masterfrom
sync/master-20261014
Oct 15, 2026
Merged

vanshk141999 merged 213 commits into
masterfrom
sync/master-20261014

Conversation

@vanshk141999

@vanshk141999 vanshk141999 commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Syncs private master up to 4153a5d38 (SureForms 2.12.9, released 2026-10-13) into the public mirror.

  • Range: private master → 4153a5d38. 212 commits are new to the mirror; the rest re-signed to SHAs already on the mirror.
  • Strip applied: yes. Internal-only paths were removed (.claude, internal-docs, docs, all CLAUDE.md, analysis docs, internal release workflows and bin scripts). README.md is the only markdown file in this diff.
  • Signatures: every commit is re-signed and verified.
  • The branch is capped with a merge on master, so this diff only shows real upstream changes (155 files), with no internal-file deletions.

Highlights (2.12.9 changelog)

  • New: Added the srfm_submission_accepted developer action, fired as soon as a submission is accepted.
  • New: Forms that do not store entries now warn you when a failed notification loses a submission.
  • New: MCP Adapter can now be installed and activated in one click.
  • Improvement: Failed notifications are no longer silently retried through PHP's mail(); the reason now shows in Entry Logs and Form Checks. Use the srfm_allow_php_mail_fallback filter to restore it.
  • Improvement: Smart tags such as {get_input:name} now resolve only inside SureForms form content. A tag written in ordinary page or post content is shown as written.
  • Fix: Deleting an option in a duplicated Multiple Choice block no longer removes it from the original.
  • Fix: Form fields no longer fail accessibility scans for a missing error message reference.
  • Fix: The submit button now stays disabled while a form redirects, preventing duplicate submissions.
  • Fix: Unticked Checkbox fields now appear in entries and notification emails.
  • Fix: This update addressed a security bug. Props to dzaku from Patchstack for reporting it responsibly to our team.

🤖 Generated with Claude Code

vanshk141999 and others added 30 commits September 25, 2026 15:29
…licated

deleteOption() was calling options.splice() which mutated the original
array in-place. When a block is duplicated in Gutenberg the new block's
attributes initially share the same array reference, so the mutation
affected both blocks simultaneously.

Additionally, setAttributes received `deleteOptions` as the key instead
of `options`, making the update a no-op and leaving the shared reference
intact.

Fixes by replacing splice/map with filter (immutable) and correcting
the setAttributes key to `options`.

Fixes #3174

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vigates

handleFormSubmission() re-enabled the submit button and hid the loader
right after redirectToUrl(), while the old page was still on screen. A
second tap in that window resubmitted into a request the unload then
cancelled (Safari: "TypeError: Load failed"). submitFormData() logged it
as type 'error', which is_fault() counts, so one tap sent the site owner
the "form submissions are failing" alert, and the visitor saw the raw
browser text.

- After a redirect that leaves the page, keep the button disabled and the
  loader on. Release them on a back/forward cache restore, or after 15s
  for a redirect that never unloads the page (e.g. a file download). A
  hash-only URL on the same page does not unload, so it releases at once.
- Track navigatingAway (set by redirectToUrl() and pagehide, cleared on a
  bfcache restore). A request that fails while it is set is logged as
  'network' with no status, which is_fault() does not count.
- Do not pass native TypeError / SyntaxError text to showErrorMessage();
  the empty message falls back to the translated generic error, as Pro's
  login handler already does.
- Test: pin that a status-less 'network' entry is not a fault.

Fixes #3173
…llback

From the self-review of #3175:

- mailto:, tel: and other non-http(s) URLs hand off to another app and
  never unload the page, so they release the button at once instead of
  after 15s.
- A URL ending in an empty "#" is a same-page fragment navigation too.
- The 15s fallback proves the page did not leave, so it clears
  navigatingAway. Otherwise a later real failure on that page would be
  logged as a non-fault. If the navigation commits later, pagehide sets
  the flag again before the unload.
- release() removes its pageshow listener, so repeated non-unloading
  redirects do not stack listeners.
- Reword the raw-error comment as the rule it enforces.
…nerate with AI screen

- Four free-safe starter cards (Contact, Quote request, Event registration,
  Feedback) that prefill the prompt; clicking again clears an untouched prompt.
- Pick / Customize / Generate progress steps and event-driven Learn tips.
- Generate stays aria-disabled until the prompt has text, with a hint.
- Inline Undo when a card replaces the user's own prompt.
- 0/2000 character counter; voice input adds a separator and respects the limit.
- Layout uses max-width and scrolls on short or narrow screens.
- Replace the resume-upload example prompt with free-supported wording.
- New srfm.aiFormScreen.starterPrompts filter with jest coverage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ated

Gutenberg's cloneBlock() does a shallow spread of block attributes, so
duplicated blocks share the same array references as the original (e.g.
the options array in multi-choice). Any in-place mutation on one block's
array would silently affect the other.

On duplicate detection, iterate all attributes and shallow-clone any
array values (one level deep is sufficient since option objects contain
only primitives) so each block immediately owns its own independent copy.

This is the root-cause fix. The deleteOption immutability fix in
multi-choice/edit.js is still correct and prevents future regressions,
but without this change the shared reference persists until the first
setAttributes({ options }) call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a narrow lifecycle hook right after Entries::add() succeeds, before
notification mail is sent, and on the compliance "do not store entries"
accepted path (entry id 0). It carries the parsed request data, so a
listener can correlate the submission with non-field keys the client sent.

Pro's Form Abandonment & Recovery (sureforms-pro#1519) uses it to mark an
unfinished capture submitted and stop its pending reminder without waiting
behind a slow mail transport. srfm_form_submit is unchanged.
- Send the selected starter card id and an edited flag with create-new-form.
- Store the card id on the form as _srfm_ai_starter.
- New ai_starter_usage event (forced, cumulative): total AI forms, forms per
  starter card and edited count, so card share is queryable in ClickHouse.
- Add ai_starter to the first_form_published event properties.
- handleAddNewPost() accepts optional extra request data.
- PHPUnit coverage for the counters, key cap and pending-event overwrite.
Forces healthy AI usage details before boot (local/CI domains fail middleware
verification) and stubs generate-form, then checks each card sends its prompt,
the inactive Generate hint, deselect and Undo.
The starter cards and Learn tips already guide the flow; the indicator added
visual noise without semantics for assistive tech.
- New AI_Helper::has_ai_generated_forms() (one-row lookup), localized as
  srfm_admin.has_ai_generated_forms on the Generate with AI screen only.
- After the first AI form the cards collapse to "Need ideas? Show examples";
  expanding moves focus to the first card. The Learn flow always shows them.
- PHPUnit + Playwright coverage.
… it replaced

Edited prompts are still kept on deselect. Adds Playwright coverage for both.
Starter usage is counted at creation time, so the per-form meta only fed a
one-time first_form_published property. Remove it (and its export/duplicate
side effects) and sanitize the starter id inside track_ai_starter_usage()
instead of relying on the caller.
… box

It sat below the credits/upgrade banner, so the blank-form option was pushed
down the page.
- Learn / generate tips open above their target, so they no longer overflow
  the scroll container at ~1280px (clipped tip + horizontal scrollbar).
- Clicking Generate with an empty prompt also opens collapsed examples, since
  the hint tells the user to pick one.
- Collapsed state uses its own subtitle.
- Deselecting an edited example announces that the edits were kept.
- Drop the always-false aria-expanded on "Show examples".
- has_ai_generated_forms(): skip the sort (orderby none), accurate docblock.
Stubs generate-form and map-fields as successful and captures the
create-new-form body for an untouched card, an edited card and free text.
Also type $starter_id as mixed in the tracker docblock.
adi3890 and others added 29 commits October 12, 2026 12:41
… fix (#3150)

Review: sureforms#3214 (optional)
…-review

Email delivery: review fixes for #3214 (stale SureMail status, lost-submission count)
Email delivery: SureMail + SureContact onboarding, delivery status and honest failures (#3150)
feat: fire srfm_submission_accepted as soon as a submission is accepted
…-submit-disabled

fix: keep the submit button disabled while a redirect confirmation navigates
feat(analytics): track AI forms created from starter cards
# Conflicts:
#	tests/unit/admin/test-analytics.php
feat(ai-builder): starter prompt cards on Generate with AI
…t, and always clean up its cap filter

WordPress trunk (7.2-alpha) wp_kses_post() drops a <script>'s contents as
well as its tags, so the exact-output assertion failed. It failed before
its own cleanup, leaking the "no unfiltered_html" filter into every later
test: the WPForms importer and page-search tests then saved through kses
and failed too. Both outputs are accepted now, and the filter and user are
removed in a finally block.
- Smart tags now resolve only in SureForms form content: get_form_markup()
  marks its whole render as form content, the srfm/form block resolves for a
  real form, and a form post's own page still resolves. check_form_by_id() is
  true only for a sureforms_form post.
- {get_input:name} and {get_cookie:name} must match in full.
- get_input / get_cookie values with a javascript:, vbscript: or data: scheme
  are dropped (numeric character references decoded at any code point), and
  braces in values are returned as character references so a later pass
  cannot resolve a tag inside a value.
- get_form_markup() body moved to render_form_markup() so the render context
  is restored on every return path.
…eckbox-fields

fix: keep an unticked checkbox in the entry and in notification emails
…by-error-ref

fix: only reference the field error message while it is shown (#3080)
…to dev-nr-2.12.9

# Conflicts:
#	tests/unit/admin/test-admin.php
…ng post title (#3219)

Round 2 review.

H1 The form-post fallback in get_block_form_id() read the global post, which a
    query loop sets to each form it renders while another post is on screen. A
    block in that post then resolved its own tags, including {get_cookie:} for
    the viewer's session. The fallback is now tied to the queried object, so it
    only fires on the form's own page. WordPress' own cookie names are refused
    outright as defence in depth.

H2 {embed_post_title} returned the raw title into a field default that had
    already been escaped as the literal tag, so a quote in the title reached the
    value attribute. It is escaped now, as {form_title} already was.

M1 @SInCE 2.12.9 -> x.x.x in 9 places; the release PR fills these in.
M2 Only leading whitespace and in-scheme tab/CR/LF are collapsed before the
    scheme check, so ordinary values keep their interior spaces.
M3 Changelog lines for the security fix and the behaviour change.
L1 Combining marks in request tag names, for Indic and Thai parameters.
L3 blockId is reduced to CSS-safe characters before it reaches a selector.
L4 The cheap brace test runs before get_block_form_id() on every block.
L5 Smart_Tags::get_instance() instead of a new render_block filter per call.

Not done: L2. Making render_form_markup() private trips PHP Insights' ordered
class elements rule, which wants the ~700-line body moved.
Resolve the readme.txt conflict: fold this PR's smart tags Improvement
and security Fix lines into the 2.12.9 entry, and set the PR's
@SInCE x.x.x placeholders to 2.12.9 since it ships in 2.12.9.
…m-scope

fix(security): resolve smart tags only in form content (#3219)
Auto-generated by /i18n command on PR #3225
@vanshk141999
vanshk141999 merged commit ef616c8 into master Oct 15, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants