Fix: corrected plugin name casing to EffortLess (was Effortless) in the plugin header, readme, and privacy exporter/eraser labels.
7.5.29 – 2026-07-10
Added: Dismissible “Support the development” donate notice on the form and submissions admin screens, plus the standard Donate link plugin header.
7.5.28 – 2026-07-04
Dev: Added bin/smoke-test.php, a WP-CLI script (wp eval-file bin/smoke-test.php) that exercises file-upload handling, the privacy exporter/eraser, and the daily cleanup cron directly against a live install, cleaning up all test data it creates. Not part of the plugin’s runtime; excluded from PHPCS scanning in phpcs.xml since it’s CLI-only output, not web-facing.
7.5.27 – 2026-07-04
Added: GDPR/Privacy Tools integration — form submissions are now included in WordPress’s built-in Export Personal Data / Erase Personal Data tools (Tools → Export/Erase Personal Data), matched by the requester’s email address across every submission CPT. Suggested privacy policy language is also registered via wp_add_privacy_policy_content().
Added: Configurable submission retention — a new “Auto-delete submissions after (days)” field in Storage Settings, enforced by a new daily cron job. Leave at 0 (default) to keep submissions forever, matching prior behavior.
Security: Form file attachments are now uploaded into a dedicated wp-content/uploads/elmfc-attachments/ directory with a deny-all .htaccess/index.php, instead of the standard public year/month uploads folder, since they may contain sensitive user-submitted documents. Cleanup of these files (and the multisite blog-switch restore) is now guaranteed via try/finally even if a hook throws mid-request.
Security: Rate-limit rows written directly to wp_options (for atomicity) are now purged once expired by the new daily cron, instead of accumulating indefinitely.
Security: Email subject lines are now run through sanitize_text_field() before being handed to wp_mail(), as defense-in-depth against control-character/header injection regardless of how the underlying setting was populated.
Fix: wp_die( ‘Unauthorized’ ) in the CSV export handler now uses esc_html__() like every other message in the plugin.
Fix: uninstall.php now also removes the elmfc_recaptcha_captcha_fixed migration-tracking option and the daily cleanup cron event.
Cleanup: The shortcode’s KSES allowlist is now memoized (built once per request) instead of being rebuilt on every form render.
Docs: Clarified the security trade-offs of the ELMFC_TRUST_PROXY constant in its doc comment.
7.5.26 – 2026-07-03
Security: The submissions admin page now validates the cpt query parameter against the plugin’s own list of managed submission post types before rendering or listing anything, closing a gap that let any user with the broad edit_posts capability browse/export posts of an arbitrary post type through this screen.
Security: Deleting a submission (single or bulk) now also checks current_user_can( ‘delete_post’, $id ) for that specific post, not just the broad edit_posts capability.
Fix: The honeypot field label now uses esc_html__() instead of an unescaped __() call.
Fix: The one-time “my_form” → “elmfc_form” CPT migration now clears the WordPress post cache for every renamed post, so sites using a persistent object cache (Redis/Memcached) don’t keep serving stale cached posts under the old post type after the rename.
7.5.25 – 2026-07-03
Fix: The one-time “my_form” → “elmfc_form” CPT migration now also runs immediately after switch_to_blog() in cross-site rendering/submission code paths, not just on init. Previously, a source site reached only via switch_to_blog() (never visited directly since upgrading) would keep rejecting valid cross-site submissions as “Invalid form”.
Fix: Forms that had Google reCAPTCHA enabled with the math captcha explicitly turned off now automatically get the math captcha re-enabled on upgrade, instead of silently ending up with no CAPTCHA at all after reCAPTCHA was removed. Stale reCAPTCHA postmeta is cleaned up at the same time.
Fix: Submission detail view no longer shows the literal text “Array” for multi-value fields (e.g. checkbox groups); values are now joined into a readable comma-separated list.
Fix: Submission detail view field ordering now matches the previous case-insensitive sort instead of PHP’s case-sensitive default.
Fix: Admin asset loading now matches the submissions page by its exact registered hook suffix instead of a substring match on the screen ID.
Cleanup: Removed a dead, unreachable branch left over from the reCAPTCHA removal in the settings import handler, and a redundant duplicate merge pass in the shortcode’s KSES allowlist builder.
7.5.24 – 2026-07-03
Code quality: Removed ~45 unnecessary phpcs:ignore/disable suppressions across the codebase (verified empirically that the underlying code already satisfied WordPress Coding Standards without them). Replaced a raw SQL query with get_post_meta() in the submission detail view, used $wpdb->esc_like() instead of a hand-escaped LIKE wildcard, and sanitized a couple of previously-unwrapped $_SERVER/$_FILES reads directly instead of suppressing the warning. No functional changes.
7.5.23 – 2026-07-03
Removed: Google reCAPTCHA v3 integration entirely (settings UI, verification, footer script). Forms now rely solely on the built-in, self-hosted math captcha plus honeypot and rate limiting — no external CAPTCHA service or data leaves your site.
7.5.22 – 2026-07-03
Fix: Renamed the “my_form” custom post type to the prefixed “elmfc_form” to avoid naming collisions with other plugins/themes. Existing forms are migrated automatically (one-time rename on the first admin request per site); no action needed.
Cleanup: Removed AI-assistant planning/spec files from the shipped plugin (docs/superpowers/); added them to .gitignore.
7.5.21 – 2026-07-02
Fix: Enqueue Google reCAPTCHA and admin/submissions meta-box JS via wp_enqueue_script()/wp_add_inline_script() instead of printing inline <script> tags.
Fix: Use wp_handle_upload() instead of move_uploaded_file() for form file attachments.
Fix: Apply a form-aware KSES allowlist to shortcode-rendered form content as defense-in-depth escaping.
Docs: Document Google reCAPTCHA and Akismet as external services in the readme.
7.5.20 – 2026-06-30
Update: Confirmed compatibility with WordPress 7.0.
7.5.19 – 2026-06-30
Update: Tested up to WordPress 7.0.
7.5.18 – 2026-06-30
FEATURE: Shortcode pre-fill — pass any field’s name= attribute directly in the shortcode to pre-populate text inputs, textarea, radio buttons, checkboxes, and selects. Example: [elmfc id=”123″ plan=”premium” firstname=”John”]. Comma-separated values pre-check multiple checkboxes in a group (name=”field[]”). Reserved attributes (id, name, popup) are not treated as field names.
7.5.17 – 2026-06-30
FIXED: User copy email never sent when the form’s email input had any name other than “email” — the handler hardcoded $data[’email’] with no way to configure it. Added “Email field name” setting (defaults to “email”) in Email Settings so any field name works. Also fixes Reply-To on admin emails for the same reason.
7.5.16 – 2026-06-30
UX: Fixed popup close animation — overlay now fades out correctly instead of snapping off instantly; replaced display:none toggle with pointer-events/visibility so CSS transitions run on both open and close.
UX: Scroll to first invalid field on validation failure — page now smoothly scrolls to and focuses the first field that failed validation, not just the error banner.
UX: Submit button loading spinner — button shows a CSS spinner while the AJAX request is in flight; text goes transparent so button size stays stable.
UX: SMTP password field show/hide toggle — added a reveal button (dashicons-visibility) next to the password input in the email settings meta box.
UX: Stats column now shows impressions → submits (conversion %) instead of two separate raw-number columns; tooltip gives full label text.
7.5.15 – 2026-06-30
REFACTOR: elmfc_get_config_keys() and elmfc_get_skip_fields() now use static caching — arrays are built once per request instead of on every call.
FIXED: Removed duplicate elmfc_captcha_answer and elmfc_redirect_url entries from elmfc_get_skip_fields() — both keys are already included via elmfc_get_config_keys().
REFACTOR: Removed dead $plain parameter from ELMFC_Email_Handler::format_html_email() — the plain-text body was passed in but never used inside the method.
REFACTOR: Extracted duplicated main-site ID resolution in the shortcode into a single pre-computed variable — the ms_gpt_source_site / get_main_site_id() lookup was previously copy-pasted in both the id= and name= fallback paths.
DOCS: Added return type hint :array to elmfc_default_texts() and elmfc_get_config_keys(); improved docblock for elmfc_get_config_keys().
7.5.14 – 2026-06-29
SECURITY: Rate limiter no longer bypassed on sites running a persistent object cache (Redis/Memcached) — counter rows are now written directly to the options table via $wpdb->replace() instead of set_transient(), so the atomic SQL UPDATE always finds the rows it needs.
SECURITY: Math captcha tokens now include the form_id in the transient key (‘elmfc_cap_{form_id}_{token}’) — a token solved on one form can no longer be replayed against a different form in the network.
SECURITY: AJAX nonce is now scoped to the specific form_id (‘elmfc_form_submit_{id}’) — a nonce obtained from form A can no longer be used to target form B on any site in the network.
SECURITY: Email subject and intro text are now read from post meta, not from $_POST — an attacker can no longer spoof the admin notification email’s subject line or opening sentence.
FIXED: form_title and form_url are now saved to CPT submissions as ‘elmfc_form’ and ‘elmfc_url’ meta keys — the rename block was previously dead code because it ran after array_diff_key() had already stripped those keys.
FIXED: Akismet API key now read with get_site_option() before switch_to_blog() — cross-site form submissions on multisite no longer bypass the spam check when Akismet is network-activated (key stored in sitemeta, not per-site options).
FIXED: elmfc_rl_limit and elmfc_rl_window added to the canonical elmfc_get_skip_fields() — these internal rate-limit config keys are no longer forwarded to external webhook payloads; local duplicates removed from email handler and CPT storage.
7.5.13 – 2026-06-29
REFACTOR: reCAPTCHA v3 verification extracted to shared elmfc_verify_recaptcha() helper — AJAX and REST handlers no longer duplicate the secret-fetch, HTTP call, and score-check logic.
REFACTOR: Rate-limit config reading extracted to shared elmfc_get_rate_limit_config() helper — eliminates duplicated get_post_meta() calls in both submission handlers.
REFACTOR: DISTINCT postmeta scan for submission meta keys extracted to shared elmfc_get_submission_meta_keys() helper — list table and CSV export now share the same cached query; fetch_meta_keys() delegates to it.
FIXED: Webhook payload builder now uses elmfc_get_skip_fields() instead of a partial hand-rolled list, ensuring elmfc_cc, elmfc_bcc, and any future skip-list additions are excluded from outgoing webhook payloads.
IMPROVED: elmfc_default_texts() now memoizes its return value with a static variable, avoiding array reconstruction on every call.
IMPROVED: elmfc_is_valid_blog() now returns true for blog ID 1 on single-site installations instead of always returning false.
DOCS: readme.txt Additional Information section corrected — elmfc_redirect_url is configured in the Appearance Settings sidebar (post meta), not via a hidden form field.
DOCS: REST endpoint now has a clear note that file uploads are not supported; the AJAX handler should be used for forms with file fields.
DOCS: docs/ directory added to .gitignore and excluded from distributable zip.
7.5.12 – 2026-06-29
SECURITY: reCAPTCHA v3 — omitting the token or leaving the secret unconfigured now rejects the submission instead of silently skipping verification (previously bypassed both reCAPTCHA and math captcha simultaneously).
SECURITY: Rate-limit guard now distinguishes a DB error (false) from zero rows affected (0) — a transient DB failure no longer resets the window and bypasses the limit.
FIXED: Cross-site multisite — redirect URL after submit now read from source blog postmeta before restore_current_blog(); previously always returned empty on cross-site forms (AJAX and REST).
FIXED: Email handler get_skip_fields() now delegates to the global elmfc_get_skip_fields() so future additions to the skip list are automatically inherited.
FIXED: send_emails() now returns true when no email routes are configured, preventing a false “Error sending email” response after a successful CPT save.
FIXED: Impression and submit stat counters — on the very first submission a concurrent-insert race could leave the counter at 1 instead of N; retry logic ensures no increment is lost.
IMPROVED: fetch_meta_keys() in the submissions list table is now cached in a 60-second transient (invalidated on new submission), eliminating a full postmeta DISTINCT scan on every page load.
IMPROVED: “Send to specific user” dropdown raised from 200 to 500 users; sites with more than 500 users now see an inline notice with the total count.
7.5.11 – 2026-04-28
SECURITY: elmfc_redirect_url is no longer emitted as a hidden form field — it is now read directly from post meta by the AJAX and REST handlers, eliminating any possibility of client-side tampering with the post-submission redirect destination.
7.5.10 – 2026-04-27
SECURITY: REST endpoint now enforces the same captcha stack as the AJAX handler — math captcha (token/transient) and reCAPTCHA v3 are both checked before processing REST submissions.
SECURITY: reCAPTCHA v3 secret key and webhook signing secret are now encrypted at rest using AES-256-CBC (same scheme as SMTP password). Existing plain-text values are decrypted transparently via the enc: prefix detection.
SECURITY: Duplicate-form action now uses object-level capability check (edit_post + post ID) instead of the broad edit_posts capability.
SECURITY: Single-submission delete action now validates the post type with elmfc_is_plugin_cpt() before calling wp_delete_post().
FIXED: JSON form import no longer allows overwriting the CPT slug (_elmfc_cpt_slug) — changing the slug via import could silently rename the submission table.
FIXED: wp_die() strings in admin actions are now translatable.
7.5.9 – 2026-04-27
FIXED: Form field labels, placeholder text, and other text inside core/html blocks is now translated. Registers the ms_gpt_translate_html_block filter (added in auto-translate 3.2.31) for my_form posts so GPT receives and translates the raw HTML content.
7.5.8 – 2026-04-27
FIXED: Forms now translate correctly — removed the filter that was telling the auto-translate plugin to skip all block content for my_form posts.
FIXED: The elmfc/form Gutenberg block now registers ms_gpt_remap_block_attrs so its formId attribute is remapped to the destination site’s translated form when a page is translated.
FIXED: my_form slugs are now preserved on translated sites (ms_gpt_preserve_post_slug) so [elmfc name=”…”] shortcodes continue to work.
FIXED: [elmfc id=…] shortcode now falls back to the source site when the translated form doesn’t exist yet on the destination site.
7.5.7 – 2026-04-26
FIXED: Resolved final two PHPCS warnings — reworded inline comment in email handler that was flagged as commented-out code; corrected operator alignment in shortcode file.
7.5.6 – 2026-04-27
SECURITY: REST endpoint redirect URL now validated with wp_validate_redirect() (parity with AJAX handler).
SECURITY: [elmfc_submissions] shortcode now validates the cpt attribute against plugin-owned slugs via elmfc_is_plugin_cpt() — prevents listing arbitrary CPT content.
FIXED: CPT slug save in meta boxes now rejects slugs that belong to built-in or third-party post types (post, page, my_form, etc.) to prevent form submissions being inserted as regular WordPress posts.
7.5.5 – 2026-04-26
SECURITY: Rate limit counter is now atomic (SQL UPDATE + expiry JOIN) — eliminates TOCTOU race that allowed bypass under concurrent requests.
SECURITY: REST endpoint now uses elmfc_get_client_ip() (proxy-aware) instead of bare $_SERVER[‘REMOTE_ADDR’] for rate limiting.
SECURITY: Copy-to-site handler validates target blog ID with elmfc_is_valid_blog() before switch_to_blog().
SECURITY: Resend-email AJAX handler verifies the submission belongs to a plugin-owned CPT via new elmfc_is_plugin_cpt() helper.
SECURITY: Bulk delete and CSV export now validate the ?cpt= slug is a plugin-owned CPT before acting.
SECURITY: Bulk delete verifies each post ID belongs to the expected CPT before deleting.
SECURITY: Redirect URL after submission is validated with wp_validate_redirect() to enforce same-origin policy.
SECURITY: File upload rename() return value is now checked — failed rename discards the temp file instead of attaching a phantom path.
SECURITY: CPT submission meta keys are sanitized with sanitize_key() and private-meta prefix (_) is rejected in elmfc_save_to_cpt().
SECURITY: Settings import rejects files larger than 512 KB before calling file_get_contents().
7.5.4 – 2026-04-26
SECURITY: Fixed DOM-based XSS in showMessage() — replaced .html() with safe DOM construction; captcha question update now uses .text() via new .elmfc-captcha-question span.
SECURITY: REST endpoint (/wp-json/elmfc/v1/submit) now enforces the same per-form, per-IP rate limiting as the AJAX handler.
SECURITY: SMTP password no longer passed through sanitize_text_field() which was silently corrupting passwords containing <, >, or &.
SECURITY: Server-side blocklist of executable extensions (php, phar, asp, sh, etc.) applied to file uploads regardless of admin-configured allowlist.
SECURITY: elmfc_source_site POST/REST parameter is now validated against real network blog IDs before switch_to_blog() is called.
SECURITY: reCAPTCHA v3 verification is now fail-closed — network errors reject the submission instead of silently passing it.
FIXED: elmfc_submissions_shortcode now escapes get_the_title() output and requires edit_posts capability.
FIXED: Cross-site block in main shortcode wrapped in try/finally so restore_current_blog() always runs on exception.
FIXED: Resend email AJAX handler now checks current_user_can(‘edit_post’, $form_id) to prevent cross-form access.
FIXED: Copy-to-site handler now verifies nonce before capability check (correct security gate order).
FIXED: Duplicate admin notices now only display on my_form screens.
FIXED: Settings import now applies type-appropriate sanitizers (URL, int, float, email) instead of blanket sanitize_text_field for all values.
FIXED: CPT registration (elmfc_register_all_dynamic_cpts) now caches results in a transient, eliminating the per-request DB query.
FIXED: Submissions badge no longer runs N+1 DB queries on every admin page load — uses elmfc_sub_counts transient.
FIXED: get_users() in email settings now limited to 200 results to prevent memory issues on large networks.
IMPROVED: Centralized skip-field list via elmfc_get_skip_fields() used across CPT storage.
IMPROVED: Uninstall transient cleanup now uses $wpdb->prepare() consistently.
7.5.2 – 2026-03-04
FIXED: Remaining PHP Deprecated warnings — str_replace(null) caused by get_edit_post_link() returning null for non-public CPT in duplicate handler; replaced with admin_url(‘post.php?…’).
FIXED: preg_replace() null return (on PCRE error) propagating as null $form_content in shortcode; cast result to string.
FIXED: Forms not rendering for visitors — deprecation warnings output before form HTML disrupted page structure; null guards now applied before $post is accessed in asset enqueue.
7.5.1 – 2026-03-04
FIXED: PHP Deprecated warnings — strpos(null) and str_replace(null) — caused by $post->post_content being null on some post types; cast to string before passing to has_shortcode() and apply_filters(‘the_content’, …)
7.5.0 – 2026-03-04
ADDED: Conditional logic — wrap any field(s) in a container with data-show-if-field, data-show-if-value, and optional data-show-if-operator (equals/not_equals/contains/not_empty/empty) to show/hide fields dynamically; hidden fields excluded from validation (M1)
ADDED: Multi-step wizard — add <div class=”elmfc-step”> sections to any form; Previous/Next navigation and an animated progress bar are injected automatically; each step validates before advancing (M2)
ADDED: Google reCAPTCHA v3 per-form — enable in Security Settings with Site Key, Secret Key, and score threshold; replaces the math captcha when active; script loaded automatically in footer (M3)
ADDED: Custom From name and email address per form in Email Settings — leave empty to use the WordPress default sender (M4)
ADDED: Per-field inline error messages — add data-error=”Your message” to any required field; the custom message appears below the field on validation failure (M5)
ADDED: Akismet integration — when Akismet is active and an API key is configured, spam submissions are silently swallowed before emails are sent (M6)
ADDED: Copy form to site (multisite only) — “Copy to site” row action; admins with manage_sites capability can duplicate a form to any other site in the network (M7)
ADDED: Resend email button in the submission detail view — reconstructs the email from stored meta and sends again; updates email status and time (M8)
ADDED: Webhook/Zapier integration — configure a webhook URL per form; payload is POSTed as JSON on each submission; optional HMAC-SHA256 X-ELMFC-Signature header for payload verification (M9)
ADDED: Form analytics — Impressions and Submits counters on the Forms list; impression incremented each time the shortcode renders; submit incremented on each successful send; atomic DB updates (M10)
7.4.9 – 2026-03-04
ADDED: File upload support — add <input type=”file”> to any form; uploaded files attach to the admin email; configurable max size (MB) and allowed extensions per form (Q1)
ADDED: Native Gutenberg block elmfc/form — form picker dropdown + popup toggle in the block inspector; server-side rendered; no shortcode typing needed (Q2)
FIXED: Required checkbox validation — unchecked required checkboxes (e.g. terms acceptance) now highlight in red, matching existing radio and text field behaviour (Q3)
IMPROVED: Popup forms auto-focus the first visible input field when the popup opens (Q4)
FIXED: Cancel button fully restores form state — re-enables all fields, restores submit button label, and shows the button row even after a successful submission (Q5)
ADDED: Submission counter in the Shortcode meta box — shows total count for this form with a direct link to the Submissions page when CPT storage is enabled (Q6)
7.4.8 – 2026-03-03
SECURITY: SMTP password now encrypted at rest using AES-256-CBC (AUTH_KEY-derived key); legacy plain-text passwords continue to work transparently (S2)
SECURITY: SMTP password field never echoed back to the browser — shows “(saved)” placeholder when a password is stored; leave blank to keep existing password
FIXED: _elmfc_email_sent explicitly unset from CPT meta before wp_insert_post (defensive — already excluded by skip list) (N27)
FIXED: CSV export now includes Email Status and Email Time columns from the delivery log (N28)
FIXED: Test emails bypass elmfc_field_value filter so raw field data is always shown in test output (N29)
ADDED: “Save the form before testing” note below the test email button (N30)
ADDED: Submission detail view — click any submission title to see all fields in a full-page table with delete action (A1)
ADDED: HTML email template with site branding header, field table, and plain-text AltBody fallback (E1)
ADDED: REST API endpoint POST /wp-json/elmfc/v1/submit — mirrors AJAX handler, supports all form fields, honeypot, before_send filter, and CPT storage (D1)
ADDED: Export / Import Settings meta box — export all form settings to JSON (password excluded), import from a JSON file with key whitelisting (D4)
7.4.7 – 2026-03-03
SECURITY: Proxy IP headers (X-Forwarded-For, CF-Connecting-IP, X-Real-IP) are now opt-in via define(‘ELMFC_TRUST_PROXY’, true) in wp-config.php — prevents rate-limit bypass by IP header spoofing on non-proxied servers
FIXED: elmfc_rl_limit and elmfc_rl_window added to email body and CPT storage skip lists
FIXED: .elmfc-input-small scoped to .elmfc-settings-section — no longer risks affecting other plugins
ADDED: “Send test email” button in Email Settings — sends a sample submission to the currently logged-in admin’s email, respecting SMTP settings
ADDED: Email delivery log — each CPT submission now stores _elmfc_email_status (sent/failed) and _elmfc_email_time; status shown as ✓/✗ in the Submissions table Email column
7.4.6 – 2026-03-03
FIXED: Rate limiter now runs after captcha validation — wrong captcha answers no longer consume a submission attempt
FIXED: Rate limiter resolves real client IP through Cloudflare, X-Forwarded-For, and X-Real-IP headers before falling back to REMOTE_ADDR
FIXED: Rate limit key is now per-form (elmfc_rl_{form_id}_hash) — different forms no longer share the same counter
FIXED: Cache invalidation hook skips autosave and all built-in WordPress post types; only clears on actual submission CPT writes
FIXED: Submission count cache is only rebuilt when the transient is missing or expired — no redundant DB queries on each page load
FIXED: Copy button double-click race condition — existing reset timer is cancelled before starting a new countdown
FIXED: Copy button original label restored correctly after timeout even if clicked multiple times
ADDED: Per-form rate limit configuration in Security Settings — set max submissions and time window (defaults: 5 per 10 min)
IMPROVED: uninstall.php now removes all elmfc_* transients with a single wildcard query
7.4.5 – 2026-03-03
FIXED: Shortcode meta box copy buttons now work correctly (data-copy attribute was misnamed data-clipboard-text)
FIXED: Copy buttons in shortcode meta box now show “Copied!” feedback text and icon, consistent with list buttons
FIXED: Submission count batch preload moved from pre_get_posts to current_screen — get_current_screen() is now reliably available
FIXED: Last submission date is now included in the batch preload — no per-row query for dates
FIXED: elmfc_sub_counts transient invalidated immediately on save_post/delete_post for any submission CPT
FIXED: elmfc_duplicated URL parameter removed from browser address bar via history.replaceState after redirect
FIXED: elmfc_after_email action already received filtered $data (confirmed correct — no code change needed)
ADDED: Rate limiting — maximum 5 submissions per IP per 10 minutes; returns a clear error message when exceeded
IMPROVED: uninstall.php also cleans up rate-limit transients (elmfc_rl_*) and the sub_counts transient
7.4.4 – 2026-03-03
FIXED: elmfc_redirect_url, elmfc_cc, elmfc_bcc excluded from email body and CPT submission meta
FIXED: Duplicating a form no longer copies CPT storage — flag reset to off, slug cleared to prevent two forms writing to same storage
FIXED: elmfc_before_send returning false now sends a silent success response instead of a confusing email error
FIXED: elmfc_before_send filter is now applied in the AJAX handler (not inside the email class) so cancellation has full context
FIXED: Shortcode meta box shows “Save the form first” prompt instead of empty slug shortcodes on unsaved forms
IMPROVED: Submission count/last-date columns now use a preloaded batch cache — one query set per page load instead of N
ADDED: Character counter for textarea fields with a maxlength attribute (turns red at 90% of limit)
7.4.3 – 2026-03-03
ADDED: Redirect URL after submit — set in Appearance Settings or via hidden field elmfc_redirect_url
ADDED: Duplicate form — row action on the Forms list clones the form and all its settings as a draft
ADDED: Submission count + last submission date columns on the Forms list (linked to Submissions page)
FIXED: Reply-To header now set consistently — admin emails reply to submitter, user copy emails reply to site admin
ADDED: CC and BCC fields in Email Settings sidebar (per-form)
ADDED: elmfc_before_send filter — return false to cancel submission programmatically
ADDED: elmfc_field_value filter — transform or suppress individual field values before email/storage
IMPROVED: Array field values (multi-select, checkbox groups) — empty strings filtered out before storage
7.4.2 – 2026-03-03
IMPROVED: Frontend CSS/JS now loads only on pages that contain an [elmfc] or [elmfc_submissions] shortcode (no longer global)
ADDED: Late-enqueue fallback in shortcode for page builders (Elementor, Divi, etc.) that bypass wp_enqueue_scripts
ADDED: Shortcode meta box in the form editor sidebar — shows all four shortcode variants with one-click copy buttons
ADDED: Submission count badge on the “All Submissions” admin menu item
ADDED: Honeypot anti-spam field — silent discard when a bot fills the hidden field
ADDED: uninstall.php — full cleanup of forms, submission CPTs, and captcha transients on plugin deletion (multisite-aware)
7.4.1 – 2026-03-03
FIXED: Fatal error “Class WP_List_Table not found” — require_once for class-wp-list-table.php moved to file top, before class declaration
7.4.0 – 2026-03-03
ADDED: Dedicated “All Submissions” admin page under Forms menu (WP_List_Table)
ADDED: Submissions overview listing all forms with CPT storage — shows submission count per form
ADDED: Dynamic columns — all submitted field names auto-detected from saved meta and shown as columns
ADDED: Sortable Date and Identifier columns, search, pagination (25 per page)
ADDED: Single-row delete with confirmation, bulk delete
ADDED: Export selected to CSV, Export all to CSV (UTF-8 BOM for Excel, CSV injection protection)
ADDED: Form name (elmfc_form) and source URL (elmfc_url) now saved as submission meta for full context
CHANGED: Submission CPTs no longer appear as individual submenus — replaced by the unified Submissions page
CHANGED: Dynamic CPTs now registered as non-public/hidden (storage only) — data unaffected
7.3.8 – 2026-03-03
REFACTOR: Consolidated admin asset loading into a single admin_enqueue_scripts hook using get_current_screen() — replaces fragile hook+global checks
REFACTOR: Extracted copy-button JS into assets/js/elmfc-admin.js (was inline in PHP)
REFACTOR: Extracted copy-button CSS into elmfc-admin.css (was inline in PHP)
ADDED: Frontend elmfc.css now loaded on the post editor screen so form HTML is styled correctly in the Gutenberg editor
FIXED: CSS version comments updated to match plugin version
7.3.7 – 2026-03-03
ADDED: Clipboard copy buttons on the Forms list — ID column copies [elmfc id=”X”] shortcode, Slug column copies [elmfc name=”slug”] shortcode
ADDED: Visual confirmation feedback (“Copied!”) after clicking copy button
7.3.6 – 2026-03-03
ADDED: Multisite cross-site form resolution — [elmfc name=”slug”] automatically falls back to the main site when the form is not found on the current site
ADDED: Source site ID passed in form POST (elmfc_source_site) so the AJAX handler queries the correct site’s DB tables and meta
CHANGED: Captcha transients now use set_site_transient/get_site_transient (network-wide) to remain accessible regardless of blog context during AJAX
FIXED: elmfc_source_site excluded from emails and CPT meta storage
7.3.5 – 2026-03-03
SECURITY: Captcha solution no longer sent to client — replaced hidden solution field with server-side transient token (one-time use, 1h TTL)
FIXED: Required radio button groups now correctly flagged as invalid when nothing is selected
FIXED: flush_rewrite_rules() now only runs when the CPT slug actually changes, not on every form save
FIXED: SMTP phpmailer_init hook now removed after wp_mail() to prevent accumulation across multiple emails
FIXED: elmfc_form_width_type excluded from hidden form fields (layout-only setting, not needed in POST)
REMOVED: Stale duplicate files in includes/ root and root-level JS/CSS