// AJAX 4: ПАКЕТНЫЙ ОБРАБОТЧИК ТОТАЛЬНОГО АУДИТА СЕМАНТИКИ (ВЕЧНЫЙ ДВУХСТОРОННИЙ РУБИЛЬНИК) add_action( 'wp_ajax_milordk_run_batch_audit', 'milordk_run_batch_audit_callback' ); function milordk_run_batch_audit_callback() { if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Нет прав' ); $offset = isset($_POST['offset']) ? (int)$_POST['offset'] : 0; $step = isset($_POST['step']) ? (int)$_POST['step'] : 5; // Вытаскиваем строго определенную пачку опубликованных постов $batch_posts = get_posts(array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => $step, 'offset' => $offset, 'orderby' => 'ID', 'order' => 'ASC' )); $response_data = array('posts' => array()); if ( empty($batch_posts) ) wp_send_json_success($response_data); $lsi_option = get_option( '_milordk_seo_lsi_clusters', '' ); $lines = explode("\n", str_replace("\r", "", $lsi_option)); $punctuation = array('—', '–', '-', '.', ',', ':', ';', '!', '?', '(', ')', '[', ']', '«', '»', '“', '”', '`', '"', "'", '*', '•'); // КАРТА СЛОВАРЯ ЧЕЛОВЕЧЕСКИХ ТЕМ ДЛЯ ДАШБОРДА $theme_human_map = array( 'реализова' => 'Бизнес и Реализация', 'дает' => 'Полезные Кейсы', 'мужчи' => 'Юмор и Психология', 'делегац' => 'Политика и Делегации', 'прилетают' => 'Международные отношения', 'верт' => 'Техно-Обзоры', 'гру' => 'Общество и Тренды', 'конкур' => 'Конкурсы и Мероприятия', 'турц' => 'Путешествия и Туризм', 'поезд' => 'Заметки и Поездки', 'проб' => 'Городская среда', 'получит' => 'Региональные Новости', 'мин' => 'Осенние зарисовки', 'камерт' => 'Культура и Юмор', 'девоч' => 'Общество (Дети)', 'сир' => 'Геополитика', 'дуров' => 'Telegram и РКН', 'перей' => 'Экономика' ); foreach ( $batch_posts as $post ) { // Очищаем контент текущей статьи через наш глобальный фильтр из tfidf-engine if ( function_exists('milordk_seo_clean_text') ) { $text_clean = milordk_seo_clean_text( $post->post_content ); } else { $raw_content = html_entity_decode($post->post_content, ENT_QUOTES, 'UTF-8'); $text_clean = preg_replace('//s', '', $raw_content); $text_clean = mb_strtolower( strip_tags( strip_shortcodes( $text_clean ) ) ); } // Считаем честные слова для ассистента Rank Math $word_count = count(explode(' ', $text_clean)); if ( ! function_exists('milordk_seo_clean_text') ) { $text_clean = str_replace( $punctuation, ' ', $text_clean ); $text_clean = preg_replace('/\s+/u', ' ', trim($text_clean)); } $best_cluster_name = ''; $max_matches = 0; // Стартуем строго с 0 для честной отсечки вакуума $best_missing_words = array(); $coverage_pct = 0; // Перебираем LSI-кластеры foreach ( $lines as $line ) { if ( empty(trim($line)) ) continue; $keywords = array_map('trim', explode(',', $line)); if ( empty($keywords) || !isset($keywords) ) continue; // Жесткая изоляция: trim() полностью удален, работаем со строкой из массива $raw_theme_root = mb_strtolower($keywords); if ( empty($raw_theme_root) ) continue; $present_count = 0; $missing_words = array(); foreach ( $keywords as $word ) { $word_trimmed = trim($word); if ( empty($word_trimmed) ) continue; $word_lower = mb_strtolower($word_trimmed); $word_lower = str_replace( $punctuation, ' ', $word_lower ); $word_lower = preg_replace('/\s+/u', ' ', trim($word_lower)); if ( empty($word_lower) ) continue; $is_present = true; $sub_words = explode(' ', $word_lower); foreach ( $sub_words as $sub_w ) { if ( empty($sub_w) || mb_strlen($sub_w) < 3 ) continue; $make_stem = (preg_match('/[a-z]/', $sub_w) || mb_strlen($sub_w) <= 4) ? $sub_w : mb_substr($sub_w, 0, -2); if ( strpos($text_clean, $make_stem) === false ) { $is_present = false; break; } } if ( $is_present ) { $present_count++; } else { $missing_words[] = $word_trimmed; } } // Перезаписываем лидера строго при превышении порога в 3 слова if ( $present_count > $max_matches && $present_count >= 3 ) { $max_matches = $present_count; $best_cluster_name = isset($theme_human_map[$raw_theme_root]) ? $theme_human_map[$raw_theme_root] : mb_convert_case($raw_theme_root, MB_CASE_TITLE, "UTF-8"); $best_missing_words = $missing_words; $coverage_pct = $present_count; } } // Если совпадений меньше порога, отправляем в вакуум if ( $max_matches === 0 ) { $best_cluster_name = 'Без темы (Вакуум)'; $coverage_pct = 0; $best_missing_words = array(); } // Честная поштучная верстка баджей if ($coverage_pct === 0) { $badge_color = '#94a3b8'; $badge_bg = '#f8fafc'; $badge_text = '0 шт'; } elseif ($coverage_pct >= 6) { $badge_color = '#16a34a'; $badge_bg = '#f0fdf4'; $badge_text = $coverage_pct . ' шт'; } elseif ($coverage_pct >= 3) { $badge_color = '#d97706'; $badge_bg = '#fffbeb'; $badge_text = $coverage_pct . ' шт'; } else { $badge_color = '#dc2626'; $badge_bg = '#fef2f2'; $badge_text = $coverage_pct . ' шт'; } $coverage_html = ''.$badge_text.''; if ($max_matches === 0) { $missing_str = 'Нет кластера'; } else { $missing_str = !empty($best_missing_words) ? implode(', ', array_slice($best_missing_words, 0, 3)) : 'Раскрыта полностью!'; } $word_style = ($word_count < 300) ? 'color:#dc2626; font-weight:600; background:#fef2f2; padding:2px 6px; border-radius:4px;' : 'color:#1e293b;'; $word_count_html = ''.$word_count.'' . ($word_count < 300 ? ' ⚠️' : ''); // ПРОВЕРКА СТАТУСА РОБОТОВ RANK MATH $robots = get_post_meta( $post->ID, 'rank_math_robots', true ); $is_noindex = ( is_array( $robots ) && in_array( 'noindex', $robots ) ); // Передаем статус в JS для инкремента информеров $seo_status = $is_noindex ? 'noindex_ok' : 'indexed_ok'; // Изолированные инлайн-стили кнопок от шаблонов WordPress $btn_style = 'display: inline-block; width: 120px; min-width: 120px; height: 28px; line-height: 26px; padding: 0 4px; border-radius: 4px; font-size: 10px; font-weight: 700; cursor: pointer; text-align: center; white-space: nowrap; box-sizing: border-box; transition: all 0.2s ease;'; if ( $is_noindex ) { // Статья закрыта. Показываем статус и синюю кнопку возврата в индекс $noindex_btn_html = '