$nbh_performance_uninstall_marker ) { if ( is_link( $nbh_performance_uninstall_dropin ) || ! is_file( $nbh_performance_uninstall_dropin ) || ! is_readable( $nbh_performance_uninstall_dropin ) ) { continue; } $nbh_performance_uninstall_head = \NBH\Performance\Modules\Core\Filesystem::get_contents( $nbh_performance_uninstall_dropin ); if ( is_string( $nbh_performance_uninstall_head ) && false !== strpos( substr( $nbh_performance_uninstall_head, 0, 4096 ), $nbh_performance_uninstall_marker ) ) { wp_delete_file( $nbh_performance_uninstall_dropin ); } } if ( ! defined( 'NBH_PERFORMANCE_ALLOW_UNINSTALL_DATA_PURGE' ) || true !== NBH_PERFORMANCE_ALLOW_UNINSTALL_DATA_PURGE ) { return; } // Capture policy before deleting any option. These values must survive the // settings cleanup so the administrator's choices are respected. $nbh_performance_uninstall_policy = (string) get_option( 'nbh_performance_uninstall_data_policy', 'retain' ); $nbh_performance_uninstall_remove_cache = (bool) get_option( 'nbh_performance_uninstall_remove_cache', true ); $nbh_performance_uninstall_remove_dropins = (bool) get_option( 'nbh_performance_uninstall_remove_dropin', true ); if ( ! in_array( $nbh_performance_uninstall_policy, array( 'retain', 'delete_settings', 'delete_all' ), true ) ) { $nbh_performance_uninstall_policy = 'retain'; } if ( 'retain' === $nbh_performance_uninstall_policy ) { return; } require_once __DIR__ . '/includes/autoload.php'; use NBH\Performance\Modules\Core\DataOwnershipRegistry; use NBH\Performance\Modules\ImageOptimization\WebpAvifConverter; /** * Remove a directory only when it exactly matches an approved NBH-owned root. * * @param string $directory Directory path. * @param array $allowed Approved roots. * @return void */ function nbh_performance_uninstall_remove_directory( $nbh_performance_directory, array $nbh_performance_allowed ) { $nbh_performance_path = untrailingslashit( wp_normalize_path( $nbh_performance_directory ) ); $nbh_performance_roots = array_map( static function ( $nbh_performance_root ) { return untrailingslashit( wp_normalize_path( $nbh_performance_root ) ); }, $nbh_performance_allowed ); if ( ! in_array( $nbh_performance_path, $nbh_performance_roots, true ) || is_link( $nbh_performance_path ) || ! is_dir( $nbh_performance_path ) ) { return; } $nbh_performance_items = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $nbh_performance_path, FilesystemIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ); foreach ( $nbh_performance_items as $nbh_performance_item ) { $nbh_performance_item_path = wp_normalize_path( $nbh_performance_item->getPathname() ); if ( 0 !== strpos( trailingslashit( $nbh_performance_item_path ), trailingslashit( $nbh_performance_path ) ) ) { continue; } if ( $nbh_performance_item->isLink() ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.unlink_unlink @\NBH\Performance\Modules\Core\Filesystem::unlink( $nbh_performance_item_path ); } elseif ( $nbh_performance_item->isDir() ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @\NBH\Performance\Modules\Core\Filesystem::rmdir( $nbh_performance_item_path ); } else { wp_delete_file( $nbh_performance_item_path ); } } // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @\NBH\Performance\Modules\Core\Filesystem::rmdir( $nbh_performance_path ); } /** Remove generated image variants before deleting plugin metadata; originals are untouched. */ function nbh_performance_uninstall_restore_images() { global $wpdb; $nbh_performance_meta_keys = array( '_nbh_performance_image_variants_v2' ); $nbh_performance_placeholders = implode( ',', array_fill( 0, count( $nbh_performance_meta_keys ), '%s' ) ); $nbh_performance_sql = "SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE meta_key IN ({$nbh_performance_placeholders})"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared $nbh_performance_attachment_ids = $wpdb->get_col( $wpdb->prepare( $nbh_performance_sql, $nbh_performance_meta_keys ) ); $nbh_performance_variants = new WebpAvifConverter(); foreach ( array_map( 'absint', (array) $nbh_performance_attachment_ids ) as $nbh_performance_attachment_id ) { $nbh_performance_variants->delete_attachment_variants( $nbh_performance_attachment_id ); } } /** Delete NBH-owned database data for the currently selected site. */ function nbh_performance_uninstall_clean_current_site( $nbh_performance_delete_all ) { global $wpdb; foreach ( DataOwnershipRegistry::cron_hooks() as $nbh_performance_hook ) { wp_clear_scheduled_hook( $nbh_performance_hook ); } if ( $nbh_performance_delete_all ) { nbh_performance_uninstall_restore_images(); foreach ( DataOwnershipRegistry::table_suffixes() as $nbh_performance_suffix ) { $nbh_performance_table = $wpdb->prefix . $nbh_performance_suffix; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder -- Explicitly opted-in uninstall removes only registry-allowlisted owned tables; this mutation has no cacheable result. $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS `%1$s`', nbh_performance_validated_table_name( $nbh_performance_table ) ) ); } $nbh_performance_meta_like = $wpdb->esc_like( DataOwnershipRegistry::postmeta_prefix() ) . '%'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder -- Explicit delete-all uninstall removes owned metadata by prefix; no persistent query result is cached, and the WordPress 6.0-compatible table identifier is strictly validated. $wpdb->query( $wpdb->prepare( 'DELETE FROM `%2$s` WHERE meta_key LIKE %s', $nbh_performance_meta_like, nbh_performance_validated_table_name( $wpdb->postmeta ) ) ); } $nbh_performance_option_like = $wpdb->esc_like( DataOwnershipRegistry::option_prefix() ) . '%'; $nbh_performance_transient_like = $wpdb->esc_like( '_transient_' . DataOwnershipRegistry::option_prefix() ) . '%'; $nbh_performance_timeout_like = $wpdb->esc_like( '_transient_timeout_' . DataOwnershipRegistry::option_prefix() ) . '%'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s", $nbh_performance_option_like, $nbh_performance_transient_like, $nbh_performance_timeout_like ) ); } $nbh_performance_delete_all = 'delete_all' === $nbh_performance_uninstall_policy; if ( is_multisite() && function_exists( 'get_sites' ) ) { $nbh_performance_site_ids = get_sites( array( 'fields' => 'ids', 'number' => 0, ) ); foreach ( (array) $nbh_performance_site_ids as $nbh_performance_site_id ) { switch_to_blog( absint( $nbh_performance_site_id ) ); nbh_performance_uninstall_clean_current_site( $nbh_performance_delete_all ); restore_current_blog(); } } else { nbh_performance_uninstall_clean_current_site( $nbh_performance_delete_all ); } // Network options and site transients are stored in sitemeta. if ( is_multisite() ) { global $wpdb; $nbh_performance_site_option_like = $wpdb->esc_like( DataOwnershipRegistry::option_prefix() ) . '%'; $nbh_performance_site_transient_like = $wpdb->esc_like( '_site_transient_' . DataOwnershipRegistry::option_prefix() ) . '%'; $nbh_performance_site_timeout_like = $wpdb->esc_like( '_site_transient_timeout_' . DataOwnershipRegistry::option_prefix() ) . '%'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE %s OR meta_key LIKE %s OR meta_key LIKE %s", $nbh_performance_site_option_like, $nbh_performance_site_transient_like, $nbh_performance_site_timeout_like ) ); } if ( $nbh_performance_delete_all ) { $nbh_performance_owned_roots = DataOwnershipRegistry::shared_directories(); foreach ( $nbh_performance_owned_roots as $nbh_performance_root ) { $nbh_performance_is_cache = untrailingslashit( wp_normalize_path( $nbh_performance_root ) ) === untrailingslashit( wp_normalize_path( trailingslashit( WP_CONTENT_DIR ) . 'cache/nbh-performance' ) ); if ( ! $nbh_performance_is_cache || $nbh_performance_uninstall_remove_cache ) { nbh_performance_uninstall_remove_directory( $nbh_performance_root, $nbh_performance_owned_roots ); } } if ( $nbh_performance_uninstall_remove_dropins ) { foreach ( DataOwnershipRegistry::dropins() as $nbh_performance_dropin => $nbh_performance_marker ) { if ( ! is_readable( $nbh_performance_dropin ) ) { continue; } $nbh_performance_head = \NBH\Performance\Modules\Core\Filesystem::get_contents( $nbh_performance_dropin ); $nbh_performance_head = is_string( $nbh_performance_head ) ? substr( $nbh_performance_head, 0, 4096 ) : false; if ( false !== $nbh_performance_head && false !== strpos( $nbh_performance_head, $nbh_performance_marker ) ) { wp_delete_file( $nbh_performance_dropin ); } } } }