'ids', 'number' => 0, 'update_site_meta_cache' => false, ) ); foreach ( $site_ids as $site_id ) { switch_to_blog( $site_id ); self::current_site(); restore_current_blog(); } } else { self::current_site(); } // 1.8.0 kept "Hide unused features" per user. 1.8.1 moved it into the settings option on // first boot, but an install that never booted 1.8.1 still has the meta. User meta is shared // by every site of a network, so the site this runs on decides; a kept value is still // migrated if the plugin comes back. if ( $sweep_users ) { delete_metadata( 'user', 0, 'adminkeep_hide_unused', '', true ); // Two-Factor Login's remembered devices, one list per user. Network-wide for the same // reason: user meta has no site. delete_metadata( 'user', 0, '_adminkeep_trusted_devices', '', true ); } } /** * Whether the current site asked for its data to go with the plugin. * * Read from the option directly rather than through PluginSettings: this runs with the plugin * not loaded, and a missing or malformed option must read as "keep". * * @return bool */ public static function deletes_data() { $settings = get_option( 'adminkeep_settings', array() ); return is_array( $settings ) && isset( $settings['_plugin'] ) && is_array( $settings['_plugin'] ) && ! empty( $settings['_plugin']['delete_data'] ); } /** * The current site: clear its jobs, and remove its data if it asked for that. * * @return void */ private static function current_site() { self::clear_jobs(); if ( self::deletes_data() ) { self::delete_data(); } } /** * Clear Adminkeep's scheduled jobs. The Live Draft cleanup is scheduled once per merged copy, * with the copy's id as its argument, so it is unscheduled by hook rather than cleared. * * @return void */ private static function clear_jobs() { wp_clear_scheduled_hook( 'adminkeep_email_log_purge' ); wp_clear_scheduled_hook( 'adminkeep_cleanup_replaced_media' ); wp_unschedule_hook( 'adminkeep_live_draft_cleanup' ); wp_clear_scheduled_hook( 'wp_pro_admin_cleanup_replaced_media' ); } /** * Remove everything this plugin owns on the current site. * * @return void */ private static function delete_data() { global $wpdb; delete_option( 'adminkeep_settings' ); delete_option( 'adminkeep_custom_css' ); delete_option( 'adminkeep_header_footer_code' ); delete_option( 'adminkeep_smtp' ); delete_option( 'adminkeep_smtp_last_error' ); delete_option( 'adminkeep_contact_form' ); delete_option( 'adminkeep_contact_form_last_error' ); $redirects = $wpdb->prefix . 'adminkeep_path_redirects'; // Plugin-owned table; the name is $wpdb->prefix plus a literal and DDL has no prepared form. $wpdb->query( "DROP TABLE IF EXISTS {$redirects}" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL, PluginCheck.Security.DirectDB.UnescapedDBParameter delete_option( 'adminkeep_redirect_schema' ); // The email log holds other people's addresses and, by default, what was written to them. $log = $wpdb->prefix . 'adminkeep_email_log'; $wpdb->query( "DROP TABLE IF EXISTS {$log}" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL, PluginCheck.Security.DirectDB.UnescapedDBParameter delete_option( 'adminkeep_email_log_schema' ); delete_option( 'adminkeep_email_log_import' ); $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_adminkeep_live_draft_of' ), array( '%s' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.SlowDBQuery // Set on a merged working copy for the ten minutes before its cleanup deletes it. $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_adminkeep_live_draft_merged' ), array( '%s' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.SlowDBQuery // Retained previous versions of replaced media. The files themselves are left on disk on // purpose: deleting user uploads is not this plugin's call to make. $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_adminkeep_replaced_file' ), array( '%s' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.SlowDBQuery self::delete_pending_logins(); // Two-Factor Login's recovery link: the hash of its token and the address it was sent to. // RecoveryLink::OPTION, spelled out for the same reason as the mutex prefix below. delete_option( 'adminkeep_2fa_recovery' ); // Names 1.1.0 stored under, for a site uninstalled before the rename in 1.2.0 ever ran. delete_option( 'wp_pro_admin_settings' ); delete_option( 'wp_pro_admin_redirect_schema' ); $old_redirects = $wpdb->prefix . 'wppa_path_redirects'; $wpdb->query( "DROP TABLE IF EXISTS {$old_redirects}" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_wppa_live_draft_of' ), array( '%s' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.SlowDBQuery $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_wppa_replaced_file' ), array( '%s' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.SlowDBQuery } /** * Two-Factor Login's transients: logins waiting on a code, each keyed by a random token, and * per user the send count and the wrong-code lock. All share the adminkeep_2fa_ prefix. Also * the per-user code-check mutex, a plain option that only a request dying mid-check would * leave behind. * * The tokens cannot be listed through the transients API, so the names are read from the * options table, value and timeout rows alike, and each is removed with delete_option(), which * also clears the copy the object cache holds for this request. A site with a persistent object * cache keeps these transients in the cache instead of the table; they expire on their own * there, most within ten minutes and a wrong-code lock within a day of its last write. * * @return void */ private static function delete_pending_logins() { global $wpdb; $names = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( '_transient_adminkeep_2fa_' ) . '%', $wpdb->esc_like( '_transient_timeout_adminkeep_2fa_' ) . '%', // Lockout::MUTEX_PREFIX, spelled out: uninstall.php loads this file alone, without the autoloader. $wpdb->esc_like( 'adminkeep_2fa_mutex_' ) . '%' ) ); foreach ( $names as $name ) { delete_option( $name ); } } }