-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathuninstall.php
More file actions
214 lines (196 loc) · 8.41 KB
/
Copy pathuninstall.php
File metadata and controls
214 lines (196 loc) · 8.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<?php
/**
* ATmosphere uninstall script.
*
* Cleans up all plugin data when the plugin is deleted.
*
* @package Atmosphere
*/
use function Atmosphere\clear_scheduled_hooks_all;
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}
/*
* Load helpers so the cron-hook list stays in lock-step with
* deactivate() and Client::disconnect(). uninstall.php is loaded by
* WordPress without the plugin itself being booted, so this require
* is necessary.
*/
require_once __DIR__ . '/includes/functions.php';
// Remove options.
$atmosphere_options = array(
'atmosphere_connection',
'atmosphere_identity',
'atmosphere_publication_tid',
'atmosphere_publication_cid',
'atmosphere_publication_uri',
'atmosphere_auto_publish',
// Legacy: written by set_handle() in 1.0.x and 1.1.0 as a revert
// snapshot for disconnect. The revert path was removed; the option
// no longer has a producer or consumer. Kept in the uninstall sweep
// so installs upgraded from those versions do not leave an orphan
// row behind.
'atmosphere_previous_handle',
'atmosphere_long_form_composition',
'atmosphere_support_post_types',
'atmosphere_sync_reactions',
'atmosphere_sync_replies',
'atmosphere_last_seen_notification',
'atmosphere_tid_last_ts',
'atmosphere_visibility_cleanup_migrated',
'atmosphere_visibility_cleanup_migrated_offset',
'atmosphere_visibility_cleanup_last_id',
// Canonical value: `\Atmosphere\OAuth\Client::REFRESH_LOCK_OPTION`.
// Hardcoded here because `uninstall.php` runs before the plugin
// bootstrap is loaded, so the constant isn't available.
'_atmosphere_refresh_lock',
// Canonical value: `\Atmosphere\OAuth\Client::DISCONNECTED_OPTION`.
// Hardcoded for the same reason as `_atmosphere_refresh_lock`.
'atmosphere_disconnected',
);
foreach ( $atmosphere_options as $atmosphere_option ) {
delete_option( $atmosphere_option );
}
// Remove scheduled events via the canonical helper. Use the
// all-hooks variant so any still-queued one-shot revoke event
// (scheduled outside the regular hook set by `Client::disconnect()`)
// is also dropped — uninstall is the final cleanup, the encrypted
// ciphertexts in `wp_options['cron']` would otherwise outlive the
// plugin forever.
clear_scheduled_hooks_all();
global $wpdb;
// Remove post meta written by the publisher and document transformer.
$atmosphere_meta_keys = array(
'_atmosphere_bsky_tid',
'_atmosphere_bsky_did',
'_atmosphere_bsky_uri',
'_atmosphere_bsky_cid',
'_atmosphere_bsky_thread_records',
'_atmosphere_bsky_uri_index',
'_atmosphere_bsky_orphan_records',
'_atmosphere_doc_tid',
'_atmosphere_doc_did',
'_atmosphere_doc_uri',
'_atmosphere_doc_cid',
'_atmosphere_doc_ref_pending',
'_atmosphere_visibility_cleanup',
'_atmosphere_blob_ref',
'atmosphere_custom_text',
);
foreach ( $atmosphere_meta_keys as $atmosphere_key ) {
$wpdb->delete( $wpdb->postmeta, array( 'meta_key' => $atmosphere_key ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
}
/*
* Remove comment meta. Two surfaces here:
* - outbound: comment-publishing TID/URI/CID + attempt counter.
* - inbound: reaction-sync stamps every imported reaction with a
* protocol marker + author DID + author avatar URL so
* the gravatar override + dedupe-by-protocol checks work.
*
* The plugin-prefixed keys are safe to wipe by key alone. The
* `protocol` key, however, is unprefixed and could conceivably be
* written by other plugins for their own purposes, so we scope the
* delete to the `'atproto'` value that ATmosphere itself writes.
*/
$atmosphere_comment_meta_keys = array(
'_atmosphere_bsky_tid',
'_atmosphere_bsky_uri',
'_atmosphere_bsky_cid',
'_atmosphere_publish_attempts',
'_atmosphere_author_did',
'_atmosphere_author_avatar',
);
foreach ( $atmosphere_comment_meta_keys as $atmosphere_key ) {
$wpdb->delete( $wpdb->commentmeta, array( 'meta_key' => $atmosphere_key ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
}
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->delete(
$wpdb->commentmeta,
array(
'meta_key' => 'protocol',
'meta_value' => 'atproto',
)
);
// phpcs:enable
// Remove fixed-key transients.
$atmosphere_transients = array(
'atmosphere_oauth_verifier',
'atmosphere_oauth_state',
'atmosphere_oauth_dpop_jwk',
'atmosphere_oauth_resolved',
'atmosphere_invalid_long_form_composition_logged',
);
foreach ( $atmosphere_transients as $atmosphere_transient ) {
delete_transient( $atmosphere_transient );
}
/*
* Remove transient + option families that use dynamic keys:
*
* - atmo_dpop_nonce_<md5> — per-URL DPoP nonces (5 min TTL).
* Wildcard MUST stay in lock-step with `Nonce_Storage::PREFIX`
* ({@see \Atmosphere\OAuth\Nonce_Storage}). uninstall.php runs
* pre-bootstrap, so we can't reference the constant directly
* without loading the autoloader here. Anyone renaming the
* constant should grep for `atmo_dpop_nonce_` and update both.
* - _atmosphere_oauth_rate_<user_id>
* — per-user OAuth rate-limit counter. Stored as a plain option
* (not a transient) so the read+CAS-update loop in
* `Client::rate_limit_check()` can be atomic via INSERT IGNORE +
* UPDATE WHERE option_value = $previous; the window expiry is
* encoded in the value itself rather than relying on transient
* TTL.
* - atmosphere_profile_<md5> — reaction-sync profile cache
* written by `Reaction_Sync::resolve_author()`. No constant —
* grep for `atmosphere_profile_` in includes/.
* - atmosphere_last_seen_own_<col> — reaction-sync watermarks per
* collection (likes, reposts, posts). Wildcard MUST stay in
* lock-step with `Reaction_Sync::OPTION_LAST_SEEN_OWN_PREFIX`
* ({@see \Atmosphere\Reaction_Sync}).
*
* Query for matching option names first, then route deletion through
* `delete_transient()` / `delete_option()` so the object cache
* (Redis, Memcached, `alloptions`) is invalidated alongside the
* underlying `wp_options` row. A plain SQL `DELETE` against the
* options table would leave stale values in any persistent cache,
* so a subsequent reinstall could read them back.
*
* Persistent-object-cache caveat: on installs with a drop-in object
* cache (Redis, Memcached, etc.) `set_transient()` writes to the
* object cache and bypasses `wp_options` entirely — so the LIKE
* queries below return zero rows on those installs, and the only
* dynamic transients that get cleaned are the ones the cache layer
* has already evicted to the database. The remaining keys age out
* naturally on their TTL (DPoP nonces: 5 min; profile cache: 1 hr)
* and contain no decryptable secrets — DPoP nonces are server-issued
* opaque tokens, profile data is public — so the residual is
* acceptable. If a future dynamic-key transient is long-lived or
* holds secret material, switch to a write-time registry pattern.
*/
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$atmosphere_transient_rows = $wpdb->get_col(
"SELECT option_name FROM {$wpdb->options}
WHERE option_name LIKE '\_transient\_atmo\_dpop\_nonce\_%'
OR option_name LIKE '\_transient\_timeout\_atmo\_dpop\_nonce\_%'
OR option_name LIKE '\_transient\_atmosphere\_profile\_%'
OR option_name LIKE '\_transient\_timeout\_atmosphere\_profile\_%'
OR option_name LIKE '\_transient\_atmosphere\_oauth\_%'
OR option_name LIKE '\_transient\_timeout\_atmosphere\_oauth\_%'"
);
$atmosphere_option_rows = $wpdb->get_col(
"SELECT option_name FROM {$wpdb->options}
WHERE option_name LIKE 'atmosphere\_last\_seen\_own\_%'
OR option_name LIKE '\_atmosphere\_oauth\_rate\_%'"
);
// phpcs:enable
foreach ( $atmosphere_transient_rows as $atmosphere_row ) {
// Strip `_transient_` or `_transient_timeout_` prefix to feed
// `delete_transient()` the bare key.
if ( 0 === strpos( $atmosphere_row, '_transient_timeout_' ) ) {
delete_transient( substr( $atmosphere_row, strlen( '_transient_timeout_' ) ) );
} elseif ( 0 === strpos( $atmosphere_row, '_transient_' ) ) {
delete_transient( substr( $atmosphere_row, strlen( '_transient_' ) ) );
}
}
foreach ( $atmosphere_option_rows as $atmosphere_row ) {
delete_option( $atmosphere_row );
}