Idioma

Entrar em contacto

Plugin para WooCommerce · desde 2018 · aka WOOBE

BEAR

Um editor em massa de produtos WooCommerce ao estilo de folha de cálculo, com um servidor MCP que permite a agentes de IA editar a loja em segurança.

Primeira versão
Instalações ativas
30 000+
Classificação
4.7 / 5 (227)Ler avaliações
  • PHP
  • WordPress
  • WooCommerce CRUD (WC_Product)
  • WP_Query
  • $wpdb / MySQL
  • WP REST API
  • JSON-RPC 2.0
  • Model Context Protocol
  • jQuery
  • DataTables
Ver em wordpress.org wordpress.org

Sobre

O BEAR mostra o catálogo WooCommerce como uma única tabela editável e altera milhares de produtos de uma só vez: preços, stock, SKUs, taxonomias, atributos, variações e meta personalizada. Cada alteração passa por um único caminho de escrita de campos que usa os objetos de produto do WooCommerce para as propriedades do produto, fica registada com o valor anterior e pode ser revertida no separador Histórico.

O difícil não é a tabela. É tornar correta uma escrita em massa numa loja real: valores guardados em cinco sítios diferentes, produtos variáveis cujos preços e stock estão nas variações, efeitos secundários do WooCommerce como o estado de stock derivado da quantidade em stock, e alojamento partilhado que interrompe um pedido PHP ao fim de 30 segundos.

Desde 2026 o plugin executa também um servidor MCP. Um agente de IA pode encontrar produtos, pré-visualizar uma alteração, aplicá-la por porções e revertê-la, através dos mesmos motores de filtro, de edição em massa e de histórico que o ecrã usa, protegido por chaves, por um token de sessão confirmado (opcional para a chave de toda a loja, sempre ativo nas chaves pessoais) e por um mapa de permissões por setor.

A versão gratuita e a premium são a mesma base de código, comutada por uma única flag; a versão gratuita limita o tamanho da página, os campos meta, a profundidade do histórico e a taxa de escrita do MCP.

BEAR · editor em massa

o que foi resolvido

ATIVOdesde 2018

Grelha de produtos e modelo de campos

shipped · 2018

O que ofereceA equipa vê e altera qualquer detalhe de um produto ou variação num único ecrã semelhante a uma folha de cálculo, mesmo em catálogos demasiado grandes para abrir de uma vez.

Problema
Um único ecrã tem de mostrar e editar qualquer valor de um catálogo de qualquer dimensão: campos de post, propriedades do WooCommerce, taxonomias, atributos, downloads e meta arbitrária, para produtos e respetivas variações.
Solução

Um registo declarativo de campos comanda cada coluna e cada escrita, e a tabela é paginada no servidor, uma página de WP_Query por pedido AJAX.

Stack
  • WP_Query
  • DataTables em modo servidor
  • setters de WC_Product
  • wp_options
Tarefas · 6
Os catálogos grandes não podem ser carregados de uma só vez no navegadorPaginação no servidor: cada página da tabela é uma WP_Query com offset, ordenação e filtros

woobe_get_products() recebe start, length e order do DataTables e passa-os a WOOBE_PRODUCTS::gets(), que constrói a WP_Query. A ordenação por uma coluna meta passa a meta_value_num para os tipos number, timestamp e unix, e a meta_value nos restantes.

O tamanho da página está limitado a 100 linhas no modelo de definições, com o comentário de que páginas maiores causavam HTTP 500 em servidores fracos; na versão gratuita está fixo em 10. Quando a opção de variações está ativa, as variações de cada produto variável são consultadas e inseridas como linhas logo a seguir ao produto-pai.

Cada tipo de coluna é guardado de forma diferente dentro do WooCommerceUma entrada de registo por coluna: tipo de armazenamento, tipo de valor, editor, sanitizador, permissões

woobe_get_fields() descreve cada coluna: field_type (field, meta, prop, taxonomy, attribute, downloads, gallery, upsells e outros), tipo de valor, vista de edição, sanitizador, tipos de produto a que não se aplica e visibilidade para gestores da loja. Os atributos globais e todas as taxonomias de produto são acrescentados em tempo de execução a partir de wc_get_attribute_taxonomies() e get_object_taxonomies().

update_page_field() despacha por field_type: update_post_meta para meta, setters de WC_Product mais save() para props, wp_set_post_terms para taxonomias, listas WC_Product_Attribute reconstruídas para atributos. As células da grelha, as reversões do Histórico e a maioria das escritas em massa terminam nesta função. As edições em massa de taxonomias e atributos escrevem os termos diretamente e disparam o mesmo hook do Histórico antes de escrever.

As chaves meta personalizadas têm de se distinguir das internas do WooCommerceUma subclasse de WC_Product_Data_Store_CPT expõe a sua lista protegida de chaves internas

woobe_meta_get_keys() lê todas as chaves meta de um produto de amostra e subtrai as chaves internas do WooCommerce. Essa lista é uma propriedade protegida do data store de produtos, pelo que WOOBE_PDS_CPT estende o data store apenas para a devolver, acrescentando _button_text e _product_url.

As chaves meta são aparadas, mas não convertidas para minúsculas nem sanitizadas, porque as chaves reais escritas por outros plugins, como _woocs_sale_price_USD, distinguem maiúsculas de minúsculas.

Um campo meta tipado tem de funcionar em colunas, filtros e edição em massaUma definição de meta é mapeada por hooks no registo, no painel de filtros e no painel de edição em massa

woobe_extend_fields() transforma cada definição de meta guardada numa coluna; o tipo number recebe o sanitizador floatval e ordenação numérica. A mesma definição alimenta woobe_filter_numbers (intervalo de/até), woobe_filter_text (LIKE, =, !=, NOT LIKE, vazio, não vazio), filtros de interruptor e woobe_bulk_number (definir, aumentar ou diminuir por valor ou por percentagem).

Uma coluna de interruptor pode guardar valores diferentes de 1/0 (por exemplo yes/no) através de uma regra de substituição por chave lida por over_switcher_swicher_to_val(). A versão gratuita mantém os primeiros 2 campos meta.

A meta de arrays serializados não tem uma célula simples para editarO editor em popup envia chaves e valores como arrays paralelos, reconstruídos em arrays PHP aninhados

process_jsoned_meta_data() reconstrói um valor meta a partir de pares keys/values para arrays e keys2/values2 para objetos, descodificando valores JSON aninhados, e recorre ao valor bruto para strings e números simples. O Histórico guarda esses valores como JSON com JSON_HEX_QUOT e JSON_HEX_TAG para que sobrevivam à ida e volta.

O estado do editor por utilizador tem de sobreviver a limpezas da cache de objetosUma linha de opção sem autoload por utilizador, com TTL por chave, em vez de transients

Os filtros e as configurações de edição em massa são mantidos entre pedidos por WOOBE_STORAGE. Com Redis ou Memcached, um transient vive apenas na cache de objetos e perde-se numa limpeza, pelo que o armazenamento por defeito é uma linha de opção woobe_memory_<user id>: cada chave tem a sua própria validade, as chaves expiradas são eliminadas na escrita, o autoload está desligado e a linha é apagada com o utilizador. Os modos session, transient e cookie continuam disponíveis através do filtro woobe_storage_type.

Filtragem

shipped · 2018

O que ofereceA equipa escolhe exatamente os produtos de que precisa, incluindo por preço ou stock da variação, e a mesma seleção é usada para edição e exportação.

Problema
Os filtros por preços, stock, dimensões, texto e taxonomias têm de selecionar exatamente os produtos certos, incluindo produtos variáveis cujos valores estão nas variações, e o mesmo conjunto tem de ser reutilizado pela paginação, pela edição em massa e pela exportação.
Solução

O filtro é guardado no servidor sob uma chave e compilado em cada consulta em cláusulas tax_query, meta_query e posts_where através de um único hook de WP_Query.

Stack
  • WP_Query
  • tax_query
  • meta_query
  • posts_where
  • $wpdb->prepare
  • MySQL REGEXP
Tarefas · 5
Os pedidos de paginação, edição em massa e exportação têm de ver o mesmo conjunto filtradoFiltro guardado sob uma chave aleatória por execução; um hook de WP_Query reconstrói-o a cada pedido

woobe_filter_products() guarda o formulário de filtro analisado em woobe_filter_<key>, onde a chave é uma string aleatória gerada em cada filtragem, de modo que a edição em massa por filtro funciona em vários separadores do navegador em simultâneo. Cada chamada a WOOBE_PRODUCTS::gets() passa os seus args por woobe_apply_query_filter_data(), que lê o filtro guardado e devolve tax_query, meta_query, post__in e post_status, e acrescenta hooks posts_where para o resto.

A paginação da grelha, a contagem da edição em massa, a contagem da exportação e as ferramentas de variações chamam todas gets(), pelo que não podem discordar sobre que produtos um filtro designa.

O preço e o stock estão nas variações, mas a grelha lista os produtos-paiConsulta por intervalo em produtos e variações, depois os resultados de variações são mapeados para os respetivos pais

regular_price_where(), sale_price_where(), stock_quantity_where() e dimensions_where() consultam o postmeta tanto de product como de product_variation, depois leem o post_parent das variações encontradas e restringem a consulta principal aos pais mais as variações com ID IN (...).

Quando nada corresponde, a cláusula passa a ID = -1, de modo que uma correspondência vazia não devolve produtos em vez de devolver todo o catálogo, o que importa a um clique de uma edição em massa.

Os números guardados como strings meta são comparados incorretamente no MySQLCAST para DECIMAL com escala explícita; strings vazias excluídas de todos os intervalos

Os intervalos numéricos personalizados usam meta_query do tipo DECIMAL(30,20): um DECIMAL sem mais tem escala 0, pelo que uma pesquisa de 100 a 150 encontraria também 99.xx. O SQL de preços faz cast para DECIMAL(30,10).

Um peso vazio é guardado como '' e o MySQL lê '' como 0, pelo que os intervalos de preço e dimensões exigem meta_value <> '' e uma caixa vazia nunca conta como 0. Um extremo em falta de um intervalo fica aberto (substituído por -999999999 ou 999999999), e de igual a até significa exatamente esse valor.

Os utilizadores precisam de AND, OR e NOT numa única caixa de pesquisa de textoPequena sintaxe de pesquisa (^ OR, ^~ AND, ^! NOT) compilada em cláusulas MySQL REGEXP

posts_txt_where() divide o texto em ^; um prefixo ~ liga a parte com AND, um prefixo ! transforma-a em NOT REGEXP dentro de um grupo AND separado. Os comportamentos exact, begin, end, not e empty correspondem a = e a REGEXP ancorado; parênteses retos e chavetas de regex são escapados. Aplica-se ao título, conteúdo, excerto e slug.

O filtro de SKU aceita uma lista separada por vírgulas e constrói condições LIKE preparadas com esc_like, OR para os modos de correspondência e AND para 'not'.

Corresponder a todos os termos de vários exige uma cláusula tax_query por termoAND dividido numa cláusula por termo; operadores desconhecidos recorrem a IN

Para o operador AND, cada termo torna-se a sua própria cláusula tax_query sob relation AND, porque uma única cláusula com operator AND pode não devolver produtos. Os operadores estão numa lista branca: IN, NOT IN, AND, EXISTS e NOT EXISTS; o WordPress ignora uma cláusula com operador desconhecido e corresponderia a todos os produtos.

A visibilidade no catálogo é traduzida em combinações dos termos de product_visibility exclude-from-catalog e exclude-from-search, e um filtro de estado de stock limita também product_type aos tipos que têm estado de stock.

Motor de edição em massa

shipped · 2018

O que ofereceAs alterações a preços, stock e variações em milhares de produtos terminam de forma fiável em alojamento comum e seguem as regras de preços e de stock da própria loja.

Problema
Uma alteração em massa pode atingir milhares de produtos em alojamentos com limites curtos de tempo PHP, e cada escrita tem de respeitar as regras do WooCommerce para preços, stock e variações.
Solução

Os IDs de destino são resolvidos uma só vez e depois escritos em pequenos lotes AJAX através do mesmo caminho de escrita de um só campo que a grelha usa, o qual transporta as regras do WooCommerce.

Stack
  • Lotes AJAX
  • WC_Product
  • WC_Product_Variation
  • WC_Product_Attribute
Tarefas · 6
Uma edição em massa de milhares de produtos esbarra nos limites de tempo do PHPIDs obtidos uma só vez e depois enviados em lotes de N por pedido AJAX; N é uma definição

woobe_bulk_products_count() guarda a configuração da edição em massa sob um bulk_key aleatório de 16 caracteres e devolve todos os IDs filtrados (fields=ids, sem paginação). __woobe_bulk_products() em bulk.js fatia essa lista e envia um lote após outro, atualiza a barra de progresso e chama woobe_bulk_finish no fim; o utilizador pode abortar o pedido em curso.

O tamanho do lote é number_posts_processed, de 10 a 100 por pedido, 30 por defeito, de modo que um servidor fraco pode trocar velocidade por estabilidade.

Os produtos editados saem do filtro enquanto a execução decorreLista de IDs congelada: cada lote traz IDs do instantâneo, não de uma consulta repetida

Paginar a consulta filtrada entre lotes saltaria produtos: uma edição de preço retira um produto de um filtro de preço e todos os offsets seguintes se deslocam. A lista de IDs obtida no início é mantida pelo cliente e cada pedido de lote envia os seus IDs explicitamente, sem a chave do filtro.

Após a execução, as linhas marcadas que já não aparecem na tabela redesenhada são retiradas da seleção, para que a ação seguinte não atinja produtos que o utilizador já não vê.

Os comportamentos numéricos têm de se combinar com fórmulas e arredondamento de preçosPipeline por produto: comportamento, fórmula com outra meta, intervalo aleatório, arredondamento

_process_number_data() aplica definir, aumentar ou diminuir por valor ou percentagem, e regras entre campos, como preço de saldo = preço normal menos uma percentagem. Depois uma fórmula opcional com outro campo meta (+, -, *, /, com proteção contra divisão por zero), depois um valor aleatório opcional num intervalo com casas decimais definidas.

update_page_field() arredonda nas casas decimais de preço da loja: a unidades inteiras, para cima até 0.05, até 0.1, ou para uma terminação fixa de cêntimos como .99. Os campos de texto aceitam acrescentar no fim, acrescentar no início, substituir (com ou sem distinção de maiúsculas) e marcadores como {TITLE}, {SKU}, {meta:key} e {attribute:pa_size}.

O preço de saldo tem de ficar abaixo do preço normal e as datas têm de ter efeitoSaldo >= normal ignorado, saldo <= 0 remove-o; _price recalculado quando as datas mudam

Numa execução em massa, um preço de saldo calculado que atinja o preço normal é ignorado e um valor de zero ou menos elimina o saldo. Uma escrita individual de um preço de saldo igual ou superior ao normal é limitada ao normal menos a menor unidade de preço.

Quando as datas de saldo são escritas, _price é definido de imediato para o preço que se aplica agora (saldo ainda não iniciado, em curso, terminado) e os transients do produto são limpos, em vez de esperar pela tarefa agendada de saldos do WooCommerce.

As escritas de stock e preço não devem danificar produtos variáveis nem stock partilhadoPais variáveis e variações com stock do pai deixados intactos; stock preservado entre alternâncias

O WooCommerce lê os preços de um produto variável a partir das suas variações, pelo que um preço escrito no pai é ignorado e uma escrita de limpeza remove qualquer preço residual do pai. Um stock de 0 ou menos normalmente ativa a gestão de stock; isto não se faz num pai variável que não controla stock, onde deixaria sem stock todas as variações sem stock próprio, nem numa variação que vende a partir do stock do pai, que ficaria desligada dele.

O WooCommerce limpa quantidade, encomendas em espera e quantidade de stock baixo quando a gestão de stock é desligada. keep_stock_on_switch() guarda-os na sua própria meta e restaura-os quando a gestão é ligada de novo.

As variações têm de ser criadas e editadas por combinação de atributosProduto cartesiano dos termos escolhidos, sem duplicados; edições correspondidas por combinação com caráter universal

generate_combinations() constrói o produto cartesiano dos termos de atributo selecionados. Ao aplicá-lo, associa os atributos como atributos de variação, ignora as combinações que o produto já tem, copia os preços do pai, atribui a cada nova variação o SKU parent-SKU-<id> para evitar duplicados, e reordena as variações através do data store.

Editar, eliminar e exportar um subconjunto de variações compara os atributos de cada variação com as combinações escolhidas: chaves e valores ordenados, entradas '-1' tratadas como qualquer valor, e ambos os lados descodificados de URL porque os slugs de atributos não latinos são guardados codificados.

Histórico e reversão

no produto

O que ofereceUma alteração errada, manual, em massa ou feita por um assistente de IA, pode ser desfeita em vez de corrigida produto a produto.

Problema
Cada alteração, manual, em massa ou feita por um agente de IA, tem de poder ser desfeita, incluindo os efeitos secundários que o WooCommerce aplica por si, e a reversão de uma execução grande esbarra nos mesmos limites de tempo que a própria execução.
Solução

Os valores anteriores são registados por campo em duas tabelas próprias por um hook no caminho único de escrita, e revertidos por porções através do mesmo caminho.

Stack
  • Tabelas MySQL próprias
  • $wpdb
  • actions do WordPress
Tarefas · 6
Registar os valores anteriores de qualquer tipo de campo sem código específico por funcionalidadeUm hook antes de cada escrita de campo; valor anterior guardado por campo numa tabela própria

update_page_field() dispara woobe_before_update_page_field e WOOBE_HISTORY::write() lê o valor atual e codifica-o por tipo: IDs de termos, atributos, galeria e relações como JSON, downloads como nome/ficheiro/hash, estado de stock como 1/0, datas de saldo como datas ISO com o desvio do fuso horário, para que uma reversão restaure o calendário exato.

As linhas contêm field_key, product_id, prev_val, mod_date, bulk_key, user_id e via_mcp, indexadas por produto, bulk key e utilizador. Uma segunda tabela guarda uma cabeça por execução em massa: estado (concluída ou terminada), início, fim, número de produtos e as chaves dos campos.

O WooCommerce altera o estado de stock como efeito secundário das edições de stockEfeitos secundários escritos como linhas extra da mesma alteração e revertidos em conjunto, por ordem

Ligar a gestão de stock, ou um stock de 0 num produto sem controlo, faz o WooCommerce derivar o estado de stock; desligar a gestão elimina também a quantidade. update_page_field() regista stock_status, e stock_quantity quando necessário, como linhas extra logo a seguir ao campo editado.

stock_status_pair() e stock_row_at() encontram estas linhas por IDs adjacentes (+1, +2) e exigem o mesmo produto, autor, canal (manual ou através de um agente) e bulk key, e um tempo de escrita próximo. Depois revert() restaura a quantidade antes do estado, porque o WooCommerce deriva o estado a partir da quantidade enquanto o stock é gerido. Um stock anterior vazio significa 'sem controlo', pelo que untrack_stock() desliga a gestão em vez de escrever 0.

Reverter uma execução em massa de milhares de linhas também excede o tempoReversão por porções por bulk_key; cada linha revertida é eliminada; cabeça removida no fim

No separador Histórico, woobe_history_revert_bulk_portion() reverte 10 linhas por pedido AJAX com uma barra de progresso, e a linha de cabeça é eliminada quando o contador atinge o total. Cada reversão elimina a sua linha, pelo que a porção seguinte pega simplesmente no que sobra.

A ferramenta de reversão do MCP é um caminho separado: revert_bulk_portion() reverte até 200 linhas por chamada e comunica as linhas realmente removidas, porque as duas linhas de uma alteração de stock são revertidas em conjunto e uma porção pode levar mais uma linha do que o seu limite.

revert() remove primeiro todos os listeners de woobe_before_update_page_field, para que as próprias escritas da reversão não sejam registadas como novo histórico.

Vários utilizadores e agentes de IA partilham um único históricoUma regra scope(): os administradores veem todas as linhas, os outros as suas; aplicada no servidor

Cada linha tem user_id e via_mcp. A chave MCP de toda a loja escreve como utilizador -777, que nunca pode colidir com um utilizador real nem com 0 (sem sessão iniciada). scope() devolve uma única condição SQL usada por cada listagem, reversão, eliminação e limpeza, pelo que nenhuma consulta se pode afastar da regra.

Os handlers verificam solo_in_scope() ou bulk_in_scope() e respondem 403 a um pedido construído à mão para uma linha que o utilizador não pode ver. Os nomes dos autores para a lista são lidos numa única consulta.

As instalações existentes precisaram de uma nova coluna sem estragar as linhas antigasOpção de esquema versionada, ALTER protegido por verificação da coluna, preenchimento retroativo das linhas de agente

CREATE TABLE IF NOT EXISTS nunca altera uma tabela que já existe. maybe_upgrade_schema() corre uma vez por versão do esquema (agora 2), verifica SHOW COLUMNS antes de cada ALTER para que um pedido concorrente não falhe, repete no carregamento seguinte se o ALTER falhar, e marca as linhas antigas do utilizador -777 como escritas através de um agente.

A versão gratuita mantém um histórico curto sem tocar nos outros utilizadoresLimpeza por autor no shutdown: últimas 2 alterações individuais e 2 em massa, linhas de stock preservadas

Na versão gratuita, prune_author() mantém as duas alterações individuais mais recentes e as duas execuções em massa mais recentes de um autor. As linhas que pertencem a uma alteração de stock mantida são mantidas com ela através de stock_change_rows(). A limpeza corre no shutdown após uma escrita, e abrir o separador nunca elimina linhas de outro utilizador.

Exportação

no produto

O que ofereceAs listas de produtos filtradas exportam-se para ficheiros que a loja pode importar de volta, construídos por partes em servidores fracos e nunca deixados abertos na web.

Problema
A exportação de um catálogo filtrado com variações tem de correr por partes em servidores fracos, produzir ficheiros que o WooCommerce consiga importar de volta e não deixar dados de produtos abertos na web.
Solução

A exportação corre como uma sequência de pedidos AJAX que acrescentam a um único ficheiro cujo nome é derivado por exportação, guardado numa pasta fechada e entregue por um handler autorizado.

Stack
  • fputcsv
  • DOMDocument (WXR)
  • hash_hmac
  • .htaccess
  • WP REST API
Tarefas · 4
Uma exportação grande não pode ser construída num único pedidoCabeçalho escrito primeiro, depois linhas acrescentadas por lote AJAX ao mesmo ficheiro

woobe_export_products_count() limpa os ficheiros anteriores, escreve o cabeçalho CSV ou o esqueleto XML e devolve os IDs filtrados. woobe_export_products() acrescenta um lote por pedido com fopen(a+) e fputcsv, dado que o WP_Filesystem não consegue gravar linha a linha; para XML carrega o documento WXR e acrescenta nós item.

As variações são adicionadas depois do respetivo pai, opcionalmente sem as esgotadas, e podem ser limitadas ou excluídas por combinações de atributos.

O CSV tem de carregar no importador de produtos do próprio WooCommerceAtributos em quatro colunas cada, downloads como pares nome/URL, qualquer valor achatado

get_csv_titles() escreve 'Attribute N value(s)', 'name', 'visible' e 'global' por atributo e 'Download N name' e 'URL' por posição de ficheiro, as colunas que o importador do WooCommerce lê; as colunas de meta chamam-se 'Meta: key'.

flatten_export_value() reduz arrays e objetos (termos, atributos, objetos de dados WC) a escalares unidos por '|', de modo que um tipo de coluna que o plugin nunca viu, incluindo um de terceiros, não escreve a palavra 'Array'.

Os ficheiros de exportação contêm preços e URLs de ficheiros e não podem ser públicosPasta fechada por .htaccess, nome de ficheiro com cauda HMAC, descarregamento por handler verificado

O sufixo do nome do ficheiro tem de corresponder a um carimbo de data/hora ao minuto e recebe uma cauda de 12 caracteres de hash_hmac('sha256', postfix, wp_salt('auth')): idêntica em todos os pedidos de uma exportação, de modo que todos os lotes acabam num só ficheiro, mas impossível de adivinhar do exterior. A cauda importa no nginx, que ignora a regra deny do .htaccess.

woobe_export_download() verifica o nonce e a capability, aceita apenas nomes que a extensão gera, limpa todos os buffers de saída e envia com readfile() sem Content-Length. Os ficheiros com mais de 8 horas são eliminados, com verificação no máximo uma vez por hora.

Um agente de IA precisa de uma ligação de descarregamento sem expor a sua chaveRota REST com chave de um token de ficheiro único de 32 hex que expira ao fim de 15 minutos

Um clique no navegador não consegue enviar um cabeçalho Authorization, e a chave MCP é mantida fora dos URLs. A ferramenta de exportação guarda o nome do ficheiro sob um token aleatório num transient com 900 segundos de vida, e /woobe/v1/mcp/export/<token> serve apenas esse ficheiro.

Servidor MCP para agentes de IA

shipped · 2026

O que ofereceUm assistente de IA pode editar a loja pelo proprietário, mas apenas sobre uma seleção confirmada, com pré-visualização prévia e cada alteração reversível.

Problema
Um agente de IA trabalha através de uma API de rede sem ecrã, pode interpretar mal um filtro ou uma contagem e pode emitir uma escrita sobre todo o catálogo; mesmo assim tem de ser seguro, reversível e caber nos limites de tempo do PHP.
Solução

As ferramentas reutilizam os motores de filtro, edição em massa e histórico do próprio editor; as escritas aceitam apenas seleções congeladas confirmadas pela sua contagem, podem ser verificadas antes com uma ferramenta de pré-visualização em dry-run e correm em porções retomáveis.

Stack
  • WP REST API
  • JSON-RPC 2.0
  • Model Context Protocol
  • wp_options
Tarefas · 6
Um filtro mal interpretado não pode alargar uma escrita a todo o catálogoAs escritas aceitam apenas um selection_id congelado mais um confirm_count igual ao seu tamanho

woobe_find_products corre o filtro uma vez e guarda os IDs numa opção sem autoload (até 200,000 IDs, válida durante 24 horas). A resposta traz selection_id, count, uma amostra e whole_catalogue, medido contra uma consulta sem filtro, de modo que um filtro que não restringe nada é comunicado como tal.

woobe_apply_bulk não aceita qualquer filtro. Recusa, a menos que confirm_count seja igual ao tamanho da seleção, pelo que um agente que não leu o número não o consegue adivinhar. Os produtos criados depois do find não fazem parte da seleção.

O filtro de um agente deve ser recusado, não ignorado, quando o motor não o consegue aplicarValidador estrito recusa qualquer chave ou forma que o motor não aplicaria, e explica porquê

O motor de filtros lê os campos do formulário do próprio editor e ignora qualquer outra chave. Um agente pode enviar uma chave com erro ortográfico ou uma data como {from, to}, e uma condição ignorada alargaria a seleção. WOOBE_MCP_FILTER verifica cada chave contra a lista de campos que o motor lê, recusa com a chave indicada e a forma aceite, e entrega o resto na forma que o ecrã envia.

O motor associa hooks posts_where que permanecem durante o resto do pedido, o que no ecrã é um carregamento de página mas pode ser vários finds num único lote JSON-RPC. release_filter_hooks() remove-os após cada consulta MCP, de modo que um find nunca herda as condições de outro. Os tipos dos argumentos são verificados contra o inputSchema de cada ferramenta, pelo que a string 'maybe' não é uma confirmação.

A pré-visualização deve mostrar o que a escrita vai realmente produzirO dry-run espelha a aritmética da edição em massa e encadeia por ordem as operações de uma execução

simulate() copia literalmente _process_number_data(), e run_chain() passa o resultado de cada operação à seguinte, de modo que um preço de saldo definido em conjunto com um novo preço normal é verificado contra o novo. A aritmética aplica-se apenas aos campos numéricos sobre os quais o motor calcula; qualquer outro campo é mostrado como uma simples substituição.

A pré-visualização devolve três contagens (seleção, afetados após expandir os pais para variações, graváveis) e avisos: percentagem de um campo vazio ignorada, pais variáveis sem preço próprio, stock sem controlo.

Uma chamada REST tem o mesmo limite de tempo que um lote AJAXBlocos de 25 por do_bulk, 20 s por chamada, retomados por bulk_key e next_offset

O ciclo de aplicação divide a fila em blocos de 25 e chama WOOBE_BULK::do_bulk(), o motor que o ecrã usa, até terem passado 20 segundos. Devolve next_offset e bulk_key; uma continuação escreve na mesma cabeça do Histórico, de modo que uma única reversão desfaz o trabalho todo. As caches são limpas uma vez após as escritas, não por bloco.

Na versão gratuita o bloco encolhe até ao limite de escrita restante: um token bucket de 100 produtos que se recarrega continuamente ao longo de 30 minutos, com tokens fracionários e um teto rígido, de modo que o tempo parado não pode pagar uma execução grande.

As variações não têm nenhum dos termos ou meta em que o filtro se baseiaFilhos acrescentados após a consulta: todos, ou apenas os que correspondem a todos os atributos indicados

include_variations 'all' acrescenta todos os filhos dos pais encontrados; 'matching' mantém os filhos cujos valores de atributo correspondem a todos os termos pa_ do filtro ('amarelo e pequeno' é uma variação, não a união), contando um valor vazio como 'qualquer'. As categorias e etiquetas não conseguem restringir os filhos.

As chaves de atributos são construídas com sanitize_title(), como o WooCommerce as constrói, porque os nomes de atributos não latinos são guardados codificados em URL e sanitize_key() eliminá-los-ia. No modo matching, os pais variáveis são removidos da seleção para que um variations_only posterior não os expanda de novo a todas as variações.

Dentro de uma chamada REST, o plugin e a loja comportam-se de forma diferente da administraçãoArranque próprio em rest_api_init, filtros de preços de moeda retirados, termos-pai preenchidos

WOOBE::init() corre em init, antes de REST_REQUEST estar definido, pelo que numa chamada REST não vê nem admin nem REST e não faria nada. bootstrap.php liga-se a rest_api_init por si e levanta o plugin; is_should_init() permite REST apenas para as rotas do próprio plugin, não para todo o tráfego REST do site.

Os conversores de moeda filtram os getters de preço, pelo que o agente lia preços convertidos; strip_price_filters() remove esses filtros apenas para o pedido MCP. Um produto colocado numa subcategoria é também colocado nas categorias ascendentes através de um listener de set_object_terms, dado que ninguém marca as caixas dos pais numa conversa.

Controlo de acessos e segurança

shipped · 2026

O que ofereceSó as pessoas certas podem alterar produtos; com o segundo fator opcional ligado, uma chave de acesso de IA que vaze não abre, por si só, a loja, e o acesso pode ser limitado por área.

Problema
Um editor em massa tem acesso de escrita a todos os produtos. Cada handler AJAX, o histórico e o endpoint MCP têm de verificar quem atua, e uma chave MCP guardada nas definições de um assistente pode vazar.
Solução

Uma proteção única para todos os handlers AJAX com verificações de propriedade por produto; para o MCP, chaves apenas em cabeçalhos comparadas em tempo constante, um token de sessão confirmado (opcional para a chave de toda a loja, sempre ativo nas chaves pessoais), chaves pessoais e um mapa de permissões por setor.

Stack
  • nonces do WordPress
  • current_user_can / capabilities meta
  • hash_equals
  • random_int
  • WP REST permission_callback
Tarefas · 5
Dezenas de handlers AJAX tinham cada um as suas verificações parciaisProteção única: nonce, capability, depois edit_post por produto, via pai para as variações

Após um relatório do WPScan, todo o conjunto de handlers AJAX foi auditado, não apenas os assinalados, e todos os handlers passaram para WOOBE_HELPER::check_ajax_access(). Verifica o nonce, depois a capability, depois current_user_can('edit_post') para cada ID de produto.

As variações são verificadas contra o produto-pai: o WooCommerce mapeia edit_post numa variação para o simples edit_products, que ignora a propriedade, e as variações têm post_author 0. get_request_array() rejeita um escalar onde se espera um array em vez de o converter. Os papéis não administradores são ainda limitados por campo.

Uma chave de API estática vaza por URLs, logs e tempos de respostaChave apenas em cabeçalhos, hash_equals, cada pedido controlado, nenhuma rota enquanto o MCP estiver desligado

Com o MCP desligado, register_routes() devolve antes de registar o que quer que seja, pelo que o endpoint não existe. permission() exige a chave em cada pedido, incluindo o handshake MCP, de modo que quem chama sem autenticação não fica a saber nada sobre o endpoint. A chave é lida de Authorization: Bearer ou X-WOOBE-KEY, com o recurso REDIRECT_HTTP_AUTHORIZATION do Apache, e comparada com hash_equals.

As recusas usam 403, não 401: no MCP um 401 inicia a descoberta OAuth e o cliente comunica o servidor como avariado. As ligações de descarregamento e de carregamento usam tokens próprios de finalidade única porque um clique no navegador não consegue enviar um cabeçalho.

Uma chave vazada, por si só, não deve dar acesso à lojaSegundo fator: um token de sessão confirmado no WordPress, guardado com hash, expiração por inatividade de 1 h

O assistente pede um token, composto por quatro grupos de oito letras e dígitos de random_int, e é confirmado por uma pessoa com sessão iniciada no WordPress: um administrador para a chave de toda a loja, o proprietário da chave nas suas próprias definições para uma chave pessoal. Para a chave de toda a loja este segundo fator é uma definição; para as chaves pessoais está sempre ativo. Apenas o seu hash sha256 é guardado, com as horas de confirmação e de último uso. check_connection() compara com hash_equals e termina a ligação após 3600 segundos sem uma chamada; a hora do último uso é escrita no máximo uma vez por minuto.

As ligações são por proprietário: um token confirmado para uma chave pessoal é recusado com qualquer outra chave.

As chaves por utilizador não devem revelar que utilizadores têm acessoChave guardada sempre lida e comparada, com um valor fictício do mesmo comprimento quando não existe

Uma chave pessoal é woobe_<40 hex>-<user id>. personal_key_owner() lê e compara sempre a chave guardada com hash_equals, usando um valor fictício do mesmo comprimento quando o utilizador não tem nenhuma, e só consulta a lista de concessões e a capability depois de uma correspondência total, de modo que o tempo de resposta não diz a um estranho quem tem acesso. Os IDs com zeros à esquerda seguem o caminho do valor fictício.

O pedido corre depois como esse utilizador através de wp_set_current_user(), de modo que o seu papel e a visibilidade de campos se aplicam exatamente como no ecrã, e a ligação de dois fatores está sempre ativa nas chaves pessoais.

Os proprietários precisam de acesso só de leitura ou nenhum por área da lojaMapa de setores full/read/none por utilizador, restringido pelas capabilities do WP; ferramentas fechadas ocultas

Cada ferramenta declara um setor e annotations.readOnlyHint. O proprietário devolve um mapa a partir do filtro woobe_mcp_permissions; '*' define o valor por defeito, pelo que funciona como lista de permissões. As entradas inválidas são registadas e ignoradas, e um '*' inválido recorre a full, tal como na ausência de callback. Para uma chave pessoal, cada setor é restringido de novo pela capability que o WooCommerce pede nesse ecrã (edit_products, edit_shop_orders, view_woocommerce_reports e outras).

As ferramentas que a identidade não pode chamar nem sequer são listadas. Uma escrita que cai noutro setor, como uma reversão que reescreve produtos, chama require_access() para esse setor antes de escrever. O filtro woobe_mcp_tool_allowed só pode restringir ainda mais. Nenhuma chave MCP, de toda a loja ou pessoal, pode escrever post_author ou ID; esses são editados no wp-admin por uma pessoa.

Números

wordpress.org
  • Instalações ativas 30 000+ contador do wordpress.org.
  • Avaliações 227 Média de 4.7 em 5.
  • Anos de suporte 8 Desde 7 de maio de 2018, última atualização em 13 de outubro de 2026.