Langue

Me contacter

Extension WooCommerce · depuis 2014 · aka WOOF

HUSKY

Un filtre de produits à facettes pour WooCommerce : taxonomies, attributs, prix, stock, champs meta et texte, en widget ou en shortcode, avec rechargement de la page ou AJAX.

Première version
Installations actives
80 000+
Note
4.6 / 5 (336)Lire les avis
  • PHP
  • WordPress
  • WooCommerce
  • WP_Query
  • WP_Tax_Query
  • WP_Meta_Query
  • MySQL
  • Transients
  • Object cache
  • jQuery
  • AlaSQL
  • PDO
  • WPML
  • Polylang

À propos

HUSKY filtre les produits WooCommerce par catégories, attributs, étiquettes, prix, statut de stock, note, auteur, SKU, champs meta personnalisés et texte. Il est présent sur wordpress.org depuis 2014. Les éditions gratuite et premium sont construites à partir d'une seule base de code ; l'édition est choisie par le numéro de version majeure, et les parties réservées au premium sont soit neutralisées, soit plafonnées dans la version gratuite.

Chaque état du filtre aboutit à une requête WordPress sur wp_posts, wp_postmeta et les tables de termes, et les boutiques gèrent des dizaines de milliers de produits et de variations. Afficher le nombre de produits que renverrait chaque option exige une requête de plus par terme visible à chaque affichage de page. Les produits variables, la visibilité du catalogue, les taxes et les extensions de devises modifient chacun ce qu'est une correspondance.

L'extension s'exécute aussi dans un traitement de requête qui ne lui appartient pas : l'analyse des règles de réécriture de WordPress, la requête principale et les templates de produits de WooCommerce, les constructeurs de pages, les extensions multilingues et les couches de cache. La plus grande partie du code décrit ci-dessous existe parce que l'une de ces couches rendait l'approche directe incorrecte ou trop lente.

HUSKY · filtre de produits

ce qui a été résolu

EN LIGNEdepuis 2014

URL SEO pour les états du filtre

dans le produit

Ce que cela apporteLes pages filtrées obtiennent des adresses propres et stables que les moteurs de recherche peuvent indexer. Le propriétaire décide quelles pages filtrées apparaissent dans Google et avec quel titre.

Problème
Les états du filtre sont des chaînes de requête, que les moteurs de recherche ignorent ou explorent sans limite. Les URL en chemin comme /swoof/color-red/size-m/ sont inconnues des règles de réécriture de WordPress et entrent en conflit avec les redirections canoniques.
Solution

Intercepter l'analyse de la requête, retirer le segment du filtre, laisser WordPress traiter le reste, puis reconstruire l'état du filtre à partir du segment avec un encodage fixe et des règles d'index par profondeur.

Stack
  • WordPress rewrite API
  • PHP
  • JavaScript
  • Yoast SEO
Tâches · 6
/shop/swoof/color-red/size-xl/ ne correspond à aucune règle de réécriture et renvoie une 404Découper le segment du filtre, acheminer le reste par les règles de réécriture du cœur, reconstruire les filtres à partir de ce segment

Un lien de filtre est l'URL de l'archive plus le slug du filtre (swoof par défaut) et un segment par filtre : /product-category/shoes/swoof/color-red-and-blue/size-xl/. L'extension n'enregistre aucune règle de réécriture. url_process() s'exécute sur do_parse_request côté public et retourne aussitôt, sauf si get_search_request() trouve /<slug>/ dans le chemin. Elle prend le segment du filtre jusqu'à /page/, ? ou #, le retire, et fait correspondre le reste (/product-category/shoes/ ou /shop/page/2) à $wp_rewrite->wp_rewrite_rules() avec du code copié de WP::parse_request() : règles de pages verbeuses, variables de requête publiques et privées, types de contenu visibles, taxonomies non interrogeables, wp_resolve_numeric_slug_conflicts() et le filtre request. Elle appelle ensuite query_posts(), handle_404() et register_globals() (l'action parse_request avant WordPress 6.0) et retourne false, de sorte que le cœur n'analyse pas à nouveau l'URL.

parse_url_query() découpe le segment par '/' et fait correspondre chaque partie à la table de noms issue de get_all_items() : slugs d'attributs sans pa_, slugs de taxonomies traduits par WPML, price, et les mots fixes instock, onsale, featured, backorder_not_in. -and- et -or- deviennent des virgules, -to- un séparateur de plage, + une espace ; price-10-to-50 devient min_price et max_price. add_request() fusionne le résultat via le filtre woof_get_request_data, de sorte que get_request_data() renvoie le même tableau que pour une chaîne de requête ?swoof=1&pa_color=red. Sur la requête principale, parse_query() fixe ensuite post_type à product, marque la requête comme archive de type de contenu et ajoute les meta queries, et tax_query() sur woocommerce_product_query_tax_query remplace les entrées de WooCommerce pour les mêmes taxonomies par la tax query de l'extension.

Comme le reste est acheminé par les règles existantes, la base de la boutique, la base des catégories de produits et la pagination des archives continuent de fonctionner sans nouvelles règles : /page/N/ est coupé avant que le segment du filtre soit localisé et reste dans le chemin acheminé. parse_query() désactive is_tax sur les pages filtrées, si bien que la catégorie courante est retrouvée à partir du dernier segment de l'URL nettoyée (get_term_from_cleared_url()). Lorsque la page boutique est aussi la page d'accueil, le correctif redirect_canonical ci-dessous empêche WooCommerce de rediriger l'URL du filtre vers /.

Pour une URL qui correspond à une règle SEO, meta_head() affiche la balise canonical : l'URL du filtre sans chaîne de requête si l'état est indexable, avec les balises canonical de Yoast et de SEOPress désactivées ; sinon, l'URL coupée au slug du filtre, c'est-à-dire l'archive simple. Avec Yoast SEO, sitemap_index() sur wpseo_sitemap_index ajoute les URL qu'un administrateur liste dans une zone de texte des réglages, une par ligne ; la version premium les échappe avec esc_url_raw() et esc_xml().

L'ordre dans lequel les filtres sont utilisés ne doit pas changer l'URLÉcrire les filtres dans l'ordre trié des clés ; refléter la grammaire -and- / -to- / + en PHP et en JS

woof_get_submit_link() dans url_parser.js trie les filtres actifs par clé et écrit chacun sous la forme <name>-<values>, avec ',' pour -and-, un séparateur de plage pour -to- et les espaces pour +. Le prix devient price-<min>-to-<max> ; en stock, en promotion, mis en avant et en réapprovisionnement deviennent des mots fixes. parse_url_query() inverse la correspondance.

Grâce au tri, choisir d'abord la couleur puis la taille donne le même chemin que dans l'ordre inverse. Les valeurs d'un même filtre sont écrites dans l'ordre où elles ont été sélectionnées.

L'attribut pa_niveau et le champ meta niveau demandent le même nom d'URLDétecter les collisions de noms : la taxonomie garde le nom court, l'autre reçoit un nom de repli unique

Les noms d'URL suppriment le préfixe pa_, si bien qu'un attribut et une clé meta peuvent produire le même nom, et une seule entrée de la table masquerait l'autre. resolve_seo_key_conflict() attribue le nom court à la taxonomie enregistrée quel que soit l'ordre des filtres dans les réglages, de sorte que les URL existantes restent valides, et dérive un nom pour l'autre filtre à partir de sa vraie clé, en ajoutant un suffixe numérique si nécessaire. Les slugs de taxonomies traduits par WPML sont résolus avant cette étape.

Chaque requête de recomptage relit l'état du filtre dans le chemin de l'URLMémoïser parse_url_query() par URL, mais seulement après wp_loaded

get_request_data() est appelée par chaque requête de recomptage, et avec les URL SEO chaque appel passe par woof_get_request_data puis parse_url_query(). Avec Dynamic Recount et Hide Empty (premium), une seule page peut faire plus d'un millier de ces appels, c'est pourquoi le résultat de l'analyse est mis en cache par URL.

Le cache n'est activé qu'après wp_loaded, afin qu'un appel effectué avant l'existence des taxonomies et des réglages ne puisse pas figer une table de noms incomplète pour le reste de la requête.

Quelles pages filtrées indexer, et avec quel titre et quelles metaIndexer par profondeur et par règles regex avec meta par règle ; ignorer sans bruit les motifs défectueux

WOOF_SEO compte les filtres actifs. Au-delà de index_deep (2 par défaut), la page reçoit le réglage « Disable page indexing » (oui par défaut, donc noindex,nofollow via wp_robots) et les règles ne sont pas vérifiées. À cette profondeur ou en deçà, une règle qui correspond rend la page indexable. Les règles sont des motifs d'URL avec des marqueurs {any} compilés en expressions régulières, chacun avec un titre, une meta description, un H1 et un texte SEO pouvant inclure les noms des termes sélectionnés, et elles sont stockées par langue. Les balises canonical ne sont affichées que pour les URL qui correspondent à une règle.

Dans la version premium, preg_match s'exécute sous un gestionnaire d'erreurs temporaire : une règle dont l'URL n'est pas une expression valide est ignorée et signalée dans un avis d'administration, et une règle vide est ignorée. L'édition gratuite n'utilise que les deux premières règles.

WooCommerce 11 redirigeait les URL de filtre sur une page d'accueil servant de boutiqueAnnuler uniquement la redirection vers home_url, et uniquement pour les URL qui portent le slug du filtre

Lorsque la page boutique est aussi la page d'accueil statique, WooCommerce 11.0.0 la définit comme objet interrogé sur les archives de produits. redirect_canonical() traite alors /swoof/... comme une copie non canonique de la page d'accueil et redirige vers /, en supprimant tous les filtres. fix_front_page_shop_canonical() ne retourne false que lorsque la page d'accueil est la page boutique, que le chemin contient le slug du filtre et que la cible de la redirection est l'URL d'accueil ; la normalisation des barres obliques et des slugs continue donc de fonctionner partout ailleurs.

Recomptage dynamique et cache des requêtes

dans le produit

Ce que cela apporteChaque option du filtre indique combien de produits elle renverra, ce qui évite aux acheteurs les impasses. Les comptes sont mis en cache, la boutique reste donc rapide sur les gros catalogues.

Problème
Chaque option du filtre affiche le nombre de produits qu'elle renverrait compte tenu de la sélection en cours. Cela représente une requête de produits par terme visible à chaque affichage de page, et c'est le principal coût sur les gros catalogues.
Solution

Les comptes proviennent d'une requête allégée, qui ne renvoie que des ID et reproduit la sémantique du filtre. Les résultats sont mis en cache par requête, dans une table dédiée et dans des transients, et le formulaire peut être différé jusqu'à ce qu'un visiteur le demande.

Stack
  • WP_Query
  • MySQL
  • Transients
  • Object cache
Tâches · 6
Le compte d'un terme doit refléter le reste de la sélection en coursReconstruire la requête par terme, en y insérant le terme selon les règles de sélection simple ou multiple

dynamic_count() reconstruit la tax_query pour chaque terme. Les termes déjà sélectionnés n'obtiennent pas de compte. Pour radio et select (single), le terme remplace la valeur sélectionnée de sa taxonomie ; pour checkbox et multi-select (multi), il lui est ajouté. Les termes de la page de catégorie courante et ceux de l'attribut taxonomies du shortcode sont toujours combinés en AND, de sorte que les comptes sur une page de catégorie restent à l'intérieur de cette catégorie.

La logique par taxonomie (OR, AND, NOT IN) issue des réglages, les plages de prix et de note, les conditions meta des extensions, la langue WPML et la visibilité du catalogue sont appliquées de la même façon que dans la requête de produits. Les extensions déclarent leur comportement de recomptage via html_type_dynamic_recount_behavior, si bien que les types color, image et label réutilisent ce code.

Le masquage des options sans résultat repose sur ces comptes : le terme est compté puis ignoré lorsque le compte vaut 0, il ne peut donc pas coûter moins cher que l'affichage des comptes. Ce réglage est réservé au premium ; l'édition gratuite le force à off dans les vues.

Une requête de comptage ne doit ni charger des posts ni exécuter FOUND_ROWSDériver WP_Query avec des méthodes found-rows et de boucle sans effet, ne récupérer que les ID

WP_QueryWOOFCounterIn étend WP_Query et surcharge set_found_posts(), setup_postdata(), the_post() et have_posts(). La requête de comptage s'exécute avec fields=ids et nopaging, si bien que MySQL renvoie une liste d'ID, que WordPress ne construit aucun objet post et n'émet pas de seconde requête FOUND_ROWS. Le compte est la longueur de la liste.

Les filtres posts_clauses de popularité et de note de WooCommerce sont retirés avant la requête de comptage, car l'ordre n'a pas d'importance pour un compte et ces filtres ajoutent des jointures au SQL.

Les mêmes comptes sont recalculés pour chaque visiteur et chaque pageStocker les comptes dans une table clé-valeur indexée par le md5 des arguments complets de la requête, vidée par cron

Avec « cache count data » activé, WP_QueryWoofCounter calcule le hachage du JSON des arguments finaux de la requête, qui incluent déjà les parties langue et visibilité, et cherche ce hachage dans {prefix}woof_query_cache, une table à deux colonnes dont le hachage est la clé primaire. Les écritures utilisent INSERT ... ON DUPLICATE KEY UPDATE, de sorte que deux affichages de page écrivant la même clé en même temps n'échouent pas. Un site doté d'un cache d'objets persistant peut déplacer les comptes vers wp_cache_* grâce au filtre woof_use_wp_cache.

Il n'existe pas de moyen peu coûteux de savoir quels comptes en cache sont touchés par un produit nouveau ou modifié ; un événement planifié vide donc toute la table selon la période configurée. Si l'utilisateur MySQL n'a pas le privilège CREATE, la page de réglages affiche le SQL à exécuter à la main et laisse l'option désactivée.

La requête min/max du curseur de prix parcourt postmeta sur les grosses boutiquesMémoïser min/max par hachage SQL pour la requête et dans un transient, effacer à l'enregistrement d'un produit

get_filtered_price() construit un seul SELECT MIN/MAX sur _price joint à la tax query et à la meta query en cours. La même plage était demandée plusieurs fois par page (min et max pour chaque filtre de prix), le résultat est donc mémoïsé dans un tableau statique indexé par le md5 du SQL. Avec « Optimize price filter » activé, les résultats vont aussi dans un transient unique pour un jour, et tout woocommerce_update_product le supprime. Le SQL passe par woof_get_filtered_price_query, si bien qu'un site peut ajouter des index hints.

Les arborescences de termes sont reconstruites de zéro pour chaque taxonomie à chaque pageMettre en cache les arborescences assemblées dans des transients indexés par les arguments et la langue courante

WOOF_HELPER::get_terms() charge tous les termes d'une taxonomie avec get_categories() et assemble en PHP l'arbre parent-enfant. Le résultat est mémoïsé par requête et, avec « Cache terms » activé, stocké dans un transient dont la clé inclut la taxonomie, hide_empty, les indicateurs de profondeur et la langue WPML ou Polylang courante, de sorte que chaque langue a son propre arbre en cache.

Le nettoyage parcourt wp_options à la recherche de _transient_woof_terms_cache_% et supprime chaque transient, car WordPress ne propose pas de suppression par préfixe pour les transients.

Le recomptage s'exécute même si le visiteur n'ouvre jamais le filtreAfficher un bouton à la place du formulaire et récupérer le formulaire en AJAX au premier clic

Avec start_filtering_btn=1, le serveur ne produit qu'un bouton, aucune requête de termes ne s'exécute donc au chargement de la page. Au clic, front.js envoie woof_draw_products avec le shortcode de produits réglé sur 'woof_nothing' : le serveur affiche le formulaire du filtre et aucun produit, la zone de redessin est remplacée et les widgets sont réinitialisés. Le bouton est omis lorsqu'une recherche est déjà active, de sorte qu'une page filtrée affiche toujours sa sélection.

Masquer le formulaire avec du CSS ou des règles d'affichage n'économise rien, car les comptes sont calculés sur le serveur avant l'envoi du HTML. Les fils sur les performances du forum de support ont souvent dû rappeler ce point.

Travailler dans la requête de produits de WooCommerce

dans le produit

Ce que cela apporteLe filtrage continue de fonctionner avec le thème de la boutique, les pages de catégories, les produits variables, les taxes et les devises, de sorte que les acheteurs ne voient jamais d'articles dans des tailles épuisées.

Problème
Le filtre modifie la requête principale de la boutique et s'appuie sur des templates WooCommerce qui ne lui appartiennent pas. Il doit continuer à fonctionner malgré les changements du modèle de données de WooCommerce, avec les produits variables, la visibilité du catalogue, les taxes et les extensions de devises.
Solution

Se brancher tard sur parse_query et sur la requête de produits de WooCommerce, restaurer l'état d'archive attendu par les templates, et traiter explicitement chaque différence du modèle de données par version ou par table.

Stack
  • WP_Query
  • WooCommerce
  • MySQL
  • jQuery
Tâches · 6
Les résultats filtrés doivent s'afficher via le template de boutique du thèmeRéécrire les indicateurs de la requête principale à la priorité 9999 de parse_query pour qu'elle s'affiche comme archive de produits

Lorsque le slug de recherche est dans la requête, parse_query() fixe post_type=product et is_post_type_archive, et efface is_tax, is_tag, is_home, is_single et is_search. WooCommerce et le thème affichent alors la boucle de la boutique pour toute URL filtrée. Les extensions ajoutent leurs conditions meta via assemble_query_params(), et les opérateurs par taxonomie viennent du filtre woof_main_query_tax_relations.

Les mêmes hooks ne doivent pas toucher aux requêtes REST et Store API. Les gestionnaires retournent tôt sur les URL /wp-json/ et parse_query est détaché sur rest_api_init, si bien que les thèmes à blocs et les clients headless reçoivent les résultats propres à WooCommerce.

Filtrer sur une page de catégorie fait perdre la catégorieTransporter le terme sous forme de really_curr_tax et le conserver dans un transient indexé par l'IP du visiteur

Une URL de filtre n'est plus l'archive du terme, get_queried_object() cesse donc de renvoyer la catégorie. Lorsque l'archive est affichée, set_really_current_term() stocke le terme via WOOF_STORAGE, que l'extension exécute en mode transient : un transient par visiteur, indexé par un hachage de l'IP du visiteur, conservé un jour. Le JS ajoute aussi really_curr_tax=<term_id>-<taxonomy> au lien du filtre, et get_really_current_term() s'y rabat quand rien n'est stocké. Le terme est alors combiné en AND dans la requête de produits, les requêtes de recomptage et la recherche textuelle. Les requêtes AJAX lisent le terme stocké mais ne l'écrasent jamais.

La visibilité du catalogue est stockée différemment selon la version de WooCommerceBrancher selon la version : meta _visibility avant 3.0, product_visibility NOT IN ensuite

WooCommerce 3.0 a déplacé les indicateurs de visibilité, de mise en avant et de rupture de stock de postmeta vers la taxonomie product_visibility. product_visibility_not_in() transforme les indicateurs nécessaires en clauses term_taxonomy_id NOT IN. generate_visibility_keys() ajoute outofstock lorsque la boutique masque les articles en rupture de stock ; quand l'option « listen catalog visibility » est activée, elle ajoute aussi exclude-from-search toujours et exclude-from-catalog hors recherche. Les boutiques plus anciennes reçoivent à la place une condition meta _visibility.

Les mêmes règles s'appliquent à la requête principale, à la requête du shortcode, aux requêtes de recomptage, à la recherche textuelle et aux données du mode turbo. Si l'une d'elles diffère, les comptes et les résultats ne concordent plus.

Produit variable affiché alors que la taille choisie est en rupture de stockTrouver les parents des variations épuisées correspondantes dans la table de recherche des attributs, les exclure

Un produit variable compte comme en stock si une seule de ses variations l'est ; une requête simple « en stock + taille M » renvoie donc une chemise dont la variation M est épuisée. Lorsque le filtre « en stock » est combiné à des filtres d'attributs, posts_where() collecte les termes pa_* sélectionnés, y compris le terme en cours de comptage pendant le recomptage, et exécute deux requêtes groupées sur wc_product_attributes_lookup : les parents ayant une variation correspondante en rupture de stock, et les parents en ayant une en stock. La différence est ajoutée sous forme de ID NOT IN.

Un ancien mode, sélectionnable dans les réglages du filtre « en stock », interroge à la place les posts product_variation par meta d'attribut et _stock_status au lieu de la table de recherche. Il est plus lent et, par défaut, il exclut tout parent ayant une variation correspondante épuisée ; les parents qui ont aussi une correspondance en stock ne sont conservés que si le filtre woof_exclude_existing_variations retourne true.

Les bornes de prix diffèrent des prix stockés avec les taxes et les devisesReconvertir les bornes dans la devise de base et retirer les taxes incluses avant la meta query

_price est stocké dans la devise de base de la boutique et, sur de nombreuses boutiques, hors taxes. Si le visiteur utilise une devise WPML (WCML), assemble_price_params() reconvertit les bornes dans la devise de base. Si les prix sont affichés taxes comprises mais stockés hors taxes, la taxe incluse au taux standard est soustraite des deux bornes. Pour la borne haute du curseur, get_max_price() trouve le produit qui détient le prix maximal et demande à WooCommerce son prix taxes comprises.

Avec FOX (WOOCS), les valeurs du curseur affichées sont converties via woocs_exchange_value, tandis que la requête garde des nombres dans la devise de base.

Mode AJAX : redessiner les résultats, les comptes et des parties de la page sans rechargementUn seul point d'accès AJAX produit les produits et le formulaire via des shortcodes ; le JS remplace les zones et met à jour l'URL

woof_draw_products() reconstruit $_GET à partir du lien que le visiteur aurait ouvert, affiche [woof_products] et [woof] avec cet état, et renvoie en JSON les produits, le formulaire et des fragments supplémentaires (nombre de résultats, tri, pagination). front.js remplace chaque fragment, remet en place les fragments que la réponse n'incluait pas, réinitialise les widgets et appelle history.pushState pour que l'URL puisse être partagée. onpopstate recharge la page lorsque la chaîne de requête a changé. Un indicateur de verrouillage ignore les clics pendant qu'une requête est en cours.

La boucle de la boutique est encadrée en se branchant sur woocommerce_before_shop_loop et woocommerce_after_shop_loop. Les thèmes qui ne déclenchent pas les deux hooks cassent le mode AJAX, ce qui explique toute une famille de fils de support avec des symptômes différents et une seule cause.

Mode turbo : filtrer dans le navigateur

dans le produit

Ce que cela apporteSur les gros catalogues, le filtrage s'exécute dans le navigateur de l'acheteur plutôt que sur le serveur. Les résultats apparaissent rapidement et le serveur est moins chargé aux heures de pointe.

Problème
Sur les gros catalogues, chaque clic sur un filtre coûte au serveur un ensemble de jointures de taxonomies et de meta, plus une requête de comptage par terme visible.
Solution

Précalculer un index JSON à plat des produits et des variations, le charger dans le navigateur et exécuter le filtre comme du SQL dessus avec AlaSQL. Le serveur n'affiche que les ID correspondants. Inclus dans les deux éditions.

Stack
  • PHP
  • JSON
  • AlaSQL
  • JavaScript
  • Cron
Tâches · 5
Les données d'un produit sont réparties entre posts, postmeta, termes et variationsAplatir chaque produit et chaque variation en un seul enregistrement JSON : prix, termes, meta, stock

get_data_by_id() écrit l'ID, le titre, le SKU, l'auteur, le parent, le statut de stock, les slugs de termes de chaque taxonomie filtrable (les attributs de variation pour les variations), les champs meta configurés et une liste de paires prix normal/prix promotionnel. Un produit variable stocke une paire par enfant, de sorte que le navigateur peut calculer sa fourchette de prix avec la même règle que WooCommerce : le prix promotionnel ne compte que s'il est inférieur au prix normal.

Les prix sont stockés taxes comprises lorsque la boutique affiche des prix TTC, et FOX est basculé sur la devise par défaut avant leur lecture, de sorte que l'index est dans une seule devise.

Indexer des milliers de produits atteint les limites de temps de PHPConstruire par étapes AJAX de 10 produits, un fichier par langue WPML et par blog multisite

create_data_search_files() prend un décalage, traite 10 ID de produits et de variations en appliquant les règles de visibilité, les fusionne dans le fichier JSON et renvoie le décalage suivant ; le script d'administration boucle jusqu'à recevoir -1. WPML est basculé sur la langue par défaut et chaque produit est écrit dans le fichier de chaque langue grâce à son ID de traduction. check_id_file() ajoute l'ID du blog en multisite pour que les sites ne partagent pas de fichier.

Le plafond par défaut est de 15,000 enregistrements, ajustable avec le filtre woof_turbo_mode_products_limit.

Une reconstruction planifiée ne doit pas servir un index à moitié écritUn exécuteur cron propre traite des tranches d'ID par requête et substitue à la fin un fichier _temp

PN_WP_CRON_WOOF_TURBO_MODE conserve l'état de l'événement (liste complète des ID, position, prochaine exécution) dans une option et s'exécute sur init. Chaque appel de process() traite une tranche de 10 ID, et une limitation de 5 secondes stockée dans woof_cron_limit_do empêche une rafale d'affichages de pages d'enchaîner les tranches. Les tranches sont écrites dans data_<id>_temp_<lang>.json ; lorsque la liste est épuisée, do_recreate_file() déplace le fichier temporaire par-dessus le fichier actif, et les visiteurs gardent l'ancien index jusqu'à ce que le nouveau soit complet.

Le prix de cette conception est le retard des données : les produits ajoutés après la dernière reconstruction sont invisibles pour le filtre, et la reconstruction dépend du trafic du site, comme WP-Cron.

L'état du filtre doit devenir une requête que le navigateur sait exécuterGénérer des clauses WHERE AlaSQL avec des fonctions personnalisées pour les plages de prix et la visibilité

turbo_mode.js transforme les valeurs courantes en une chaîne WHERE : appartenance aux taxonomies via indexOf sur les tableaux de termes, comparaisons de meta, LIKE pour le SKU et le titre, BETWEEN pour les curseurs et les dates. Des fonctions AlaSQL personnalisées calculent le prix min et max d'un produit à partir de sa liste de variations et appliquent la visibilité du catalogue. Pour les produits variables, le filtre « en stock » sélectionne d'abord les parents des variations en rupture de stock qui correspondent aux autres filtres. Le recomptage s'exécute sur la même table en mémoire.

Les ID obtenus sont envoyés à woof_draw_products sous le nom turbo_mode_ids, et le serveur exécute une requête post__in à la place des jointures du filtre.

Le navigateur a besoin des réglages, mais ils contiennent des secrets du serveurRetirer des réglages localisés les identifiants de la base de statistiques et les clés secrètes du cron

Le JS reconstruit la logique du filtre à partir de woof_settings, il a donc besoin de la plus grande partie de cette option dans la page. get_public_settings() retire le bloc des statistiques, qui contient les identifiants MySQL externes, ainsi que chaque cron_secret_key, et transmet le reste à un filtre pour d'autres retraits. Dans la version premium, le point d'accès de reconstruction exige un nonce et manage_woocommerce.

Statistiques de recherche

dans le produit

Ce que cela apporteLe propriétaire voit ce que les acheteurs filtrent et recherchent réellement, sans ralentir la boutique ni remplir sa base de données d'un journal en croissance continue.

Problème
Enregistrer ce que les acheteurs filtrent revient à écrire sur le chemin critique de chaque affichage de page filtrée, pour des visiteurs anonymes et des robots, souvent sur un hébergement mutualisé.
Solution

Écrire une petite ligne brute par requête dans une table d'attente, et conserver les statistiques à long terme sur un serveur MySQL distinct que l'extension exige.

Stack
  • MySQL
  • PDO
Tâches · 2
L'enregistrement ne doit pas multiplier les écritures ni être inondé par un seul clientEnregistrer une fois par requête, dédupliquer par hachage requête+IP+jour, plafonner les lignes en attente par IP

L'extension de statistiques se branche sur woof_get_request_data, qui s'exécute de nombreuses fois par page ; un indicateur statique limite l'enregistrement au premier appel, et les appels AJAX autres que l'affichage des produits sont ignorés. Seules les clés de filtre choisies dans les réglages sont conservées, la requête est réduite à request_max_deep clés (5 par défaut) tout en préservant la plage de prix, puis elle est hachée avec l'IP et la date.

Une ligne n'est insérée que si ce hachage est nouveau et que l'IP a moins de user_max_requests lignes (10 par défaut) dans la table d'attente.

Les statistiques croissent sans limite dans la base de la boutiqueStocker les statistiques sur un serveur MySQL distinct via PDO, une table par mois

L'extension a besoin de son propre serveur MySQL, indiqué dans les options. init_pdo() s'y connecte avec PDO, car $wpdb ne peut pas porter une seconde connexion avec d'autres identifiants. place_statdata_into_db() crée une table par mois nommée <year>_<month> et insère les lignes avec des requêtes préparées.

Les rapports interrogent les tables mensuelles par plage de dates, de sorte que la base de la boutique ne contient pas les statistiques à long terme.

Durcissement et intégrité des réglages

dans le produit

Ce que cela apporteLes visiteurs anonymes ne peuvent pas utiliser le filtre pour exécuter du code ou atteindre des données cachées, et les réglages enregistrés ne sont ni perdus ni écrasés lors des modifications.

Problème
Le filtre reçoit des chaînes de requête et des envois AJAX de visiteurs anonymes, affiche des shortcodes et des templates nommés dans ces requêtes, et stocke des réglages qui contiennent du HTML, du JavaScript et du SQL. Le changelog mentionne des chercheurs extérieurs pour une série de signalements de sécurité de 2022 à 2026.
Solution

Traiter chaque valeur de requête comme une donnée : n'autoriser que les shortcodes et templates connus, assainir les réglages de façon récursive selon le chemin de la clé, exiger des capacités pour tout ce qui stocke du code, et fusionner les réglages au lieu de les écraser.

Stack
  • PHP
  • WordPress sanitization API
  • WPML
  • Polylang
Tâches · 5
Les requêtes AJAX transportent le nom d'un template PHP à afficherN'accepter que les noms de templates vus d'abord dans le contenu de la page ; l'AJAX ne peut pas en ajouter de nouveaux

[woof_products custom_tpl=...] est légitimement renvoyé dans les appels AJAX, la valeur ne peut donc être ni crue ni écartée. sanitize_custom_tpl() ne conserve que les lettres, les chiffres, '_', '-' et '/', ce qui supprime les points et les séquences de traversée de répertoires. Un nom affiché à partir du vrai contenu d'une page est ajouté à une option de liste d'autorisation (les 20 derniers conservés) ; pendant l'AJAX, seuls les noms déjà présents dans cette liste sont acceptés.

Les chemins d'inclusion construits à partir de noms de types de vue passent par safe_view_slug(), qui rejette tout ce qui n'est pas un slug simple.

Les requêtes AJAX transportent du texte de shortcode qui ne doit pas exécuter des shortcodes arbitrairesExtraire du texte envoyé uniquement les balises propres à l'extension avec get_shortcode_regex

woof_draw_products() reçoit de la page le texte des shortcodes de produits et de formulaire, pour que la sortie AJAX corresponde à l'appel d'origine. check_shortcode() construit get_shortcode_regex() pour les seules balises de l'extension ([woof_products] ou [woof], plus les alias du shortcode de produits de l'extension) et conserve la première correspondance ; tout le reste de la chaîne est écarté avant do_shortcode().

Les réglages mêlent HTML, JavaScript, slugs et texte brut à n'importe quelle profondeurAssainir récursivement ; classer chaque champ selon sa clé ou son chemin dans l'arbre des réglages

sanitize_array() parcourt l'arbre des réglages, stocke chaque valeur sous sa clé assainie et classe chaque champ par son nom ou par son chemin dans l'arbre : le texte des règles SEO comme HTML via wp_kses_post, les descriptions comme textarea, les clés utilisées dans les chemins d'inclusion comme slugs stricts, tout le reste comme texte brut. Les règles par chemin sont nécessaires parce que des clés situées en profondeur dans l'arbre, comme text ou description, sont trop génériques pour être listées globalement.

Le réglage « JavaScript after AJAX » ne peut être modifié que par les utilisateurs disposant de unfiltered_html.

La réponse AJAX injecte des fragments HTML, et la requête est reconstruite à partir de l'URL du visiteurRetirer woof_redraw_elements du lien analysé (premium) ; faire passer chaque fragment par wp_kses

Le rendu AJAX reconstruit $_GET à partir du lien de page envoyé par le navigateur, et la réponse injecte woof_redraw_elements dans la page sous forme de HTML ; un lien forgé ne doit donc pas pouvoir fournir cette clé. Dans la version premium, la clé est retirée du lien analysé. Les fragments légitimes (titre SEO et H1, nombre de résultats, tri) sont ajoutés par des filtres côté serveur, et chaque fragment passe par wp_kses avant d'être renvoyé.

Les couleurs et les images des termes doivent être définies une fois pour toutes les languesFusionner les couleurs par terme soumises sur celles stockées ; se rabattre sur le terme de la langue par défaut

La page de réglages ne liste que les termes de la langue WPML ou Polylang courante, si bien qu'un enregistrement n'envoie les couleurs que pour ces termes. woof_save_options() fusionne les entrées stockées color, color_img et images_term_* sous les valeurs soumises, de sorte que les autres langues conservent les leurs.

Un terme traduit sans couleur propre utilise son original dans la langue par défaut, trouvé par get_original_language_term(). Elle lit le slug directement dans wp_terms, car avec l'ajustement des ID de WPML, get_term() peut renvoyer de nouveau le terme traduit.

Débogage en conditions réelles

support forum

Ce que cela apporteLes problèmes d'une boutique en production sont trouvés à leur vraie cause, même lorsqu'il s'agit du thème, du serveur, d'un cache ou d'une autre extension.

Problème
Un signalement venant d'une boutique en production nomme un symptôme, pas une cause. Le même symptôme peut venir d'un template du thème, d'une couche de cache ou d'optimisation, du serveur, d'une autre extension ou du filtre lui-même.
Solution

Reproduire sur une installation propre ou une copie du site, écarter une couche à la fois, puis corriger là où se trouve la cause : dans l'extension pour tous, ou par une modification ciblée sur ce site.

Stack
  • Browser DevTools
  • Nginx
  • LiteSpeed Cache
  • jQuery
  • ACF
  • MySQL
Tâches · 5
Les bascules de sous-catégories disparaissent et le filtrage casse dès que l'URL comporte une chaîne de requêteFaute isolée côté serveur par un test avec un paramètre GET arbitraire, puis réécriture Nginx corrigée

Même un paramètre sans signification comme ?dwdadd=1adw cassait la page, ce qui prouvait que la faute venait du traitement de la chaîne de requête et non du filtre. La configuration Nginx insérait un ? parasite après php et supprimait tous les paramètres, si bien que le filtre ne recevait jamais ses arguments.

La boutique cesse de se rafraîchir après un filtrage dès que LiteSpeed Cache est installéJournalisation de la réponse AJAX pour révéler une erreur d'analyse JSON, puis recherche dichotomique dans les réglages de LiteSpeed jusqu'à CDN Mapping

La réponse arrivait mais JSON.parse échouait sur un caractère échappé invalide, ce qui signifiait que CDN Mapping de LiteSpeed réécrivait des URL à l'intérieur. Désactiver CDN Mapping a rétabli le filtrage.

Les boutons Filtrer et Réinitialiser modifient l'URL mais la liste de produits ne se recharge jamaisCause retrouvée dans la version allégée de jQuery ; jQuery complet restauré et cœur WordPress modifié par l'hébergeur remplacé par une installation propre

Le site chargeait jQuery slim, qui n'a pas de module AJAX, si bien que la requête des résultats filtrés n'était jamais envoyée alors que l'URL changeait côté client ; désactiver toutes les autres extensions n'y changeait rien. La version allégée venait d'un cœur WordPress modifié par l'installateur automatique de l'hébergeur ; restaurer jQuery complet et réinstaller le cœur a réglé le problème.

Les curseurs meta sur de petites valeurs décimales ACF (autour de .005) ne filtrent rien ou toutAjout du filtre woof_slider_meta_query_type pour fixer le cast SQL à DECIMAL par clé meta

L'auteur du signalement a recréé le champ comme simple champ personnalisé et la faute est restée, ACF n'était donc pas en cause ; le support l'a reproduite. La meta query comparait les valeurs sans cast DECIMAL, et l'extension a reçu le hook woof_slider_meta_query_type, qui reçoit $type et $key.

Le filtre se fige après le bouton Retour du navigateur ; Rechercher ne fait rienAjout d'un écouteur pageshow qui réinitialise le verrou d'envoi et la fenêtre contextuelle lors d'une restauration depuis le bfcache, livré dans les mises à jour

Les pages restaurées depuis le cache précédent/suivant gardaient woof_submit_link_locked activé et la fenêtre de chargement ouverte. L'écouteur vérifie event.persisted ou le type de navigation 2 et réinitialise les deux ; la modification a été intégrée à l'extension.

Chiffres

wordpress.org
  • Installations actives 80 000+ compteur de wordpress.org.
  • Avis 336 Moyenne de 4.6 sur 5.
  • Années de maintenance 12 Depuis 1 décembre 2014, dernière mise à jour 9 octobre 2026.