HUSKY aka WOOF
Permet aux acheteurs de restreindre un grand catalogue par catégorie, attribut, prix, stock et texte, avec des pages de filtres que Google peut indexer.
Je mène un produit de la première spécification jusqu'à la file de support, des années plus tard. 12 extensions sur wordpress.org, un SaaS qui transforme une boutique WooCommerce en bot Telegram, une extension Chrome et des applications Android.
const realmag777 = { role: ["Senior WordPress & WooCommerce Developer", "Plugins & Products Architect"], inIT: 2006, ownProductsSince: 2012, // first plugin on wordpress.org activeInstalls: 167_000, // sum of wordpress.org counters stack: ["PHP", "WordPress", "WooCommerce", "MySQL", "JavaScript", "Node.js", "HTML5", "CSS3", "Docker", "MCP"],}; export default realmag777;
Projets
Chaque page de projet présente les problèmes résolus dans le produit, regroupés par domaine. Les nombres d'installations et les notes proviennent de wordpress.org, où vous pouvez lire ce qu'écrivent les utilisateurs : avis sur HUSKY, avis sur FOX, avis sur BEAR.
Permet aux acheteurs de restreindre un grand catalogue par catégorie, attribut, prix, stock et texte, avec des pages de filtres que Google peut indexer.
Affiche les prix dans la devise du visiteur et peut encaisser le paiement dans cette devise, pour que les clients étrangers sachent ce qu'ils paient.
Permet au personnel de modifier des milliers de produits comme dans un tableur, avec chaque modification annulable, et permet aux assistants IA de le faire en toute sécurité.
Affiche le catalogue sous forme de tableaux de produits triables et filtrables, où les clients ajoutent des articles au panier directement depuis chaque ligne.
Permet au personnel du site de modifier, filtrer, exporter et annuler des modifications sur des milliers d'articles, de pages et de contenus personnalisés dans un seul écran façon tableur.
Les visiteurs recherchent des annonces, des articles ou des produits selon plusieurs critères à la fois, comme le prix, l'année et la catégorie.
Affiche les prix écrits dans le contenu du site dans la devise de chaque visiteur, sans avoir besoin d'une boutique WooCommerce.
Affiche des articles ou des produits sous forme de tableaux triables et consultables, que les visiteurs peuvent filtrer et parcourir sur n'importe quel appareil.
Associez un ensemble d'images à n'importe quel article et affichez-les sous forme de galeries ou de diaporamas partout sur le site.
Des listes déroulantes pas à pas, comme marque, modèle et génération, guident les visiteurs dans un grand catalogue jusqu'à la bonne page ou au bon produit.
Une boutique WooCommerce fonctionnant comme un bot Telegram : les clients parcouraient le catalogue, remplissaient un panier et payaient sans quitter la conversation.
Une extension Chrome qui saisit ce que vous dites dans n'importe quel champ de texte de n'importe quel site, avec un nettoyage par IA en option.
Un chronomètre Android hors ligne avec temps de tour, qui continue de compter correctement même lorsque l'application est en arrière-plan.
Un lecteur qui affiche un texte mot par mot à la vitesse choisie et se souvient d'où vous vous êtes arrêté.
Des entreprises sous forme de bulles sur un graphique à quadrants, qui se déplacent lorsque les pondérations du modèle changent.
Collecte d'événements, agrégation et rapports avec graphiques dans l'administration WordPress.
Un acheteur s'abonne à une requête de filtre et reçoit un e-mail lorsque de nouveaux produits correspondants apparaissent.
Un PDF mis en forme avec toutes les voitures de l'ensemble de résultats en cours d'un catalogue de véhicules.
Ce qui a été résolu · 14 produits
Choisissez un produit. Chaque onglet correspond à un domaine de son travail et commence par ce qu'il apporte à son propriétaire, en termes simples. Dessous se trouve la partie technique, destinée à vos équipes techniques : le problème et la manière dont il a été résolu, d'après le code source. Ouvrez une ligne pour les détails.
ce qui a été résolu · choisissez un projet
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.
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
_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.
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.
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.
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.
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.
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.
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.
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 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.
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.
É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.
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.
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.
Ce que cela apporteLes acheteurs trouvent des produits par SKU, par attribut ou par des mots des descriptions de variations. Les clients inscrits peuvent enregistrer une recherche et être prévenus lorsque de nouveaux produits correspondants arrivent.
Construire la clause WHERE à partir de fragments préparés par mot, avec des jointures ajoutées seulement si nécessaire, résoudre les variations en parents dans une requête séparée, borner ce que le point d'accès public peut demander, et relancer les recherches enregistrées à partir du SQL capturé côté serveur.
create_where_query() découpe l'expression en mots. Pour chaque mot, elle combine en OR les champs de post sélectionnés et les clés meta listées (le SKU n'est que _sku), plus une correspondance du nom de terme sur l'expression entière, et les groupes de mots sont combinés en AND. Un '-' initial transforme un mot en NOT LIKE joint par AND. Les fragments passent par $wpdb->prepare().
Les jointures de termes et de postmeta ne sont ajoutées dans posts_join que lorsque ces options sont activées, et posts_groupby fusionne les lignes en double créées par les jointures.
Les variations sont des posts distincts, et la requête parente ne peut pas voir _variation_description. Lorsque l'option est activée, une requête trouve les posts product_variation dont la description contient tous les mots, une deuxième les associe à post_parent, et la clause principale devient (conditions de mots OR ID IN (parents)). La liste des parents peut être mise en cache dans le cache d'objets par liste de mots.
ajax_search() est ouvert aux invités et pagine la requête en SQL avec max_posts comme taille de page, de sorte qu'une seule page de résultats est chargée. Dans la version premium, max_posts reçu dans la requête est borné entre 1 et la plus grande valeur entre le réglage d'administration et un 100 filtrable. Les suggestions respectent l'état courant du filtre, la page de catégorie et la restriction aux produits en promotion.
Le filtre texte construit sa propre requête de recherche, si bien qu'une extension de recherche distincte ne peut pas décider de l'ensemble des résultats dans la même requête ; les fils de support citent Ivory Search, Relevanssi et Advanced Woo Search parmi ces conflits. get_all_ids() applique d'abord le filtre woof_text_search_ids. Lorsqu'un callback retourne un tableau, par exemple des ID issus de SearchWP, ce tableau devient l'ensemble des correspondances et le SQL intégré est ignoré. Connecter un moteur donné demande du code sur mesure.
La vue Products Messenger enregistre $wp_query->request, le SQL généré par WordPress pour les résultats filtrés, dans le stockage par visiteur de l'extension, sous l'ID de l'utilisateur. À l'abonnement, qui exige un utilisateur connecté, le serveur le relit depuis ce stockage plutôt que depuis la requête, retire LIMIT, vérifie qu'il s'agit d'un seul SELECT sans mots-clés DML ou DDL, l'exécute une fois pour enregistrer une base de référence d'ID et stocke l'abonnement dans les user meta.
La tâche planifiée relance chaque requête enregistrée et n'envoie par e-mail que les ID absents de la base de référence, puis étend cette base. Un abonnement prend fin au bout d'un certain nombre de messages ou à une date. Les user meta ne sont mises à jour que si wp_mail() réussit, de sorte qu'un envoi échoué est retenté à l'exécution suivante.
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.
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.
[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.
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().
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.
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é.
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.
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.
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.
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 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.
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.
woof_slider_meta_query_type pour fixer le cast SQL à DECIMAL par clé metaL'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.
pageshow qui réinitialise le verrou d'envoi et la fenêtre contextuelle lors d'une restauration depuis le bfcache, livré dans les mises à jourLes 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.
Anneau intérieur : stack principale · anneau extérieur : outils et plateformes
À propos · stack
Je conçois l'architecture et le modèle de données, puis j'écris le code, je le livre et je le maintiens. Aucune couche n'est confiée à quelqu'un que je ne peux pas vérifier.
Sur mesure
Des travaux sur mesure pour des clients, le plus souvent des personnes qui avaient commencé avec l'une de mes extensions. Les noms et les sites restent confidentiels.
Un grand annuaire de fournisseurs B2B construit sur WordPress.
Le même portail WordPress.
Né d'un travail sur mesure pour le propriétaire d'une boutique, puis livré comme l'extension Products Messenger pour HUSKY et MDTF.
Un catalogue de véhicules construit sur MDTF, module complémentaire sur mesure pour un client en Allemagne.
Intégré, pas rapporté
Les propriétaires obtiennent les chiffres dans la même administration où ils configurent l'extension.
Chronologie
Uniquement mes propres produits. Les dates de sortie sont les dates d'ajout de chaque extension à wordpress.org.
Avant de m'écrire
Décrivez la tâche dans le formulaire ci-dessous ou par e-mail à realmag777 [at] gmail.com : ce qui doit être créé ou modifié, et pour quand. Si vous avez un site de préproduction, mentionnez-le : c'est là que le travail est réalisé et vérifié. Je réponds sous deux jours ouvrés, avec des questions ou avec une estimation du délai et du coût. Le travail commence une fois le périmètre convenu.
Une tâche clairement décrite reçoit une estimation forfaitaire pour le périmètre convenu. Les travaux ouverts, comme la recherche de la cause d'un problème sur un site en production ou le support continu, sont convenus à l'heure.
Heure d'Europe centrale (UTC+1, UTC+2 en été). J'écris et je parle en anglais, ukrainien et espagnol. L'essentiel du travail passe par e-mail et messages, donc le décalage horaire compte rarement.
Oui. Les noms des clients et leurs sites ne sont jamais montrés ici, et je signe un NDA lorsque le projet en demande un.
Non. Je développe des fonctionnalités par-dessus mes extensions ou n'importe quelles autres, je travaille dans du code écrit par d'autres développeurs et je conçois de nouveaux projets à partir de zéro. Avec le code de quelqu'un d'autre, je commence par le lire et je vous dis ce que j'ai trouvé avant de rien modifier.
Seul. L'architecture, le code, la livraison et le support sont de mon ressort : vous parlez à la personne qui construit la chose, et rien ne se perd entre les intervenants.
Oui, y compris sous la marque de l'agence. Dites-moi qui vous êtes et ce dont vous avez besoin.
Selon accord. Parlez-moi du projet et du volume attendu, et nous fixons cela avant le début du travail.
En euros.
D'un site de préproduction, une copie du site en production, avec un accès administrateur et aux fichiers, ainsi que d'une copie de la base de données lorsque la tâche concerne les données. S'il n'y a pas de site de préproduction, j'en mets un en place. Les modifications n'arrivent sur le site en production qu'une fois qu'elles fonctionnent en préproduction.
À vous, une fois le travail payé.
Les erreurs dans le travail livré sont corrigées. Les modifications ultérieures et le support peuvent être convenus séparément. Mes propres extensions sont maintenues depuis plus de dix ans : la maintenance à long terme est donc le cas normal, pas une exception.
Contact
Décrivez ce qui doit être créé ou modifié, et pour quand. Je réponds sous deux jours ouvrés.
realmag777 [at] gmail.com linkedin.com/in/realmag777