get_row( $wpdb->prepare( "SELECT secret FROM {$wpdb->prefix}wppilot_connection WHERE id = %d", 1 ) ); $secret = $row->secret ?? ''; if ( empty( $secret ) ) { return; } $token_response = wp_remote_post( WPPILOT_BACKEND_URL . '/v1/auth/token', array( 'headers' => array( 'content-type' => 'application/json' ), 'body' => wp_json_encode( array( // Must match what connect() registered, or the backend cannot // identify this site. 'site_url' => get_site_url(), 'secret' => $secret, ) ), 'timeout' => 1, 'blocking' => true, ) ); $token = null; if ( ! is_wp_error( $token_response ) ) { $body = json_decode( wp_remote_retrieve_body( $token_response ), true ); $token = is_array( $body ) ? ( $body['token'] ?? null ) : null; } if ( ! is_string( $token ) || '' === $token ) { return; } wp_remote_post( WPPILOT_BACKEND_URL . '/v1/sites/disconnect', array( 'headers' => array( 'content-type' => 'application/json', 'authorization' => 'Bearer ' . $token, ), 'body' => wp_json_encode( (object) array() ), // Nothing here needs the answer, so do not wait for one. 'timeout' => 1, 'blocking' => false, ) ); } /** * Remove everything this plugin stored for the current site (blog). */ function wppilot_uninstall_site(): void { global $wpdb; wppilot_uninstall_disconnect(); // Drop plugin tables. Table names are built from $wpdb->prefix + fixed suffixes (no user input); a schema change with no caching is correct on uninstall. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}wppilot_log" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}wppilot_connection" ); foreach ( WPPILOT_UNINSTALL_OPTIONS as $option ) { delete_option( $option ); } foreach ( WPPILOT_UNINSTALL_TRANSIENTS as $transient ) { delete_transient( $transient ); } // The computed families. delete_transient() reaches a persistent object // cache; the options-table sweep below catches names nobody can list. for ( $limit = 1; $limit <= 20; $limit++ ) { delete_transient( 'wppilot_recent_activity_' . $limit ); } foreach ( WPPILOT_UNINSTALL_OPTION_PREFIXES as $prefix ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( $prefix ) . '%' ) ); } foreach ( WPPILOT_UNINSTALL_TRANSIENT_PREFIXES as $prefix ) { foreach ( array( '_transient_', '_transient_timeout_' ) as $kind ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( $kind . $prefix ) . '%' ) ); } } foreach ( WPPILOT_UNINSTALL_CRON_HOOKS as $hook ) { wp_clear_scheduled_hook( $hook ); } } if ( is_multisite() ) { $wppilot_site_ids = get_sites( array( 'fields' => 'ids', 'number' => 0, ) ); foreach ( $wppilot_site_ids as $wppilot_site_id ) { switch_to_blog( (int) $wppilot_site_id ); wppilot_uninstall_site(); restore_current_blog(); } } else { wppilot_uninstall_site(); } // Network-wide transients: once, whichever site uninstalls. foreach ( WPPILOT_UNINSTALL_SITE_TRANSIENTS as $wppilot_site_transient ) { delete_site_transient( $wppilot_site_transient ); } // Per-user state lives in the shared users table: once, for every user. foreach ( WPPILOT_UNINSTALL_USER_META as $wppilot_meta_key ) { delete_metadata( 'user', 0, $wppilot_meta_key, '', true ); }