Skip to content

Sync master from upstream#104

Merged
adi3890 merged 128 commits into
masterfrom
sync/master-20260610
Jun 10, 2026
Merged

Sync master from upstream#104
adi3890 merged 128 commits into
masterfrom
sync/master-20260610

Conversation

@vanshk141999

Copy link
Copy Markdown
Collaborator

Syncs the public mirror with private master.

  • Upstream range: 7d3724dc6..c621ce372 (275 commits)
  • Strip applied: yes — internal-only paths removed in a single commit (chore: strip internal-only paths from public mirror sync), including AI tooling configs, internal CI workflows, internal docs/planning files, and nested CLAUDE.md/TODO.md files previously present on the mirror (removed as cleanup).
  • All commits are re-signed with verified signatures to satisfy the branch ruleset.
  • The branch is capped with a merge commit whose first parent is master on this repo, so this diff shows only real upstream changes — no internal files appear.

Highlights

c621ce372 Merge pull request #2837 from brainstormforce/next-release
2fe0a45d4 Merge pull request #2841 from brainstormforce/fix/i18n-verified-commits-master
24ace988d Merge pull request #2823 from brainstormforce/readme-update
e11019185 Merge pull request #2809 from brainstormforce/chore/sync-public-skill-ruleset-fix
fd78afd1f Merge pull request #2808 from brainstormforce/next-release
e823c3ccf Version 2.10.0 (#2779)
f6f9d0de8 Merge pull request #2745 from brainstormforce/chore/lock-deploys-to-development-env
0148f2bc6 Merge pull request #2759 from brainstormforce/next-release
2b089e4ab Merge pull request #2736 from brainstormforce/next-release

🤖 Generated with Claude Code

rahulvarma722 and others added 30 commits May 24, 2026 21:51
P1 foundation: REST endpoints, abstract Base_Migrator with idempotent
re-imports and unsupported-field tracking, a block-markup emitter for
each SureForms field block, a full Contact Form 7 importer (shortcode
parser plus admin notification and confirmation metadata stub), and a
four-step Migration tab inside SureForms -> Settings.

WPForms, Gravity Forms, Ninja Forms, and Caldera Forms importers are
planned for P2 and P3. See docs/FORM_MIGRATION_PLAN.md for the detailed
spec and docs/FORM_MIGRATION_OVERVIEW.md for a plain-language summary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… add CF7 tests

- block-templates: encode_attrs() now uses JSON_HEX_TAG|AMP|APOS|QUOT so CF7
  labels containing `-->` cannot break out of the Gutenberg block-comment
  delimiter and inject HTML into the SureForms editor.
- bootstrap: verify_nonce() returns WP_Error('rest_cookie_invalid_nonce', 403)
  instead of wp_send_json_error(); REST callbacks short-circuit on it so api.js
  receives a properly-shaped REST error response.
- cf7-importer: get_source_forms() restricts post_status to publish/draft/private
  so trashed CF7 forms don't silently re-import.
- tests: 8 PHPUnit cases (32 assertions) covering per-tag parser output, the
  XSS regression, idempotency on re-import, deletion recovery, and dry-run
  no-side-effects.
…subclass

- Add return types and typed array hints throughout the test file so it
  passes PHPStan level 9 when scanned directly (pre-push gate).
- Move Cf7_Importer_Testable to its own file (one class per file —
  PHP Insights architecture rule).
- Guard mixed-type array accesses via local helpers (first_srfm_id,
  first_preview) instead of direct casts.
Browser QA on a real WP install surfaced four blockers in the CF7 importer.
All four are fixed and covered by additional PHPUnit tests.

1. FormSelector list rendered as blank checkboxes — @bsf/force-ui Checkbox
   expects label as { heading } object, not a string. Both call sites updated.

2. CF7 templates wrapped in <p>...</p> leaked the literal "<p>" into field
   labels. wp_strip_all_tags() now runs over the captured label text before
   handing it to the JSON encoder.

3. wp_insert_post applies wp_unslash to post_content, which stripped the
   backslash from every JSON unicode escape — labels in the editor showed
   "u003Cp" instead of being properly escaped. wp_slash() the markup before
   insert and update.

4. Block_Templates::form_wrapper wrapped imported fields in a
   <!-- wp:srfm/form --> block. That block is the form-EMBED block (used in
   pages, expects formId); it is NOT the storage shape for the sureforms_form
   CPT. Wrapped imports rendered as the embed's empty "Select a Form"
   picker. Drop the wrapper.

Verified end-to-end on a Valet install across five fixtures: happy path,
every supported tag (text/email/url/tel/number/date/select/checkbox/radio/
textarea/acceptance), unsupported-fields telemetry (file/captcha), the XSS
regression (label with `--> <script>`), and re-import idempotency (count
of sureforms_form posts unchanged, map updated in place).
…edupe

Closes the P0 ship-blockers surfaced during the PO audit of PR #2773.

Re-import safety (B1.1):
- list_forms() now returns imported_srfm_id AND imported_srfm_edit_url.
- import_forms() accepts a per-source behavior map: update / skip / create.
- REST endpoint /sources/{key}/import accepts a `behavior` payload field.
- FormSelector renders a Select dropdown next to the "Previously imported"
  badge plus an external-link icon to open the existing SureForms post.
- DryRunPreview flips its CTA to "Update & import" when at least one form
  is being updated and nothing is being created fresh.
- Skipped forms are reported under a new `skipped` array in the response
  and surfaced on the result page as "Nothing was imported" when all
  selections are skipped.

Mail-template translation (B1.2):
- CF7 [your-name] field shortcodes are rewritten to SureForms {slug}
  smart tags based on a field-name → slug map built during parsing.
- CF7 system shortcodes (_post_title, _user_email, _remote_ip, _url,
  _date, _time, etc.) map to the equivalent SureForms smart tags.
- CF7 _mail.recipient becomes the SureForms email_to address (falls back
  to {admin_email} when unset).
- CF7 _messages.mail_sent_ok replaces the hardcoded "Thank you" stub on
  the form confirmation.

Slug collision dedupe (B1.3):
- build_field_from_tag_blob() seeds slugs from the CF7 field `name` attr
  (falling back to the label) and tracks emitted slugs in `used_slugs`;
  collisions get a -2, -3, ... suffix so submission handlers don't break
  silently on duplicate keys.
- Block_Templates::slug_from_args() honors an explicit `slug` arg so the
  importer can pass the deduped slug instead of letting Block_Templates
  derive it from the label.

CF7 addon detection (B1.4):
- collect_tag_blobs() now flags `[step]` (Multi-Step Forms) and `[group]`
  (Conditional Fields) shortcodes as unsupported with a dedicated label
  instead of silently flattening them into the imported form.

UX polish (B1.5 + B1.6):
- FormSelector and DryRunPreview surface REST error messages from the
  server (`err.message`) instead of generic "Could not load forms" /
  "Could not generate the import preview" strings.
- DryRunPreview now uses a synchronous inFlight ref to block double-click
  imports that could fire before React re-renders the disabled state.

Tests (B1.7):
- 9 new PHPUnit cases covering slug dedupe, mail-template translation
  (field and system shortcodes), mail_sent_ok confirmation, [step] +
  [group] warnings, list_forms enriched response, and behavior=skip /
  behavior=create import flows. 19/19 tests pass.

Browser QA performed on a Valet test-site for all five flows:
- Happy-path re-import surfaces the badge, external-link, and per-row
  behavior Select.
- Picking "Skip" leaves the existing SureForms post's post_modified_gmt
  unchanged (verified via wp-cli).
- Mail-template rewriting verified end-to-end through the REST flow.
The result-page button used `<Button as="a" href=...>`, but @bsf/force-ui's
Button reads the polymorphic prop as `tag`, not `as`. With `as` ignored, the
component rendered a plain <button> (tag defaulted to "button") and the href
was spread onto it as a no-op attribute, so clicking did nothing.

Switch `as="a"` to `tag="a"` so Button renders an <a> and forwards
href/target/rel — verified in-browser the link now navigates to
post.php?post=<id>&action=edit.
… mapping

Field-by-field comparison of a CF7 form against its migrated SureForms form
surfaced three mapping defects affecting every import:

1. Checkbox groups lost all options. CF7 [checkbox] is always a multi-option
   group, but it mapped to srfm/checkbox — SureForms' single on/off toggle,
   which carries no options. Remap to srfm/multi-choice with
   singleSelection:false (multi-select), mirroring the radio mapping; all
   options now survive.

2. Duplicate submit button. The migrator appended an srfm/inline-button block
   while SureForms also auto-renders its own submit from the
   _srfm_submit_button_text meta — so every imported form showed two buttons.
   Route the CF7 [submit] label into _srfm_submit_button_text and stop adding
   a button block. Removed the now-dead Block_Templates::submit_button and
   ::form_wrapper.

3. Acceptance block-form consent text dropped. CF7's modern
   [acceptance id] consent text [/acceptance] syntax fell back to the generic
   "Acceptance" label. Capture the text between the tags and use it as the
   GDPR field label (inline-quoted form still works too).

Also removed dead `field_type => date` plumbing — srfm/input has no date type
and SureForms ships no date field, so CF7 [date] is a documented best-effort
plain-text mapping.

Full coverage audit (text/email/url/tel/number/range/date/textarea/select/
checkbox/radio/acceptance/file/quiz/hidden/submit) confirms every supported
tag maps to the right srfm block with options + min/max/length attrs intact,
and file/quiz/hidden are correctly flagged unsupported.

Tests: 21 PHPUnit cases (84 assertions). Verified end-to-end in the browser:
imported form shows 5 Skills checkboxes, the full consent sentence, and a
single submit button.
… tabs

The Migration wizard hand-rolled its panels, lists, code preview and button
rows with raw <div>/<ul>/<li>/<details> + Tailwind, so it read as a different
product from the rest of SureForms settings (which compose @bsf/force-ui
primitives). Converted all steps to force-ui:

- SourcePicker: source tiles now use a force-ui grid Container whose column
  count tracks the number of detected plugins — 1 plugin = full width,
  2 = 50/50, 3 = thirds, 4+ = thirds wrapping onto new rows (3 per row).
  Implemented as `containerType="grid"` with responsive
  `cols={ { sm:1, md:min(n,2), lg:min(n,3) } }` so tiles also stack on small
  screens. Tile card rebuilt as a Container.
- FormSelector & ImportResult: raw <ul>/<li> list panels → Container rows.
- DryRunPreview: raw <details>/<summary> preview → force-ui Accordion
  (the <pre> code block stays inside Accordion.Content).
- All steps: raw `<div><Loader/></div>` loading → shared <LoadingSkeleton>
  (the same component every other tab uses).

Behavior preserved verbatim: behaviorMap + per-row Update/Skip/Create Select,
inFlight double-submit guard, onContinue(ids, behaviorMap) signature,
"Previously imported" badge + external-link, tag="a" Edit button, dynamic
confirm label, and all i18n strings.

Verified in-browser (kimi-webbridge) at 1/2/3/4 source counts (full →
50/50 → thirds → wrap) and across the full wizard (form list, Accordion
preview, result). ESLint + build clean; 21 PHPUnit tests still green.
…mporter

Reviewing the Integrations and Form Validation tabs showed the Migration
wizard diverged from every other settings screen. Rebuilt it to match the
plugin's native idioms.

- Remove the dead header Save button — add 'migration-settings' to
  PageTitleSection's srfm.settings.exclusionList (the tab persists nothing).
- Drop the 4-step ProgressSteps wizard. MigrationPage is now a single screen
  with a lightweight view state (select → review → result): the source is
  auto-selected when one plugin is installed, with a compact Select shown
  only when 2+ are available. Removed SourcePicker.js (folded in).
- Forms list → native force-ui Table, reusing the shared listing Table
  (@Admin/common/listing/components/Table) used by Entries/Forms/Payments:
  checkbox selection + select-all (indeterminate), Status column (New vs
  Previously imported + edit link), and the re-import behavior Select.
- Review step → human-readable summary instead of raw Gutenberg block markup:
  new parseFieldSummary.js extracts "N fields will import" + field-name chips
  from the dry-run markup via a registration-independent regex (the @wordpress/
  blocks parser can't resolve srfm/* on the settings screen). Skipped fields
  shown as a plain-language warning. No block comments are ever shown to users.
- Remove redundant in-body headings ("Choose a source plugin", "Choose forms
  to import from X", "Review the migration preview") — the tab title +
  description already live in the top header, matching Integrations.

Behavior preserved: behaviorMap (update/skip/create), idempotent re-import,
inFlight double-submit guard, imported/skipped/failed reporting, dynamic CTA
label, and all i18n strings. force-ui only; no PHP touched (21 PHPUnit tests
still green). Verified end-to-end in-browser (kimi-webbridge): no Save button,
table selection, readable review with field chips + skip warning, successful
import + result screen — all visually consistent with Integrations.
The Migration tab now opens on a table of the form plugins you can import
from — the same tabular idiom as the Integrations tab — instead of jumping
straight into Contact Form 7's forms. This surfaces the importer as a
multi-vendor capability and gives a clear entry point.

- New SourcePicker: a plain force-ui Table (Source · Status · Forms · Import)
  listing only sources that are actually available on the site (an active,
  supported plugin). Contact Form 7 shows today; future vendors appear here
  as their importers ship. No "coming soon" placeholder rows — only what's
  available is shown.
- MigrationPage flow: sources → select → review → result, with Back from the
  forms table returning to the sources list and "Import more forms" resetting
  to it. Removed the auto-select-single-source shortcut and the inline source
  dropdown (the table is the picker now).
- FormSelector: added a Back button (to the sources table) in the action row.

force-ui only; no PHP/backend change (the existing rest_list_sources already
returns the registered importer). 21 PHPUnit tests still green. Verified in
the browser (kimi-webbridge): sources table → Import → forms table → review →
result, plus Back navigation, all consistent with Integrations.
Replace the plain git commit/push with planetscale/ghcommit-action, which
commits through the GitHub GraphQL API so the commit is signed by GitHub's
GPG key and shows as Verified. A git CLI push over HTTPS is never signed,
which left the auto-generated i18n PRs with unverified commits.
The two failing CI checks on PR #2773 are now green:

PHP Insights (--min-quality/architecture/style=100): three rule violations
fixed — Class constant visibility (added `public` to both Base_Migrator
constants), Useless constant type hint (removed @var on those constants),
No blank lines after class opening (across base-migrator, block-templates,
cf7-importer), and Ordered class elements (reshuffled base-migrator,
cf7-importer, and bootstrap so methods follow public-concrete →
public-abstract → protected-concrete → protected-abstract → private).

check-test-coverage: the action expects a test_<method>() for every
public/protected method, in a sibling test file mirroring the source path.
Added 26 such tests across three new files — test-base-migrator.php,
test-block-templates.php, test-bootstrap.php — and moved the existing
CF7 test class to tests/unit/inc/migrator/importers/test-cf7-importer.php
with 6 additional method-named tests (test_exist, test_get_source_forms,
test_get_source_form_id, test_get_source_form_name, test_get_form_metas,
test_build_form_content).

The new tests are real assertions, not empty stubs: each exercises the
target method through its public surface (or via a Cf7_Importer subclass
that stubs exist()) and asserts a concrete outcome.

PHPUnit total now 53 tests / 159 assertions (was 21 / 84). All four gates
(PHPCS, PHPStan, PHP Insights, PHPUnit) clean locally.
Free's CF7 importer is currently a closed pipeline — the tag-to-template
map, the unsupported-tags list, and the dispatch switch are all private to
Cf7_Importer. To let SureForms Pro (and other future add-ons) map CF7 tags
to their own blocks (date-picker, upload, hidden, slider, page-break)
without forking the migrator, expose four filters:

- srfm_migrator_preprocess_template — rewrite the raw CF7 `_form` string
  before parsing (e.g. swap `[step]` markers for synthetic tag lines).
- srfm_migrator_tag_to_template_map — overlay extra CF7-tag → template
  method entries on the built-in map.
- srfm_migrator_unsupported_tags — drop entries from the default
  ['file', 'captchar', 'hidden'] list when an add-on can render them.
- srfm_migrator_block_template — emit Gutenberg markup for a template
  method this importer doesn't know about, before the tag is flagged as
  unsupported.

dispatch_template() no longer note_unsupported()s in its default branch —
the caller now gives the block-template filter a chance first, then flags
the tag only if the markup is still empty. Behaviour is unchanged when no
subscriber is registered (all four filters default to the existing data).

Coverage: tests/unit/inc/migrator/importers/test-cf7-importer-extensibility.php
exercises each filter end-to-end and proves they are additive — a no-op
subscriber falls through to the unsupported-fields warning, so the filters
can't accidentally silence migration failures.

PHPUnit: 58 tests / 167 assertions. PHPCS, PHPStan L9, PHP Insights
(quality/arch/style 100/100/100) all clean.
…ibility

feat(migrator): filter seams so add-ons can plug in extra block mappings
Adds a complete Wpforms_Importer that decodes WPForms' JSON form schema
(stored as a wp-slashed JSON blob in the 'wpforms' CPT's post_content)
and emits SureForms block markup for every Lite-only field plus full
extensibility seams for SureForms Pro to overlay Pro-field mappings.

## Phase 0 — abstraction extraction (no CF7 behavior change)

Three CF7-importer privates moved to Base_Migrator as protected so every
importer can share them:
- reserve_slug($seed) + the used_slugs property — collision-safe slug
  generation, reset per form by the subclass.
- default_confirmation_message() — canonical 'Thank you' HTML.
- dispatch_template($method, $args) — static Block_Templates dispatch
  switch, reused by both importers.

All 30 existing CF7 tests stay green; 3 new test_*() methods cover the
moved helpers via a test subclass on Base_Migrator.

## Phase 1 — WPForms importer

inc/migrator/importers/wpforms-importer.php (~770 LOC):
- exist()                  → class_exists(WPForms) || defined(WPFORMS_VERSION)
- get_source_forms()       → get_posts(post_type=wpforms)
- build_form_content($f)   → parse_form_json + per-field translate dispatch
- get_form_metas($f)       → submit_text + notifications + confirmation
                              + accumulated conditional_logic post-meta

### Lite field coverage (11 types)
text → input, textarea → textarea (size→rows), email → email,
number → number, number-slider → slider (deferred to Pro filter),
select → dropdown, radio/checkbox → multi_choice (with
disclaimer_format → single-checkbox semantics), name → 1/2/3 inputs
based on `format` (simple / first-last / first-middle-last),
gdpr-checkbox → gdpr. captcha_* is form-level, not a block.

### Cross-cutting translators
- translate_choices() preserves WPForms' 1-indexed choice ids in an id_map
  so conditional logic can re-key target values onto SureForms' option
  index after translation.
- translate_layout_field() emits a core/columns block and recurses into
  each column's children (uses block markup directly — no Layout block in
  SureForms).
- translate_conditional_logic() emits the SureForms Pro
  _srfm_conditional_logic post-meta shape. WPForms operators map onto
  SureForms operators: == / != / c → includes / !c → !includes /
  ^ → startWith / ~ → endWith / e → null / !e → !null. Operators >
  and < are dropped with a one-line warning. Rule targets are rewritten
  from WPForms field ids to the SureForms target block_id captured
  during emission.
- translate_email_notifications() and translate_confirmation() lift
  WPForms' first notification + confirmation onto SureForms'
  _srfm_email_notification / _srfm_form_confirmation meta keys.

### Extensibility
All four filter seams from PR #2789 receive $key='wpforms', so Pro's
forthcoming Migrator_WPForms subscriber plugs in Pro-only mappings
(date-time, file-upload, signature, repeater, page-break, …) without
touching Free.

### Registration
inc/migrator/bootstrap.php — one-line addition to $importer_classes;
the React admin UI auto-discovers the new source.

### Tests
- tests/unit/inc/migrator/importers/test-wpforms-importer.php — 25 cases
  covering per-Lite-field translation, composite Name (3 formats),
  layout/columns recursion, XSS-safe label escaping, idempotency,
  invalid-JSON fallthrough, conditional-logic operator translation,
  email + confirmation meta.
- tests/unit/inc/migrator/importers/test-wpforms-importer-extensibility.php
  — 5 cases verifying each filter fires for $key='wpforms'.
- tests/unit/inc/migrator/test-base-migrator.php — 3 new tests for the
  moved helpers (reserve_slug / default_confirmation_message /
  dispatch_template).
- tests/unit/inc/migrator/test-bootstrap.php — asserts both 'cf7' and
  'wpforms' keys surface in the REST source listing.

Total: 94 PHPUnit tests / 263 assertions, all green.

### Gates
- PHPCS: clean (5/5 files).
- PHPStan level 9: clean.
- PHP Insights: Code 100 / Complexity 0 / Architecture 100 / Style 100.

### Stacks on
- #2773 (CF7 importer base) — provides Base_Migrator + Block_Templates.
- #2789 (filter seams) — provides the four srfm_migrator_* filters.
…ter args

Caught during browser smoke testing: date-time field with format='time'
was rendering as srfm/date-picker because build_block_args wasn't carrying
the WPForms type-specific keys through to the Pro emitter. Same gap
applied to rating (icon/scale), nps (low/high labels), signature
(ink_color), html/content (code/content), file-upload (extensions,
max_size, max_file_number), phone/address (format), and the
internal-information code marker.

Also adds translate_repeater_field, mirroring translate_layout_field —
recurses into the repeater's nested children, assembles the child markup,
then routes to a 'repeater_container' filter so Pro's srfm/repeater
emitter can wrap it with innerBlocks markers. When no subscriber answers
(Free-only environment) the children fall back to top-level so submission
data isn't lost.

Added 4 new test_*() methods covering:
- date-time format/date_format threading
- file-upload extensions/max_size/max_file_number/multiple
- repeater recursion with subscriber → srfm/repeater wrapper
- repeater fallback (no subscriber) → inline children at top level

98 PHPUnit tests / 275 assertions, all green. PHPStan/PHPCS/Insights clean.

Caught by the local browser-QA smoke seed (forms 644 + 645).
…tions

Caught during side-by-side comparison with the source WPForms forms:
dropdown / multi-choice 'preselected' defaults weren't carrying over.

SureForms' dropdown + multi-choice renderers match preselected entries
by *option index*, not label text (see
inc/fields/dropdown-markup.php:162 — `in_array( $i, $preselected, true )`).
The importer was pushing label strings.

translate_choices() now pushes the integer index of each choice that has
`default: '1'` on the source side. The test assertion updated to
expect [1] (index of Canada) instead of ["Canada"].

98 PHPUnit tests / 273 assertions, all green. PHPCS / PHPStan clean.
Verified end-to-end: the migrated Country dropdown now shows Canada
selected by default on the frontend, matching the source WPForms form.
Adds a Gravity_Importer that reads from Gravity's custom DB tables
(wp_gf_form + wp_gf_form_meta on 2.3+ installs, wp_rg_form* on pre-2.3),
decodes display_meta JSON (with PHP-serialize fallback for legacy rows),
and emits SureForms block markup.

Field coverage (Lite blocks): text/textarea/email (+ confirm pair)/
number/select/radio/checkbox/website/phone/consent/name composite.

Pro field routing via existing srfm_migrator_* filters: date/time/
fileupload/hidden/page/html/section/address (innerBlocks)/list.

Hard-unsupported (logged in unsupported_fields): creditcard, all
product/payment fields, post_* fields, calculation, survey/quiz/poll,
chainedselect.

Conditional logic ports the seven Gravity operators (is/isnot/>/</
contains/starts_with/ends_with) to SureForms equivalents and rewrites
rule targets from Gravity field IDs to SureForms block_ids.
logicType=any becomes OR-of-one-rule subgroups; logicType=all becomes
one AND group.

Notifications + confirmations + submit text → SureForms meta keys.

Tests: 27 PHPUnit cases / 61 assertions covering every method,
per-field-type translation, choice-id preservation, CL operator
mapping, table-version gating, XSS-safe label escaping.

Gates: PHPCS clean, PHPStan level 9 clean, PHP Insights Code 100 /
Architecture 100 / Style 100.

Stacks on the WPForms Free PR (#2790).
Adds Ninja_Importer reading from Ninja 3.x's six custom tables
(nf3_forms + nf3_fields + nf3_field_meta + nf3_form_meta + nf3_objects
+ nf3_object_meta + nf3_relationships). Field settings live as one row
per key in nf3_field_meta with maybe_unserialize on each value, so the
importer does a JOIN-then-collapse pass per field.

Lite coverage: textbox/firstname/lastname/address/city/zip → input;
textarea/email/number/phone/checkbox (single)/select/multiselect →
dropdown; radio/checkbox lists → multi_choice; state/country lists →
dropdown; terms → gdpr.

Pro routing via srfm_migrator_* filters: date → date-picker,
file_upload → upload, starrating → rating, hidden → hidden,
password → password input, signature → signature, html/note → html,
hr → divider.

Hard-unsupported: creditcard + all sub-fields, total/product/quantity/
shipping/tax/listmodifier/stripeshipping/unknown.

Conditional logic ports six Ninja operators (equal/not_equal/
greater_than/less_than/contains/starts_with/ends_with) to SureForms.
Connector 'or' becomes OR-of-one-rule subgroups; 'and' becomes one AND
group. Rule targets rewritten from Ninja field keys (slugs) to
SureForms block_ids. Only meaningful when the paid Ninja CL add-on is
active.

Actions (email/successmessage/redirect) loaded from nf3_objects via
nf3_relationships and translated into SureForms email + confirmation
meta keys.

22 PHPUnit / 54 assertions. PHPCS, PHPStan level 9, Insights all clean.

Stacks on Gravity Forms Free PR (#2793).
…rms format strings

Caught during end-to-end browser QA: srfm/date-picker rendered '5142026'
because we were passing Gravity's internal 'mdy'/'dmy' slug through as
dateFormat. SureForms' date-picker expects a fixed enum (mm/dd/yyyy,
dd/mm/yyyy, yyyy-mm-dd, etc.).

normalize_date_format() maps Gravity's seven slugs (mdy, dmy, dmy_dash,
dmy_dot, ymd_slash, ymd_dash, ymd_dot) and falls back to mm/dd/yyyy.

Verified end-to-end: imported date field shows mm/dd/yyyy placeholder
matching the source.
Brings in the dateFormat normalization fix (1cf50542e) so the final
stacked PR contains the complete Gravity Forms importer with QA-
verified date-picker output.
… Status column + empty footer

Two small UX polish items:

1. SourcePicker — rename column 'Source' → 'Plugin' (clearer for end users
   who don't know the importer's internal vocabulary), and drop the Status
   column entirely. Status only ever showed an 'Active' badge for the
   plugins we list (we filter out installed=false before render), so the
   column was static visual noise. Dropping it widens 'Plugin' and 'Forms'
   for a cleaner three-column layout (Plugin · Forms · Action).

2. Shared listing Table — gate FUITable.Footer behind isLoading ||
   paginationProps || children so tables without pagination (the
   Migration tab's form list, for example) don't render an empty footer
   row whose padding leaves dead space under the last row. Other tables
   that pass children or paginationProps render exactly as before.
The /sureforms:sync-public skill's force-push + unsigned-strip-commit flow
is rejected by sureforms-public's new branch ruleset (verified signatures
required, sync/master force-push protected). This doc records the validated
replacement flow (re-sign with id_ed25519, merge-cap on public/master, fresh
branch, user-run push) to be folded into the skill.
Add an extension slot in the entry detail's action area (beside the
"Resend Email Notification" button) so add-ons can contribute their own
entry-level actions, passing { entryId, formId }. The action row is made
flex-wrap so contributed buttons sit inline beside the existing one.

Used by SureForms Pro to render the "Retry failed cloud uploads" action
on entries whose Third Party File Upload had failures.
Apply the fix directly to the skill instead of a side doc: bulk re-sign with
id_ed25519 + verified committer email, cap with a merge commit on the mirror
for a clean public diff, push fresh sync/master-<date> branch (user-run).
Removes the temporary sync-public-skill-UPDATED.md.
Stage the actual skill edits (the prior commit only removed the side doc;
the colon in the filename prevented git add -A from staging it).
…dentity)

- Detect mirror remote ($MIRROR) once; use it in Steps 2/3/6/7 (was hardcoded/undefined)
- Drive committer + signing key from the running user's git config
  (user.name/user.email/user.signingkey) instead of one person's identity
- Step 7 push uses "$BRANCH" not the <BRANCH> placeholder
- Note that filter-branch rewrites committer (author preserved)
- Align prose to .pub key path; generalize error-recovery guidance
adi3890 and others added 29 commits June 7, 2026 00:34
Satisfy the check-test-coverage gate, which requires a test_<func>()
method for every new public/protected function:
- provider package methods (supports_packages/start/finish/register/
  translate) across Provider, Null_Provider, WPML_Provider tests
- String_Translator static name builders + form_package + block_type_label
- Get_Instance::reset_instance (new tests/unit/inc/traits/test-get-instance.php)
phpinsights --fix: move the PACKAGE_KIND constant above the methods in
String_Translator, and the protected get_active_languages/guess_current_url
helpers below the public package methods in WPML_Provider. Pure reordering —
no behavioral change (verified: sorted line-sets identical). Restores the
style score to 100.
feat(multilingual): native WPML support via per-form String Packages (#2760)
feat(i18n): BSF Top 20 languages config (release-time generation)
…xtra-actions

GIT-1264: Add srfm.entryDetail.extraActions filter to the entry detail view
GIT: i18n workflow — produce verified (signed) commits
The Language value is sourced from the multilingual provider and is
only populated when WPML is active, so on single-language sites the
row always rendered '-'. Drop the row and its unused detail-transform
mapping; the entries list table column (fed by the list transform) is
unchanged.
…anguage-row

Remove Language row from entry detail submission info
Replace the plain git commit/push with planetscale/ghcommit-action, which
commits through the GitHub GraphQL API so the commit is signed by GitHub's
GPG key and shows as Verified. A git CLI push over HTTPS is never signed,
which left the auto-generated i18n PRs with unverified commits.
…ts-master

GIT: i18n workflow — produce verified (signed) commits (master)
Auto-generated by /i18n command on PR #2837
@adi3890 adi3890 merged commit 4ad4d65 into master Jun 10, 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.

5 participants