isset( $stored['values'] ) && is_array( $stored['values'] ) ? $stored['values'] : array(), 'enforced' => isset( $stored['enforced'] ) && is_array( $stored['enforced'] ) ? $stored['enforced'] : array(), ); } /** * Effective "Delete data on uninstall" for the current site. * * @param array $network Network settings. * @return bool */ function streamsera_uninstall_delete_data( $network ) { if ( in_array( 'delete_data', $network['enforced'], true ) && array_key_exists( 'delete_data', $network['values'] ) ) { return ! empty( $network['values']['delete_data'] ); } $settings = get_option( 'streamsera_settings', array() ); return is_array( $settings ) && ! empty( $settings['delete_data'] ); } /** * Clean up the current site. * * @param bool $delete_data Also delete user content and settings. * @return void */ function streamsera_uninstall_site( $delete_data ) { global $wpdb; // --- Always: housekeeping. -------------------------------------------------------------------- foreach ( array( 'streamsera_version', 'streamsera_flush_rewrite', 'streamsera_np_queue', 'streamsera_activation_redirect' ) as $option ) { delete_option( $option ); } // Transients (with their timeouts), last-known now-playing snapshots and fetch timings (caches, rebuilt on demand). $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- uninstall clean-up. $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( '_transient_streamsera_' ) . '%', $wpdb->esc_like( '_transient_timeout_streamsera_' ) . '%', $wpdb->esc_like( 'streamsera_last_np_' ) . '%', $wpdb->esc_like( 'streamsera_np_stat_' ) . '%' ) ); // WP-Cron fallbacks. wp_clear_scheduled_hook( 'streamsera_poll_metadata' ); // Pending Action Scheduler jobs. The library is not loaded during uninstall, so work on its table directly. $as_table = $wpdb->prefix . 'actionscheduler_actions'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $as_table ) ) ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- uninstall clean-up. $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- uninstall clean-up. $wpdb->prepare( 'UPDATE %i SET status = %s WHERE hook LIKE %s AND status = %s', $as_table, 'canceled', $wpdb->esc_like( 'streamsera_' ) . '%', 'pending' ) ); } // --- Only when "Delete data on uninstall" is on. --------------------------------------------- if ( $delete_data ) { $post_types = array( 'streamsera_station', 'streamsera_player' ); // Batches keep memory flat on large sites; force-delete bypasses the trash. do { $ids = get_posts( array( 'post_type' => $post_types, // 'any' skips trashed and auto-draft posts, so list every status explicitly. 'post_status' => array_merge( array_keys( get_post_stati() ), array( 'trash', 'auto-draft', 'inherit' ) ), 'numberposts' => 200, 'fields' => 'ids', 'orderby' => 'ID', 'order' => 'ASC', ) ); foreach ( $ids as $post_id ) { wp_delete_post( (int) $post_id, true ); } } while ( count( $ids ) === 200 ); // Orphaned `_streamsera_*` meta only (rows whose post no longer exists). Meta on other post types is // left alone: add-ons (e.g. shows and playlists) clean up their own content when they uninstall. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s AND post_id NOT IN ( SELECT ID FROM {$wpdb->posts} )", $wpdb->esc_like( '_streamsera_' ) . '%' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.SlowDBQuery -- uninstall clean-up. // Widget instances, style presets (user content), settings. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( 'widget_streamsera_' ) . '%' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- uninstall clean-up. delete_option( 'streamsera_style_presets' ); delete_option( 'streamsera_settings' ); } // Rows were removed with direct queries: drop this request's cached copy of autoloaded options. wp_cache_delete( 'alloptions', 'options' ); } $streamsera_network = streamsera_uninstall_network_settings(); if ( is_multisite() ) { $streamsera_main_delete = false; foreach ( get_sites( array( 'fields' => 'ids', 'number' => 0, ) ) as $streamsera_site_id ) { switch_to_blog( (int) $streamsera_site_id ); $streamsera_site_delete = streamsera_uninstall_delete_data( $streamsera_network ); if ( (int) $streamsera_site_id === (int) get_main_site_id() ) { $streamsera_main_delete = $streamsera_site_delete; } streamsera_uninstall_site( $streamsera_site_delete ); restore_current_blog(); } // Dismissed admin notices (user meta is network-wide). delete_metadata( 'user', 0, 'streamsera_dismissed_notices', '', true ); if ( $streamsera_main_delete ) { delete_site_option( 'streamsera_network_settings' ); } } else { streamsera_uninstall_site( streamsera_uninstall_delete_data( $streamsera_network ) ); delete_metadata( 'user', 0, 'streamsera_dismissed_notices', '', true ); }