query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('relaybell_') . '%')); // Transients kept in the options table (relaybell_sub_count, // relaybell_cache_probe, relaybell_pid_fixed, relaybell_identity_checked, // relaybell_connect_state_, relaybell_connect_error_) and their // expiry rows. $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like('_transient_relaybell_') . '%', $wpdb->esc_like('_transient_timeout_relaybell_') . '%' )); // Post meta (e.g. _relaybell_last_stock_qty, _relaybell_last_price, …). $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", $wpdb->esc_like('_relaybell_') . '%')); // User meta (e.g. _relaybell_signup_sent). $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", $wpdb->esc_like('_relaybell_') . '%')); // HPOS order meta (custom order tables), when present. The table name is // built from $wpdb->prefix and verified to exist before use — it never // contains user input, and a table name cannot be bound with prepare(). $orders_meta = $wpdb->prefix . 'wc_orders_meta'; $found = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $orders_meta)); if ($found === $orders_meta) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name derived from $wpdb->prefix, validated above; not bindable via prepare(). $wpdb->query($wpdb->prepare("DELETE FROM {$orders_meta} WHERE meta_key LIKE %s", $wpdb->esc_like('_relaybell_') . '%')); } // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching // The option rows are gone above, but drop the cached copies too (with a // persistent object cache, transients live only there). delete_transient('relaybell_sub_count'); delete_transient('relaybell_cache_probe'); delete_transient('relaybell_pid_fixed'); delete_transient('relaybell_identity_checked'); wp_clear_scheduled_hook('relaybell_identity_daily'); // Background catalog sync / past-orders import (catalog.php). wp_clear_scheduled_hook('relaybell_sync_run'); wp_cache_flush(); } if (is_multisite()) { // Every site in the network, 100 at a time: get_sites() returns only the first // 100 unless asked for more, and the rest would keep the API key and settings. $relaybell_offset = 0; do { $relaybell_site_ids = get_sites(array( 'fields' => 'ids', 'number' => 100, 'offset' => $relaybell_offset, 'orderby' => 'id', 'order' => 'ASC', 'no_found_rows' => true, )); foreach ($relaybell_site_ids as $relaybell_site_id) { switch_to_blog($relaybell_site_id); relaybell_uninstall_cleanup(); restore_current_blog(); } $relaybell_offset += 100; } while (count($relaybell_site_ids) === 100); } else { relaybell_uninstall_cleanup(); }