]*?>.*?@si', '', $html ); // Удаляем HTML-комментарии (разметка Gutenberg) $html = preg_replace( '@@s', '', $html ); return $html; } /** * Собирает гибкий регулярный паттерн из искомой фразы. */ public static function build_elastic_pattern( $phrase ) { // Разбиваем фразу на слова, игнорируя всё, кроме букв и цифр (поддержка Юникода) $words = preg_split('/[^\p{L}\p{N}]+/u', wp_unslash($phrase), -1, PREG_SPLIT_NO_EMPTY); if ( empty( $words ) ) { return false; } // Прослойка: разрешает HTML-теги, сущности ( ), пробелы и любую пунктуацию/спецсимволы $separator = '(?:<[^>]+>|&\w+;|&#\d+;|[^\p{L}\p{N}])+'; $escaped_words = array_map(function($word) { return preg_quote($word, '/'); }, $words); $pattern = '/' . implode($separator, $escaped_words) . '/iu'; return $pattern; } /** * Ищет фразу в сыром HTML и возвращает её вместе с окружающим контекстом (очищенным для ИИ). */ public static function find_context( $html, $phrase, $context_length = 200 ) { $clean_html = self::pre_clean_html( $html ); $pattern = self::build_elastic_pattern( $phrase ); if ( ! $pattern ) { return false; } if ( preg_match( $pattern, $clean_html, $matches, PREG_OFFSET_CAPTURE ) ) { $match_text = $matches[0][0]; $match_pos = $matches[0][1]; // Захватываем найденный кусок + по 200 символов слева и справа для контекста $start = max( 0, $match_pos - $context_length ); $length = strlen( $match_text ) + ( $context_length * 2 ); $context_raw = substr( $clean_html, $start, $length ); // Add spaces after block tags to avoid word concatenation before stripping tags $context_raw = preg_replace('/<\/(p|li|ul|ol|blockquote|h[1-6])>/i', '$0 ', $context_raw); // Очищаем результат для отправки в OpenAI (убираем теги, декодируем сущности) $context_clean = wp_strip_all_tags( $context_raw ); $context_clean = html_entity_decode( $context_clean, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); // Схлопываем множественные пробелы и переносы строк в один $context_clean = preg_replace( '/\s+/u', ' ', $context_clean ); return trim( $context_clean ); } return false; } }