Skip to content

feat: save form submissions before delivery#2856

Merged
Soare-Robert-Daniel merged 17 commits into
developmentfrom
feat/better-form-retention
Jun 16, 2026
Merged

feat: save form submissions before delivery#2856
Soare-Robert-Daniel merged 17 commits into
developmentfrom
feat/better-form-retention

Conversation

@Soare-Robert-Daniel

@Soare-Robert-Daniel Soare-Robert-Daniel commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Closes #2775

Summary

Persist every valid submission as an otter_form_record before any delivery action runs, so a failed email/integration can no longer lose the data.

  • move core storage (CPT, save hook, submissions dashboard, /form/confirm) from Otter Pro to lite; Pro keeps filter, export and Stripe steps)
  • replace the Save Location selector with an Email Notification toggle; legacy submissionsSaveLocation values are migrated at read time and rewritten on the next editor save
  • record delivery_status/delivery_errors meta on each Record and surface them in the dashboard list and detail views
  • treat captcha provider outages as infrastructure failures: save the Record, skip delivery, alert the admin (also fixes a latent fatal on WP_Error responses in check_form_captcha)
  • throttle admin alerts to one per form per hour per failure type
  • add unit suites (form server, submissions storage, Pro delivery) and a form-retention e2e spec with scenario tooling in the e2e mu-plugin

Screenshots

CleanShot 2026-06-12 at 15 06 17@2x CleanShot 2026-06-12 at 15 33 26@2x

Test instructions

  • The Submissions section is now available for free. Any form that is made should save the data in the submissions.
  • Check if the saving is working fine. It should save the entries even without an SMTP client.

Checklist before the final review

  • Included E2E or unit tests for the changes in this PR.
  • Visual elements are not affected by independent changes.
  • It is at least compatible with the minimum WordPress version.
  • It loads additional script in frontend only if it is required.
  • Does not impact the Core Web Vitals.
  • In case of deprecation, old blocks are safely migrated.
  • It is usable in Widgets and FSE.
  • Copy/Paste is working if the attributes are modified.
  • PR is following the best practices

Persist every valid submission as an otter_form_record before any delivery action runs, so a failed email/integration can no longer lose the data.
  - move core storage (CPT, save hook, submissions dashboard, /form/confirm)
    from Otter Pro to lite; Pro keeps filter, export and Stripe steps
  - replace the Save Location selector with an Email Notification toggle; legacy submissionsSaveLocation values are migrated at read time and rewritten on the next editor save
  - record delivery_status/delivery_errors meta on each Record
  and surface them in the dashboard list and detail views
  - treat captcha provider outages as infrastructure failures: save the Record, skip delivery, alert the admin (also fixes a latent fatal on WP_Error responses in check_form_captcha)
  - throttle admin alerts to one per form per hour per failure type
  - add unit suites (form server, submissions storage, Pro delivery) and a form-retention e2e spec with scenario tooling in the e2e mu-plugin
@pirate-bot pirate-bot added the pr-checklist-complete The Pull Request checklist is complete. (automatic label) label Jun 10, 2026
@pirate-bot

pirate-bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Diff

Package Old Size New Size Diff
Animations 178.27 KB 178.27 KB 0 B (0.00%)
Blocks 1.51 MB 1.51 MB 118 B (0.01%)
CSS 7.87 KB 7.87 KB 0 B (0.00%)
Dashboard 111.06 KB 111.06 KB 0 B (0.00%)
Onboarding 68.14 KB 68.14 KB 0 B (0.00%)
Export Import 4.7 KB 4.7 KB 0 B (0.00%)
Pro 322.45 KB 321.06 KB -1.39 KB (-0.43%)

@pirate-bot

pirate-bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Plugin build for 3152c9f is ready 🛎️!

@pirate-bot

pirate-bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

E2E Tests

Playwright Test Status: See serial and parallel matrix jobs

Performance Results serverResponse: {"q25":462.3,"q50":464.4,"q75":494.5,"cnt":10}, firstPaint: {"q25":571.4,"q50":650.25,"q75":730,"cnt":10}, domContentLoaded: {"q25":3469,"q50":3513.15,"q75":3529.3,"cnt":10}, loaded: {"q25":3471.1,"q50":3515.05,"q75":3531.1,"cnt":10}, firstContentfulPaint: {"q25":4010.2,"q50":4052.35,"q75":4089.5,"cnt":10}, firstBlock: {"q25":13691.1,"q50":13763.25,"q75":13860.9,"cnt":10}, type: {"q25":22.97,"q50":23.58,"q75":25.64,"cnt":10}, typeWithoutInspector: {"q25":18.88,"q50":20.34,"q75":25.17,"cnt":10}, typeWithTopToolbar: {"q25":27.71,"q50":30.22,"q75":33.3,"cnt":10}, typeContainer: {"q25":13.09,"q50":13.8,"q75":14.85,"cnt":10}, focus: {"q25":105.63,"q50":107.12,"q75":112.71,"cnt":10}, inserterOpen: {"q25":36.07,"q50":36.93,"q75":38.89,"cnt":10}, inserterSearch: {"q25":12.42,"q50":12.64,"q75":12.89,"cnt":10}, inserterHover: {"q25":5,"q50":5.2,"q75":5.6,"cnt":20}, loadPatterns: {"q25":1510.91,"q50":1560.18,"q75":1660.56,"cnt":10}, listViewOpen: {"q25":215.59,"q50":216.65,"q75":223.96,"cnt":10}

Soare-Robert-Daniel and others added 7 commits June 10, 2026 15:37
- fix JSDoc errors in e2e helpers (param names, alignment)
- drop stale phpstan baseline entries for removed transition_draft_to_read()
- ignore deliberate method_exists() guard for older Otter Pro versions
- add WP_REST_Request generic to replace_corrupted_form_data() docblock
- ignore intentional meta_query usage in bounded record lookups
- e2e: close the font family popover while fonts load; rendering the full
  font list starves actionability checks on slow CI runners

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
get_the_title() returns an empty string for untitled pages, leaving the
Post column link with no visible text. Mirror the core list-table
convention instead, and fall back to the stored post_url when the
resolved permalink is empty (deleted source post).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@abaicus abaicus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things look good, @Soare-Robert-Daniel , I just have a suggestion to maybe split the form submissions class if possible and it makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a chance we could maybe split this file a bit by concerns? If not, it's ok.

Soare-Robert-Daniel and others added 9 commits June 16, 2026 16:34
Move uploaded-file cleanup (delete on record delete + path helpers) into a
dedicated Form_Records_Files class that registers its own before_delete_post
hook. Behaviour unchanged; covered by the existing permanent-delete tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the Pro list-table filter UI (form/post dropdowns, parse_query handler,
locked upsell) into a dedicated Form_Records_Filters class registering its own
restrict_manage_posts/parse_query hooks. Add a characterization test for the
Pro filter query via the parse_query hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the single-record edit screen (submission-data meta box, Update box with
delivery status, admin-menu placement, field save handler) into a dedicated
Form_Records_Meta_Box class registering its own add_meta_boxes/admin_menu/
save_post hooks. Add a characterization test for saving edited field values
via the save_post hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the Submissions list-table presentation (columns, sortable columns,
bulk/row-action definitions, per-column values, delivery badge, unread-row
styling) into a dedicated Form_Records_List_Table class. Status mutations
(bulk handler, untrash restore) stay in Form_Submissions. Add a
characterization test for the delivery column via the custom-column hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the otter_form_record post type, its read/unread statuses and the admin
capability grants into a dedicated Form_Records_Post_Type class registering its
own init/admin_init hooks. Update the test setup to grant caps via the new
class.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the admin-ajax bulk export handler into a dedicated Form_Records_Export
class registering its own wp_ajax hook, and drop the now-unused Pro import from
Form_Submissions. Update the export test to call the new class.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rm-retention

# Conflicts:
#	src/blocks/test/e2e/blocks/ai-block.spec.js
…rm-retention

# Conflicts:
#	phpstan-baseline.neon
#	src/blocks/test/e2e/blocks/ai-block.spec.js
Integrate the Cloudflare Turnstile captcha feature (#2832) with the form
submission retention work.

Conflict resolutions:
- class-form-server.php: union the captcha-verification guard
  (form_has_captcha() + token present + no prior error) and keep the
  infrastructure-failure handling (mark_infrastructure_failure on
  is_wp_error/non-200), which subsumes development's simpler is_wp_error
  check.
- otter-e2e-bootstrap.php: keep both captcha mocks — scenario-mode
  mock_captcha_provider (recaptcha) and stub_captcha_http_verification_for_e2e
  (recaptcha + turnstile). The /captcha endpoint now also sets a dummy
  recaptcha secret for active scenario modes so the merged empty($secret)
  gate doesn't short-circuit.
- playwright.config.js: keep both serial specs (form-retention,
  form-turnstile).

Also fix a pre-existing race in the form block surfaced by the merged
behavior: a late/duplicate server load could migrate the legacy
submissionsSaveLocation value back over an Email Notification toggle the
user had just changed. A notificationDirty ref now preserves the edited
value once dirty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Soare-Robert-Daniel Soare-Robert-Daniel merged commit e34053a into development Jun 16, 2026
16 of 17 checks passed
@Soare-Robert-Daniel Soare-Robert-Daniel deleted the feat/better-form-retention branch June 16, 2026 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-checklist-complete The Pull Request checklist is complete. (automatic label)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve Form block resilience: save entry to database before sending email and log errors

3 participants