Idioma

Contactar

Plugin de WooCommerce · desde 2018 · aka WOOBE

BEAR

Un editor masivo para productos de WooCommerce con formato de hoja de cálculo y un servidor MCP que permite a los agentes de IA editar la tienda de forma segura.

Primera versión
Instalaciones activas
30.000+
Valoración
4.7 / 5 (227)Leer reseñas
  • PHP
  • WordPress
  • WooCommerce CRUD (WC_Product)
  • WP_Query
  • $wpdb / MySQL
  • WP REST API
  • JSON-RPC 2.0
  • Model Context Protocol
  • jQuery
  • DataTables
Ver en wordpress.org wordpress.org

Sobre mí

BEAR muestra el catálogo de WooCommerce como una única tabla editable y cambia miles de productos a la vez: precios, stock, SKU, taxonomías, atributos, variaciones y meta personalizado. Todo cambio pasa por una única vía de escritura de campos que usa los objetos de producto de WooCommerce para las propiedades del producto, se registra con su valor anterior y puede revertirse desde la pestaña History.

Lo difícil no es la tabla. Lo difícil es que una escritura masiva sea correcta en una tienda real: valores guardados en cinco lugares distintos, productos variables cuyos precios y stock residen en sus variaciones, efectos secundarios de WooCommerce como el estado del stock derivado de la cantidad en stock, y alojamientos compartidos que cortan una petición PHP a los 30 segundos.

Desde 2026 el plugin ejecuta además un servidor MCP. Un agente de IA puede buscar productos, previsualizar un cambio, aplicarlo por porciones y revertirlo, a través de los mismos motores de filtro, edición masiva e historial que usa la pantalla, protegido por claves, un token de sesión confirmado (opcional para la clave de toda la tienda, siempre activo para las claves personales) y un mapa de permisos por sector.

Las versiones gratuita y premium son una sola base de código controlada por un único indicador; la versión gratuita limita el tamaño de página, los campos meta, la profundidad del historial y la tasa de escritura MCP.

BEAR · editor masivo

qué se resolvió

ACTIVOdesde 2018

Cuadrícula de productos y modelo de campos

shipped · 2018

Qué aportaEl personal puede ver y cambiar cualquier detalle de un producto o variación en una sola pantalla tipo hoja de cálculo, incluso en catálogos demasiado grandes para abrirlos de una vez.

Problema
Una sola pantalla debe mostrar y editar cualquier valor de un catálogo de cualquier tamaño: campos de entrada, propiedades de WooCommerce, taxonomías, atributos, descargas y meta arbitrario, tanto de productos como de sus variaciones.
Solución

Un registro declarativo de campos gobierna cada columna y cada escritura, y la tabla se pagina en el servidor, una página de WP_Query por petición AJAX.

Stack
  • WP_Query
  • DataTables server-side mode
  • WC_Product setters
  • wp_options
Tareas · 6
Los catálogos grandes no se pueden cargar de una vez en el navegadorPaginación en el servidor: cada página de la tabla es un WP_Query con desplazamiento, orden y filtros

woobe_get_products() toma start, length y order de DataTables y los pasa a WOOBE_PRODUCTS::gets(), que construye el WP_Query. La ordenación por una columna meta cambia a meta_value_num para los tipos number, timestamp y unix, y a meta_value en los demás casos.

El tamaño de página está limitado a 100 filas en el modelo de ajustes, con el comentario de que páginas mayores provocaban HTTP 500 en servidores débiles; la versión gratuita está fijada en 10. Con la opción de variaciones activada, las variaciones de cada producto variable se consultan y se insertan como filas justo después de su producto padre.

Cada tipo de columna se almacena de forma distinta dentro de WooCommerceUna entrada de registro por columna: tipo de almacenamiento, tipo de valor, editor, sanitizador, permisos

woobe_get_fields() describe cada columna: field_type (field, meta, prop, taxonomy, attribute, downloads, gallery, upsells y otros), tipo de valor, vista de edición, sanitizador, tipos de producto a los que no se aplica y visibilidad para los gestores de la tienda. Los atributos globales y todas las taxonomías de producto se añaden en tiempo de ejecución desde wc_get_attribute_taxonomies() y get_object_taxonomies().

update_page_field() despacha según field_type: update_post_meta para meta, setters de WC_Product más save() para props, wp_set_post_terms para taxonomías, listas de WC_Product_Attribute reconstruidas para atributos. Las celdas de la cuadrícula, las reversiones del historial y la mayoría de las escrituras masivas terminan en esta función. Las ediciones masivas de taxonomías y atributos escriben los términos directamente y disparan antes el mismo hook del historial.

Las claves meta personalizadas deben distinguirse de las internas de WooCommerceUna subclase de WC_Product_Data_Store_CPT expone su lista protegida de claves internas

woobe_meta_get_keys() lee todas las claves meta de un producto de muestra y resta las claves internas de WooCommerce. Esa lista es una propiedad protegida del almacén de datos de producto, así que WOOBE_PDS_CPT extiende el almacén solo para devolverla, añadiendo _button_text y _product_url.

Las claves meta se recortan de espacios, pero no se pasan a minúsculas ni se sanean, porque las claves reales escritas por otros plugins, como _woocs_sale_price_USD, distinguen entre mayúsculas y minúsculas.

Un campo meta tipado debe funcionar en columnas, filtros y edición masivaUna definición meta se asigna mediante hooks al registro, al panel de filtros y al panel de edición masiva

woobe_extend_fields() convierte cada definición meta guardada en una columna; un tipo numérico recibe el sanitizador floatval y ordenación numérica. La misma definición alimenta woobe_filter_numbers (rango desde/hasta), woobe_filter_text (LIKE, =, !=, NOT LIKE, vacío, no vacío), filtros de interruptor y woobe_bulk_number (establecer, aumentar o disminuir por valor o por porcentaje).

Una columna de interruptor puede almacenar valores distintos de 1/0 (por ejemplo yes/no) mediante una regla de sustitución por clave que lee over_switcher_swicher_to_val(). La versión gratuita conserva los 2 primeros campos meta.

El meta de arrays serializados no tiene una celda plana que editarEl editor emergente envía claves y valores como arrays paralelos, reconstruidos como arrays PHP anidados

process_jsoned_meta_data() reconstruye un valor meta a partir de pares keys/values para arrays y keys2/values2 para objetos, decodificando valores JSON anidados, y recurre al valor sin procesar para cadenas y números simples. El historial guarda esos valores como JSON con JSON_HEX_QUOT y JSON_HEX_TAG para que sobrevivan al viaje de ida y vuelta.

El estado del editor de cada usuario debe sobrevivir al vaciado de la caché de objetosUna fila de opción sin autocarga por usuario, con TTL por clave, en lugar de transients

Los filtros y las configuraciones de edición masiva se conservan entre peticiones mediante WOOBE_STORAGE. Con Redis o Memcached un transient vive solo en la caché de objetos y se pierde al vaciarla, así que el almacenamiento por defecto es una fila de opción woobe_memory_<user id>: cada clave lleva su propia caducidad, las claves caducadas se eliminan al escribir, autoload está desactivado y la fila se borra junto con el usuario. Los modos session, transient y cookie siguen disponibles tras el filtro woobe_storage_type.

Filtrado

shipped · 2018

Qué aportaEl personal puede elegir exactamente los productos que necesita, también por precio o stock de variación, y la misma selección se usa para editar y exportar.

Problema
Los filtros por precios, stock, dimensiones, texto y taxonomías deben seleccionar exactamente los productos correctos, incluidos los productos variables cuyos valores residen en las variaciones, y el mismo conjunto debe reutilizarse en la paginación, la edición masiva y la exportación.
Solución

El filtro se guarda en el servidor bajo una clave y se compila en cada consulta en cláusulas tax_query, meta_query y posts_where mediante un único hook de WP_Query.

Stack
  • WP_Query
  • tax_query
  • meta_query
  • posts_where
  • $wpdb->prepare
  • MySQL REGEXP
Tareas · 5
Las peticiones de paginación, edición masiva y exportación deben ver el mismo conjunto filtradoFiltro guardado bajo una clave aleatoria por ejecución; un hook de WP_Query lo reconstruye en cada petición

woobe_filter_products() guarda el formulario de filtro analizado bajo woobe_filter_<key>, donde la clave es una cadena aleatoria generada en cada filtrado, de modo que la edición masiva por filtro funciona en varias pestañas del navegador a la vez. Cada llamada a WOOBE_PRODUCTS::gets() pasa sus argumentos por woobe_apply_query_filter_data(), que lee el filtro almacenado y devuelve tax_query, meta_query, post__in y post_status, y engancha hooks posts_where para el resto.

La paginación de la cuadrícula, el recuento de la edición masiva, el recuento de la exportación y las herramientas de variaciones llaman todos a gets(), por lo que no pueden discrepar sobre qué productos significa un filtro.

El precio y el stock residen en las variaciones, pero la cuadrícula lista los productos padreConsulta por rango sobre productos y variaciones; las variaciones encontradas se asignan a sus padres

regular_price_where(), sale_price_where(), stock_quantity_where() y dimensions_where() consultan postmeta tanto para product como para product_variation, luego leen post_parent de las variaciones coincidentes y restringen la consulta principal a los padres más las variaciones con ID IN (...).

Cuando nada coincide, la cláusula pasa a ser ID = -1, de modo que una coincidencia vacía no devuelve ningún producto en lugar de todo el catálogo, lo cual importa un clic antes de una edición masiva.

Los números guardados como cadenas meta se comparan mal en MySQLCAST a DECIMAL con escala explícita; cadenas vacías excluidas de todo rango

Los rangos numéricos personalizados usan meta_query de tipo DECIMAL(30,20): un DECIMAL sin más tiene escala 0, así que una búsqueda de 100 a 150 encontraría también 99.xx. El SQL de precios convierte a DECIMAL(30,10).

Un peso vacío se guarda como '' y MySQL lee '' como 0, por lo que los rangos de precio y dimensiones exigen meta_value <> '' y una casilla vacía nunca cuenta como 0. Un extremo ausente del rango queda abierto (sustituido por -999999999 o 999999999), y si desde es igual a hasta, significa exactamente ese valor.

Los usuarios necesitan AND, OR y NOT dentro de un mismo cuadro de búsqueda de textoPequeña sintaxis de búsqueda (^ OR, ^~ AND, ^! NOT) compilada a cláusulas MySQL REGEXP

posts_txt_where() divide el texto por ^; un prefijo ~ une la parte con AND, un prefijo ! la convierte en NOT REGEXP dentro de un grupo AND separado. Los comportamientos exact, begin, end, not y empty se asignan a = y a REGEXP anclado; los corchetes y llaves de las expresiones regulares se escapan. Se aplica a título, contenido, extracto y slug.

El filtro de SKU acepta una lista separada por comas y construye condiciones LIKE preparadas con esc_like, OR para los modos de coincidencia y AND para 'not'.

Coincidir con todos los términos de varios exige una cláusula tax_query por términoAND dividido en una cláusula por término; los operadores desconocidos recurren a IN

Para el operador AND, cada término se convierte en su propia cláusula tax_query bajo la relación AND, porque una única cláusula con el operador AND puede no devolver ningún producto. Los operadores están en una lista blanca de IN, NOT IN, AND, EXISTS y NOT EXISTS; WordPress descarta una cláusula con un operador desconocido y coincidiría con todos los productos.

La visibilidad en el catálogo se traduce a combinaciones de los términos product_visibility exclude-from-catalog y exclude-from-search, y un filtro de estado de stock limita además product_type a los tipos que tienen estado de stock.

Motor de edición masiva

shipped · 2018

Qué aportaLos cambios de precios, stock y variaciones en miles de productos terminan de forma fiable en alojamientos corrientes y siguen las reglas de precios y stock de la propia tienda.

Problema
Un cambio masivo puede afectar a miles de productos en alojamientos con límites de tiempo de PHP cortos, y cada escritura debe respetar las reglas de WooCommerce para precios, stock y variaciones.
Solución

Los ID de destino se resuelven una vez y luego se escriben en pequeños lotes AJAX por la misma vía de escritura de un solo campo que usa la cuadrícula, la cual incorpora las reglas de WooCommerce.

Stack
  • AJAX batching
  • WC_Product
  • WC_Product_Variation
  • WC_Product_Attribute
Tareas · 6
Una edición masiva de miles de productos choca con los límites de tiempo de PHPID obtenidos una vez, luego enviados en lotes de N por petición AJAX; N es un ajuste

woobe_bulk_products_count() guarda la configuración de la edición masiva bajo un bulk_key aleatorio de 16 caracteres y devuelve todos los ID filtrados (fields=ids, sin paginación). __woobe_bulk_products() en bulk.js divide esa lista y envía un lote tras otro, actualiza la barra de progreso y llama a woobe_bulk_finish al final; el usuario puede abortar la petición en curso.

El tamaño del lote es number_posts_processed, de 10 a 100 por petición, 30 por defecto, de modo que un servidor débil puede cambiar velocidad por estabilidad.

Los productos editados salen del filtro mientras la ejecución está en marchaLista de ID congelada: cada lote lleva ID de la instantánea, no de una consulta repetida

Paginar la consulta filtrada entre lotes saltaría productos: una edición de precio saca un producto de un filtro de precio y todos los desplazamientos posteriores se mueven. La lista de ID tomada al inicio la conserva el cliente y cada petición de lote envía sus ID de forma explícita, sin la clave del filtro.

Tras la ejecución, las filas marcadas que ya no aparecen en la tabla redibujada se quitan de la selección, para que la siguiente acción no apunte a productos que el usuario ya no ve.

Los comportamientos numéricos deben combinarse con fórmulas y con el redondeo de preciosCanalización por producto: comportamiento, fórmula con otro meta, rango aleatorio, redondeo

_process_number_data() aplica establecer, aumentar o disminuir por valor o porcentaje, y reglas entre campos como precio de oferta = precio normal menos un porcentaje. Después, una fórmula opcional con otro campo meta (+, -, *, / con protección frente a la división por cero) y, por último, una cantidad aleatoria opcional dentro de un rango con decimales definidos.

update_page_field() redondea según los decimales de precio de la tienda: a unidades enteras, hacia arriba a 0.05, hacia arriba a 0.1, o a una terminación fija de céntimos como .99. Los campos de texto admiten añadir al final, añadir al principio, reemplazar (con o sin distinción de mayúsculas) y marcadores como {TITLE}, {SKU}, {meta:key} y {attribute:pa_size}.

El precio de oferta debe quedar por debajo del precio normal y las fechas deben surtir efectoOferta >= normal se omite, oferta <= 0 la elimina; _price se recalcula al cambiar las fechas

En una ejecución masiva, un precio de oferta calculado que alcanza el precio normal se omite, y un valor de cero o menos elimina la oferta. Una escritura individual de un precio de oferta igual o superior al normal se limita al precio normal menos la unidad de precio más pequeña.

Cuando se escriben las fechas de oferta, _price se fija de inmediato al precio vigente en ese momento (oferta sin empezar, en curso, terminada) y se borran los transients del producto, en lugar de esperar a la tarea programada de ofertas de WooCommerce.

Las escrituras de stock y precio no deben dañar los productos variables ni el stock compartidoPadres variables y variaciones con stock del padre se dejan intactos; el stock se conserva al alternar la gestión

WooCommerce lee los precios de un producto variable desde sus variaciones, de modo que un precio escrito en el padre se omite y una escritura que lo borra elimina cualquier precio residual del padre. Un stock de 0 o menos normalmente activa la gestión de stock; esto no se hace en un padre variable que no controla stock, donde dejaría agotada toda variación sin stock propio, ni en una variación que vende del stock de su padre, a la que desvincularía.

WooCommerce borra la cantidad, los pedidos pendientes y la cantidad de stock bajo cuando se desactiva la gestión de stock. keep_stock_on_switch() los guarda en su propio meta y los restaura cuando la gestión se vuelve a activar.

Las variaciones deben crearse y editarse por combinación de atributosProducto cartesiano de los términos elegidos, sin duplicados; ediciones emparejadas por combinación con comodín

generate_combinations() construye el producto cartesiano de los términos de atributo seleccionados. Al aplicarlo, adjunta los atributos como atributos de variación, omite las combinaciones que un producto ya tiene, copia los precios del padre, da a cada nueva variación el SKU parent-SKU-<id> para evitar duplicados y reordena las variaciones a través del almacén de datos.

Editar, eliminar y exportar un subconjunto de variaciones compara los atributos de cada variación con las combinaciones elegidas: claves y valores ordenados, entradas '-1' tratadas como cualquier valor, y ambos lados decodificados de URL porque los slugs de atributos no latinos se guardan codificados.

Historial y reversión

en el producto

Qué aportaUn cambio erróneo, manual, masivo o hecho por un asistente de IA, puede deshacerse en lugar de corregirse producto a producto.

Problema
Todo cambio, manual, masivo o hecho por un agente de IA, tiene que poder deshacerse, incluidos los efectos secundarios que WooCommerce aplica por su cuenta, y la reversión de una ejecución grande choca con los mismos límites de tiempo que la ejecución.
Solución

Los valores anteriores se registran por campo en dos tablas propias mediante un hook en la vía de escritura única, y se revierten por porciones por la misma vía.

Stack
  • Tablas MySQL propias
  • $wpdb
  • Acciones de WordPress
Tareas · 6
Registrar los valores previos de cualquier tipo de campo sin código específico por funciónUn hook antes de cada escritura de campo; valor anterior guardado por campo en una tabla propia

update_page_field() dispara woobe_before_update_page_field y WOOBE_HISTORY::write() lee el valor actual y lo codifica según el tipo: ID de términos, atributos, galería y relaciones como JSON, descargas como name/file/hash, estado de stock como 1/0, fechas de oferta como fechas ISO con su desfase de zona horaria, de modo que una reversión restaura el calendario exacto.

Las filas contienen field_key, product_id, prev_val, mod_date, bulk_key, user_id y via_mcp, indexadas por producto, bulk key y usuario. Una segunda tabla guarda una cabecera por ejecución masiva: estado (completed o terminated), inicio, fin, número de productos y las claves de los campos.

WooCommerce cambia el estado del stock como efecto secundario de las ediciones de stockEfectos secundarios escritos como filas adicionales de un mismo cambio y revertidos juntos, en orden

Activar la gestión de stock, o un stock de 0 en un producto sin control, hace que WooCommerce derive el estado de stock; desactivar la gestión también elimina la cantidad. update_page_field() registra stock_status, y stock_quantity cuando hace falta, como filas adicionales justo después del campo editado.

stock_status_pair() y stock_row_at() encuentran esas filas por ID adyacentes (+1, +2) y exigen el mismo producto, autor, canal (a mano o mediante un agente) y bulk key, además de un tiempo de escritura cercano. Luego revert() restaura la cantidad antes que el estado, porque WooCommerce deriva el estado de la cantidad mientras el stock está gestionado. Un stock previo vacío significa 'sin control', así que untrack_stock() desactiva la gestión en lugar de escribir 0.

Revertir una ejecución masiva de miles de filas también agota el tiempoReversión por porciones según bulk_key; cada fila revertida se elimina; la cabecera se quita al final

En la pestaña History, woobe_history_revert_bulk_portion() revierte 10 filas por petición AJAX con una barra de progreso, y la fila de cabecera se elimina cuando el contador alcanza el total. Cada reversión elimina su fila, así que la siguiente porción toma simplemente lo que queda.

La herramienta de reversión de MCP es una vía separada: revert_bulk_portion() revierte hasta 200 filas por llamada e informa de las filas realmente eliminadas, porque las dos filas de un cambio de stock se revierten juntas y una porción puede tomar una fila más que su límite.

revert() elimina primero todos los listeners de woobe_before_update_page_field, de modo que las propias escrituras de la reversión no se registran como historial nuevo.

Varios usuarios y agentes de IA comparten un mismo historialUna regla scope(): los administradores ven todas las filas, los demás las suyas; aplicada en el servidor

Cada fila lleva user_id y via_mcp. La clave MCP de toda la tienda escribe como usuario -777, que nunca puede coincidir con un usuario real ni con 0 (sin sesión iniciada). scope() devuelve una condición SQL que usan todos los listados, reversiones, eliminaciones y vaciados, de modo que ninguna consulta pueda desviarse de la regla.

Los manejadores comprueban solo_in_scope() o bulk_in_scope() y responden 403 a una petición construida a mano para una fila que el usuario no puede ver. Los nombres de autor del listado se leen en una sola consulta.

Las instalaciones existentes necesitaban una columna nueva sin romper las filas antiguasOpción de esquema versionada, ALTER protegido por comprobación de columna, relleno retroactivo de las filas de agente

CREATE TABLE IF NOT EXISTS nunca altera una tabla que ya existe. maybe_upgrade_schema() se ejecuta una vez por versión de esquema (ahora la 2), comprueba SHOW COLUMNS antes de cada ALTER para que una petición concurrente no falle en él, reintenta en la siguiente carga si ALTER falla, y marca las filas antiguas del usuario -777 como escritas a través de un agente.

La versión gratuita conserva un historial corto sin tocar el de otros usuariosPoda por autor en shutdown: los últimos 2 cambios individuales y 2 masivos, filas de stock conservadas

En la versión gratuita, prune_author() conserva los dos cambios individuales más recientes y las dos ejecuciones masivas más recientes de un autor. Las filas que pertenecen a un cambio de stock conservado se conservan con él mediante stock_change_rows(). La poda se ejecuta en shutdown tras una escritura, y abrir la pestaña nunca elimina filas de otro usuario.

Exportación

en el producto

Qué aportaLas listas de productos filtradas se exportan a archivos que la tienda puede volver a importar, construidos por partes en servidores débiles y nunca abiertos en la web.

Problema
Una exportación de un catálogo filtrado con variaciones debe ejecutarse por partes en servidores débiles, producir archivos que WooCommerce pueda volver a importar y no dejar datos de producto abiertos en la web.
Solución

La exportación se ejecuta como una secuencia de peticiones AJAX que añaden contenido a un único archivo cuyo nombre se deriva por exportación, guardado en una carpeta cerrada y servido mediante un manejador autorizado.

Stack
  • fputcsv
  • DOMDocument (WXR)
  • hash_hmac
  • .htaccess
  • WP REST API
Tareas · 4
Una exportación grande no se puede construir en una sola peticiónPrimero se escribe la cabecera, luego se añaden filas por lote AJAX al mismo archivo

woobe_export_products_count() borra los archivos anteriores, escribe la cabecera CSV o el esqueleto XML y devuelve los ID filtrados. woobe_export_products() añade un lote por petición con fopen(a+) y fputcsv, ya que WP_Filesystem no puede escribir línea a línea en streaming; para XML carga el documento WXR y añade nodos item.

Las variaciones se añaden después de su padre, opcionalmente sin las agotadas, y pueden limitarse a combinaciones de atributos o excluirse por ellas.

El CSV debe cargarse en el importador de productos propio de WooCommerceAtributos en cuatro columnas cada uno, descargas como pares nombre/URL, cualquier valor aplanado

get_csv_titles() escribe 'Attribute N value(s)', 'name', 'visible' y 'global' por atributo y 'Download N name' y 'URL' por cada hueco de archivo, las columnas que lee el importador de WooCommerce; las columnas meta se llaman 'Meta: key'.

flatten_export_value() reduce arrays y objetos (términos, atributos, objetos de datos de WC) a escalares unidos con '|', de modo que un tipo de columna que el plugin nunca ha visto, incluido uno de terceros, no escriba la palabra 'Array'.

Los archivos de exportación contienen precios y URL de archivos y no deben ser públicosCarpeta cerrada con .htaccess, nombre de archivo con sufijo HMAC, descarga mediante un manejador con comprobación

El sufijo del nombre de archivo debe coincidir con una marca de tiempo en minutos y recibe una cola de 12 caracteres de hash_hmac('sha256', postfix, wp_salt('auth')): idéntica en todas las peticiones de una exportación, de modo que todos los lotes terminan en un solo archivo, pero imposible de adivinar desde fuera. La cola importa en nginx, que ignora la regla deny de .htaccess.

woobe_export_download() comprueba nonce y capacidad, acepta solo los nombres que genera la extensión, vacía todos los búferes de salida y transmite con readfile() sin Content-Length. Los archivos de más de 8 horas se eliminan, con una comprobación como máximo cada hora.

Un agente de IA necesita un enlace de descarga sin exponer su claveRuta REST identificada por un token de un solo archivo de 32 hex que caduca a los 15 minutos

Un clic en el navegador no puede enviar una cabecera Authorization, y la clave MCP se mantiene fuera de las URL. La herramienta de exportación guarda el nombre del archivo bajo un token aleatorio en un transient con una vida de 900 segundos, y /woobe/v1/mcp/export/<token> sirve solo ese archivo.

Servidor MCP para agentes de IA

shipped · 2026

Qué aportaUn asistente de IA puede editar la tienda en nombre del propietario, pero solo sobre una selección confirmada, con una vista previa antes y con todos los cambios reversibles.

Problema
Un agente de IA trabaja a través de una API de red sin pantalla, puede interpretar mal un filtro o un recuento y puede emitir una escritura sobre todo el catálogo; aun así tiene que ser seguro, reversible y ajustarse a los límites de tiempo de PHP.
Solución

Las herramientas reutilizan los propios motores de filtro, edición masiva e historial del editor; las escrituras solo aceptan selecciones congeladas confirmadas por su recuento, pueden comprobarse antes con una herramienta de vista previa en modo simulación y se ejecutan en porciones reanudables.

Stack
  • WP REST API
  • JSON-RPC 2.0
  • Model Context Protocol
  • wp_options
Tareas · 6
Un filtro mal interpretado no debe ampliar una escritura a todo el catálogoLas escrituras solo aceptan un selection_id congelado más un confirm_count igual a su tamaño

woobe_find_products ejecuta el filtro una vez y guarda los ID en una opción sin autocarga (hasta 200,000 ID, válida 24 horas). La respuesta incluye selection_id, count, una muestra y whole_catalogue, medido frente a una consulta sin filtrar, de modo que un filtro que no acota nada se informa como tal.

woobe_apply_bulk no acepta ningún filtro. Se niega a actuar salvo que confirm_count sea igual al tamaño de la selección, así que un agente que no leyó el número no puede adivinarlo. Los productos creados después de la búsqueda no están en la selección.

El filtro de un agente debe rechazarse, no ignorarse, cuando el motor no puede aplicarloUn validador estricto rechaza cualquier clave o estructura que el motor no aplicaría, e indica el motivo

El motor de filtros lee los campos del formulario propio del editor e ignora cualquier otra clave. Un agente puede enviar una clave con errores ortográficos o una fecha como {from, to}, y una condición ignorada ampliaría la selección. WOOBE_MCP_FILTER comprueba cada clave frente a la lista de campos que lee el motor, rechaza indicando la clave y la estructura aceptada, y entrega el resto en la forma que envía la pantalla.

El motor engancha hooks posts_where que permanecen durante el resto de la petición, que en la pantalla es una carga de página pero puede ser varias búsquedas en un solo lote JSON-RPC. release_filter_hooks() los elimina tras cada consulta MCP, de modo que una búsqueda nunca hereda las condiciones de otra. Los tipos de los argumentos se comprueban frente al inputSchema de cada herramienta, así que la cadena 'maybe' no es una confirmación.

La vista previa debe mostrar lo que la escritura producirá realmenteEl modo simulación replica la aritmética de la edición masiva y encadena en orden las operaciones de una ejecución

simulate() copia literalmente _process_number_data(), y run_chain() pasa cada resultado a la operación siguiente, de modo que un precio de oferta establecido junto con un nuevo precio normal se comprueba frente al nuevo. La aritmética se aplica solo a los campos numéricos sobre los que calcula el motor; cualquier otro campo se muestra como un simple reemplazo.

La vista previa devuelve tres recuentos (selección, afectados tras expandir los padres a variaciones, escribibles) y avisos: porcentaje de un campo vacío omitido, padres variables sin precio propio, stock sin control.

Una llamada REST tiene el mismo límite de tiempo que un lote AJAXFragmentos de 25 mediante do_bulk, 20 s por llamada, reanudación por bulk_key y next_offset

El bucle de aplicación divide la cola en fragmentos de 25 y llama a WOOBE_BULK::do_bulk(), el motor que usa la pantalla, hasta que pasan 20 segundos. Devuelve next_offset y bulk_key; una continuación escribe en la misma cabecera del historial, de modo que una sola reversión deshace todo el trabajo. Las cachés se borran una vez tras las escrituras, no por fragmento.

En la versión gratuita el fragmento se reduce a la cuota de escritura restante: un token bucket de 100 productos que se rellena de forma continua a lo largo de 30 minutos, con tokens fraccionarios y un techo estricto, de modo que el tiempo de inactividad no pueda pagar una ejecución grande.

Las variaciones no llevan ninguno de los términos ni el meta por los que filtra el filtroHijos añadidos tras la consulta: todos, o solo los que coinciden con cada atributo nombrado

include_variations 'all' añade todos los hijos de los padres coincidentes; 'matching' conserva los hijos cuyos valores de atributo coinciden con todos los términos pa_ del filtro ('amarillo y pequeño' es una variación, no la unión), y un valor vacío cuenta como 'cualquiera'. Las categorías y etiquetas no pueden acotar los hijos.

Las claves de atributo se construyen con sanitize_title(), como las construye WooCommerce, porque los nombres de atributo no latinos se guardan codificados en URL y sanitize_key() los eliminaría. En el modo matching, los padres variables se quitan de la selección para que un variations_only posterior no los expanda de nuevo a todas las variaciones.

Dentro de una llamada REST, el plugin y la tienda se comportan de forma distinta que en el administradorArranque propio en rest_api_init, filtros de precio de divisas eliminados, términos padre completados

WOOBE::init() se ejecuta en init, antes de que se defina REST_REQUEST, así que en una llamada REST no ve ni admin ni REST y no haría nada. bootstrap.php engancha rest_api_init por su cuenta y levanta el plugin; is_should_init() permite REST solo para las rutas propias del plugin, no para todo el tráfico REST del sitio.

Los conmutadores de divisas filtran los getters de precios, así que el agente leía precios convertidos; strip_price_filters() elimina esos filtros solo para la petición MCP. Un producto colocado en una subcategoría se coloca también en sus ancestros mediante un listener de set_object_terms, ya que en un chat nadie marca las casillas de los padres.

Control de acceso y seguridad

shipped · 2026

Qué aportaSolo las personas adecuadas pueden cambiar productos; con el segundo factor opcional activado, una clave de acceso de IA filtrada por sí sola no abre la tienda, y el acceso puede limitarse por área.

Problema
Un editor masivo tiene acceso de escritura a todos los productos. Cada manejador AJAX, el historial y el endpoint MCP deben comprobar quién actúa, y una clave MCP guardada en los ajustes de un asistente puede filtrarse.
Solución

Un único guardián para todos los manejadores AJAX con comprobaciones de propiedad por producto; para MCP, claves solo en cabeceras comparadas en tiempo constante, un token de sesión confirmado (opcional para la clave de toda la tienda, siempre activo para las claves personales), claves personales y un mapa de permisos por sector.

Stack
  • Nonces de WordPress
  • current_user_can / capacidades meta
  • hash_equals
  • random_int
  • WP REST permission_callback
Tareas · 5
Decenas de manejadores AJAX tenían cada uno sus propias comprobaciones parcialesUn único guardián: nonce, capacidad y después edit_post por producto, a través del padre en las variaciones

Tras un informe de WPScan se auditó todo el conjunto de manejadores AJAX, no solo los señalados, y cada manejador se pasó a WOOBE_HELPER::check_ajax_access(). Esta función comprueba el nonce, luego la capacidad y después current_user_can('edit_post') para cada ID de producto.

Las variaciones se comprueban frente al producto padre: WooCommerce asigna edit_post en una variación al simple edit_products, que ignora la propiedad, y las variaciones tienen post_author 0. get_request_array() rechaza un escalar donde se espera un array en lugar de convertirlo. Los roles que no son administrador están además limitados por campo.

Una clave de API estática se filtra por las URL, los registros y los tiempos de respuestaClave solo en cabeceras, hash_equals, cada petición controlada, ninguna ruta mientras MCP está desactivado

Con MCP desactivado, register_routes() retorna antes de registrar nada, así que el endpoint no existe. permission() exige la clave en cada petición, incluido el handshake de MCP, de modo que quien llama sin autenticar no averigua nada sobre el endpoint. La clave se lee de Authorization: Bearer o de X-WOOBE-KEY, con el respaldo REDIRECT_HTTP_AUTHORIZATION de Apache, y se compara con hash_equals.

Los rechazos usan 403, no 401: en MCP un 401 inicia el descubrimiento OAuth y el cliente informa de que el servidor está roto. Los enlaces de descarga y subida usan sus propios tokens de un solo propósito porque un clic en el navegador no puede enviar una cabecera.

Una clave filtrada por sí sola no debería dar acceso a la tiendaSegundo factor: un token de sesión confirmado en WordPress, guardado con hash, caducidad por inactividad de 1 h

El asistente pide un token, formado por cuatro grupos de ocho letras y dígitos de random_int, y lo confirma una persona con sesión iniciada en WordPress: un administrador para la clave de toda la tienda, el propietario de la clave en sus propios ajustes para una clave personal. Para la clave de toda la tienda este segundo factor es un ajuste; para las claves personales siempre está activo. Solo se guarda su hash sha256 junto con las horas de confirmación y de último uso. check_connection() compara con hash_equals y descarta la conexión tras 3600 segundos sin una llamada; la hora de último uso se escribe como máximo una vez por minuto.

Las conexiones son por propietario: un token confirmado para una clave personal se rechaza con cualquier otra clave.

Las claves por usuario no deben revelar qué usuarios tienen accesoLa clave almacenada se lee y se compara siempre, con un señuelo de igual longitud cuando no existe

Una clave personal es woobe_<40 hex>-<user id>. personal_key_owner() lee y compara siempre la clave almacenada con hash_equals, usando un señuelo de la misma longitud cuando el usuario no tiene ninguna, y consulta la lista de concesiones y la capacidad solo tras una coincidencia total, de modo que el tiempo de respuesta no revela a un extraño quién tiene acceso. Los ID con ceros a la izquierda siguen la ruta del señuelo.

La petición se ejecuta entonces como ese usuario mediante wp_set_current_user(), de modo que su rol y la visibilidad de campos se aplican exactamente igual que en la pantalla, y la conexión de doble factor está siempre activa para las claves personales.

Los propietarios necesitan acceso de solo lectura o ningún acceso por área de la tiendaMapa de sectores full/read/none por usuario, acotado por las capacidades de WP; herramientas cerradas ocultas

Cada herramienta declara un sector y annotations.readOnlyHint. El propietario devuelve un mapa desde el filtro woobe_mcp_permissions; '*' fija el valor por defecto, por lo que funciona como lista de permitidos. Las entradas no válidas se registran y se ignoran, y un '*' no válido vuelve a full, igual que sin callback. Para una clave personal, cada sector se acota de nuevo con la capacidad que WooCommerce pide en esa pantalla (edit_products, edit_shop_orders, view_woocommerce_reports y otras).

Las herramientas que la identidad no puede llamar ni se listan. Una escritura que cae en otro sector, como una reversión que reescribe productos, pide require_access() para ese sector antes de escribir. El filtro woobe_mcp_tool_allowed solo puede restringir más. Ninguna clave MCP, de toda la tienda o personal, puede escribir post_author ni ID; eso lo edita una persona en wp-admin.

Cifras

wordpress.org
  • Instalaciones activas 30.000+ contador de wordpress.org.
  • Reseñas 227 Media de 4.7 sobre 5.
  • Años de soporte 8 Desde 7 de mayo de 2018, última actualización 13 de octubre de 2026.