*/ public function detect( int $limit = 500 ): array { global $wpdb; if ( ! isset( $wpdb ) ) { return array(); } $limit = max( 1, min( $limit, 5000 ) ); $table = TableNames::report_links(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- On-demand report query; caching is the caller's responsibility. $rows = $wpdb->get_results( $wpdb->prepare( "SELECT rl.*, p.post_status AS target_status, CASE WHEN rl.target_type = 'post' AND p.ID IS NULL THEN 'deleted' WHEN rl.target_type = 'post' AND p.post_status != 'publish' THEN 'not_published' WHEN rl.target_type = 'term' AND tt.term_id IS NULL THEN 'deleted' WHEN rl.target_type = 'unresolved' THEN 'unresolved' ELSE 'unknown' END AS rot_reason FROM %i AS rl LEFT JOIN {$wpdb->posts} AS p ON rl.target_type = 'post' AND rl.target_id = p.ID LEFT JOIN {$wpdb->term_taxonomy} AS tt ON rl.target_type = 'term' AND rl.target_id = tt.term_id WHERE rl.is_internal = 1 AND ( ( rl.target_type = 'post' AND ( p.ID IS NULL OR p.post_status != 'publish' ) ) OR ( rl.target_type = 'term' AND tt.term_id IS NULL ) OR rl.target_type = 'unresolved' ) ORDER BY rl.post_id ASC, rl.id ASC LIMIT %d", $table, $limit ) ); return is_array( $rows ) ? $rows : array(); } /** * Returns the count of internal-link rot rows without fetching them. * * Useful for dashboard widgets that only need a number. * * @since 1.0.0 * @since 1.7.0 Includes `target_type = 'unresolved'` rows in the count. * * @return int */ public function count(): int { global $wpdb; if ( ! isset( $wpdb ) ) { return 0; } $table = TableNames::report_links(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Count query; caching is the caller's responsibility. $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(rl.id) FROM %i AS rl LEFT JOIN {$wpdb->posts} AS p ON rl.target_type = 'post' AND rl.target_id = p.ID LEFT JOIN {$wpdb->term_taxonomy} AS tt ON rl.target_type = 'term' AND rl.target_id = tt.term_id WHERE rl.is_internal = 1 AND ( ( rl.target_type = 'post' AND ( p.ID IS NULL OR p.post_status != 'publish' ) ) OR ( rl.target_type = 'term' AND tt.term_id IS NULL ) OR rl.target_type = 'unresolved' )", $table ) ); return is_numeric( $count ) ? (int) $count : 0; } }