BEAR
Табличний масовий редактор товарів WooCommerce з MCP-сервером, який дає змогу AI-агентам безпечно редагувати магазин.
- Перший реліз
- Активні встановлення
- 30 000+
- Рейтинг
- Читати відгуки
Про мене
BEAR показує каталог WooCommerce як одну редаговану таблицю й змінює тисячі товарів за раз: ціни, залишки, SKU, таксономії, атрибути, варіації та власні meta-поля. Кожна зміна проходить одним шляхом запису поля, який для властивостей товару використовує об'єкти товарів WooCommerce, зберігається разом із попереднім значенням і може бути відкочена на вкладці «Історія».
Складна частина не в таблиці. Складно зробити масовий запис коректним на реальному магазині: значення зберігаються в п'яти різних місцях, у змінних товарів ціни й залишки лежать на варіаціях, WooCommerce має побічні ефекти (наприклад, статус наявності виводиться з кількості на складі), а на спільному хостингу PHP-запит обривається через 30 секунд.
З 2026 року плагін також запускає MCP-сервер. AI-агент може знаходити товари, переглядати зміну наперед, застосовувати її порціями та відкочувати — через ті самі рушії фільтрів, масового редагування та історії, що й екран, — за ключами, підтвердженим токеном сесії (для ключа всього магазину необов'язковим, для персональних ключів завжди увімкненим) і картою дозволів за секторами.
Безкоштовна та преміум-збірки — це одна кодова база, що перемикається одним прапорцем; безкоштовна збірка обмежує розмір сторінки, кількість meta-полів, глибину історії та частоту записів через MCP.
BEAR · масовий редактор
що розв'язано
Таблиця товарів і модель полів
shipped · 2018Що це даєСпівробітники бачать і змінюють будь-яку деталь товару чи варіації на одному екрані, схожому на таблицю, навіть у каталогах, надто великих, щоб відкрити їх цілком.
- Проблема
- Один екран має показувати й редагувати будь-яке значення каталогу будь-якого розміру: поля запису, властивості WooCommerce, таксономії, атрибути, завантаження та довільні meta-поля — для товарів і їхніх варіацій.
- Рішення
Декларативний реєстр полів керує кожною колонкою та кожним записом, а таблиця розбивається на сторінки на сервері: одна сторінка WP_Query на один AJAX-запит.
- Стек
Великі каталоги не можна завантажити в браузер за один разСерверна пагінація: кожна сторінка таблиці — це WP_Query зі зміщенням, сортуванням і фільтрами
woobe_get_products() приймає start, length і order від DataTables та передає їх у WOOBE_PRODUCTS::gets(), який будує WP_Query. Сортування за meta-колонкою переходить на meta_value_num для типів number, timestamp і unix, в інших випадках — на meta_value.
Розмір сторінки в моделі налаштувань обмежено 100 рядками з коментарем, що більші сторінки спричиняли HTTP 500 на слабких серверах; у безкоштовній збірці він зафіксований на 10. Коли перемикач варіацій увімкнено, варіації кожного змінного товару запитуються й вставляються рядками одразу після батьківського товару.
Кожен тип колонки зберігається в WooCommerce по-різномуОдин запис реєстру на колонку: тип сховища, тип значення, редактор, санітайзер, права
woobe_get_fields() описує кожну колонку: field_type (field, meta, prop, taxonomy, attribute, downloads, gallery, upsells та інші), тип значення, вигляд редагування, санітайзер, типи товарів, до яких вона не застосовується, і видимість для менеджерів магазину. Глобальні атрибути та всі таксономії товарів додаються під час виконання з wc_get_attribute_taxonomies() і get_object_taxonomies().
update_page_field() розподіляє за field_type: update_post_meta для meta, сетери WC_Product плюс save() для props, wp_set_post_terms для таксономій, перебудовані списки WC_Product_Attribute для атрибутів. Комірки таблиці, відкати з Історії та більшість масових записів завершуються цією функцією. Масове редагування таксономій і атрибутів пише терміни напряму та перед записом викликає той самий хук Історії.
Власні meta-ключі треба відрізняти від внутрішніх ключів WooCommerceПідклас WC_Product_Data_Store_CPT відкриває свій захищений список внутрішніх ключів
woobe_meta_get_keys() читає всі meta-ключі зразкового товару й віднімає внутрішні ключі WooCommerce. Цей список — захищена властивість сховища даних товару, тому WOOBE_PDS_CPT розширює сховище лише для того, щоб його повернути, додаючи _button_text і _product_url.
Meta-ключі обрізаються по пробілах, але не приводяться до нижнього регістру й не санітизуються, бо реальні ключі, які пишуть інші плагіни, наприклад _woocs_sale_price_USD, чутливі до регістру.
Типізоване meta-поле має працювати в колонках, фільтрах і масовому редагуванніОдне визначення meta через хуки потрапляє в реєстр, панель фільтрів і панель масового редагування
woobe_extend_fields() перетворює кожне збережене визначення meta на колонку; числовий тип отримує санітайзер floatval і числове сортування. Те саме визначення живить woobe_filter_numbers (діапазон від/до), woobe_filter_text (LIKE, =, !=, NOT LIKE, порожнє, непорожнє), фільтри-перемикачі та woobe_bulk_number (встановити, збільшити або зменшити на значення чи на відсоток).
Колонка-перемикач може зберігати значення, відмінні від 1/0 (наприклад, yes/no), через правило перевизначення для кожного ключа, яке читає over_switcher_swicher_to_val(). Безкоштовна збірка залишає перші 2 meta-поля.
У серіалізованого meta-масиву немає плоскої комірки для редагуванняРедактор у спливному вікні надсилає ключі й значення паралельними масивами, які перебудовуються у вкладені PHP-масиви
process_jsoned_meta_data() перебудовує meta-значення з пар keys/values для масивів і keys2/values2 для об'єктів, декодуючи вкладені JSON-значення, а для звичайних рядків і чисел повертається до сирого значення. Історія зберігає такі значення як JSON із JSON_HEX_QUOT і JSON_HEX_TAG, щоб вони пережили круговий шлях без втрат.
Стан редактора для кожного користувача має пережити скидання об'єктного кешуОдин рядок опції без autoload на користувача з TTL для кожного ключа замість transients
Фільтри та конфігурації масового редагування зберігаються між запитами через WOOBE_STORAGE. За Redis або Memcached transient живе лише в об'єктному кеші й губиться при його скиданні, тому сховище за замовчуванням — рядок опції woobe_memory_<user id>: кожен ключ має власний термін дії, прострочені ключі видаляються під час запису, autoload вимкнено, а рядок видаляється разом із користувачем. Режими session, transient і cookie лишаються за фільтром woobe_storage_type.
Фільтрація
shipped · 2018Що це даєСпівробітники вибирають саме ті товари, які потрібні, зокрема за ціною чи залишком варіації, а та сама вибірка використовується для редагування й експорту.
- Проблема
- Фільтри за цінами, залишками, габаритами, текстом і таксономіями мають вибирати саме потрібні товари, зокрема змінні, чиї значення лежать на варіаціях, а ту саму вибірку мають використовувати пагінація, масове редагування й експорт.
- Рішення
Фільтр зберігається на сервері під ключем і при кожному запиті компілюється в частини tax_query, meta_query і posts_where через один хук WP_Query.
- Стек
Запити пагінації, масового редагування й експорту мають бачити одну й ту саму відфільтровану вибіркуФільтр зберігається під випадковим ключем на кожен запуск; один хук WP_Query перебудовує його для кожного запиту
woobe_filter_products() зберігає розібрану форму фільтра під woobe_filter_<key>, де ключ — випадковий рядок, що генерується при кожній фільтрації, тому масове редагування за фільтром працює одразу в кількох вкладках браузера. Кожен виклик WOOBE_PRODUCTS::gets() пропускає свої аргументи через woobe_apply_query_filter_data(), яка читає збережений фільтр і повертає tax_query, meta_query, post__in та post_status, а для решти підключає хуки posts_where.
Пагінація таблиці, лічильник масового редагування, лічильник експорту та інструменти варіацій викликають gets(), тому не можуть розійтися в тому, що означає фільтр.
Ціна й залишок лежать на варіаціях, а таблиця показує батьківські товариЗапит діапазону по товарах і варіаціях, потім знайдені варіації зіставляються з батьками
regular_price_where(), sale_price_where(), stock_quantity_where() і dimensions_where() запитують postmeta і для product, і для product_variation, потім читають post_parent знайдених варіацій і обмежують основний запит батьками плюс варіаціями через ID IN (...).
Коли нічого не знайдено, умова стає ID = -1, тож порожній збіг повертає нуль товарів, а не весь каталог, що важливо за один клік до масового редагування.
Числа, збережені як рядки meta, неправильно порівнюються в MySQLCAST до DECIMAL із явною точністю; порожні рядки виключаються з кожного діапазону
Власні числові діапазони використовують meta_query типу DECIMAL(30,20): голий DECIMAL має scale 0, тому пошук від 100 до 150 знайшов би й 99.xx. SQL цін приводить до DECIMAL(30,10).
Порожня вага зберігається як '', а MySQL читає '' як 0, тому діапазони цін і габаритів вимагають meta_value <> '', і порожнє поле ніколи не вважається нулем. Відсутній кінець діапазону відкритий (підставляється -999999999 або 999999999), а «від» дорівнює «до» означає саме це значення.
Користувачам потрібні AND, OR і NOT в одному полі текстового пошукуНевеликий синтаксис пошуку (^ OR, ^~ AND, ^! NOT), що компілюється в умови MySQL REGEXP
posts_txt_where() розбиває текст за ^; префікс ~ з'єднує частину через AND, префікс ! перетворює її на NOT REGEXP в окремій групі AND. Режими exact, begin, end, not і empty відповідають = та REGEXP із прив'язкою; дужки regex екрануються. Застосовується до заголовка, вмісту, уривка та slug.
Фільтр SKU приймає список через кому й будує підготовлені умови LIKE з esc_like: OR для режимів збігу й AND для «not».
Збіг з усіма термінами одночасно потребує однієї частини tax_query на кожен термінAND розбивається на одну частину на термін; невідомі оператори замінюються на IN
Для оператора AND кожен термін стає окремою частиною tax_query під relation AND, бо одна частина з оператором AND може не повернути жодного товару. Оператори обмежено білим списком: IN, NOT IN, AND, EXISTS і NOT EXISTS; WordPress відкидає частину з невідомим оператором і збігся б з усіма товарами.
Видимість у каталозі перетворюється на комбінації термінів product_visibility exclude-from-catalog і exclude-from-search, а фільтр статусу наявності ще й обмежує product_type типами, які мають статус наявності.
Рушій масового редагування
shipped · 2018Що це даєЗміни цін, залишків і варіацій у тисячах товарів надійно завершуються на звичайному хостингу та дотримуються власних правил магазину щодо цін і залишків.
- Проблема
- Масова зміна може зачепити тисячі товарів на хостингу з короткими лімітами часу PHP, і кожен запис має дотримуватися правил WooCommerce щодо цін, залишків і варіацій.
- Рішення
ID цілей визначаються один раз, потім записуються малими AJAX-пакетами через той самий шлях запису одного поля, що й таблиця, який несе правила WooCommerce.
- Стек
Масове редагування тисяч товарів упирається в ліміти часу PHPID отримуються один раз, потім надсилаються пакетами по N на AJAX-запит; N — налаштування
woobe_bulk_products_count() зберігає конфігурацію масового редагування під випадковим 16-символьним bulk_key і повертає всі відфільтровані ID (fields=ids, без пагінації). __woobe_bulk_products() у bulk.js нарізає цей список і надсилає пакет за пакетом, оновлює індикатор прогресу й наприкінці викликає woobe_bulk_finish; користувач може перервати поточний запит.
Розмір пакета — number_posts_processed, від 10 до 100 на запит, за замовчуванням 30, тож слабкий сервер може проміняти швидкість на стабільність.
Відредаговані товари випадають з фільтра, поки триває запускЗаморожений список ID: кожен пакет несе ID зі знімка, а не повторно виконаного запиту
Пагінація відфільтрованого запиту між пакетами пропускала б товари: зміна ціни виводить товар з фільтра за ціною, і всі наступні зміщення зсуваються. Список ID, знятий на початку, зберігається на клієнті, а кожен запит пакета явно надсилає свої ID без ключа фільтра.
Після запуску позначені рядки, яких більше немає в перемальованій таблиці, прибираються з виділення, тож наступна дія не торкнеться товарів, яких користувач уже не бачить.
Числові поведінки мають поєднуватися з формулами й округленням цінКонвеєр на товар: поведінка, формула з іншим meta, випадковий діапазон, округлення
_process_number_data() застосовує встановлення, збільшення або зменшення на значення чи відсоток і міжпольові правила, наприклад ціна зі знижкою = звичайна мінус відсоток. Потім необов'язкова формула з іншим meta-полем (+, -, *, /, з захистом від ділення на нуль), потім необов'язкова випадкова величина в діапазоні із заданою кількістю десяткових знаків.
update_page_field() округлює в десяткових знаках цін магазину: до цілих одиниць, угору до 0.05, до 0.1 або до фіксованого закінчення копійок, як .99. Текстові поля отримують append, prepend, replace (з урахуванням регістру чи без) і плейсхолдери на кшталт {TITLE}, {SKU}, {meta:key} та {attribute:pa_size}.
Ціна зі знижкою має лишатися нижчою за звичайну, а дати мають набирати чинностіЗнижка >= звичайної пропускається, знижка <= 0 її прибирає; _price перераховується при зміні дат
У масовому запуску обчислена ціна зі знижкою, що досягає звичайної ціни, пропускається, а значення нуль або менше видаляє знижку. Одиночний запис ціни зі знижкою, рівної звичайній або вищої, обмежується звичайною мінус найменша цінова одиниця.
Коли записуються дати знижки, _price одразу встановлюється на ціну, що діє зараз (знижка ще не почалася, триває, закінчилась), а transients товару очищаються, замість того щоб чекати на запланове завдання знижок WooCommerce.
Записи залишків і цін не повинні пошкоджувати змінні товари чи спільний залишокЗмінні батьки й варіації із залишком батька не чіпаються; залишок зберігається при перемиканнях
WooCommerce читає ціни змінного товару з його варіацій, тому ціна, записана батьку, пропускається, а очищувальний запис видаляє будь-яку залишкову ціну батька. Залишок 0 або менше зазвичай вмикає облік залишків; це не робиться на змінному батьку, який не веде облік (інакше всі варіації без власного залишку стали б відсутніми на складі), і не на варіації, що продається із залишку батька (її було б від'єднано).
WooCommerce очищає кількість, дозамовлення та поріг низького залишку, коли облік залишків вимикається. keep_stock_on_switch() зберігає їх у власному meta і відновлює, коли облік вмикається знову.
Варіації треба створювати й редагувати за комбінацією атрибутівДекартів добуток вибраних термінів без дублікатів; редагування зіставляється за комбінацією з підстановкою
generate_combinations() будує декартів добуток вибраних термінів атрибутів. Застосування прикріплює атрибути як атрибути варіацій, пропускає комбінації, які товар уже має, копіює ціни батька, дає кожній новій варіації SKU parent-SKU-<id>, щоб уникнути дублікатів, і пересортовує варіації через сховище даних.
Редагування, видалення та експорт підмножини варіацій порівнює атрибути кожної варіації з вибраними комбінаціями: ключі та значення відсортовані, записи '-1' вважаються будь-яким значенням, а обидві сторони URL-декодуються, бо нелатинські slug атрибутів зберігаються закодованими.
Історія та відкат
у продуктіЩо це даєПомилкову зміну — вручну, масово чи від AI-асистента — можна скасувати, а не виправляти товар за товаром.
- Проблема
- Кожну зміну — вручну, масово чи від AI-агента — потрібно мати можливість скасувати, зокрема побічні ефекти, які WooCommerce застосовує сам, а відкат великого запуску впирається в ті самі ліміти часу, що й сам запуск.
- Рішення
Попередні значення записуються по полях у двох власних таблицях хуком на єдиному шляху запису й відкочуються порціями тим самим шляхом.
- Стек
Записувати попередні значення для будь-якого типу поля без коду під кожну функціюОдин хук перед кожним записом поля; попереднє значення зберігається по полях у власній таблиці
update_page_field() викликає woobe_before_update_page_field, і WOOBE_HISTORY::write() читає поточне значення та кодує його за типом: ID термінів, атрибути, галерея й зв'язки як JSON, завантаження як name/file/hash, статус наявності як 1/0, дати знижки як ISO-дати зі зсувом часового поясу, тож відкат відновлює точний розклад.
Рядки містять field_key, product_id, prev_val, mod_date, bulk_key, user_id і via_mcp та індексуються за товаром, bulk key і користувачем. Друга таблиця зберігає один заголовок на кожен масовий запуск: стан (completed або terminated), початок, завершення, кількість товарів і ключі полів.
WooCommerce змінює статус наявності як побічний ефект редагування залишкуПобічні ефекти записуються додатковими рядками однієї зміни й відкочуються разом, по порядку
Перемикання обліку залишків або залишок 0 на товарі без обліку змушує WooCommerce вивести статус наявності; вимкнення обліку також скидає кількість. update_page_field() записує stock_status і, за потреби, stock_quantity додатковими рядками одразу після відредагованого поля.
stock_status_pair() і stock_row_at() знаходять ці рядки за сусідніми ID (+1, +2) і вимагають того самого товару, автора, каналу (вручну чи через агента) та bulk key, а також близького часу запису. Потім revert() відновлює кількість раніше за статус, бо WooCommerce виводить статус з кількості, поки залишок ведеться. Порожній попередній залишок означає «не відстежується», тому untrack_stock() вимикає облік замість запису 0.
Відкат масового запуску на тисячі рядків теж упирається в тайм-аутВідкат порціями за bulk_key; кожен відкочений рядок видаляється; заголовок видаляється наприкінці
На вкладці «Історія» woobe_history_revert_bulk_portion() відкочує по 10 рядків за AJAX-запит з індикатором прогресу, а рядок заголовка видаляється, коли лічильник досягає загальної кількості. Кожен відкат видаляє свій рядок, тож наступна порція просто бере те, що залишилось.
Інструмент відкату MCP — окремий шлях: revert_bulk_portion() відкочує до 200 рядків за виклик і повідомляє фактично видалені рядки, бо два рядки зміни залишку відкочуються разом і порція може взяти на один рядок більше за свій ліміт.
revert() спершу знімає всі слухачі woobe_before_update_page_field, щоб власні записи відкату не фіксувалися як нова історія.
Кілька користувачів і AI-агентів ділять одну історіюОдне правило scope(): адміністратори бачать усі рядки, інші — свої; примусово на сервері
Кожен рядок несе user_id і via_mcp. Ключ MCP для всього магазину пише від користувача -777, який ніколи не збігається ні з реальним користувачем, ні з 0 (не авторизований). scope() повертає одну SQL-умову, що використовується в кожному списку, відкаті, видаленні й очищенні, тож жоден запит не може відійти від правила.
Обробники перевіряють solo_in_scope() або bulk_in_scope() і відповідають 403 на запит, зібраний вручну для рядка, якого користувач не бачить. Імена авторів для списку читаються одним запитом.
Наявним установкам потрібна була нова колонка без пошкодження старих рядківОпція з версією схеми, ALTER із перевіркою колонки, заповнення рядків агента заднім числом
CREATE TABLE IF NOT EXISTS ніколи не змінює таблицю, що вже існує. maybe_upgrade_schema() запускається раз на версію схеми (зараз 2), перед кожним ALTER перевіряє SHOW COLUMNS, щоб паралельний запит не завершився помилкою, повторює спробу при наступному завантаженні, якщо ALTER не вдався, і позначає старі рядки користувача -777 як записані через агента.
Безкоштовна збірка зберігає коротку історію, не чіпаючи інших користувачівОчищення по авторах під час shutdown: останні 2 одиночні й 2 масові зміни, рядки залишків зберігаються
У безкоштовній збірці prune_author() зберігає дві найновіші одиночні зміни та два найновіші масові запуски одного автора. Рядки, що належать до збереженої зміни залишку, зберігаються разом із нею через stock_change_rows(). Очищення виконується під час shutdown після запису, а відкриття вкладки ніколи не видаляє рядки іншого користувача.
Експорт
у продуктіЩо це даєВідфільтровані списки товарів експортуються у файли, які магазин може імпортувати назад, будуються частинами на слабких серверах і ніколи не лишаються відкритими в мережі.
- Проблема
- Експорт відфільтрованого каталогу з варіаціями має виконуватися частинами на слабких серверах, давати файли, які WooCommerce може імпортувати назад, і не залишати дані товарів відкритими в мережі.
- Рішення
Експорт виконується як послідовність AJAX-запитів, що дописують в один файл, ім'я якого виводиться окремо для кожного експорту, який лежить у закритій теці й віддається через авторизований обробник.
- Стек
Великий експорт не можна побудувати за один запитСпершу записується заголовок, потім рядки дописуються пакетами AJAX в той самий файл
woobe_export_products_count() очищає попередні файли, записує заголовок CSV або каркас XML і повертає відфільтровані ID. woobe_export_products() дописує один пакет за запит через fopen(a+) і fputcsv, бо WP_Filesystem не вміє потокового запису по рядках; для XML він завантажує документ WXR і дописує вузли item.
Варіації додаються після свого батька, за бажанням без відсутніх на складі, і можуть обмежуватися комбінаціями атрибутів або виключатися за ними.
CSV має завантажуватися у власний імпортер товарів WooCommerceАтрибути як чотири колонки кожен, завантаження як пари name/URL, будь-яке значення розгортається в рядок
get_csv_titles() пише 'Attribute N value(s)', 'name', 'visible' і 'global' для кожного атрибута та 'Download N name' і 'URL' для кожного слота файлу — колонки, які читає імпортер WooCommerce; meta-колонки називаються 'Meta: key'.
flatten_export_value() зводить масиви й об'єкти (терміни, атрибути, об'єкти даних WC) до скалярів, з'єднаних через '|', тож тип колонки, якого плагін ніколи не бачив, зокрема сторонній, не пише слово 'Array'.
Файли експорту містять ціни та URL файлів і не повинні бути публічнимиТека закрита через .htaccess, ім'я файлу з HMAC-хвостом, завантаження через перевірений обробник
Постфікс імені файлу має відповідати хвилинній мітці часу й отримує 12-символьний хвіст із hash_hmac('sha256', postfix, wp_salt('auth')): однаковий для всіх запитів одного експорту, тож усі пакети потрапляють в один файл, але його неможливо вгадати ззовні. Хвіст важливий на nginx, який ігнорує правило deny з .htaccess.
woobe_export_download() перевіряє nonce і capability, приймає лише імена, які генерує розширення, очищає всі буфери виводу й віддає потоком через readfile() без Content-Length. Файли старші за 8 годин видаляються, перевірка — не частіше ніж раз на годину.
AI-агенту потрібне посилання для завантаження без розкриття його ключаREST-маршрут за токеном одного файлу з 32 hex-символів, який спливає через 15 хвилин
Клік у браузері не може надіслати заголовок Authorization, а ключ MCP не потрапляє в URL. Інструмент експорту зберігає ім'я файлу під випадковим токеном у transient із часом життя 900 секунд, а /woobe/v1/mcp/export/<token> віддає лише цей файл.
MCP-сервер для AI-агентів
shipped · 2026Що це даєAI-асистент може редагувати магазин за власника, але лише на підтвердженій вибірці, спершу з переглядом наперед, і кожна зміна оборотна.
- Проблема
- AI-агент працює через мережевий API без екрана, може неправильно прочитати фільтр чи кількість і може видати запис на весь каталог; і все ж він має бути безпечним, оборотним і вкладатися в ліміти часу PHP.
- Рішення
Інструменти використовують власні рушії фільтрів, масового редагування та історії редактора; записи приймають лише заморожені вибірки, підтверджені їхньою кількістю, можуть бути попередньо перевірені інструментом пробного запуску й виконуються порціями, які можна продовжити.
- Стек
Неправильно прочитаний фільтр не повинен розширити запис на весь каталогЗаписи приймають лише заморожений selection_id і confirm_count, що дорівнює його розміру
woobe_find_products виконує фільтр один раз і зберігає ID в опції без autoload (до 200,000 ID, діє 24 години). Відповідь містить selection_id, count, зразок і whole_catalogue, виміряне порівнянням із нефільтрованим запитом, тож фільтр, що нічого не звужує, повідомляється саме так.
woobe_apply_bulk не приймає жодного фільтра. Він відмовляє, якщо confirm_count не дорівнює розміру вибірки, тож агент, який не прочитав число, не може його вгадати. Товари, створені після пошуку, до вибірки не входять.
Фільтр агента має бути відхилений, а не проігнорований, якщо рушій не може його застосуватиСуворий валідатор відхиляє будь-який ключ чи форму, яку рушій не застосував би, і пояснює чому
Рушій фільтра читає поля власної форми редактора й ігнорує будь-які інші ключі. Агент може надіслати ключ з помилкою або дату як {from, to}, а проігнорована умова розширила б вибірку. WOOBE_MCP_FILTER перевіряє кожен ключ за списком полів, які читає рушій, відхиляє запит із зазначенням ключа та допустимої форми, а решту передає у вигляді, в якому її надсилає екран.
Рушій підключає хуки posts_where, які лишаються до кінця запиту: на екрані це одне завантаження сторінки, але в пакеті JSON-RPC може бути кілька пошуків. release_filter_hooks() знімає їх після кожного запиту MCP, тож один пошук ніколи не успадковує умови іншого. Типи аргументів перевіряються за inputSchema кожного інструмента, тому рядок 'maybe' не є підтвердженням.
Перегляд наперед має показувати, що запис справді дастьПробний запуск дзеркалить арифметику масового редагування та послідовно ланцюжить операції запуску
simulate() буквально копіює _process_number_data(), а run_chain() передає кожен результат наступній операції, тож ціна зі знижкою, задана разом з новою звичайною ціною, перевіряється щодо нової. Арифметика застосовується лише до числових полів, на яких рахує рушій; будь-яке інше поле показується як проста заміна.
Перегляд повертає три лічильники (вибірка, зачеплені після розгортання батьків у варіації, доступні для запису) і попередження: відсоток від порожнього поля пропущено, змінні батьки без власної ціни, залишок не відстежується.
REST-виклик має той самий ліміт часу, що й AJAX-пакетЧастини по 25 через do_bulk, 20 с на виклик, продовження за bulk_key і next_offset
Цикл застосування нарізає чергу на частини по 25 і викликає WOOBE_BULK::do_bulk(), рушій, який використовує екран, доки не мине 20 секунд. Він повертає next_offset і bulk_key; продовження пише в той самий заголовок Історії, тож один відкат скасовує все завдання. Кеші очищаються один раз після записів, а не на кожну частину.
У безкоштовній збірці частина зменшується до залишку дозволу на запис: token bucket зі 100 товарів, що безперервно поповнюється протягом 30 хвилин, з дробовими токенами та жорсткою стелею, тож простій не може оплатити великий запуск.
Варіації не несуть жодного з термінів чи meta, за якими збігається фільтрДочірні додаються після запиту: всі або лише ті, що збігаються з кожним названим атрибутом
include_variations 'all' додає всіх дочірніх знайдених батьків; 'matching' залишає дочірніх, чиї значення атрибутів збігаються з усіма термінами pa_ фільтра («жовтий і малий» — це одна варіація, а не об'єднання), причому порожнє значення означає «будь-яке». Категорії та мітки не можуть звузити дочірні.
Ключі атрибутів будуються через sanitize_title(), як це робить WooCommerce, бо нелатинські назви атрибутів зберігаються URL-закодованими, а sanitize_key() їх би зрізав. У режимі matching змінні батьки прибираються з вибірки, щоб подальший variations_only не розгорнув їх назад у всі варіації.
Усередині REST-виклику плагін і магазин поводяться інакше, ніж в адмінціВласний запуск на rest_api_init, фільтри цін валют знімаються, батьківські терміни доповнюються
WOOBE::init() виконується на init, до визначення REST_REQUEST, тож під час REST-виклику не бачить ні адмінки, ні REST і нічого б не робив. bootstrap.php сам підключається до rest_api_init і піднімає плагін; is_should_init() дозволяє REST лише для власних маршрутів плагіна, а не для всього REST-трафіку сайту.
Перемикачі валют фільтрують гетери цін, тож агент читав конвертовані ціни; strip_price_filters() знімає ці фільтри лише на час запиту MCP. Товар, поміщений у підкатегорію, також поміщається в її предків через слухач set_object_terms, бо в чаті ніхто не ставить позначки на батьківських категоріях.
Контроль доступу та безпека
shipped · 2026Що це даєЗмінювати товари можуть лише потрібні люди, з увімкненим необов'язковим другим фактором; один лише витеклий ключ доступу AI не відкриває магазин, а доступ можна обмежити по ділянках.
- Проблема
- Масовий редактор має право запису до кожного товару. Кожен AJAX-обробник, історія та кінцева точка MCP мають перевіряти, хто діє, а ключ MCP, що зберігається в налаштуваннях асистента, може витекти.
- Рішення
Один захист для всіх AJAX-обробників із перевіркою володіння по кожному товару; для MCP — ключі лише в заголовках, що порівнюються за сталий час, підтверджений токен сесії (для ключа всього магазину необов'язковий, для персональних ключів завжди увімкнений), персональні ключі та карта дозволів за секторами.
- Стек
Десятки AJAX-обробників мали кожен свої неповні перевіркиЄдиний захист: nonce, capability, потім edit_post на кожен товар, для варіацій — через батька
Після звіту WPScan було проаудитовано весь набір AJAX-обробників, а не лише згадані, і кожен обробник перенесено на WOOBE_HELPER::check_ajax_access(). Він перевіряє nonce, потім capability, потім current_user_can('edit_post') для кожного ID товару.
Варіації перевіряються щодо батьківського товару: WooCommerce зіставляє edit_post на варіації зі звичайним edit_products, який ігнорує володіння, а варіації мають post_author 0. get_request_array() відхиляє скаляр там, де очікується масив, замість приведення типу. Ролі не-адмінів додатково обмежуються по полях.
Статичний API-ключ витікає через URL, логи та часові характеристикиКлюч лише в заголовках, hash_equals, кожен запит перевіряється, без маршруту, поки MCP вимкнено
Коли MCP вимкнено, register_routes() повертається до реєстрації будь-чого, тож кінцевої точки не існує. permission() вимагає ключ у кожному запиті, включно з рукостисканням MCP, тому неавторизований викликач нічого не дізнається про кінцеву точку. Ключ читається з Authorization: Bearer або X-WOOBE-KEY, із запасним варіантом Apache REDIRECT_HTTP_AUTHORIZATION, і порівнюється через hash_equals.
Відмови використовують 403, а не 401: у MCP код 401 запускає виявлення OAuth, і клієнт повідомляє, що сервер зламаний. Посилання для завантаження й вивантаження використовують власні одноцільові токени, бо клік у браузері не може надіслати заголовок.
Сам по собі витеклий ключ не повинен давати доступ до магазинуДругий фактор: токен сесії, підтверджений у WordPress, зберігається хешованим, спливає після 1 год простою
Асистент просить токен, що складається з чотирьох груп по вісім літер і цифр з random_int, і його підтверджує людина, яка увійшла у WordPress: адміністратор для ключа всього магазину, власник ключа у своїх налаштуваннях для персонального ключа. Для ключа всього магазину цей другий фактор — налаштування; для персональних ключів він завжди увімкнений. Зберігається лише його хеш sha256 з часом підтвердження й останнього використання. check_connection() порівнює через hash_equals і розриває з'єднання після 3600 секунд без викликів; час останнього використання записується не частіше ніж раз на хвилину.
З'єднання прив'язані до власника: токен, підтверджений для одного персонального ключа, відхиляється з будь-яким іншим ключем.
Ключі окремих користувачів не повинні розкривати, хто має доступЗбережений ключ завжди читається й порівнюється, з фіктивним ключем тієї самої довжини, якщо справжнього немає
Персональний ключ має вигляд woobe_<40 hex>-<user id>. personal_key_owner() завжди читає й порівнює збережений ключ через hash_equals, використовуючи фіктивний ключ тієї самої довжини, якщо в користувача його немає, і питає список дозволів та capability лише після повного збігу, тож час відповіді не підказує стороннім, хто має доступ. ID з ведучими нулями йдуть фіктивним шляхом.
Потім запит виконується від імені цього користувача через wp_set_current_user(), тож його роль і видимість полів діють точно так, як на екрані, а двофакторне з'єднання для персональних ключів завжди увімкнене.
Власникам потрібен доступ лише для читання або його відсутність по ділянках магазинуКарта секторів full/read/none для кожного користувача, звужена правами WP; закриті інструменти приховані
Кожен інструмент оголошує сектор і annotations.readOnlyHint. Власник повертає карту з фільтра woobe_mcp_permissions; '*' задає значення за замовчуванням, тож це працює як білий список. Некоректні записи логуються та ігноруються, а некоректний '*' повертається до full, так само як і відсутність callback. Для персонального ключа кожен сектор додатково звужується capability, яку WooCommerce запитує на відповідному екрані (edit_products, edit_shop_orders, view_woocommerce_reports та інші).
Інструменти, які ідентичність не може викликати, взагалі не показуються. Запис, що потрапляє в інший сектор, наприклад відкат, який переписує товари, перед записом запитує require_access() для цього сектора. Фільтр woobe_mcp_tool_allowed може лише звужувати далі. Жоден ключ MCP, ні для всього магазину, ні персональний, не може записувати post_author чи ID; їх редагує людина в wp-admin.
Цифри
wordpress.org- Активні встановлення 30 000+ лічильник wordpress.org.
- Відгуки 227 Середня оцінка: 4.7 з 5.
- Років підтримки 8 З 7 травня 2018 р., останнє оновлення 13 жовтня 2026 р..