get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", 'helpora_doc' ) ) ); if ( $ids ) { $revisions = array_map( 'intval', $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'revision' AND post_parent IN (" . implode( ',', array_fill( 0, count( $ids ), '%d' ) ) . ')', $ids ) ) ); $all = array_merge( $ids, $revisions ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ',', array_fill( 0, count( $all ), '%d' ) ) . ')', $all ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->term_relationships} WHERE object_id IN (" . implode( ',', array_fill( 0, count( $all ), '%d' ) ) . ')', $all ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->comments} WHERE comment_post_ID IN (" . implode( ',', array_fill( 0, count( $all ), '%d' ) ) . ')', $all ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->posts} WHERE ID IN (" . implode( ',', array_fill( 0, count( $all ), '%d' ) ) . ')', $all ) ); // Media stays, but no longer points at an article that is gone. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_parent = 0 WHERE post_type = 'attachment' AND post_parent IN (" . implode( ',', array_fill( 0, count( $ids ), '%d' ) ) . ')', $ids ) ); } // Any stray Helpora meta on other content. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", $wpdb->esc_like( '_helpora_' ) . '%' ) ); // Terms in the three taxonomies, with their meta and relationships. $terms = $wpdb->get_results( $wpdb->prepare( "SELECT term_taxonomy_id, term_id FROM {$wpdb->term_taxonomy} WHERE taxonomy IN (%s, %s, %s)", 'helpora_category', 'helpora_tag', 'helpora_kb' ) ); if ( $terms ) { $tt_ids = array_map( static fn( $t ) => (int) $t->term_taxonomy_id, $terms ); $term_ids = array_map( static fn( $t ) => (int) $t->term_id, $terms ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN (" . implode( ',', array_fill( 0, count( $tt_ids ), '%d' ) ) . ')', $tt_ids ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->term_taxonomy} WHERE term_taxonomy_id IN (" . implode( ',', array_fill( 0, count( $tt_ids ), '%d' ) ) . ')', $tt_ids ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->termmeta} WHERE term_id IN (" . implode( ',', array_fill( 0, count( $term_ids ), '%d' ) ) . ')', $term_ids ) ); // A term row can only go once no other taxonomy still uses it. $wpdb->query( $wpdb->prepare( "DELETE t FROM {$wpdb->terms} t LEFT JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = t.term_id WHERE t.term_id IN (" . implode( ',', array_fill( 0, count( $term_ids ), '%d' ) ) . ') AND tt.term_id IS NULL', $term_ids ) ); } // The custom tables. foreach ( array( 'helpora_index', 'helpora_events', 'helpora_stats_daily', 'helpora_search_terms' ) as $table ) { $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $wpdb->prefix . $table ) ); } // Options and transients (rate-limit counters, caches, locks). $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( 'helpora_' ) . '%', $wpdb->esc_like( '_transient_helpora_' ) . '%', $wpdb->esc_like( '_transient_timeout_helpora_' ) . '%' ) ); // phpcs:enable // Scheduled events. foreach ( array( 'helpora_rollup', 'helpora_index_batch' ) as $hook ) { wp_clear_scheduled_hook( $hook ); } // Capabilities granted to roles on activation — the same list activation // used, so the two cannot drift apart. if ( class_exists( \Helpora\Content\Capabilities::class ) ) { \Helpora\Content\Capabilities::remove_roles(); } // Rewrite rules are cached in one option; deleting it makes WordPress // rebuild them on the next request, without the docs routes. delete_option( 'rewrite_rules' ); // WordPress's own "recently active" record for the Plugins screen. $recent = get_option( 'recently_activated', array() ); if ( is_array( $recent ) && isset( $recent['helpora/helpora.php'] ) ) { unset( $recent['helpora/helpora.php'] ); update_option( 'recently_activated', $recent, false ); } wp_cache_flush_group( 'helpora' ); } /** * Remove network-wide user meta, once, after the sites. */ function helpora_uninstall_users(): void { delete_metadata( 'user', 0, 'helpora_dismissed_notices', '', true ); } if ( is_multisite() ) { $helpora_any = false; foreach ( get_sites( array( 'fields' => 'ids' ) ) as $helpora_site_id ) { switch_to_blog( (int) $helpora_site_id ); $helpora_settings = get_option( 'helpora_settings', array() ); $helpora_any = $helpora_any || ( is_array( $helpora_settings ) && ! empty( $helpora_settings['delete_data_on_uninstall'] ) ); helpora_uninstall_site(); restore_current_blog(); } if ( $helpora_any ) { helpora_uninstall_users(); } } else { $helpora_settings = get_option( 'helpora_settings', array() ); if ( is_array( $helpora_settings ) && ! empty( $helpora_settings['delete_data_on_uninstall'] ) ) { helpora_uninstall_users(); } helpora_uninstall_site(); }