0 ? ':' . $current_port : '' ); } } // Explicit plugin deletion is also explicit local revocation. Attempt the // server-side revoke before deleting the only credential; the local copy is // removed regardless of network availability so this WordPress instance // always fails closed. Site/account deletion also cascades server-side. if ( is_array( $state ) && 'active' === ( isset( $state['status'] ) ? $state['status'] : '' ) && is_string( $secret ) && '' !== $secret && is_string( $install ) && '' !== $install && isset( $state['site_id'], $state['origin'] ) && '' !== $current_origin && hash_equals( (string) $state['origin'], $current_origin ) ) { $path = '/api/wp/installation/disconnect'; $body = '{}'; $timestamp = (string) time(); $nonce = wp_generate_password( 32, false, false ); $canonical = implode( "\n", array( 'figgle.wp.v1', 'POST', $path, $timestamp, $nonce, hash( 'sha256', $body ) ) ); $api_base = isset( $state['api_base'] ) && is_string( $state['api_base'] ) ? $state['api_base'] : 'https://api.figgle.de'; wp_remote_post( trailingslashit( $api_base ) . ltrim( $path, '/' ), array( 'timeout' => 8, 'headers' => array( 'Content-Type' => 'application/json', 'X-Figgle-Signature-Version' => '1', 'X-Figgle-Installation' => $install, 'X-Figgle-Site' => (string) $state['site_id'], 'X-Figgle-Origin' => (string) $state['origin'], 'X-Figgle-Timestamp' => $timestamp, 'X-Figgle-Nonce' => $nonce, 'X-Figgle-Signature' => hash_hmac( 'sha256', $canonical, $secret ), ), 'body' => $body, ) ); } global $wpdb; delete_option( 'figgle_options' ); delete_option( 'figgle_connection_state' ); delete_option( 'figgle_connection_credential' ); delete_option( 'figgle_installation_id' ); // v0.5 — dauerhafte Options der Shop-Module. Ohne sie bleiben nach dem // Loeschen des Plugins Bestell-Protokoll und Selbsttest-Ergebnis in der // Datenbank und damit in jedem Backup stehen. delete_option( 'figgle_woo_log' ); delete_option( 'figgle_plp_selftest' ); delete_option( 'figgle_plp_test_present' ); delete_option( 'figgle_woo_cron_health' ); delete_option( 'figgle_woo_pending' ); delete_transient( 'figgle_connection_pending' ); delete_transient( 'figgle_connection_origin_proof' ); delete_transient( 'figgle_connection_error' ); delete_transient( 'figgle_show_welcome' ); delete_transient( 'figgle_compat_warnings' ); delete_transient( 'figgle_verify_result' ); delete_transient( 'figgle_woo_test_result' ); delete_transient( 'figgle_plp_selftest_nonce' ); delete_transient( 'figgle_plp_selftest_result' ); delete_transient( 'figgle_site_id_locked' ); delete_transient( 'figgle_site_id_inert' ); // v0.7.2 — Sperr-/Zaehlerzeilen von Figgle_Atomic (autoload=no, laufen // von selbst ab; hier trotzdem restlos weg). Kein Plugin-Code geladen, // deshalb direkt ueber $wpdb; die Namen sind feste Prefixe. if ( isset( $wpdb ) && is_object( $wpdb ) && method_exists( $wpdb, 'esc_like' ) ) { foreach ( array( 'figgle_lock_', 'figgle_ctr_' ) as $figgle_atomic_prefix ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Tabellenname aus $wpdb->prefix, Prefix via esc_like + prepare(); Uninstall-Request ohne Object-Cache. $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'options WHERE option_name LIKE %s', $wpdb->esc_like( $figgle_atomic_prefix ) . '%' ) ); } } // v0.4 — Sofort-Modus-Artefakte. delete_transient( 'figgle_show_inline_mode_notice' ); if ( '' !== $site_id ) { delete_transient( 'figgle_inline_cfg_' . md5( $site_id ) ); delete_transient( 'figgle_inline_cfg_fail_' . md5( $site_id ) ); delete_transient( 'figgle_goals_cache_' . md5( $site_id ) ); } // Bestell-Metadaten des WooCommerce-Adapters. Darunter `_figgle_user_hash`, // die Besucher-Kennung — sie bleibt sonst in jeder Bestellung und in jedem // Backup stehen, obwohl das Plugin geloescht wurde. // // Zwei Speicherorte, weil WooCommerce zwei kennt: klassisch als Postmeta, // und mit HPOS in der eigenen Bestell-Metatabelle. Beide werden abgeraeumt. // Bewusst ohne WooCommerce-Klassen: uninstall.php laeuft in einem eigenen // Request ohne Plugin-Code, `$wpdb` ist das einzig Verfuegbare. $figgle_order_meta_keys = array( '_figgle_sent', '_figgle_user_hash', '_figgle_queued', '_figgle_queued_at', '_figgle_refunds_sent', '_figgle_cancellation_sent', ); foreach ( $figgle_order_meta_keys as $figgle_meta_key ) { if ( function_exists( 'delete_post_meta_by_key' ) ) { delete_post_meta_by_key( $figgle_meta_key ); } } global $wpdb; if ( isset( $wpdb ) && is_object( $wpdb ) ) { $figgle_hpos_table = $wpdb->prefix . 'wc_orders_meta'; // Ohne HPOS existiert die Tabelle nicht — ein DELETE darauf waere ein // DB-Fehler im Loeschvorgang, den der Betreiber als roten Bildschirm // sieht. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Tabellen-Existenzpruefung; im Uninstall-Request gibt es keinen Objekt-Cache. $figgle_hpos_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $figgle_hpos_table ) ); if ( $figgle_hpos_exists === $figgle_hpos_table ) { $figgle_placeholders = implode( ', ', array_fill( 0, count( $figgle_order_meta_keys ), '%s' ) ); // Tabellenname stammt aus $wpdb->prefix, die Platzhalter-Kette aus // array_fill — beides kommt nie von aussen; die Metaschluessel // selbst gehen als %s durch prepare(). // Direkt in query() hinein, ohne Zwischenvariable: der Plugin-Check-Sniff // UnescapedDBParameter verfolgt eine Zuweisung nicht bis zum prepare() // zurueck und meldete den Aufruf sonst als ungeprueft. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Tabellenname stammt aus $wpdb->prefix, die Platzhalter-Kette aus array_fill; die Metaschluessel gehen als %s durch prepare(). Ein Objekt-Cache existiert im Uninstall-Request nicht. $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Tabellenname aus $wpdb->prefix, Platzhalter-Kette aus array_fill; die Werte gehen als %s durch prepare(). "DELETE FROM `{$figgle_hpos_table}` WHERE meta_key IN ( {$figgle_placeholders} )", $figgle_order_meta_keys ) ); } } // Textkopie von figgle_all_cron_hooks() aus // includes/class-figgle-connection.php: WordPress laedt uninstall.php in // einem eigenen Request ohne jeden Plugin-Code, ein Aufruf des Helpers ist // hier also nicht moeglich. Diese Liste muss jeden `wp_schedule_*`-Aufruf // in den includes/-Dateien enthalten, damit beide Listen nicht dieselbe // Luecke tragen und die Deinstallation nichts stehen laesst. $figgle_cron_hooks = array( 'figgle_refresh_inline_config', 'figgle_check_installation', 'figgle_woo_retry_conversion', 'figgle_woo_retry_refund', 'figgle_woo_retry_cancellation', 'figgle_woo_cron_heartbeat', 'figgle_plp_config_check', 'figgle_plp_selftest_refresh', ); if ( function_exists( 'wp_unschedule_hook' ) ) { foreach ( $figgle_cron_hooks as $figgle_cron_hook ) { wp_unschedule_hook( $figgle_cron_hook ); } } } // Multisite raeumt jeden Blog im Loop ab — inklusive des aktuellen. Der // Einzel-Aufruf gehoert deshalb in den else-Zweig, sonst laeuft der Cleanup // (und der signierte Widerruf-Request) fuer den aktuellen Blog doppelt. if ( is_multisite() ) { $figgle_blog_ids = get_sites( array( 'fields' => 'ids', 'number' => 0, ) ); foreach ( $figgle_blog_ids as $figgle_site_blog_id ) { switch_to_blog( $figgle_site_blog_id ); figgle_uninstall_cleanup_blog(); restore_current_blog(); } } else { figgle_uninstall_cleanup_blog(); }