HUSKY aka WOOF
Lässt Kunden einen großen Katalog nach Kategorie, Attribut, Preis, Lagerbestand und Text eingrenzen, mit Filterseiten, die Google indexieren kann.
Ich begleite ein Produkt von der ersten Spezifikation bis zur Support-Warteschlange Jahre später. 12 Plugins auf wordpress.org, eine SaaS-Lösung, die einen WooCommerce-Shop in einen Telegram-Bot verwandelt, eine Chrome-Erweiterung und Android-Apps.
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;
Projekte
Jede Projektseite listet die im Produkt gelösten Probleme, nach Bereichen gruppiert. Installationszahlen und Bewertungen stammen von wordpress.org, wo Sie lesen können, was Nutzer schreiben: HUSKY-Rezensionen, FOX-Rezensionen, BEAR-Rezensionen.
Lässt Kunden einen großen Katalog nach Kategorie, Attribut, Preis, Lagerbestand und Text eingrenzen, mit Filterseiten, die Google indexieren kann.
Zeigt Preise in der eigenen Währung des Besuchers und kann die Zahlung in dieser Währung annehmen, damit ausländische Kunden wissen, was sie bezahlen.
Lässt Mitarbeiter Tausende Produkte wie in einer Tabellenkalkulation bearbeiten, wobei jede Änderung rückgängig zu machen ist, und lässt KI-Assistenten dies sicher tun.
Zeigt den Katalog als sortier- und filterbare Produkttabellen, in denen Kunden Artikel direkt aus jeder Zeile in den Warenkorb legen.
Lässt Site-Mitarbeiter Tausende Beiträge, Seiten und benutzerdefinierte Inhalte auf einem tabellenartigen Bildschirm bearbeiten, filtern, exportieren und zurücksetzen.
Besucher durchsuchen Einträge, Beiträge oder Produkte nach mehreren Eigenschaften zugleich, etwa Preis, Baujahr und Kategorie.
Zeigt im Inhalt der Site geschriebene Preise in der eigenen Währung jedes Besuchers, ohne dass ein WooCommerce-Shop nötig ist.
Zeigt Beiträge oder Produkte als sortier- und durchsuchbare Tabellen, die Besucher auf jedem Gerät filtern und durchblättern können.
Hängen Sie einem beliebigen Beitrag eine Bildergruppe an und zeigen Sie sie als Galerien oder Slider an beliebiger Stelle der Site.
Schrittweise Dropdowns wie Marke, Modell und Generation führen Besucher durch einen großen Katalog zur richtigen Seite oder zum richtigen Produkt.
Ein WooCommerce-Shop, der als Telegram-Bot lief: Kunden stöberten, füllten einen Warenkorb und bezahlten, ohne den Chat zu verlassen.
Eine Chrome-Erweiterung, die das Gesprochene in jedes Textfeld auf jeder Website tippt, mit optionaler KI-Bereinigung.
Eine Offline-Stoppuhr für Android mit Rundenzeiten, die auch dann richtig weiterzählt, wenn die App im Hintergrund läuft.
Ein Reader, der einen Text Wort für Wort im gewählten Tempo zeigt und sich merkt, wo Sie aufgehört haben.
Unternehmen als Blasen in einem Quadrantendiagramm, die sich bewegen, wenn sich die Modellgewichte ändern.
Ereigniserfassung, Aggregation und Berichte mit Diagrammen im WordPress-Admin.
Ein Kunde abonniert eine Filterabfrage und erhält eine E-Mail, wenn neue passende Produkte erscheinen.
Ein gestaltetes PDF mit jedem Auto aus der aktuellen Ergebnismenge eines Fahrzeugkatalogs.
Was gelöst wurde · 14 Produkte
Wählen Sie ein Produkt. Jeder Tab ist ein Arbeitsbereich des Produkts und beginnt mit dem, was er dem Inhaber bringt, in einfachen Worten. Darunter folgt der technische Teil für Ihre Entwickler: das Problem und seine Lösung, aus dem Quellcode entnommen. Öffnen Sie eine Zeile für die Details.
was gelöst wurde · Projekt wählen
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.
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
_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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Pro Request eine kleine Rohzeile in eine Staging-Tabelle schreiben und die Langzeitstatistik auf einem separaten MySQL-Server halten, den die Erweiterung voraussetzt.
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.
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.
Was es bringtKunden finden Produkte über SKU, Attribut oder Wörter in Variantenbeschreibungen. Registrierte Kunden können eine Suche speichern und werden benachrichtigt, wenn neue passende Produkte eintreffen.
Die WHERE-Klausel aus vorbereiteten Fragmenten pro Wort aufbauen, Joins nur bei Bedarf hinzufügen, Varianten in einer separaten Abfrage auf Eltern abbilden, begrenzen, was der öffentliche Endpunkt anfordern kann, und gespeicherte Suchen aus vom Server erfasstem SQL erneut ausführen.
create_where_query() zerlegt die Phrase in Wörter. Für jedes Wort verknüpft es die gewählten Post-Felder und aufgeführten Meta-Schlüssel per OR (SKU ist einfach _sku), dazu einen Term-Namen-Treffer auf die ganze Phrase, und die Wortgruppen werden per AND verknüpft. Ein führendes '-' macht ein Wort zu NOT LIKE, per AND verbunden. Die Fragmente laufen durch $wpdb->prepare().
Term- und Postmeta-Joins werden in posts_join nur hinzugefügt, wenn diese Optionen aktiv sind, und posts_groupby fasst die doppelten Zeilen zusammen, die die Joins erzeugen.
Varianten sind eigene Posts, und die Elternabfrage kann _variation_description nicht sehen. Ist die Option aktiv, findet eine Abfrage product_variation-Posts, deren Beschreibung alle Wörter enthält, eine zweite bildet sie auf post_parent ab, und die Hauptklausel wird zu (Wortbedingungen OR ID IN (Eltern)). Die Elternliste kann pro Wortliste im Object Cache zwischengespeichert werden.
ajax_search() steht Gästen offen und paginiert die Abfrage in SQL mit max_posts als Seitengröße, sodass nur eine Seite mit Treffern geladen wird. Im Premium-Build wird max_posts aus dem Request auf einen Wert zwischen 1 und dem größeren von Admin-Einstellung und einem per Filter änderbaren Wert 100 begrenzt. Vorschläge berücksichtigen den aktuellen Filterzustand, die Kategorieseite und die Einschränkung auf Angebote.
Der Textfilter baut seine eigene Suchabfrage, daher kann ein separates Such-Plugin im selben Request nicht über die Ergebnismenge entscheiden; Support-Threads nennen Ivory Search, Relevanssi und Advanced Woo Search unter solchen Konflikten. get_all_ids() wendet zuerst den Filter woof_text_search_ids an. Gibt ein Callback ein Array zurück, zum Beispiel IDs aus SearchWP, ist dieses Array die Treffermenge, und das eingebaute SQL wird übersprungen. Die Anbindung einer bestimmten Suchmaschine erfordert eigenen Code.
Die View von Products Messenger speichert $wp_query->request, das von WordPress für die gefilterten Ergebnisse erzeugte SQL, im Speicher des Plugins pro Besucher unter der Benutzer-ID. Beim Abonnieren, das einen angemeldeten Benutzer voraussetzt, liest der Server es aus diesem Speicher statt aus dem Request zurück, entfernt LIMIT, prüft, dass es ein einzelnes SELECT ohne DML- oder DDL-Schlüsselwörter ist, führt es einmal aus, um eine Ausgangsbasis an IDs festzuhalten, und speichert das Abonnement in den User-Meta.
Der geplante Job führt jede gespeicherte Abfrage erneut aus und sendet per E-Mail nur IDs, die nicht in der Ausgangsbasis sind, und erweitert dann die Ausgangsbasis. Ein Abonnement endet nach Nachrichtenzahl oder Datum. User-Meta werden nur aktualisiert, wenn wp_mail() erfolgreich ist, sodass ein fehlgeschlagener Versand beim nächsten Lauf wiederholt wird.
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.
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.
[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.
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.
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.
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.
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.
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.
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.
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.
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 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.
woof_slider_meta_query_type hinzugefügt, damit der SQL-Cast pro Meta-Schlüssel auf DECIMAL gesetzt werden kannDer 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.
pageshow-Listener hinzugefügt, der die Absende-Sperre und das Popup bei der bfcache-Wiederherstellung zurücksetzt, mit Updates ausgeliefertAus 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.
Innerer Ring: Kern-Stack · äußerer Ring: Werkzeuge und Plattformen
Über mich · Stack
Ich entwerfe die Architektur und das Datenmodell, schreibe dann den Code, veröffentliche ihn und betreue ihn. Keine Ebene geht an jemanden, den ich nicht prüfen kann.
Auftragsarbeiten
Auftragsarbeiten für Kunden, meist für Leute, die mit einem meiner Plugins angefangen haben. Namen und Websites bleiben vertraulich.
Ein großes B2B-Anbieterverzeichnis auf WordPress-Basis.
Dasselbe WordPress-Portal.
Begann als Auftragsarbeit für einen Shop-Inhaber und erschien später als Erweiterung Products Messenger für HUSKY und MDTF.
Ein Fahrzeugkatalog auf Basis von MDTF, individuelles Add-on für einen Kunden in Deutschland.
Eingebaut, nicht angeflanscht
Inhaber sehen die Zahlen im selben Admin-Bereich, in dem sie das Plugin einrichten.
Zeitleiste
Nur meine eigenen Produkte. Als Veröffentlichungsdatum gilt das Datum, an dem das jeweilige Plugin zu wordpress.org hinzugefügt wurde.
Bevor Sie schreiben
Beschreiben Sie die Aufgabe im Formular unten oder per E-Mail an realmag777 [at] gmail.com: was erstellt oder geändert werden soll und bis wann. Wenn Sie eine Staging-Site haben, erwähnen Sie sie: Dort wird die Arbeit erledigt und geprüft. Ich antworte innerhalb von zwei Werktagen mit Rückfragen oder mit einer Schätzung von Zeit und Kosten. Die Arbeit beginnt, sobald wir uns auf den Umfang geeinigt haben.
Eine klar beschriebene Aufgabe erhält eine feste Schätzung für den vereinbarten Umfang. Offene Arbeit, etwa die Suche nach der Ursache eines Problems auf einer Live-Site oder laufender Support, wird nach Stunden vereinbart.
Mitteleuropäische Zeit (UTC+1, im Sommer UTC+2). Ich schreibe und spreche Englisch, Ukrainisch und Spanisch. Der Großteil der Arbeit läuft über E-Mail und Nachrichten, daher spielt die Zeitverschiebung selten eine Rolle.
Ja. Kundennamen und Websites werden hier nie gezeigt, und ich unterschreibe eine NDA, wenn das Projekt eine verlangt.
Nein. Ich baue Funktionen auf Basis meiner Plugins oder beliebiger anderer, arbeite in Code, den andere Entwickler geschrieben haben, und entwerfe neue Projekte von Grund auf. Bei fremdem Code lese ich ihn zuerst und sage Ihnen, was ich gefunden habe, bevor ich etwas ändere.
Allein. Architektur, Code, Release und Support liegen bei mir, Sie sprechen also mit dem, der das Produkt baut, und zwischen mehreren Personen geht nichts verloren.
Ja, auch unter der Marke der Agentur. Schreiben Sie, wer Sie sind und was Sie brauchen.
Nach Vereinbarung. Erzählen Sie mir vom Projekt und vom erwarteten Umfang, und wir klären es vor Arbeitsbeginn.
In Euro.
Eine Staging-Site, eine Kopie der Live-Site, mit Admin- und Dateizugriff, dazu eine Kopie der Datenbank, wenn es bei der Aufgabe um Daten geht. Gibt es keine Staging-Site, richte ich eine ein. Änderungen gelangen erst auf die Live-Site, nachdem sie auf der Staging-Site funktionieren.
Ihnen, sobald die Arbeit bezahlt ist.
Fehler in der gelieferten Arbeit werden behoben. Weitere Änderungen und Support lassen sich gesondert vereinbaren. Meine eigenen Plugins betreue ich seit über zehn Jahren, langfristige Wartung ist also der Normalfall und keine Ausnahme.
Kontakt
Schreiben Sie, was erstellt oder geändert werden soll und bis wann. Ich antworte innerhalb von zwei Werktagen.
realmag777 [at] gmail.com linkedin.com/in/realmag777