$item) { if ($item['url'] === $url_to_remove) { unset($frozen_map[$key]); $updated = true; $target_id = url_to_postid($url_to_remove); if ($target_id) { $count = (int) get_post_meta($target_id, '_ailm_inbound_count', true); update_post_meta($target_id, '_ailm_inbound_count', max(0, $count - 1)); } } } if ($updated) { update_post_meta($post_id, '_ailm_frozen_map', array_values($frozen_map)); global $wpdb; $table_name = AILM_Scout_DB::get_table_name(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->delete( $table_name, array( 'post_id' => $post_id, 'url' => $url_to_remove, 'link_origin' => 'ailm' ), array('%d', '%s', '%s') ); wp_send_json_success('Link removed from AILM map.'); } } wp_send_json_error('Link not found in map.'); } /** * Helper function for generating sorting links. * @param string $orderby_val The column to sort by. * @param string $current_orderby The current sorting column. * @param string $current_order The current sort order. * @return string The new sort URL. */ private function get_sort_link($orderby_val, $current_orderby, $current_order) { if ($orderby_val === $current_orderby) { $order = (strtolower($current_order) === 'desc') ? 'asc' : 'desc'; } else { $order = 'desc'; } return add_query_arg(['orderby' => $orderby_val, 'order' => $order]); } public function render_tab() { global $wpdb; $table_name = AILM_Scout_DB::get_table_name(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $per_page = isset( $_GET['per_page'] ) && in_array( $_GET['per_page'], [20, 50, 100] ) ? intval($_GET['per_page']) : 50; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $paged = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1; $offset = ( $paged - 1 ) * $per_page; // Sanitize and whitelist orderby parameter $allowed_orderby = ['internal_cnt', 'external_cnt', 'errors_count']; $orderby = 'errors_count'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $requested_orderby = sanitize_key( wp_unslash( $_GET['orderby'] ) ); if ( in_array( $requested_orderby, $allowed_orderby, true ) ) { $orderby = $requested_orderby; } } // Sanitize and whitelist order parameter $order = 'DESC'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $requested_order = strtoupper( sanitize_key( wp_unslash( $_GET['order'] ) ) ); if ( in_array( $requested_order, [ 'ASC', 'DESC' ], true ) ) { $order = $requested_order; } } $base_query = "SELECT post_id, COUNT(id) as total_links, SUM(CASE WHEN status_code >= 400 THEN 1 ELSE 0 END) as errors_count, SUM(CASE WHEN link_type = 'internal' THEN 1 ELSE 0 END) as internal_cnt, SUM(CASE WHEN link_type = 'external' THEN 1 ELSE 0 END) as external_cnt FROM {$table_name} GROUP BY post_id"; // Note: $orderby and $order are whitelisted above, so they are safe to use here. $order_clause = "ORDER BY {$orderby} {$order}, post_id DESC"; $sql = $base_query . ' ' . $order_clause . ' LIMIT %d OFFSET %d'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $total_posts_with_links = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT post_id) FROM {$table_name} WHERE 1=%d", 1 ) ); $total_pages = $total_posts_with_links ? ceil( $total_posts_with_links / $per_page ) : 0; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared $posts_stats = $wpdb->get_results( $wpdb->prepare( $sql, $per_page, $offset ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $total_errors = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$table_name} WHERE status_code >= %d", 400 ) ); ?>
Checks all internal and external links on your site for errors.
Total broken links found:
This table lists **Donor Posts** - these are posts and pages that **contain outgoing links**. The scanner checks these outgoing links for errors. This list is different from "Orphan Articles", which are posts that do not have any **incoming links**.
| Source Post (Donor) | Internal Links | External Links | Errors | Details |
|---|---|---|---|---|
| No data found. Please run a scan or refresh the page if a scan has just completed. | ||||
| internal_cnt; ?> | external_cnt; ?> | errors_count > 0): ?>errors_count; ?> brokenAll OK | ||