Ogranicz wyszukiwanie do znaków łacińskich

9

Chciałbym ograniczyć wyszukiwanie do znaków używanych w języku angielskim + cyfry. Powodem jest to, że patrząc na najwolniejsze zapytania w dzienniku mysql, które większość znalazłem, pochodzą z wyszukiwań znaków arabskich, rosyjskich i chińskich, więc chciałbym je pominąć i zamiast tego wyświetlić komunikat o błędzie.

Michael Rogers
źródło
Jeśli wyszczególnisz, w jaki sposób chcesz wyświetlać swój błąd, zmodyfikuję moją odpowiedź, aby go uwzględnić
bosco
Chciałbym, aby błąd pojawił się na stronie wyszukiwania, poniżej lub powyżej formularza wyszukiwania.
Michael Rogers

Odpowiedzi:

10

To rozwiązanie filtruje ciągi wyszukiwania, stosując wyrażenie regularne, które pasuje tylko do znaków ze skryptów Unicode Common i Latin.


Dopasowywanie znaków łacińskich do wyrażeń regularnych

Właśnie przeleciał mi umysł w Stack Overflow . Jak się okazuje, wyrażenia regularne mają mechanizm dopasowywania całych kategorii Unicode, w tym wartości określających całe „skrypty” Unicode , z których każdy odpowiada grupom znaków używanych w różnych systemach pisania.

Odbywa się to za pomocą \pmeta-znaku, po którym następuje nawias identyfikujący kategorię Unicode w nawiasach klamrowych - więc [\p{Common}\p{Latin}]pasuje do pojedynczego znaku w skrypcie łacińskim lub wspólnym - obejmuje to znaki interpunkcyjne, cyfry i różne symbole.

Jak wskazuje @Paul „Sparrow Hawk” Biron , u flaga modyfikatora wzorca powinna być ustawiona na końcu wyrażenia regularnego, aby funkcje PCRE PHP mogły traktować ciąg znaków jako UTF-8kodowany w Unicode.

Wszystko razem więc wzór

/^[\p{Latin}\p{Common}]+$/u

dopasuje cały ciąg złożony z jednego lub więcej znaków w skryptach Latin i Common Unicode.


Filtrowanie wyszukiwanego ciągu

Dobrym miejscem do przechwytywania ciąg wyszukiwania jest działanie jak pożary bezpośrednio przed WordPress wykonuje zapytanie. Z większą ostrożnością można to również osiągnąć za pomocą filtra .pre_get_postsrequest

function wpse261038_validate_search_characters( $query ) {
  // Leave admin, non-main query, and non-search queries alone
  if( is_admin() || !$query->is_main_query() || !$query->is_seach() )
    return;

  // Check if the search string contains only Latin/Common Unicode characters
  $match_result = preg_match( '/^[\p{Latin}\p{Common}]+$/u', $query->get( 's' ) );

  // If the search string only contains Latin/Common characters, let it continue
  if( 1 === $match_result )
    return;

  // If execution reaches this point, the search string contains non-Latin characters
  //TODO: Handle non-Latin search strings
  //TODO: Set up logic to display error message
}

add_action( 'pre_get_posts', 'wpse261038_validate_search_characters' );

Reagowanie na niedozwolone wyszukiwania

Po ustaleniu, że ciąg wyszukiwania zawiera znaki spoza alfabetu łacińskiego, możesz użyć go WP_Query::set()do zmodyfikowania zapytania poprzez zmianę jego nazwanych zmiennych zapytania - wpływając w ten sposób na zapytanie SQL, które WordPress następnie tworzy i wykonuje.

Najbardziej odpowiednie zmienne zapytania są prawdopodobnie następujące:

  • sjest zmienną zapytania odpowiadającą wyszukiwanemu ciągowi. Ustawienie go na nulllub pusty ciąg znaków ( '') spowoduje, że WordPress przestanie traktować zapytanie jako wyszukiwanie - często powoduje to, że szablon archiwum wyświetla wszystkie posty lub stronę główną witryny, w zależności od wartości innych zapytania zmienne. Ustawienie go na pojedynczą spację ( ' ') spowoduje jednak, że WordPress rozpozna go jako wyszukiwanie, a zatem spróbuje wyświetlić search.phpszablon.
  • page_id może zostać wykorzystany do przekierowania użytkownika na wybraną stronę.
  • post__inmoże ograniczyć zapytanie do określonego wyboru postów. Ustawienie go na tablicę z niemożliwym identyfikatorem postu może służyć jako miara zapewniająca, że ​​zapytanie nie zwróci absolutnie niczego .

Mając powyższe na uwadze, możesz wykonać następujące czynności, aby odpowiedzieć na złe wyszukiwanie, ładując search.phpszablon bez wyników:

function wpse261038_validate_search_characters( $query ) {
  // Leave admin, non-main query, and non-search queries alone
  if( is_admin() || !$query->is_main_query() || !$query->is_seach() )
    return;

  // Check if the search string contains only Latin/Common Unicode characters
  $match_result = preg_match( '/^[\p{Latin}\p{Common}]+$/u', $query->get( 's' ) );

  // If the search string only contains Latin/Common characters, let it continue
  if( 1 === $match_result )
    return;

  $query->set( 's', ' ' ); // Replace the non-latin search with an empty one
  $query->set( 'post__in', array(0) ); // Make sure no post is ever returned

  //TODO: Set up logic to display error message
}

add_action( 'pre_get_posts', 'wpse261038_validate_search_characters' );

Wyświetlanie błędu

Sposób, w jaki faktycznie wyświetlasz komunikat o błędzie, zależy w dużej mierze od Twojej aplikacji i możliwości motywu - można to zrobić na wiele sposobów. Jeśli motyw wywołuje się get_search_form()w szablonie wyszukiwania, najłatwiejszym rozwiązaniem jest prawdopodobnie użycie haka do pre_get_search_formdziałania, aby wyświetlić błąd bezpośrednio nad formularzem wyszukiwania:

function wpse261038_validate_search_characters( $query ) {
  // Leave admin, non-main query, and non-search queries alone
  if( is_admin() || !$query->is_main_query() || !$query->is_seach() )
    return;

  // Check if the search string contains only Latin/Common Unicode characters
  $match_result = preg_match( '/^[\p{Latin}\p{Common}]+$/u', $query->get( 's' ) );

  // If the search string only contains Latin/Common characters, let it continue
  if( 1 === $match_result )
    return;

  $query->set( 's', ' ' ); // Replace the non-latin search with an empty one
  $query->set( 'post__in', array(0) ); // Make sure no post is ever returned

  add_action( 'pre_get_search_form', 'wpse261038_display_search_error' );
}

add_action( 'pre_get_posts', 'wpse261038_validate_search_characters' );

function wpse261038_display_search_error() {
  echo '<div class="notice notice-error"><p>Your search could not be completed as it contains characters from non-Latin alphabets.<p></div>';
}

Niektóre inne możliwości wyświetlania komunikatu o błędzie obejmują:

  • Jeśli Twoja strona korzysta z JavaScript, który może wyświetlać komunikaty „flash” lub „modalne” (lub dodajesz takie możliwości samodzielnie), dodaj logikę do wyświetlania komunikatów przy ładowaniu strony, gdy ustawiona jest określona zmienna, a następnie dodaj wp_enqueue_scripthaczyk z $prioritywiększym niż ten, który kolejkuje ten JavaScript, i użyj, wp_localize_script()aby ustawić tę zmienną tak, aby zawierała komunikat o błędzie.
  • Służy wp_redirect()do wysyłania użytkownika na wybrany adres URL (ta metoda wymaga dodatkowego ładowania strony).
  • Ustaw zmienną PHP lub wywołaj metodę, która poinformuje twoją kompozycję / wtyczkę o błędzie, tak aby wyświetlał ją w razie potrzeby.
  • Ustaw szmienną zapytania na ''zamiast ' 'i użyj page_idzamiast post__in, aby zwrócić wybraną stronę.
  • Użyj loop_starthaka, aby wstrzyknąć fałszywy WP_Postobiekt zawierający błąd do wyników zapytania - jest to zdecydowanie brzydki hack i może nie wyglądać dobrze z określonym motywem, ale ma potencjalnie pożądany efekt uboczny polegający na pomijaniu komunikatu „Brak wyników”.
  • Użyj template_includehaka filtru, aby zamienić szablon wyszukiwania na niestandardowy w motywie lub wtyczce, który wyświetla błąd.

Bez zbadania omawianego tematu trudno jest ustalić, którą trasę należy obrać.

bosco
źródło
2

Zrobiłbyś to, wprowadzając funkcję sprawdzania poprawności w PHP, aby przetestować dane wejściowe względem wyrażenia regularnego, takiego jak ^[a-zA-Z0-9,.!?' ]*

Więc wyglądałoby to tak:

if ( preg_match( "^[a-zA-Z0-9,.!?'" ]*", {search variable} ) ) {
   // Success
} else {
   // Fail
}

RexEx użyłem dla wszystkich znaków A-Z, a-z, 0-9jak również ,, ., !, ?, ', ", i (spacja).

Cedon
źródło
2

EDYCJA: To rozwiązanie nie jest zalecane

Moje rozwiązanie poniżej to hack, który nadużywa funkcji mbstring PHP, próbując magicznie rozróżnić alfabety, patrząc na układ bajtów składających się na ciąg. To naprawdę zły pomysł i bardzo podatny na błędy .

Zobacz moją drugą odpowiedź na znacznie prostsze i bardziej niezawodne rozwiązanie.


Jednym ze sposobów zapobiegania wyszukiwaniu przy użyciu alfabetów innych niż łacińskie jest użycie funkcji PHP,mb_detect_encoding() aby sprawdzić, czy szukany ciąg znaków jest zgodny z jednym z niestandardowego wyboru kodowania znaków. Dobrym miejscem do tego jest działanie , jak pożary tuż przed wykonaniem zapytania.pre_get_posts

To, co faktycznie robisz po ustaleniu, że używasz nieprawidłowego kodowania, jest tak naprawdę specyficzne dla aplikacji. Tutaj ustawiłem zapytanie do pojedynczej spacji, aby upewnić się, że WordPress nadal interpretuje zapytanie jako wyszukiwanie, a tym samym nadal ładuje search.phpszablon (i nie kieruje użytkownika do pierwszej strony, jak to się dzieje, gdy ciąg wyszukiwania jest pusty ciąg). Podejmuję również dodatkowe środki ostrożności , 'post__in'aby ustawić tablicę z niemożliwym identyfikatorem postu, aby mieć pewność, że absolutnie nic nie zostanie zwrócone .

Alternatywnie możesz rozważyć ustawienie ciągu wyszukiwania na nulli ustawienie page_id, aby skierować użytkownika do strony z niestandardowym komunikatem o błędzie.

function wpse261038_validate_search_query_encoding( $query ) {
  $valid_encodings = array( 'Windows-1252' );

  // Ignore admin, non-main query, and non-search queries
  if( is_admin() || !$query->is_main_query() || !$query->is_seach() )
    return;

  // Retrieve the encoding of the search string (if it's one listed in $valid_encodings)
  $search_encoding = mb_detect_encoding( $query->get( 's' ), $valid_encodings, true );

  // If the search encoding is one in $valid_encodings, leave the query as-is
  if( in_array( $search_encoding, $valid_encodings ) )
    return;

  // If it wasn't, sabotage the search query
  $query->set( 's', ' ' );
  $query->set( 'post__in', array(0) );

  // Set up your error message logic here somehow, perhaps one of the following:
  // - Add a template_include filter to load a custom error template
  // - Add a wp_enqueue_scripts hook with a greater priority than your theme/plugin's, and
  //     use wp_localize_script() in the hook to pass an error message for your JavaScript
  //     to display
  // - Perform a wp_redirect() to send the user to the URL of your choice
  // - Set a variable with an error message which your theme or plugin can display
}

add_action( 'pre_get_posts', 'wpse261038_validate_search_query_encoding' );

Wybór kodowania

Napisałem test zasięgu porównujący niektóre fikcyjne łańcuchy w różnych alfabetach ze wszystkimi domyślnymi kodowaniami obsługiwanymi przez PHP . Nie jest idealny pod żadnym względem (nie mam pojęcia, jak realistyczne są moje smoczkie struny i wydaje się, że dusi się po japońskim wykryciu), ale jest nieco przydatny do określania kandydatów. Możesz to zobaczyć tutaj w akcji .

Po zbadaniu potencjalnych kodowań znaków oznaczonych przez ten test wydaje się, że Windows-1252jest to idealny wybór dla twoich potrzeb, obejmujący alfabet łaciński, a także akcenty dla popularnych języków łacińskich.

Wybór ISO-8859zestawów znaków powinien być kolejnym wykonalnym wyborem, jednak z powodów, dla których nie mogę się obejść, mb_funkcje nie wydają się rozróżniać ISO-8859różnych zestawów znaków, mimo że wymieniają je jako osobne kodowania.

Aby zezwolić na niektóre inne popularne znaki, możesz również rozważyć dodanie HTML-ENTITIES.

bosco
źródło
Wydaje się, że mechanizm, za pomocą którego działają funkcje mbstring, nieISO-8859 jest w stanie odróżnić kodowania .
bosco
Dowiedziałem się, że mój test połączony jest niedokładny i wprowadzający w błąd - funkcje mbstring działają w oparciu o sekwencje bajtów, więc chociaż kodowanie może wykorzystywać sekwencje bajtów, które mogłyby obsługiwać wymienione alfabety, tak naprawdę nie oznacza to, że kodowanie faktycznie obsługuje te postacie. Dlatego filtrowanie alfabetów ciągów przez testowanie kodowań nie jest niezawodnym mechanizmem . Zamiast tego rozważ moją drugą odpowiedź.
bosco
1

Jak próbowałem wyjaśnić @MichaelRogers, gdy kilka dni temu napisał podobne pytanie, znajomość zestawu znaków (lub skryptu) użytego w ciągu nie jest wystarczająca do wykrycia języka tego ciągu.

Tak więc, podczas gdy sposób szczegółowy przez @bosco będzie usunąć rosyjsku itd łańcuchy (z poprawkami 2 poniżej), będzie NIE ograniczyć swoje wyszukiwania na angielski.

Aby to zobaczyć, spróbuj:

$strings = array (
    'I\'m sorry',                   // English
    'Je suis désolé',               // French
    'Es tut mir Leid',              // German
    'Lorem ipsum dolor sit amet',   // Lorem ipsum
    'أنا سعيد',                     // Arabic
    'я счастлив',                   // Russian
    '我很高兴',                     // Chinese (Simplified)
    '我很高興',                     // Chinese (Traditional)
    ) ;
foreach ($strings as $s) {
    if (preg_match ('/^[\p{Latin}\p{Common}]+$/u', $s) === 1) {
        echo "$s: matches latin+common\n" ;
        }
    else {
        echo "$s: does not match latin+common\n" ;
        }
    }

[ Uwaga: 2 wyżej wymienione poprawki do tego, co podał @bosco to:

  1. wzorzec jest zawarty w łańcuchu (wymagane do poprawnej składni PHP)
  2. dodano /umodyfikator (wymagany do traktowania wzorca i tematu jako kodowanego w UTF-8, patrz PHP: Modyfikatory wzorca regex ]

który wytworzy:

I'm sorry: matches latin+common
Je suis désolé: matches latin+common
Es tut mir Leid: matches latin+common
Lorem ipsum dolor sit amet: matches latin+common
أنا سعيد: does not match latin+common
я счастлив: does not match latin+common
我很高兴: does not match latin+common
我很高興: does not match latin+common

[ uwaga: mówię po angielsku, francusku i trochę po niemiecku (i trochę Lorem ipsum :-), ale polegałem na Tłumaczu Google dla języka arabskiego, rosyjskiego i chińskiego]

Jak widać, poleganie na sprawdzeniu skryptu łacińskiego NIE gwarantuje, że znasz angielski.

StackOverflow ma wiele wątków (np. Wykryj język z łańcucha znaków w PHP ), które dostarczają więcej informacji na ten temat.

Paul „Sparrow Hawk” Biron
źródło
Pozwólcie, że zostawię przyjazną, pedantyczną notatkę: Lorem ipsum nie jest językiem, mówiąc, że ktoś mówi „lorem ipsum” jest jak mówienie, że ktoś mówi „cześć światu” :) Język Lorem ipsum to stara łacina i nie, „lorem ipsum ” nie oznacza „ hello world ” :) W rzeczywistości jest literówką dla „ dolorem ipsum ”, co oznacza „ sam ból ” lub coś w tym rodzaju.
gmazzap
@gmazzap Wiem, to był żart (stąd „:-)”). I zawarte Lorem Ipsum wzmocnienie tego stopnia, że sprawdzanie skrypt ma nie przetestować język.
Paul „Sparrow Hawk” Biron
i jeszcze bardziej pedantyczny, jak mówi na lipsum.com , „Lorem Ipsum pochodzi z sekcji 1.10.32 i 1.10.33„ de Finibus Bonorum et Malorum ”(The Extremes of Good and Evil) Cycerona, napisanej w 45 PNE." Ale ma też różne „losowości”, co czyni go nonsensownym dla rodzimego użytkownika języka łacińskiego, więc nie jest to tak naprawdę „stary łacina”, ale całkowicie wymyślony „język”.
Paul „Sparrow Hawk” Biron
Ach, niezłe łapania @ Paul'SparrowHawk'Biron! Zaktualizuję moją odpowiedź, aby naprawić wyrażenie regularne i wyjaśnię, co dokładnie robi moje rozwiązanie.
bosco
1
Nie obchodzi mnie, czy osoba ta pisze po hiszpańsku. Nie musi to być wyłącznie język angielski. Powiedziałem, że znaki są używane w języku angielskim, więc od A do Z (wielkimi literami i bez wielkich liter) + cyfry. Jeśli inne języki używają tych samych znaków, to dobrze. To, czego nie chcę dopuścić, to cyrylica, kanji, litery arabskie (nie znam nazwy) i wszystko, co nie jest Aa-Zz + 0-9. Język nie ma znaczenia.
Michael Rogers