| @@ -14,8 +14,17 @@ | ||
| 14 | 14 | /** |
| 15 | 15 | * Run all uninstall cleanup. Wrapped in a function so locals don't pollute global scope. |
| 16 | 16 | */ |
| 17 | 17 | function xspeed_uninstall_cleanup() { |
| 18 | + /* | |
| 19 | + * Everything here is ours to delete. `wpdeveloper_xspeed_offer` is NOT — it | |
| 20 | + * is the site's answer about whether it wants this plugin, written by | |
| 21 | + * whichever WPDeveloper plugin offered it. Removing xSpeed is itself an | |
| 22 | + * answer — the siblings read it as one, from that row plus the absent plugin | |
| 23 | + * files. Deleting it here would make every one of them offer xSpeed again to | |
| 24 | + * the user who just took it off. | |
| 25 | + * See docs/guides/installing-from-another-plugin.md. | |
| 26 | + */ | |
| 18 | 27 | delete_option( 'xspeed_options' ); |
| 19 | 28 | |
| 20 | 29 | /* |
| 21 | 30 | * Per-module rows, for the modules THIS plugin ships. The slugs are read |
| @@ -39,8 +48,11 @@ | ||
| 39 | 48 | delete_option( 'xspeed_data_version' ); |
| 40 | 49 | delete_option( 'xspeed_activity_log' ); |
| 41 | 50 | delete_option( 'xspeed_server_type' ); |
| 42 | 51 | delete_option( 'xspeed_oc_dropin_synced' ); |
| 52 | + delete_option( 'xspeed_oc_generation' ); | |
| 53 | + delete_option( 'xspeed_oc_sync_attempts' ); | |
| 54 | + delete_option( 'xspeed_overridden_constants' ); | |
| 43 | 55 | delete_option( 'xspeed_last_mobile_separate' ); |
| 44 | 56 | delete_option( 'xspeed_redirect_to_onboarding' ); |
| 45 | 57 | delete_option( 'xspeed_onboarding_complete' ); |
| 46 | 58 | // Provenance a host plugin wrote before it activated us, and the profile |
| @@ -55,12 +67,73 @@ | ||
| 55 | 67 | delete_option( 'xspeed_stats' ); |
| 56 | 68 | delete_option( 'xspeed_gc_cursor' ); |
| 57 | 69 | wp_clear_scheduled_hook( 'xspeed_gc' ); |
| 58 | 70 | |
| 71 | + global $wpdb; | |
| 72 | + | |
| 73 | + // Hit counters and the Hub attachment flag — ours, and simply never named | |
| 74 | + // here before. xspeed_hit_buffer is BOTH an option and a transient of the | |
| 75 | + // same name (Hit_Counter uses one string for the memory buffer and the | |
| 76 | + // durable counter), so both stores need clearing. | |
| 77 | + delete_option( 'xspeed_hit_buffer' ); | |
| 78 | + delete_transient( 'xspeed_hit_buffer' ); | |
| 79 | + delete_option( 'xspeed_hit_daily' ); | |
| 80 | + delete_option( 'xspeed_hub_site_attached' ); | |
| 81 | + | |
| 82 | + // Usage-tracking state, which lives in the shared WP Insights rows rather | |
| 83 | + // than under our own prefix. Left behind, the consent key survives an | |
| 84 | + // uninstall: a REINSTALL then reads as already opted in before the wizard | |
| 85 | + // has asked anything, and a later deactivation posts a diagnostic payload | |
| 86 | + // for a consent this install never collected (#439). | |
| 87 | + // | |
| 88 | + // The two keyed rows are SHARED with sibling WPDeveloper plugins, so only | |
| 89 | + // our own key comes out and the row itself is deleted only once nothing | |
| 90 | + // else is using it. Deleting them outright would wipe another plugin's | |
| 91 | + // consent state. | |
| 92 | + foreach ( array( 'wpins_allow_tracking', 'wpins_last_track_time' ) as $shared ) { | |
| 93 | + $value = get_option( $shared ); | |
| 94 | + if ( ! is_array( $value ) ) { | |
| 95 | + continue; // Absent, or a shape we did not write — leave it alone. | |
| 96 | + } | |
| 97 | + unset( $value['xspeed'] ); | |
| 98 | + if ( empty( $value ) ) { | |
| 99 | + delete_option( $shared ); | |
| 100 | + } else { | |
| 101 | + update_option( $shared, $value ); | |
| 102 | + } | |
| 103 | + } | |
| 104 | + // The deactivation-feedback payload. Normally consumed-then-deleted by the | |
| 105 | + // tracker's own deactivation send, but that only happens when consent | |
| 106 | + // passes and the send succeeds — a user who deactivates without consent | |
| 107 | + // leaves both rows behind, and they are exactly the orphaned diagnostic | |
| 108 | + // payload #439 describes. | |
| 109 | + delete_option( 'wpins_deactivation_reason_xspeed' ); | |
| 110 | + delete_option( 'wpins_deactivation_details_xspeed' ); | |
| 111 | + // The tracker's recurring send. Cleared on opt-out, but nothing guarantees | |
| 112 | + // an opt-out ever happened before the uninstall. | |
| 113 | + wp_clear_scheduled_hook( 'xspeed_do_weekly_action' ); | |
| 114 | + // Our own WP Insights rows: the site id, the original URL, and the last | |
| 115 | + // payload — whose name embeds the site id. The payload names are | |
| 116 | + // derivable from the id, but a failed earlier uninstall or a renamed row | |
| 117 | + // shape would strand them, so sweep the prefix. `xspeed-pro`'s rows | |
| 118 | + // survive this only because its slug's HYPHEN doesn't match the | |
| 119 | + // underscore in `wpins_xspeed_%` — that separator is load-bearing. | |
| 120 | + delete_option( 'wpins_xspeed_site_id' ); | |
| 121 | + delete_option( 'wpins_xspeed_original_url' ); | |
| 122 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- options API has no prefix delete; uninstall only. | |
| 123 | + $wpins_rows = $wpdb->get_col( | |
| 124 | + $wpdb->prepare( | |
| 125 | + "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", | |
| 126 | + $wpdb->esc_like( 'wpins_xspeed_' ) . '%' | |
| 127 | + ) | |
| 128 | + ); | |
| 129 | + foreach ( (array) $wpins_rows as $wpins_row ) { | |
| 130 | + delete_option( $wpins_row ); | |
| 131 | + } | |
| 132 | + | |
| 59 | 133 | // Score history — the plugin's own table, plus the legacy option the |
| 60 | 134 | // table was migrated from (kept on upgrade so a bad migration is |
| 61 | 135 | // recoverable; there is nothing to recover on uninstall). |
| 62 | - global $wpdb; | |
| 63 | 136 | // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- own table, uninstall. |
| 64 | 137 | $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'xspeed_scores' ); |
| 65 | 138 | delete_option( 'xspeed_score_schema' ); |
| 66 | 139 | delete_option( 'xspeed_score_history' ); |