Skip to content

Feature: Cleanup plugin data on uninstall via user opt in#692

Open
hbhalodia wants to merge 3 commits into
WordPress:developfrom
hbhalodia:feature/issue-690
Open

Feature: Cleanup plugin data on uninstall via user opt in#692
hbhalodia wants to merge 3 commits into
WordPress:developfrom
hbhalodia:feature/issue-690

Conversation

@hbhalodia

@hbhalodia hbhalodia commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

What?

Closes #690

Why?

  • PR provides the way to cleanup the plugin data on uninstall based on user's optin.

How?

  • Adds uninstall.php file, UI settings for opt in and cleanup class to remove tables, options and cron schedules.

Use of AI Tools

  • Yes
  • Claude Code, Opus 4.8
  • Used for implementation. Implementation was reviewed and tested by me.

Testing Instructions

  1. Activate AI plugin.
  2. Go to Settings --> AI.
  3. Scroll down to end, enable option to delete the data on uninstall.
  4. Uninstall the plugin via plugin list page.
  5. Open the database and check for wpai_* options or check for table wp_wpai_request_logs. This should not exists.

Screenshots or screencast

Screen.Recording.2026-06-10.at.12.26.58.PM.mov

Changelog Entry

Added - Cleanup plugin data based on user opt in.


AI Summary

Summary

Implements full data cleanup when the plugin is deleted, addressing #690 (no uninstall.php, custom table and wpai_* options left behind). Cleanup is opt-in and destructive, so it only runs when the site explicitly enables a new "Remove all plugin data on uninstall" setting. Deletion happens on uninstall only — never on deactivation.

Behavior

  • Adds an opt-in toggle in the AI settings page (a collapsible "Data" section), disabled by default.
  • When the toggle is off (default), uninstalling the plugin leaves all data intact — no behavior change for existing users.
  • When the toggle is on, deleting the plugin removes:
    • The custom table {$wpdb->prefix}wpai_request_logs.
    • All wpai_* options.
    • All plugin transients (_transient_wpai_* / _transient_timeout_wpai_* and their site-transient equivalents).
    • The scheduled wpai_request_logs_cleanup cron event.
  • Multisite: cleanup runs per-site (get_sites() + switch_to_blog()), and the opt-in is evaluated per site so each site controls its own data.
  • Cleanup runs from uninstall.php (the WordPress-recommended approach) rather than register_uninstall_hook(), keeping the logic out of the normal runtime path.

Changes

Backend (PHP)

  • uninstall.php (new) — plugin-root uninstall entry point. Guards on WP_UNINSTALL_PLUGIN, loads only the autoloader (deliberately not ai.php, to avoid bootstrapping Main), and calls Uninstall::uninstall().
  • includes/Admin/Uninstall.php (new) — the cleanup service:
    • uninstall() — multisite-aware orchestration (loops sites; single-site otherwise).
    • maybe_clean_current_site() — no-ops unless wpai_remove_data_on_uninstall is true for the current site.
    • Drops the request-logs table, deletes wpai_* options (via esc_like + LIKE), deletes plugin transients, and clears the request-log cleanup cron.
    • Exposes OPTION_REMOVE_DATA ('wpai_remove_data_on_uninstall') as the single source of truth for the option key.
  • includes/Settings/Settings_Registration.php — registers wpai_remove_data_on_uninstall (boolean, default false, rest_sanitize_boolean, show_in_rest: true) under the existing ai_experiments option group, so the React UI can read/write it via the core root/site entity.

Frontend (React / TypeScript)

  • routes/ai-home/hooks/use-data-removal-setting.ts (new) — useDataRemovalSetting() returning { enabled, update, isSaving }, mirroring the use-developer-feature-settings pattern. Reads the value from the edited root/site record and persists via __experimentalSaveSpecifiedEntityEdits, with success/error snackbars:
    • Success: "Data removal on uninstall enabled." / "…disabled."
    • Error: "Failed to save settings."
  • routes/ai-home/components/DataRemovalSetting.tsx (new) — presentational component consuming the hook: a @wordpress/ui warning Notice describing the consequences, stacked above a ToggleControl.
  • routes/ai-home/stage.tsx — integrates the control as a DataForm collapsible card section (layout: { type: 'card', withHeader: true, isOpened: true, isCollapsible: true }, label "Data"), consistent with the existing experiment-group accordions. The field is a presentational pseudo-field (same approach as the existing section-actions-* fields), rendered last.

Tests

  • tests/Integration/Includes/Admin/UninstallTest.php (new):
    • test_uninstall_removes_data_when_opted_in — seeds the table + wpai_* options + a scheduled event, opts in, asserts all are removed and a non-wpai_ option is preserved.
    • test_uninstall_preserves_data_when_not_opted_in — asserts data is untouched when the opt-in is off.
    • Flushes the object cache after uninstall() since the bulk DELETE runs via direct SQL (irrelevant during a real uninstall request, but the in-request alloptions cache would otherwise return stale values).

Implementation notes

  • Opt-in default off — preserves existing behavior; matches the issue's recommendation and WordPress guidelines (don't surprise users by deleting data).
  • Deactivation is untouched — only uninstall.php performs deletion, so deactivating remains fully reversible.
  • Option sweep uses LIKE 'wpai\_%' (escaped via esc_like) so dynamic per-feature options (wpai_feature_{id}_enabled, wpai_feature_{id}_field_developer, etc.) are covered without enumerating them.
  • Snackbar on change — the toggle persists through the hook (not DataForm's generic handleChange) so the success message reads cleanly; routing it through buildToggleMessage would have shown the raw option key.
  • Focus retention — the toggle is intentionally not disabled during save. Disabling mid-save drops keyboard focus (a disabled element can't hold focus); the other settings toggles don't disable on save, so this matches their behavior.

Testing instructions

  1. Activate the plugin and visit AI → settings; expand the Data section.
  2. Confirm the toggle is off by default and a warning Notice is shown.
  3. Toggle it on/off — confirm a snackbar appears and keyboard focus stays on the toggle.
  4. With the toggle off, delete the plugin → confirm wp_wpai_request_logs and wpai_* options remain.
  5. With the toggle on, delete the plugin → confirm the table, wpai_* options, transients, and the wpai_request_logs_cleanup cron are all removed.
  6. Run the integration tests:
    npm run wp-env:test start
    npm run test:php tests/Integration/Includes/Admin/UninstallTest.php

Open WordPress Playground Preview

@hbhalodia hbhalodia marked this pull request as ready for review June 10, 2026 07:00
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: hbhalodia <hbhalodia@git.wordpress.org>
Co-authored-by: itsgajendraSingh <gajendrasingh@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

)
);

register_setting(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So I know this was mentioned in the attached Issue but not sure I agree with having a setting for this. This seems like a lot to ask the average user to have to decide if they want this checked or not (most won't fully understand what it means). I'm open to differing opinions here but I'd suggest we remove the setting and just have the data deletion happen by default. cc / @jeffpaul

New data settings section

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also fine to avoid an additional setting here as well and default to data deletion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @dkotter @jeffpaul, In my opinion, we should go with what other plugins do mostly. As I have from 10 plugins, almost 8-9 plugins provide the an user option to cleanup the data in either way.

Either they provide the settings or when deactivate/uninstall they provide a modal popup to ask user choice to retain/remove the data.

In any how, we should respect the user's choice if they want to cleanup or not.

If we are not adding this, we can update this choice when user try to uninstall the plugin?

@jeffpaul jeffpaul added this to the 1.1.0 milestone Jun 10, 2026
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.

Plugin does not clean up database table and options on uninstall

3 participants