Skip to content

Commit 9b62a55

Browse files
authored
Merge pull request #540 from 10up/fix/349
Fix: Uninstall distributor
2 parents ef9ab4c + 19e47fa commit 9b62a55

File tree

4 files changed

+278
-3
lines changed

4 files changed

+278
-3
lines changed

includes/bootstrap.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,75 @@ function() {
224224
1
225225
);
226226

227+
228+
/**
229+
* Add a deactivation modal when deactivating the plugin.
230+
*
231+
* @since x.x.x
232+
*/
233+
function register_deactivation_modal() {
234+
// Exit if deactivating plugin from sub site.
235+
$screen = get_current_screen();
236+
if ( ! ( ! is_multisite() || $screen->in_admin( 'network' ) ) ) {
237+
return;
238+
}
239+
240+
wp_enqueue_script( 'jquery-ui-dialog' );
241+
wp_enqueue_style( 'wp-jquery-ui-dialog' );
242+
243+
add_action(
244+
'admin_footer',
245+
static function () {
246+
printf(
247+
'<div id="my-modal" style="display:none;"><p>%1$s</p><p>%2$s</p><p><code>%3$s</code></p><p>%4$s</p></div>',
248+
esc_html__( 'Would you like to delete all Distributor data?', 'distributor' ),
249+
esc_html__( 'By default, the database entries are not deleted when you deactivate Distributor. If you are deleting Distributor completely from your website and want those items removed as well, add the code below to wp-config.php:', 'distributor' ),
250+
'define( \'DT_REMOVE_ALL_DATA\', true );',
251+
esc_html__( 'After adding this code, the Distributor plugin data will be removed from the website database when deleting the plugin. This will not delete the posts with their metadata other than the subscription. You can review uninstall.php (in the plugin root directory) to learn more about the deleted data. After deleting the Distributor plugin, you can remove the code from the wp-config.php file. Please make sure that this action cannot be undone; take a backup before proceeding.', 'distributor' )
252+
);
253+
}
254+
);
255+
256+
$modal_title = wp_json_encode( __( 'Distributor Deactivation', 'distributor' ) );
257+
$modal_button_title_deactivate = wp_json_encode( __( 'Deactivate', 'distributor' ) );
258+
$modal_button_title_cancel = wp_json_encode( __( 'Cancel', 'distributor' ) );
259+
$script = <<<EOD
260+
jQuery(document).ready(function($) {
261+
const deactivateButton = jQuery('#deactivate-distributor');
262+
deactivateButton.on( 'click', function() {
263+
$("#my-modal").dialog({
264+
modal: true,
265+
title: $modal_title,
266+
width: 550,
267+
buttons: [
268+
{
269+
text: $modal_button_title_cancel,
270+
class: "button-secondary",
271+
click: function() {
272+
$(this).dialog("close");
273+
}
274+
},
275+
{
276+
text:$modal_button_title_deactivate,
277+
class: 'button-primary',
278+
click: function() {
279+
$(this).dialog("close");
280+
window.location.assign(deactivateButton.attr('href'));
281+
},
282+
style: 'margin-left: 10px;'
283+
}
284+
]
285+
});
286+
287+
return false;
288+
});
289+
});
290+
EOD;
291+
292+
wp_add_inline_script( 'jquery-ui-dialog', $script );
293+
}
294+
add_action( 'load-plugins.php', __NAMESPACE__ . '\register_deactivation_modal' );
295+
227296
/**
228297
* We use setup functions to avoid unit testing WP_Mock strict mode errors.
229298
*/

includes/classes/InternalConnections/NetworkSiteConnection.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ public static function build_available_authorized_sites( $user_id = false, $cont
887887
$last_changed = self::set_sites_last_changed_time();
888888
}
889889

890-
$cache_key = "authorized_sites:$user_id:$context:$last_changed";
890+
$cache_key = "dt_authorized_sites:$user_id:$context:$last_changed";
891891
$authorized_sites = get_transient( $cache_key );
892892

893893
if ( $force || false === $authorized_sites ) {
@@ -972,8 +972,8 @@ public static function clear_authorized_sites_cache( $user_id = false ) {
972972
if ( ! $last_changed ) {
973973
self::set_sites_last_changed_time();
974974
} else {
975-
delete_transient( "authorized_sites:$user_id:push:$last_changed" );
976-
delete_transient( "authorized_sites:$user_id:pull:$last_changed" );
975+
delete_transient( "dt_authorized_sites:$user_id:push:$last_changed" );
976+
delete_transient( "dt_authorized_sites:$user_id:pull:$last_changed" );
977977
}
978978
}
979979

uninstall.php

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<?php
2+
/**
3+
* Distributor uninstall script.
4+
*
5+
* @package distributor
6+
* @since x.x.x
7+
*/
8+
9+
// phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
10+
11+
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
12+
die;
13+
}
14+
15+
/*
16+
* Only remove ALL data if DT_REMOVE_ALL_DATA constant is set to true in user's
17+
* wp-config.php. This is to prevent data loss when deleting the plugin from the backend
18+
* and to ensure only the site owner can perform this action.
19+
*/
20+
if ( defined( 'DT_REMOVE_ALL_DATA' ) && true === DT_REMOVE_ALL_DATA ) {
21+
global $wpdb;
22+
23+
/**
24+
* Function to delete all relevant data from the site.
25+
*/
26+
function dt_delete_data() {
27+
global $wpdb;
28+
29+
// Delete post meta and posts of type 'dt_subscription'.
30+
$subscription_post_ids = $wpdb->get_col(
31+
$wpdb->prepare(
32+
"SELECT ID FROM $wpdb->posts WHERE post_type = %s",
33+
'dt_subscription'
34+
)
35+
);
36+
37+
if ( ! empty( $subscription_post_ids ) ) {
38+
$ids_string = implode( ',', array_map( 'intval', $subscription_post_ids ) );
39+
40+
// Delete subscription meta.
41+
$wpdb->query(
42+
"DELETE FROM $wpdb->postmeta WHERE post_id IN ($ids_string)"
43+
);
44+
45+
// Delete subscription posts.
46+
$wpdb->query(
47+
$wpdb->prepare(
48+
"DELETE FROM $wpdb->posts WHERE ID IN (%s)",
49+
$ids_string
50+
)
51+
);
52+
53+
// Clear the post cache.
54+
wp_cache_set_posts_last_changed();
55+
wp_cache_delete_multiple( $subscription_post_ids, 'posts' );
56+
wp_cache_delete_multiple( $subscription_post_ids, 'post_meta' );
57+
}
58+
59+
// Delete relevant options from the options table.
60+
delete_site_options();
61+
}
62+
63+
/**
64+
* Delete all relevant options from the options table.
65+
*/
66+
function delete_site_options() {
67+
global $wpdb;
68+
69+
$option_prefixes = array(
70+
'dt_',
71+
'_transient_dt_',
72+
'_transient_timeout_dt_',
73+
);
74+
75+
// Site transients can be used on single sites too.
76+
// See https://github.com/10up/distributor/pull/540#discussion_r1759587692
77+
if ( ! is_multisite() ) {
78+
$option_prefixes = array_merge(
79+
$option_prefixes,
80+
array(
81+
'_site_transient_dt_',
82+
'_site_transient_timeout_dt_',
83+
)
84+
);
85+
}
86+
87+
// Prepare the WHERE clause for the options table.
88+
$where_clause = implode( ' OR ', array_fill( 0, count( $option_prefixes ), 'option_name LIKE %s' ) );
89+
90+
// Prepare the query.
91+
$query = $wpdb->prepare(
92+
sprintf(
93+
"SELECT option_id, option_name FROM $wpdb->options WHERE %s;",
94+
$where_clause
95+
),
96+
array_map(
97+
function( $prefix ) use ( $wpdb ) {
98+
return $wpdb->esc_like( $prefix ) . '%';
99+
},
100+
$option_prefixes
101+
)
102+
);
103+
104+
// Fetch the options to delete.
105+
$options_to_delete = $wpdb->get_results( $query, ARRAY_A );
106+
107+
if ( ! empty( $options_to_delete ) ) {
108+
// Collect IDs from fetched options.
109+
$ids = array_column( $options_to_delete, 'option_id' );
110+
$ids_string = implode( ',', array_map( 'intval', $ids ) );
111+
112+
// Delete the options using the retrieved IDs.
113+
$wpdb->query(
114+
$wpdb->prepare(
115+
"DELETE FROM $wpdb->options WHERE option_id IN ($ids_string)"
116+
)
117+
);
118+
119+
// Flush the options cache.
120+
$option_names = array_column( $options_to_delete, 'option_name' );
121+
wp_cache_delete_multiple( $option_names, 'options' );
122+
123+
// Flush the alloptions cache.
124+
wp_cache_delete( 'alloptions', 'options' );
125+
}
126+
}
127+
128+
/**
129+
* Delete all relevant options from the sitemeta table (multisite only).
130+
*/
131+
function delete_sitemeta_options() {
132+
global $wpdb;
133+
134+
$option_prefixes = array(
135+
'dt_',
136+
'_site_transient_dt_',
137+
'_site_transient_timeout_dt_',
138+
);
139+
140+
// Prepare the WHERE clause for the sitemeta table.
141+
$where_clause = implode( ' OR ', array_fill( 0, count( $option_prefixes ), 'meta_key LIKE %s' ) );
142+
143+
$site_id = get_current_network_id();
144+
145+
$query = $wpdb->prepare(
146+
sprintf(
147+
"SELECT meta_id, meta_key FROM $wpdb->sitemeta WHERE site_id = %%d AND (%s);",
148+
$where_clause
149+
),
150+
array_merge(
151+
[ $site_id ],
152+
array_map(
153+
function( $prefix ) use ( $wpdb ) {
154+
return $wpdb->esc_like( $prefix ) . '%';
155+
},
156+
$option_prefixes
157+
)
158+
)
159+
);
160+
161+
// Fetch the sitemeta to delete.
162+
$sitemeta_to_delete = $wpdb->get_results( $query, ARRAY_A );
163+
164+
if ( ! empty( $sitemeta_to_delete ) ) {
165+
// Collect IDs from fetched options.
166+
$ids = array_column( $sitemeta_to_delete, 'meta_id' );
167+
$ids_string = implode( ',', array_map( 'intval', $ids ) );
168+
169+
// Delete the sitemeta using the retrieved IDs.
170+
$wpdb->query(
171+
$wpdb->prepare(
172+
"DELETE FROM $wpdb->sitemeta WHERE meta_id IN ($ids_string)"
173+
)
174+
);
175+
176+
// Flush the site options cache.
177+
$key_names = array_column( $sitemeta_to_delete, 'meta_key' );
178+
$key_names = array_map(
179+
function( $key ) use ( $site_id ) {
180+
return $site_id . ':' . $key;
181+
},
182+
$key_names
183+
);
184+
wp_cache_delete_multiple( $key_names, 'site-options' );
185+
}
186+
}
187+
188+
// Check if it's a multisite installation.
189+
if ( is_multisite() ) {
190+
// Loop through each site in the network.
191+
$sites = get_sites();
192+
foreach ( $sites as $site ) {
193+
switch_to_blog( $site->blog_id );
194+
dt_delete_data();
195+
restore_current_blog();
196+
}
197+
198+
// Delete network-wide sitemeta options.
199+
delete_sitemeta_options();
200+
} else {
201+
// Single site.
202+
dt_delete_data();
203+
}
204+
}
205+

webpack.config.release.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = {
1010
new CopyPlugin( {
1111
patterns: [
1212
{ from: 'readme.txt', to: './' },
13+
{ from: 'uninstall.php', to: './' },
1314
{ from: 'README.md', to: './' },
1415
{ from: 'CHANGELOG.md', to: './' },
1516
{ from: 'composer.json', to: './' },

0 commit comments

Comments
 (0)