Filtres de recherche en PHP

Vous êtes ici : Accueil » Construire et gérer un blog avec WordPress » Introduction aux langages du Web » Filtres de recherche en PHP

Nous allons nous initier au langage PHP à travers un cas concret consistant à effectuer une recherche à base de mots clés, de deux façons différentes.

Exposition du problème

Le bloc Recherche est très pratique pour faire de la recherche sur un site. Il serait dommage de s’en passer.

Cependant, ce bloc a la fâcheuse propriété de remonter tout ce qu’il trouve : articles, pages, y compris certaines pages qui pourraient polluer le résultat de la recherche. Il suffit de rechercher le mot le pour remonter tout ce qui existe !

Voyons comment éviter ça. Je vais prendre l’exemple du site que vous consultez. Ca sera plus simple car vous commencez à le connaître.

Il est organisé de la façon suivante (les pages sont en vert) :

  • Tous les articles sont listés dans la page d’accueil au moyen d’un bloc Boucle de requête limitée aux articles, mis en place dans un modèle du thème.
  • Le menu principal, qu’on retrouve dans la barre de navigation de tous les documents (articles, pages, etc.), donne accès à toutes les catégories d’articles et à certaines pages spéciales (Qui, par exemple ; Page 1 dans le schéma), dont la Page WP (Construire et gérer un blog avec WordPress), qui est parent de toutes les pages qui traitent de WP (Page WP1, 2 et 3).
    • Ce menu ne propose pas de lien vers les pages enfants de la Page WP car ce n’est pas le sujet premier du blog.
    • Il ne propose pas non plus de liens vers les articles (seules les catégories sont ciblées) ni vers les pages éventuellement appelées par les articles car il faut de toutes façon passer par l’article (Page 2, 3, 4)
    • La mise en œuvre du menu sur mesure ne pose pas de problème dans WP.
  • Avant modification, le bloc Recherche de la page d’accueil remontait potentiellement tous les articles et toutes les pages, y compris les pages enfants WP (qui ne sont pas le sujet premier du site) ainsi que des pages trop spécialisées qui nécessitent un passage par l’articles auquel elles sont rattachées (dans le schéma, Page 3 et 4).
  • Conclusion, on aimerait pouvoir exclure certaines pages de la recherche de la page d’accueil (Page 3 et 4, Page WP1, 2 et 3)

Faisons un zoom sur les pages WP :

  • Toutes les pages qui traitent de WP sont accessibles depuis la page parent, Page WP, grâce à des liens.
  • En plus du menu principal, dont nous venons de parler, qui se trouve dans la barre de navigation de tous les document, on dispose d’un menu spécifiques à la présentation de WP, qui se trouve en haut du sommaire de toutes les pages filles (Page WP1, 2, 3). Il donne accès à toutes les pages filles.
  • La mise en place de ce menu secondaire ne pose pas de difficulté dans WP.
  • Par ailleurs, la page WP parent dispose d’un bloc Recherche qui doit, dans l’idéal, limiter sa recherche aux pages filles WP (Page WP1, 2, 3).

Cahier des charges

  • Pouvoir exclure certaines pages de la recherche du bloc Recherche principal
  • Pouvoir limiter la recherche du bloc Recherche de la page parent WP, à ses pages filles :
    • Soit statiquement (en listant les identifiants des pages où rechercher)
    • Soit dynamiquement (en récupérant les pages filles pour en faire la liste qui sera celle des pages où rechercher).

Les outils mis à disposition par WP (les Hooks)

Hook signifie crochet.

Pour interagir avec WP, il y a 2 types de Hooks que l’on peut mettre dans fucntions.php (idéalement celui d’un thème enfant), qui permettent de lancer, à un moment clé, une fonction que l’on a créée :

  • Les Filtres, pour intercepter une valeur afin de la modifier (par exemple ajouter une colonne supplémentaire à un tableau existant) : add_filter (‘id WP du contexte’, ‘nom de ma fonction’)
  • Les Actions, pour faire des choses supplémentaires (par exemple récupérer des valeurs pour mettre à jour une colonne sans modifier la structure) : add_action (‘id WP du moment clé’, ‘nom de ma fonction’).

Pour l’exemple de l’ajout de colonne dans l’écran d’admin-WP /Pages, voir le Bonus n°3.

Exclure certaines pages d’une recherche

Voyons les outils (fonctions écrites en langage PHP) que WP met à notre disposition pour exclure des pages côté serveur :

function cm_recherche_exclure($query)
{
    $pagesAExclure = array(14000, 14001, 14002, 14003);

    if( !$query->is_admin && $query->is_search )
    {
        $query->set( 'post__not_in', $pagesAExclure );
    }
    return $query;
}
add_filter('pre_get_posts','cm_recherche_exclure');
  • function : permet de déclarer une fonction (comme on déclarait une variable avec let dans JS)
  • cm_recherche_exclure($query) :
    • cm_recherche_exclure : c’est le nom que j’ai donné à ma fonction (je l’ai préfixé avec mes initials)
    • cm_recherche_exclure($query) : il faut passer en paramètre à cette fonction la variable $query qui est l’objet requête manipulé par WP lors de la recherche.
      • En PHP, les variables commencent par $
      • Un objet est une variable complexe regroupant des données (attributs de l’objet) et des fonctions (méthodes de l’objet) permettant de manipuler les attributs
      • Query : anglais demande
  • array : anglais tableau. C’est une suite de valeurs.
    • Ici, la variable $pagesAExclure est un tableau qui va contenir les identifiants des pages que je veux exclure
    • Pour récupérer l’identifiant d’une page : WP-admin /Pages /passer la souris sur la page dans la liste /lire l’id contenu dans l’URL qui s’affiche à gauche en bas de l’écran (post=xxxxx)
  • is_admin : c’est un attribut de l’objet $query qui prend la valeur VRAI si la requête est issue de l’environnement WP-admin (on peut faire des recherches sur mot clé dans la liste des pages par exemple). Or on veut éviter que notre fonction s’applique à cet environnement.
  • Donc on appliquera la restriction que si la requête n’est pas is_admin à VRAI
  • Autrement dit si on n’a PAS is_admin VRAI : PAS is_admin VRAI s’écrit NOT is_admin soit ! is_admin
  • Pas n’importe quel valeur d’is_admin, celle de notre $query, donc !$query->is_admin
    • En PHP, les attribut des objets sont accessible non pas avec un simple . comme dans JS, mais avec ->
  • && : le ET logique
  • $query->is_search : il faut aussi que la requête concerne une recherche (cet objet $query doit probablement être utilisé dans d’autre cas)
  • $query->set( ‘post__not_in’, $pagesAExclure );
    • set() est une fonction, plus précisément une méthode de l’objet $query, accessible comme les attribut avec ->
    • On lui passe en paramètres :
      • d’abord la chaîne de caractère ‘post__not_in’ (attention aux 2 underscores qui se suivent entre post et not) pour indiquer de quoi l’on parle (ici, les posts ou pages HTML non incluses) : en anglais, not in signifie pas dans.
      • et ensuite la liste de ces pages sous la forme d’un tableau.
    • Ne pas oublier le point virgule
  • return $query; : la fonction cm_recherche_exclure appelée prendra la valeur $query qu’elle retournera à la fin de son exécution. WP pourra ainsi si nécessaire utiliser la fonction comme suit :
    • $nouveauQuery = cm_recherche_exclure ($query);
  • add_filter (‘pre_get_posts’, ‘cm_recherche_exclure’);
    • Le code déclaré (function) de la fonction cm_recherche_exclure n’est pas exécuté tant qu’on n’appelle pas la fonction (pour appeler une fonction, il faut l’écrire sans le type function utilisé lors de sa déclaration : cm_recherche_exclure($query); déclencherait l’exécution du code de la fonction)
    • Une autre façon de faire en sorte que la fonction cm_recherche_exclure soit exécutée est de s’en remettre à une autre fonction, fournie par WP, qui va noter le nom de la fonction et faire le nécessaire le moment venu : cette fonction fournie par WP est add_filter
    • On passe à la fonction add_filter 2 paramètres :
      • ‘pre_get_posts’ : une chaîne de caractères qui précise quand utiliser la fonction (en l’occurrence avant (pre) de récupérer (get) les pages (posts)
      • ‘cm_recherche_exclure’ : une chaîne de caractères qui n’est autre que le nom que l’on a donné à notre fonction.
    • Je pense qu’il est possible de définir plusieurs fonctions et de les déclencher l’une après l’autre en utilisant plusieurs fois la fonction add_filter

Limiter une recherche à certaines pages

C’est d’une certaine façon l’exercice inverse : j’ai repris le code précédent et l’ai modifié (modifications en gras) :

function cm_recherche_inclure($query)
{
    $pagesAInclure = array(14001, 14002);

    if( !$query->is_admin && $query->is_search )
    {
        $query->set( 'post__in', $pagesAInclure );
    }
    return $query;
}
add_filter('pre_get_posts','cm_recherche_inclure');
  • La nouvelle fonction s’appelle cm_recherche_inclure
  • La liste des pages à considérer s’appelle maintenant $pagesAInclure
    • J’aurais pu conserver le nom $pagesAExclure, ça aurait fonctionné, mais le code serait devenu incompréhensible à la lecture
    • Naturellement, les pages ne sont pas forcément les mêmes
  • $query->set( ‘post__in‘, $pagesAInclure );
    • Toute la subtilité est ici : ‘post__in‘ (le 1er paramètre de la fonction $query->set) précise comment il faut considérer les pages HTML (post) du tableau qui est passé en 2e paramètre ($pagesAInclure) : en anglais, in signifie dans.
    • Attention aux 2 underscores qui se suivent entre post et in.

Comment répondre au cahier des charges ?

Rappel du cahier des charges :

  • Pouvoir exclure certaines pages de la recherche du bloc Recherche principal
  • Pouvoir limiter la recherche du bloc Recherche de la page parent WP, à ses pages filles.

Quel est le problème ?

  • Quand le vais utiliser un bloc Recherche, WP appliquera tous les filtres que j’aurai ajouté (add_filter signifie ajouter_filtre)
  • Or, quand je fais une recherche depuis la page d’accueil je veux que WP utilise add_filter (‘pre_get_posts’, ‘cm_recherche_exclure’);
    et quand je fais une recherche depuis la page parent qui parle de WP je veux que WP utilise add_filter (‘pre_get_posts’, ‘cm_recherche_inclure’);
  • Il faudrait utiliser une condition qui me permettrait de savoir sur quel bloc Recherche je suis positionné : celui de la page d’accueil ou bien l’autre
    • Or il n’existe qu’un seul bloc Recherche dans WP : pas moyen de les distinguer côté serveur
    • Une fausse piste consisterait à se dire, je vais attribuer une classe particulière à mon bloc Recherche (c’est le seul réglage avancé que l’on puisse faire sur un bloc Recherche) et côté serveur, je tiendrai compte de cette classe pour agir.
      Le problème est que quand il fabrique la page html, le serveur crache du texte vers le client et après il ne connaît plus rien, il attend d’éventuelles requêtes en provenance du client, issues de la page en question mais il faut tout lui dire : la page d’origine, ce qu’il doit faire et avec quelles données.
    • Autrement dit, il faudrait que la requête envoyée au serveur, quand on clique sur Rechercher (la loupe), contienne l’information

Solution proposée

Il existe sans doute plusieurs solution, je vous présente celle que je suis parvenu à mettre en place.

Quand quand on clique sur Rechercher, le client WP envoie au serveur un formulaire : le bloc Recherche n’est rien d’autre qu’un formulaire dans lequel on peut saisir une information (le mot recherché) à transmettre au serveur qui attend qu’on le sollicite.

On peut s’en rendre compte avec F12 : les balises HTML d’un formulaire sont <form> </form>

Les valeurs et les noms des balises <input> qui sont comprise à l’intérieur d’un formulaire sont transmis au serveur qui peut ainsi déclencher un traitement dès réception (nous verrons comment).

L’idée est donc, lorsqu’on met en place un bloc Recherche dans une page WP, soit de ne rien faire (dans le cas d’une recherche générale), soit de rajouter au formulaire une balise <input> contenant l’information comme quoi je suis sur une recherche de type pages filles seulement quand c’est le cas (JS devrait nous le permettre).

Il va donc falloir, dans le cas d’une recherche de type pages filles, préciser toutes les pages filles concernées (qui seront différentes selon la page parent depuis laquelle j’effectue ma recherche) : heureusement, il suffit de donner le slug de la page parent, le serveur saura retrouver l’ensemble de ses pages filles (ça va nous éviter d’avoir à lister précisément l’ensemble des pages filles pour chaque Recherche différente).

Mise en œuvre de la solution côté client

Besoin :

  • Dans la page parent depuis laquelle la recherche doit avoir lieu, ajouter un <input> nommé « racine » dont la valeur sera le slug de la page en cours.

Code JS (le code est en gras) :

<script>

//Pour considérer cette page comme page racine de ma Recherche, on ajoute un champ caché (input), ayant pour valeur le nom de cette page (son slug), à côté du champ de saisie de la recherche : si ce champ existe, la fonction ma_recherche_filter() (dans le répertoire functions du serveur), limitera la recherche à certaines pages (les pages feuilles) ; sinon elle procédera à une recherche générale (avec exclusion cependant de certaines pages). 

var libelleracine = "racine"; //je choisis ce nom pour l'input caché
 

//Création de l'input caché (attention, il faudra encore l'incérer quelque part) :
var myinput = document.createElement("input");

//récupération du slug de la page en cours :
var urlcourante = document.location.href; 
// Supprimons l'éventuel dernier slash de l'URL
var urlcourante = urlcourante.replace(/\/$/, "");
// Gardons dans la variable queue_url uniquement la portion derrière le dernier slash de urlcourante
var queue_url = urlcourante.substring (urlcourante.lastIndexOf( "/" ) + 1 );

//Mise à jour de l'input caché :
myinput.setAttribute("name", libelleracine);
myinput.setAttribute("value", queue_url);
myinput.setAttribute("type", "hidden"); //hidden = caché


//Récupération de l'objet input contenant les mots recherchés

var currentInput = document.getElementsByName("s"); //j'ai trouvé "s" avec F12 ; c'est aussi le nom du paramètre qu'on retrouve dans l'URL des pages Recherche retournées (par exemple dans https://xn--crire-9ra.fun/?s=le&racine=wordpress si on a cherché le mot 'le' : tiens, ça ne vous met pas la puce à l'oreille ?)

if(currentInput.length > 0){ //je fais cette vérif pour ne pas planter le programme au cas où il y aurait un pb
    currentInput[currentInput.length - 1].parentElement.appendChild(myinput); //je me base sur le dernier input de nom 's' remonté dans le tableau, au cas où j'en trouverais plusieurs
}

</script>
  • var myinput = document.createElement(« input »);
    • myinput signifie mon input : c’est l’input que je vais incérer
    • document.createElement() est une fonction JS qui permet de créer dynamiquement un bloc ; ici je lui demande en paramètre de créer un bloc « input »
  • var urlcourante = document.location.href;
    • Je récupère dans une variable, l’url courante qui est un attribut (une donnée) de l’objet document (la page courante)
    • Par exemple https://xn--crire-9ra.fun/wordpress/
    • C’est de cette url que je vais devoir extraire le slug de la page courante (ici wordpress)
  • var urlcourante = urlcourante.replace(/\/$/, «  »);
    • Supprime l’éventuel dernier slash de l’URL contenu dans la variable urlcourante : l’opération consiste à remplacer le caractère /, s’il est sité à la fin, par une chaîne vide : «  »
    • Par exemple, https://xn--crire-9ra.fun/wordpress/ va se transformer en
      https://xn--crire-9ra.fun/wordpress
    • /\/$/, «  » : c’est une syntaxe particulière, exploitée par des fonctions de différents langages dont JS et PHP, appelée expressions régulières (un langage dans le langage en quelque sorte)
      • En JS, l’expression régulière est délimitée au début et à la fin par des / (en PHP, c’est une chaîne de caractères délimitée par des simples ou doubles quotes) : donc considérons seulement \/$
      • \ signale que le caractère suivant doit être considéré comme un caractère simple (on vient de voir que / a une signification particulière en JS)
      • $ signal que le caractère précédent doit se trouver à la fin de la chaîne de caractères
      • Si le modèle proposé par l’expression régulière match avec un ou plusieurs caractère de la chaîne, ceux-ci seront localisé et pris en considération : soit par une fonction de simple recherche de caractères, soit par une fonction de de substitution de caractères (comme c’est le cas de la fonction (méthode) replace()
      • voir ici
  • var queue_url = urlcourante.substring (urlcourante.lastIndexOf( « / » ) + 1 );
    • Je récupère dans la variable queue_url, la sous-chaîne (substring) qui commence à la position (Index) de la dernière (last) occurence du (Of) caractère « / » trouvé dans la chaîne de caractère urlcourante
    • Par exemple, dans https://xn--crire-9ra.fun/wordpress je récupère wordpress (c’est le slug que je recherche)
  • myinput.setAttribute(« name », libelleracine)
    • myinput est le nouvel objet input que j’ai créé
    • .setAttribute() est uen méthode (fonction) des objets blocs
      • Je lui passe en paramètre le nom de l’attribut (« name »)
      • et la valeur de cet attribut (la variable libelleracine vaut « racine »)
    • Le but étant d’obtenir le HTML suivant : <input name= »racine »>
  • myinput.setAttribute(« value », queue_url);
    • On complète avec l’attribut value (valeur), qui vaut le slug de la page
    • Le but étant d’obtenir le HTML suivant :
      <input name= »racine » value= »wordpress »>
  • myinput.setAttribute(« type », « hidden »);
    • On veut en plus que cet attribut ne soit pas affiché sur la page (en anglais hidden signifie caché)
    • Le but étant d’obtenir le HTML suivant :
      <input name= »racine » value= »wordpress » hidden>
  • var currentInput = document.getElementsByName (« s »);
    • il faut récupérer l’objet bloc Recherche, après lequel on veut insérer notre input caché. Il se trouve qu’on connaît son nom ‘s’, on utilise donc la fonction getElementsByName qui retourne un tableau contenant tous les blocs de la page (document) portant ce nom
  • if(currentInput.length > 0)
    • Si (if) le tableau currentInput n’est pas vide, c’est à dire si j’ai récupéré au moins un bloc, c’est à dire si la longueur (length) de mon tableau n’est pas égale à 0, alors je poursuit mon traitement
    • Jai posé cette condition pour éviter de faire n’importe quoi au cas où je ne récupérerait pas le bloc recherché (« s »).
  • currentInput[currentInput.length -1].parentElement.appendChild (myinput);
    • J’insère (append) mon input caché (myinput) en temps qu’enfant (Child) dans l’élément parent (parentElement) du dernier ([currentInput.length -1]) bloc récupéré avec le nom ‘s’ dans le tableau currentInput
    • En JS et PHP, les tableau sont indicés à partir de zéro, ainsi le premier élément d’un tableau (s’il n’est pas vide) est nomDuTableau[0], le 2e est nomDuTableau[1] etc.
    • Le dernier élément d’un tableau peut être récupéré avec nomDuTableau [nomDuTableau.length – 1] c’est à dire avec l’indice qui vaut la longueur (length) du tableau moins un.
    • Ouf !

La touche F12 permet de vérifier le résultat :

Ce script pourra être au choix :

  • Inscrit dans un bloc HTML personnalisé directement dans la page (au moins pour être testé)
  • Inscrit dans un bloc HTML personnalisé et associé à un bloc Recherche dans un bloc Composition
  • Chargé par une fonction du fichier functions.php, appelée par un bloc Code court
  • Associé à un nouveau modèle de page (par exemple « Page racine ») :
    • Soit inscrit dans un bloc HTML personnalisé, associé à un bloc Recherche, directement dans le nouveau modèle
    • Soit chargé par une fonction du fichier functions.php pour ce nouveau modèle : voir si la fonction is_page(‘une page’) le permet.

Le cas qu’on a oublié

On oublie toujours un cas. Rappelez-vous, l’URL des pages Recherche retournées par WP, par exemple https://xn--crire-9ra.fun/?s=le&racine=wordpress

Dans le modèle des Résultats de recherche, il y a aussi un bloc Recherche, permettant de refaire une recherche si les éléments remontés ne sont pas ceux attendus.

Or, l’éventuelle nouvelle recherche devra se faire sur le même mode que la précédente, à savoir soit en mode Recherche générale, soit en mode Recherche dans les pages filles, à partir de la même page racine parent.

On a bon espoir d’y arriver car on a remarqué que l’URL des pages Recherche retournées par WP contient le paramère « &racine= » quand la recherche est faite sur dans des pages filles (parr exemple https://xn--crire-9ra.fun/?s=le&racine=wordpress). On va donc pouvoir tester la présence de ce paramètre dans l’URL des pages Résultats de recherche et, si on le trouve, réintroduire notre input caché.

Ce code JS, très semblable au précédent, est à mettre dans le modèle des pages Résultats de recherche (j’ai mis le code en gras) :

<script>

var libelleracine = "racine";
var libracine = "&"+libelleracine+"="; //on va rechercher dans l'URL, la chaîne de caractères "&racine="

//Récupérer l'URL :
var urlcourante = document.location.href;
// Supprimons l'éventuel dernier slash de l'URL :
urlcourante = urlcourante.replace(/\/$/, "");
//chercher libracine ("&racine=") dans l'URL (urlcourante)
var indexmaracine = urlcourante.indexOf(libracine);

if(indexmaracine > -1){ //alors libracine (c'est à dire "&racine=") a été trouvé dans urlcourante et on a son index (position du 1er caractère)

    //Récupérer le nom de la racine (slug)
    var nomracine = urlcourante.substring(urlcourante.indexOf(libracine)+libracine.length, urlcourante.length);

    //Créer et mettre à jour l'input caché :
    var myinput = document.createElement("input");
    myinput.setAttribute("name", libelleracine);
    myinput.setAttribute("value", nomracine);
    myinput.setAttribute("type", "hidden");
    
    //Incérer l'input caché dans le formulaire, à côté de l'input text devant contenir les mots recherchés :
    var currentInput = document.getElementsByName("s");
    if(currentInput.length> 0){
        currentInput[currentInput.length - 1].parentElement.appendChild(myinput);
    }
}
</script>

Ce script pourra être au choix :

  • Inscrit dans un bloc HTML personnalisé directement dans la page (au moins pour être testé)
  • Chargé par une fonction du fichier functions.php (sans passer par un bloc Code court)

Mise en œuvre de la solution côté serveur

Voyons maintenant comment on va exploiter ça côté serveur.

Code à écrire à la fin du fichier functions.php (attention, à réécrire à chaque changement de version de WordPress) :

function ma_recherche_filter( $query )
{
    //Les id des pages de test à exclure :
    $pagesAExclureTests = array(13584);
    //Les id des pages filles à exclure qui parlent de WP :
    $pagesAExclureWordPress = array(13650, 13743, 13759, 13779, 13797, 13825, 13878, 14009, 14270, 14470, 14489, 14739, 14907, 14926, 15014, 15054, 15184, 15295, 15427, 15436, 16804);
    //N.B. : pour éviter d'avoir à noter tous ces id, prévoir une fonction qui récupère ces id à tous les niveaux de profondeur, à partir du slug de la page parent.

    //Tableau complet des id de toutes les pages HTML à exclure :
    $pagesAExclure = array_merge($pagesAExclureTests, $pagesAExclureWordPress);

    $libelleracine = "racine";

    //Si je ne suis pas dans WP-admin :
    if(!is_admin() && $query->is_main_query()){
        //Si la requête concerne une recherche :
        if ( $query->is_search ){ 
            //Si le formulaire contient un champ "racine" et qu'il n'est pas vide :
            if (isset($_GET[$libelleracine]) and $_GET[$libelleracine] != "" ) {
                //Je récupère l'objet associé à la page racine :
                $mypost = get_page_by_path($_GET[$libelleracine], '', 'page');
                //Je récupère les pages fille de cette page racine :
                $mypages = get_pages(array('child_of' => $mypost->ID, 'sort_column' => 'post_date', 'sort_order' => 'desc'));
                //Je mets dans un tableau l'id de chacune des pages filles :
                foreach($mypages as $myPage) $pagesAInclure[] = $myPage->ID;
                //Je paramètre la requête pour qu'elle se limite à rechercher parmi les pages (filles) dont je fourni le tableau des id :
                $query->set('post__in', $pagesAInclure);
            }
  
            //Si le formulaire ne contient pas de champ "racine" ou que celui-ci est vide :
            else {
                //Je paramètre la requête pour qu'elle exclue certaines pages dont je fourni le tableau des id :
                $query->set('post__not_in', $pagesAExclure);
            }
        }
    }
    //A toute fin utile, la fonction retourne la requête reparamétrée :
    return $query;
} //fin de ma fonction
//Je demande que ma fonction (ma_recherche_filter) soit utilisée (add) avant (pre) de récupérer (get) les page HTML (posts) pour filtrer (filter) le résultat de la Recherche (add_filter('pre_get_posts',) : 
add_filter('pre_get_posts','ma_recherche_filter');
  • $pagesAExclure = array_merge($pagesAExclureTests, $pagesAExclureWordPress);
    • La fonction array_merge() permet de concaténer plusieurs tableaux.
  • if (! is_admin() && $query->is_main_query())
    • ! is_admin() : non (!) est (is) WP-admin (admin)
    • && : et logique
    • $query->is_main_query() : la requête ($query) doit être (is) une requête (query) principale (main)
      • Dans WP, les requêtes principales concernent les page, les articles et les archives.
  • if ( $query->is_search )
    • Si (if) la requête ($query) est (is) une recherche (search)
  • if (isset($_GET[$libelleracine]) and $_GET[$libelleracine] != «  » )
    • if (isset($_GET[$libelleracine]) : si (if) le champ « racine » ($libelleracine) fait partie du formulaire (isset = is set = est paramétré)
      • Le tableau $_GET[] est une variable globale contenant toutes les valeurs des inputs du formulaire (le nom de l’input sert d’indexe dans ce tableau)
    • $_GET[$libelleracine] != «  » : la valeur du champ s’il existe n’est pas vide («  »)
      • != signifie différent de
  • $mypost = get_page_by_path ($_GET[$libelleracine],  », ‘page’);
    • Je récupère (get) dans la variable $mypost, l’objet associé à la page HTML (_page_) de type page (‘page’), en utilisant (by = par) son slug (path) dont la valeur est contenue dans $_GET[$libelleracine]
    • and (et en anglais) : autre façon avec && d’écrire le et logique
    •  » (deux simples quotes collées) : le 2e paramètre de cette fonction WP est optionnel
  • $mypages = get_pages(array(‘child_of’ => $mypost->ID, ‘sort_column’ => ‘post_date’, ‘sort_order’ => ‘desc’));
    • Cette fonction WP récupère (get) dans le tableau ($mypages) les pages filles (_pages) de la page parent dont l’id est $mypost->ID, triée par date (‘sort_column’ => ‘post_date’) et triées (sort) dans l’ordre (order) antéchronologique (desc)
    • Les paramètres de cette fonction WP est un tableau (array) de paramètres. Les éléments de ce tableau sont référencés non par index numérique mais selon une syntaxe clé => valeur. Ainsi, si ce tableau avait un nom, mettons monTableau, monTableau[‘sort_column’] vaudrait ‘post_date’. c’est pratique quand on n’utilise pas toutes les positions d’un tableau et qu’on ne connait pas la position de chaque information ; en plus c’est plus lisible).
  • $query->set(‘post__in’, $pagesAInclure);
    • On modifie le paramétrage (set) de la requête ($query) concernant les pages HTML (post) dans lesquelles (__in) effectuer la recherche en en fournissant la liste sous la forme d’un tableau ($pagesAInclure)
  • else {
    • else = sinon : il faut remonter les accolade jusqu’à la condition :
      if (isset($_GET[$libelleracine]) and $_GET[$libelleracine] != «  » )
    • Autrement dit nous sommes dans le cas contraire, à savoir :
      Si le formulaire ne contient pas de champ « racine » ou que celui-ci est vide
  • $query->set(‘post__not_in’, $pagesAExclure);
    • On opte ici pour un paramétrage de la requête basé sur les pages à exclure car on est dans le cas de la recherche générale (puisqu’on n’est pas dans le cas d’une recherche dans des pages filles).

Les cas qu’on a oublié (encore)

Il faudra encore ajouter du code à cette fonction pour faire en sorte qu’aucun document ne soit afficher dans les cas suivants :

  • Si la page désignée comme page parent n’a en fait aucune page fille
  • Si la page désignée comme page parent n’a pas de bloc Recherche
  • Si un internaute s’amuse à entrer dans la barre d’adresse de son navigateur, une adresse de la forme https://xn--crire-9ra.fun/?s=le&racine=qui
    • s est le name de l’input recevant le mot recherché
    • le est le mot recherché (mot très courant)
    • racine est le name de l’input ajouté par notre script dans le formulaire, après s) pour signaler à la fonction ma_recherche_filter que la recherche ne doit se faire que dans les pages filles de la page mentionnée
    • qui est le slug de la page mentionnée en question mais celle-ci n’a en réalité pas de pages filles.

Pour toutes ces subtilités, je vous renvoie à la page Documentation du site écrire.fun, paragraphe ma_recherche_filter : vous pourrez vous amuser à décortiquer le code final de la fonction.

Vous vous apercevrez à cette occasion qu’un cas complémentaire a été géré : lorsqu’on fait une recherche depuis la liste des articles d’une catégorie, on ne continue la recherche que parmi les articles de cette catégorie (ici, c’est la catégorie qui, en quelque sorte, prend le rôle de la page parent de notre cas de base).

Entrainement

Avant de mettre en place ce dispositif client-serveur, vous pouvez vous entraîner à mettre en place cette petite fonction côté serveur (aucun script n’est nécessaire côté client) : son action consiste à limiter aux articles toutes les recherches :

function search_only_posts($query){
  if(!is_admin() && $query->is_main_query()){
    if ($query->is_search){
      $query->set('post_type', ['post']);
      //'post' versus 'page'
    }
  }
}
add_action('pre_get_posts', 'search_only_posts');

Vous pourrez conserver cette fonction dans le fichier functions.php sous réserve de ne plus l’appeler en mettant add_action en commentaire, comme suit :
// add_action(‘pre_get_posts’, ‘search_only_posts’);

Si vous voulez limiter les recherches aux pages WP (versus aux articles), il suffit de remplacer $query->set(‘post_type’, [‘post’]);
par $query->set(‘post_type’, [‘page’]);

Déploiement côté client

Le script concernant les pages Résultat de Recherche peut être mis à un seul endroit une fois pour toute, dans le modèle des pages Résultat de Recherche. Si ce code doit être mis à jour, toutes les pages Résultat de Recherche hériteront du code puisqu’elles héritent du modèle.

Le script concernant les pages racine parent, où se trouve le bloc recherche, doit être inséré dans chacune de ces page. Il faut donc créer une composition synchronisée constituée de 2 blocs : un bloc Recherche et un bloc HTML personnalisé contenant le script JS.

N.B. : il n’y a pas de script dans la page d’Accueil.

Déploiement côté serveur

Pour être testé, le code proposé doit être inclus à la fin du fichier functions.php

Vous pouvez déclarer (function) plusieurs fonctions, du moment qu’elles portent des noms différents : une fonction n’est exécutée que si elle est appelée.

Pour ne pas appeler une fonction, il suffit de mettre en commentaire avec //, l’appel à la fonction add_filter qui appelle votre fonction ; par exemple ci-dessous, ma fonction cm_recherche_exclure ne sera pas appelée :

//add_filter('pre_get_posts','cm_recherche_exclure');

Rappel : pour accéder aux fichiers du serveur (attention de ne rien casser), il faudra aller sur votre compte d’hébergement (réservé aux utilisateurs à l’aise avec la bureautique des profondeurs) :

  • Sur OVH :
    • Il faut utiliser la fonctionnalité FTP Explorer (file transfert protocole) : il y a un mot de passe qui a été fourni
    • Le fichier functions.php se trouve dans le répertoire www/wp-includes
  • Sur HOSTINGER :
    • Il faut utiliser le Gestionnaire de fichiers (il n’y a pas de mot de passe)
    • Le fichier functions.php se trouve dans le répertoire public_html/wp-includes

Documentation

Puisque nous avons modifié le comportement de WP (en l’occurrence la fonction Recherche du bloc Recherche, il est important de documenter le nouveau fonctionnement dans un commentaire, au début de chaque script et au début de la fonction se trouvant sur le serveur ; le contenu pourrait ressembler à cela :

Script page racine parent

  • Ce script associé au bloc Recherche dans la composition « Recherche dans pages filles » permet de considérer cette page comme la page racine de ma Recherche, celle-ci devant être réalisée dans les page filles de cette page racine, à tous les niveaux de profondeur.
  • Attention, seul le dernier bloc recherche de la page peut être considéré comme tel.
  • On ajoute un input caché ayant pour valeur le slug de la page racine, à côté de l’input de saisie du bloc Recherche, dans le formulaire du bloc Recherche.
  • Si la valeur de l’input caché n’est pas vide, la fonction ma_recherche_filter() (dans le répertoire functions du serveur), limitera la recherche aux pages feuilles dont le slug de la page parent se trouve dans la valeur de l’input caché. Sinon la fonction ma_recherche_filter() procédera à une recherche générale avec exclusion cependant de certaines pages (voir cette fonction).

Script page Résultat de recherche

  • Ce script permet de faire une nouvelle recherche sur le même mode que la recherche précédente. Si le paramètre « &racine= » est trouvé dans l’URL, on récupère dans l’URL le slug de la page racine de la recherche précédente et l’on fait en sorte que la nouvelle recherche utilise ce slug comme référence (la recherche devant être réalisée dans les page filles de cette page racine, à tous les niveaux de profondeur.
  • S’il on a trouvé ce slug dans l’URL, on ajoute un input caché ayant pour valeur le slug de la page racine, à côté de l’input de saisie du bloc Recherche, dans le formulaire du bloc Recherche.
  • Si la valeur de l’input caché n’est pas vide, la fonction ma_recherche_filter() (dans le répertoire functions du serveur), limitera la recherche aux pages feuilles dont le slug de la page parent se trouve dans la valeur de l’input caché. Sinon la fonction ma_recherche_filter() procédera à une recherche générale avec exclusion cependant de certaines pages (voir cette fonction).

Fonction côté serveur

  • Cette fonction tient compte d’un éventuel input caché fourni dans le formulaire de Recherche de mots clés (bloc Recherche) pour savoir quel type de filtre mettre en place : exclusion de pages si Recherche générale standard, ou limitation de la recherche aux page filles de la page parent dont le slug est passé dans l’input caché.
  • Cet input caché est mis en place soit par un script JS du modèle des Résultats de Recherche, soit dans une composition regroupant un bloc Recherche est un script JS, utilisée dans la page parent.
  • Cette fonction devrait évoluer dans le cas où plusieurs menus de type page parent seraient utilisés.
  • Les id des pages à exclure doivent être renseignés en dur dans la fonction.

Bonus n°1

// Passer de 55 mots par défaut à 100 mots pour l'extrait des pages (pour les articles on peut le gérer dans le modèle, mais ce programme gère les 2)

function new_excerpt_length($length) {
    return 100;
}
add_filter('excerpt_length', 'new_excerpt_length');

Bonus n°2

Vous avez une version encore plus élaborée du filtre dans le cours Documenter le site chapitre Compositions Synchronisées /Recherche

Le filtre mis en œuvre est complémentaire des compositions :

  • Script page racine de recherche
  • Script categorie de recherche
  • Recherche et html.

Selon le cas, la fonction ma_recherche_filter() du répertoire functions du serveur :

  • Limitera la recherche à certaines pages (les pages feuilles) quand le champ invisible ‘racine’ sera présent et renseigné dans le formulaire de recherche
  • Limitera la recherche aux articles de cette catégorie quand le champ invisible ‘categorie’ sera présent et renseigné dans le formulaire de recherche.

Bonus n°3

Ajout d’une colonne « Nb mots » dans l’écran d’admin-WP /Pages, affichant le nombre de mots de chaque page.

Code PHP pour modifier le tableau des colonnes en ajoutant une colonne
add_filter('manage_page_posts_columns', 'ajouterUneColonne');

function ajouterUneColonne($lesColonnes) {
    $lesColonnes['nombre_de_mots'] = 'Nb mots'; //Titre
    return $lesColonnes;
});

Explications :

  • manage_page_posts_columns : dans ce contexte, WP attend une fonction (que j’ai appelée ajouterUneColonne) avec un argument (que j’ai appelé $lesColonnes) qui sera renseigné par WP avec l’adresse du tableau contenant l’ensemble des colonnes de l’écran d’administration des pages, lorsque ma fonction sera déclenchée dans ce contexte par la fonction WP add_filter.
  • $lesColonnes[‘nombre_de_mots’] = ‘Nb mots’ ajoute un élément au tableau (dont l’adresse est dans $lesColonnes) : cet élément est indexé dans le tableau au moyen de l’étiquette ‘nombre_de_mots’ et sa valeur est ‘Nb mots’ qui sera le titre de la colonne ajoutée.
  • return $lesColonnes : ma fonction doit retourner l’adresse du tableau modifié.
Code PHP pour changer l’ordre des colonnes
function ordre_des_colonnes($columns) {
    return array(
        'cb' => $columns['cb'],
        'title' => $columns['title'],
        'nombre_de_mots' => $columns['nombre_de_mots'],
        'comments' => $columns['comments'],
        'date' => $columns['date']
    );
}
add_filter('manage_page_posts_columns', 'ordre_des_colonnes');

Explications :

  • ‘cb’ => $columns[‘cb’] : on se contente de donner à un élément donné du tableau, la valeur qu’il a déjà (ça évite de se tromper)
  • ‘nombre_de_mots’ => $columns[‘nombre_de_mots’] : on positionne notre nouvelle colonne à l’endroit qu’on a choisi.

En fait, on peut mixer ces deux fonctions en une seule :

Code PHP pour ajouter une colonne et changer l’ordre des colonnes
function ordre_des_colonnes($columns) {
    return array(
        'cb' => $columns['cb'],
        'title' => $columns['title'],
        'nombre_de_mots' => 'Nb mots',
        'comments' => $columns['comments'],
        'date' => $columns['date']
    );
}
add_filter('manage_page_posts_columns' , 'ordre_des_colonnes');

Explications :

  • ‘cb’ => $columns[‘cb’] : on se contente de donner à un élément donné du tableau, la valeur qu’il a déjà (ça évite de se tromper)
  • ‘nombre_de_mots’ => $columns[‘nombre_de_mots’] : on positionne notre nouvelle colonne à l’endroit qu’on a choisi.

Enfin, il faut renseigner chaque ligne de la colonne ajoutée avec les bonnes valeurs :

Code PHP pour valoriser chaque lignes de la colonne
add_action('manage_pages_custom_column','renseignerColonne', 10, 2); //priorité : 10 par défaut, 2 : nombre d'arguments acceptés par la fonction
function renseignerColonne($laColonne, $le_id){
    switch ($laColonne) {
        case 'nombre_de_mots':
            $nbMots = str_word_count( //PHP : compter les mots
                strip_tags(  //PHP : ôter les balises HTML et PHP
                    strip_shortcodes(    //WP : ôter les balises de codes courts
                        get_post_field('post_content', $le_id)  //WP : récupérer le contenu de l'élément dont l'id est dans $post_id
                    )
                )
            );
            echo $nbMots;   //PHP : cracher la valeur de $nbMots
        break;
    }
}

Explications :

  • manage_page_posts_columns : dans ce contexte, WP attend une fonction (que j’ai appelée ajouterUneColonne) avec un argument (que j’ai appelé $lesColonnes) qui sera renseigné par WP avec l’adresse du tableau contenant l’ensemble des colonnes de l’écran d’administration des pages, lorsque ma fonction sera déclenchée dans ce contexte par la fonction WP add_filter.
  • $lesColonnes[‘nombre_de_mots’] = ‘Nb mots’ ajoute un élément au tableau (dont l’adresse est dans $lesColonnes) : cet élément est indexé dans le tableau au moyen de l’étiquette ‘nombre_de_mots’ et sa valeur est ‘Nb mots’ qui sera le titre de la colonne ajoutée.
  • return $lesColonnes : ma fonction doit retourner l’adresse du tableau modifié.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Get 30% off your first purchase

X