ensure_tables(); } public function admin_enqueue_styles($hook) { // Only load on our plugin pages if (strpos($hook, 'ai-404-redirect') !== false || $hook === 'toplevel_page_ai-404-redirect') { wp_enqueue_style( 'ai-404-redirect-admin', plugin_dir_url(__FILE__) . 'assets/admin-style.css', array(), AI_404_REDIRECT_VERSION ); } } public function activate() { $this->create_tables(); // Store version for future upgrades add_option('ai_404_redirect_version', AI_404_REDIRECT_VERSION); // Flush rewrite rules to ensure proper URL handling flush_rewrite_rules(); } public function deactivate() { // Flush rewrite rules on deactivation flush_rewrite_rules(); } private function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // Create 404 hits table $hits_table = $wpdb->prefix . 'ai_404_redirect_hits'; $hits_sql = "CREATE TABLE $hits_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, url varchar(500) NOT NULL, first_hit datetime DEFAULT CURRENT_TIMESTAMP, hit_count int DEFAULT 1, last_hit datetime DEFAULT CURRENT_TIMESTAMP, redirected_to varchar(500), user_agent text, referer varchar(500), PRIMARY KEY (id), UNIQUE KEY url (url), KEY first_hit (first_hit) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($hits_sql); // Create manual redirects table $redirects_table = $wpdb->prefix . 'ai_404_manual_redirects'; $redirects_sql = "CREATE TABLE $redirects_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, from_url varchar(500) NOT NULL, to_url varchar(500) NOT NULL, redirect_type int DEFAULT 302, is_active tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, hit_count int DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY from_url (from_url), KEY is_active (is_active) ) $charset_collate;"; dbDelta($redirects_sql); } private function ensure_tables() { global $wpdb; // Check if manual redirects table exists $manual_table = $wpdb->prefix . 'ai_404_manual_redirects'; $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $manual_table)) === $manual_table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching if (!$table_exists) { $this->create_tables(); } } public function handle_404() { if (is_404()) { $requested_url = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : ''; if (empty($requested_url)) { return; } // Track 404 hit $this->track_404_hit($requested_url); // Check for manual redirect first $manual_redirect = $this->get_manual_redirect($requested_url); if ($manual_redirect) { // Get final destination to prevent redirect chains $final_destination = $this->resolve_redirect_chain($manual_redirect['to_url']); $this->increment_manual_redirect_count($manual_redirect['id']); $redirect_url = $this->get_full_redirect_url($final_destination); $this->update_404_redirect($requested_url, $final_destination); wp_redirect($redirect_url, $manual_redirect['redirect_type']); exit; } // Check if we already have a cached redirect for this URL $cached_redirect = $this->get_cached_404_redirect($requested_url); if ($cached_redirect) { // Get final destination to prevent redirect chains $final_destination = $this->resolve_redirect_chain($cached_redirect); // Verify the final redirect target still exists if ($this->verify_redirect_target($final_destination)) { // Update cache if destination changed due to chain resolution if ($final_destination !== $cached_redirect) { $this->update_404_redirect($requested_url, $final_destination); } $redirect_url = home_url($final_destination); wp_redirect($redirect_url, 302); exit; } else { $this->clear_404_redirect_cache($requested_url); } } // If no manual or cached redirect, use AI $best_match = $this->find_best_match($requested_url); if ($best_match && $best_match !== $requested_url) { // Update redirect info before redirecting $this->update_404_redirect($requested_url, $best_match); // Force database write before redirect global $wpdb; if (function_exists('wp_cache_flush')) { wp_cache_flush(); } $redirect_url = home_url($best_match); wp_redirect($redirect_url, 302); exit; } } } private function find_best_match($requested_url) { $available_routes = $this->get_available_routes(); if (empty($available_routes)) { return null; } $ai_response = $this->query_ai($requested_url, $available_routes); if ($ai_response === null) { return null; } return $this->parse_ai_response($ai_response, $available_routes); } private function get_available_routes() { $routes = array(); // Add homepage $routes[] = home_url('/'); // Get all published posts and pages $posts = get_posts(array( 'post_type' => array('post', 'page'), 'post_status' => 'publish', 'numberposts' => -1 )); foreach ($posts as $post) { $routes[] = get_permalink($post->ID); } // Get category and tag archives $categories = get_categories(); foreach ($categories as $category) { $routes[] = get_category_link($category->term_id); } $tags = get_tags(); foreach ($tags as $tag) { $routes[] = get_tag_link($tag->term_id); } // Remove home URL from routes to get relative paths and clean up URLs $home_url = home_url(); $routes = array_map(function($route) use ($home_url) { $relative_path = str_replace($home_url, '', $route); // Remove /index.php/ if present $relative_path = str_replace('/index.php/', '/', $relative_path); // Ensure path starts with / if ($relative_path && $relative_path[0] !== '/') { $relative_path = '/' . $relative_path; } // If empty path, it's homepage if (empty($relative_path) || $relative_path === '/') { return '/'; } return $relative_path; }, $routes); return array_unique($routes); } private function query_ai($requested_url, $available_routes) { $prompt = "A user tried to access: {$requested_url}\n\n"; $prompt .= "Available routes:\n" . implode("\n", $available_routes) . "\n\n"; $prompt .= "Find the most relevant and related route from the available routes. Be creative and consider:\n"; $prompt .= "- Similar topics (e.g., 'rocket' relates to 'spacex', 'space', 'nasa')\n"; $prompt .= "- Related keywords and synonyms\n"; $prompt .= "- Conceptual connections\n"; $prompt .= "- Partial matches and variations\n"; $prompt .= "Return only the exact route path from the list above, nothing else."; $data = array( 'model' => $this->api_model, 'messages' => array( array( 'role' => 'user', 'content' => $prompt ) ), 'max_tokens' => 100, 'temperature' => $this->temperature ); $args = array( 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $this->api_key ), 'body' => json_encode($data), 'timeout' => 30 ); $response = wp_remote_post($this->api_endpoint, $args); if (is_wp_error($response)) { return null; } $response_code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($response_code !== 200) { return null; } $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { return null; } if (isset($data['choices'][0]['message']['content'])) { return trim($data['choices'][0]['message']['content']); } return null; } private function parse_ai_response($ai_response, $available_routes) { if (empty($ai_response)) { return null; } // Clean up the AI response $ai_response = trim($ai_response, " \t\n\r\0\x0B\"'"); // First try exact match foreach ($available_routes as $route) { if ($ai_response === $route) { return $route; } } // Then try if AI response is contained in any route (but avoid matching "/" with everything) foreach ($available_routes as $route) { if ($route !== '/' && strpos($route, $ai_response) !== false) { return $route; } } // Then try if any route is contained in AI response (but avoid matching "/" with everything) foreach ($available_routes as $route) { if ($route !== '/' && strpos($ai_response, $route) !== false) { return $route; } } // Finally, check if AI response matches homepage specifically if ($ai_response === '/' || $ai_response === 'home' || $ai_response === 'homepage') { return '/'; } return null; } public function disable_wp_redirect_guess($redirect_url, $requested_url) { // Disable WordPress's redirect guessing when on 404 pages if (is_404()) { return false; } return $redirect_url; } private function get_cached_404_redirect($url) { global $wpdb; $table_name = $wpdb->prefix . 'ai_404_redirect_hits'; $cached = $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching "SELECT redirected_to FROM `{$table_name}` WHERE url = %s AND redirected_to IS NOT NULL AND redirected_to != ''", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $url )); return $cached; } private function verify_redirect_target($target_path) { $target_url = home_url($target_path); $response = wp_remote_head($target_url, array( 'timeout' => 5, 'redirection' => 0 )); if (is_wp_error($response)) { return false; } $status_code = wp_remote_retrieve_response_code($response); return ($status_code !== 404 && $status_code !== 500); } private function clear_404_redirect_cache($url) { global $wpdb; $table_name = $wpdb->prefix . 'ai_404_redirect_hits'; $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $table_name, array('redirected_to' => null), array('url' => $url), array('%s'), array('%s') ); } private function get_manual_redirect($url) { global $wpdb; $table_name = $wpdb->prefix . 'ai_404_manual_redirects'; $redirect = $wpdb->get_row($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching "SELECT * FROM `{$table_name}` WHERE from_url = %s AND is_active = 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $url ), ARRAY_A); return $redirect; } private function get_full_redirect_url($to_url) { // If it starts with http/https, it's already a full URL if (strpos($to_url, 'http://') === 0 || strpos($to_url, 'https://') === 0) { return $to_url; } // If it starts with /, it's a relative path - convert to full URL if (strpos($to_url, '/') === 0) { return home_url($to_url); } // If it doesn't start with /, assume it's a relative path and add / return home_url('/' . $to_url); } private function increment_manual_redirect_count($redirect_id) { global $wpdb; $table_name = $wpdb->prefix . 'ai_404_manual_redirects'; $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching "UPDATE `{$table_name}` SET hit_count = hit_count + 1 WHERE id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $redirect_id )); } private function track_404_hit($url) { global $wpdb; $table_name = $wpdb->prefix . 'ai_404_redirect_hits'; $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''; $referer = isset($_SERVER['HTTP_REFERER']) ? esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER'])) : ''; $url = sanitize_text_field($url); $existing = $wpdb->get_row($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching "SELECT * FROM `{$table_name}` WHERE url = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $url )); if ($existing) { $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $table_name, array( 'hit_count' => absint($existing->hit_count) + 1, 'last_hit' => current_time('mysql') ), array('url' => $url), array('%d', '%s'), array('%s') ); } else { $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $table_name, array( 'url' => $url, 'user_agent' => $user_agent, 'referer' => $referer ), array('%s', '%s', '%s') ); } } private function update_404_redirect($url, $redirected_to) { global $wpdb; $table_name = $wpdb->prefix . 'ai_404_redirect_hits'; $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $table_name, array('redirected_to' => $redirected_to), array('url' => $url), array('%s'), array('%s') ); } public function add_admin_menu() { // Add main menu page add_menu_page( 'AI 404 Redirect', 'AI 404 Redirect', 'manage_options', 'ai-404-redirect', array($this, 'hits_page'), 'dashicons-redo', 30 ); // Add submenu pages add_submenu_page( 'ai-404-redirect', '404 Hits History', '404 Hits', 'manage_options', 'ai-404-redirect', array($this, 'hits_page') ); add_submenu_page( 'ai-404-redirect', 'Manual Redirects', 'Manual Redirects', 'manage_options', 'ai-404-redirect-manual', array($this, 'manual_redirects_page') ); } public function hits_page() { // Check user permissions if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'ai-404-redirect')); } if (isset($_POST['clear_hits']) && check_admin_referer('clear_404_hits', 'clear_hits_nonce')) { $this->clear_404_hits(); echo '

' . esc_html__('404 hits cleared successfully!', 'ai-404-redirect') . '

'; } if (isset($_POST['fix_chains']) && check_admin_referer('fix_redirect_chains', 'fix_chains_nonce')) { $this->fix_existing_redirect_chains(); echo '

' . esc_html__('Redirect chains have been resolved and fixed!', 'ai-404-redirect') . '

'; } global $wpdb; $table_name = $wpdb->prefix . 'ai_404_redirect_hits'; $hits = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching "SELECT * FROM `{$table_name}` ORDER BY first_hit DESC LIMIT 200", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared ARRAY_A ); ?>

'return confirm("' . esc_js(__('Are you sure you want to clear all 404 hits? This action cannot be undone.', 'ai-404-redirect')) . '")')); ?>
'return confirm("' . esc_js(__('This will resolve any redirect chains in your manual redirects and cached 404 redirects. Continue?', 'ai-404-redirect')) . '")')); ?>

→ View
prefix . 'ai_404_manual_redirects'; // Handle form submissions if (isset($_POST['add_redirect']) && check_admin_referer('add_manual_redirect', 'redirect_nonce')) { $from_url = isset($_POST['from_url']) ? sanitize_text_field(wp_unslash($_POST['from_url'])) : ''; $to_url = isset($_POST['to_url']) ? sanitize_text_field(wp_unslash($_POST['to_url'])) : ''; $redirect_type = isset($_POST['redirect_type']) ? absint($_POST['redirect_type']) : 302; // Auto-format URLs if (!empty($from_url) && strpos($from_url, '/') !== 0) { $from_url = '/' . $from_url; } if (!empty($to_url) && strpos($to_url, '/') !== 0 && strpos($to_url, 'http://') !== 0 && strpos($to_url, 'https://') !== 0) { $to_url = '/' . $to_url; } if (!empty($from_url) && !empty($to_url)) { // Check if URL already exists in manual redirects $existing = $wpdb->get_row($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching "SELECT * FROM `{$table_name}` WHERE from_url = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $from_url )); if ($existing) { /* translators: %s is the URL that already has a redirect */ echo '

' . sprintf(esc_html__('Error: A redirect for "%s" already exists. Please delete the existing one first or use a different URL.', 'ai-404-redirect'), esc_html($from_url)) . '

'; } else { // Check if this URL exists in 404 hits table to transfer hit count $hits_table = $wpdb->prefix . 'ai_404_redirect_hits'; $hit_record = $wpdb->get_row($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching "SELECT * FROM `{$hits_table}` WHERE url = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $from_url )); $hit_count = $hit_record ? $hit_record->hit_count : 0; $result = $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $table_name, array( 'from_url' => $from_url, 'to_url' => $to_url, 'redirect_type' => $redirect_type, 'hit_count' => $hit_count ), array('%s', '%s', '%d', '%d') ); if ($result) { // If this was created from a 404 hit, remove the 404 hit record if ($hit_record) { $wpdb->delete($hits_table, array('url' => $from_url), array('%s')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching } if ($hit_count > 0) { /* translators: %d is the number of hits transferred from 404 history */ $success_msg = sprintf(__('Manual redirect added successfully! Transferred %d hits from 404 history.', 'ai-404-redirect'), $hit_count); } else { $success_msg = __('Manual redirect added successfully!', 'ai-404-redirect'); } echo '

' . esc_html($success_msg) . '

'; } else { /* translators: %s is the database error message */ echo '

' . sprintf(esc_html__('Database error: %s', 'ai-404-redirect'), esc_html($wpdb->last_error)) . '

'; } } } else { echo '

' . esc_html__('Error: Both From URL and To URL are required.', 'ai-404-redirect') . '

'; } } if (isset($_POST['delete_redirect']) && isset($_POST['redirect_id']) && check_admin_referer('delete_redirect_' . absint($_POST['redirect_id']))) { $redirect_id = absint($_POST['redirect_id']); $wpdb->delete($table_name, array('id' => $redirect_id), array('%d')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching echo '

' . esc_html__('Redirect deleted successfully!', 'ai-404-redirect') . '

'; } if (isset($_POST['toggle_redirect']) && isset($_POST['redirect_id']) && isset($_POST['is_active']) && check_admin_referer('toggle_redirect_' . absint($_POST['redirect_id']))) { $redirect_id = absint($_POST['redirect_id']); $is_active = absint($_POST['is_active']) ? 0 : 1; $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $table_name, array('is_active' => $is_active), array('id' => $redirect_id), array('%d'), array('%d') ); echo '

' . esc_html__('Redirect status updated!', 'ai-404-redirect') . '

'; } $redirects = $wpdb->get_results("SELECT * FROM `{$table_name}` ORDER BY created_at DESC", ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching ?>

Creating manual redirect for:

'RankMath SEO', 'wordpress-seo/wp-seo-main.php' => 'Yoast SEO', 'redirection/redirection.php' => 'Redirection', 'simple-301-redirects/wp-simple-301-redirects.php' => 'Simple 301 Redirects', 'safe-redirect-manager/safe-redirect-manager.php' => 'Safe Redirect Manager', 'quick-page-post-redirect-plugin/page_post_redirect_plugin.php' => 'Quick Page/Post Redirect' ); foreach ($plugins_to_check as $plugin_file => $plugin_name) { if (is_plugin_active($plugin_file)) { $conflicting_plugins[] = $plugin_name; } } // Also check for common classes $class_checks = array( 'RankMath' => 'RankMath SEO', 'WPSEO_Options' => 'Yoast SEO', 'Red_Item' => 'Redirection Plugin' ); foreach ($class_checks as $class_name => $plugin_name) { if (class_exists($class_name) && !in_array($plugin_name, $conflicting_plugins)) { $conflicting_plugins[] = $plugin_name; } } return $conflicting_plugins; } public function admin_notices() { if (current_user_can('manage_options')) { $conflicting_plugins = $this->get_conflicting_plugins(); if (!empty($conflicting_plugins)) { echo '
'; echo '

⚠️ AI 404 Redirect Notice: The following redirect/SEO plugins are active: ' . esc_html(implode(', ', $conflicting_plugins)) . '

'; echo '

These plugins may interfere with AI-powered 404 redirects. Consider disabling their 404 redirect features for best results.

'; echo '
'; } } } private function clear_404_hits() { global $wpdb; $table_name = $wpdb->prefix . 'ai_404_redirect_hits'; $wpdb->query("TRUNCATE TABLE `{$table_name}`"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared } /** * Resolves redirect chains to prevent infinite loops and multiple redirects * * @param string $url The URL to resolve * @param array $visited Track visited URLs to prevent infinite loops * @return string The final destination URL */ private function resolve_redirect_chain($url, $visited = array()) { // Prevent infinite loops by limiting depth and tracking visited URLs if (count($visited) >= 10 || in_array($url, $visited)) { return $url; } $visited[] = $url; // Check if this URL has a manual redirect $manual_redirect = $this->get_manual_redirect($url); if ($manual_redirect && $manual_redirect['is_active']) { return $this->resolve_redirect_chain($manual_redirect['to_url'], $visited); } // Check if this URL has a cached AI redirect $cached_redirect = $this->get_cached_404_redirect($url); if ($cached_redirect && $cached_redirect !== $url) { return $this->resolve_redirect_chain($cached_redirect, $visited); } return $url; } /** * Updates all redirect chains when a URL destination changes * This prevents redirect chains from forming when content URLs change * * @param string $old_url The old URL that was a destination * @param string $new_url The new URL it should point to */ private function update_redirect_chains($old_url, $new_url) { global $wpdb; // Update manual redirects that point to the old URL $manual_table = $wpdb->prefix . 'ai_404_manual_redirects'; $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $manual_table, array('to_url' => $new_url), array('to_url' => $old_url), array('%s'), array('%s') ); // Update 404 hit cache that points to the old URL $hits_table = $wpdb->prefix . 'ai_404_redirect_hits'; $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $hits_table, array('redirected_to' => $new_url), array('redirected_to' => $old_url), array('%s'), array('%s') ); } /** * Detects and fixes existing redirect chains in the database * This can be run periodically or on-demand to clean up chains */ public function fix_existing_redirect_chains() { global $wpdb; // Get all manual redirects $manual_table = $wpdb->prefix . 'ai_404_manual_redirects'; $redirects = $wpdb->get_results("SELECT * FROM `{$manual_table}` WHERE is_active = 1", ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching foreach ($redirects as $redirect) { $final_destination = $this->resolve_redirect_chain($redirect['to_url']); // If the final destination is different, update it if ($final_destination !== $redirect['to_url']) { $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $manual_table, array('to_url' => $final_destination), array('id' => $redirect['id']), array('%s'), array('%d') ); } } // Get all 404 hit redirects $hits_table = $wpdb->prefix . 'ai_404_redirect_hits'; $hits = $wpdb->get_results("SELECT * FROM `{$hits_table}` WHERE redirected_to IS NOT NULL AND redirected_to != ''", ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching foreach ($hits as $hit) { $final_destination = $this->resolve_redirect_chain($hit['redirected_to']); // If the final destination is different, update it if ($final_destination !== $hit['redirected_to']) { $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $hits_table, array('redirected_to' => $final_destination), array('id' => $hit['id']), array('%s'), array('%d') ); } } } /** * Handle post URL changes (slug updates, status changes) */ public function handle_post_url_change($post_id, $post_after, $post_before) { // Only process published posts and pages if ($post_after->post_status !== 'publish' && $post_before->post_status !== 'publish') { return; } $old_url = ''; $new_url = ''; // Get old URL if post was previously published if ($post_before->post_status === 'publish') { $old_permalink = get_permalink($post_before); $old_url = str_replace(home_url(), '', $old_permalink); } // Get new URL if post is now published if ($post_after->post_status === 'publish') { $new_permalink = get_permalink($post_after); $new_url = str_replace(home_url(), '', $new_permalink); } // If URLs changed, update redirect chains if (!empty($old_url) && !empty($new_url) && $old_url !== $new_url) { $this->update_redirect_chains($old_url, $new_url); } } /** * Handle taxonomy term URL changes (category, tag slug updates) */ public function handle_term_url_change($term_id, $taxonomy, $args) { $term = get_term($term_id, $taxonomy); if (!$term || is_wp_error($term)) { return; } // Get old term data if available if (isset($args['slug'])) { $old_slug = $args['slug']; $new_slug = $term->slug; if ($old_slug !== $new_slug) { // Construct old and new URLs if ($taxonomy === 'category') { $old_url = str_replace(home_url(), '', get_category_link($term_id)); $old_url = str_replace($new_slug, $old_slug, $old_url); $new_url = str_replace(home_url(), '', get_category_link($term_id)); } elseif ($taxonomy === 'post_tag') { $old_url = str_replace(home_url(), '', get_tag_link($term_id)); $old_url = str_replace($new_slug, $old_slug, $old_url); $new_url = str_replace(home_url(), '', get_tag_link($term_id)); } else { $old_url = str_replace(home_url(), '', get_term_link($term_id, $taxonomy)); $old_url = str_replace($new_slug, $old_slug, $old_url); $new_url = str_replace(home_url(), '', get_term_link($term_id, $taxonomy)); } if (!empty($old_url) && !empty($new_url) && $old_url !== $new_url) { $this->update_redirect_chains($old_url, $new_url); } } } } /** * Handle menu item URL changes */ public function handle_menu_url_change($menu_id, $menu_item_db_id, $args) { // This is less critical as menu items aren't typically redirect destinations // but included for completeness if (isset($args['menu-item-url'])) { $new_url = $args['menu-item-url']; // Get old URL from database if needed $old_menu_item = wp_setup_nav_menu_item(get_post($menu_item_db_id)); if ($old_menu_item && isset($old_menu_item->url)) { $old_url = str_replace(home_url(), '', $old_menu_item->url); $new_url = str_replace(home_url(), '', $new_url); if (!empty($old_url) && !empty($new_url) && $old_url !== $new_url) { $this->update_redirect_chains($old_url, $new_url); } } } } } // Initialize the plugin AI_404_Redirect::get_instance();