Core Forms 4.2.0-beta
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.medeep 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_logstable. The Type column labels them; Resend is suppressed for non-email rows (which would otherwise try towp_maila phone number). cf_log_email()now switches betweensanitize_emailandsanitize_text_fieldbased 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 viacf_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/testrequiresmanage_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 replacesstrip_tagson every header substitution in Email, Emailit, and Autoresponder actions. - Custom CSS
</style>escape closed. The previous regex pattern didn't consume the closing>. Now strips every<from custom CSS at both save and render time. process_save_formgains inline cap+nonce check (defense in depth — matches the existing guard onprocess_create_form).- Two ghost
wp_ajax_*handlers removed (registered methods that didn't exist; calling them returned a fatal). - Polls trust
REMOTE_ADDRonly.HTTP_CLIENT_IPandHTTP_X_FORWARDED_FORno longer honored by default. Opt back in viacf_poll_trust_forwarded_forfor 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 astext/htmlon the WP origin. - Honeypot returns opaque success on missing/populated field instead of WordPress's silent
0response — bots can no longer distinguish "rejected" from "accepted but never processed."
Upgrade notes
- Backwards-compatible: the CSRF migration only writes
enable_nonce=0into 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 thecf_is_safe_outbound_urlfilter. - Forms with JS-injected fields not present in the static markup will have those keys dropped. Use the
cf_allowed_field_namesfilter to opt them in. - Editor-role users with
edit_formscan no longer test arbitrary actions via the REST endpoint — they can still test by saving the action first.
Full commit: b37bfae