Skip to content

Add protected options, quarantine, and JSON export/import#79

Draft
ilicfilip wants to merge 3 commits into
developfrom
filip/protected-quarantine-export
Draft

Add protected options, quarantine, and JSON export/import#79
ilicfilip wants to merge 3 commits into
developfrom
filip/protected-quarantine-export

Conversation

@ilicfilip
Copy link
Copy Markdown
Collaborator

@ilicfilip ilicfilip commented May 12, 2026

Summary

Three safety/portability features inspired by orpharion:

  • Protected options — server-side guard prevents deletion of ~120 WordPress core options plus anything matching prefixes (option_optimizer*, aaa_option_optimizer*, _aaaoo_q__*, _transient_*, _site_transient_*). Extensible via the aaa_option_optimizer_protected_options filter. In the UI, a lock icon replaces the Delete button.
  • Quarantine before delete — clicking Delete now moves the option to a new wp_option_optimizer_quarantine table; it can be Restored or Permanently Deleted from a new Quarantine tab. Retention is configurable (1–30 days, default 7) and the expiry action is keep (default — manual cleanup) or delete (auto-cleanup via daily wp-cron). Bulk-delete operations over 25 options prompt the user to confirm. Pass force=true on the REST call to skip quarantine.
  • JSON export/import — per-row and bulk Export download a versioned JSON payload (version, exported_at, site_url, wp_version, options[]). Import accepts the same format with an overwrite toggle; protected option names are always rejected.

REST changes

  • Existing routes hardened: /delete-option, /delete-options, /update-autoload now refuse protected options (HTTP 403). /delete-option and /delete-options quarantine by default (use force=true to hard-delete).
  • New routes under /wp-json/aaa-option-optimizer/v1/:
    • GET /quarantine — list
    • POST /quarantine/restore — restore an option
    • POST /quarantine/delete — permanently delete
    • POST /export — returns JSON payload + X-AAAOO-Filename header
    • POST /import — accepts { payload, overwrite }

Files

New: `src/class-protected-options.php`, `src/class-quarantine.php`, `src/class-exporter.php`, `src/class-importer.php`. Modified: database (new table + helpers), REST (handler updates + new routes), Admin Page (new tabs + settings fields), JS (lock UI, export/import flows, quarantine table).

Test plan

  • Fresh activation: `wp db query "SHOW TABLES LIKE 'wp_option_optimizer%'"` returns both `wp_option_optimizer_tracked` and `wp_option_optimizer_quarantine`.
  • Existing install: load `tools.php?page=aaa-option-optimizer`; quarantine table is auto-created by maybe_upgrade.
  • Delete button is replaced by a lock icon for `siteurl`, `blogname`, `active_plugins`, and `option_optimizer*` rows.
  • Direct REST hit on `siteurl` returns 403; `wp option get siteurl` still works.
  • Delete a benign option → it disappears from wp_options and appears in the Quarantine tab.
  • Restore from quarantine → option reappears with its original value/autoload.
  • Permanently Delete from quarantine → gone from both tables.
  • Set expiry action to `delete`, backdate a row, run `wp cron event run aaa_option_optimizer_quarantine_cleanup` → row is removed.
  • Bulk-delete >25 options → confirmation prompt appears before quarantining.
  • Bulk Export selected options → downloads valid JSON with expected schema.
  • Import that JSON with `overwrite=true` after deleting the originals → all options restored.
  • Import a JSON containing `siteurl` → rejected with `reason: protected` and the option is unchanged.

🤖 Generated with Claude Code

Adopts three safety/portability features inspired by orpharion:

- Protected options (Protected_Options class) — hardcoded WP core list +
  prefix-based protection for the plugin's own options, transients, and
  quarantine rows. Exposes the `aaa_option_optimizer_protected_options`
  filter. Enforced server-side in REST delete/autoload handlers.
- Quarantine workflow (Quarantine class + new wp_option_optimizer_quarantine
  table) — deleting an option moves it to a soft-delete table where it can
  be Restored or Permanently Deleted. Retention is configurable (1–30 days,
  default 7); expiry action is `keep` (default) or `delete`, with a daily
  wp-cron cleanup. 50-row cap prevents runaway growth.
- JSON export/import (Exporter + Importer classes) — per-row and bulk
  export download a versioned JSON payload of option_name/value/autoload;
  import restores from the same format with an overwrite toggle and
  always refuses protected option names.

REST: existing delete handlers default to quarantine (pass force=true to
skip). New routes under /aaa-option-optimizer/v1/: /quarantine,
/quarantine/restore, /quarantine/delete, /export, /import.

Admin UI: new Quarantine tab and Import tab, retention + expiry settings,
lock icon replaces Delete for protected rows, per-row Export button, bulk
Export action.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Test on Playground
Test this pull request on the Playground
or download the zip

ilicfilip and others added 2 commits May 12, 2026 15:43
Removing the legacy `.button-delete { margin-right: 0 }` rule — that
override made sense when Delete was the last button in the cell, but now
Export sits after it. Apply margin-right:0 only to the actual last child
button.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 50-row cap was a foot-gun guard, but it cut against the goal:
quarantine exists specifically so users can be brave about deleting
options and recover later. Capping at 50 means a bulk delete of 80 rows
partially succeeds with no UI surface for the failures — worse UX than
just letting it through.

Replaces the hard cap with a browser confirmation prompt when bulk
deleting more than 25 selected options. The prompt names the count and
reminds the user that quarantine is recoverable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant