plugin_name = $plugin_name; $this->version = $version; $this->core = $core; require_once plugin_dir_path(__FILE__) . 'class-ailm-settings-tab.php'; require_once plugin_dir_path(__FILE__) . 'class-ailm-stats-tab.php'; require_once plugin_dir_path(__FILE__) . 'class-ailm-faq-tab.php'; require_once plugin_dir_path(__FILE__) . '../modules/pagerank/class-ailm-pagerank.php'; $this->settings_tab = new AILM_Settings_Tab($this->plugin_name, $this->version, $this->core); $this->stats_tab = new AILM_Stats_Tab($this->plugin_name, $this->version, $this->core); $this->faq_tab = new AILM_FAQ_Tab($this->plugin_name, $this->version, $this->core); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); add_action('admin_menu', array($this, 'add_plugin_admin_menu')); add_action('admin_notices', array($this, 'display_limit_reached_notice')); // AJAX для поиска постов в настройках add_action('wp_ajax_ailm_search_posts', array($this, 'ajax_search_posts')); add_action('wp_ajax_ailm_get_dashboard_analytics', array($this, 'ajax_get_dashboard_analytics')); add_action('wp_ajax_ailm_get_inbound_links_details', array($this, 'ajax_get_inbound_links_details')); add_action('wp_ajax_ailm_dashboard_delete_inbound_link', array($this, 'ajax_dashboard_delete_inbound_link')); add_action('wp_ajax_ailm_dashboard_create_direct_link', array($this, 'ajax_dashboard_create_direct_link')); add_action('wp_ajax_ailm_index_selected_post', array($this, 'ajax_index_selected_post')); add_action('wp_ajax_ailm_dismiss_sync_banner', array($this, 'ajax_dismiss_sync_banner')); // Инициализация модуля PageRank new AILM_PageRank_Engine(); // CSV require_once plugin_dir_path(__FILE__) . 'class-ailm-csv-handler.php'; new AILM_CSV_Handler($this->plugin_name); // Clicks Stats — early init so admin_post hooks are registered before HTML output require_once plugin_dir_path(__FILE__) . '../modules/clicks-stats/class-ailm-clicks-admin.php'; $this->clicks_tab = new AILM_Clicks_Admin(); // Review Queue — early init so admin_enqueue_scripts fires before page render require_once plugin_dir_path(__FILE__) . '../modules/review-queue/class-ailm-review-queue-admin.php'; $this->review_queue_tab = new AILM_Review_Queue_Admin(); } public function enqueue_admin_assets($hook) { if (strpos($hook, $this->plugin_name) !== false) { wp_enqueue_style( $this->plugin_name . '-admin-settings', plugin_dir_url(__FILE__) . '../assets/admin-settings.css', array(), time(), 'all' ); // Подключаем Select2 // phpcs:ignore PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent wp_enqueue_style('select2', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css', array(), '4.1.0'); // phpcs:ignore PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent wp_enqueue_script('select2', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js', array('jquery'), '4.1.0', true); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'dashboard'; // Скрипты для страницы настроек if ($tab === 'settings') { wp_enqueue_script( 'ailm-settings-js', plugin_dir_url(__FILE__) . '../assets/admin-settings.js', array('jquery', 'select2'), $this->version, true ); wp_localize_script('ailm-settings-js', 'ailmSettings', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ailm_settings_nonce'), )); } if ($tab === 'stats') { wp_enqueue_script('ailm-stats-js', plugin_dir_url(__FILE__) . '../assets/admin-stats.js', array('jquery'), $this->version, true); wp_localize_script('ailm-stats-js', 'ailmStats', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ailm_stats_nonce'), 'i18n' => array( 'no_recipients' => esc_html__('No recipients found.', 'ailinkmaster-internal-auto-manager'), ) )); } if ($tab === 'dashboard' || $tab === 'orphan-rescue') { wp_enqueue_script('ailm-orphan-js', plugin_dir_url(__FILE__) . '../assets/admin-orphan.js', array('jquery'), time(), true); wp_localize_script('ailm-orphan-js', 'ailmOrphan', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ailm_orphan_nonce'), 'i18n' => array( 'no_orphans' => esc_html__('No orphans found.', 'ailinkmaster-internal-auto-manager'), 'rescue' => esc_html__('Rescue', 'ailinkmaster-internal-auto-manager'), 'saving' => esc_html__('Saving...', 'ailinkmaster-internal-auto-manager'), 'save_stop_words' => esc_html__('Save Stop Words', 'ailinkmaster-internal-auto-manager'), 'err_save_stop_words' => esc_html__('Error saving stop words.', 'ailinkmaster-internal-auto-manager'), 'err_connection' => esc_html__('Connection error.', 'ailinkmaster-internal-auto-manager'), 'no_keywords' => esc_html__('No clear keywords found. Please enter them manually below.', 'ailinkmaster-internal-auto-manager'), 'err_analyze' => esc_html__('Error analyzing article. Please check your connection.', 'ailinkmaster-internal-auto-manager'), 'err_no_keyword_selected' => esc_html__('Please select or enter at least one keyword.', 'ailinkmaster-internal-auto-manager'), 'posts_checked' => esc_html__('posts checked', 'ailinkmaster-internal-auto-manager'), 'donors_found' => esc_html__('donors found', 'ailinkmaster-internal-auto-manager'), 'search_failed' => esc_html__('Search failed: ', 'ailinkmaster-internal-auto-manager'), 'err_db_scan' => esc_html__('Connection error during database scan.', 'ailinkmaster-internal-auto-manager'), 'try_broader' => esc_html__('Try Broader Keywords', 'ailinkmaster-internal-auto-manager'), 'found' => esc_html__('Found', 'ailinkmaster-internal-auto-manager'), 'donors' => esc_html__('Donors', 'ailinkmaster-internal-auto-manager'), 'select_articles' => esc_html__('Select the articles you want to link from. All of these have passed safety checks (outbound limits, H2 zones, etc).', 'ailinkmaster-internal-auto-manager'), 'donor_context' => esc_html__('Donor Article & Context', 'ailinkmaster-internal-auto-manager'), 'edit' => esc_html__('Edit', 'ailinkmaster-internal-auto-manager'), 'err_select_donor' => esc_html__('Please select at least one donor article.', 'ailinkmaster-internal-auto-manager'), 'creating_links' => esc_html__('Creating Links...', 'ailinkmaster-internal-auto-manager'), 'success_links_created' => esc_html__('Success! Links have been instantly created on the live site.', 'ailinkmaster-internal-auto-manager'), 'error' => esc_html__('Error: ', 'ailinkmaster-internal-auto-manager'), 'err_creating_rules' => esc_html__('Connection error while creating rules.', 'ailinkmaster-internal-auto-manager'), 'no_matches_html' => wp_kses_post(__('
None of the scanned articles contained these keywords outside of headers/existing links, or they reached their outbound limit.
', 'ailinkmaster-internal-auto-manager')), 'view' => esc_html__('View', 'ailinkmaster-internal-auto-manager'), ) )); } if ($tab === 'dashboard') { wp_enqueue_script('ailm-dashboard-js', plugin_dir_url(__FILE__) . '../assets/admin-dashboard.js', array('jquery'), time(), true); } } } /** * AJAX handler for the Unified Dashboard Table data. */ public function ajax_get_dashboard_analytics() { check_ajax_referer('ailm_orphan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(); } // Sync indexed status from SaaS so checkmarks are always fresh if (class_exists('AILM_OpenAI_Provider')) { $provider = new AILM_OpenAI_Provider(); $sync_resp = $provider->saas_get_indexed_ids(); if (!is_wp_error($sync_resp) && isset($sync_resp['indexed_ids'])) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_ailm_is_indexed'"); $indexed_ids = $sync_resp['indexed_ids']; if (!empty($indexed_ids)) { $time = current_time('mysql'); $values = []; foreach ($indexed_ids as $id) { $values[] = $wpdb->prepare("(%d, %s, %s)", $id, '_ailm_is_indexed', $time); } foreach (array_chunk($values, 500) as $chunk) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared -- $chunk values prepared via $wpdb->prepare() above. $wpdb->query("INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) VALUES " . implode(', ', $chunk)); } } } } // Fetch all statuses except trash and auto-draft so drafts/pending/future/private // are visible in the dashboard table. Link building and indexing still rely on // 'publish' internally, so non-published rows will show zero counts (expected). $posts = get_posts(array( 'numberposts' => 1000, 'post_type' => array('post', 'page'), 'post_status' => array('publish', 'draft', 'pending', 'future', 'private'), 'suppress_filters' => true, )); // Bulk-load all counts in two queries via Repository. $outbound_counts = AILM_Link_Repository::get_bulk_outbound_counts('internal'); $inbound_counts = AILM_Link_Repository::get_bulk_inbound_counts('internal'); $data = array(); foreach ($posts as $p) { $post_status = get_post_status($p->ID); $data[] = array( 'id' => $p->ID, 'title' => html_entity_decode(get_the_title($p->ID), ENT_QUOTES, 'UTF-8'), 'date' => get_the_date('M j, Y', $p->ID), 'type' => ucfirst($p->post_type), 'status' => $post_status, 'inbound' => isset($inbound_counts[$p->ID]) ? $inbound_counts[$p->ID] : 0, 'outbound' => isset($outbound_counts[$p->ID]) ? $outbound_counts[$p->ID] : 0, 'pagerank' => AILM_PageRank_Engine::get_post_pr($p->ID), 'is_indexed' => get_post_meta($p->ID, '_ailm_is_indexed', true) ? true : false, 'edit_url' => get_edit_post_link($p->ID, 'raw'), 'url' => get_permalink($p->ID), ); } wp_send_json_success($data); } /** * AJAX handler to index a specific post selected from the dashboard. */ public function ajax_index_selected_post() { check_ajax_referer('ailm_orphan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized access.'); } $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; if (!$post_id) { wp_send_json_error('Missing post ID.'); } $post = get_post($post_id); if (!$post || empty($post->post_content)) { wp_send_json_error('Post not found or empty content.'); } if (!class_exists('AILM_OpenAI_Provider')) { require_once dirname(__DIR__) . '/modules/ai-core/providers/class-ailm-openai-provider.php'; } $provider = new AILM_OpenAI_Provider(); $response = $provider->saas_index_document($post_id, $post->post_content); if (is_wp_error($response)) { wp_send_json_error(array('message' => $response->get_error_message())); } update_post_meta($post_id, '_ailm_is_indexed', current_time('mysql')); wp_send_json_success(); } /** * AJAX handler to permanently dismiss the "Sync Manual Links" banner. * * @return void */ public function ajax_dismiss_sync_banner() { check_ajax_referer('ailm_dismiss_sync_banner_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'ailinkmaster-internal-auto-manager')); } update_option('ailm_sync_banner_dismissed', true); wp_send_json_success(); } /** * AJAX поиск постов для черного списка */ public function ajax_search_posts() { if (!current_user_can('manage_options')) { wp_send_json_error(); } $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : ''; if (!wp_verify_nonce($nonce, 'ailm_settings_nonce') && !wp_verify_nonce($nonce, 'ailm_orphan_nonce')) { wp_send_json_error('Invalid security token.'); } $search_text = isset($_GET['q']) ? sanitize_text_field(wp_unslash($_GET['q'])) : ''; $post_types = get_option('ailm_allowed_post_types', array('post', 'page')); $query = new WP_Query(array( 's' => $search_text, 'post_type' => $post_types, 'posts_per_page' => 15, 'post_status' => 'publish', )); $results = array(); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $results[] = array( 'id' => get_the_ID(), 'text' => html_entity_decode(get_the_title(), ENT_QUOTES, 'UTF-8') . ' (ID: ' . get_the_ID() . ')', ); } } wp_reset_postdata(); wp_send_json_success($results); } public function ajax_dashboard_create_direct_link() { check_ajax_referer('ailm_orphan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Permission denied.'); } $donor_id = isset($_POST['donor_id']) ? absint($_POST['donor_id']) : 0; $target_id = isset($_POST['target_id']) ? absint($_POST['target_id']) : 0; $anchor = isset($_POST['anchor']) ? sanitize_text_field(wp_unslash($_POST['anchor'])) : ''; if (!$donor_id || !$target_id || empty($anchor)) { wp_send_json_error('Missing parameters.'); } if ($donor_id === $target_id) { wp_send_json_error('Cannot link an article to itself.'); } if (!class_exists('AILM_Link_Builder')) { require_once plugin_dir_path(__DIR__) . 'includes/class-ailm-link-builder.php'; } if (!class_exists('AILM_DOM_Helper')) { require_once plugin_dir_path(__DIR__) . 'includes/class-ailm-dom-helper.php'; } $link_builder = new AILM_Link_Builder(null); if (!$link_builder->can_inject_phrase($donor_id, $anchor)) { wp_send_json_error('Link insertion rejected. Limits exceeded, phrase already occupied, or restricted HTML zone.'); } $donor_post = get_post($donor_id); if (!$donor_post) { wp_send_json_error('Donor post not found.'); } $target_url = get_permalink($target_id); $target_limit = (int) get_option('ailm_target_limit', 5); if (class_exists('AILM_Link_Repository')) { $inbound_count = AILM_Link_Repository::get_active_inbound_rule_count($target_id); } else { $inbound_count = (int) get_post_meta($target_id, '_ailm_inbound_count', true); } if ($inbound_count >= $target_limit) { wp_send_json_error('The target post has reached its incoming links limit (' . $inbound_count . '/' . $target_limit . ').'); } $skip_chars = (int) get_option('ailm_skip_chars', 100); $gap = (int) get_option('ailm_min_gap', 200); $dom_helper = new AILM_DOM_Helper($donor_post->post_content); $virtual_text = $dom_helper->get_virtual_text(); $escaped_parts = array_map(function ($p) { return str_replace("'", "['''`´]", preg_quote($p, '/')); }, preg_split('/\s+/u', trim($anchor), -1, PREG_SPLIT_NO_EMPTY)); $fuzzy_kw = implode('[\p{Z}\s\p{P}\p{S}]+', $escaped_parts); $pattern = '/(?get_manual_links_positions(); foreach ($manual_link_positions as $pos) { $occupied_ranges[] = array('start' => max(0, $pos - $gap), 'end' => $pos + $gap); } foreach ($existing_rules as $rule) { $item_occ = (int) $rule->occurrence; $rule_parts = array_map(function ($p) { return str_replace("'", "['''`´]", preg_quote($p, '/')); }, preg_split('/\s+/u', trim($rule->anchor), -1, PREG_SPLIT_NO_EMPTY)); $rule_fuzzy = implode('[\p{Z}\s\p{P}\p{S}]+', $rule_parts); $rule_pattern = '/(? max(0, $char_offset - $gap), 'end' => $char_offset + $char_length + $gap, ); break; } } } } $selected_word = ''; $selected_occurrence = 0; $current_match_idx = 0; foreach ($matches[0] as $match) { $current_match_idx++; $found_word = $match[0]; $byte_offset = $match[1]; $char_offset = mb_strlen(substr($virtual_text, 0, $byte_offset), 'UTF-8'); $char_length = mb_strlen($found_word, 'UTF-8'); if ($char_offset < $skip_chars) { continue; } $overlap = false; foreach ($occupied_ranges as $range) { if ($char_offset < $range['end'] && ($char_offset + $char_length) > $range['start']) { $overlap = true; break; } } if (!$overlap) { $selected_word = $found_word; $selected_occurrence = $current_match_idx; break; } } if ($selected_occurrence === 0) { wp_send_json_error('No valid insertion point found. The phrase is either inside the Skip Intro zone or too close to another link.'); } // Duplicate check against the selected word. foreach ($existing_rules as $existing) { if ((int) $existing->target_id === $target_id && mb_strtolower($existing->anchor, 'UTF-8') === mb_strtolower($selected_word, 'UTF-8')) { wp_send_json_error('This exact link already exists.'); } } AILM_Link_Repository::register_link( $donor_id, $target_url, $selected_word, 'manual_dashboard', $selected_occurrence, false // in_html = 0; injector will insert and mark in_html=1. ); $result = $link_builder->inject_physical_links($donor_id); if (!$result) { wp_send_json_error('Link injection failed. The content may have changed or the target phrase is no longer available.'); } $inbound_count = (int) get_post_meta($target_id, '_ailm_inbound_count', true); update_post_meta($target_id, '_ailm_inbound_count', $inbound_count + 1); if (class_exists('AILM_Scout_Scanner')) { AILM_Scout_Scanner::process_post($donor_id); } wp_send_json_success(); } public function ajax_get_inbound_links_details() { check_ajax_referer('ailm_orphan_nonce', 'nonce'); if (!current_user_can('manage_options')) wp_send_json_error(); $target_id = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0; if (!$target_id) { wp_send_json_error('Invalid post ID.'); } // Single query: get all inbound internal links for this target. $raw_links = AILM_Link_Repository::get_inbound_links($target_id, 'internal'); $links = array(); foreach ($raw_links as $link) { $donor_id = (int) $link->donor_id; $links[] = array( 'donor_id' => $donor_id, 'donor_title' => html_entity_decode(get_the_title($donor_id), ENT_QUOTES, 'UTF-8'), 'donor_url' => get_permalink($donor_id), 'keyword' => $link->anchor, 'type' => $link->origin, // 'plugin' or 'manual' ); } wp_send_json_success($links); } /** * Shared helper to remove a specific donor from a surgical rule and clean up frozen links. * * @param int $target_id Target post ID. * @param int $donor_id Donor post ID. * @param string $keyword The anchor phrase. * @return true|WP_Error True on success, WP_Error on failure. */ public static function remove_surgical_donor($target_id, $donor_id, $keyword) { if (!class_exists('AILM_Link_Repository')) { require_once plugin_dir_path(__DIR__) . 'includes/class-ailm-link-repository.php'; } $rule_deleted = false; $rules = AILM_Link_Repository::get_rules_for_donor($donor_id); foreach ($rules as $rule) { if ((int) $rule->target_id === (int) $target_id && mb_strtolower($rule->anchor, 'UTF-8') === mb_strtolower($keyword, 'UTF-8')) { AILM_Link_Repository::delete_rule($rule->id); $rule_deleted = true; break; } } // Remove physical link from HTML if (!class_exists('AILM_DOM_Helper')) { require_once plugin_dir_path(__DIR__) . 'includes/class-ailm-dom-helper.php'; } $donor_post = get_post($donor_id); $target_url = get_permalink($target_id); $physically_removed = false; if ($donor_post && !empty($donor_post->post_content) && $target_url) { $is_elementor = class_exists('AILM_Elementor_Handler') && AILM_Elementor_Handler::is_elementor_post($donor_id); $global_modified = false; $process_html = function ($html) use (&$global_modified, $target_url, $keyword) { $dom_helper = new AILM_DOM_Helper($html); if ($dom_helper->unwrap_link($target_url, $keyword)) { $global_modified = true; return $dom_helper->get_html(); } return $html; }; if ($is_elementor) { AILM_Elementor_Handler::process_content($donor_id, $process_html); if ($global_modified) $physically_removed = true; } else { $new_content = $process_html($donor_post->post_content); if ($global_modified) { wp_update_post([ 'ID' => $donor_id, 'post_content' => $new_content ]); $physically_removed = true; } } } if ($physically_removed && class_exists('AILM_Scout_Scanner')) { AILM_Scout_Scanner::process_post($donor_id); } if (!$rule_deleted && !$physically_removed) { return new WP_Error('not_found', __('Link not found in database or physical content.', 'ailinkmaster-internal-auto-manager')); } $inbound = (int) get_post_meta($target_id, '_ailm_inbound_count', true); update_post_meta($target_id, '_ailm_inbound_count', max(0, $inbound - 1)); if (class_exists('AILM_Scout_Scanner')) { AILM_Scout_Scanner::process_post($donor_id); } return true; } public function ajax_dashboard_delete_inbound_link() { check_ajax_referer('ailm_orphan_nonce', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error('Permission denied.'); } $donor_id = isset($_POST['donor_id']) ? absint($_POST['donor_id']) : 0; $target_id = isset($_POST['target_id']) ? absint($_POST['target_id']) : 0; $word = isset($_POST['word']) ? sanitize_text_field(wp_unslash($_POST['word'])) : ''; if (empty($word) && isset($_POST['anchor'])) $word = sanitize_text_field(wp_unslash($_POST['anchor'])); if (empty($word) && isset($_POST['keyword'])) $word = sanitize_text_field(wp_unslash($_POST['keyword'])); if (!$donor_id || !$target_id || empty($word)) { wp_send_json_error('Missing parameters. Received: ' . wp_json_encode($_POST)); } $result = self::remove_surgical_donor($target_id, $donor_id, $word); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } wp_send_json_success(); } public function add_plugin_admin_menu() { add_menu_page( __('LinkMesh', 'ailinkmaster-internal-auto-manager'), __('LinkMesh', 'ailinkmaster-internal-auto-manager'), 'manage_options', $this->plugin_name, array($this, 'display_plugin_setup_page'), 'dashicons-admin-links', 90 ); add_submenu_page( $this->plugin_name, __('Dashboard', 'ailinkmaster-internal-auto-manager'), __('Dashboard', 'ailinkmaster-internal-auto-manager'), 'manage_options', $this->plugin_name, array($this, 'display_plugin_setup_page') ); add_submenu_page( $this->plugin_name, __('Review Queue', 'ailinkmaster-internal-auto-manager'), __('Review Queue', 'ailinkmaster-internal-auto-manager'), 'manage_options', $this->plugin_name . '-review-queue', array($this, 'display_plugin_setup_page') ); add_submenu_page( $this->plugin_name, __('FAQ / Help', 'ailinkmaster-internal-auto-manager'), __('FAQ / Help', 'ailinkmaster-internal-auto-manager'), 'manage_options', $this->plugin_name . '-faq', array($this, 'display_faq_page') ); add_submenu_page( $this->plugin_name, __('llms.txt', 'ailinkmaster-internal-auto-manager'), __('llms.txt', 'ailinkmaster-internal-auto-manager'), 'manage_options', $this->plugin_name . '-llms-txt', array($this, 'display_llms_txt_page') ); // URL Migrator submenu page. add_submenu_page( $this->plugin_name, __('URL Migrator', 'ailinkmaster-internal-auto-manager'), __('URL Migrator', 'ailinkmaster-internal-auto-manager'), 'manage_options', $this->plugin_name . '-url-migrator', array($this, 'display_url_migrator_page') ); } /** * Display the URL Migrator admin page. */ public function display_url_migrator_page() { if (!current_user_can('manage_options')) { return; } if (!class_exists('AILM_URL_Migrator_Admin')) { require_once plugin_dir_path(__FILE__) . '../modules/url-migrator/class-ailm-url-migrator-admin.php'; } ?>
' . esc_html__('None', 'ailinkmaster-internal-auto-manager') . ''; } else { $cat_strings = []; foreach ($sim_data['top_cats'] as $cat => $count) { $cat_strings[] = esc_html($cat) . ' (' . number_format_i18n($count) . ')'; } echo esc_html(implode(', ', $cat_strings)); } ?>