Files
cf7-spam-to-telegram/includes/class-message-formatter.php
T
2026-04-21 16:20:28 +05:00

173 lines
6.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Cf7stg;
final class MessageFormatter
{
private const TG_MAX_LEN = 4096;
private const LABEL_MAP = [
'client' => ['emoji' => '🟢', 'text' => 'похоже клиент'],
'unclear' => ['emoji' => '🟡', 'text' => 'неясно'],
'spam' => ['emoji' => '🔴', 'text' => 'похоже спам'],
'drop' => ['emoji' => '⛔', 'text' => 'авто-дроп'],
];
/**
* @param array{
* fields:array<string,mixed>,
* classification:array{score:int,label:string,reasons:array},
* site_title:string,
* form_title:string,
* submitted_at:string,
* reason:string,
* ip:string,
* user_agent:string,
* is_retro:bool
* } $ctx
*
* @return array{text:string,reply_markup:array|null}
*/
public static function build(array $ctx): array
{
$fields = $ctx['fields'];
$class = $ctx['classification'];
$phone = PhoneParser::parse(self::flatten_fields_for_phone($fields));
$name = self::pick($fields, ['your-name', 'name', 'имя', 'fio']);
if ($name === '') $name = FieldDetector::find_name($fields);
$email = self::pick($fields, ['your-email', 'email', 'e-mail', 'mail']);
if ($email === '') $email = FieldDetector::find_email($fields);
$message = self::pick($fields, ['your-message', 'message', 'comment', 'сообщение', 'вопрос']);
$lines = [];
$label_info = self::LABEL_MAP[$class['label']] ?? self::LABEL_MAP['unclear'];
$header = ($ctx['is_retro'] ? '📂 Ретроспектива · ' : '')
. $label_info['emoji'] . ' ' . $label_info['text'];
$lines[] = $header;
$lines[] = '🌐 ' . esc_html($ctx['site_title']) . ' · форма «' . esc_html($ctx['form_title']) . '»';
$lines[] = '🗓 ' . esc_html($ctx['submitted_at']);
$lines[] = '';
$lines[] = '📱 ' . ($phone !== null ? esc_html($phone['pretty']) : '(не указан)');
$lines[] = '👤 ' . ($name !== '' ? esc_html($name) : '(не указано)');
$lines[] = '📧 ' . ($email !== '' ? esc_html($email) : '(не указан)');
$lines[] = '';
$lines[] = '💬 ' . ($message !== '' ? esc_html($message) : '(пусто)');
$lines[] = '';
$lines[] = '⚙️ Попало в спам: ' . esc_html($ctx['reason']);
$lines[] = '📊 Классификация: ' . self::format_reasons($class['reasons']);
$meta_bits = [];
if ($ctx['ip'] !== '') $meta_bits[] = 'IP ' . esc_html($ctx['ip']);
if ($ctx['user_agent'] !== '') $meta_bits[] = 'UA ' . esc_html(self::shorten_ua($ctx['user_agent']));
if ($meta_bits) $lines[] = '🌍 ' . implode(' · ', $meta_bits);
$utms = self::extract_utms($fields);
if ($utms !== '') $lines[] = '🏷 ' . $utms;
$other = self::extract_other_fields($fields);
if ($other !== '') $lines[] = '📋 ' . $other;
$text = implode("\n", $lines);
$text = self::truncate($text);
// Telegram Bot API rejects tel: URLs in inline_keyboard with HTTP 400
// ("Wrong port number specified"), so no inline button is emitted.
// The phone number is already shown in the message body (line '📱 ...')
// and mobile Telegram clients make such numbers tappable automatically.
return ['text' => $text, 'reply_markup' => null];
}
private static function pick(array $fields, array $keys): string
{
foreach ($keys as $k) {
if (isset($fields[$k])) {
$v = $fields[$k];
if (is_array($v)) $v = implode(' ', $v);
return trim((string)$v);
}
}
return '';
}
private static function flatten_fields_for_phone(array $fields): string
{
$out = [];
foreach ($fields as $k => $v) {
if (is_array($v)) $v = implode(' ', array_map('strval', $v));
$out[] = (string)$v;
}
return implode("\n", $out);
}
private static function format_reasons(array $reasons): string
{
if (!$reasons) return '—';
$parts = [];
foreach ($reasons as $r) {
// Drop reasons use sign='×' weight=0 as a sentinel — render text only
// to avoid the meaningless "×0 hard-rule: ..." prefix.
if (($r['sign'] ?? '') === '×' && (int)($r['weight'] ?? 0) === 0) {
$parts[] = (string)$r['text'];
continue;
}
$parts[] = $r['sign'] . $r['weight'] . ' ' . $r['text'];
}
return esc_html(implode(', ', $parts));
}
private static function shorten_ua(string $ua): string
{
$ua = preg_replace('/\s+/', ' ', $ua) ?? $ua;
if (mb_strlen($ua) > 80) $ua = mb_substr($ua, 0, 80) . '…';
return $ua;
}
private static function extract_utms(array $fields): string
{
$keys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
$parts = [];
foreach ($keys as $k) {
if (!empty($fields[$k])) {
$v = is_array($fields[$k]) ? implode(' ', $fields[$k]) : (string)$fields[$k];
$parts[] = esc_html($k) . '=' . esc_html($v);
}
}
return implode(' · ', $parts);
}
private static function extract_other_fields(array $fields): string
{
$known = [
'your-name', 'name', 'имя', 'fio',
'your-email', 'email', 'e-mail', 'mail',
'your-tel', 'tel', 'phone', 'телефон',
'your-message', 'message', 'comment', 'сообщение', 'вопрос',
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
'_wpcf7', '_wpcf7_version', '_wpcf7_locale', '_wpcf7_unit_tag',
'_wpcf7_container_post', '_wpcf7_posted_data_hash', 'g-recaptcha-response',
];
$parts = [];
foreach ($fields as $k => $v) {
if (in_array($k, $known, true)) continue;
if (strpos($k, 'wpcf7') === 0) continue;
if (is_array($v)) $v = implode(', ', array_map('strval', $v));
$v = trim((string)$v);
if ($v === '') continue;
$parts[] = esc_html($k) . ': ' . esc_html($v);
}
return implode(' · ', $parts);
}
private static function truncate(string $text): string
{
if (mb_strlen($text) <= self::TG_MAX_LEN) return $text;
$suffix = "\n…[обрезано]";
$cut = self::TG_MAX_LEN - mb_strlen($suffix);
return mb_substr($text, 0, $cut) . $suffix;
}
}