CI / Coding Standards (pull_request) Failing after 28s
CI / Tests (PHP 8.5) (pull_request) Failing after 27s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Failing after 39s
CI / Tests (PHP 8.3) (pull_request) Failing after 1m7s
CI / Tests (PHP 8.2) (pull_request) Failing after 1m8s
CI / Static Analysis (pull_request) Successful in 1m17s
CI / Build Plugin Zip (pull_request) Skipped
The assessment looked for three things: whether students can reach each other's bookings, whether payment settings can be dodged, and whether the plugin opens a way into the rest of the install. The student-isolation and payment paths held up. These are what did not. - The front-end login form told WordPress not to work out whether the site was secure, so on HTTPS every student's session cookie was issued without the Secure flag. wp_signon() only derives it from is_ssl() when the second argument is left at its default; an explicit false reads like "no preference" and is not. - The update check took whatever download URL the release API returned and handed it to core, which unpacks it over the installed plugin. The package must now be https on git.unsupervised.ca exactly, compared on the parsed host so a lookalike name cannot pass. - Uninstalling dropped 2 of 14 tables and left the Stripe secret and webhook signing key in wp_options. Removal is now a choice made in advance on Access -> Plugin removal: records are kept unless the owner opts in (with a typed confirmation), while credentials and the borrowed core registration settings go every time. - Open registration switches on the site-wide users_can_register and makes Student the default role, arming any other signup form on the site to mint students who could book and be billed immediately. The pending state is now decided once, on user_register, rather than by whichever form created the account. - Cancel and withdraw answered "not yours" differently from "does not exist", which let a signed-in student enumerate the studio's bookings. Both now give the same 404. Co-Authored-By: Claude Opus 5 <[email protected]>
80 lines
4.2 KiB
Markdown
80 lines
4.2 KiB
Markdown
# Feature: Data Removal
|
|
|
|
## Overview
|
|
What deleting the plugin takes with it, and how the site owner says so.
|
|
|
|
WordPress gives an uninstall no interface of its own: `uninstall.php` runs
|
|
headless, after the plugin is already gone from the screen, with no opportunity
|
|
to ask anything. So the answer is given in advance, on **Access → Plugin
|
|
removal**, and read back at uninstall time.
|
|
|
|
Two things are true at once, and the split below is how both are honoured:
|
|
|
|
- **A studio's records are irreplaceable.** Lessons taught, payments taken, what
|
|
families agreed to and when. A delete during a migration, or a
|
|
delete-and-reinstall while troubleshooting, must not be the thing that loses
|
|
them. So the data is **kept unless the owner explicitly says otherwise**.
|
|
- **Credentials are not records.** A Stripe secret and webhook signing key can be
|
|
re-pasted from the Stripe dashboard in under a minute. Live keys sitting in
|
|
`wp_options` on a site that no longer has the code to use them are nothing but
|
|
exposure. So those are **always** removed.
|
|
|
|
## Option `us_delete_data_on_uninstall`
|
|
`'1'` or `'0'` (default `'0'`). Written only from the Access page.
|
|
|
|
## Always removed
|
|
Whatever the setting says:
|
|
|
|
| What | Why |
|
|
|---|---|
|
|
| `us_stripe_secret_key`, `us_stripe_webhook_secret`, `us_stripe_publishable_key`, `us_stripe_mode` | Credentials, not records — cheap to restore, dangerous to leave |
|
|
| `us_schedular_latest_release` transient | Cached release metadata; meaningless without the plugin |
|
|
| The `us_generate_due_payments` cron event | Deactivation clears it too, but a site whose plugin files simply vanished never ran that hook |
|
|
| `users_can_register` / `default_role` | Restored from the snapshot open registration took (`us_registration_prev_*`). These are the *site's* settings, borrowed; leaving them behind would leave the site accepting public signups into a Student role that is about to stop existing. Only acts when a snapshot exists |
|
|
|
|
## Removed only on an explicit full purge
|
|
- Every table in `Schema::TABLES` — all 14, dropped by name.
|
|
- Every remaining `us_*` option, including the removal setting itself.
|
|
- Every `us_*` user meta key, for all users (`delete_metadata( 'user', 0, $key, '', true )`) — billing overrides, child markers, birth years, pending-signup state.
|
|
- The `us_studio_admin`, `us_instructor` and `us_student` roles.
|
|
|
|
Roles go **only** on a full purge. A site keeping its data is keeping its
|
|
students, and a student whose role was deleted out from under them holds no
|
|
capabilities at all until the plugin is reinstalled.
|
|
|
|
`Schema::TABLES` is the single list of tables the plugin owns; the `CREATE TABLE`
|
|
statements in `Schema::tables()` spell their own names out, so a new table has to
|
|
be added in both places or uninstalling will leave it behind.
|
|
|
|
## Admin Interface
|
|
**Access → Plugin removal** (`manage_options` — the same capability as deleting a
|
|
plugin, so the switch and the act it governs are in the same pair of hands):
|
|
|
|
- A read-only note stating what is removed regardless.
|
|
- **Erase everything when the plugin is deleted** — off by default.
|
|
|
|
Turning it **on** takes the tick *and* the word `DELETE` typed into a confirmation
|
|
box. It is the only place in the plugin where a stray click is unrecoverable, so
|
|
the checkbox alone is not enough. A refused confirmation reports why and still
|
|
saves everything else on the page — a mistyped word must not silently swallow a
|
|
capability change made in the same submit.
|
|
|
|
Turning it **off** needs nothing but unticking the box. Saving the page for some
|
|
other reason while it is already on leaves it on, without asking for the word
|
|
again.
|
|
|
|
## Implementation
|
|
- `Unsupervised\Schedular\Uninstaller` — the option, and the whole of `run()`
|
|
- `uninstall.php` — guards on `WP_UNINSTALL_PLUGIN`, loads the autoloader, calls `Uninstaller::run()`
|
|
- `Unsupervised\Schedular\Schema::TABLES` — the table list
|
|
- `Unsupervised\Schedular\Auth\AccessSettings` — the setting's UI and the typed confirmation
|
|
- `templates/admin/access.php`
|
|
|
|
## Tests
|
|
- `tests/Unit/UninstallerTest.php`
|
|
- `tests/Unit/Auth/AccessSettingsTest.php` (the confirmation rules)
|
|
|
|
## Related
|
|
- `payments.md` — the Stripe credentials this always forgets
|
|
- `account-registration.md` — the core options open registration borrows
|