Remove Leftover Data From Uninstalled Plugins
Adds Tools > Leftover Plugin Data, where you enter a prefix (such as old_slider_) to see how many options, post meta, user meta and scheduled tasks use it, and delete them after reviewing the counts.
Opens WPIDE on
Code
add_action( 'admin_menu', function () {
add_management_page( 'Leftover Plugin Data', 'Leftover Plugin Data', 'manage_options', 'wpide-leftovers', function () {
global $wpdb;
$prefix = isset( $_POST['wpide_prefix'] ) ? sanitize_key( wp_unslash( $_POST['wpide_prefix'] ) ) : '';
$blocked = array( 'wp_', 'wp', '_wp', 'widget', 'theme', 'user', 'site', 'blog', 'woocommerce', 'wc_', '_transient', '_site_transient' );
$valid = strlen( $prefix ) >= 4 && ! in_array( $prefix, $blocked, true ) && 0 !== strpos( $prefix, $wpdb->prefix );
$counts = array();
if ( $prefix && isset( $_POST['_wpnonce'] ) && check_admin_referer( 'wpide_leftovers' ) ) {
if ( ! $valid ) {
echo '<div class="notice notice-error"><p>Please use a more specific prefix (at least 4 characters, not a WordPress prefix).</p></div>';
} else {
$like = $wpdb->esc_like( $prefix ) . '%';
$counts = array(
'Options' => (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE %s", $like ) ),
'Post meta' => (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", $like ) ),
'User meta' => (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", $like ) ),
);
$cron = 0;
foreach ( (array) _get_cron_array() as $hooks ) {
foreach ( array_keys( (array) $hooks ) as $hook ) {
$cron += 0 === strpos( $hook, $prefix ) ? 1 : 0;
}
}
$counts['Scheduled tasks'] = $cron;
if ( isset( $_POST['wpide_delete_leftovers'] ) && current_user_can( 'manage_options' ) ) {
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $like ) );
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", $like ) );
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", $like ) );
foreach ( (array) _get_cron_array() as $hooks ) {
foreach ( array_keys( (array) $hooks ) as $hook ) {
if ( 0 === strpos( $hook, $prefix ) ) {
wp_unschedule_hook( $hook );
}
}
}
wp_cache_flush();
printf( '<div class="notice notice-success"><p>%s</p></div>', esc_html( sprintf( 'Deleted %d item(s) starting with "%s".', array_sum( $counts ), $prefix ) ) );
$counts = array();
}
}
}
echo '<div class="wrap"><h1>Leftover Plugin Data</h1><form method="post">';
wp_nonce_field( 'wpide_leftovers' );
echo '<p><label for="wpide-prefix">Prefix used by the removed plugin</label><br><input type="text" id="wpide-prefix" name="wpide_prefix" value="' . esc_attr( $prefix ) . '" class="regular-text" placeholder="old_slider_"></p>';
submit_button( 'Preview', 'secondary', 'wpide_preview', false );
if ( $counts && array_sum( $counts ) ) {
echo '<table class="widefat striped" style="max-width:420px;margin:1em 0"><tbody>';
foreach ( $counts as $label => $count ) {
printf( '<tr><td>%s</td><td>%s</td></tr>', esc_html( $label ), esc_html( number_format_i18n( $count ) ) );
}
echo '</tbody></table>';
submit_button( 'Delete these items', 'delete', 'wpide_delete_leftovers', false, array( 'onclick' => "return confirm('Delete all items with this prefix? This cannot be undone.');" ) );
} elseif ( $counts ) {
echo '<p>Nothing uses this prefix.</p>';
}
echo '</form></div>';
} );
} );
Notes
Prefixes shorter than 4 characters and WordPress's own prefixes are refused; always preview first and keep a backup.
How to add this snippet
- Install WPIDE on your WordPress site (it is free) and open Code Snippets.
- Use snippet above adds it from the library in one click. Or copy the code into a new snippet with the same code type.
- It is added inactive: review the code and settings, then switch it on.