feat: clean up own data on deactivation and uninstall

Deactivation now clears the endpoint-check transient and the cached rewrite
rules (forcing WordPress to regenerate them without this plugin's routes on
the next request) and activation asks mcp-oauth to re-flush its own rules.
uninstall.php removes all own options/transient/cron/table across every site
of a network, delegating to the new self-contained LIT\MCP\Uninstaller class
so it works without the plugin's autoloader and is unit-testable on its own.
mcp-oauth's data (JWT secret, rewrite marker, refresh-token meta, application
passwords) is left untouched, as is every post/attachment/revision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 04:21:23 +02:00
co-authored by Claude Opus 5.5
parent 522f5bbd19
commit 7035448f7d
7 changed files with 229 additions and 3 deletions
+62
View File
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace LIT\MCP;
/**
* Removes WP-LIT-MCP's own data (uninstall.php, Task 12): options, the endpoint-check
* transient, the log-cleanup cron hook and the change log table – on every site of a
* network.
*
* Deliberately self-contained (no reference to any other LIT\MCP\* class): uninstall.php
* is executed by WordPress core standalone, without loading the plugin's main file first,
* so the Composer/Jetpack autoloader is not guaranteed to have run. The option/hook/table
* names below are therefore duplicated from Access::OPTION, Abilities\ExposurePolicy::OPTION,
* Limits::OPTION, ChangeLog\ChangeLogInstaller::VERSION_OPTION, Admin\ConnectionStatus::
* ENDPOINT_TRANSIENT, ChangeLog\ChangeLogCleanup::HOOK and ChangeLog\ChangeLog::TABLE on
* purpose; tests/Unit/UninstallerTest.php and tests/Unit/UninstallTest.php keep them in
* sync with those constants.
*
* Never touches the bundled wp-media/mcp-oauth library's data (option mcp_jwt_secret,
* option wpmedia_mcp_oauth_rewrite_version, user meta mcp_refresh_jti_*, application
* passwords of connected clients) – that library may be shared with other plugins on
* this site. Never deletes posts, attachments or revisions.
*/
final class Uninstaller
{
private const OPTIONS = ['lit_mcp_allowed_roles', 'lit_mcp_exposure', 'lit_mcp_limits', 'lit_mcp_db_version'];
private const TRANSIENT = 'lit_mcp_endpoint_check';
private const CRON_HOOK = 'lit_mcp_log_cleanup';
private const TABLE = 'lit_mcp_log';
public static function run(): void
{
if (! is_multisite()) {
self::removeSiteData();
return;
}
foreach (get_sites(['fields' => 'ids', 'number' => 0]) as $site_id) {
switch_to_blog((int) $site_id);
self::removeSiteData();
restore_current_blog();
}
}
public static function removeSiteData(): void
{
global $wpdb;
foreach (self::OPTIONS as $option) {
delete_option($option);
}
delete_transient(self::TRANSIENT);
wp_clear_scheduled_hook(self::CRON_HOOK);
$table = $wpdb->prefix . self::TABLE;
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- own table name, not user input.
$wpdb->query("DROP TABLE IF EXISTS {$table}");
}
}