Gatilab Products

Changelogs

Improvements, fixes, and the details behind each release.

Core Forms

Build forms, collect submissions, and connect the actions your site needs.

View Product
Stable

Core Forms 4.2.1

Read release notes

Patch release.

Per-form spam view routes correctly

`?page=core-forms&view=spam&form_id=X&tab=submissions` now opens the form-edit screen on the Submissions tab in spam-filter mode instead of falling through to the all-forms list. The router branched on `view=edit`; it now branches on the presence of `form_id` so any view value (`spam` / `all` / `inbox`) routes correctly.

Submissions list — visual cleanup

Dropped the 3px blue / red inset left border on new and spam rows and the muted grey text on spam rows. Status is communicated entirely by the badge in the STATUS column (New / Read / Replied / Spam) — much cleaner against WP's native striped table.

Dashicon alignment fix for WP < 7.0

Buttons that pair a dashicon with text (Documentation, Refresh, Open, copy buttons in the Headless tab) had the icon glyph sitting ~2px above the text baseline on WordPress 6.9.x and earlier. WP 7.0 normalized this natively; for older WP a high-specificity rule scoped to Core Forms admin screens applies `vertical-align: middle`, line-height/font-size resets, and `:has(.dashicons)` inline-flex centering to match.

Stable

Core Forms 4.2.0

Read release notes

Stable release. Consolidates eleven betas (`v4.2.0-beta` through `v4.2.0-beta11`) into one shipping version.

WhatsApp, three ways

Three new actions cover the full range of WhatsApp delivery models — pick whichever fits your existing stack.

  • WhatsApp Cloud API action. Send template messages and free-form text (24-hour window) via Meta's Graph API. Free tier covers 1,000 conversations per month. Credentials live globally under Settings → WhatsApp Cloud API with per-action override; a "Test connection" button reports the verified business name + quality rating.
  • Twilio WhatsApp action. For shops already on Twilio — reuses your Account SID / Auth Token, posts to the `whatsapp:` channel, supports approved Content Template SIDs.
  • Click-to-WhatsApp redirect. Generates a `wa.me` deep link with the submission pre-filled into the chat draft, used as the post-submit redirect.

Both Cloud API and Twilio sends log through Email Logs. The `cf_form_redirect_url` filter now fires on every successful submission and receives the `Submission` as a 4th argument.

Contact Form 7 → Core Forms migration tool

New page under Core Forms → Import that scans every `wpcf7_contact_form` post, converts shortcode markup to plain HTML (14 tag types covered), maps the CF7 mail template to a Send Email action, and carries `messages` overrides across as `cf_message*` post meta. Per-row preview lets you sanity-check before committing. Gravity Forms, WPForms, Fluent Forms, Ninja Forms, Formidable previewed as "Coming soon" cards.

Per-form analytics dashboard

New Analytics tab on every form-edit screen, powered by the existing `cf/v1/analytics/*` REST API: - One "Funnel" postbox with views, starts, submissions, spam. - SVG daily-activity chart with all three series overlaid. - Field interactions panel (where users focus/fill). - Top sources panel (UTM attribution with referrer-domain fallback). - Auto-generated recommendations — high spam rate, low completion, drop-off hotspots. - Date-range presets (7d / 30d / 90d / 1y) + custom from/to.

Form versioning + change history

The `core-form` post type now declares `'supports' => […, 'revisions', …]`. `cf_settings`, `_cf_hidden_columns`, and every `cf_message` override persist alongside each revision. Settings-only or messages-only saves still create a revision (we force `wp_save_post_revision_check_for_changes` to `false` for `core-form`). New Revisions* tab lists each snapshot with author, timestamp, and a diff signal — restore drops back into the Core Forms admin with a flash and copies meta back automatically.

Security hardening — 14 audit findings fixed

  • CSRF on by default (existing installs preserved via 4.2.0 migration).
  • Submission mass-assignment closed via markup-derived allowlist.
  • Headless API key header-only; new CORS allowlist textarea.
  • Workflow `/actions/test` bumped to `manage_options`.
  • Webhook SSRF guard: `cf_is_safe_outbound_url()` rejects loopback / RFC1918 / link-local / IPv6 ULA / non-HTTP schemes.
  • Email header CRLF injection closed via `cf_esc_email_header()`.
  • Custom CSS `` escape closed.
  • `process_save_form` inline cap+nonce check.
  • Two ghost `wp_ajax_*` handlers removed.
  • Polls trust `REMOTE_ADDR` only.
  • Draft saves rate-limited (30/IP+form/min).
  • File upload blocklist extended (html/htm/xhtml/xml/xsl/xslt).
  • Honeypot returns opaque success.

Admin redesign — WordPress-native everything

  • New plugin icon (Font Awesome Pro `pen-field`) across admin menu, block inserter, and editor placeholder.
  • Analytics + Revisions tabs use native WP chrome end-to-end — `.postbox`, `.wp-list-table widefat striped`, `.tablenav top`, `.row-actions`, native `.notice` variants.
  • Button styling unified — removed the `.button-primary` glow override that broke consistency.
  • Fullscreen / Typeform-style display mode promoted to its own top-level card on Settings.
  • WhatsApp actions surfaced in Add Action picker under new "Messaging & SMS" category.
  • Per-form Template picker unified with Add-New-Form gallery — all 12 templates available everywhere.

Headless `[CF_REFERRER_URL]` fix

Cross-origin Referer headers are stripped to the bare origin by browsers. The submit endpoint now reads `_cf_page_url` from the JSON body and substitutes it into `$_SERVER['HTTP_REFERER']` for the duration of `Forms::process()`. Bundled embed.js forwards `window.location.href` automatically.

Build pipeline

Production builds run through `bash build.sh` which executes `composer install --no-dev` so dev-only autoload entries can't leak. 148 unit tests pass; PHPStan clean against regenerated baseline; production zip 741KB.

Upgrade

Download the attached zip and upload via Plugins → Add New → Upload. Existing data, settings, and forms are preserved. The 4.2.0 migration runs automatically on first admin pageload after upgrade.

Beta

Core Forms 4.2.0-beta11

Read release notes

Template picker on the per-form builder now reads from the same cf_get_form_templates() registry the Add-New-Form gallery uses — all 12 templates available everywhere instead of just 5. Button styling unified: removed the .button-primary brand-blue + glow override on Core Forms admin pages, so Save Form now matches Refresh, Restore, and every other native .button on the same screen.

Beta

Core Forms 4.2.0-beta10

Read release notes

Postbox header padding bumped from WP's tight default (8px/12px) to 14px/16px on the Analytics and Revisions tabs. Same WordPress chrome, just with breathing room.

Beta

Core Forms 4.2.0-beta9

Read release notes

Pre-release. Polish pass on beta8 based on visual feedback.

What changed

Stat boxes restructured

Beta8 left the four funnel stat numbers in individual postboxes — each one carried an empty postbox-header bar at top, wasting vertical space and not matching anything else in WP admin. Replaced with one `Funnel` postbox containing a 4-column grid of label / number / hint (matches the "At a Glance" dashboard widget pattern).

Empty states no longer use `.notice`

The `.notice notice-info inline` empty states inside postbox bodies brought their own colored left bar — which read as a UI element nested inside another UI element. Replaced with plain `

` for the chart-empty and recommendations-empty cases.

Recommendations now render as real `.notice` rows

Each recommendation is now a native `.notice .notice-success` / `.notice-warning` / `.notice-info` — so the colored left bar is WordPress's own variant rather than a plugin-flavored tint.

Revisions: dropped the "Live" pill badge

Replaced the custom badge with plain `Live version` text. No more custom badge colors.

Deployed and verified

Live on gatilab.com — plugin active at 4.2.0-beta9.

Carries over everything from v4.2.0-beta8.

Beta

Core Forms 4.2.0-beta8

Read release notes

Pre-release. Builds on v4.2.0-beta7.

What changed

Rewrote the Analytics + Revisions tabs to use standard WordPress admin chrome instead of custom card panels. They now look at home in the WP admin alongside the rest of the post-edit screens.

Native patterns now used

  • `.postbox` / `.postbox-header` / `.hndle` / `.inside` containers — the same primitives WP uses for dashboard widgets and post-edit metaboxes.
  • `.wp-list-table widefat striped` for the field-interactions, top-sources, and revisions history tables.
  • `.tablenav top` for the analytics date-range toolbar.
  • `.row-actions` (View diff | Restore) on revision rows, mirroring the Posts list pattern.
  • `.notice notice-info inline` / `.notice notice-warning inline` for empty states.
  • `.column-primary` + `data-colname` on revision rows for native responsive behavior.
  • Native `.button` / `.button-primary` for date-range presets.

Plugin polish kept

Only the bits that genuinely have no native equivalent: - Severity-tinted left-bar recommendations (a list of typed alerts has no built-in WP component). - The daily-activity SVG line chart. - A small "Live" pill badge on the current-version revision row.

No more rounded custom panels, no oversized stat numbers, no plugin-flavored colors. The tabs blend with the rest of WP admin.

Deployed and verified

Live on a test install — plugin active at 4.2.0-beta8, frontend 200.

Carries over everything from v4.2.0-beta7 (WSOD fix, analytics/revisions bugfixes, security hardening, WhatsApp, CF7 migration).

Beta

Core Forms 4.2.0-beta7

Read release notes

Pre-release. Hot-fix for the WSOD that affected beta5 and beta6.

What was wrong

Beta5 added an init-action callback registered as `'Core_Forms\_cf_wire_revisions'`. But `src/functions.php` doesn't declare a namespace — every function in it (`cf_get_form`, `cf_get_settings`, `_cf_wire_revisions`, etc.) lives in the global namespace despite the `use Core_Forms\Form;` at the top of the file.

The namespaced callback string resolved to nothing, and at init time WordPress fataled with \"function not found\" — taking the admin (and depending on cache state, the whole site) down.

Beta6's `function_exists('add_action')` guard helped CLI tools (PHPUnit, PHPStan, dump-autoload) load `functions.php` without fataling, but never addressed the runtime fatal. Sites running beta5 or beta6 still saw the WSOD.

What's fixed

The init callback is now registered as plain `'_cf_wire_revisions'`.

Verified on a live install: - Plugin active at 4.2.0-beta7 - Frontend `200` - Admin `302` (login redirect — no fatal) - `php -r 'require autoload.php; var_dump(function_exists(\"_cf_wire_revisions\"));' → bool(true)`

Upgrade strategy

If you tested beta5 or beta6 and hit a blank screen, this is your fix. Download the attached zip and upload via Plugins → Add New → Upload — WordPress will replace your existing install.

Everything else from the beta5/beta6 release notes (analytics tab, revisions tab, CF7 migration, WhatsApp actions, security hardening) is intact and unchanged.

Beta

Core Forms 4.2.0-beta6

Read release notes

Pre-release. Critical fix on top of v4.2.0-beta5.

⚠️ Critical — fixes a WSOD some users hit on beta5

beta5 added a top-level `add_action()` call at the bottom of `src/functions.php` that fired during composer's files-autoload step. Inside a WordPress request that was fine — `plugin.php` (which defines `add_action`) loads before plugins. But outside WP — PHPUnit, PHPStan, custom CLI tools, or any other plugin that requires our `vendor/autoload.php` before WP's plugin loader runs — `add_action` was undefined and the plugin fataled at autoload, taking the whole site down.

The registration is now wrapped in `function_exists( 'add_action' )` so `functions.php` is safe to load standalone.

This was the root cause of the WSOD reported on test sites running beta5.

Analytics tab bugfixes

  • Field-interactions table reads the `interactions` column the Dashboard actually returns (was reading `count`, so every row showed 0).
  • Top-sources panel maps the Dashboard's `{utm_sources, referrers}` shape correctly (was reading the non-existent `top_sources`). Falls back to referrer-domain rows when no UTM tags are in use.
  • Recommendations substitute the `value` (e.g. completion-rate %) into the printf-style `message` template instead of showing the literal `%s%%`.

Revisions tab bugfixes

  • Restore redirects back to Core Forms admin (`?page=core-forms&view=edit&form_id=X&tab=revisions&restored=1`) instead of WordPress's native post.php editor, which doesn't render Core Forms' custom UI. A success flash confirms the restore.
  • Meta-only saves create revisions. `wp_save_post_revision_check_for_changes` is set to `false` for `core-form`, so settings-only or messages-only saves snapshot a revision instead of being silently dropped by WP's content-diff check.
  • Revision-meta filter is now version-agnostic. `wp_post_revision_meta_keys` passes the parent in WP 6.4+ but earlier patches may have passed the revision; the callback resolves a parent id defensively so the check matches on both.

Build pipeline

  • Regenerated `phpstan-baseline.neon` (previous baseline was stale after the beta CSRF default flip — `enable_nonce` shape change broke ignore patterns).
  • Build script now exits clean: 148 unit tests pass, PHPStan analyse clean.
  • Production zip (738KB) ships without phpstan, phpunit, tests, or composer.json — all dev deps stripped via `composer install --no-dev` in the build step.
  • Use `bash build.sh` for releases. Never rsync the dev vendor directly — that's how dev-only `autoload_files` entries (phpstan/bootstrap.php) ended up in the deployed autoloader earlier today.
Beta

Core Forms 4.2.0-beta4

Read release notes

Pre-release. Quick UI consistency fix on top of v4.2.0-beta3.

What changed

Removed the `body.toplevel_page_core-forms .notice` CSS override in `design-system.css` that gave admin notices on Core Forms screens a rounded 4px-left-bar look. Notices now match WordPress's native styling exactly — squared corners, native left border, no plugin-flavored variation.

The Import page's custom `.cf-import__flash` flash variants were also swapped for native `.notice .notice-success` / `.notice-warning`, and the orphaned CSS rules removed.

Carries over everything from v4.2.0-beta3 (WhatsApp picker, headless referer fix, Import page redesign, prominent Display mode card).

Beta

Core Forms 4.2.0-beta3

Read release notes

Pre-release. Iterating on v4.2.0-beta2 based on tester feedback.

What changed

WhatsApp actions now show in the per-form Add Action picker

All three v4.2.0 WhatsApp actions (Cloud API, Twilio WhatsApp, Click-to-WhatsApp redirect) live under a new Messaging & SMS category alongside Twilio SMS. They show up regardless of whether global WhatsApp Cloud credentials have been configured — credentials can be filled per-action when no global ones exist. Previously the picker only surfaced actions present in the static category map, so the three new actions were silently hidden.

Headless [CF_REFERRER_URL] etc. now resolve correctly

The headless submit endpoint now reads _cf_page_url from the JSON body and substitutes it into $_SERVER['HTTP_REFERER'] for the duration of Forms::process(). The bundled embed.js automatically forwards window.location.href; the native-fetch snippet in the Headless tab now shows this pattern too.

Cross-origin Referer headers are stripped to the bare origin (https://example.com) by browsers under the default strict-origin-when-cross-origin policy, so without this [CF_REFERRER_URL], [CF_REFERRER_HOST], [CF_REFERRER_PATH], and [CF_POST_ID] were resolving to the WP REST endpoint's own URL on every embed.

Import page redesigned

  • Renamed in the sidebar from "Import (CF7)" to Import — this page will house migration paths from multiple form plugins.
  • New source-picker card grid: Contact Form 7 (active), plus Gravity Forms, WPForms, Fluent Forms, Ninja Forms, and Formidable as visibly muted "Coming soon" cards.
  • CF7 form list rebuilt as a custom card-style layout below the picker rather than a wp-list-table.
  • Replaced the WP-native red-bar notice-error with a clean inline empty state.

Typeform-style mode is prominent

Promoted from a buried row inside the "Add-ons" accordion at the bottom of the Settings tab to a dedicated Display mode card at the top, with a marketing-style pitch panel explaining what Fullscreen mode is for. Card auto-opens when the form is already in fullscreen.

Carried over from earlier betas

See v4.2.0-beta for the WhatsApp / CF7 migration / 14 security fixes changelog, and v4.2.0-beta2 for the new Core Forms icon.

Beta

Core Forms 4.2.0-beta2

Read release notes

Pre-release. Builds on v4.2.0-beta.

What's new

Replaced the abstract circular "C" admin-menu icon and the generic clipboard block-inserter icon with pen-field from Font Awesome Pro 7.2 — a square frame containing an input row and a pen.

Single visual language across every touchpoint:

  • WP admin sidebar menu
  • Gutenberg block inserter
  • In-editor Placeholder state (shown when no form is selected)

viewBox is normalised to a square 640×640 so the natural 640×512 glyph reads balanced at small render sizes. Menu fill stays black so WordPress's color-scheme stylesheet can tint it for active/hover states; block icons use currentColor for Gutenberg's hover/selected states.

Carried over from v4.2.0-beta

  • WhatsApp Cloud API, Twilio WhatsApp, and Click-to-WhatsApp redirect actions
  • CF7 → Core Forms migration tool (forms + messages, with preview)
  • WhatsApp activity logging through Email Logs
  • 14 security audit findings fixed (CSRF default-on, headless API key header-only, headless CORS allowlist, webhook SSRF guard, email header CRLF injection, custom CSS &lt;/style&gt; escape, polls/drafts hardening, file upload blocklist extended, honeypot returns opaque success, and more)

See v4.2.0-beta release notes for the full v4.2.0 changelog.

Beta

Core Forms 4.2.0-beta

Read release notes

Pre-release. Distribution channel for testers before 4.2.0 GA.

WhatsApp, three ways

  • WhatsApp Cloud API action. Meta Graph API. Template + free-form modes. Global creds (Settings → WhatsApp Cloud API) with per-action override. Includes a Test connection button that pings /v21.0/{phone_number_id} and reports verified business name + quality rating.
  • Twilio WhatsApp action. Reuses your existing Twilio SID/token; posts to the whatsapp: channel. Optional Content Template SID for business-initiated outreach.
  • Click-to-WhatsApp redirect. Generates a wa.me deep link with the submission pre-filled into the chat draft and injects it as the post-submit redirect.

The cf_form_redirect_url filter now fires on every successful submission (not just when a per-form Redirect URL is set) and receives the Submission as a 4th argument. Existing 3-arg handlers keep working.

Contact Form 7 → Core Forms migration

New page at Core Forms → Import (CF7). Scans every wpcf7_contact_form post and converts: - Shortcode markup → plain HTML (text, email, tel, url, number, date, hidden, textarea, select, checkbox, radio, acceptance, file, submit, recaptcha) - CF7 mail template → Core Forms Send Email action - _messages meta → cf_message_* post meta (so your custom success/validation/spam copy carries over)

Each row in the importer table has a "Preview converted HTML" disclosure so you can sanity-check before committing. CF7 stays installed and untouched — run both side-by-side during cutover. Flamingo submission import is out of scope.

Operational polish

  • WhatsApp activity in Email Logs. Both Cloud API and Twilio sends log through the existing cf_email_logs table. The Type column labels them; Resend is suppressed for non-email rows (which would otherwise try to wp_mail a phone number).
  • cf_log_email() now switches between sanitize_email and sanitize_text_field based on action type.

Security hardening (14 audit findings fixed)

  • CSRF default flipped on. New installs get enable_nonce=1. Existing installs are preserved at 0 via a 4.2.0 migration that writes the prior value explicitly into stored settings. Sites that have always cached aggressively can keep nonces off; sites that haven't been should turn them on.
  • Submission mass-assignment closed. Forms::process() intersects POST keys with field names declared in the form markup. Filterable via cf_allowed_field_names.
  • Headless API key is header-only. The ?cf_key= query-string fallback is gone (leaked into logs/history/Referer).
  • Headless CORS allowlist. New textarea at Settings → Headless → Allowed origins. Empty preserves 4.1 behavior with an admin warning.
  • Workflow /actions/test requires manage_options. No longer a wp_mail / wp_remote / paid-SMS-anywhere primitive in editor-level hands.
  • Webhook SSRF guard. New cf_is_safe_outbound_url() helper rejects loopback, RFC1918, link-local, IPv6 unique-local, and non-HTTP schemes. Webhook auth header name/value also strip CR/LF.
  • Email header CRLF injection closed. New cf_esc_email_header() sanitizer replaces strip_tags on every header substitution in Email, Emailit, and Autoresponder actions.
  • Custom CSS &lt;/style&gt; escape closed. The previous regex pattern didn't consume the closing &gt;. Now strips every &lt; from custom CSS at both save and render time.
  • process_save_form gains inline cap+nonce check (defense in depth — matches the existing guard on process_create_form).
  • Two ghost wp_ajax_* handlers removed (registered methods that didn't exist; calling them returned a fatal).
  • Polls trust REMOTE_ADDR only. HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR no longer honored by default. Opt back in via cf_poll_trust_forwarded_for for sites behind a trusted reverse proxy.
  • Draft saves rate-limited to 30 new drafts per IP+form per minute (filterable via cf_draft_save_rate_limit).
  • File upload blocklist extended with html, htm, xhtml, xml, xsl, xslt. Closes a stored-XSS path where nginx (which ignores the uploads .htaccess) served uploaded HTML as text/html on the WP origin.
  • Honeypot returns opaque success on missing/populated field instead of WordPress's silent 0 response — bots can no longer distinguish "rejected" from "accepted but never processed."

Upgrade notes

  • Backwards-compatible: the CSRF migration only writes enable_nonce=0 into installs that had no explicit value before, so existing form-bearing cached pages keep working.
  • Behavior changes worth knowing:
  • Webhooks pointing at 127.0.0.1, 192.168.*, 10.*, etc. will now fail. Local dev environments needing the loopback can override via the cf_is_safe_outbound_url filter.
  • Forms with JS-injected fields not present in the static markup will have those keys dropped. Use the cf_allowed_field_names filter to opt them in.
  • Editor-role users with edit_forms can no longer test arbitrary actions via the REST endpoint — they can still test by saving the action first.

Full commit: b37bfae