$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 = esc_sql( AILM_Scout_DB::get_table_name() ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE post_id = %d AND url = %s AND link_origin = %s", $post_id, $url_to_remove, 'ailm' ) ); wp_send_json_success( __( 'Link removed from AILM map.', 'ailinkmaster-internal-auto-manager' ) ); } } */ // v2.0: Remove via unified rules table. $removed = false; if (class_exists('AILM_Link_Repository')) { $rules = AILM_Link_Repository::get_rules_for_donor($post_id); foreach ($rules as $rule) { if (untrailingslashit($rule->target_url) === untrailingslashit($url_to_remove)) { AILM_Link_Repository::delete_rule((int) $rule->id); $removed = true; } } } if ($removed) { global $wpdb; $table_name = esc_sql(AILM_Scout_DB::get_table_name()); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->query($wpdb->prepare("DELETE FROM {$table_name} WHERE post_id = %d AND url = %s AND link_origin = %s", $post_id, $url_to_remove, 'ailm')); wp_send_json_success(__('Link removed from AILM map.', 'ailinkmaster-internal-auto-manager')); } wp_send_json_error(__('Link not found in map.', 'ailinkmaster-internal-auto-manager')); } private function get_sort_link($orderby_val, $current_orderby, $current_order, $filter_val) { $order = 'desc'; if ($orderby_val === $current_orderby) { $order = (strtolower($current_order) === 'desc') ? 'asc' : 'desc'; } return add_query_arg(['orderby' => $orderby_val, 'order' => $order, 'link_filter' => $filter_val]); } // ========================================== // UI RENDERER // ========================================== public function render_tab() { global $wpdb; $table_name = esc_sql(AILM_Scout_DB::get_table_name()); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 50; if (!in_array($per_page, [20, 50, 100], true)) { $per_page = 50; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $paged = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $offset = ($paged - 1) * $per_page; // Sorting // phpcs:ignore WordPress.Security.NonceVerification.Recommended $orderby = isset($_GET['orderby']) ? sanitize_text_field(wp_unslash($_GET['orderby'])) : 'errors_count'; if (!in_array($orderby, ['internal_cnt', 'external_cnt', 'sponsored_cnt', 'errors_count'], true)) { $orderby = 'errors_count'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $order = isset($_GET['order']) ? sanitize_text_field(wp_unslash($_GET['order'])) : 'DESC'; $order = strtoupper($order); if ('ASC' !== $order && 'DESC' !== $order) { $order = 'DESC'; } // Filtering // phpcs:ignore WordPress.Security.NonceVerification.Recommended $filter = isset($_GET['link_filter']) ? sanitize_text_field(wp_unslash($_GET['link_filter'])) : 'all'; $having_clause = ""; $count_where = ""; if ($filter === 'errors') { $having_clause = "HAVING errors_count > 0"; $count_where = "WHERE status_code >= 400"; } elseif ($filter === 'sponsored') { $having_clause = "HAVING sponsored_cnt > 0"; $count_where = "WHERE link_type = 'sponsored'"; } elseif ($filter === 'external') { $having_clause = "HAVING external_cnt > 0"; $count_where = "WHERE link_type = 'external'"; } // Count total posts (dynamically based on filter) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $total_posts_with_links = $wpdb->get_var("SELECT COUNT(DISTINCT post_id) FROM {$table_name} {$count_where}"); $total_pages = ceil($total_posts_with_links / $per_page); // Fetch stats // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $sql = "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 status_code = 0 THEN 1 ELSE 0 END) as pending_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, SUM(CASE WHEN link_type = 'sponsored' THEN 1 ELSE 0 END) as sponsored_cnt FROM {$table_name} GROUP BY post_id {$having_clause} ORDER BY {$orderby} {$order}, post_id DESC LIMIT %d, %d"; // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $posts_stats = $wpdb->get_results($wpdb->prepare($sql, $offset, $per_page)); // Global Error Count // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $total_errors = $wpdb->get_var("SELECT COUNT(id) FROM {$table_name} WHERE status_code >= 400"); $scout_nonce = wp_create_nonce('ailm_scout_nonce'); ?>
Donor Posts - posts and pages that contain outgoing links.', 'ailinkmaster-internal-auto-manager')); ?>