get_sites_list( $filters['site_id'] ); /* ────────────────────────────────────────────────────────────── * 1. Collect post types per site from the database * ─────────────────────────────────────────────────────────────*/ $post_types_list = []; foreach ( $sites_list as $site ) { $table_name = $site['prefix'] . 'posts'; $public_post_types_names = self::get_public_post_types_names_for_site( (int) $site['id'] ); $results = $wpdb->get_results( "SELECT post_type, COUNT(*) AS posts_count FROM {$table_name} WHERE post_type != '' GROUP BY post_type", OBJECT ); if ( ! empty( $results ) ) { foreach ( $results as $row ) { $post_types_list[] = (object) [ 'name' => (string) $row->post_type, 'posts_count' => (int) $row->posts_count, 'site_id' => $site['id'], 'is_public' => isset( $public_post_types_names[ $row->post_type ] ), ]; } } } /* ────────────────────────────────────────────────────────────── * 2. Load scan / hardcoded categorization onto items * ─────────────────────────────────────────────────────────────*/ if ( ADBC_VERSION_TYPE === 'PREMIUM' ) ADBC_Scan_Results::instance()->load_scan_results_to_items_rows( $post_types_list, 'post_types' ); else ADBC_Common_Model::load_scan_results_to_items_for_free_version( $post_types_list ); ADBC_Hardcoded_Items::instance()->load_hardcoded_scan_results_to_items_rows( $post_types_list, 'post_types' ); /* ────────────────────────────────────────────────────────────── * 3. Apply filters (belongs_to, posts_count, search) and count categories * ─────────────────────────────────────────────────────────────*/ $min_posts_count = $filters['post_types_posts_count']; $search_for = strtolower( $filters['search_for'] ); $post_types_public = $filters['post_types_visibility']; $scan_counter = new ADBC_Scan_Counter(); $filtered_post_types_list = []; foreach ( $post_types_list as $post_type ) { // Filter by posts count if ( $min_posts_count > 0 && $post_type->posts_count <= $min_posts_count ) continue; // Public / non-public filter if ( $post_types_public === 'public' && ! $post_type->is_public ) continue; if ( $post_types_public === 'non_public' && $post_type->is_public ) continue; // Search filter if ( $search_for !== '' && strpos( strtolower( $post_type->name ), $search_for ) === false ) continue; $scan_counter->refresh_categorization_count( $post_type->belongs_to ); if ( ! ADBC_Common_Model::is_item_satisfies_belongs_to( $filters, $post_type->belongs_to ) ) continue; $filtered_post_types_list[] = $post_type; } /* ────────────────────────────────────────────────────────────── * 4. Sorting * ─────────────────────────────────────────────────────────────*/ $sort_by = $filters['sort_by']; $sort_order = $filters['sort_order']; $allowed_columns = [ 'name', 'posts_count', 'site_id', 'is_public', ]; if ( in_array( $sort_by, $allowed_columns, true ) ) { usort( $filtered_post_types_list, function ($a, $b) use ($sort_by, $sort_order) { if ( $sort_by === 'posts_count' ) { $cmp = $a->posts_count <=> $b->posts_count; } elseif ( $sort_by === 'site_id' ) { $cmp = $a->site_id <=> $b->site_id; } elseif ( $sort_by === 'is_public' ) { $cmp = (int) $a->is_public <=> (int) $b->is_public; } else { $cmp = strnatcasecmp( (string) $a->name, (string) $b->name ); } return ( $sort_order === 'DESC' ) ? -$cmp : $cmp; } ); } /* ────────────────────────────────────────────────────────────── * 5. Pagination * ─────────────────────────────────────────────────────────────*/ $total_post_types = count( $filtered_post_types_list ); $startRecord = ( $filters['current_page'] - 1 ) * $filters['items_per_page']; $paginated_post_types_list = array_slice( $filtered_post_types_list, $startRecord, $filters['items_per_page'] ); $post_types_list = []; foreach ( $paginated_post_types_list as $post_type ) { $post_types_list[] = [ 'composite_id' => [ 'items_type' => 'post_types', 'site_id' => (int) $post_type->site_id, 'name' => $post_type->name, ], 'name' => $post_type->name, 'posts_count' => $post_type->posts_count, 'site_id' => $post_type->site_id, 'is_public' => (bool) $post_type->is_public, 'belongs_to' => $post_type->belongs_to, 'known_plugins' => $post_type->known_plugins, 'known_themes' => $post_type->known_themes, ]; } // Dictionary names if ( ADBC_VERSION_TYPE === 'PREMIUM' ) ADBC_Dictionary::add_missing_addons_names_from_dictionary( $post_types_list, $scan_counter, 'post_types' ); $total_real_pages = max( 1, ceil( $total_post_types / $filters['items_per_page'] ) ); return ADBC_Rest::success( "", [ 'items' => $post_types_list, 'total_items' => $total_post_types, 'real_current_page' => min( $filters['current_page'], $total_real_pages ), 'categorization_count' => $scan_counter->get_categorization_count(), 'plugins_count' => $scan_counter->get_plugins_count(), 'themes_count' => $scan_counter->get_themes_count(), ] ); } /** * Get all distinct post type names for a site. * * * @param string $site_prefix Site table prefix (e.g. wp_ or wp_2_). * * @return array Flat list of post type names. */ public static function get_all_post_type_names_for_site( $site_prefix ) { global $wpdb; $table = $site_prefix . 'posts'; $names = $wpdb->get_col( "SELECT DISTINCT post_type FROM `{$table}` WHERE post_type != ''" ); return is_array( $names ) ? $names : []; } /** * Get unique post type names across all sites. * * @return array Associative array of post type slugs. */ public static function get_post_types_names() { global $wpdb; $all_post_types = []; $sites = ADBC_Sites::instance()->get_sites_list(); foreach ( $sites as $site ) { $table_name = $site['prefix'] . 'posts'; $post_types = $wpdb->get_col( "SELECT DISTINCT post_type FROM {$table_name} WHERE post_type != ''" ); $all_post_types = array_merge( $all_post_types, $post_types ); } return array_fill_keys( $all_post_types, true ); } /** * Count the total number of post types that are not scanned. * * @return int Total not-scanned post types. */ public static function count_total_not_scanned_post_types() { $total_not_scanned = 0; if ( ADBC_VERSION_TYPE === 'PREMIUM' ) $registered_post_types_dictionary = array_keys( ADBC_Registered_Post_Types_Dict_Tracker::load_dictionary_from_file() ); $sites_list = ADBC_Sites::instance()->get_sites_list(); foreach ( $sites_list as $site ) { $post_types = self::get_all_post_type_names_for_site( $site['prefix'] ); if ( ADBC_VERSION_TYPE === 'PREMIUM' ) { $post_types = array_diff( $post_types, $registered_post_types_dictionary ); // don't count the registered post types in dictionary as not scanned $not_scanned_count = ADBC_Scan_Utils::count_not_scanned_items_in_list( 'post_types', $post_types ); } else { $not_scanned_count = ADBC_Common_Model::count_not_scanned_items_in_list_for_free( 'post_types', $post_types ); } $total_not_scanned += $not_scanned_count; } return $total_not_scanned; } /** * Count the total number of non-public post types with a large number of posts. * * @return int Total count of non-public post types above the threshold. */ public static function count_total_large_non_public_post_types() { global $wpdb; $total = 0; $threshold = self::NON_PUBLIC_POST_TYPES_POST_COUNT_THRESHOLD; $sites_list = ADBC_Sites::instance()->get_sites_list(); foreach ( $sites_list as $site ) { $table_name = $site['prefix'] . 'posts'; $public_post_types_names = self::get_public_post_types_names_for_site( (int) $site['id'] ); $results = $wpdb->get_results( $wpdb->prepare( "SELECT post_type, COUNT(*) AS posts_count FROM {$table_name} WHERE post_type != '' GROUP BY post_type HAVING COUNT(*) >= %d", $threshold ), OBJECT ); if ( ! empty( $results ) ) { foreach ( $results as $row ) { if ( ! isset( $public_post_types_names[ $row->post_type ] ) ) { $total++; } } } } return $total; } /** * Get total post types count. * * @return int Sum of unique post types per site across the network. */ public static function get_total_post_types_count() { global $wpdb; $total_post_types = 0; $sites_list = ADBC_Sites::instance()->get_sites_list(); foreach ( $sites_list as $site ) { $table_name = $site['prefix'] . 'posts'; $site_unique_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT post_type) FROM {$table_name}" ); $total_post_types += $site_unique_count; } return $total_post_types; } /** * Get a paginated list of posts for a given post type in a given site. * * @param int $site_id The site ID. * @param string $post_type The post type name. * @param array $filters Sanitized filters (current_page, items_per_page, sort_by, sort_order). * * @return array Paginated result with items, total_items, and real_current_page. */ public static function get_posts_rows( $site_id, $post_type, $filters ) { global $wpdb; // All filters are already validated, casted and defaulted at the endpoint level. $current_page = $filters['current_page']; $items_per_page = $filters['items_per_page']; $sort_by = $filters['sort_by']; $sort_order = $filters['sort_order']; $prefix = ADBC_Sites::instance()->get_prefix_from_site_id( $site_id ); $table_name = $prefix . 'posts'; // Validate sort_by against real columns of the table. If invalid, do not sort. $allowed_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$table_name}", 0 ); $order_by_sql = ''; if ( in_array( $sort_by, $allowed_columns, true ) ) { $order_by_sql = "ORDER BY `{$sort_by}` {$sort_order}"; } // Total posts for this post type in this site. $total_items = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE post_type = %s", $post_type ) ); $offset = ( $current_page - 1 ) * $items_per_page; // Fetch the requested page of rows. $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table_name} WHERE post_type = %s {$order_by_sql} LIMIT %d OFFSET %d", $post_type, $items_per_page, $offset ), ARRAY_A ); $total_real_pages = $items_per_page > 0 ? max( 1, (int) ceil( $total_items / $items_per_page ) ) : 1; return [ 'items' => is_array( $rows ) ? $rows : [], 'total_items' => $total_items, 'real_current_page' => min( $current_page, $total_real_pages ), ]; } /** * Delete all posts belonging to the selected post types, grouped by site ID. * * Expected input shape: * [ * 1 => [ [ 'name' => 'my_cpt' ], ... ], * 2 => [ [ 'name' => 'another_cpt' ], ... ], * ] * * @param array $grouped_selected Post types grouped by site ID. * @return array Post type names that could not be fully purged. */ public static function delete_posts( $grouped_selected ) { $cleanup_method = ADBC_Settings::instance()->get_setting( 'sql_or_native_cleanup_method' ); if ( $cleanup_method === 'native' ) return self::delete_posts_native( $grouped_selected ); return self::delete_posts_sql( $grouped_selected ); } /** * Delete posts using WordPress native wp_delete_post(). * * @param array $grouped_selected Post types grouped by site ID. * @return array Post type names that could not be fully purged. */ protected static function delete_posts_native( $grouped_selected ) { global $wpdb; $not_processed = []; foreach ( $grouped_selected as $site_id => $group ) { ADBC_Sites::instance()->switch_to_blog_id( $site_id ); foreach ( $group as $selected ) { $post_type = $selected['name']; $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", $post_type ) ); if ( empty( $post_ids ) ) continue; $has_failure = false; foreach ( $post_ids as $post_id ) { $result = wp_delete_post( (int) $post_id, true ); if ( ! $result ) $has_failure = true; } if ( $has_failure ) { $remaining = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s", $post_type ) ); if ( $remaining > 0 ) $not_processed[] = $post_type; } } ADBC_Sites::instance()->restore_blog(); } return $not_processed; } /** * Delete posts using direct SQL for each post type per site. * * @param array $grouped_selected Post types grouped by site ID. * @return array Post type names that could not be fully purged. */ protected static function delete_posts_sql( $grouped_selected ) { global $wpdb; $not_processed = []; foreach ( $grouped_selected as $site_id => $group ) { $prefix = ADBC_Sites::instance()->get_prefix_from_site_id( $site_id ); $table_name = $prefix . 'posts'; foreach ( $group as $selected ) { $post_type = $selected['name']; $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE post_type = %s", $post_type ) ); $remaining = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE post_type = %s", $post_type ) ); if ( $remaining > 0 ) $not_processed[] = $post_type; } } return $not_processed; } /** * Get public post types names for a given site. * * @param int $site_id The site ID. * @return array Associative array of public post types names as keys and true as values. */ protected static function get_public_post_types_names_for_site( $site_id ) { // Cache the public post types names for each site to avoid multiple calls to the database if the site public post types names were already fetched. static $cache = []; if ( isset( $cache[ $site_id ] ) ) { return $cache[ $site_id ]; } $sites = ADBC_Sites::instance(); $sites->switch_to_blog_id( $site_id ); $public_post_types = get_post_types( [ 'public' => true, ], 'names' ); $sites->restore_blog(); $cache[ $site_id ] = is_array( $public_post_types ) ? array_fill_keys( $public_post_types, true ) : []; return $cache[ $site_id ]; } }