Sprache

Kontakt aufnehmen

WooCommerce-Plugin · seit 2018 · aka WOOBE

BEAR

Ein Massen-Editor im Tabellenstil für WooCommerce-Produkte, mit einem MCP-Server, über den KI-Agenten den Shop sicher bearbeiten können.

Erstveröffentlichung
Aktive Installationen
30.000+
Bewertung
4.7 / 5 (227)Rezensionen lesen
  • PHP
  • WordPress
  • WooCommerce CRUD (WC_Product)
  • WP_Query
  • $wpdb / MySQL
  • WP REST API
  • JSON-RPC 2.0
  • Model Context Protocol
  • jQuery
  • DataTables

Über mich

BEAR zeigt den WooCommerce-Katalog als eine einzige bearbeitbare Tabelle und ändert Tausende Produkte auf einmal: Preise, Lagerbestand, SKUs, Taxonomien, Attribute, Variationen und benutzerdefinierte Metadaten. Jede Änderung läuft über einen einzigen Schreibpfad, der für Produkteigenschaften die Produktobjekte von WooCommerce verwendet, mit dem vorherigen Wert protokolliert wird und sich im Tab „History“ zurücknehmen lässt.

Schwierig ist nicht die Tabelle, sondern ein korrektes Massenschreiben in einem echten Shop: Werte, die an fünf verschiedenen Stellen gespeichert sind, variable Produkte, deren Preise und Lagerbestand an den Variationen hängen, Nebeneffekte von WooCommerce wie ein aus der Lagermenge abgeleiteter Lagerstatus und Shared Hosting, das einen PHP-Request nach 30 Sekunden abbricht.

Seit 2026 betreibt das Plugin außerdem einen MCP-Server. Ein KI-Agent kann Produkte finden, eine Änderung in der Vorschau prüfen, sie portionsweise anwenden und zurücknehmen, und zwar über dieselben Filter-, Bulk- und History-Engines, die auch die Oberfläche nutzt. Abgesichert ist das durch Schlüssel, ein bestätigtes Sitzungs-Token (beim Shop-weiten Schlüssel optional, bei persönlichen Schlüsseln immer aktiv) und eine Berechtigungsmatrix pro Bereich.

Die kostenlose und die Premium-Version sind eine Codebasis, die über ein einziges Flag umgeschaltet wird; die kostenlose Version begrenzt Seitengröße, Meta-Felder, History-Tiefe und die MCP-Schreibrate.

BEAR · Massenbearbeitung

was gelöst wurde

LIVEseit 2018

Produkttabelle und Feldmodell

shipped · 2018

Was es bringtMitarbeitende sehen und ändern jedes Detail eines Produkts oder einer Variation auf einem tabellenähnlichen Bildschirm, selbst bei Katalogen, die zu groß sind, um sie auf einmal zu öffnen.

Problem
Ein Bildschirm muss jeden Wert eines Katalogs beliebiger Größe anzeigen und bearbeiten können: Post-Felder, WooCommerce-Eigenschaften, Taxonomien, Attribute, Downloads und beliebige Metadaten, für Produkte und ihre Variationen.
Lösung

Eine deklarative Feld-Registry steuert jede Spalte und jeden Schreibvorgang, und die Tabelle wird serverseitig paginiert: eine WP_Query-Seite pro AJAX-Request.

Stack
  • WP_Query
  • DataTables server-side mode
  • WC_Product setters
  • wp_options
Aufgaben · 6
Große Kataloge lassen sich nicht auf einmal in den Browser ladenServerseitige Paginierung: Jede Tabellenseite ist eine WP_Query mit Offset, Sortierung und Filtern

woobe_get_products() übernimmt start, length und order von DataTables und reicht sie an WOOBE_PRODUCTS::gets() weiter, das die WP_Query aufbaut. Beim Sortieren nach einer Meta-Spalte wird für die Typen number, timestamp und unix auf meta_value_num umgeschaltet, sonst auf meta_value.

Die Seitengröße ist im Einstellungsmodell auf 100 Zeilen begrenzt, mit dem Kommentar, dass größere Seiten auf schwachen Servern zu HTTP 500 führten; die kostenlose Version ist fest auf 10 eingestellt. Ist der Variationen-Schalter aktiv, werden die Variationen jedes variablen Produkts abgefragt und als Zeilen direkt hinter ihrem Elternprodukt eingefügt.

Jeder Spaltentyp wird in WooCommerce anders gespeichertEin Registry-Eintrag pro Spalte: Speichertyp, Werttyp, Editor, Sanitizer, Rechte

woobe_get_fields() beschreibt jede Spalte: field_type (field, meta, prop, taxonomy, attribute, downloads, gallery, upsells und weitere), Werttyp, Bearbeitungsansicht, Sanitizer, Produkttypen, für die die Spalte nicht gilt, und die Sichtbarkeit für Shop-Manager. Globale Attribute und alle Produkt-Taxonomien werden zur Laufzeit aus wc_get_attribute_taxonomies() und get_object_taxonomies() ergänzt.

update_page_field() verzweigt nach field_type: update_post_meta für Meta, WC_Product-Setter plus save() für Eigenschaften, wp_set_post_terms für Taxonomien, neu aufgebaute WC_Product_Attribute-Listen für Attribute. Zellen der Tabelle, History-Rücknahmen und die meisten Bulk-Schreibvorgänge enden in dieser Funktion. Bulk-Änderungen an Taxonomien und Attributen schreiben die Terms direkt und lösen vor dem Schreiben denselben History-Hook aus.

Benutzerdefinierte Meta-Keys müssen von WooCommerce-Interna unterscheidbar seinEine Unterklasse von WC_Product_Data_Store_CPT gibt ihre geschützte Liste interner Keys heraus

woobe_meta_get_keys() liest alle Meta-Keys eines Beispielprodukts und zieht die internen Keys von WooCommerce ab. Diese Liste ist eine geschützte Eigenschaft des Product Data Store, deshalb erweitert WOOBE_PDS_CPT den Store nur, um sie zurückzugeben, und ergänzt _button_text und _product_url.

Meta-Keys werden getrimmt, aber weder in Kleinbuchstaben umgewandelt noch bereinigt, weil echte Keys, die andere Plugins schreiben, etwa _woocs_sale_price_USD, zwischen Groß- und Kleinschreibung unterscheiden.

Ein typisiertes Meta-Feld muss in Spalten, Filtern und Bulk-Edit funktionierenEine Meta-Definition wird per Hooks in Registry, Filter-Panel und Bulk-Panel abgebildet

woobe_extend_fields() macht aus jeder gespeicherten Meta-Definition eine Spalte; ein Zahlentyp erhält den floatval-Sanitizer und numerische Sortierung. Dieselbe Definition speist woobe_filter_numbers (Bereich von/bis), woobe_filter_text (LIKE, =, !=, NOT LIKE, leer, nicht leer), Switcher-Filter und woobe_bulk_number (setzen, erhöhen oder verringern um einen Wert oder um Prozent).

Eine Switcher-Spalte kann andere Werte als 1/0 speichern (zum Beispiel yes/no), und zwar über eine Override-Regel pro Key, die over_switcher_swicher_to_val() liest. Die kostenlose Version behält die ersten 2 Meta-Felder.

Serialisierte Array-Metadaten haben keine flache Zelle zum BearbeitenDer Popup-Editor sendet Keys und Werte als parallele Arrays, die zu verschachtelten PHP-Arrays zusammengesetzt werden

process_jsoned_meta_data() baut einen Meta-Wert aus keys/values-Paaren für Arrays und keys2/values2 für Objekte wieder auf, dekodiert verschachtelte JSON-Werte und greift bei einfachen Strings und Zahlen auf den Rohwert zurück. Die History speichert solche Werte als JSON mit JSON_HEX_QUOT und JSON_HEX_TAG, damit sie den Hin- und Rückweg unbeschadet überstehen.

Der Editor-Zustand pro Benutzer muss das Leeren des Object Cache überstehenEine nicht automatisch geladene Option pro Benutzer mit TTL pro Key, statt Transients

Filter und Bulk-Konfigurationen werden von WOOBE_STORAGE zwischen Requests gehalten. Unter Redis oder Memcached lebt ein Transient nur im Object Cache und geht beim Leeren verloren. Deshalb ist der Standardspeicher eine Option woobe_memory_<user id>: Jeder Key hat sein eigenes Ablaufdatum, abgelaufene Keys werden beim Schreiben verworfen, Autoload ist aus, und die Zeile wird mit dem Benutzer gelöscht. Session-, Transient- und Cookie-Modus bleiben über den Filter woobe_storage_type verfügbar.

Filtern

shipped · 2018

Was es bringtMitarbeitende wählen genau die Produkte aus, die sie brauchen, auch nach Variationspreis oder Lagerbestand, und dieselbe Auswahl dient dem Bearbeiten und dem Export.

Problem
Filter für Preise, Lagerbestand, Maße, Text und Taxonomien müssen genau die richtigen Produkte auswählen, auch variable Produkte, deren Werte an den Variationen hängen, und dieselbe Menge muss von Paginierung, Bulk-Edit und Export wiederverwendet werden.
Lösung

Der Filter wird auf dem Server unter einem Key gespeichert und bei jeder Abfrage über einen einzigen WP_Query-Hook in tax_query-, meta_query- und posts_where-Klauseln übersetzt.

Stack
  • WP_Query
  • tax_query
  • meta_query
  • posts_where
  • $wpdb->prepare
  • MySQL REGEXP
Aufgaben · 5
Paginierungs-, Bulk- und Export-Requests müssen dieselbe gefilterte Menge sehenFilter pro Durchlauf unter einem zufälligen Key gespeichert; ein WP_Query-Hook baut ihn pro Request neu auf

woobe_filter_products() speichert das geparste Filterformular unter woobe_filter_<key>, wobei der Key ein bei jedem Filtern erzeugter Zufalls-String ist, sodass Bulk-nach-Filter in mehreren Browser-Tabs gleichzeitig funktioniert. Jeder Aufruf von WOOBE_PRODUCTS::gets() reicht seine Argumente durch woobe_apply_query_filter_data(), das den gespeicherten Filter liest, tax_query, meta_query, post__in und post_status zurückgibt und für den Rest posts_where-Hooks anhängt.

Tabellenpaginierung, Bulk-Zähler, Export-Zähler und die Variationswerkzeuge rufen alle gets() auf, daher können sie sich nicht darüber uneinig sein, welche Produkte ein Filter meint.

Preis und Lagerbestand hängen an Variationen, die Tabelle listet aber ElternprodukteBereichsabfrage über Produkte und Variationen, danach werden Variationstreffer ihren Elternprodukten zugeordnet

regular_price_where(), sale_price_where(), stock_quantity_where() und dimensions_where() fragen postmeta sowohl für product als auch für product_variation ab, lesen dann post_parent der gefundenen Variationen und beschränken die Hauptabfrage auf die Elternprodukte plus Variationen mit ID IN (...).

Gibt es keinen Treffer, wird die Klausel zu ID = -1, damit eine leere Übereinstimmung keine Produkte liefert statt des ganzen Katalogs, was einen Klick vor einem Bulk-Edit entscheidend ist.

Als Meta-Strings gespeicherte Zahlen werden in MySQL falsch verglichenCAST zu DECIMAL mit expliziter Skala; leere Strings aus jedem Bereich ausgeschlossen

Benutzerdefinierte Zahlenbereiche verwenden meta_query-Typ DECIMAL(30,20): Ein nacktes DECIMAL hat Skala 0, sodass eine Suche von 100 bis 150 auch 99.xx finden würde. Preis-SQL castet auf DECIMAL(30,10).

Ein leeres Gewicht wird als '' gespeichert, und MySQL liest '' als 0, daher verlangen Preis- und Maßbereiche meta_value <> '', damit ein leeres Feld nie als 0 zählt. Ein fehlendes Bereichsende ist offen (ersetzt durch -999999999 oder 999999999), und von gleich bis bedeutet genau diesen Wert.

Nutzer brauchen AND, OR und NOT in einem einzigen TextsuchfeldKleine Suchsyntax (^ OR, ^~ AND, ^! NOT), die in MySQL-REGEXP-Klauseln übersetzt wird

posts_txt_where() teilt den Text an ^; ein Präfix ~ verbindet den Teil mit AND, ein Präfix ! macht daraus ein NOT REGEXP in einer separaten AND-Gruppe. Die Verhaltensweisen exact, begin, end, not und empty werden auf = und verankertes REGEXP abgebildet; eckige und geschweifte Klammern werden maskiert. Es gilt für Titel, Inhalt, Auszug und Slug.

Der SKU-Filter nimmt eine kommagetrennte Liste und baut vorbereitete LIKE-Bedingungen mit esc_like, OR für die Treffermodi und AND für „not“.

Alle von mehreren Terms zugleich zu treffen erfordert eine tax_query-Klausel pro TermAND wird in eine Klausel pro Term aufgeteilt; unbekannte Operatoren fallen auf IN zurück

Beim Operator AND wird jeder Term zu einer eigenen tax_query-Klausel unter relation AND, weil eine einzelne Klausel mit Operator AND keine Produkte liefern kann. Operatoren stehen auf einer Whitelist aus IN, NOT IN, AND, EXISTS und NOT EXISTS; WordPress verwirft eine Klausel mit unbekanntem Operator und würde jedes Produkt treffen.

Die Katalogsichtbarkeit wird in Kombinationen der product_visibility-Terms exclude-from-catalog und exclude-from-search übersetzt, und ein Lagerstatusfilter beschränkt product_type zusätzlich auf Typen, die einen Lagerstatus haben.

Bulk-Edit-Engine

shipped · 2018

Was es bringtÄnderungen an Preisen, Lagerbestand und Variationen über Tausende Produkte laufen auf gewöhnlichem Hosting zuverlässig durch und folgen den eigenen Preis- und Lagerregeln des Shops.

Problem
Eine Massenänderung kann Tausende Produkte betreffen, auf Hosting mit kurzen PHP-Zeitlimits, und jeder Schreibvorgang muss die WooCommerce-Regeln für Preise, Lagerbestand und Variationen einhalten.
Lösung

Die Ziel-IDs werden einmal ermittelt und dann in kleinen AJAX-Batches über denselben Einzelfeld-Schreibpfad geschrieben, den die Tabelle nutzt und der die WooCommerce-Regeln enthält.

Stack
  • AJAX batching
  • WC_Product
  • WC_Product_Variation
  • WC_Product_Attribute
Aufgaben · 6
Ein Bulk-Edit von Tausenden Produkten stößt an PHP-ZeitlimitsIDs einmal geholt, dann in Batches von N pro AJAX-Request gesendet; N ist eine Einstellung

woobe_bulk_products_count() speichert die Bulk-Konfiguration unter einem zufälligen, 16 Zeichen langen bulk_key und gibt jede gefilterte ID zurück (fields=ids, ohne Paginierung). __woobe_bulk_products() in bulk.js zerlegt diese Liste, sendet einen Batch nach dem anderen, aktualisiert die Fortschrittsanzeige und ruft am Ende woobe_bulk_finish auf; der Benutzer kann den laufenden Request abbrechen.

Die Batch-Größe ist number_posts_processed, 10 bis 100 pro Request, standardmäßig 30, sodass ein schwacher Server Geschwindigkeit gegen Stabilität tauschen kann.

Bearbeitete Produkte fallen während des Durchlaufs aus dem FilterEingefrorene ID-Liste: Jeder Batch trägt IDs aus dem Snapshot, nicht aus einer erneuten Abfrage

Würde man die gefilterte Abfrage zwischen den Batches paginieren, würden Produkte übersprungen: Eine Preisänderung schiebt ein Produkt aus einem Preisfilter, und jeder spätere Offset verschiebt sich. Die zu Beginn erfasste ID-Liste hält der Client, und jeder Batch-Request sendet seine IDs ausdrücklich, ohne den Filter-Key.

Nach dem Durchlauf werden markierte Zeilen, die in der neu gezeichneten Tabelle nicht mehr erscheinen, aus der Auswahl entfernt, damit die nächste Aktion keine Produkte trifft, die der Benutzer nicht mehr sieht.

Zahlenverhalten muss sich mit Formeln und Preisrundung kombinieren lassenPipeline pro Produkt: Verhalten, Formel mit einem anderen Meta, Zufallsbereich, Rundung

_process_number_data() wendet setzen, erhöhen oder verringern um Wert oder Prozent an sowie feldübergreifende Regeln wie Aktionspreis = regulärer Preis minus Prozent. Danach folgt eine optionale Formel mit einem anderen Meta-Feld (+, -, *, / mit Schutz vor Division durch null), dann ein optionaler Zufallsbetrag in einem Bereich mit festgelegten Dezimalstellen.

update_page_field() rundet auf die Preisdezimalstellen des Shops: auf ganze Einheiten, auf 0.05, auf 0.1 oder auf eine feste Cent-Endung wie .99. Textfelder erhalten Anhängen, Voranstellen, Ersetzen (mit oder ohne Beachtung der Groß-/Kleinschreibung) und Platzhalter wie {TITLE}, {SKU}, {meta:key} und {attribute:pa_size}.

Der Aktionspreis muss unter dem regulären Preis bleiben, und Datumsangaben müssen wirksam werdenAktionspreis >= regulärer Preis wird übersprungen, Aktionspreis <= 0 entfernt ihn; _price wird bei Datumsänderung neu berechnet

In einem Bulk-Durchlauf wird ein berechneter Aktionspreis, der den regulären Preis erreicht, übersprungen, und ein Wert von null oder weniger löscht den Aktionspreis. Ein einzelnes Schreiben eines Aktionspreises auf oder über dem regulären Preis wird auf den regulären Preis minus die kleinste Preiseinheit begrenzt.

Beim Schreiben von Aktionsdaten wird _price sofort auf den aktuell geltenden Preis gesetzt (Aktion noch nicht begonnen, laufend, beendet) und die Produkt-Transients werden geleert, statt auf den geplanten Aktions-Job von WooCommerce zu warten.

Lager- und Preisschreibvorgänge dürfen variable Produkte oder gemeinsamen Lagerbestand nicht beschädigenVariable Elternprodukte und Variationen mit Eltern-Lager bleiben unberührt; Lagerwerte bleiben über Umschaltungen erhalten

WooCommerce liest die Preise eines variablen Produkts aus seinen Variationen, daher wird ein ins Elternprodukt geschriebener Preis übersprungen, und ein löschendes Schreiben entfernt jeden übrig gebliebenen Elternpreis. Ein Lagerbestand von 0 oder weniger schaltet normalerweise die Lagerverwaltung ein; das geschieht nicht bei einem variablen Elternprodukt, das keinen Lagerbestand führt, weil sonst jede Variation ohne eigenen Lagerbestand als nicht vorrätig gälte, und auch nicht bei einer Variation, die aus dem Lager ihres Elternprodukts verkauft, weil sie dadurch abgekoppelt würde.

WooCommerce leert Menge, Nachbestellung und Warnschwelle für niedrigen Lagerbestand, wenn die Lagerverwaltung ausgeschaltet wird. keep_stock_on_switch() speichert sie in einem eigenen Meta und stellt sie wieder her, wenn die Verwaltung erneut eingeschaltet wird.

Variationen müssen nach Attributkombination erstellt und bearbeitet werdenKartesisches Produkt der gewählten Terms, dedupliziert; Änderungen über Kombination mit Platzhalter zugeordnet

generate_combinations() bildet das kartesische Produkt der gewählten Attribut-Terms. Beim Anwenden werden die Attribute als Variationsattribute angehängt, Kombinationen übersprungen, die ein Produkt bereits hat, Elternpreise kopiert, jeder neuen Variation zur Vermeidung von Duplikaten die SKU parent-SKU-<id> vergeben und die Variationen über den Data Store neu sortiert.

Beim Bearbeiten, Löschen und Exportieren einer Teilmenge von Variationen werden die Attribute jeder Variation mit den gewählten Kombinationen verglichen: Keys und Werte sortiert, Einträge '-1' gelten als beliebiger Wert, und beide Seiten werden URL-dekodiert, weil nicht-lateinische Attribut-Slugs kodiert gespeichert werden.

History und Rollback

im Produkt

Was es bringtEine falsche Änderung, ob von Hand, in Masse oder durch einen KI-Assistenten, lässt sich rückgängig machen, statt Produkt für Produkt korrigiert zu werden.

Problem
Jede Änderung, ob von Hand, in Masse oder durch einen KI-Agenten, muss sich rückgängig machen lassen, einschließlich der Nebeneffekte, die WooCommerce selbst anwendet, und das Zurücknehmen eines großen Durchlaufs stößt an dieselben Zeitlimits wie der Durchlauf.
Lösung

Vorherige Werte werden pro Feld in zwei eigenen Tabellen über einen Hook auf dem einzigen Schreibpfad festgehalten und portionsweise über denselben Pfad zurückgesetzt.

Stack
  • Custom MySQL tables
  • $wpdb
  • WordPress actions
Aufgaben · 6
Vorherige Werte für jeden Feldtyp festhalten, ohne Code pro FunktionEin Hook vor jedem Feldschreiben; der vorherige Wert wird pro Feld in einer eigenen Tabelle gespeichert

update_page_field() löst woobe_before_update_page_field aus, und WOOBE_HISTORY::write() liest den aktuellen Wert und kodiert ihn nach Typ: Term-IDs, Attribute, Galerie und Relationen als JSON, Downloads als name/file/hash, Lagerstatus als 1/0, Aktionsdaten als ISO-Daten mit ihrem Zeitzonen-Offset, sodass eine Rücknahme den genauen Zeitplan wiederherstellt.

Die Zeilen enthalten field_key, product_id, prev_val, mod_date, bulk_key, user_id und via_mcp, indiziert nach Produkt, Bulk-Key und Benutzer. Eine zweite Tabelle führt pro Bulk-Durchlauf einen Kopfeintrag: Status (completed oder terminated), Start, Ende, Produktanzahl und die Feld-Keys.

WooCommerce ändert den Lagerstatus als Nebeneffekt von LagerbearbeitungenNebeneffekte werden als zusätzliche Zeilen derselben Änderung geschrieben und gemeinsam in der richtigen Reihenfolge zurückgesetzt

Das Umschalten der Lagerverwaltung oder ein Lagerbestand von 0 bei einem nicht verwalteten Produkt lässt WooCommerce den Lagerstatus ableiten; das Ausschalten der Verwaltung entfernt auch die Menge. update_page_field() protokolliert stock_status und bei Bedarf stock_quantity als zusätzliche Zeilen direkt nach dem bearbeiteten Feld.

stock_status_pair() und stock_row_at() finden diese Zeilen über benachbarte IDs (+1, +2) und verlangen dasselbe Produkt, denselben Autor, denselben Kanal (von Hand oder über einen Agenten) und denselben Bulk-Key sowie eine nahe Schreibzeit. revert() stellt dann die Menge vor dem Status wieder her, weil WooCommerce den Status aus der Menge ableitet, solange der Lagerbestand verwaltet wird. Ein leerer vorheriger Lagerbestand bedeutet „nicht verwaltet“, daher schaltet untrack_stock() die Verwaltung aus, statt 0 zu schreiben.

Auch das Zurücknehmen eines Bulk-Durchlaufs mit Tausenden Zeilen läuft in ein TimeoutRollback in Portionen nach bulk_key; jede zurückgesetzte Zeile wird gelöscht; der Kopfeintrag am Ende entfernt

Im Tab History setzt woobe_history_revert_bulk_portion() 10 Zeilen pro AJAX-Request zurück, mit Fortschrittsanzeige, und die Kopfzeile wird gelöscht, wenn der Zähler die Gesamtzahl erreicht. Jede Rücknahme löscht ihre Zeile, sodass die nächste Portion einfach nimmt, was übrig ist.

Das MCP-Rollback-Werkzeug ist ein eigener Pfad: revert_bulk_portion() setzt bis zu 200 Zeilen pro Aufruf zurück und meldet die tatsächlich entfernten Zeilen, weil die beiden Zeilen einer Lageränderung gemeinsam zurückgesetzt werden und eine Portion eine Zeile mehr als ihr Limit enthalten kann.

revert() entfernt zuerst alle woobe_before_update_page_field-Listener, damit die eigenen Schreibvorgänge des Rollbacks nicht als neue History aufgezeichnet werden.

Mehrere Benutzer und KI-Agenten teilen sich eine HistoryEine scope()-Regel: Administratoren sehen alle Zeilen, andere ihre eigenen; serverseitig durchgesetzt

Jede Zeile trägt user_id und via_mcp. Der Shop-weite MCP-Schlüssel schreibt als Benutzer -777, was weder mit einem echten Benutzer noch mit 0 (nicht angemeldet) kollidieren kann. scope() liefert eine SQL-Bedingung, die jede Liste, Rücknahme, Löschung und Bereinigung verwendet, sodass keine Abfrage von der Regel abweichen kann.

Handler prüfen solo_in_scope() oder bulk_in_scope() und antworten mit 403 auf einen von Hand gebauten Request für eine Zeile, die der Benutzer nicht sehen darf. Die Autorennamen für die Liste werden in einer einzigen Abfrage gelesen.

Bestehende Installationen brauchten eine neue Spalte, ohne alte Zeilen zu beschädigenVersionierte Schema-Option, ALTER durch Spaltenprüfung abgesichert, Nachtragen der Agenten-Zeilen

CREATE TABLE IF NOT EXISTS verändert nie eine bereits vorhandene Tabelle. maybe_upgrade_schema() läuft einmal pro Schema-Version (derzeit 2), prüft vor jedem ALTER mit SHOW COLUMNS, damit ein gleichzeitiger Request nicht daran scheitert, versucht es beim nächsten Laden erneut, falls ALTER fehlschlägt, und markiert alte Zeilen von Benutzer -777 als über einen Agenten geschrieben.

Die kostenlose Version führt eine kurze History, ohne andere Benutzer anzutastenBereinigung pro Autor beim Shutdown: letzte 2 Einzel- und 2 Bulk-Änderungen, Lagerzeilen bleiben erhalten

In der kostenlosen Version behält prune_author() die zwei neuesten Einzeländerungen und die zwei neuesten Bulk-Durchläufe eines Autors. Die Zeilen, die zu einer behaltenen Lageränderung gehören, bleiben über stock_change_rows() mit erhalten. Die Bereinigung läuft beim Shutdown nach einem Schreibvorgang, und das Öffnen des Tabs löscht nie Zeilen eines anderen Benutzers.

Export

im Produkt

Was es bringtGefilterte Produktlisten werden in Dateien exportiert, die der Shop wieder importieren kann, auf schwachen Servern in Teilen aufgebaut und nie offen im Web belassen.

Problem
Ein Export eines gefilterten Katalogs mit Variationen muss auf schwachen Servern in Teilen laufen, Dateien erzeugen, die WooCommerce wieder importieren kann, und darf keine Produktdaten offen im Web liegen lassen.
Lösung

Der Export läuft als Folge von AJAX-Requests, die an eine Datei anhängen, deren Name pro Export abgeleitet wird; sie liegt in einem geschlossenen Ordner und wird über einen autorisierten Handler ausgeliefert.

Stack
  • fputcsv
  • DOMDocument (WXR)
  • hash_hmac
  • .htaccess
  • WP REST API
Aufgaben · 4
Ein großer Export lässt sich nicht in einem Request aufbauenZuerst der Header, dann werden Zeilen pro AJAX-Batch an dieselbe Datei angehängt

woobe_export_products_count() löscht frühere Dateien, schreibt den CSV-Header oder das XML-Gerüst und gibt die gefilterten IDs zurück. woobe_export_products() hängt pro Request einen Batch mit fopen(a+) und fputcsv an, da WP_Filesystem nicht zeilenweise streamen kann; bei XML lädt es das WXR-Dokument und hängt item-Knoten an.

Variationen werden nach ihrem Elternprodukt eingefügt, wahlweise ohne nicht vorrätige, und können auf Attributkombinationen beschränkt oder davon ausgeschlossen werden.

Die CSV muss sich in den eigenen Produktimporter von WooCommerce laden lassenAttribute als je vier Spalten, Downloads als Paare aus Name und URL, jeder Wert flachgezogen

get_csv_titles() schreibt 'Attribute N value(s)', 'name', 'visible' und 'global' pro Attribut sowie 'Download N name' und 'URL' pro Datei-Slot, also die Spalten, die der WooCommerce-Importer liest; Meta-Spalten heißen 'Meta: key'.

flatten_export_value() reduziert Arrays und Objekte (Terms, Attribute, WC-Datenobjekte) auf Skalare, die mit '|' verbunden werden, sodass ein Spaltentyp, den das Plugin nie gesehen hat, auch von Drittanbietern, nicht das Wort 'Array' schreibt.

Exportdateien enthalten Preise und Datei-URLs und dürfen nicht öffentlich seinOrdner per .htaccess gesperrt, Dateiname mit HMAC-Endung, Download über geprüften Handler

Das Postfix des Dateinamens muss einem Minuten-Zeitstempel entsprechen und erhält eine 12 Zeichen lange Endung aus hash_hmac('sha256', postfix, wp_salt('auth')): identisch für jeden Request eines Exports, sodass alle Batches in einer Datei landen, von außen aber nicht erratbar. Die Endung ist bei nginx wichtig, das die .htaccess-Sperrregel ignoriert.

woobe_export_download() prüft Nonce und Berechtigung, akzeptiert nur Namen, die die Erweiterung erzeugt, leert alle Ausgabepuffer und streamt mit readfile() ohne Content-Length. Dateien, die älter als 8 Stunden sind, werden gelöscht, höchstens einmal pro Stunde geprüft.

Ein KI-Agent braucht einen Download-Link, ohne seinen Schlüssel preiszugebenREST-Route mit einem Einzeldatei-Token aus 32 Hex-Zeichen, das nach 15 Minuten abläuft

Ein Klick im Browser kann keinen Authorization-Header senden, und der MCP-Schlüssel wird aus URLs herausgehalten. Das Export-Werkzeug speichert den Dateinamen unter einem zufälligen Token in einem Transient mit 900 Sekunden Lebensdauer, und /woobe/v1/mcp/export/<token> liefert nur diese Datei aus.

MCP-Server für KI-Agenten

shipped · 2026

Was es bringtEin KI-Assistent kann den Shop für den Inhaber bearbeiten, aber nur auf einer bestätigten Auswahl, mit vorheriger Vorschau und jede Änderung umkehrbar.

Problem
Ein KI-Agent arbeitet über eine Netzwerk-API ohne Bildschirm, kann einen Filter oder eine Anzahl falsch lesen und kann einen Schreibbefehl gegen den gesamten Katalog absetzen; trotzdem muss er sicher und umkehrbar sein und in die PHP-Zeitlimits passen.
Lösung

Die Werkzeuge nutzen die eigenen Filter-, Bulk- und History-Engines des Editors; Schreibvorgänge nehmen nur eingefrorene Auswahlen an, die durch ihre Anzahl bestätigt sind, lassen sich vorab mit einem Dry-Run-Vorschauwerkzeug prüfen und laufen in fortsetzbaren Portionen.

Stack
  • WP REST API
  • JSON-RPC 2.0
  • Model Context Protocol
  • wp_options
Aufgaben · 6
Ein falsch gelesener Filter darf einen Schreibvorgang nicht auf den ganzen Katalog ausweitenSchreibvorgänge nehmen nur eine eingefrorene selection_id plus confirm_count in Höhe ihrer Größe an

woobe_find_products führt den Filter einmal aus und speichert die IDs in einer nicht automatisch geladenen Option (bis zu 200,000 IDs, 24 Stunden gültig). Die Antwort enthält selection_id, count, eine Stichprobe und whole_catalogue, gemessen an einer ungefilterten Abfrage, sodass ein Filter, der nichts eingrenzt, als solcher gemeldet wird.

woobe_apply_bulk akzeptiert gar keinen Filter. Es lehnt ab, sofern confirm_count nicht der Auswahlgröße entspricht, sodass ein Agent, der die Zahl nicht gelesen hat, sie nicht erraten kann. Produkte, die nach dem Find angelegt wurden, gehören nicht zur Auswahl.

Der Filter eines Agenten muss abgelehnt statt ignoriert werden, wenn die Engine ihn nicht anwenden kannStrenger Validator lehnt jeden Key und jede Form ab, die die Engine nicht anwenden würde, und nennt den Grund

Die Filter-Engine liest die Felder des eigenen Formulars des Editors und ignoriert jeden anderen Key. Ein Agent kann einen falsch geschriebenen Key oder ein Datum als {from, to} senden, und eine ignorierte Bedingung würde die Auswahl ausweiten. WOOBE_MCP_FILTER prüft jeden Key gegen die Feldliste, die die Engine liest, lehnt unter Nennung des Keys und der akzeptierten Form ab und übergibt den Rest in der Form, die die Oberfläche sendet.

Die Engine hängt posts_where-Hooks an, die für den Rest des Requests bestehen bleiben, was auf der Oberfläche ein Seitenaufruf ist, in einem JSON-RPC-Batch aber mehrere Finds sein können. release_filter_hooks() entfernt sie nach jeder MCP-Abfrage, damit ein Find nie die Bedingungen eines anderen erbt. Argumenttypen werden gegen das inputSchema jedes Werkzeugs geprüft, sodass der String 'maybe' keine Bestätigung ist.

Die Vorschau muss zeigen, was der Schreibvorgang wirklich erzeugtDer Dry-Run spiegelt die Bulk-Arithmetik und verkettet die Operationen eines Durchlaufs der Reihe nach

simulate() kopiert _process_number_data() wortgetreu, und run_chain() reicht jedes Ergebnis an die nächste Operation weiter, sodass ein zusammen mit einem neuen regulären Preis gesetzter Aktionspreis gegen den neuen geprüft wird. Arithmetik wird nur auf die numerischen Felder angewendet, auf denen die Engine rechnet; jedes andere Feld wird als einfache Ersetzung angezeigt.

Die Vorschau liefert drei Zahlen (Auswahl, betroffen nach Ausweitung der Elternprodukte auf Variationen, beschreibbar) sowie Warnungen: Prozent eines leeren Feldes übersprungen, variable Elternprodukte ohne eigenen Preis, Lagerbestand nicht verwaltet.

Ein REST-Aufruf hat dasselbe Zeitlimit wie ein AJAX-BatchChunks zu 25 über do_bulk, 20 s pro Aufruf, fortgesetzt über bulk_key und next_offset

Die Apply-Schleife zerlegt die Warteschlange in Chunks zu 25 und ruft WOOBE_BULK::do_bulk() auf, die Engine der Oberfläche, bis 20 Sekunden vergangen sind. Sie gibt next_offset und bulk_key zurück; eine Fortsetzung schreibt in denselben History-Kopf, sodass ein Rollback den ganzen Auftrag zurücknimmt. Caches werden einmal nach den Schreibvorgängen geleert, nicht pro Chunk.

In der kostenlosen Version schrumpft der Chunk auf das verbleibende Schreibkontingent: ein Token Bucket von 100 Produkten, der sich über 30 Minuten kontinuierlich auffüllt, mit Bruchteil-Tokens und fester Obergrenze, sodass Leerlaufzeit keinen großen Durchlauf bezahlen kann.

Variationen tragen keine der Terms oder Metadaten, nach denen der Filter suchtKinder werden nach der Abfrage ergänzt: alle oder nur die, die jedes genannte Attribut treffen

include_variations 'all' ergänzt jedes Kind der gefundenen Elternprodukte; 'matching' behält Kinder, deren Attributwerte zu allen pa_-Terms des Filters passen („gelb und klein“ ist eine Variation, nicht die Vereinigung), wobei ein leerer Wert als „beliebig“ gilt. Kategorien und Schlagwörter können Kinder nicht eingrenzen.

Attribut-Keys werden mit sanitize_title() gebildet, wie WooCommerce sie bildet, weil nicht-lateinische Attributnamen URL-kodiert gespeichert werden und sanitize_key() sie entfernen würde. Im Matching-Modus werden variable Elternprodukte aus der Auswahl entfernt, damit ein späteres variations_only sie nicht wieder auf alle Variationen ausweitet.

In einem REST-Aufruf verhalten sich Plugin und Shop anders als im AdminEigener Start bei rest_api_init, Währungs-Preisfilter entfernt, übergeordnete Terms ergänzt

WOOBE::init() läuft bei init, bevor REST_REQUEST definiert ist, sieht also bei einem REST-Aufruf weder Admin noch REST und würde nichts tun. bootstrap.php hängt sich selbst an rest_api_init und fährt das Plugin hoch; is_should_init() erlaubt REST nur für die eigenen Routen des Plugins, nicht für den gesamten REST-Verkehr der Website.

Währungsumschalter filtern Preis-Getter, sodass der Agent umgerechnete Preise las; strip_price_filters() entfernt diese Filter nur für den MCP-Request. Ein Produkt, das in eine Unterkategorie gelegt wird, wird über einen set_object_terms-Listener auch in deren Vorfahren gelegt, da in einem Chat niemand übergeordnete Kästchen anhakt.

Zugriffskontrolle und Sicherheit

shipped · 2026

Was es bringtNur die richtigen Personen können Produkte ändern, mit aktiviertem optionalem zweiten Faktor öffnet ein durchgesickerter KI-Zugangsschlüssel allein den Shop nicht, und der Zugriff lässt sich pro Bereich begrenzen.

Problem
Ein Bulk-Editor hat Schreibzugriff auf jedes Produkt. Jeder AJAX-Handler, die History und der MCP-Endpunkt müssen prüfen, wer handelt, und ein in den Einstellungen eines Assistenten abgelegter MCP-Schlüssel kann durchsickern.
Lösung

Ein Guard für alle AJAX-Handler mit Eigentumsprüfung pro Produkt; für MCP Schlüssel nur im Header, in konstanter Zeit verglichen, ein bestätigtes Sitzungs-Token (beim Shop-weiten Schlüssel optional, bei persönlichen Schlüsseln immer aktiv), persönliche Schlüssel und eine Berechtigungsmatrix pro Bereich.

Stack
  • WordPress nonces
  • current_user_can / meta capabilities
  • hash_equals
  • random_int
  • WP REST permission_callback
Aufgaben · 5
Dutzende AJAX-Handler hatten jeweils eigene, unvollständige PrüfungenEin einziger Guard: Nonce, Berechtigung, dann edit_post pro Produkt, bei Variationen über das Elternprodukt

Nach einem WPScan-Bericht wurde die gesamte Menge der AJAX-Handler geprüft, nicht nur die gemeldeten, und jeder Handler wurde auf WOOBE_HELPER::check_ajax_access() umgestellt. Es prüft die Nonce, dann die Berechtigung, dann current_user_can('edit_post') für jede Produkt-ID.

Variationen werden gegen das Elternprodukt geprüft: WooCommerce bildet edit_post bei einer Variation auf das einfache edit_products ab, das den Besitz ignoriert, und Variationen haben post_author 0. get_request_array() weist einen Skalar zurück, wo ein Array erwartet wird, statt ihn zu casten. Nicht-Admin-Rollen sind zusätzlich pro Feld eingeschränkt.

Ein statischer API-Schlüssel sickert über URLs, Logs und Timing durchSchlüssel nur in Headern, hash_equals, jeder Request geprüft, keine Route bei ausgeschaltetem MCP

Bei ausgeschaltetem MCP kehrt register_routes() zurück, bevor etwas registriert wird, sodass der Endpunkt nicht existiert. permission() verlangt den Schlüssel bei jedem Request, auch beim MCP-Handshake, sodass ein nicht authentifizierter Aufrufer nichts über den Endpunkt erfährt. Der Schlüssel wird aus Authorization: Bearer oder X-WOOBE-KEY gelesen, mit dem Apache-Fallback REDIRECT_HTTP_AUTHORIZATION, und mit hash_equals verglichen.

Ablehnungen verwenden 403, nicht 401: In MCP startet ein 401 die OAuth-Erkennung, und der Client meldet den Server als defekt. Download- und Upload-Links verwenden eigene Tokens für einen einzigen Zweck, weil ein Klick im Browser keinen Header senden kann.

Ein durchgesickerter Schlüssel allein soll keinen Zugang zum Shop gebenZweiter Faktor: ein in WordPress bestätigtes Sitzungs-Token, gehasht gespeichert, Ablauf nach 1 h Inaktivität

Der Assistent fordert ein Token an, bestehend aus vier Gruppen zu je acht Buchstaben und Ziffern aus random_int, und es wird von einer in WordPress angemeldeten Person bestätigt: bei dem Shop-weiten Schlüssel von einem Administrator, bei einem persönlichen Schlüssel vom Inhaber des Schlüssels in seinen eigenen Einstellungen. Beim Shop-weiten Schlüssel ist dieser zweite Faktor eine Einstellung; bei persönlichen Schlüsseln ist er immer aktiv. Gespeichert wird nur sein sha256-Hash zusammen mit den Zeiten der Bestätigung und der letzten Verwendung. check_connection() vergleicht mit hash_equals und trennt die Verbindung nach 3600 Sekunden ohne Aufruf; die Zeit der letzten Verwendung wird höchstens einmal pro Minute geschrieben.

Verbindungen gehören jeweils einem Inhaber: Ein für einen persönlichen Schlüssel bestätigtes Token wird mit jedem anderen Schlüssel abgelehnt.

Persönliche Schlüssel dürfen nicht verraten, welche Benutzer Zugriff habenDer gespeicherte Schlüssel wird immer gelesen und verglichen, mit einem gleich langen Platzhalter, wenn keiner existiert

Ein persönlicher Schlüssel hat die Form woobe_<40 hex>-<user id>. personal_key_owner() liest und vergleicht den gespeicherten Schlüssel immer mit hash_equals, mit einem Platzhalter gleicher Länge, wenn der Benutzer keinen hat, und fragt Freigabeliste und Berechtigung erst nach vollständiger Übereinstimmung ab, sodass die Antwortzeit einem Fremden nicht verrät, wer Zugriff hat. IDs mit führenden Nullen nehmen den Platzhalterpfad.

Der Request läuft dann über wp_set_current_user() als dieser Benutzer, sodass seine Rolle und Feldsichtbarkeit genau wie auf der Oberfläche gelten, und die Zwei-Faktor-Verbindung ist bei persönlichen Schlüsseln immer aktiv.

Inhaber brauchen pro Shop-Bereich Nur-Lese- oder keinen ZugriffBereichsmatrix full/read/none pro Benutzer, durch WP-Berechtigungen weiter eingeengt; gesperrte Werkzeuge ausgeblendet

Jedes Werkzeug deklariert einen Bereich und annotations.readOnlyHint. Der Inhaber liefert eine Matrix über den Filter woobe_mcp_permissions; '*' setzt den Standard, sodass sie als Allow-List funktioniert. Ungültige Einträge werden protokolliert und ignoriert, und ein ungültiges '*' fällt auf full zurück, genau wie ohne Callback. Bei einem persönlichen Schlüssel wird jeder Bereich zusätzlich durch die Berechtigung eingeengt, die WooCommerce für diesen Bildschirm verlangt (edit_products, edit_shop_orders, view_woocommerce_reports und weitere).

Werkzeuge, die die Identität nicht aufrufen darf, werden gar nicht aufgelistet. Ein Schreibvorgang, der in einem anderen Bereich landet, etwa ein Rollback, das Produkte neu schreibt, fragt vor dem Schreiben require_access() für diesen Bereich. Der Filter woobe_mcp_tool_allowed kann nur weiter einengen. Kein MCP-Schlüssel, weder Shop-weit noch persönlich, darf post_author oder ID schreiben; diese werden im wp-admin von einer Person bearbeitet.

Zahlen

wordpress.org
  • Aktive Installationen 30.000+ Zähler von wordpress.org.
  • Rezensionen 227 Durchschnitt 4.7 von 5.
  • Jahre Support 8 Seit 7. Mai 2018, letztes Update 13. Oktober 2026.