Sprache

Kontakt aufnehmen

WooCommerce-Plugin · seit 2014 · aka WOOF

HUSKY

Ein Facettenfilter für WooCommerce-Produkte: Taxonomien, Attribute, Preis, Lagerbestand, Meta-Felder und Text, als Widget oder Shortcode, mit Seiten-Reload oder AJAX.

Erstveröffentlichung
Aktive Installationen
80.000+
Bewertung
4.6 / 5 (336)Rezensionen lesen
  • PHP
  • WordPress
  • WooCommerce
  • WP_Query
  • WP_Tax_Query
  • WP_Meta_Query
  • MySQL
  • Transients
  • Object cache
  • jQuery
  • AlaSQL
  • PDO
  • WPML
  • Polylang

Über mich

HUSKY filtert WooCommerce-Produkte nach Kategorien, Attributen, Schlagwörtern, Preis, Lagerstatus, Bewertung, Autor, SKU, benutzerdefinierten Meta-Feldern und Text. Das Plugin ist seit 2014 auf wordpress.org verfügbar. Die kostenlose und die Premium-Edition werden aus einer Codebasis gebaut; die Edition wird über die Hauptversionsnummer bestimmt, und Premium-Teile sind in der kostenlosen Version entweder durch Platzhalter ersetzt oder begrenzt.

Jeder Filterzustand endet als WordPress-Abfrage über wp_posts, wp_postmeta und die Term-Tabellen, und Shops führen zehntausende Produkte und Varianten. Die Anzeige, wie viele Produkte jede Option liefern würde, bedeutet bei jedem Seitenaufruf eine weitere Abfrage pro sichtbarem Term. Variable Produkte, Katalogsichtbarkeit, Steuern und Währungs-Plugins verändern jeweils, was als Treffer gilt.

Das Plugin läuft außerdem innerhalb einer Request-Verarbeitung, die ihm nicht gehört: WordPress-Rewrite-Parsing, die Hauptproduktabfrage und die Templates von WooCommerce, Page Builder, mehrsprachige Plugins und Caching-Schichten. Der größte Teil des unten beschriebenen Codes existiert, weil eine dieser Schichten den direkten Weg falsch oder zu langsam gemacht hat.

HUSKY · Produktfilter

was gelöst wurde

LIVEseit 2014

SEO-URLs für Filterzustände

im Produkt

Was es bringtGefilterte Seiten erhalten saubere, stabile Adressen, die Suchmaschinen indexieren können. Der Inhaber entscheidet, welche Filterseiten in Google erscheinen und mit welchem Titel.

Problem
Filterzustände sind Query-Strings, die Suchmaschinen entweder ignorieren oder unbegrenzt crawlen. Pfad-URLs wie /swoof/color-red/size-m/ sind den Rewrite-Regeln von WordPress unbekannt und kollidieren mit kanonischen Weiterleitungen.
Lösung

Das Request-Parsing abfangen, das Filtersegment entfernen, den Rest von WordPress zuordnen lassen und den Filterzustand aus dem Segment mit fester Kodierung und Indexregeln je Tiefe neu aufbauen.

Stack
  • WordPress rewrite API
  • PHP
  • JavaScript
  • Yoast SEO
Aufgaben · 6
/shop/swoof/color-red/size-xl/ passt auf keine Rewrite-Regel und liefert 404Das Filtersegment abschneiden, den Rest über die Core-Rewrite-Regeln routen, die Filter daraus neu aufbauen

Ein Filterlink ist die Archiv-URL plus der Filter-Slug (standardmäßig swoof) und ein Segment pro Filter: /product-category/shoes/swoof/color-red-and-blue/size-xl/. Das Plugin registriert keine Rewrite-Regeln. url_process() läuft im Frontend auf do_parse_request und kehrt sofort zurück, sofern get_search_request() nicht /<slug>/ im Pfad findet. Es nimmt das Filtersegment bis /page/, ? oder #, entfernt es und gleicht den Rest (/product-category/shoes/ oder /shop/page/2) mit $wp_rewrite->wp_rewrite_rules() ab, mit aus WP::parse_request() kopiertem Code: ausführliche Seitenregeln, öffentliche und private Query-Variablen, sichtbare Post-Types, nicht abfragbare Taxonomien, wp_resolve_numeric_slug_conflicts() und der request-Filter. Danach ruft es query_posts(), handle_404() und register_globals() auf (vor WordPress 6.0 die Action parse_request) und gibt false zurück, sodass der Core die URL nicht noch einmal parst.

parse_url_query() teilt das Segment an '/' und gleicht jeden Teil mit der Namensübersicht aus get_all_items() ab: Attribut-Slugs ohne pa_, per WPML übersetzte Taxonomie-Slugs, price und die festen Wörter instock, onsale, featured, backorder_not_in. -and- und -or- werden zu Kommas, -to- zu einem Bereichstrenner, + zu einem Leerzeichen; price-10-to-50 wird zu min_price und max_price. add_request() führt das Ergebnis über den Filter woof_get_request_data zusammen, sodass get_request_data() dasselbe Array liefert wie bei einem Query-String ?swoof=1&pa_color=red. Bei der Hauptabfrage setzt parse_query() dann post_type product, markiert die Abfrage als Post-Type-Archiv und fügt die Meta-Abfragen hinzu, und tax_query() auf woocommerce_product_query_tax_query ersetzt die Einträge von WooCommerce für dieselben Taxonomien durch die Tax-Query des Plugins.

Weil der Rest über die vorhandenen Regeln geroutet wird, funktionieren Shop-Basis, Produktkategorie-Basis und Archiv-Paginierung ohne neue Regeln weiter: /page/N/ wird abgeschnitten, bevor das Filtersegment gesucht wird, und bleibt im gerouteten Pfad. parse_query() schaltet is_tax auf Filterseiten aus, daher wird die aktuelle Kategorie aus dem letzten Segment der bereinigten URL ermittelt (get_term_from_cleared_url()). Wenn die Shop-Seite zugleich die Startseite ist, verhindert die unten beschriebene redirect_canonical-Korrektur, dass WooCommerce die Filter-URL auf / umleitet.

Bei einer URL, die auf eine SEO-Regel passt, gibt meta_head() den Canonical aus: die Filter-URL ohne Query-String, wenn der Zustand indexierbar ist, wobei die Canonicals von Yoast und SEOPress abgeschaltet werden, andernfalls die am Filter-Slug abgeschnittene URL, also das einfache Archiv. Mit Yoast SEO hängt sitemap_index() auf wpseo_sitemap_index die URLs an, die ein Admin in einem Textfeld der Einstellungen einträgt, eine pro Zeile; der Premium-Build maskiert sie mit esc_url_raw() und esc_xml().

Die Reihenfolge, in der Filter benutzt werden, soll die URL nicht verändernFilter in sortierter Schlüsselreihenfolge schreiben; die Grammatik -and- / -to- / + in PHP und JS spiegeln

woof_get_submit_link() in url_parser.js sortiert die aktiven Filter nach Schlüssel und schreibt jeden als <name>-<values>, mit ',' als -and-, einem Bereichstrenner als -to- und Leerzeichen als +. Der Preis wird zu price-<min>-to-<max>; auf Lager, im Angebot, hervorgehoben und nachbestellbar werden zu festen Wörtern. parse_url_query() kehrt die Zuordnung um.

Durch die Sortierung ergibt die Wahl von color zuerst und size danach denselben Pfad wie in umgekehrter Reihenfolge. Werte innerhalb eines Filters werden in der Reihenfolge geschrieben, in der sie ausgewählt wurden.

Attribut pa_niveau und Meta-Feld niveau verlangen denselben URL-NamenNamenskollisionen erkennen: die Taxonomie behält den Kurznamen, der andere erhält einen eindeutigen Ersatznamen

URL-Namen lassen das Präfix pa_ weg, daher können ein Attribut und ein Meta-Schlüssel denselben Namen ergeben, und ein Eintrag in der Übersicht würde den anderen verdecken. resolve_seo_key_conflict() gibt den Kurznamen unabhängig von der Reihenfolge der Filter in den Einstellungen der registrierten Taxonomie, damit bestehende URLs gültig bleiben, und leitet für den anderen Filter einen Namen aus seinem echten Schlüssel ab, bei Bedarf mit numerischem Suffix. Per WPML übersetzte Taxonomie-Slugs werden vor diesem Schritt aufgelöst.

Jede Neuzählungs-Abfrage liest den Filterzustand erneut aus dem URL-Pfadparse_url_query() pro URL zwischenspeichern, aber erst nach wp_loaded

get_request_data() wird von jeder Neuzählungs-Abfrage aufgerufen, und bei SEO-URLs läuft jeder Aufruf über woof_get_request_data zu parse_url_query(). Mit Dynamic Recount und Hide Empty (Premium) kann eine Seite mehr als tausend solcher Aufrufe auslösen, daher wird das Parse-Ergebnis pro URL zwischengespeichert.

Der Cache wird erst nach wp_loaded aktiviert, damit ein Aufruf vor dem Vorhandensein von Taxonomien und Einstellungen keine unvollständige Namensübersicht für den Rest des Requests festschreiben kann.

Welche Filterseiten indexiert werden, und mit welchem Titel und welchen Meta-AngabenIndexierung nach Tiefe und nach Regex-Regeln mit Meta-Angaben pro Regel; fehlerhafte Muster still überspringen

WOOF_SEO zählt die aktiven Filter. Oberhalb von index_deep (Standard 2) erhält die Seite die Einstellung 'Disable page indexing' (Standard ja, also noindex,nofollow über wp_robots), und Regeln werden nicht geprüft. Bei oder unterhalb davon macht eine passende Regel die Seite indexierbar. Regeln sind URL-Muster mit {any}-Platzhaltern, die zu regulären Ausdrücken kompiliert werden, jeweils mit Titel, Meta-Beschreibung, H1 und SEO-Text, die die Namen der gewählten Terme enthalten können, und werden pro Locale gespeichert. Canonical-Tags werden nur für URLs ausgegeben, die auf eine Regel passen.

Im Premium-Build läuft preg_match unter einem temporären Error-Handler, sodass eine Regel, deren URL kein gültiger Ausdruck ist, übersprungen und in einem Admin-Hinweis aufgelistet wird; eine leere Regel wird übersprungen. Die kostenlose Edition verwendet nur die ersten zwei Regeln.

WooCommerce 11 leitete Filter-URLs auf einer Shop-Startseite umNur die Weiterleitung auf home_url aufheben, und nur für URLs mit dem Filter-Slug

Wenn die Shop-Seite zugleich die statische Startseite ist, setzt WooCommerce 11.0.0 sie bei Produktarchiven als abgefragtes Objekt. redirect_canonical() behandelt /swoof/... dann als nicht kanonische Kopie der Startseite und leitet auf / um, wobei jeder Filter verloren geht. fix_front_page_shop_canonical() gibt nur dann false zurück, wenn die Startseite die Shop-Seite ist, der Pfad den Filter-Slug enthält und das Weiterleitungsziel die Home-URL ist, sodass die Normalisierung von Slashes und Slugs überall sonst weiter funktioniert.

Dynamische Neuzählung und Query-Caching

im Produkt

Was es bringtJede Filteroption zeigt, wie viele Produkte sie liefern wird, sodass Kunden Sackgassen vermeiden. Die Zahlen werden zwischengespeichert, daher bleibt der Shop auch bei großen Katalogen schnell.

Problem
Jede Option im Filter zeigt, wie viele Produkte sie bei der aktuellen Auswahl liefern würde. Das ist bei jedem Seitenaufruf eine Produktabfrage pro sichtbarem Term und der größte Kostenfaktor bei großen Katalogen.
Lösung

Die Zahlen stammen aus einer abgespeckten Abfrage, die nur IDs liefert und die Filtersemantik nachbildet. Ergebnisse werden pro Request, in einer eigenen Tabelle und in Transients zwischengespeichert, und das Formular kann zurückgestellt werden, bis ein Besucher es anfordert.

Stack
  • WP_Query
  • MySQL
  • Transients
  • Object cache
Aufgaben · 6
Die Zahl eines Terms muss den Rest der aktuellen Auswahl berücksichtigenDie Abfrage pro Term neu aufbauen und den Term nach den Regeln für Einzel- oder Mehrfachauswahl einsetzen

dynamic_count() baut die tax_query für jeden Term neu auf. Bereits gewählte Terme erhalten keine Zahl. Bei Radio und Select (single) ersetzt der Term den gewählten Wert seiner Taxonomie; bei Checkbox und Mehrfachauswahl (multi) wird er hinzugefügt. Terme der aktuellen Kategorieseite und der Taxonomien aus dem Shortcode-Attribut taxonomies werden immer per AND hinzugenommen, sodass die Zahlen auf einer Kategorieseite innerhalb dieser Kategorie bleiben.

Die Logik pro Taxonomie (OR, AND, NOT IN) aus den Einstellungen, Preis- und Bewertungsbereiche, Meta-Bedingungen von Erweiterungen, die WPML-Sprache und die Katalogsichtbarkeit werden genauso angewendet wie in der Produktabfrage. Erweiterungen deklarieren ihr Neuzählungsverhalten über html_type_dynamic_recount_behavior, daher nutzen die Typen Farbe, Bild und Label diesen Code mit.

Das Ausblenden von Optionen ohne Ergebnisse baut auf diesen Zahlen auf: der Term wird gezählt und bei der Zahl 0 übersprungen, daher kann es nicht günstiger sein als das Anzeigen der Zahlen. Dieser Schalter ist nur in Premium verfügbar; die kostenlose Edition erzwingt ihn in den Views als ausgeschaltet.

Eine Zählabfrage soll keine Beiträge laden und kein FOUND_ROWS ausführenWP_Query ableiten, die Methoden für Found-Rows und Loop als No-Ops überschreiben, nur IDs holen

WP_QueryWOOFCounterIn erweitert WP_Query und überschreibt set_found_posts(), setup_postdata(), the_post() und have_posts(). Die Zählabfrage läuft mit fields=ids und nopaging, sodass MySQL eine Liste von IDs liefert, WordPress keine Post-Objekte aufbaut und keine zweite FOUND_ROWS-Abfrage absetzt. Die Zahl ist die Länge der Liste.

Die posts_clauses-Filter von WooCommerce für Beliebtheit und Bewertung werden vor der Zählabfrage entfernt, weil die Sortierung für eine Zahl keine Rolle spielt und diese Filter dem SQL Joins hinzufügen.

Dieselben Zahlen werden für jeden Besucher und jede Seite neu berechnetZahlen in einer Key-Value-Tabelle ablegen, mit md5 der vollständigen Abfrageargumente als Schlüssel, per Cron leeren

Bei aktiviertem 'cache count data' bildet WP_QueryWoofCounter den Hash des JSON der endgültigen Abfrageargumente, die Sprach- und Sichtbarkeitsanteile bereits enthalten, und sucht den Hash in {prefix}woof_query_cache, einer Tabelle mit zwei Spalten und dem Hash als Primärschlüssel. Schreibvorgänge nutzen INSERT ... ON DUPLICATE KEY UPDATE, sodass zwei Seitenaufrufe, die gleichzeitig denselben Schlüssel schreiben, nicht fehlschlagen. Eine Website mit persistentem Object Cache kann die Zahlen über den Filter woof_use_wp_cache nach wp_cache_* verlagern.

Es gibt keinen günstigen Weg herauszufinden, welche zwischengespeicherten Zahlen ein neues oder bearbeitetes Produkt betrifft, daher leert ein geplantes Ereignis die gesamte Tabelle im eingestellten Zeitraum. Fehlt dem MySQL-Benutzer das CREATE-Recht, gibt die Einstellungsseite das manuell auszuführende SQL aus und lässt die Option ausgeschaltet.

Die Min/Max-Abfrage des Preisschiebereglers durchsucht in großen Shops postmetaMin/Max pro SQL-Hash je Request und in einem Transient zwischenspeichern, beim Speichern eines Produkts leeren

get_filtered_price() baut ein SELECT MIN/MAX über _price, verknüpft mit der aktuellen Tax- und Meta-Query. Derselbe Bereich wurde pro Seite mehrfach angefordert (Min und Max für jeden Preisfilter), daher wird das Ergebnis in einem statischen Array mit md5 des SQL als Schlüssel zwischengespeichert. Bei aktiviertem 'Optimize price filter' landen die Ergebnisse zusätzlich einen Tag lang in einem Transient, und jedes woocommerce_update_product löscht ihn. Das SQL läuft durch woof_get_filtered_price_query, sodass eine Website Index-Hints hinzufügen kann.

Term-Bäume werden für jede Taxonomie bei jedem Seitenaufruf von Grund auf neu aufgebautFertige Bäume in Transients zwischenspeichern, mit Argumenten und aktueller Sprache als Schlüssel

WOOF_HELPER::get_terms() lädt alle Terme einer Taxonomie mit get_categories() und baut den Eltern-Kind-Baum in PHP auf. Das Ergebnis wird pro Request zwischengespeichert und bei aktiviertem 'Cache terms' in einem Transient abgelegt, dessen Schlüssel Taxonomie, hide_empty, Tiefen-Flags und die aktuelle WPML- oder Polylang-Sprache enthält, sodass jede Sprache ihren eigenen zwischengespeicherten Baum hat.

Das Leeren durchläuft wp_options nach _transient_woof_terms_cache_% und löscht jeden Transient, da WordPress kein Löschen von Transients nach Präfix kennt.

Die Neuzählung läuft auch dann, wenn der Besucher den Filter nie öffnetStatt des Formulars einen Button ausgeben und das Formular beim ersten Klick per AJAX nachladen

Mit start_filtering_btn=1 gibt der Server nur einen Button aus, sodass beim Laden der Seite keine Term-Abfragen laufen. Beim Klick sendet front.js woof_draw_products mit dem Produkt-Shortcode 'woof_nothing': der Server rendert das Filterformular und keine Produkte, die Redraw-Zone wird ersetzt und die Widgets werden neu initialisiert. Der Button entfällt, wenn bereits eine Suche aktiv ist, sodass eine gefilterte Seite ihre Auswahl weiter anzeigt.

Das Formular per CSS oder Anzeigeregeln auszublenden spart nichts, weil die Zahlen auf dem Server berechnet werden, bevor das HTML gesendet wird. In Performance-Threads im Support-Forum musste dieser Punkt oft erklärt werden.

Arbeiten innerhalb der Produktabfrage von WooCommerce

im Produkt

Was es bringtDas Filtern funktioniert weiter mit dem eigenen Theme des Shops, Kategorieseiten, variablen Produkten, Steuern und Währungen, sodass Kunden nie Artikel in ausverkauften Größen sehen.

Problem
Der Filter verändert die Haupt-Shop-Abfrage und stützt sich auf WooCommerce-Templates, die ihm nicht gehören. Er muss über Änderungen am Datenmodell von WooCommerce hinweg funktionieren, mit variablen Produkten, Katalogsichtbarkeit, Steuern und Währungs-Plugins.
Lösung

Spät in parse_query und die WooCommerce-Produktabfrage einhaken, den von Templates erwarteten Archivzustand wiederherstellen und jeden Unterschied im Datenmodell ausdrücklich nach Version oder Tabelle behandeln.

Stack
  • WP_Query
  • WooCommerce
  • MySQL
  • jQuery
Aufgaben · 6
Gefilterte Ergebnisse müssen über das Shop-Template des Themes gerendert werdenFlags der Hauptabfrage bei parse_query mit Priorität 9999 umschreiben, sodass sie als Produktarchiv gerendert wird

Wenn der Such-Slug im Request steht, setzt parse_query() post_type=product und is_post_type_archive und löscht is_tax, is_tag, is_home, is_single und is_search. WooCommerce und das Theme rendern dann für jede gefilterte URL die Shop-Schleife. Erweiterungen fügen ihre Meta-Bedingungen über assemble_query_params() hinzu, und die Operatoren pro Taxonomie kommen aus dem Filter woof_main_query_tax_relations.

Dieselben Hooks dürfen REST- und Store-API-Requests nicht berühren. Die Handler kehren bei /wp-json/-URLs früh zurück, und parse_query wird bei rest_api_init ausgehängt, sodass Block-Themes und Headless-Clients die eigenen Ergebnisse von WooCommerce erhalten.

Beim Filtern auf einer Kategorieseite geht die Kategorie verlorenDen Term als really_curr_tax mitführen und in einem Transient mit der Besucher-IP als Schlüssel halten

Eine Filter-URL ist nicht mehr das Term-Archiv, daher liefert get_queried_object() die Kategorie nicht mehr. Wenn das Archiv gerendert wird, speichert set_really_current_term() den Term über WOOF_STORAGE, das das Plugin im Transient-Modus betreibt: ein Transient pro Besucher, mit einem Hash der Besucher-IP als Schlüssel, einen Tag lang aufbewahrt. Das JS hängt außerdem really_curr_tax=<term_id>-<taxonomy> an den Filterlink an, und get_really_current_term() greift darauf zurück, wenn nichts gespeichert ist. Der Term wird dann per AND in die Produktabfrage, die Neuzählungs-Abfragen und die Textsuche aufgenommen. AJAX-Requests lesen den gespeicherten Term, überschreiben ihn aber nie.

Die Katalogsichtbarkeit wird je nach WooCommerce-Version unterschiedlich gespeichertNach Version verzweigen: _visibility-Meta vor 3.0, product_visibility NOT IN danach

WooCommerce 3.0 verlagerte die Flags für Sichtbarkeit, Hervorhebung und ausverkauft von postmeta in die Taxonomie product_visibility. product_visibility_not_in() macht aus den benötigten Flags term_taxonomy_id-NOT-IN-Klauseln. generate_visibility_keys() fügt outofstock hinzu, wenn der Shop ausverkaufte Artikel ausblendet; ist die Option 'listen catalog visibility' aktiv, fügt es außerdem immer exclude-from-search und außerhalb einer Suche exclude-from-catalog hinzu. Ältere Shops erhalten stattdessen eine _visibility-Meta-Bedingung.

Dieselben Regeln gelten für die Hauptabfrage, die Shortcode-Abfrage, die Neuzählungs-Abfragen, die Textsuche und die Daten des Turbo-Modus. Weicht eine davon ab, stimmen Zahlen und Ergebnisse nicht überein.

Variables Produkt wird angezeigt, obwohl die gewählte Größe ausverkauft istElternprodukte ausverkaufter passender Varianten in der Attribut-Lookup-Tabelle finden und ausschließen

Ein variables Produkt gilt als auf Lager, wenn irgendeine Variante es ist, daher liefert eine einfache Abfrage nach 'auf Lager + Größe M' ein Hemd, dessen M-Variante ausverkauft ist. Wenn der Lagerfilter mit Attributfiltern kombiniert wird, sammelt posts_where() die gewählten pa_*-Terme, einschließlich des bei der Neuzählung gerade gezählten Terms, und führt zwei gruppierte Abfragen auf wc_product_attributes_lookup aus: Eltern mit einer ausverkauften passenden Variante und Eltern mit einer vorrätigen. Die Differenz wird als ID NOT IN angehängt.

Ein älterer Modus, der in den Einstellungen des Lagerfilters wählbar ist, fragt stattdessen product_variation-Beiträge nach Attribut-Meta und _stock_status ab, statt die Lookup-Tabelle zu nutzen. Er ist langsamer und schließt standardmäßig jedes Elternprodukt mit einer ausverkauften passenden Variante aus; Eltern, die zusätzlich eine vorrätige Übereinstimmung haben, bleiben nur erhalten, wenn der Filter woof_exclude_existing_variations true zurückgibt.

Preisgrenzen weichen unter Steuer und Währung von den gespeicherten Preisen abGrenzen vor der Meta-Abfrage in die Basiswährung zurückrechnen und die enthaltene Steuer abziehen

_price wird in der Basiswährung des Shops gespeichert und in vielen Shops ohne Steuer. Verwendet der Besucher eine WPML-Währung (WCML), rechnet assemble_price_params() die Grenzen in die Basiswährung zurück. Werden Preise inklusive Steuer angezeigt, aber ohne Steuer gespeichert, wird die enthaltene Steuer des Standardsatzes von beiden Grenzen abgezogen. Für das obere Ende des Schiebereglers findet get_max_price() das Produkt mit dem Höchstpreis und fragt WooCommerce nach dessen Preis inklusive Steuer.

Mit FOX (WOOCS) werden die angezeigten Reglerwerte über woocs_exchange_value umgerechnet, während die Abfrage Zahlen in Basiswährung behält.

AJAX-Modus: Ergebnisse, Zahlen und Seitenteile ohne Seiten-Reload neu zeichnenEin AJAX-Endpunkt rendert Produkte und Formular über Shortcodes; JS tauscht Zonen aus und setzt die URL

woof_draw_products() baut $_GET aus dem Link neu auf, den der Besucher geöffnet hätte, rendert [woof_products] und [woof] mit diesem Zustand und liefert Produkte, Formular und weitere Fragmente (Ergebniszahl, Sortierung, Paginierung) als JSON zurück. front.js ersetzt jedes Fragment, setzt Fragmente zurück, die die Antwort nicht enthielt, initialisiert die Widgets neu und ruft history.pushState auf, damit die URL geteilt werden kann. onpopstate lädt die Seite neu, wenn sich der Query-String geändert hat. Ein Sperr-Flag verwirft Klicks, solange ein Request läuft.

Die Shop-Schleife wird durch Einhaken in woocommerce_before_shop_loop und woocommerce_after_shop_loop umschlossen. Themes, die nicht beide Hooks auslösen, brechen den AJAX-Modus, was eine Gruppe von Support-Threads mit unterschiedlichen Symptomen und einer Ursache erklärt.

Turbo-Modus: Filtern im Browser

im Produkt

Was es bringtBei großen Katalogen läuft das Filtern im Browser des Kunden statt auf dem Server. Ergebnisse erscheinen schnell, und der Server wird zu Stoßzeiten weniger belastet.

Problem
Bei großen Katalogen kostet jeder Filterklick auf dem Server eine Reihe von Tax- und Meta-Joins, plus eine Zählabfrage pro sichtbarem Term.
Lösung

Einen flachen JSON-Index von Produkten und Varianten vorab berechnen, im Browser laden und den Filter als SQL darüber mit AlaSQL ausführen. Der Server rendert nur noch die passenden IDs. In beiden Editionen enthalten.

Stack
  • PHP
  • JSON
  • AlaSQL
  • JavaScript
  • Cron
Aufgaben · 5
Produktdaten sind über Posts, Postmeta, Terme und Varianten verteiltJedes Produkt und jede Variante in einen JSON-Datensatz abflachen: Preise, Terme, Meta, Lager

get_data_by_id() schreibt ID, Titel, SKU, Autor, Eltern, Lagerstatus, die Term-Slugs jeder filterbaren Taxonomie (bei Varianten die Variationsattribute), die konfigurierten Meta-Felder und eine Liste von Paaren aus regulärem Preis und Angebotspreis. Ein variables Produkt speichert ein Paar pro Kind, sodass der Browser seinen Preisbereich nach derselben Regel berechnen kann wie WooCommerce: der Angebotspreis zählt nur, wenn er niedriger ist als der reguläre Preis.

Preise werden inklusive Steuer gespeichert, wenn der Shop Preise inklusive Steuer anzeigt, und FOX wird vor dem Lesen auf die Standardwährung umgestellt, sodass der Index in einer Währung vorliegt.

Das Indexieren tausender Produkte stößt an PHP-ZeitlimitsIn AJAX-Schritten zu je 10 Produkten aufbauen, eine Datei pro WPML-Sprache und pro Multisite-Blog

create_data_search_files() nimmt einen Offset, verarbeitet 10 IDs von Produkten und Varianten unter Anwendung der Sichtbarkeitsregeln, führt sie in die JSON-Datei ein und gibt den nächsten Offset zurück; das Admin-Skript läuft in einer Schleife, bis es -1 erhält. WPML wird auf die Standardsprache umgestellt, und jedes Produkt wird über seine Übersetzungs-ID in die Datei jeder Sprache geschrieben. check_id_file() fügt bei Multisite die Blog-ID hinzu, damit Websites keine Datei teilen.

Die Standardgrenze liegt bei 15,000 Datensätzen und lässt sich mit dem Filter woof_turbo_mode_products_limit anpassen.

Ein geplanter Neuaufbau darf keinen halb geschriebenen Index ausliefernEin eigener Cron-Runner verarbeitet ID-Abschnitte pro Request und tauscht am Ende eine _temp-Datei ein

PN_WP_CRON_WOOF_TURBO_MODE hält den Ereigniszustand (vollständige ID-Liste, Position, nächster Lauf) in einer Option und läuft auf init. Jeder process()-Aufruf verarbeitet einen Abschnitt von 10 IDs, und eine in woof_cron_limit_do gespeicherte Drosselung von 5 Sekunden verhindert, dass ein Schwall von Seitenaufrufen Abschnitte direkt hintereinander ausführt. Abschnitte werden nach data_<id>_temp_<lang>.json geschrieben; ist die Liste abgearbeitet, verschiebt do_recreate_file() die temporäre Datei über die aktive, und Besucher behalten den alten Index, bis der neue vollständig ist.

Der Preis dieses Designs ist Veraltung: Produkte, die nach dem letzten Neuaufbau hinzukamen, sind für den Filter unsichtbar, und der Neuaufbau hängt vom Website-Traffic ab, genau wie bei WP-Cron.

Der Filterzustand muss zu einer Abfrage werden, die der Browser ausführen kannAlaSQL-WHERE-Klauseln mit eigenen Funktionen für Preisbereiche und Sichtbarkeit erzeugen

turbo_mode.js macht aus den aktuellen Werten einen WHERE-String: Taxonomie-Zugehörigkeit über indexOf auf den Term-Arrays, Meta-Vergleiche, LIKE für SKU und Titel, BETWEEN für Schieberegler und Daten. Eigene AlaSQL-Funktionen berechnen den minimalen und maximalen Preis eines Produkts aus seiner Variantenliste und wenden die Katalogsichtbarkeit an. Bei variablen Produkten wählt der Lagerfilter zuerst die Eltern ausverkaufter Varianten, die zu den anderen Filtern passen. Die Neuzählung läuft über dieselbe In-Memory-Tabelle.

Die resultierenden IDs werden als turbo_mode_ids an woof_draw_products gesendet, und der Server rendert statt der Filter-Joins eine post__in-Abfrage.

Der Browser braucht die Einstellungen, doch sie enthalten Server-GeheimnisseZugangsdaten der Statistik-Datenbank und geheime Cron-Schlüssel aus den lokalisierten Einstellungen entfernen

Das JS baut die Filterlogik aus woof_settings neu auf und braucht daher den Großteil dieser Option auf der Seite. get_public_settings() entfernt den Statistikblock, der die Zugangsdaten der externen MySQL-Datenbank enthält, sowie jeden cron_secret_key und reicht den Rest zur weiteren Entfernung durch einen Filter. Im Premium-Build verlangt der Neuaufbau-Endpunkt einen Nonce und manage_woocommerce.

Suchstatistik

im Produkt

Was es bringtDer Inhaber sieht, wonach Kunden tatsächlich filtern und suchen, ohne den Shop zu verlangsamen oder seine Datenbank mit einem ständig wachsenden Log zu füllen.

Problem
Aufzuzeichnen, wonach Kunden filtern, bedeutet Schreiben auf dem Hot Path jedes gefilterten Seitenaufrufs, von anonymen Besuchern und Bots, oft auf Shared Hosting.
Lösung

Pro Request eine kleine Rohzeile in eine Staging-Tabelle schreiben und die Langzeitstatistik auf einem separaten MySQL-Server halten, den die Erweiterung voraussetzt.

Stack
  • MySQL
  • PDO
Aufgaben · 2
Das Logging darf Schreibvorgänge nicht vervielfachen und nicht von einem einzelnen Client überflutet werdenEinmal pro Request loggen, per Hash aus Request, IP und Tag deduplizieren, gespeicherte Zeilen pro IP begrenzen

Die Statistik-Erweiterung hängt sich an woof_get_request_data, das pro Seite oft läuft; ein statisches Flag beschränkt das Logging auf den ersten Aufruf, und AJAX-Aufrufe außer dem Rendern der Produkte werden ignoriert. Nur die in den Einstellungen gewählten Filterschlüssel bleiben erhalten, der Request wird auf request_max_deep Schlüssel gekürzt (Standard 5), wobei der Preisbereich erhalten bleibt, und zusammen mit der IP und dem Datum gehasht.

Eine Zeile wird nur eingefügt, wenn der Hash neu ist und die IP weniger als user_max_requests Zeilen (Standard 10) in der Staging-Tabelle hat.

Statistiken wachsen in der Shop-Datenbank ohne GrenzeDie Statistik per PDO auf einem separaten MySQL-Server speichern, eine Tabelle pro Monat

Die Erweiterung braucht einen eigenen MySQL-Server, der in den Optionen eingetragen wird. init_pdo() verbindet sich per PDO mit ihm, da $wpdb keine zweite Verbindung mit anderen Zugangsdaten halten kann. place_statdata_into_db() legt pro Monat eine Tabelle namens <year>_<month> an und fügt die Zeilen mit Prepared Statements ein.

Berichte fragen die Monatstabellen nach Datumsbereich ab, sodass die Shop-Datenbank die Langzeitstatistik nicht enthält.

Härtung und Integrität der Einstellungen

im Produkt

Was es bringtAnonyme Besucher können den Filter nicht nutzen, um Code auszuführen oder an versteckte Daten zu gelangen, und gespeicherte Einstellungen gehen bei Änderungen nicht verloren und werden nicht überschrieben.

Problem
Der Filter nimmt Query-Strings und AJAX-Posts von anonymen Besuchern entgegen, rendert Shortcodes und Templates, die in diesen Requests benannt sind, und speichert Einstellungen, die HTML, JavaScript und SQL enthalten. Das Changelog nennt externe Forscher als Urheber einer Reihe von Sicherheitsmeldungen von 2022 bis 2026.
Lösung

Jeden Request-Wert als Daten behandeln: nur bekannte Shortcodes und Templates zulassen, Einstellungen rekursiv nach Schlüsselpfad bereinigen, für alles, was Code speichert, Berechtigungen verlangen und Einstellungen zusammenführen statt sie zu überschreiben.

Stack
  • PHP
  • WordPress sanitization API
  • WPML
  • Polylang
Aufgaben · 5
AJAX-Requests tragen den Namen eines zu rendernden PHP-TemplatesNur Template-Namen akzeptieren, die zuvor im Seiteninhalt vorkamen; AJAX kann keine neuen hinzufügen

[woof_products custom_tpl=...] wird in AJAX-Aufrufen legitim zurückgereicht, daher kann der Wert weder vertraut noch verworfen werden. sanitize_custom_tpl() behält nur Buchstaben, Ziffern, '_', '-' und '/', was Punkte und Traversal-Sequenzen entfernt. Ein aus echtem Seiteninhalt gerenderter Name wird in eine Allowlist-Option aufgenommen (die letzten 20 bleiben); bei AJAX werden nur Namen akzeptiert, die bereits in dieser Liste stehen.

Include-Pfade, die aus View-Typ-Namen gebaut werden, laufen durch safe_view_slug(), das alles ablehnt, was kein einfacher Slug ist.

AJAX-Requests tragen Shortcode-Text, der nicht beliebige Shortcodes ausführen darfNur die eigenen Tags des Plugins mit get_shortcode_regex aus dem gesendeten Text extrahieren

woof_draw_products() erhält den Shortcode-Text für Produkte und Formular von der Seite, damit die AJAX-Ausgabe dem ursprünglichen Aufruf entspricht. check_shortcode() baut get_shortcode_regex() nur für die Tags des Plugins ([woof_products] oder [woof], plus die Produkt-Shortcode-Aliase des Plugins) und behält den ersten Treffer; alles andere im String wird vor do_shortcode() verworfen.

Einstellungen mischen HTML, JavaScript, Slugs und Klartext in beliebiger TiefeRekursiv bereinigen; jedes Feld nach seinem Schlüssel oder seinem Pfad im Einstellungsbaum klassifizieren

sanitize_array() durchläuft den Einstellungsbaum, speichert jeden Wert unter seinem bereinigten Schlüssel und klassifiziert jedes Feld nach Name oder nach seinem Pfad im Baum: SEO-Regeltext als HTML über wp_kses_post, Beschreibungen als Textarea, in Include-Pfaden verwendete Schlüssel als strikte Slugs, alles andere als Klartext. Pfadregeln sind nötig, weil Schlüssel tief im Baum, etwa text oder description, zu allgemein sind, um sie global aufzulisten.

Die Einstellung 'JavaScript after AJAX' kann nur von Benutzern mit unfiltered_html geändert werden.

Die AJAX-Antwort fügt HTML-Fragmente ein, und der Request wird aus der URL des Besuchers neu aufgebautwoof_redraw_elements aus dem geparsten Link entfernen (Premium); jedes Fragment durch wp_kses leiten

Das AJAX-Rendering baut $_GET aus dem vom Browser gesendeten Seitenlink neu auf, und die Antwort fügt woof_redraw_elements als HTML in die Seite ein, daher darf ein präparierter Link diesen Schlüssel nicht liefern können. Im Premium-Build wird der Schlüssel aus dem geparsten Link entfernt. Legitime Fragmente (SEO-Titel und H1, Ergebniszahl, Sortierung) werden durch serverseitige Filter hinzugefügt, und jedes Fragment läuft vor der Rückgabe durch wp_kses.

Term-Farben und -Bilder sollen einmal für alle Sprachen festgelegt werdenÜbermittelte Farben pro Term über gespeicherte legen; auf den Term der Standardsprache zurückfallen

Die Einstellungsseite listet nur die Terme der aktuellen WPML- oder Polylang-Sprache auf, daher sendet ein Speichern nur Farben für diese Terme. woof_save_options() führt die gespeicherten Einträge color, color_img und images_term_* unter den übermittelten Werten zusammen, sodass die anderen Sprachen ihre behalten.

Ein übersetzter Term ohne eigene Farbe nutzt sein Original in der Standardsprache, das get_original_language_term() findet. Es liest den Slug direkt aus wp_terms, weil get_term() bei der ID-Anpassung von WPML den übersetzten Term erneut liefern kann.

Fehlersuche in der Praxis

support forum

Was es bringtProbleme in einem Live-Shop werden an ihrer eigentlichen Ursache gefunden, auch wenn es das Theme, der Server, ein Cache oder ein anderes Plugin ist.

Problem
Eine Meldung aus einem Live-Shop nennt ein Symptom, keine Ursache. Dasselbe Symptom kann von einem Theme-Template, einer Cache- oder Optimierungsschicht, dem Server, einem anderen Plugin oder dem Filter selbst kommen.
Lösung

Auf einer frischen Installation oder einer Kopie der Website reproduzieren, eine Schicht nach der anderen ausschließen und dort beheben, wo die Ursache liegt: im Plugin für alle oder mit einer gezielten Änderung auf dieser Website.

Stack
  • Browser DevTools
  • Nginx
  • LiteSpeed Cache
  • jQuery
  • ACF
  • MySQL
Aufgaben · 5
Unterkategorie-Umschalter verschwinden und das Filtern bricht, sobald die URL irgendeinen Query-String hatDen Fehler per Test mit einem beliebigen GET-Parameter auf den Server eingegrenzt, dann die Nginx-Rewrite-Regel korrigiert

Schon ein bedeutungsloser Parameter wie ?dwdadd=1adw brach die Seite, was bewies, dass der Fehler in der Query-String-Verarbeitung lag und nicht im Filter. Die Nginx-Konfiguration fügte nach php ein überzähliges ? ein und verwarf jeden Parameter, sodass der Filter seine Argumente nie erhielt.

Der Shop aktualisiert sich nach einmaligem Filtern nicht mehr, sobald LiteSpeed Cache installiert istDie AJAX-Antwort protokolliert, um einen JSON-Parse-Fehler sichtbar zu machen, dann die LiteSpeed-Einstellungen per Bisektion auf CDN Mapping eingegrenzt

Die Antwort kam an, aber JSON.parse scheiterte mit einem fehlerhaft maskierten Zeichen, was bedeutete, dass das CDN Mapping von LiteSpeed URLs darin umschrieb. Das Abschalten von CDN Mapping stellte das Filtern wieder her.

Die Buttons Filter und Reset aktualisieren die URL, aber die Produktliste lädt nie neuAuf den Slim-Build von jQuery zurückgeführt, volles jQuery wiederhergestellt und den veränderten WordPress-Core des Hosts durch eine saubere Installation ersetzt

Die Website lud jQuery slim, das kein AJAX-Modul hat, sodass der Request für die gefilterten Ergebnisse nie gesendet wurde, während sich die URL clientseitig trotzdem änderte; das Deaktivieren aller anderen Plugins änderte nichts. Der Slim-Build kam mit einem veränderten WordPress-Core aus dem Auto-Installer des Hosts; die Wiederherstellung von vollem jQuery und die Neuinstallation des Cores lösten es.

Meta-Schieberegler auf kleinen ACF-Dezimalwerten (um .005) filtern nichts oder allesDen Filter woof_slider_meta_query_type hinzugefügt, damit der SQL-Cast pro Meta-Schlüssel auf DECIMAL gesetzt werden kann

Der Melder baute das Feld als einfaches benutzerdefiniertes Feld neu auf und der Fehler blieb, also war ACF nicht die Ursache; der Support reproduzierte ihn. Die Meta-Abfrage verglich die Werte ohne DECIMAL-Cast, und das Plugin erhielt den Hook woof_slider_meta_query_type, der $type und $key bekommt.

Der Filter friert nach dem Zurück-Button des Browsers ein; Suchen bewirkt nichtsEinen pageshow-Listener hinzugefügt, der die Absende-Sperre und das Popup bei der bfcache-Wiederherstellung zurücksetzt, mit Updates ausgeliefert

Aus dem Back/Forward-Cache wiederhergestellte Seiten behielten woof_submit_link_locked gesetzt und das Lade-Popup geöffnet. Der Listener prüft event.persisted oder Navigationstyp 2 und setzt beides zurück; die Änderung ging ins Plugin ein.

Zahlen

wordpress.org
  • Aktive Installationen 80.000+ Zähler von wordpress.org.
  • Rezensionen 336 Durchschnitt 4.6 von 5.
  • Jahre Support 12 Seit 1. Dezember 2014, letztes Update 9. Oktober 2026.