Sync master from upstream#104
Merged
Merged
Conversation
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.
Master to Dev 2.10.1
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
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)
Color Picker Free Feature
…xtra-actions GIT-1264: Add srfm.entryDetail.extraActions filter to the entry detail view
…into master-dev-2.11.0
Master to Dev 2.11.0
…to dev-nr-2.11.0
Dev to Next Release 2.11.0
GIT: i18n workflow — produce verified (signed) commits
Version Bump 2.11.0
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
Update 2.11.0 release date to 10th June 2026
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
chore: update i18n translations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Syncs the public mirror with private master.
7d3724dc6..c621ce372(275 commits)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).masteron this repo, so this diff shows only real upstream changes — no internal files appear.Highlights
🤖 Generated with Claude Code