google / google/site-kit-wp

Provide ability to delete all plugin data on uninstall

Open
#8,988 5 comments 0 reactions 1 assignee Claimed by @tofumatt View on GitHub
Needs Documentation P1 Type: Enhancement UX
Dominant language
JavaScript
Stars
1.4k
Forks
383
Avg merge
4d 14h
Merged PRs (30d)
77

Description

## Feature Description

Originally, Site Kit deleted all of its data when the plugin was uninstalled. While this seemed to be the proper approach, it resulted in more problems for users than it solved.

See
- https://github.com/google/site-kit-wp/issues/363

In https://github.com/google/site-kit-wp/issues/1069 we changed this behavior to no longer delete plugin data on uninstall, and allow users to do this via the "reset" functionality before uninstalling if desired.

Over time, we can see that this is not an ideal solution either as there are a number of options that are initialized as soon as the plugin is activated, or kept as persistent values. See also #6992.

One pattern that exists for solving this well is to provide an option in the plugin's settings to delete its data on uninstall. This way, the choice is still left to the user and Site Kit can perform a complete clean of its footprint on the DB when enabled at uninstall time.

---------------

_Do not alter or remove anything below. The following sections will be managed by moderators only._

## Acceptance criteria

* The Site Kit settings screen shows a checkbox labelled "Reset Site Kit when uninstalling" in the Plugin status section, next to the Reset Site Kit link.
* The checkbox is described with the text "Site Kit persists some data across installations, even after being uninstalled. Use this setting if you want to entirely clear all data associated with Site Kit on this WordPress site after uninstalling Site Kit."
* The WordPress Tools page shows the same checkbox, with the same label and description, inside the existing "Reset Site Kit" card.
* The Reset Site Kit confirmation dialog shows the same setting as a checkbox labelled "Reset persistent data", so an administrator can turn it on or off as they confirm a reset.
* The checkbox is described with the text "Site Kit persists some data, even after being reset. Use this setting if you want to entirely clear all data associated with Site Kit on this WordPress site during this reset."
* The setting is off by default, so resetting or uninstalling Site Kit leaves data that persists across installations untouched, exactly as they do today.
* With the setting turned on, resetting Site Kit also removes the data that persists across installations.
* With the setting turned on, deleting the Site Kit plugin through WordPress removes everything Site Kit has stored on the site: options, user settings, transients, post and term metadata, and Site Kit's own posts.
* The setting returns to off after a reset that removed persistent data.
* Only administrators who are able to set up Site Kit can see or change the setting.

## Implementation Brief

* [ ] Create file `includes/Core/Util/Full_Reset_Enabled.php`:
* Add a `Setting` subclass following `includes/Core/Admin_Bar/Admin_Bar_Enabled.php`, with `const OPTION = 'googlesitekitpersistent_full_reset_enabled'`, type `boolean`, default `false`, sanitize callback `boolval`, and a `get()` that casts to `bool`.
* The persistent option prefix is required: `Reset::all()` deletes every `googlesitekit_` option before `Reset::maybe_hard_reset()` reads the setting.

* [ ] Create file `includes/Core/Util/REST_Full_Reset_Controller.php`:
* Take `Full_Reset_Enabled` in the constructor and register routes on the `googlesitekit_rest_routes` filter, following `includes/Core/Tags/Google_Tag_Gateway/REST_Google_Tag_Gateway_Controller.php`.
* Add `core/site/data/full-reset-settings` with a `READABLE` method returning `array( 'enabled' => bool )` and an `EDITABLE` method that reads a required boolean `data.enabled`, stores it, and returns the same shape.
* Use `current_user_can( Permissions::SETUP )` as the permission callback on both methods.
* Add `/core/site/data/full-reset-settings` to `googlesitekit_apifetch_preload_paths`.

* [ ] Update file `includes/Core/Util/Reset.php`:
* Add an optional `Options` constructor parameter defaulting to a new `Options( $context )`, and build a `Full_Reset_Enabled` instance from it.
* In `maybe_hard_reset()`, pass the setting's value as the default for the `googlesitekit_hard_reset_enabled` filter in place of the current `false`, so the filter continues to override the setting in both directions. Update the filter docblock and add an `@since` line for the changed default.
* In `handle_reset_action()`, when the request carries `googlesitekit_full_reset_submitted`, store `! empty( googlesitekit_full_reset )` on `Full_Reset_Enabled` before calling `all()`, reading both values through `$this->context->input()->filter( INPUT_GET, ... )`.

* [ ] Update file `includes/Core/Util/Uninstallation.php`:
* Build a `Full_Reset_Enabled` instance from `$this->options`.
* In the `googlesitekit_uninstallation` callback, after `uninstall()` and `clear_scheduled_events()`, and only when the setting is enabled, call `all()` on a new `Reset( $this->context, $this->options )` and then on a new `Reset_Persistent( $this->context, $this->options )`. The wipe must stay last, as proxy unregistration reads the credentials it deletes.

* [ ] Update file `includes/Core/Admin/Available_Tools.php`:
* Take `Options` in a new constructor and build a `Full_Reset_Enabled` instance from it.
* In `render_tool_box()`, replace the reset link with a `` posting to `admin_url( 'index.php' )`, carrying hidden `action` and `nonce` fields for `Reset::ACTION`, a hidden `googlesitekit_full_reset_submitted` field set to `1`, and a `button button-primary` submit reading "Reset Site Kit".
* Above the submit, render a checkbox named `googlesitekit_full_reset`, checked from the stored setting, labelled "Reset Site Kit when uninstalling", with the description paragraph from the acceptance criteria beneath it.

* [ ] Update file `includes/Plugin.php`:
* Register `Core\Util\REST_Full_Reset_Controller` alongside `Core\Util\Reset`.
* Pass `$options` to `Core\Admin\Available_Tools`.

* [ ] Update file `assets/js/googlesitekit/datastore/site/settings.js`:
* Add `fetchGetFullResetSettingsStore` and `fetchSetFullResetSettingsStore` for `core/site/data/full-reset-settings`, mirroring the `adminBarSettings` stores, and add `fullResetSettings` to `initialState`.
* Add a `setFullResetEnabled( enabled )` action, a `getFullResetSettings()` selector, a `getFullResetEnabled()` registry selector returning `getFullResetSettings()?.enabled`, and a `getFullResetSettings` resolver.

* [ ] Update file `assets/js/components/ModalDialog.js`:
* Accept a `children` prop, render it inside `DialogContent` below `provides` and `notes`, and add it to `propTypes` as `PropTypes.node`.

* [ ] Create file `assets/js/components/settings/SettingsResetOnUninstall.tsx`:
* Render a `Checkbox` with `id` and `name` `reset-on-uninstall`, the label "Reset Site Kit when uninstalling", and the `description` prop set to the paragraph from the acceptance criteria.
* Bind it to `getFullResetEnabled` and `setFullResetEnabled` on `CORE_SITE`, disabled and loading while the stored value is `undefined`, following the admin bar checkbox in `assets/js/components/settings/SettingsPlugin.js`.
* On change, call `trackEvent( viewContext, 'enable_full_reset' )` or `trackEvent( viewContext, 'disable_full_reset' )`.

* [ ] Update file `assets/js/components/settings/SettingsAdmin.js`:
* Render `SettingsResetOnUninstall` in the Plugin status footer, in its own `Cell` above the `ResetButton` cell.

* [ ] Update file `assets/js/components/ResetButton.js`:
* Read `getFullResetEnabled()` from `CORE_SITE` and hold the dialog checkbox in local state, seeded from the stored value each time the dialog opens.
* Render a `Checkbox` inside `ModalDialog` with `id` and `name` `reset-persistent-data`, the label "Reset persistent data", and the `description` prop set to the paragraph from the acceptance criteria, disabled and loading while the stored value is `undefined`.
* In `handleUnlinkConfirm`, when the checkbox differs from the stored value, await `setFullResetEnabled()` and send the matching `enable_full_reset` or `disable_full_reset` event before calling `reset()`.

* [ ] Update file `assets/sass/components/settings/_googlesitekit-settings-module.scss`:
* Space the new checkbox from the Reset Site Kit link in the Plugin status footer.

### Test Coverage

* `tests/phpunit/integration/Core/Util/Full_Reset_EnabledTest.php` (new file):
* The default value is `false`, and non-boolean values are stored as booleans.
* `tests/phpunit/integration/Core/Util/REST_Full_Reset_ControllerTest.php` (new file):
* `GET` returns the stored value and `POST` updates it for a user who can set up Site Kit.
* Both methods are rejected for a user without that capability.
* `tests/phpunit/integration/Core/Util/ResetTest.php`:
* With the setting enabled, a reset removes `googlesitekitpersistent_` options; with it disabled, they survive.
* The `googlesitekit_hard_reset_enabled` filter still overrides the setting in both directions.
* The reset admin action saves the setting from the Tools page form fields before deleting anything.
* `tests/phpunit/integration/Core/Util/UninstallationTest.php`:
* With the setting enabled, `googlesitekit_uninstallation` removes regular and persistent options, user options, post meta, term meta and Site Kit posts.
* With the setting disabled, all Site Kit data survives uninstallation.
* The proxy unregistration request is still made before any data is deleted.
* `tests/phpunit/integration/Core/Admin/Available_ToolsTest.php`:
* The checkbox renders checked or unchecked to match the stored setting.
* Nothing renders for a user who cannot set up Site Kit.
* This test constructs `Available_Tools` with no arguments and needs updating for the new constructor.
* `assets/js/googlesitekit/datastore/site/settings.test.ts`:
* `getFullResetEnabled` is `undefined` before resolution and returns the stored value afterwards.
* `setFullResetEnabled` sends the new value and updates the store.
* `assets/js/components/settings/SettingsResetOnUninstall.test.tsx` (new file):
* The label and description render, the checkbox reflects the stored value, and changing it saves the new value.
* `assets/js/components/ResetButton.test.js`:
* The dialog checkbox reflects the stored setting.
* Confirming with the checkbox changed saves the setting before the reset request is sent.
* Cancelling the dialog saves nothing.
* Storybook:
* Add a story for `SettingsResetOnUninstall` covering the checked and unchecked states.
* Add a story to `assets/js/components/ResetButton.stories.js` with the dialog open, covering the checkbox checked and unchecked.

## QA Brief

*

## Changelog entry

*

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.