BEAR
Masowy edytor produktów WooCommerce w stylu arkusza kalkulacyjnego, z serwerem MCP, dzięki któremu agenci AI mogą bezpiecznie edytować sklep.
- Pierwsze wydanie
- Aktywne instalacje
- 30 000+
- Ocena
- Czytaj opinie
O mnie
BEAR pokazuje katalog WooCommerce jako jedną edytowalną tabelę i zmienia tysiące produktów naraz: ceny, stany magazynowe, SKU, taksonomie, atrybuty, warianty i własne pola meta. Każda zmiana przechodzi jedną ścieżką zapisu pola, która dla właściwości produktu korzysta z obiektów produktów WooCommerce, jest zapisywana wraz z poprzednią wartością i można ją cofnąć z karty Historia.
Trudna jest nie sama tabela, lecz poprawny masowy zapis w prawdziwym sklepie: wartości przechowywane w pięciu różnych miejscach, produkty z wariantami, których ceny i stany leżą na wariantach, skutki uboczne WooCommerce, takie jak status magazynowy wyliczany z ilości w magazynie, oraz współdzielony hosting, który przerywa żądanie PHP po 30 sekundach.
Od 2026 roku wtyczka uruchamia też serwer MCP. Agent AI może znajdować produkty, podglądać zmianę, stosować ją porcjami i cofać, przez te same silniki filtrów, masowej edycji i historii, których używa ekran, za kluczami, potwierdzonym tokenem sesji (opcjonalnym dla klucza całego sklepu, zawsze włączonym dla kluczy osobistych) i mapą uprawnień dla poszczególnych sektorów.
Wersja darmowa i premium to jedna baza kodu przełączana jedną flagą; wersja darmowa ogranicza rozmiar strony, pola meta, głębokość historii i tempo zapisu MCP.
BEAR · edytor masowy
co rozwiązano
Tabela produktów i model pól
shipped · 2018Co to dajePracownicy widzą i zmieniają dowolny szczegół produktu lub wariantu na jednym ekranie przypominającym arkusz, nawet w katalogach zbyt dużych, by otworzyć je naraz.
- Problem
- Jeden ekran musi pokazywać i edytować dowolną wartość w katalogu dowolnej wielkości: pola wpisu, właściwości WooCommerce, taksonomie, atrybuty, pliki do pobrania i dowolne pola meta, dla produktów i ich wariantów.
- Rozwiązanie
Deklaratywny rejestr pól steruje każdą kolumną i każdym zapisem, a tabela jest stronicowana na serwerze, jedna strona WP_Query na żądanie AJAX.
- Technologie
Dużych katalogów nie można załadować do przeglądarki narazStronicowanie po stronie serwera: każda strona tabeli to WP_Query z offsetem, sortowaniem i filtrami
woobe_get_products() pobiera start, length i order z DataTables i przekazuje je do WOOBE_PRODUCTS::gets(), która buduje WP_Query. Sortowanie po kolumnie meta przełącza się na meta_value_num dla typów number, timestamp i unix, a w pozostałych przypadkach na meta_value.
Rozmiar strony jest w modelu ustawień ograniczony do 100 wierszy, z komentarzem, że większe strony powodowały HTTP 500 na słabych serwerach; w wersji darmowej jest na stałe 10. Gdy przełącznik wariantów jest włączony, warianty każdego produktu z wariantami są pobierane i wstawiane jako wiersze zaraz po produkcie nadrzędnym.
Każdy typ kolumny jest w WooCommerce przechowywany inaczejJeden wpis rejestru na kolumnę: typ przechowywania, typ wartości, edytor, sanitizer, uprawnienia
woobe_get_fields() opisuje każdą kolumnę: field_type (field, meta, prop, taxonomy, attribute, downloads, gallery, upsells i inne), typ wartości, widok edycji, sanitizer, typy produktów, których kolumna nie dotyczy, oraz widoczność dla menedżerów sklepu. Atrybuty globalne i wszystkie taksonomie produktów są dodawane w czasie działania z wc_get_attribute_taxonomies() i get_object_taxonomies().
update_page_field() rozdziela pracę według field_type: update_post_meta dla meta, settery WC_Product plus save() dla właściwości, wp_set_post_terms dla taksonomii, przebudowane listy WC_Product_Attribute dla atrybutów. Komórki tabeli, cofnięcia z Historii i większość zapisów masowych kończy się w tej funkcji. Masowe edycje taksonomii i atrybutów zapisują terminy bezpośrednio i przed zapisem wywołują ten sam hook Historii.
Własne klucze meta trzeba odróżnić od wewnętrznych kluczy WooCommercePodklasa WC_Product_Data_Store_CPT udostępnia swoją chronioną listę kluczy wewnętrznych
woobe_meta_get_keys() odczytuje wszystkie klucze meta przykładowego produktu i odejmuje klucze wewnętrzne WooCommerce. Ta lista jest chronioną właściwością magazynu danych produktu, więc WOOBE_PDS_CPT rozszerza magazyn tylko po to, by ją zwrócić, dodając _button_text i _product_url.
Klucze meta są przycinane, ale nie zamieniane na małe litery ani sanityzowane, ponieważ prawdziwe klucze zapisywane przez inne wtyczki, takie jak _woocs_sale_price_USD, rozróżniają wielkość liter.
Typowane pole meta musi działać w kolumnach, filtrach i edycji masowejJedna definicja meta jest przez hooki mapowana na rejestr, panel filtrów i panel edycji masowej
woobe_extend_fields() zamienia każdą zapisaną definicję meta w kolumnę; typ number dostaje sanitizer floatval i sortowanie liczbowe. Ta sama definicja zasila woobe_filter_numbers (zakres od/do), woobe_filter_text (LIKE, =, !=, NOT LIKE, puste, niepuste), filtry przełączników oraz woobe_bulk_number (ustaw, zwiększ lub zmniejsz o wartość lub o procent).
Kolumna przełącznika może przechowywać wartości inne niż 1/0 (na przykład yes/no) dzięki regule nadpisania dla danego klucza, odczytywanej przez over_switcher_swicher_to_val(). Wersja darmowa zachowuje pierwsze 2 pola meta.
Zserializowane tablice meta nie mają płaskiej komórki do edycjiEdytor w oknie wysyła klucze i wartości jako równoległe tablice, odbudowywane w zagnieżdżone tablice PHP
process_jsoned_meta_data() odbudowuje wartość meta z par keys/values dla tablic i keys2/values2 dla obiektów, dekodując zagnieżdżone wartości JSON, a dla zwykłych ciągów i liczb wraca do surowej wartości. Historia zapisuje takie wartości jako JSON z JSON_HEX_QUOT i JSON_HEX_TAG, aby przetrwały zapis i odczyt.
Stan edytora dla każdego użytkownika musi przetrwać opróżnienie cache obiektówJeden wiersz opcji bez autoload na użytkownika, z TTL na klucz, zamiast transientów
Filtry i konfiguracje edycji masowej są między żądaniami przechowywane przez WOOBE_STORAGE. W Redis lub Memcached transient żyje tylko w cache obiektów i ginie przy jego opróżnieniu, dlatego domyślnym magazynem jest wiersz opcji woobe_memory_<user id>: każdy klucz ma własny czas wygaśnięcia, wygasłe klucze są usuwane przy zapisie, autoload jest wyłączony, a wiersz jest usuwany razem z użytkownikiem. Tryby sesji, transientów i cookie pozostają dostępne przez filtr woobe_storage_type.
Filtrowanie
shipped · 2018Co to dajePracownicy wybierają dokładnie te produkty, których potrzebują, także według ceny lub stanu wariantu, a ten sam wybór służy do edycji i eksportu.
- Problem
- Filtry cen, stanów, wymiarów, tekstu i taksonomii muszą wybierać dokładnie właściwe produkty, także produkty z wariantami, których wartości leżą na wariantach, a ten sam zbiór musi być użyty przy stronicowaniu, edycji masowej i eksporcie.
- Rozwiązanie
Filtr jest zapisywany na serwerze pod kluczem i przy każdym zapytaniu kompilowany do klauzul tax_query, meta_query i posts_where przez jeden hook WP_Query.
- Technologie
Żądania stronicowania, edycji masowej i eksportu muszą widzieć ten sam przefiltrowany zbiórFiltr zapisany pod losowym kluczem dla każdego uruchomienia; jeden hook WP_Query odbudowuje go przy każdym żądaniu
woobe_filter_products() zapisuje sparsowany formularz filtra pod woobe_filter_<key>, gdzie klucz to losowy ciąg generowany przy każdym filtrowaniu, dzięki czemu edycja masowa według filtra działa jednocześnie w kilku kartach przeglądarki. Każde wywołanie WOOBE_PRODUCTS::gets() przepuszcza swoje argumenty przez woobe_apply_query_filter_data(), która odczytuje zapisany filtr i zwraca tax_query, meta_query, post__in oraz post_status, a dla reszty podpina hooki posts_where.
Stronicowanie tabeli, licznik edycji masowej, licznik eksportu i narzędzia wariantów wywołują gets(), więc nie mogą się różnić w tym, które produkty oznacza filtr.
Cena i stan leżą na wariantach, a tabela wyświetla produkty nadrzędneZapytanie zakresowe po produktach i wariantach, potem trafienia wariantów mapowane na ich produkty nadrzędne
regular_price_where(), sale_price_where(), stock_quantity_where() i dimensions_where() odpytują postmeta zarówno dla product, jak i product_variation, następnie odczytują post_parent dopasowanych wariantów i zawężają zapytanie główne do produktów nadrzędnych oraz wariantów z ID IN (...).
Gdy nic nie pasuje, klauzula przyjmuje postać ID = -1, więc puste dopasowanie nie zwraca żadnych produktów zamiast całego katalogu, co ma znaczenie na jedno kliknięcie przed edycją masową.
Liczby zapisane w meta jako ciągi znaków są w MySQL porównywane błędnieCAST do DECIMAL z jawną skalą; puste ciągi wykluczone z każdego zakresu
Własne zakresy liczbowe używają meta_query typu DECIMAL(30,20): sam DECIMAL ma skalę 0, więc wyszukiwanie od 100 do 150 znalazłoby też 99.xx. SQL cen rzutuje na DECIMAL(30,10).
Pusta waga jest zapisana jako '', a MySQL odczytuje '' jako 0, więc zakresy cen i wymiarów wymagają meta_value <> '' i puste pole nigdy nie liczy się jako 0. Brakujący koniec zakresu jest otwarty (podstawiany jako -999999999 lub 999999999), a od równe do oznacza dokładnie tę wartość.
Użytkownicy potrzebują AND, OR i NOT w jednym polu wyszukiwania tekstuMała składnia wyszukiwania (^ OR, ^~ AND, ^! NOT) kompilowana do klauzul MySQL REGEXP
posts_txt_where() dzieli tekst po ^; prefiks ~ łączy część przez AND, prefiks ! zamienia ją na NOT REGEXP w osobnej grupie AND. Zachowania exact, begin, end, not i empty mapują się na = i zakotwiczone REGEXP; nawiasy kwadratowe i klamrowe wyrażeń regularnych są escapowane. Dotyczy tytułu, treści, zajawki i slugu.
Filtr SKU przyjmuje listę rozdzieloną przecinkami i buduje przygotowane warunki LIKE z esc_like, OR dla trybów dopasowania i AND dla 'not'.
Dopasowanie wszystkich z kilku terminów wymaga jednej klauzuli tax_query na terminAND rozbity na jedną klauzulę na termin; nieznane operatory wracają do IN
Dla operatora AND każdy termin staje się własną klauzulą tax_query w relacji AND, ponieważ pojedyncza klauzula z operatorem AND może nie zwrócić żadnych produktów. Operatory są ograniczone białą listą do IN, NOT IN, AND, EXISTS i NOT EXISTS; WordPress odrzuca klauzulę z nieznanym operatorem i dopasowałby każdy produkt.
Widoczność w katalogu jest tłumaczona na kombinacje terminów product_visibility exclude-from-catalog i exclude-from-search, a filtr statusu magazynowego ogranicza też product_type do typów, które mają status magazynowy.
Silnik edycji masowej
shipped · 2018Co to dajeZmiany cen, stanów i wariantów w tysiącach produktów kończą się niezawodnie na zwykłym hostingu i podążają za własnymi regułami cen i stanów sklepu.
- Problem
- Zmiana masowa może objąć tysiące produktów na hostingu z krótkimi limitami czasu PHP, a każdy zapis musi respektować reguły WooCommerce dotyczące cen, stanów i wariantów.
- Rozwiązanie
ID docelowe są ustalane raz, a następnie zapisywane małymi partiami AJAX tą samą ścieżką zapisu pojedynczego pola, której używa tabela i która niesie reguły WooCommerce.
- Technologie
Edycja masowa tysięcy produktów trafia na limity czasu PHPID pobrane raz, potem wysyłane partiami po N na żądanie AJAX; N jest ustawieniem
woobe_bulk_products_count() zapisuje konfigurację edycji masowej pod losowym 16-znakowym bulk_key i zwraca każde przefiltrowane ID (fields=ids, bez stronicowania). __woobe_bulk_products() w bulk.js tnie tę listę i wysyła partię za partią, aktualizuje pasek postępu i na końcu wywołuje woobe_bulk_finish; użytkownik może przerwać bieżące żądanie.
Rozmiar partii to number_posts_processed, od 10 do 100 na żądanie, domyślnie 30, więc słaby serwer może wymienić szybkość na stabilność.
Edytowane produkty wypadają z filtra w trakcie trwania przebieguZamrożona lista ID: każda partia niesie ID z migawki, a nie ponownie uruchomione zapytanie
Stronicowanie przefiltrowanego zapytania między partiami pomijałoby produkty: edycja ceny wypycha produkt z filtra cen i każdy kolejny offset się przesuwa. Lista ID pobrana na początku jest trzymana przez klienta, a każde żądanie partii wysyła swoje ID jawnie, bez klucza filtra.
Po przebiegu zaznaczone wiersze, które nie pojawiają się już w przerysowanej tabeli, są usuwane z zaznaczenia, aby kolejna akcja nie dotyczyła produktów, których użytkownik już nie widzi.
Zachowania liczbowe muszą łączyć się z formułami i zaokrąglaniem cenPotok na produkt: zachowanie, formuła z innym meta, losowy zakres, zaokrąglanie
_process_number_data() stosuje ustaw, zwiększ lub zmniejsz o wartość lub procent oraz reguły międzypolowe, takie jak cena promocyjna = regularna minus procent. Następnie opcjonalna formuła z innym polem meta (+, -, *, / z zabezpieczeniem przed dzieleniem przez zero), potem opcjonalna losowa kwota z zakresu z zadaną liczbą miejsc po przecinku.
update_page_field() zaokrągla do liczby miejsc dziesiętnych cen sklepu: do całych jednostek, w górę do 0.05, w górę do 0.1 albo do stałej końcówki groszy, takiej jak .99. Pola tekstowe dostają dopisanie na końcu, na początku, zamianę (z rozróżnianiem wielkości liter lub bez) oraz symbole zastępcze, takie jak {TITLE}, {SKU}, {meta:key} i {attribute:pa_size}.
Cena promocyjna musi pozostać niższa od regularnej, a daty muszą zadziałaćPromocyjna >= regularnej pomijana, promocyjna <= 0 usuwa promocję; _price przeliczane przy zmianie dat
W przebiegu masowym obliczona cena promocyjna, która dosięga ceny regularnej, jest pomijana, a wartość zero lub mniejsza usuwa promocję. Pojedynczy zapis ceny promocyjnej równej lub wyższej od regularnej jest ograniczany do ceny regularnej minus najmniejsza jednostka ceny.
Gdy zapisywane są daty promocji, _price jest od razu ustawiane na cenę obowiązującą teraz (promocja nierozpoczęta, trwająca, zakończona), a transienty produktu są czyszczone, zamiast czekać na zaplanowane zadanie promocji WooCommerce.
Zapisy stanu i ceny nie mogą uszkodzić produktów z wariantami ani współdzielonego stanuProdukty nadrzędne z wariantami i warianty ze stanem nadrzędnym pozostawione w spokoju; stan zachowany przy przełączeniach
WooCommerce odczytuje ceny produktu z wariantami z jego wariantów, więc cena zapisana na produkcie nadrzędnym jest pomijana, a zapis czyszczący usuwa pozostałą cenę nadrzędną. Stan 0 lub mniejszy zwykle włącza zarządzanie stanem; nie robi się tego na nadrzędnym produkcie z wariantami, który nie śledzi stanu, bo każdy wariant bez własnego stanu zostałby oznaczony jako niedostępny, ani na wariancie sprzedającym ze stanu produktu nadrzędnego, bo zostałby od niego odłączony.
WooCommerce czyści ilość, zamówienia oczekujące (backorders) i próg niskiego stanu, gdy zarządzanie stanem zostaje wyłączone. keep_stock_on_switch() zapisuje je we własnym meta i przywraca, gdy zarządzanie zostanie włączone ponownie.
Warianty trzeba tworzyć i edytować według kombinacji atrybutówIloczyn kartezjański wybranych terminów, bez duplikatów; edycje dopasowywane po kombinacji z symbolem wieloznacznym
generate_combinations() buduje iloczyn kartezjański wybranych terminów atrybutów. Zastosowanie go przypina atrybuty jako atrybuty wariantów, pomija kombinacje, które produkt już ma, kopiuje ceny produktu nadrzędnego, nadaje każdemu nowemu wariantowi SKU parent-SKU-<id>, aby uniknąć duplikatów, i ponownie sortuje warianty przez magazyn danych.
Edycja, usuwanie i eksport podzbioru wariantów porównuje atrybuty każdego wariantu z wybranymi kombinacjami: klucze i wartości posortowane, wpisy '-1' traktowane jak dowolna wartość, a obie strony zdekodowane z URL, ponieważ nielacińskie slugi atrybutów są przechowywane w postaci zakodowanej.
Historia i cofanie zmian
w produkcieCo to dajeBłędną zmianę, ręczną, masową lub wykonaną przez asystenta AI, można cofnąć, zamiast poprawiać produkt po produkcie.
- Problem
- Każda zmiana, ręczna, masowa lub od agenta AI, musi dać się cofnąć, łącznie ze skutkami ubocznymi, które WooCommerce stosuje samodzielnie, a cofnięcie dużego przebiegu trafia na te same limity czasu co sam przebieg.
- Rozwiązanie
Poprzednie wartości są zapisywane dla każdego pola w dwóch własnych tabelach przez hook na jednej ścieżce zapisu i cofane porcjami tą samą ścieżką.
- Technologie
Zapisywać wartości sprzed zmiany dla dowolnego typu pola bez kodu dla każdej funkcjiJeden hook przed każdym zapisem pola; poprzednia wartość zapisana dla pola we własnej tabeli
update_page_field() wywołuje woobe_before_update_page_field, a WOOBE_HISTORY::write() odczytuje bieżącą wartość i koduje ją według typu: ID terminów, atrybuty, galerię i powiązania jako JSON, pliki do pobrania jako name/file/hash, status magazynowy jako 1/0, daty promocji jako daty ISO z przesunięciem strefy czasowej, aby cofnięcie przywracało dokładny harmonogram.
Wiersze zawierają field_key, product_id, prev_val, mod_date, bulk_key, user_id i via_mcp, z indeksami po produkcie, kluczu masowym i użytkowniku. Druga tabela przechowuje jeden nagłówek na przebieg masowy: stan (completed lub terminated), start, koniec, liczbę produktów i klucze pól.
WooCommerce zmienia status magazynowy jako skutek uboczny edycji stanuSkutki uboczne zapisywane jako dodatkowe wiersze jednej zmiany i cofane razem, w kolejności
Przełączenie zarządzania stanem albo stan 0 na produkcie bez śledzenia sprawia, że WooCommerce wylicza status magazynowy; wyłączenie zarządzania usuwa też ilość. update_page_field() zapisuje stock_status, a w razie potrzeby stock_quantity, jako dodatkowe wiersze zaraz po edytowanym polu.
stock_status_pair() i stock_row_at() znajdują te wiersze po sąsiednich ID (+1, +2) i wymagają tego samego produktu, autora, kanału (ręcznie lub przez agenta) i klucza masowego oraz bliskiego czasu zapisu. revert() przywraca potem ilość przed statusem, ponieważ WooCommerce wylicza status z ilości, gdy stan jest zarządzany. Pusty poprzedni stan oznacza 'nieśledzony', więc untrack_stock() wyłącza zarządzanie zamiast zapisywać 0.
Cofanie masowego przebiegu tysięcy wierszy też przekracza limit czasuCofanie porcjami według bulk_key; każdy cofnięty wiersz usuwany; nagłówek usuwany na końcu
Na karcie Historia woobe_history_revert_bulk_portion() cofa 10 wierszy na żądanie AJAX z paskiem postępu, a wiersz nagłówka jest usuwany, gdy licznik osiągnie sumę. Każde cofnięcie usuwa swój wiersz, więc następna porcja po prostu bierze to, co zostało.
Narzędzie wycofywania MCP to osobna ścieżka: revert_bulk_portion() cofa do 200 wierszy na wywołanie i zgłasza wiersze faktycznie usunięte, ponieważ dwa wiersze zmiany stanu są cofane razem, a porcja może zabrać o jeden wiersz więcej niż jej limit.
revert() najpierw usuwa wszystkie nasłuchiwacze woobe_before_update_page_field, aby własne zapisy cofania nie były rejestrowane jako nowa historia.
Kilku użytkowników i agentów AI dzieli jedną historięJedna reguła scope(): administratorzy widzą wszystkie wiersze, pozostali własne; egzekwowana na serwerze
Każdy wiersz niesie user_id i via_mcp. Klucz MCP całego sklepu zapisuje jako użytkownik -777, który nigdy nie zderzy się z prawdziwym użytkownikiem ani z 0 (niezalogowany). scope() zwraca jeden warunek SQL używany przez każdą listę, cofnięcie, usunięcie i czyszczenie, więc żadne zapytanie nie może odejść od reguły.
Handlery sprawdzają solo_in_scope() lub bulk_in_scope() i odpowiadają 403 na żądanie zbudowane ręcznie dla wiersza, którego użytkownik nie może zobaczyć. Nazwy autorów dla listy są pobierane jednym zapytaniem.
Istniejące instalacje potrzebowały nowej kolumny bez psucia starych wierszyOpcja z wersją schematu, ALTER zabezpieczony sprawdzeniem kolumny, uzupełnienie wierszy agenta
CREATE TABLE IF NOT EXISTS nigdy nie zmienia istniejącej tabeli. maybe_upgrade_schema() uruchamia się raz na wersję schematu (obecnie 2), przed każdym ALTER sprawdza SHOW COLUMNS, aby równoległe żądanie się na nim nie wysypało, ponawia przy następnym załadowaniu, jeśli ALTER się nie powiedzie, i oznacza stare wiersze użytkownika -777 jako zapisane przez agenta.
Wersja darmowa trzyma krótką historię, nie ruszając innych użytkownikówPrzycinanie według autora przy zamknięciu: ostatnie 2 zmiany pojedyncze i 2 masowe, wiersze stanów zachowane
W wersji darmowej prune_author() zachowuje dwie najnowsze zmiany pojedyncze i dwa najnowsze przebiegi masowe jednego autora. Wiersze należące do zachowanej zmiany stanu są zachowywane razem z nią przez stock_change_rows(). Przycinanie działa przy zamknięciu po zapisie, a otwarcie karty nigdy nie usuwa wierszy innego użytkownika.
Eksport
w produkcieCo to dajePrzefiltrowane listy produktów są eksportowane do plików, które sklep może zaimportować z powrotem, budowanych w częściach na słabych serwerach i nigdy nie zostawianych otwartych w sieci.
- Problem
- Eksport przefiltrowanego katalogu z wariantami musi działać w częściach na słabych serwerach, tworzyć pliki, które WooCommerce potrafi zaimportować z powrotem, i nie może zostawiać danych produktów otwartych w sieci.
- Rozwiązanie
Eksport działa jako ciąg żądań AJAX dopisujących do jednego pliku, którego nazwa jest wyprowadzana osobno dla każdego eksportu, przechowywanego w zamkniętym folderze i przesyłanego strumieniowo przez autoryzowany handler.
- Technologie
Dużego eksportu nie da się zbudować w jednym żądaniuNajpierw zapisywany nagłówek, potem wiersze dopisywane partiami AJAX do tego samego pliku
woobe_export_products_count() czyści wcześniejsze pliki, zapisuje nagłówek CSV lub szkielet XML i zwraca przefiltrowane ID. woobe_export_products() dopisuje jedną partię na żądanie przez fopen(a+) i fputcsv, ponieważ WP_Filesystem nie potrafi strumieniować linia po linii; dla XML ładuje dokument WXR i dopisuje węzły item.
Warianty są dodawane po swoim produkcie nadrzędnym, opcjonalnie bez niedostępnych, i można je ograniczyć do kombinacji atrybutów lub z nich wykluczyć.
CSV musi się wczytywać do własnego importera produktów WooCommerceAtrybuty jako po cztery kolumny, pliki do pobrania jako pary nazwa/URL, każda wartość spłaszczona
get_csv_titles() zapisuje 'Attribute N value(s)', 'name', 'visible' i 'global' dla każdego atrybutu oraz 'Download N name' i 'URL' dla każdego miejsca na plik, czyli kolumny, które czyta importer WooCommerce; kolumny meta nazywają się 'Meta: key'.
flatten_export_value() redukuje tablice i obiekty (terminy, atrybuty, obiekty danych WC) do skalarów łączonych znakiem '|', dzięki czemu typ kolumny, którego wtyczka nigdy nie widziała, także pochodzący od innego dostawcy, nie zapisuje słowa 'Array'.
Pliki eksportu zawierają ceny i adresy URL plików i nie mogą być publiczneFolder zamknięty przez .htaccess, nazwa pliku z końcówką HMAC, pobieranie przez sprawdzany handler
Przyrostek nazwy pliku musi odpowiadać znacznikowi czasu z dokładnością do minuty i dostaje 12-znakową końcówkę z hash_hmac('sha256', postfix, wp_salt('auth')): identyczną dla każdego żądania jednego eksportu, więc wszystkie partie trafiają do jednego pliku, a mimo to niemożliwą do odgadnięcia z zewnątrz. Końcówka ma znaczenie na nginx, który ignoruje regułę deny z .htaccess.
woobe_export_download() sprawdza nonce i uprawnienia, akceptuje tylko nazwy generowane przez rozszerzenie, czyści wszystkie bufory wyjścia i przesyła strumieniowo przez readfile() bez Content-Length. Pliki starsze niż 8 godzin są usuwane, sprawdzane najwyżej raz na godzinę.
Agent AI potrzebuje linku do pobrania bez ujawniania swojego kluczaTrasa REST z jednoplikowym tokenem 32-hex, wygasającym po 15 minutach
Kliknięcie w przeglądarce nie może wysłać nagłówka Authorization, a klucz MCP nie trafia do adresów URL. Narzędzie eksportu zapisuje nazwę pliku pod losowym tokenem w transiencie o czasie życia 900 sekund, a /woobe/v1/mcp/export/<token> udostępnia wyłącznie ten plik.
Serwer MCP dla agentów AI
shipped · 2026Co to dajeAsystent AI może edytować sklep w imieniu właściciela, ale tylko na potwierdzonym wyborze, najpierw z podglądem, a każda zmiana jest odwracalna.
- Problem
- Agent AI pracuje przez sieciowe API bez ekranu, może źle odczytać filtr lub liczbę i może wydać zapis na cały katalog; mimo to musi być bezpieczny, odwracalny i mieścić się w limitach czasu PHP.
- Rozwiązanie
Narzędzia używają własnych silników filtrów, edycji masowej i historii edytora; zapisy przyjmują tylko zamrożone wybory potwierdzone ich liczbą, można je najpierw sprawdzić narzędziem podglądu w trybie próbnym i wykonują się w porcjach, które można wznowić.
- Technologie
Źle odczytany filtr nie może rozszerzyć zapisu na cały katalogZapisy przyjmują tylko zamrożony selection_id plus confirm_count równy jego rozmiarowi
woobe_find_products uruchamia filtr raz i zapisuje ID w opcji bez autoload (do 200,000 ID, ważne 24 godziny). Odpowiedź zawiera selection_id, count, próbkę i whole_catalogue, mierzone względem niefiltrowanego zapytania, więc filtr, który niczego nie zawęża, jest zgłaszany jako taki.
woobe_apply_bulk w ogóle nie przyjmuje filtra. Odmawia, dopóki confirm_count nie jest równy rozmiarowi wyboru, więc agent, który nie odczytał liczby, nie może jej zgadnąć. Produkty utworzone po wyszukiwaniu nie wchodzą do wyboru.
Filtr agenta musi zostać odrzucony, a nie zignorowany, gdy silnik nie może go zastosowaćŚcisły walidator odrzuca każdy klucz lub kształt, którego silnik by nie zastosował, i podaje powód
Silnik filtrów czyta pola własnego formularza edytora i ignoruje każdy inny klucz. Agent może wysłać klucz z literówką albo datę jako {from, to}, a zignorowany warunek rozszerzyłby wybór. WOOBE_MCP_FILTER sprawdza każdy klucz względem listy pól, które czyta silnik, odmawia z nazwanym kluczem i akceptowanym kształtem, a resztę przekazuje w postaci, którą wysyła ekran.
Silnik podpina hooki posts_where, które zostają do końca żądania, co na ekranie oznacza jedno załadowanie strony, ale w jednej paczce JSON-RPC może oznaczać kilka wyszukiwań. release_filter_hooks() usuwa je po każdym zapytaniu MCP, więc jedno wyszukiwanie nigdy nie dziedziczy warunków innego. Typy argumentów są sprawdzane względem inputSchema każdego narzędzia, więc ciąg 'maybe' nie jest potwierdzeniem.
Podgląd musi pokazywać to, co zapis naprawdę wytworzyPróba robocza odtwarza arytmetykę edycji masowej i łączy operacje przebiegu po kolei
simulate() kopiuje _process_number_data() dosłownie, a run_chain() przekazuje wynik każdej operacji następnej, więc cena promocyjna ustawiona razem z nową ceną regularną jest sprawdzana względem nowej. Arytmetyka jest stosowana tylko do pól liczbowych, na których liczy silnik; każde inne pole jest pokazywane jako zwykła zamiana.
Podgląd zwraca trzy liczniki (wybór, objęte po rozwinięciu produktów nadrzędnych do wariantów, możliwe do zapisu) oraz ostrzeżenia: procent z pustego pola pominięty, produkty nadrzędne z wariantami bez własnej ceny, stan nieśledzony.
Wywołanie REST ma ten sam limit czasu co partia AJAXPorcje po 25 przez do_bulk, 20 s na wywołanie, wznawiane przez bulk_key i next_offset
Pętla zastosowania tnie kolejkę na porcje po 25 i wywołuje WOOBE_BULK::do_bulk(), silnik używany przez ekran, aż upłynie 20 sekund. Zwraca next_offset i bulk_key; kontynuacja zapisuje do tego samego nagłówka Historii, więc jedno cofnięcie wycofuje całe zadanie. Cache są czyszczone raz po zapisach, a nie przy każdej porcji.
W wersji darmowej porcja kurczy się do pozostałego limitu zapisu: kubełek tokenów (token bucket) 100 produktów, uzupełniany w sposób ciągły w ciągu 30 minut, z ułamkowymi tokenami i twardym pułapem, więc czas bezczynności nie może opłacić dużego przebiegu.
Warianty nie niosą żadnych terminów ani meta, według których dopasowuje filtrDzieci dodawane po zapytaniu: wszystkie albo tylko te pasujące do każdego wskazanego atrybutu
include_variations 'all' dodaje wszystkie dzieci dopasowanych produktów nadrzędnych; 'matching' zachowuje dzieci, których wartości atrybutów pasują do wszystkich terminów pa_ filtra ('yellow and small' to jeden wariant, a nie suma), przy czym pusta wartość liczy się jako 'dowolna'. Kategorie i tagi nie mogą zawężać dzieci.
Klucze atrybutów są budowane przez sanitize_title(), tak jak buduje je WooCommerce, ponieważ nielacińskie nazwy atrybutów są przechowywane zakodowane w URL, a sanitize_key() by je wycięło. W trybie matching produkty nadrzędne z wariantami są usuwane z wyboru, aby późniejsze variations_only nie rozwinęło ich z powrotem do wszystkich wariantów.
W wywołaniu REST wtyczka i sklep zachowują się inaczej niż w panelu administracyjnymWłasny rozruch na rest_api_init, filtry cen walut zdjęte, terminy nadrzędne uzupełniane
WOOBE::init() uruchamia się na init, zanim zdefiniowane jest REST_REQUEST, więc w wywołaniu REST nie widzi ani panelu administracyjnego, ani REST i nic by nie zrobił. bootstrap.php sam podpina rest_api_init i uruchamia wtyczkę; is_should_init() dopuszcza REST tylko dla własnych tras wtyczki, a nie dla całego ruchu REST witryny.
Przełączniki walut filtrują gettery cen, więc agent odczytywał ceny przeliczone; strip_price_filters() usuwa te filtry tylko na czas żądania MCP. Produkt wstawiony do podkategorii jest też wstawiany do jej przodków przez nasłuchiwacz set_object_terms, ponieważ w czacie nikt nie zaznacza pól kategorii nadrzędnych.
Kontrola dostępu i bezpieczeństwo
shipped · 2026Co to dajeProdukty mogą zmieniać tylko właściwe osoby, a przy włączonym opcjonalnym drugim czynniku sam wyciek klucza dostępu AI nie otwiera sklepu, i dostęp można ograniczać dla poszczególnych obszarów.
- Problem
- Masowy edytor ma prawo zapisu do każdego produktu. Każdy handler AJAX, historia i punkt końcowy MCP muszą sprawdzać, kto działa, a klucz MCP przechowywany w ustawieniach asystenta może wyciec.
- Rozwiązanie
Jedna straż dla wszystkich handlerów AJAX ze sprawdzaniem własności każdego produktu; dla MCP klucze wyłącznie w nagłówkach, porównywane w stałym czasie, potwierdzony token sesji (opcjonalny dla klucza całego sklepu, zawsze włączony dla kluczy osobistych), klucze osobiste i mapa uprawnień dla poszczególnych sektorów.
- Technologie
Kilkadziesiąt handlerów AJAX miało każdy własne, częściowe sprawdzeniaJedna straż: nonce, uprawnienia, potem edit_post dla każdego produktu, dla wariantów przez produkt nadrzędny
Po raporcie WPScan zaudytowano cały zestaw handlerów AJAX, nie tylko zgłoszone, a każdy handler przeniesiono na WOOBE_HELPER::check_ajax_access(). Sprawdza ona nonce, potem uprawnienia, potem current_user_can('edit_post') dla każdego ID produktu.
Warianty są sprawdzane względem produktu nadrzędnego: WooCommerce mapuje edit_post na wariancie na zwykłe edit_products, które ignoruje własność, a warianty mają post_author 0. get_request_array() odrzuca skalar tam, gdzie oczekiwana jest tablica, zamiast go rzutować. Role inne niż administrator są dodatkowo ograniczane dla poszczególnych pól.
Statyczny klucz API wycieka przez adresy URL, logi i czas odpowiedziKlucz tylko w nagłówkach, hash_equals, każde żądanie bramkowane, brak trasy przy wyłączonym MCP
Przy wyłączonym MCP register_routes() wraca, zanim cokolwiek zarejestruje, więc punkt końcowy nie istnieje. permission() wymaga klucza przy każdym żądaniu, także przy uzgadnianiu MCP, więc niezalogowany wywołujący nie dowiaduje się niczego o punkcie końcowym. Klucz jest odczytywany z Authorization: Bearer lub X-WOOBE-KEY, z zapasowym REDIRECT_HTTP_AUTHORIZATION z Apache, i porównywany przez hash_equals.
Odmowy używają 403, nie 401: w MCP kod 401 uruchamia wykrywanie OAuth, a klient zgłasza serwer jako zepsuty. Linki do pobierania i wysyłania używają własnych jednocelowych tokenów, ponieważ kliknięcie w przeglądarce nie może wysłać nagłówka.
Sam wyciek klucza nie powinien dawać dostępu do sklepuDrugi czynnik: token sesji potwierdzony w WordPressie, przechowywany jako hash, wygasa po 1 h bezczynności
Asystent prosi o token, złożony z czterech grup po osiem liter i cyfr z random_int, który potwierdza osoba zalogowana do WordPressa: administrator dla klucza całego sklepu, właściciel klucza w swoich ustawieniach dla klucza osobistego. Dla klucza całego sklepu ten drugi czynnik jest ustawieniem; dla kluczy osobistych jest zawsze włączony. Zapisywany jest tylko jego hash sha256 wraz z czasami potwierdzenia i ostatniego użycia. check_connection() porównuje przez hash_equals i zrywa połączenie po 3600 sekundach bez wywołania; czas ostatniego użycia jest zapisywany najwyżej raz na minutę.
Połączenia są przypisane do właściciela: token potwierdzony dla jednego klucza osobistego jest odrzucany przy każdym innym kluczu.
Klucze dla poszczególnych użytkowników nie mogą ujawniać, którzy użytkownicy mają dostępZapisany klucz zawsze odczytywany i porównywany, z atrapą równej długości, gdy go nie ma
Klucz osobisty ma postać woobe_<40 hex>-<user id>. personal_key_owner() zawsze odczytuje i porównuje zapisany klucz przez hash_equals, używając atrapy tej samej długości, gdy użytkownik go nie ma, a listę uprawnień i capability sprawdza dopiero po pełnym dopasowaniu, więc czas odpowiedzi nie mówi obcemu, kto ma dostęp. ID z wiodącymi zerami trafiają na ścieżkę atrapy.
Żądanie działa wtedy jako ten użytkownik przez wp_set_current_user(), więc jego rola i widoczność pól obowiązują dokładnie tak jak na ekranie, a połączenie dwuczynnikowe jest dla kluczy osobistych zawsze włączone.
Właściciele potrzebują dostępu tylko do odczytu lub braku dostępu dla poszczególnych obszarów sklepuMapa sektorów full/read/none na użytkownika, zawężana przez uprawnienia WP; zamknięte narzędzia ukryte
Każde narzędzie deklaruje sektor i annotations.readOnlyHint. Właściciel zwraca mapę z filtra woobe_mcp_permissions; '*' ustawia wartość domyślną, więc działa jak lista dozwolonych. Nieprawidłowe wpisy są logowane i ignorowane, a nieprawidłowe '*' wraca do full, tak samo jak brak callbacku. Dla klucza osobistego każdy sektor jest zawężany jeszcze raz przez uprawnienie, o które WooCommerce prosi na danym ekranie (edit_products, edit_shop_orders, view_woocommerce_reports i inne).
Narzędzia, których tożsamość nie może wywołać, nie są w ogóle wymieniane. Zapis, który trafia do innego sektora, taki jak cofnięcie przepisujące produkty, przed zapisem pyta require_access() o ten sektor. Filtr woobe_mcp_tool_allowed może tylko zawężać dalej. Żaden klucz MCP, ani całego sklepu, ani osobisty, nie może zapisywać post_author ani ID; te pola edytuje w wp-admin człowiek.
Liczby
wordpress.org- Aktywne instalacje 30 000+ licznik wordpress.org.
- Opinie 227 Średnia ocena: 4.7 na 5.
- Lat wsparcia 8 Od 7 maja 2018, ostatnia aktualizacja 13 października 2026.