diff --git a/includes/class-classifier.php b/includes/class-classifier.php index 9686d32..4eaaf13 100644 --- a/includes/class-classifier.php +++ b/includes/class-classifier.php @@ -243,4 +243,23 @@ final class Classifier } return false; } + + /** + * Checks whether the form's name field contains 1-3 ASCII alphanumeric characters + * (typical bot pattern: "OB", "MU", "A1B"). Used by drop hard-rule. + * + * Looks first at canonical CF7 keys (your-name, name, имя, fio), then falls back to + * the value-based heuristic in FieldDetector::find_name(). Returns false if no name + * field is present (i.e. forms without a name field are immune to this rule). + * + * @param array $fields CF7 posted_data + */ + public static function has_short_latin_name(array $fields): bool + { + $name = self::pick_field($fields, ['your-name', 'name', 'имя', 'fio']); + if ($name === '') $name = FieldDetector::find_name($fields); + $name = trim($name); + if ($name === '') return false; + return (bool)preg_match('/^[A-Za-z0-9]{1,3}$/', $name); + } } diff --git a/tests/ClassifierDropTest.php b/tests/ClassifierDropTest.php index 067c510..6b91278 100644 --- a/tests/ClassifierDropTest.php +++ b/tests/ClassifierDropTest.php @@ -43,4 +43,42 @@ final class ClassifierDropTest extends Cf7stgTestCase $fields = ['msg' => 'try alice@in or bob@example.com']; self::assertTrue(Classifier::has_valid_email_anywhere($fields)); } + + public function test_has_short_latin_name_true_for_two_letters(): void + { + $fields = ['your-name' => 'OB']; + self::assertTrue(Classifier::has_short_latin_name($fields)); + } + + public function test_has_short_latin_name_true_for_three_alnum(): void + { + $fields = ['name' => 'A1B']; + self::assertTrue(Classifier::has_short_latin_name($fields)); + } + + public function test_has_short_latin_name_false_for_long_name(): void + { + $fields = ['your-name' => 'Melissa']; + self::assertFalse(Classifier::has_short_latin_name($fields)); + } + + public function test_has_short_latin_name_false_for_cyrillic(): void + { + $fields = ['your-name' => 'Иван']; + self::assertFalse(Classifier::has_short_latin_name($fields)); + } + + public function test_has_short_latin_name_false_when_no_name_field(): void + { + // Только телефон, нет name-полей. FieldDetector::find_name пропустит +79991234567 (digits) + $fields = ['tel-1' => '+79991234567']; + self::assertFalse(Classifier::has_short_latin_name($fields)); + } + + public function test_has_short_latin_name_picks_via_field_detector(): void + { + // text-211 не в списке известных name-ключей, FieldDetector найдёт 'XY' (no digits, len=2) + $fields = ['text-211' => 'XY', 'text-212' => '+79991234567']; + self::assertTrue(Classifier::has_short_latin_name($fields)); + } }