Limiter la recherche aux caractères latins

9

Je voudrais limiter la recherche aux caractères utilisés sur la langue anglaise + les chiffres. La raison en est qu'en regardant les requêtes les plus lentes sur le journal mysql, je trouve que la plupart proviennent de recherches en caractères arabes, russes et chinois, donc je voudrais les ignorer et afficher un message d'erreur à la place.

Michael Rogers
la source
Si vous précisez comment vous souhaitez afficher votre erreur, je modifierai ma réponse pour l'inclure
bosco
Je souhaite que l'erreur apparaisse sur la page de recherche, en dessous ou au-dessus du formulaire de recherche.
Michael Rogers

Réponses:

10

Cette solution filtre les chaînes de recherche en appliquant une expression régulière qui ne correspond qu'aux caractères des scripts Unicode commun et latin.


Faire correspondre les caractères latins aux expressions régulières

Je venais juste d' avoir le souffle coupé à Stack Overflow . Il s'avère que les expressions régulières ont un mécanisme pour faire correspondre des catégories Unicode entières, y compris des valeurs pour spécifier des "scripts" Unicode entiers , chacun correspondant à des groupes de caractères utilisés dans différents systèmes d'écriture.

Cela se fait en utilisant le \pméta-caractère suivi d'un identifiant de catégorie Unicode entre accolades - [\p{Common}\p{Latin}]correspond donc à un seul caractère dans les scripts latin ou commun - cela inclut la ponctuation, les chiffres et les symboles divers.

Comme le souligne @Paul 'Sparrow Hawk' Biron , l' u indicateur de modificateur de modèle doit être défini à la fin de l'expression régulière afin que les fonctions PCRE de PHP traitent la chaîne en question comme UTF-8codée Unicode.

Tous ensemble alors, le motif

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

correspondra à une chaîne entière composée d'un ou plusieurs caractères dans les scripts Latin et Common Unicode.


Filtrage de la chaîne de recherche

Un bon endroit pour intercepter une chaîne de recherche est l' pre_get_postsaction car elle se déclenche immédiatement avant que WordPress n'exécute la requête. Avec plus de soin , cela pourrait également être accompli en utilisant un requestfiltre .

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' );

Répondre aux recherches non autorisées

Une fois qu'il a été déterminé qu'une chaîne de recherche contient des caractères non latins, vous pouvez utiliser WP_Query::set()afin de modifier la requête en changeant son nom de variable de requête - affectant ainsi la requête SQL que WordPress compose et exécute ensuite.

Les variables de requête les plus pertinentes sont probablement les suivantes:

  • sest la variable de requête correspondant à une chaîne de recherche. Si vous le définissez sur nullou une chaîne vide ( ''), WordPress ne traitera plus la requête comme une recherche - cela aboutit souvent à un modèle d'archive affichant toutes les publications ou la première page du site, selon les valeurs de l'autre requête vars. ' 'Cependant, si vous le définissez sur un seul espace ( ), WordPress le reconnaîtra comme une recherche et tentera donc d'afficher le search.phpmodèle.
  • page_id pourrait être utilisé pour diriger l'utilisateur vers une page spécifique de votre choix.
  • post__inpeut restreindre la requête à une sélection spécifique de publications. En le définissant sur un tableau avec un ID de publication impossible, il peut servir de mesure pour garantir que la requête ne renvoie absolument rien .

Ce qui précède à l'esprit, vous pouvez faire ce qui suit afin de répondre à une mauvaise recherche en chargeant le search.phpmodèle sans résultat:

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' );

Affichage d'une erreur

La façon dont vous affichez réellement le message d'erreur dépend fortement de votre application et des capacités de votre thème - il y a plusieurs façons de le faire. Si votre thème appelle get_search_form()dans son modèle de recherche, la solution la plus simple consiste probablement à utiliser un crochet d' pre_get_search_formaction pour afficher votre erreur immédiatement au-dessus du formulaire de recherche:

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>';
}

Voici d'autres possibilités pour afficher un message d'erreur:

  • Si votre site utilise JavaScript qui peut afficher des messages "flash" ou "modaux" (ou si vous ajoutez de telles capacités par vous-même), ajoutez-y la logique pour afficher les messages au chargement de la page lorsqu'une variable spécifique est définie, puis ajoutez un wp_enqueue_scriptcrochet avec un $priorityplus grand que celui qui met en file d'attente ce JavaScript, et utilisez wp_localize_script()pour définir cette variable pour inclure votre message d'erreur.
  • Utilisez wp_redirect()pour envoyer l'utilisateur à l'URL de votre choix (cette méthode nécessite un chargement de page supplémentaire).
  • Définissez une variable PHP ou appelez une méthode qui informera votre thème / plugin de l'erreur de sorte qu'il puisse l'afficher le cas échéant.
  • Définissez la svariable de requête à la ''place de ' 'et utilisez page_idà la place de post__inafin de retourner une page de votre choix.
  • Utilisez un loop_startcrochet pour injecter un faux WP_Postobjet contenant votre erreur dans les résultats de la requête - il s'agit certainement d'un vilain hack et peut ne pas convenir à votre thème particulier, mais il a l'effet secondaire potentiellement souhaitable de supprimer le message "Aucun résultat".
  • Utilisez un template_includecrochet de filtre pour échanger le modèle de recherche avec un modèle personnalisé dans votre thème ou plug-in qui affiche votre erreur.

Sans examiner le thème en question, il est difficile de déterminer l'itinéraire à suivre.

bosco
la source
2

Pour ce faire, mettez une fonction de validation en PHP pour tester l'entrée par rapport à une expression régulière comme ^[a-zA-Z0-9,.!?' ]*

Donc, cela ressemblerait à ceci:

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

Le RexEx j'ai utilisé pour tous les personnages A-Z, a-z, 0-9, ainsi que ,, ., !, ?, ', ", et (espace).

Cedon
la source
2

EDIT: Cette solution n'est pas recommandée

Ma solution ci-dessous est un hack qui abuse des fonctions mbstring de PHP dans une tentative de diviser magiquement les alphabets en regardant l'agencement des octets qui composent la chaîne. C'est une très mauvaise idée et très sujette aux erreurs .

Veuillez consulter mon autre réponse pour une solution beaucoup plus simple et beaucoup plus fiable.


Un moyen d'empêcher les recherches utilisant des alphabets non latins consiste à utiliser la mb_detect_encoding()fonction PHP pour voir si la chaîne de recherche est conforme à l'une d'une sélection personnalisée d'encodages de caractères. Un bon endroit pour le faire est l' une pre_get_postsaction , comme il se déclenche juste avant que la requête est exécutée.

Ce que vous faites réellement après avoir déterminé qu'une recherche utilise un encodage invalide est vraiment spécifique à l'application. Ici, j'ai défini la requête de recherche sur un seul espace pour garantir que WordPress interprète toujours la requête comme une recherche et charge donc toujours le search.phpmodèle (et ne dirige pas l'utilisateur vers la page d'accueil, comme cela se produit lorsque la chaîne de recherche est une chaîne vide). Je prends également une précaution supplémentaire de définir 'post__in'un tableau avec un ID de publication impossible afin de m'assurer que rien ne soit retourné .

Vous pouvez également envisager de définir la chaîne de recherche sur nullet de définir page_idafin de diriger l'utilisateur vers une page avec votre message d'erreur personnalisé.

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' );

Choix des encodages

J'ai écrit un test de couverture comparant certaines chaînes factices dans différents alphabets à tous les encodages par défaut pris en charge par PHP . Ce n'est parfait par aucun tronçon (je n'ai aucune idée du réalisme de mes chaînes factices, et cela semble étouffer la détection japonaise), mais c'est quelque peu utile pour déterminer les candidats. Vous pouvez le voir en action ici .

Après avoir recherché des encodages de caractères potentiels signalés par ce test, il semble que ce Windows-1252soit le choix parfait pour vos besoins, couvrant l'alphabet latin ainsi que les accents des langues latines courantes.

Une sélection des ISO-8859jeux de caractères devrait être un autre choix viable, mais pour des raisons que je ne peux pas comprendre, les mb_fonctions ne semblent pas faire la différence entre ISO-8859les différents jeux de caractères de, bien qu'ils les répertorient comme des encodages séparés.

Pour autoriser d'autres caractères communs, vous pouvez également envisager d'ajouter HTML-ENTITIES.

bosco
la source
Il semble que le mécanisme par lequel les fonctions mbstring fonctionnent est incapable de différencier les ISO-8859encodages .
bosco
J'ai appris que mon test lié est inexact et trompeur - les fonctions mbstring fonctionnent sur le principe des séquences d'octets, donc même si un encodage peut utiliser des séquences d'octets qui pourraient prendre en charge les alphabets répertoriés, cela ne signifie pas réellement que l'encodage prend réellement en charge ceux personnages. Ainsi, filtrer les alphabets des chaînes en testant les encodages n'est pas un mécanisme fiable . Veuillez plutôt considérer mon autre réponse.
bosco
1

Comme j'ai essayé de l'expliquer à @MichaelRogers lorsqu'il a posté une question similaire il y a plusieurs jours, connaître le jeu de caractères (ou script) utilisé dans une chaîne n'est PAS suffisant pour détecter la langue de cette chaîne.

Ainsi, alors que la méthode détaillée par @bosco va supprimer russe, etc cordes (avec les 2 corrections ci - dessous), il pas limiter vos recherches à l' anglais.

Pour voir cela, essayez:

$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" ;
        }
    }

[ note: les 2 corrections mentionnées ci-dessus à ce que @bosco a fournies sont:

  1. le modèle est entouré d'une chaîne (requis pour être syntaxiquement correct PHP)
  2. ajout du /umodificateur (requis pour traiter le motif et le sujet comme encodés en UTF-8, voir PHP: Modificateurs de motifs regex ]

qui produira:

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

[ note: je parle anglais, français et un peu d'allemand (et un peu de Lorem ipsum :-), mais je me suis appuyé sur Google Translate pour l'arabe, le russe et le chinois]

Comme vous pouvez le voir, s'appuyer sur la vérification du script latin NE garantira PAS que vous avez l'anglais.

Il existe un certain nombre de threads sur StackOverflow (par exemple, Détecter le langage de la chaîne en PHP ) qui fournissent plus d'informations sur le sujet.

Paul 'Sparrow Hawk' Biron
la source
Permettez-moi de laisser une note amicale et pédante: Lorem ipsum n'est pas une langue, dire que quelqu'un parle "lorem ipsum", c'est comme dire que quelqu'un parle "bonjour le monde" :) La langue de Lorem ipsum est du vieux latin , et non, "lorem ipsum " ne signifie pas " bonjour le monde " :) En fait, c'est une faute de frappe pour " dolorem ipsum " qui signifie " la douleur elle-même " ou quelque chose comme ça.
gmazzap
@gmazzap Je sais, c'était une blague (d'où le ":-)"). J'ai inclus lorem ipsum pour renforcer le fait que la vérification du script ne teste pas la langue.
Paul 'Sparrow Hawk' Biron
et pour être encore plus pédant, comme il est dit sur lipsum.com , "Lorem Ipsum provient des sections 1.10.32 et 1.10.33 de" de Finibus Bonorum et Malorum "(Les extrêmes du bien et du mal) de Cicéron, écrit en 45 AVANT JC." Mais il a également diverses "randomisations" pour le rendre insensé pour un locuteur latin natif, donc ce n'est pas en fait un "vieux latin", mais une "langue" complètement inventée.
Paul 'Sparrow Hawk' Biron
Ah, belles prises @ Paul'SparrowHawk'Biron! Je mettrai à jour ma réponse pour corriger l'expression régulière et clarifier ce que fait exactement ma solution.
bosco
1
Je me fiche que la personne tape en espagnol. Il n'a pas besoin d'être strictement en anglais. J'ai dit les caractères utilisés sur la langue anglaise donc de A à Z (en majuscules et sans majuscules) + chiffres. S'il arrive que d'autres langues utilisent les mêmes caractères, ça me va. Ce que je ne veux pas autoriser, c'est le cyrillique, le kanji, les lettres arabes (je ne connais pas le nom) et tout ce qui n'est pas Aa-Zz + 0-9. La langue n'a pas d'importance.
Michael Rogers